#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(1, "b", b); } static unique_ptr Deserialize(Deserializer &deserializer) { auto result = make_uniq(); deserializer.ReadProperty(1, "b", result->b); return result; } }; struct Foo { int32_t a; unique_ptr bar; int32_t c; void Serialize(Serializer &serializer) const { serializer.WriteProperty(1, "a", a); serializer.WritePropertyWithDefault>(2, "bar", bar, unique_ptr()); serializer.WriteProperty(3, "c", c); } static unique_ptr Deserialize(Deserializer &deserializer) { auto result = make_uniq(); deserializer.ReadProperty(1, "a", result->a); deserializer.ReadPropertyWithExplicitDefault>(2, "bar", result->bar, unique_ptr()); deserializer.ReadProperty(3, "c", result->c); return result; } }; TEST_CASE("Test default values", "[serialization]") { Foo foo_in; foo_in.a = 42; foo_in.bar = make_uniq(); 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(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(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(1, "c1", c1); serializer.WriteProperty(2, "c2", c2); } static unique_ptr Deserialize(Deserializer &deserializer) { auto result = make_uniq(); deserializer.ReadProperty(1, "c1", result->c1); deserializer.ReadProperty(2, "c2", result->c2); return result; } }; struct FooV1 { int p1; vector> p2; int p3; unique_ptr p4; void Serialize(Serializer &serializer) const { serializer.WriteProperty(1, "p1", p1); serializer.WritePropertyWithDefault>>(2, "p2", p2); serializer.WriteProperty(3, "p3", p3); serializer.WriteProperty>(4, "p4", p4); } static unique_ptr Deserialize(Deserializer &deserializer) { auto result = make_uniq(); deserializer.ReadProperty(1, "p1", result->p1); deserializer.ReadPropertyWithDefault>>(2, "p2", result->p2); deserializer.ReadProperty(3, "p3", result->p3); deserializer.ReadProperty>(4, "p4", result->p4); return result; } }; struct FooV2 { int p1; /*vector> p2;*/ // In v2, this is deleted int p3; unique_ptr p4; unique_ptr p5; // In v2, this is added void Serialize(Serializer &serializer) const { serializer.WriteProperty(1, "p1", p1); // This field is deleted! /* serializer.WriteDeletedProperty>>(2, "p2"); */ serializer.WriteProperty(3, "p3", p3); serializer.WriteProperty>(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>(5, "p5", p5); } static unique_ptr Deserialize(Deserializer &deserializer) { auto result = make_uniq(); deserializer.ReadProperty(1, "p1", result->p1); deserializer.ReadDeletedProperty>>(2, "p2"); deserializer.ReadProperty(3, "p3", result->p3); deserializer.ReadProperty(4, "p4", result->p4); deserializer.ReadPropertyWithDefault>(5, "p5", result->p5); return result; } }; TEST_CASE("Test deleted values", "[serialization]") { FooV1 v1_in = {1, {}, 6, make_uniq(1, "foo")}; v1_in.p2.push_back(make_uniq(2, "3")); v1_in.p2.push_back(make_uniq(4, "5")); FooV2 v2_in = {1, 3, make_uniq(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(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(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(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(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(2, "foo"); options.serialize_default_values = false; BinarySerializer::Serialize(v2_in, stream, options); { stream.Rewind(); REQUIRE_THROWS(BinaryDeserializer::Deserialize(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(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