should be it
This commit is contained in:
4
external/duckdb/test/serialize/CMakeLists.txt
vendored
Normal file
4
external/duckdb/test/serialize/CMakeLists.txt
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
add_library_unity(test_serialization OBJECT serialization_test.cpp)
|
||||
set(ALL_OBJECT_FILES
|
||||
${ALL_OBJECT_FILES} $<TARGET_OBJECTS:test_serialization>
|
||||
PARENT_SCOPE)
|
||||
266
external/duckdb/test/serialize/serialization_test.cpp
vendored
Normal file
266
external/duckdb/test/serialize/serialization_test.cpp
vendored
Normal file
@@ -0,0 +1,266 @@
|
||||
#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
|
||||
Reference in New Issue
Block a user