An amazing project that generates micro reports from tournament results
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

831 lines
35 KiB

////////////////////////////////////////////////////////////////////////////
//
// Copyright 2016 Realm Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
////////////////////////////////////////////////////////////////////////////
#include "catch.hpp"
#include "util/test_file.hpp"
#include "object_schema.hpp"
#include "object_store.hpp"
#include "property.hpp"
#include "schema.hpp"
#include <realm/descriptor.hpp>
#include <realm/group.hpp>
#include <realm/table.hpp>
using namespace realm;
struct SchemaChangePrinter {
std::ostream& out;
template<typename Value>
void print(Value value) const
{
out << value;
}
template<typename Value, typename... Rest>
void print(Value value, Rest... rest) const
{
out << value << ", ";
print(rest...);
}
#define REALM_SC_PRINT(type, ...) \
void operator()(schema_change::type v) const \
{ \
out << #type << "{"; \
print(__VA_ARGS__); \
out << "}"; \
}
REALM_SC_PRINT(AddIndex, v.object, v.property)
REALM_SC_PRINT(AddProperty, v.object, v.property)
REALM_SC_PRINT(AddTable, v.object)
REALM_SC_PRINT(RemoveTable, v.object)
REALM_SC_PRINT(AddInitialProperties, v.object)
REALM_SC_PRINT(ChangePrimaryKey, v.object, v.property)
REALM_SC_PRINT(ChangePropertyType, v.object, v.old_property, v.new_property)
REALM_SC_PRINT(MakePropertyNullable, v.object, v.property)
REALM_SC_PRINT(MakePropertyRequired, v.object, v.property)
REALM_SC_PRINT(RemoveIndex, v.object, v.property)
REALM_SC_PRINT(RemoveProperty, v.object, v.property)
#undef REALM_SC_PRINT
};
namespace Catch {
template<>
struct StringMaker<SchemaChange> {
static std::string convert(SchemaChange const& sc)
{
std::stringstream ss;
sc.visit(SchemaChangePrinter{ss});
return ss.str();
}
};
} // namespace Catch
#define REQUIRE_THROWS_CONTAINING(expr, msg) \
REQUIRE_THROWS_WITH(expr, Catch::Matchers::Contains(msg))
TEST_CASE("ObjectSchema") {
SECTION("Aliases are still present in schema returned from the Realm") {
TestFile config;
config.schema_version = 1;
config.schema = Schema{
{"object", {
{"value", PropertyType::Int, Property::IsPrimary{false}, Property::IsIndexed{false}, "alias"}
}},
};
auto realm = Realm::get_shared_realm(config);
REQUIRE(realm->schema().find("object")->property_for_name("value")->public_name == "alias");
}
SECTION("looking up properties by alias matches name if alias is not set") {
auto schema = Schema{
{"object", {
{"value", PropertyType::Int, Property::IsPrimary{false}, Property::IsIndexed{false}, "alias"},
{"other_value", PropertyType::Int, Property::IsPrimary{false}, Property::IsIndexed{false}}
}},
};
REQUIRE(schema.find("object")->property_for_public_name("value") == nullptr);
REQUIRE(schema.find("object")->property_for_public_name("alias")->name == "value");
REQUIRE(schema.find("object")->property_for_public_name("other_value")->name == "other_value");
}
SECTION("from a Group") {
Group g;
TableRef pk = g.add_table("pk");
pk->add_column(type_String, "pk_table");
pk->add_column(type_String, "pk_property");
pk->add_empty_row();
pk->set_string(0, 0, "table");
pk->set_string(1, 0, "pk");
TableRef table = g.add_table("class_table");
TableRef target = g.add_table("class_target");
table->add_column(type_Int, "pk");
table->add_column(type_Int, "int");
table->add_column(type_Bool, "bool");
table->add_column(type_Float, "float");
table->add_column(type_Double, "double");
table->add_column(type_String, "string");
table->add_column(type_Binary, "data");
table->add_column(type_Timestamp, "date");
table->add_column_link(type_Link, "object", *target);
table->add_column_link(type_LinkList, "array", *target);
table->add_column(type_Int, "int?", true);
table->add_column(type_Bool, "bool?", true);
table->add_column(type_Float, "float?", true);
table->add_column(type_Double, "double?", true);
table->add_column(type_String, "string?", true);
table->add_column(type_Binary, "data?", true);
table->add_column(type_Timestamp, "date?", true);
table->add_column(type_Table, "subtable 1");
size_t col = table->add_column(type_Table, "subtable 2");
table->get_subdescriptor(col)->add_column(type_Int, "value");
auto add_list = [](TableRef table, DataType type, StringData name, bool nullable) {
size_t col = table->add_column(type_Table, name);
table->get_subdescriptor(col)->add_column(type, ObjectStore::ArrayColumnName, nullptr, nullable);
};
add_list(table, type_Int, "int array", false);
add_list(table, type_Bool, "bool array", false);
add_list(table, type_Float, "float array", false);
add_list(table, type_Double, "double array", false);
add_list(table, type_String, "string array", false);
add_list(table, type_Binary, "data array", false);
add_list(table, type_Timestamp, "date array", false);
add_list(table, type_Int, "int? array", true);
add_list(table, type_Bool, "bool? array", true);
add_list(table, type_Float, "float? array", true);
add_list(table, type_Double, "double? array", true);
add_list(table, type_String, "string? array", true);
add_list(table, type_Binary, "data? array", true);
add_list(table, type_Timestamp, "date? array", true);
size_t indexed_start = table->get_column_count();
table->add_column(type_Int, "indexed int");
table->add_column(type_Bool, "indexed bool");
table->add_column(type_String, "indexed string");
table->add_column(type_Timestamp, "indexed date");
table->add_column(type_Int, "indexed int?", true);
table->add_column(type_Bool, "indexed bool?", true);
table->add_column(type_String, "indexed string?", true);
table->add_column(type_Timestamp, "indexed date?", true);
for (size_t i = indexed_start; i < table->get_column_count(); ++i)
table->add_search_index(i);
ObjectSchema os(g, "table");
#define REQUIRE_PROPERTY(name, type, ...) do { \
Property* prop; \
REQUIRE((prop = os.property_for_name(name))); \
REQUIRE((*prop == Property{name, PropertyType::type, __VA_ARGS__})); \
REQUIRE(prop->table_column == expected_col++); \
} while (0)
size_t expected_col = 0;
REQUIRE(os.property_for_name("nonexistent property") == nullptr);
REQUIRE_PROPERTY("pk", Int, Property::IsPrimary{true});
REQUIRE_PROPERTY("int", Int);
REQUIRE_PROPERTY("bool", Bool);
REQUIRE_PROPERTY("float", Float);
REQUIRE_PROPERTY("double", Double);
REQUIRE_PROPERTY("string", String);
REQUIRE_PROPERTY("data", Data);
REQUIRE_PROPERTY("date", Date);
REQUIRE_PROPERTY("object", Object|PropertyType::Nullable, "target");
REQUIRE_PROPERTY("array", Array|PropertyType::Object, "target");
REQUIRE_PROPERTY("int?", Int|PropertyType::Nullable);
REQUIRE_PROPERTY("bool?", Bool|PropertyType::Nullable);
REQUIRE_PROPERTY("float?", Float|PropertyType::Nullable);
REQUIRE_PROPERTY("double?", Double|PropertyType::Nullable);
REQUIRE_PROPERTY("string?", String|PropertyType::Nullable);
REQUIRE_PROPERTY("data?", Data|PropertyType::Nullable);
REQUIRE_PROPERTY("date?", Date|PropertyType::Nullable);
// Unsupported column type should be skipped entirely
REQUIRE(os.property_for_name("subtable 1") == nullptr);
REQUIRE(os.property_for_name("subtable 2") == nullptr);
expected_col += 2;
REQUIRE_PROPERTY("int array", Int|PropertyType::Array);
REQUIRE_PROPERTY("bool array", Bool|PropertyType::Array);
REQUIRE_PROPERTY("float array", Float|PropertyType::Array);
REQUIRE_PROPERTY("double array", Double|PropertyType::Array);
REQUIRE_PROPERTY("string array", String|PropertyType::Array);
REQUIRE_PROPERTY("data array", Data|PropertyType::Array);
REQUIRE_PROPERTY("date array", Date|PropertyType::Array);
REQUIRE_PROPERTY("int? array", Int|PropertyType::Array|PropertyType::Nullable);
REQUIRE_PROPERTY("bool? array", Bool|PropertyType::Array|PropertyType::Nullable);
REQUIRE_PROPERTY("float? array", Float|PropertyType::Array|PropertyType::Nullable);
REQUIRE_PROPERTY("double? array", Double|PropertyType::Array|PropertyType::Nullable);
REQUIRE_PROPERTY("string? array", String|PropertyType::Array|PropertyType::Nullable);
REQUIRE_PROPERTY("data? array", Data|PropertyType::Array|PropertyType::Nullable);
REQUIRE_PROPERTY("date? array", Date|PropertyType::Array|PropertyType::Nullable);
REQUIRE_PROPERTY("indexed int", Int, Property::IsPrimary{false}, Property::IsIndexed{true});
REQUIRE_PROPERTY("indexed bool", Bool, Property::IsPrimary{false}, Property::IsIndexed{true});
REQUIRE_PROPERTY("indexed string", String, Property::IsPrimary{false}, Property::IsIndexed{true});
REQUIRE_PROPERTY("indexed date", Date, Property::IsPrimary{false}, Property::IsIndexed{true});
REQUIRE_PROPERTY("indexed int?", Int|PropertyType::Nullable, Property::IsPrimary{false}, Property::IsIndexed{true});
REQUIRE_PROPERTY("indexed bool?", Bool|PropertyType::Nullable, Property::IsPrimary{false}, Property::IsIndexed{true});
REQUIRE_PROPERTY("indexed string?", String|PropertyType::Nullable, Property::IsPrimary{false}, Property::IsIndexed{true});
REQUIRE_PROPERTY("indexed date?", Date|PropertyType::Nullable, Property::IsPrimary{false}, Property::IsIndexed{true});
pk->set_string(1, 0, "nonexistent property");
REQUIRE(ObjectSchema(g, "table").primary_key_property() == nullptr);
}
}
TEST_CASE("Schema") {
SECTION("validate()") {
SECTION("rejects link properties with no target object") {
Schema schema = {
{"object", {
{"link", PropertyType::Object|PropertyType::Nullable}
}},
};
REQUIRE_THROWS_CONTAINING(schema.validate(), "Property 'object.link' of type 'object' has unknown object type ''");
}
SECTION("rejects array properties with no target object") {
Schema schema = {
{"object", {
{"array", PropertyType::Array|PropertyType::Object}
}},
};
REQUIRE_THROWS_CONTAINING(schema.validate(), "Property 'object.array' of type 'array' has unknown object type ''");
}
SECTION("rejects link properties with a target not in the schema") {
Schema schema = {
{"object", {
{"link", PropertyType::Object|PropertyType::Nullable, "invalid target"}
}}
};
REQUIRE_THROWS_CONTAINING(schema.validate(), "Property 'object.link' of type 'object' has unknown object type 'invalid target'");
}
SECTION("rejects array properties with a target not in the schema") {
Schema schema = {
{"object", {
{"array", PropertyType::Array|PropertyType::Object, "invalid target"}
}}
};
REQUIRE_THROWS_CONTAINING(schema.validate(), "Property 'object.array' of type 'array' has unknown object type 'invalid target'");
}
SECTION("rejects linking objects without a source object") {
Schema schema = {
{"object", {
{"value", PropertyType::Int},
}, {
{"incoming", PropertyType::Array|PropertyType::LinkingObjects, "", ""}
}}
};
REQUIRE_THROWS_CONTAINING(schema.validate(), "Property 'object.incoming' of type 'linking objects' has unknown object type ''");
}
SECTION("rejects linking objects without a source property") {
Schema schema = {
{"object", {
{"value", PropertyType::Int},
}, {
{"incoming", PropertyType::Array|PropertyType::LinkingObjects, "object", ""}
}}
};
REQUIRE_THROWS_CONTAINING(schema.validate(), "Property 'object.incoming' of type 'linking objects' must have an origin property name.");
}
SECTION("rejects linking objects with invalid source object") {
Schema schema = {
{"object", {
{"value", PropertyType::Int},
}, {
{"incoming", PropertyType::Array|PropertyType::LinkingObjects, "not an object type", ""}
}}
};
REQUIRE_THROWS_CONTAINING(schema.validate(), "Property 'object.incoming' of type 'linking objects' has unknown object type 'not an object type'");
}
SECTION("rejects linking objects with invalid source property") {
Schema schema = {
{"object", {
{"value", PropertyType::Int},
}, {
{"incoming", PropertyType::Array|PropertyType::LinkingObjects, "object", "value"}
}}
};
REQUIRE_THROWS_CONTAINING(schema.validate(), "Property 'object.value' declared as origin of linking objects property 'object.incoming' is not a link");
schema = {
{"object", {
{"value", PropertyType::Int},
{"link", PropertyType::Object|PropertyType::Nullable, "object 2"},
}, {
{"incoming", PropertyType::Array|PropertyType::LinkingObjects, "object", "link"}
}},
{"object 2", {
{"value", PropertyType::Int},
}}
};
REQUIRE_THROWS_CONTAINING(schema.validate(), "Property 'object.link' declared as origin of linking objects property 'object.incoming' links to type 'object 2'");
}
SECTION("rejects non-array linking objects") {
Schema schema = {
{"object", {
{"link", PropertyType::Object|PropertyType::Nullable, "object"},
}, {
{"incoming", PropertyType::LinkingObjects, "object", "link"}
}}
};
REQUIRE_THROWS_CONTAINING(schema.validate(), "Linking Objects property 'object.incoming' must be an array.");
}
SECTION("rejects target object types for non-link properties") {
Schema schema = {
{"object", {
{"int", PropertyType::Int},
{"bool", PropertyType::Bool},
{"float", PropertyType::Float},
{"double", PropertyType::Double},
{"string", PropertyType::String},
{"date", PropertyType::Date},
}}
};
for (auto& prop : schema.begin()->persisted_properties) {
REQUIRE_NOTHROW(schema.validate());
prop.object_type = "object";
REQUIRE_THROWS_CONTAINING(schema.validate(), "cannot have an object type.");
prop.object_type = "";
}
}
SECTION("rejects source property name for non-linking objects properties") {
Schema schema = {
{"object", {
{"int", PropertyType::Int},
{"bool", PropertyType::Bool},
{"float", PropertyType::Float},
{"double", PropertyType::Double},
{"string", PropertyType::String},
{"data", PropertyType::Data},
{"date", PropertyType::Date},
{"object", PropertyType::Object|PropertyType::Nullable, "object"},
{"array", PropertyType::Object|PropertyType::Array, "object"},
}}
};
for (auto& prop : schema.begin()->persisted_properties) {
REQUIRE_NOTHROW(schema.validate());
prop.link_origin_property_name = "source";
auto expected = util::format("Property 'object.%1' of type '%1' cannot have an origin property name.", prop.name, prop.name);
REQUIRE_THROWS_CONTAINING(schema.validate(), expected);
prop.link_origin_property_name = "";
}
}
SECTION("rejects non-nullable link properties") {
Schema schema = {
{"object", {
{"link", PropertyType::Object, "target"}
}},
{"target", {
{"value", PropertyType::Int}
}}
};
REQUIRE_THROWS_CONTAINING(schema.validate(), "Property 'object.link' of type 'object' must be nullable.");
}
SECTION("rejects nullable array properties") {
Schema schema = {
{"object", {
{"array", PropertyType::Array|PropertyType::Object|PropertyType::Nullable, "target"}
}},
{"target", {
{"value", PropertyType::Int}
}}
};
REQUIRE_THROWS_CONTAINING(schema.validate(), "Property 'object.array' of type 'array' cannot be nullable.");
}
SECTION("rejects nullable linking objects") {
Schema schema = {
{"object", {
{"link", PropertyType::Object|PropertyType::Nullable, "object"},
}, {
{"incoming", PropertyType::LinkingObjects|PropertyType::Array|PropertyType::Nullable, "object", "link"}
}}
};
REQUIRE_THROWS_CONTAINING(schema.validate(), "Property 'object.incoming' of type 'linking objects' cannot be nullable.");
}
SECTION("rejects duplicate primary keys") {
Schema schema = {
{"object", {
{"pk1", PropertyType::Int, Property::IsPrimary{true}},
{"pk2", PropertyType::Int, Property::IsPrimary{true}},
}}
};
REQUIRE_THROWS_CONTAINING(schema.validate(), "Properties 'pk2' and 'pk1' are both marked as the primary key of 'object'.");
}
SECTION("rejects invalid primary key types") {
Schema schema = {
{"object", {
{"pk", PropertyType::Float, Property::IsPrimary{true}},
}}
};
schema.begin()->primary_key_property()->type = PropertyType::Any;
REQUIRE_THROWS_CONTAINING(schema.validate(), "Property 'object.pk' of type 'any' cannot be made the primary key.");
schema.begin()->primary_key_property()->type = PropertyType::Bool;
REQUIRE_THROWS_CONTAINING(schema.validate(), "Property 'object.pk' of type 'bool' cannot be made the primary key.");
schema.begin()->primary_key_property()->type = PropertyType::Float;
REQUIRE_THROWS_CONTAINING(schema.validate(), "Property 'object.pk' of type 'float' cannot be made the primary key.");
schema.begin()->primary_key_property()->type = PropertyType::Double;
REQUIRE_THROWS_CONTAINING(schema.validate(), "Property 'object.pk' of type 'double' cannot be made the primary key.");
schema.begin()->primary_key_property()->type = PropertyType::Object;
REQUIRE_THROWS_CONTAINING(schema.validate(), "Property 'object.pk' of type 'object' cannot be made the primary key.");
schema.begin()->primary_key_property()->type = PropertyType::LinkingObjects;
REQUIRE_THROWS_CONTAINING(schema.validate(), "Property 'object.pk' of type 'linking objects' cannot be made the primary key.");
schema.begin()->primary_key_property()->type = PropertyType::Data;
REQUIRE_THROWS_CONTAINING(schema.validate(), "Property 'object.pk' of type 'data' cannot be made the primary key.");
schema.begin()->primary_key_property()->type = PropertyType::Date;
REQUIRE_THROWS_CONTAINING(schema.validate(), "Property 'object.pk' of type 'date' cannot be made the primary key.");
}
SECTION("allows valid primary key types") {
Schema schema = {
{"object", {
{"pk", PropertyType::Int, Property::IsPrimary{true}},
}}
};
REQUIRE_NOTHROW(schema.validate());
schema.begin()->primary_key_property()->type = PropertyType::Int|PropertyType::Nullable;
REQUIRE_NOTHROW(schema.validate());
schema.begin()->primary_key_property()->type = PropertyType::String;
REQUIRE_NOTHROW(schema.validate());
schema.begin()->primary_key_property()->type = PropertyType::String|PropertyType::Nullable;
REQUIRE_NOTHROW(schema.validate());
}
SECTION("rejects nonexistent primary key") {
Schema schema = {
{"object", {
{"value", PropertyType::Int},
}}
};
schema.begin()->primary_key = "nonexistent";
REQUIRE_THROWS_CONTAINING(schema.validate(), "Specified primary key 'object.nonexistent' does not exist.");
}
SECTION("rejects indexes for types that cannot be indexed") {
Schema schema = {
{"object", {
{"float", PropertyType::Float},
{"double", PropertyType::Double},
{"data", PropertyType::Data},
{"object", PropertyType::Object|PropertyType::Nullable, "object"},
{"array", PropertyType::Array|PropertyType::Object, "object"},
}}
};
for (auto& prop : schema.begin()->persisted_properties) {
REQUIRE_NOTHROW(schema.validate());
prop.is_indexed = true;
auto expected = util::format("Property 'object.%1' of type '%1' cannot be indexed.", prop.name);
REQUIRE_THROWS_CONTAINING(schema.validate(), expected);
prop.is_indexed = false;
}
}
SECTION("allows indexing types that can be indexed") {
Schema schema = {
{"object", {
{"int", PropertyType::Int, Property::IsPrimary{false}, Property::IsIndexed{true}},
{"bool", PropertyType::Bool, Property::IsPrimary{false}, Property::IsIndexed{true}},
{"string", PropertyType::String, Property::IsPrimary{false}, Property::IsIndexed{true}},
{"date", PropertyType::Date, Property::IsPrimary{false}, Property::IsIndexed{true}},
}}
};
REQUIRE_NOTHROW(schema.validate());
}
SECTION("rejects duplicate types with the same name") {
Schema schema = {
{"object1", {
{"int", PropertyType::Int},
}},
{"object2", {
{"int", PropertyType::Int},
}},
{"object3", {
{"int", PropertyType::Int},
}},
{"object2", {
{"int", PropertyType::Int},
}},
{"object1", {
{"int", PropertyType::Int},
}}
};
REQUIRE_THROWS_CONTAINING(schema.validate(),
"- Type 'object1' appears more than once in the schema.\n"
"- Type 'object2' appears more than once in the schema.");
}
SECTION("rejects properties with the same name") {
Schema schema = {
{"object", {
{"child", PropertyType::Object|PropertyType::Nullable, "object"},
{"parent", PropertyType::Int},
{"field1", PropertyType::Int},
{"field2", PropertyType::String},
{"field1", PropertyType::String},
{"field2", PropertyType::String},
{"field1", PropertyType::Int},
}, {
{"parent", PropertyType::Array|PropertyType::LinkingObjects, "object", "child"}
}}
};
REQUIRE_THROWS_CONTAINING(schema.validate(),
"- Property 'field1' appears more than once in the schema for type 'object'.\n"
"- Property 'field2' appears more than once in the schema for type 'object'.\n"
"- Property 'parent' appears more than once in the schema for type 'object'.");
}
SECTION("rejects schema if all properties have the same name") {
Schema schema = {
{"object", {
{"field", PropertyType::Int},
{"otherField", PropertyType::Int},
{"field", PropertyType::Int},
{"otherField", PropertyType::Int},
{"field", PropertyType::Int},
{"otherField", PropertyType::Int},
{"field", PropertyType::Int},
{"otherField", PropertyType::Int},
{"field", PropertyType::Int},
{"otherField", PropertyType::Int},
}}
};
REQUIRE_THROWS_CONTAINING(schema.validate(),
"- Property 'field' appears more than once in the schema for type 'object'.\n"
"- Property 'otherField' appears more than once in the schema for type 'object'.");
}
SECTION("rejects properties with the same alias") {
Schema schema = {
{"object", {
{"child", PropertyType::Object|PropertyType::Nullable, "object"},
// Alias == Name on computed property
{"parentA", PropertyType::Int, Property::IsPrimary{false}, Property::IsIndexed{false}, "_parent"},
// Name == Alias on other property
{"fieldA", PropertyType::Int, Property::IsPrimary{false}, Property::IsIndexed{false}, "_field1"},
{"fieldB", PropertyType::String, Property::IsPrimary{false}, Property::IsIndexed{false}, "_field2"},
{"fieldC", PropertyType::String, Property::IsPrimary{false}, Property::IsIndexed{false}, "_field1"},
{"fieldD", PropertyType::String, Property::IsPrimary{false}, Property::IsIndexed{false}, "_field2"},
{"fieldE", PropertyType::Int, Property::IsPrimary{false}, Property::IsIndexed{false}, "_field1"},
// Name == Alias
{"fieldF", PropertyType::Int, Property::IsPrimary{false}, Property::IsIndexed{false}, "fieldF"},
}, {
// Computed property alias == name on persisted property
{"parentB", PropertyType::Array|PropertyType::LinkingObjects, "object", "child", "_parent"}
}}
};
REQUIRE_THROWS_CONTAINING(schema.validate(),
"- Alias '_field1' appears more than once in the schema for type 'object'.\n"
"- Alias '_field2' appears more than once in the schema for type 'object'.\n"
"- Alias '_parent' appears more than once in the schema for type 'object'.");
}
SECTION("rejects properties whose name conflicts with an alias for another property") {
Schema schema = {
{"object", {
{"child", PropertyType::Object|PropertyType::Nullable, "object"},
{"field1", PropertyType::Int, Property::IsPrimary{false}, Property::IsIndexed{false}, "field2"},
{"field2", PropertyType::Int, Property::IsPrimary{false}, Property::IsIndexed{false}, "parent"},
}, {
{"parent", PropertyType::Array|PropertyType::LinkingObjects, "object", "child", "field1"}
}}
};
REQUIRE_THROWS_CONTAINING(schema.validate(),
"- Property 'object.parent' has an alias 'field1' that conflicts with a property of the same name.\n"
"- Property 'object.field1' has an alias 'field2' that conflicts with a property of the same name.\n"
"- Property 'object.field2' has an alias 'parent' that conflicts with a property of the same name.");
}
}
SECTION("compare()") {
using namespace schema_change;
using vec = std::vector<SchemaChange>;
SECTION("add table") {
Schema schema1 = {
{"object 1", {
{"int", PropertyType::Int},
}}
};
Schema schema2 = {
{"object 1", {
{"int", PropertyType::Int},
}},
{"object 2", {
{"int", PropertyType::Int},
}}
};
auto obj = &*schema2.find("object 2");
auto expected = vec{AddTable{obj}, AddInitialProperties{obj}};
REQUIRE(schema1.compare(schema2) == expected);
}
SECTION("add property") {
Schema schema1 = {
{"object", {
{"int 1", PropertyType::Int},
}}
};
Schema schema2 = {
{"object", {
{"int 1", PropertyType::Int},
{"int 2", PropertyType::Int},
}}
};
REQUIRE(schema1.compare(schema2) == vec{(AddProperty{&*schema1.find("object"), &schema2.find("object")->persisted_properties[1]})});
}
SECTION("remove property") {
Schema schema1 = {
{"object", {
{"int 1", PropertyType::Int},
{"int 2", PropertyType::Int},
}}
};
Schema schema2 = {
{"object", {
{"int 1", PropertyType::Int},
}}
};
REQUIRE(schema1.compare(schema2) == vec{(RemoveProperty{&*schema1.find("object"), &schema1.find("object")->persisted_properties[1]})});
}
SECTION("change property type") {
Schema schema1 = {
{"object", {
{"value", PropertyType::Int},
}}
};
Schema schema2 = {
{"object", {
{"value", PropertyType::Double},
}}
};
REQUIRE(schema1.compare(schema2) == vec{(ChangePropertyType{
&*schema1.find("object"),
&schema1.find("object")->persisted_properties[0],
&schema2.find("object")->persisted_properties[0]})});
};
SECTION("change link target") {
Schema schema1 = {
{"object", {
{"value", PropertyType::Object, "target 1"},
}},
{"target 1", {
{"value", PropertyType::Int},
}},
{"target 2", {
{"value", PropertyType::Int},
}},
};
Schema schema2 = {
{"object", {
{"value", PropertyType::Object, "target 2"},
}},
{"target 1", {
{"value", PropertyType::Int},
}},
{"target 2", {
{"value", PropertyType::Int},
}},
};
REQUIRE(schema1.compare(schema2) == vec{(ChangePropertyType{
&*schema1.find("object"),
&schema1.find("object")->persisted_properties[0],
&schema2.find("object")->persisted_properties[0]})});
}
SECTION("add index") {
Schema schema1 = {
{"object", {
{"int", PropertyType::Int},
}}
};
Schema schema2 = {
{"object", {
{"int", PropertyType::Int, Property::IsPrimary{false}, Property::IsIndexed{true}},
}}
};
auto object_schema = &*schema1.find("object");
REQUIRE(schema1.compare(schema2) == vec{(AddIndex{object_schema, &object_schema->persisted_properties[0]})});
}
SECTION("remove index") {
Schema schema1 = {
{"object", {
{"int", PropertyType::Int, Property::IsPrimary{false}, Property::IsIndexed{true}},
}}
};
Schema schema2 = {
{"object", {
{"int", PropertyType::Int},
}}
};
auto object_schema = &*schema1.find("object");
REQUIRE(schema1.compare(schema2) == vec{(RemoveIndex{object_schema, &object_schema->persisted_properties[0]})});
}
SECTION("add index and make nullable") {
Schema schema1 = {
{"object", {
{"int", PropertyType::Int},
}}
};
Schema schema2 = {
{"object", {
{"int", PropertyType::Int|PropertyType::Nullable, Property::IsPrimary{false}, Property::IsIndexed{true}},
}}
};
auto object_schema = &*schema1.find("object");
REQUIRE(schema1.compare(schema2) == (vec{
MakePropertyNullable{object_schema, &object_schema->persisted_properties[0]},
AddIndex{object_schema, &object_schema->persisted_properties[0]}}));
}
SECTION("add index and change type") {
Schema schema1 = {
{"object", {
{"value", PropertyType::Int},
}}
};
Schema schema2 = {
{"object", {
{"value", PropertyType::Double, Property::IsPrimary{false}, Property::IsIndexed{true}},
}}
};
REQUIRE(schema1.compare(schema2) == vec{(ChangePropertyType{
&*schema1.find("object"),
&schema1.find("object")->persisted_properties[0],
&schema2.find("object")->persisted_properties[0]})});
}
SECTION("make nullable and change type") {
Schema schema1 = {
{"object", {
{"value", PropertyType::Int},
}}
};
Schema schema2 = {
{"object", {
{"value", PropertyType::Double|PropertyType::Nullable},
}}
};
REQUIRE(schema1.compare(schema2) == vec{(ChangePropertyType{
&*schema1.find("object"),
&schema1.find("object")->persisted_properties[0],
&schema2.find("object")->persisted_properties[0]})});
}
}
}