schema.cpp 35 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831
  1. ////////////////////////////////////////////////////////////////////////////
  2. //
  3. // Copyright 2016 Realm Inc.
  4. //
  5. // Licensed under the Apache License, Version 2.0 (the "License");
  6. // you may not use this file except in compliance with the License.
  7. // You may obtain a copy of the License at
  8. //
  9. // http://www.apache.org/licenses/LICENSE-2.0
  10. //
  11. // Unless required by applicable law or agreed to in writing, software
  12. // distributed under the License is distributed on an "AS IS" BASIS,
  13. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. // See the License for the specific language governing permissions and
  15. // limitations under the License.
  16. //
  17. ////////////////////////////////////////////////////////////////////////////
  18. #include "catch2/catch.hpp"
  19. #include "util/test_file.hpp"
  20. #include "object_schema.hpp"
  21. #include "object_store.hpp"
  22. #include "property.hpp"
  23. #include "schema.hpp"
  24. #include <realm/descriptor.hpp>
  25. #include <realm/group.hpp>
  26. #include <realm/table.hpp>
  27. using namespace realm;
  28. struct SchemaChangePrinter {
  29. std::ostream& out;
  30. template<typename Value>
  31. void print(Value value) const
  32. {
  33. out << value;
  34. }
  35. template<typename Value, typename... Rest>
  36. void print(Value value, Rest... rest) const
  37. {
  38. out << value << ", ";
  39. print(rest...);
  40. }
  41. #define REALM_SC_PRINT(type, ...) \
  42. void operator()(schema_change::type v) const \
  43. { \
  44. out << #type << "{"; \
  45. print(__VA_ARGS__); \
  46. out << "}"; \
  47. }
  48. REALM_SC_PRINT(AddIndex, v.object, v.property)
  49. REALM_SC_PRINT(AddProperty, v.object, v.property)
  50. REALM_SC_PRINT(AddTable, v.object)
  51. REALM_SC_PRINT(RemoveTable, v.object)
  52. REALM_SC_PRINT(AddInitialProperties, v.object)
  53. REALM_SC_PRINT(ChangePrimaryKey, v.object, v.property)
  54. REALM_SC_PRINT(ChangePropertyType, v.object, v.old_property, v.new_property)
  55. REALM_SC_PRINT(MakePropertyNullable, v.object, v.property)
  56. REALM_SC_PRINT(MakePropertyRequired, v.object, v.property)
  57. REALM_SC_PRINT(RemoveIndex, v.object, v.property)
  58. REALM_SC_PRINT(RemoveProperty, v.object, v.property)
  59. #undef REALM_SC_PRINT
  60. };
  61. namespace Catch {
  62. template<>
  63. struct StringMaker<SchemaChange> {
  64. static std::string convert(SchemaChange const& sc)
  65. {
  66. std::stringstream ss;
  67. sc.visit(SchemaChangePrinter{ss});
  68. return ss.str();
  69. }
  70. };
  71. } // namespace Catch
  72. #define REQUIRE_THROWS_CONTAINING(expr, msg) \
  73. REQUIRE_THROWS_WITH(expr, Catch::Matchers::Contains(msg))
  74. TEST_CASE("ObjectSchema") {
  75. SECTION("Aliases are still present in schema returned from the Realm") {
  76. TestFile config;
  77. config.schema_version = 1;
  78. config.schema = Schema{
  79. {"object", {
  80. {"value", PropertyType::Int, Property::IsPrimary{false}, Property::IsIndexed{false}, "alias"}
  81. }},
  82. };
  83. auto realm = Realm::get_shared_realm(config);
  84. REQUIRE(realm->schema().find("object")->property_for_name("value")->public_name == "alias");
  85. }
  86. SECTION("looking up properties by alias matches name if alias is not set") {
  87. auto schema = Schema{
  88. {"object", {
  89. {"value", PropertyType::Int, Property::IsPrimary{false}, Property::IsIndexed{false}, "alias"},
  90. {"other_value", PropertyType::Int, Property::IsPrimary{false}, Property::IsIndexed{false}}
  91. }},
  92. };
  93. REQUIRE(schema.find("object")->property_for_public_name("value") == nullptr);
  94. REQUIRE(schema.find("object")->property_for_public_name("alias")->name == "value");
  95. REQUIRE(schema.find("object")->property_for_public_name("other_value")->name == "other_value");
  96. }
  97. SECTION("from a Group") {
  98. Group g;
  99. TableRef pk = g.add_table("pk");
  100. pk->add_column(type_String, "pk_table");
  101. pk->add_column(type_String, "pk_property");
  102. pk->add_empty_row();
  103. pk->set_string(0, 0, "table");
  104. pk->set_string(1, 0, "pk");
  105. TableRef table = g.add_table("class_table");
  106. TableRef target = g.add_table("class_target");
  107. table->add_column(type_Int, "pk");
  108. table->add_column(type_Int, "int");
  109. table->add_column(type_Bool, "bool");
  110. table->add_column(type_Float, "float");
  111. table->add_column(type_Double, "double");
  112. table->add_column(type_String, "string");
  113. table->add_column(type_Binary, "data");
  114. table->add_column(type_Timestamp, "date");
  115. table->add_column_link(type_Link, "object", *target);
  116. table->add_column_link(type_LinkList, "array", *target);
  117. table->add_column(type_Int, "int?", true);
  118. table->add_column(type_Bool, "bool?", true);
  119. table->add_column(type_Float, "float?", true);
  120. table->add_column(type_Double, "double?", true);
  121. table->add_column(type_String, "string?", true);
  122. table->add_column(type_Binary, "data?", true);
  123. table->add_column(type_Timestamp, "date?", true);
  124. table->add_column(type_Table, "subtable 1");
  125. size_t col = table->add_column(type_Table, "subtable 2");
  126. table->get_subdescriptor(col)->add_column(type_Int, "value");
  127. auto add_list = [](TableRef table, DataType type, StringData name, bool nullable) {
  128. size_t col = table->add_column(type_Table, name);
  129. table->get_subdescriptor(col)->add_column(type, ObjectStore::ArrayColumnName, nullptr, nullable);
  130. };
  131. add_list(table, type_Int, "int array", false);
  132. add_list(table, type_Bool, "bool array", false);
  133. add_list(table, type_Float, "float array", false);
  134. add_list(table, type_Double, "double array", false);
  135. add_list(table, type_String, "string array", false);
  136. add_list(table, type_Binary, "data array", false);
  137. add_list(table, type_Timestamp, "date array", false);
  138. add_list(table, type_Int, "int? array", true);
  139. add_list(table, type_Bool, "bool? array", true);
  140. add_list(table, type_Float, "float? array", true);
  141. add_list(table, type_Double, "double? array", true);
  142. add_list(table, type_String, "string? array", true);
  143. add_list(table, type_Binary, "data? array", true);
  144. add_list(table, type_Timestamp, "date? array", true);
  145. size_t indexed_start = table->get_column_count();
  146. table->add_column(type_Int, "indexed int");
  147. table->add_column(type_Bool, "indexed bool");
  148. table->add_column(type_String, "indexed string");
  149. table->add_column(type_Timestamp, "indexed date");
  150. table->add_column(type_Int, "indexed int?", true);
  151. table->add_column(type_Bool, "indexed bool?", true);
  152. table->add_column(type_String, "indexed string?", true);
  153. table->add_column(type_Timestamp, "indexed date?", true);
  154. for (size_t i = indexed_start; i < table->get_column_count(); ++i)
  155. table->add_search_index(i);
  156. ObjectSchema os(g, "table");
  157. #define REQUIRE_PROPERTY(name, type, ...) do { \
  158. Property* prop; \
  159. REQUIRE((prop = os.property_for_name(name))); \
  160. REQUIRE((*prop == Property{name, PropertyType::type, __VA_ARGS__})); \
  161. REQUIRE(prop->table_column == expected_col++); \
  162. } while (0)
  163. size_t expected_col = 0;
  164. REQUIRE(os.property_for_name("nonexistent property") == nullptr);
  165. REQUIRE_PROPERTY("pk", Int, Property::IsPrimary{true});
  166. REQUIRE_PROPERTY("int", Int);
  167. REQUIRE_PROPERTY("bool", Bool);
  168. REQUIRE_PROPERTY("float", Float);
  169. REQUIRE_PROPERTY("double", Double);
  170. REQUIRE_PROPERTY("string", String);
  171. REQUIRE_PROPERTY("data", Data);
  172. REQUIRE_PROPERTY("date", Date);
  173. REQUIRE_PROPERTY("object", Object|PropertyType::Nullable, "target");
  174. REQUIRE_PROPERTY("array", Array|PropertyType::Object, "target");
  175. REQUIRE_PROPERTY("int?", Int|PropertyType::Nullable);
  176. REQUIRE_PROPERTY("bool?", Bool|PropertyType::Nullable);
  177. REQUIRE_PROPERTY("float?", Float|PropertyType::Nullable);
  178. REQUIRE_PROPERTY("double?", Double|PropertyType::Nullable);
  179. REQUIRE_PROPERTY("string?", String|PropertyType::Nullable);
  180. REQUIRE_PROPERTY("data?", Data|PropertyType::Nullable);
  181. REQUIRE_PROPERTY("date?", Date|PropertyType::Nullable);
  182. // Unsupported column type should be skipped entirely
  183. REQUIRE(os.property_for_name("subtable 1") == nullptr);
  184. REQUIRE(os.property_for_name("subtable 2") == nullptr);
  185. expected_col += 2;
  186. REQUIRE_PROPERTY("int array", Int|PropertyType::Array);
  187. REQUIRE_PROPERTY("bool array", Bool|PropertyType::Array);
  188. REQUIRE_PROPERTY("float array", Float|PropertyType::Array);
  189. REQUIRE_PROPERTY("double array", Double|PropertyType::Array);
  190. REQUIRE_PROPERTY("string array", String|PropertyType::Array);
  191. REQUIRE_PROPERTY("data array", Data|PropertyType::Array);
  192. REQUIRE_PROPERTY("date array", Date|PropertyType::Array);
  193. REQUIRE_PROPERTY("int? array", Int|PropertyType::Array|PropertyType::Nullable);
  194. REQUIRE_PROPERTY("bool? array", Bool|PropertyType::Array|PropertyType::Nullable);
  195. REQUIRE_PROPERTY("float? array", Float|PropertyType::Array|PropertyType::Nullable);
  196. REQUIRE_PROPERTY("double? array", Double|PropertyType::Array|PropertyType::Nullable);
  197. REQUIRE_PROPERTY("string? array", String|PropertyType::Array|PropertyType::Nullable);
  198. REQUIRE_PROPERTY("data? array", Data|PropertyType::Array|PropertyType::Nullable);
  199. REQUIRE_PROPERTY("date? array", Date|PropertyType::Array|PropertyType::Nullable);
  200. REQUIRE_PROPERTY("indexed int", Int, Property::IsPrimary{false}, Property::IsIndexed{true});
  201. REQUIRE_PROPERTY("indexed bool", Bool, Property::IsPrimary{false}, Property::IsIndexed{true});
  202. REQUIRE_PROPERTY("indexed string", String, Property::IsPrimary{false}, Property::IsIndexed{true});
  203. REQUIRE_PROPERTY("indexed date", Date, Property::IsPrimary{false}, Property::IsIndexed{true});
  204. REQUIRE_PROPERTY("indexed int?", Int|PropertyType::Nullable, Property::IsPrimary{false}, Property::IsIndexed{true});
  205. REQUIRE_PROPERTY("indexed bool?", Bool|PropertyType::Nullable, Property::IsPrimary{false}, Property::IsIndexed{true});
  206. REQUIRE_PROPERTY("indexed string?", String|PropertyType::Nullable, Property::IsPrimary{false}, Property::IsIndexed{true});
  207. REQUIRE_PROPERTY("indexed date?", Date|PropertyType::Nullable, Property::IsPrimary{false}, Property::IsIndexed{true});
  208. pk->set_string(1, 0, "nonexistent property");
  209. REQUIRE(ObjectSchema(g, "table").primary_key_property() == nullptr);
  210. }
  211. }
  212. TEST_CASE("Schema") {
  213. SECTION("validate()") {
  214. SECTION("rejects link properties with no target object") {
  215. Schema schema = {
  216. {"object", {
  217. {"link", PropertyType::Object|PropertyType::Nullable}
  218. }},
  219. };
  220. REQUIRE_THROWS_CONTAINING(schema.validate(), "Property 'object.link' of type 'object' has unknown object type ''");
  221. }
  222. SECTION("rejects array properties with no target object") {
  223. Schema schema = {
  224. {"object", {
  225. {"array", PropertyType::Array|PropertyType::Object}
  226. }},
  227. };
  228. REQUIRE_THROWS_CONTAINING(schema.validate(), "Property 'object.array' of type 'array' has unknown object type ''");
  229. }
  230. SECTION("rejects link properties with a target not in the schema") {
  231. Schema schema = {
  232. {"object", {
  233. {"link", PropertyType::Object|PropertyType::Nullable, "invalid target"}
  234. }}
  235. };
  236. REQUIRE_THROWS_CONTAINING(schema.validate(), "Property 'object.link' of type 'object' has unknown object type 'invalid target'");
  237. }
  238. SECTION("rejects array properties with a target not in the schema") {
  239. Schema schema = {
  240. {"object", {
  241. {"array", PropertyType::Array|PropertyType::Object, "invalid target"}
  242. }}
  243. };
  244. REQUIRE_THROWS_CONTAINING(schema.validate(), "Property 'object.array' of type 'array' has unknown object type 'invalid target'");
  245. }
  246. SECTION("rejects linking objects without a source object") {
  247. Schema schema = {
  248. {"object", {
  249. {"value", PropertyType::Int},
  250. }, {
  251. {"incoming", PropertyType::Array|PropertyType::LinkingObjects, "", ""}
  252. }}
  253. };
  254. REQUIRE_THROWS_CONTAINING(schema.validate(), "Property 'object.incoming' of type 'linking objects' has unknown object type ''");
  255. }
  256. SECTION("rejects linking objects without a source property") {
  257. Schema schema = {
  258. {"object", {
  259. {"value", PropertyType::Int},
  260. }, {
  261. {"incoming", PropertyType::Array|PropertyType::LinkingObjects, "object", ""}
  262. }}
  263. };
  264. REQUIRE_THROWS_CONTAINING(schema.validate(), "Property 'object.incoming' of type 'linking objects' must have an origin property name.");
  265. }
  266. SECTION("rejects linking objects with invalid source object") {
  267. Schema schema = {
  268. {"object", {
  269. {"value", PropertyType::Int},
  270. }, {
  271. {"incoming", PropertyType::Array|PropertyType::LinkingObjects, "not an object type", ""}
  272. }}
  273. };
  274. REQUIRE_THROWS_CONTAINING(schema.validate(), "Property 'object.incoming' of type 'linking objects' has unknown object type 'not an object type'");
  275. }
  276. SECTION("rejects linking objects with invalid source property") {
  277. Schema schema = {
  278. {"object", {
  279. {"value", PropertyType::Int},
  280. }, {
  281. {"incoming", PropertyType::Array|PropertyType::LinkingObjects, "object", "value"}
  282. }}
  283. };
  284. REQUIRE_THROWS_CONTAINING(schema.validate(), "Property 'object.value' declared as origin of linking objects property 'object.incoming' is not a link");
  285. schema = {
  286. {"object", {
  287. {"value", PropertyType::Int},
  288. {"link", PropertyType::Object|PropertyType::Nullable, "object 2"},
  289. }, {
  290. {"incoming", PropertyType::Array|PropertyType::LinkingObjects, "object", "link"}
  291. }},
  292. {"object 2", {
  293. {"value", PropertyType::Int},
  294. }}
  295. };
  296. REQUIRE_THROWS_CONTAINING(schema.validate(), "Property 'object.link' declared as origin of linking objects property 'object.incoming' links to type 'object 2'");
  297. }
  298. SECTION("rejects non-array linking objects") {
  299. Schema schema = {
  300. {"object", {
  301. {"link", PropertyType::Object|PropertyType::Nullable, "object"},
  302. }, {
  303. {"incoming", PropertyType::LinkingObjects, "object", "link"}
  304. }}
  305. };
  306. REQUIRE_THROWS_CONTAINING(schema.validate(), "Linking Objects property 'object.incoming' must be an array.");
  307. }
  308. SECTION("rejects target object types for non-link properties") {
  309. Schema schema = {
  310. {"object", {
  311. {"int", PropertyType::Int},
  312. {"bool", PropertyType::Bool},
  313. {"float", PropertyType::Float},
  314. {"double", PropertyType::Double},
  315. {"string", PropertyType::String},
  316. {"date", PropertyType::Date},
  317. }}
  318. };
  319. for (auto& prop : schema.begin()->persisted_properties) {
  320. REQUIRE_NOTHROW(schema.validate());
  321. prop.object_type = "object";
  322. REQUIRE_THROWS_CONTAINING(schema.validate(), "cannot have an object type.");
  323. prop.object_type = "";
  324. }
  325. }
  326. SECTION("rejects source property name for non-linking objects properties") {
  327. Schema schema = {
  328. {"object", {
  329. {"int", PropertyType::Int},
  330. {"bool", PropertyType::Bool},
  331. {"float", PropertyType::Float},
  332. {"double", PropertyType::Double},
  333. {"string", PropertyType::String},
  334. {"data", PropertyType::Data},
  335. {"date", PropertyType::Date},
  336. {"object", PropertyType::Object|PropertyType::Nullable, "object"},
  337. {"array", PropertyType::Object|PropertyType::Array, "object"},
  338. }}
  339. };
  340. for (auto& prop : schema.begin()->persisted_properties) {
  341. REQUIRE_NOTHROW(schema.validate());
  342. prop.link_origin_property_name = "source";
  343. auto expected = util::format("Property 'object.%1' of type '%1' cannot have an origin property name.", prop.name, prop.name);
  344. REQUIRE_THROWS_CONTAINING(schema.validate(), expected);
  345. prop.link_origin_property_name = "";
  346. }
  347. }
  348. SECTION("rejects non-nullable link properties") {
  349. Schema schema = {
  350. {"object", {
  351. {"link", PropertyType::Object, "target"}
  352. }},
  353. {"target", {
  354. {"value", PropertyType::Int}
  355. }}
  356. };
  357. REQUIRE_THROWS_CONTAINING(schema.validate(), "Property 'object.link' of type 'object' must be nullable.");
  358. }
  359. SECTION("rejects nullable array properties") {
  360. Schema schema = {
  361. {"object", {
  362. {"array", PropertyType::Array|PropertyType::Object|PropertyType::Nullable, "target"}
  363. }},
  364. {"target", {
  365. {"value", PropertyType::Int}
  366. }}
  367. };
  368. REQUIRE_THROWS_CONTAINING(schema.validate(), "Property 'object.array' of type 'array' cannot be nullable.");
  369. }
  370. SECTION("rejects nullable linking objects") {
  371. Schema schema = {
  372. {"object", {
  373. {"link", PropertyType::Object|PropertyType::Nullable, "object"},
  374. }, {
  375. {"incoming", PropertyType::LinkingObjects|PropertyType::Array|PropertyType::Nullable, "object", "link"}
  376. }}
  377. };
  378. REQUIRE_THROWS_CONTAINING(schema.validate(), "Property 'object.incoming' of type 'linking objects' cannot be nullable.");
  379. }
  380. SECTION("rejects duplicate primary keys") {
  381. Schema schema = {
  382. {"object", {
  383. {"pk1", PropertyType::Int, Property::IsPrimary{true}},
  384. {"pk2", PropertyType::Int, Property::IsPrimary{true}},
  385. }}
  386. };
  387. REQUIRE_THROWS_CONTAINING(schema.validate(), "Properties 'pk2' and 'pk1' are both marked as the primary key of 'object'.");
  388. }
  389. SECTION("rejects invalid primary key types") {
  390. Schema schema = {
  391. {"object", {
  392. {"pk", PropertyType::Float, Property::IsPrimary{true}},
  393. }}
  394. };
  395. schema.begin()->primary_key_property()->type = PropertyType::Any;
  396. REQUIRE_THROWS_CONTAINING(schema.validate(), "Property 'object.pk' of type 'any' cannot be made the primary key.");
  397. schema.begin()->primary_key_property()->type = PropertyType::Bool;
  398. REQUIRE_THROWS_CONTAINING(schema.validate(), "Property 'object.pk' of type 'bool' cannot be made the primary key.");
  399. schema.begin()->primary_key_property()->type = PropertyType::Float;
  400. REQUIRE_THROWS_CONTAINING(schema.validate(), "Property 'object.pk' of type 'float' cannot be made the primary key.");
  401. schema.begin()->primary_key_property()->type = PropertyType::Double;
  402. REQUIRE_THROWS_CONTAINING(schema.validate(), "Property 'object.pk' of type 'double' cannot be made the primary key.");
  403. schema.begin()->primary_key_property()->type = PropertyType::Object;
  404. REQUIRE_THROWS_CONTAINING(schema.validate(), "Property 'object.pk' of type 'object' cannot be made the primary key.");
  405. schema.begin()->primary_key_property()->type = PropertyType::LinkingObjects;
  406. REQUIRE_THROWS_CONTAINING(schema.validate(), "Property 'object.pk' of type 'linking objects' cannot be made the primary key.");
  407. schema.begin()->primary_key_property()->type = PropertyType::Data;
  408. REQUIRE_THROWS_CONTAINING(schema.validate(), "Property 'object.pk' of type 'data' cannot be made the primary key.");
  409. schema.begin()->primary_key_property()->type = PropertyType::Date;
  410. REQUIRE_THROWS_CONTAINING(schema.validate(), "Property 'object.pk' of type 'date' cannot be made the primary key.");
  411. }
  412. SECTION("allows valid primary key types") {
  413. Schema schema = {
  414. {"object", {
  415. {"pk", PropertyType::Int, Property::IsPrimary{true}},
  416. }}
  417. };
  418. REQUIRE_NOTHROW(schema.validate());
  419. schema.begin()->primary_key_property()->type = PropertyType::Int|PropertyType::Nullable;
  420. REQUIRE_NOTHROW(schema.validate());
  421. schema.begin()->primary_key_property()->type = PropertyType::String;
  422. REQUIRE_NOTHROW(schema.validate());
  423. schema.begin()->primary_key_property()->type = PropertyType::String|PropertyType::Nullable;
  424. REQUIRE_NOTHROW(schema.validate());
  425. }
  426. SECTION("rejects nonexistent primary key") {
  427. Schema schema = {
  428. {"object", {
  429. {"value", PropertyType::Int},
  430. }}
  431. };
  432. schema.begin()->primary_key = "nonexistent";
  433. REQUIRE_THROWS_CONTAINING(schema.validate(), "Specified primary key 'object.nonexistent' does not exist.");
  434. }
  435. SECTION("rejects indexes for types that cannot be indexed") {
  436. Schema schema = {
  437. {"object", {
  438. {"float", PropertyType::Float},
  439. {"double", PropertyType::Double},
  440. {"data", PropertyType::Data},
  441. {"object", PropertyType::Object|PropertyType::Nullable, "object"},
  442. {"array", PropertyType::Array|PropertyType::Object, "object"},
  443. }}
  444. };
  445. for (auto& prop : schema.begin()->persisted_properties) {
  446. REQUIRE_NOTHROW(schema.validate());
  447. prop.is_indexed = true;
  448. auto expected = util::format("Property 'object.%1' of type '%1' cannot be indexed.", prop.name);
  449. REQUIRE_THROWS_CONTAINING(schema.validate(), expected);
  450. prop.is_indexed = false;
  451. }
  452. }
  453. SECTION("allows indexing types that can be indexed") {
  454. Schema schema = {
  455. {"object", {
  456. {"int", PropertyType::Int, Property::IsPrimary{false}, Property::IsIndexed{true}},
  457. {"bool", PropertyType::Bool, Property::IsPrimary{false}, Property::IsIndexed{true}},
  458. {"string", PropertyType::String, Property::IsPrimary{false}, Property::IsIndexed{true}},
  459. {"date", PropertyType::Date, Property::IsPrimary{false}, Property::IsIndexed{true}},
  460. }}
  461. };
  462. REQUIRE_NOTHROW(schema.validate());
  463. }
  464. SECTION("rejects duplicate types with the same name") {
  465. Schema schema = {
  466. {"object1", {
  467. {"int", PropertyType::Int},
  468. }},
  469. {"object2", {
  470. {"int", PropertyType::Int},
  471. }},
  472. {"object3", {
  473. {"int", PropertyType::Int},
  474. }},
  475. {"object2", {
  476. {"int", PropertyType::Int},
  477. }},
  478. {"object1", {
  479. {"int", PropertyType::Int},
  480. }}
  481. };
  482. REQUIRE_THROWS_CONTAINING(schema.validate(),
  483. "- Type 'object1' appears more than once in the schema.\n"
  484. "- Type 'object2' appears more than once in the schema.");
  485. }
  486. SECTION("rejects properties with the same name") {
  487. Schema schema = {
  488. {"object", {
  489. {"child", PropertyType::Object|PropertyType::Nullable, "object"},
  490. {"parent", PropertyType::Int},
  491. {"field1", PropertyType::Int},
  492. {"field2", PropertyType::String},
  493. {"field1", PropertyType::String},
  494. {"field2", PropertyType::String},
  495. {"field1", PropertyType::Int},
  496. }, {
  497. {"parent", PropertyType::Array|PropertyType::LinkingObjects, "object", "child"}
  498. }}
  499. };
  500. REQUIRE_THROWS_CONTAINING(schema.validate(),
  501. "- Property 'field1' appears more than once in the schema for type 'object'.\n"
  502. "- Property 'field2' appears more than once in the schema for type 'object'.\n"
  503. "- Property 'parent' appears more than once in the schema for type 'object'.");
  504. }
  505. SECTION("rejects schema if all properties have the same name") {
  506. Schema schema = {
  507. {"object", {
  508. {"field", PropertyType::Int},
  509. {"otherField", PropertyType::Int},
  510. {"field", PropertyType::Int},
  511. {"otherField", PropertyType::Int},
  512. {"field", PropertyType::Int},
  513. {"otherField", PropertyType::Int},
  514. {"field", PropertyType::Int},
  515. {"otherField", PropertyType::Int},
  516. {"field", PropertyType::Int},
  517. {"otherField", PropertyType::Int},
  518. }}
  519. };
  520. REQUIRE_THROWS_CONTAINING(schema.validate(),
  521. "- Property 'field' appears more than once in the schema for type 'object'.\n"
  522. "- Property 'otherField' appears more than once in the schema for type 'object'.");
  523. }
  524. SECTION("rejects properties with the same alias") {
  525. Schema schema = {
  526. {"object", {
  527. {"child", PropertyType::Object|PropertyType::Nullable, "object"},
  528. // Alias == Name on computed property
  529. {"parentA", PropertyType::Int, Property::IsPrimary{false}, Property::IsIndexed{false}, "_parent"},
  530. // Name == Alias on other property
  531. {"fieldA", PropertyType::Int, Property::IsPrimary{false}, Property::IsIndexed{false}, "_field1"},
  532. {"fieldB", PropertyType::String, Property::IsPrimary{false}, Property::IsIndexed{false}, "_field2"},
  533. {"fieldC", PropertyType::String, Property::IsPrimary{false}, Property::IsIndexed{false}, "_field1"},
  534. {"fieldD", PropertyType::String, Property::IsPrimary{false}, Property::IsIndexed{false}, "_field2"},
  535. {"fieldE", PropertyType::Int, Property::IsPrimary{false}, Property::IsIndexed{false}, "_field1"},
  536. // Name == Alias
  537. {"fieldF", PropertyType::Int, Property::IsPrimary{false}, Property::IsIndexed{false}, "fieldF"},
  538. }, {
  539. // Computed property alias == name on persisted property
  540. {"parentB", PropertyType::Array|PropertyType::LinkingObjects, "object", "child", "_parent"}
  541. }}
  542. };
  543. REQUIRE_THROWS_CONTAINING(schema.validate(),
  544. "- Alias '_field1' appears more than once in the schema for type 'object'.\n"
  545. "- Alias '_field2' appears more than once in the schema for type 'object'.\n"
  546. "- Alias '_parent' appears more than once in the schema for type 'object'.");
  547. }
  548. SECTION("rejects properties whose name conflicts with an alias for another property") {
  549. Schema schema = {
  550. {"object", {
  551. {"child", PropertyType::Object|PropertyType::Nullable, "object"},
  552. {"field1", PropertyType::Int, Property::IsPrimary{false}, Property::IsIndexed{false}, "field2"},
  553. {"field2", PropertyType::Int, Property::IsPrimary{false}, Property::IsIndexed{false}, "parent"},
  554. }, {
  555. {"parent", PropertyType::Array|PropertyType::LinkingObjects, "object", "child", "field1"}
  556. }}
  557. };
  558. REQUIRE_THROWS_CONTAINING(schema.validate(),
  559. "- Property 'object.parent' has an alias 'field1' that conflicts with a property of the same name.\n"
  560. "- Property 'object.field1' has an alias 'field2' that conflicts with a property of the same name.\n"
  561. "- Property 'object.field2' has an alias 'parent' that conflicts with a property of the same name.");
  562. }
  563. }
  564. SECTION("compare()") {
  565. using namespace schema_change;
  566. using vec = std::vector<SchemaChange>;
  567. SECTION("add table") {
  568. Schema schema1 = {
  569. {"object 1", {
  570. {"int", PropertyType::Int},
  571. }}
  572. };
  573. Schema schema2 = {
  574. {"object 1", {
  575. {"int", PropertyType::Int},
  576. }},
  577. {"object 2", {
  578. {"int", PropertyType::Int},
  579. }}
  580. };
  581. auto obj = &*schema2.find("object 2");
  582. auto expected = vec{AddTable{obj}, AddInitialProperties{obj}};
  583. REQUIRE(schema1.compare(schema2) == expected);
  584. }
  585. SECTION("add property") {
  586. Schema schema1 = {
  587. {"object", {
  588. {"int 1", PropertyType::Int},
  589. }}
  590. };
  591. Schema schema2 = {
  592. {"object", {
  593. {"int 1", PropertyType::Int},
  594. {"int 2", PropertyType::Int},
  595. }}
  596. };
  597. REQUIRE(schema1.compare(schema2) == vec{(AddProperty{&*schema1.find("object"), &schema2.find("object")->persisted_properties[1]})});
  598. }
  599. SECTION("remove property") {
  600. Schema schema1 = {
  601. {"object", {
  602. {"int 1", PropertyType::Int},
  603. {"int 2", PropertyType::Int},
  604. }}
  605. };
  606. Schema schema2 = {
  607. {"object", {
  608. {"int 1", PropertyType::Int},
  609. }}
  610. };
  611. REQUIRE(schema1.compare(schema2) == vec{(RemoveProperty{&*schema1.find("object"), &schema1.find("object")->persisted_properties[1]})});
  612. }
  613. SECTION("change property type") {
  614. Schema schema1 = {
  615. {"object", {
  616. {"value", PropertyType::Int},
  617. }}
  618. };
  619. Schema schema2 = {
  620. {"object", {
  621. {"value", PropertyType::Double},
  622. }}
  623. };
  624. REQUIRE(schema1.compare(schema2) == vec{(ChangePropertyType{
  625. &*schema1.find("object"),
  626. &schema1.find("object")->persisted_properties[0],
  627. &schema2.find("object")->persisted_properties[0]})});
  628. };
  629. SECTION("change link target") {
  630. Schema schema1 = {
  631. {"object", {
  632. {"value", PropertyType::Object, "target 1"},
  633. }},
  634. {"target 1", {
  635. {"value", PropertyType::Int},
  636. }},
  637. {"target 2", {
  638. {"value", PropertyType::Int},
  639. }},
  640. };
  641. Schema schema2 = {
  642. {"object", {
  643. {"value", PropertyType::Object, "target 2"},
  644. }},
  645. {"target 1", {
  646. {"value", PropertyType::Int},
  647. }},
  648. {"target 2", {
  649. {"value", PropertyType::Int},
  650. }},
  651. };
  652. REQUIRE(schema1.compare(schema2) == vec{(ChangePropertyType{
  653. &*schema1.find("object"),
  654. &schema1.find("object")->persisted_properties[0],
  655. &schema2.find("object")->persisted_properties[0]})});
  656. }
  657. SECTION("add index") {
  658. Schema schema1 = {
  659. {"object", {
  660. {"int", PropertyType::Int},
  661. }}
  662. };
  663. Schema schema2 = {
  664. {"object", {
  665. {"int", PropertyType::Int, Property::IsPrimary{false}, Property::IsIndexed{true}},
  666. }}
  667. };
  668. auto object_schema = &*schema1.find("object");
  669. REQUIRE(schema1.compare(schema2) == vec{(AddIndex{object_schema, &object_schema->persisted_properties[0]})});
  670. }
  671. SECTION("remove index") {
  672. Schema schema1 = {
  673. {"object", {
  674. {"int", PropertyType::Int, Property::IsPrimary{false}, Property::IsIndexed{true}},
  675. }}
  676. };
  677. Schema schema2 = {
  678. {"object", {
  679. {"int", PropertyType::Int},
  680. }}
  681. };
  682. auto object_schema = &*schema1.find("object");
  683. REQUIRE(schema1.compare(schema2) == vec{(RemoveIndex{object_schema, &object_schema->persisted_properties[0]})});
  684. }
  685. SECTION("add index and make nullable") {
  686. Schema schema1 = {
  687. {"object", {
  688. {"int", PropertyType::Int},
  689. }}
  690. };
  691. Schema schema2 = {
  692. {"object", {
  693. {"int", PropertyType::Int|PropertyType::Nullable, Property::IsPrimary{false}, Property::IsIndexed{true}},
  694. }}
  695. };
  696. auto object_schema = &*schema1.find("object");
  697. REQUIRE(schema1.compare(schema2) == (vec{
  698. MakePropertyNullable{object_schema, &object_schema->persisted_properties[0]},
  699. AddIndex{object_schema, &object_schema->persisted_properties[0]}}));
  700. }
  701. SECTION("add index and change type") {
  702. Schema schema1 = {
  703. {"object", {
  704. {"value", PropertyType::Int},
  705. }}
  706. };
  707. Schema schema2 = {
  708. {"object", {
  709. {"value", PropertyType::Double, Property::IsPrimary{false}, Property::IsIndexed{true}},
  710. }}
  711. };
  712. REQUIRE(schema1.compare(schema2) == vec{(ChangePropertyType{
  713. &*schema1.find("object"),
  714. &schema1.find("object")->persisted_properties[0],
  715. &schema2.find("object")->persisted_properties[0]})});
  716. }
  717. SECTION("make nullable and change type") {
  718. Schema schema1 = {
  719. {"object", {
  720. {"value", PropertyType::Int},
  721. }}
  722. };
  723. Schema schema2 = {
  724. {"object", {
  725. {"value", PropertyType::Double|PropertyType::Nullable},
  726. }}
  727. };
  728. REQUIRE(schema1.compare(schema2) == vec{(ChangePropertyType{
  729. &*schema1.find("object"),
  730. &schema1.find("object")->persisted_properties[0],
  731. &schema2.find("object")->persisted_properties[0]})});
  732. }
  733. }
  734. }