Files
email-tracker/external/duckdb/test/serialize/serialization_test.cpp
2025-10-24 19:21:19 -05:00

267 lines
8.1 KiB
C++

#include "catch.hpp"
#include "duckdb/common/serializer/binary_deserializer.hpp"
#include "duckdb/common/serializer/binary_serializer.hpp"
#include "duckdb/common/serializer/memory_stream.hpp"
namespace duckdb {
struct Bar {
uint32_t b;
void Serialize(Serializer &serializer) const {
serializer.WriteProperty<uint32_t>(1, "b", b);
}
static unique_ptr<Bar> Deserialize(Deserializer &deserializer) {
auto result = make_uniq<Bar>();
deserializer.ReadProperty<uint32_t>(1, "b", result->b);
return result;
}
};
struct Foo {
int32_t a;
unique_ptr<Bar> bar;
int32_t c;
void Serialize(Serializer &serializer) const {
serializer.WriteProperty<int32_t>(1, "a", a);
serializer.WritePropertyWithDefault<unique_ptr<Bar>>(2, "bar", bar, unique_ptr<Bar>());
serializer.WriteProperty<int32_t>(3, "c", c);
}
static unique_ptr<Foo> Deserialize(Deserializer &deserializer) {
auto result = make_uniq<Foo>();
deserializer.ReadProperty<int32_t>(1, "a", result->a);
deserializer.ReadPropertyWithExplicitDefault<unique_ptr<Bar>>(2, "bar", result->bar, unique_ptr<Bar>());
deserializer.ReadProperty<int32_t>(3, "c", result->c);
return result;
}
};
TEST_CASE("Test default values", "[serialization]") {
Foo foo_in;
foo_in.a = 42;
foo_in.bar = make_uniq<Bar>();
foo_in.bar->b = 43;
foo_in.c = 44;
Allocator allocator;
MemoryStream stream(allocator);
SerializationOptions options;
options.serialize_default_values = false;
BinarySerializer::Serialize(foo_in, stream, options);
auto pos1 = stream.GetPosition();
stream.Rewind();
auto foo_out_ptr = BinaryDeserializer::Deserialize<Foo>(stream);
auto &foo_out = *foo_out_ptr.get();
REQUIRE(foo_in.a == foo_out.a);
REQUIRE(foo_in.bar->b == foo_out.bar->b);
REQUIRE(foo_in.c == foo_out.c);
// Now try with a default value
foo_in.bar = nullptr;
stream.Rewind();
options.serialize_default_values = false;
BinarySerializer::Serialize(foo_in, stream, options);
auto pos2 = stream.GetPosition();
stream.Rewind();
foo_out_ptr = BinaryDeserializer::Deserialize<Foo>(stream);
auto &foo_out2 = *foo_out_ptr.get();
REQUIRE(foo_in.a == foo_out2.a);
REQUIRE(foo_out2.bar == nullptr);
REQUIRE(foo_in.c == foo_out2.c);
// We should not have written the default value
REQUIRE(pos1 > pos2);
}
//------------------------------------------------------
// Test deleted properties
//------------------------------------------------------
struct Complex {
int c1;
string c2;
Complex(int c1, string c2) : c1(c1), c2(c2) {
}
Complex() : c1(0), c2("") {
}
void Serialize(Serializer &serializer) const {
serializer.WriteProperty<int>(1, "c1", c1);
serializer.WriteProperty<string>(2, "c2", c2);
}
static unique_ptr<Complex> Deserialize(Deserializer &deserializer) {
auto result = make_uniq<Complex>();
deserializer.ReadProperty<int>(1, "c1", result->c1);
deserializer.ReadProperty<string>(2, "c2", result->c2);
return result;
}
};
struct FooV1 {
int p1;
vector<unique_ptr<Complex>> p2;
int p3;
unique_ptr<Complex> p4;
void Serialize(Serializer &serializer) const {
serializer.WriteProperty<int>(1, "p1", p1);
serializer.WritePropertyWithDefault<vector<unique_ptr<Complex>>>(2, "p2", p2);
serializer.WriteProperty<int>(3, "p3", p3);
serializer.WriteProperty<unique_ptr<Complex>>(4, "p4", p4);
}
static unique_ptr<FooV1> Deserialize(Deserializer &deserializer) {
auto result = make_uniq<FooV1>();
deserializer.ReadProperty<int>(1, "p1", result->p1);
deserializer.ReadPropertyWithDefault<vector<unique_ptr<Complex>>>(2, "p2", result->p2);
deserializer.ReadProperty<int>(3, "p3", result->p3);
deserializer.ReadProperty<unique_ptr<Complex>>(4, "p4", result->p4);
return result;
}
};
struct FooV2 {
int p1;
/*vector<unique_ptr<Complex>> p2;*/ // In v2, this is deleted
int p3;
unique_ptr<Complex> p4;
unique_ptr<Complex> p5; // In v2, this is added
void Serialize(Serializer &serializer) const {
serializer.WriteProperty<int>(1, "p1", p1);
// This field is deleted!
/* serializer.WriteDeletedProperty<vector<unique_ptr<Complex>>>(2, "p2"); */
serializer.WriteProperty<int>(3, "p3", p3);
serializer.WriteProperty<unique_ptr<Complex>>(4, "p4", p4);
// Because this is a new field, we have to provide a default value
// to try to preserve backwards compatibility (in best case)
serializer.WritePropertyWithDefault<unique_ptr<Complex>>(5, "p5", p5);
}
static unique_ptr<FooV2> Deserialize(Deserializer &deserializer) {
auto result = make_uniq<FooV2>();
deserializer.ReadProperty(1, "p1", result->p1);
deserializer.ReadDeletedProperty<vector<unique_ptr<Complex>>>(2, "p2");
deserializer.ReadProperty(3, "p3", result->p3);
deserializer.ReadProperty(4, "p4", result->p4);
deserializer.ReadPropertyWithDefault<unique_ptr<Complex>>(5, "p5", result->p5);
return result;
}
};
TEST_CASE("Test deleted values", "[serialization]") {
FooV1 v1_in = {1, {}, 6, make_uniq<Complex>(1, "foo")};
v1_in.p2.push_back(make_uniq<Complex>(2, "3"));
v1_in.p2.push_back(make_uniq<Complex>(4, "5"));
FooV2 v2_in = {1, 3, make_uniq<Complex>(1, "foo"), nullptr};
Allocator allocator;
MemoryStream stream(allocator);
SerializationOptions options;
options.serialize_default_values = false;
// First of, sanity check that foov1 <-> foov1 works
BinarySerializer::Serialize(v1_in, stream, options);
{
stream.Rewind();
auto v1_out_ptr = BinaryDeserializer::Deserialize<FooV1>(stream);
auto &v1_out = *v1_out_ptr.get();
REQUIRE(v1_in.p1 == v1_out.p1);
REQUIRE(v1_in.p2.size() == v1_out.p2.size());
REQUIRE(v1_in.p2[0]->c1 == v1_out.p2[0]->c1);
REQUIRE(v1_in.p2[0]->c2 == v1_out.p2[0]->c2);
REQUIRE(v1_in.p2[1]->c1 == v1_out.p2[1]->c1);
REQUIRE(v1_in.p2[1]->c2 == v1_out.p2[1]->c2);
REQUIRE(v1_in.p3 == v1_out.p3);
REQUIRE(v1_in.p4->c1 == v1_out.p4->c1);
REQUIRE(v1_in.p4->c2 == v1_out.p4->c2);
}
stream.Rewind();
// Also check that foov2 <-> foov2 works
options.serialize_default_values = false;
BinarySerializer::Serialize(v2_in, stream, options);
{
stream.Rewind();
auto v2_out_ptr = BinaryDeserializer::Deserialize<FooV2>(stream);
auto &v2_out = *v2_out_ptr.get();
REQUIRE(v2_in.p1 == v2_out.p1);
REQUIRE(v2_in.p3 == v2_out.p3);
REQUIRE(v2_in.p4->c1 == v2_out.p4->c1);
REQUIRE(v2_in.p4->c2 == v2_out.p4->c2);
REQUIRE(v2_in.p5 == v2_out.p5);
}
// Check that foov1 -> foov2 works (backwards compatible)
stream.Rewind();
options.serialize_default_values = false;
BinarySerializer::Serialize(v1_in, stream, options);
{
stream.Rewind();
auto v2_out_ptr = BinaryDeserializer::Deserialize<FooV2>(stream);
auto &v2_out = *v2_out_ptr.get();
REQUIRE(v1_in.p1 == v2_out.p1);
REQUIRE(v1_in.p3 == v2_out.p3);
REQUIRE(v1_in.p4->c1 == v2_out.p4->c1);
REQUIRE(v1_in.p4->c2 == v2_out.p4->c2);
REQUIRE(v2_out.p5 == nullptr);
}
// Check that foov2 -> foov1 works (forwards compatible)
// This should be ok, since the property we deleted was optional (had a default value)
stream.Rewind();
options.serialize_default_values = false;
BinarySerializer::Serialize(v2_in, stream, options);
{
stream.Rewind();
auto v1_out_ptr = BinaryDeserializer::Deserialize<FooV1>(stream);
auto &v1_out = *v1_out_ptr.get();
REQUIRE(v2_in.p1 == v1_out.p1);
REQUIRE(v2_in.p3 == v1_out.p3);
REQUIRE(v2_in.p4->c1 == v1_out.p4->c1);
REQUIRE(v2_in.p4->c2 == v1_out.p4->c2);
REQUIRE(v1_out.p2.empty());
}
// If we change the new value in foov2 to something thats not the default, we break forwards compatibility.
// But thats life. Tough shit.
stream.Rewind();
v2_in.p5 = make_uniq<Complex>(2, "foo");
options.serialize_default_values = false;
BinarySerializer::Serialize(v2_in, stream, options);
{
stream.Rewind();
REQUIRE_THROWS(BinaryDeserializer::Deserialize<FooV1>(stream));
}
// However, the new value should be read correctly!
stream.Rewind();
options.serialize_default_values = false;
BinarySerializer::Serialize(v2_in, stream, options);
{
stream.Rewind();
auto v2_out_ptr = BinaryDeserializer::Deserialize<FooV2>(stream);
auto &v2_out = *v2_out_ptr.get();
REQUIRE(v2_in.p1 == v2_out.p1);
REQUIRE(v2_in.p3 == v2_out.p3);
REQUIRE(v2_in.p4->c1 == v2_out.p4->c1);
REQUIRE(v2_in.p4->c2 == v2_out.p4->c2);
REQUIRE(v2_out.p5->c1 == 2);
REQUIRE(v2_out.p5->c2 == "foo");
}
}
} // namespace duckdb