schema.cpp 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715
  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 "catch.hpp"
  19. #include "object_schema.hpp"
  20. #include "object_store.hpp"
  21. #include "property.hpp"
  22. #include "schema.hpp"
  23. #include <realm/descriptor.hpp>
  24. #include <realm/group.hpp>
  25. #include <realm/table.hpp>
  26. using namespace realm;
  27. struct SchemaChangePrinter {
  28. std::ostream& out;
  29. template<typename Value>
  30. void print(Value value) const
  31. {
  32. out << value;
  33. }
  34. template<typename Value, typename... Rest>
  35. void print(Value value, Rest... rest) const
  36. {
  37. out << value << ", ";
  38. print(rest...);
  39. }
  40. #define REALM_SC_PRINT(type, ...) \
  41. void operator()(schema_change::type v) const \
  42. { \
  43. out << #type << "{"; \
  44. print(__VA_ARGS__); \
  45. out << "}"; \
  46. }
  47. REALM_SC_PRINT(AddIndex, v.object, v.property)
  48. REALM_SC_PRINT(AddProperty, v.object, v.property)
  49. REALM_SC_PRINT(AddTable, v.object)
  50. REALM_SC_PRINT(RemoveTable, v.object)
  51. REALM_SC_PRINT(AddInitialProperties, v.object)
  52. REALM_SC_PRINT(ChangePrimaryKey, v.object, v.property)
  53. REALM_SC_PRINT(ChangePropertyType, v.object, v.old_property, v.new_property)
  54. REALM_SC_PRINT(MakePropertyNullable, v.object, v.property)
  55. REALM_SC_PRINT(MakePropertyRequired, v.object, v.property)
  56. REALM_SC_PRINT(RemoveIndex, v.object, v.property)
  57. REALM_SC_PRINT(RemoveProperty, v.object, v.property)
  58. #undef REALM_SC_PRINT
  59. };
  60. namespace Catch {
  61. template<>
  62. struct StringMaker<SchemaChange> {
  63. static std::string convert(SchemaChange const& sc)
  64. {
  65. std::stringstream ss;
  66. sc.visit(SchemaChangePrinter{ss});
  67. return ss.str();
  68. }
  69. };
  70. } // namespace Catch
  71. #define REQUIRE_THROWS_CONTAINING(expr, msg) \
  72. REQUIRE_THROWS_WITH(expr, Catch::Matchers::Contains(msg))
  73. TEST_CASE("ObjectSchema") {
  74. SECTION("from a Group") {
  75. Group g;
  76. TableRef pk = g.add_table("pk");
  77. pk->add_column(type_String, "pk_table");
  78. pk->add_column(type_String, "pk_property");
  79. pk->add_empty_row();
  80. pk->set_string(0, 0, "table");
  81. pk->set_string(1, 0, "pk");
  82. TableRef table = g.add_table("class_table");
  83. TableRef target = g.add_table("class_target");
  84. table->add_column(type_Int, "pk");
  85. table->add_column(type_Int, "int");
  86. table->add_column(type_Bool, "bool");
  87. table->add_column(type_Float, "float");
  88. table->add_column(type_Double, "double");
  89. table->add_column(type_String, "string");
  90. table->add_column(type_Binary, "data");
  91. table->add_column(type_Timestamp, "date");
  92. table->add_column_link(type_Link, "object", *target);
  93. table->add_column_link(type_LinkList, "array", *target);
  94. table->add_column(type_Int, "int?", true);
  95. table->add_column(type_Bool, "bool?", true);
  96. table->add_column(type_Float, "float?", true);
  97. table->add_column(type_Double, "double?", true);
  98. table->add_column(type_String, "string?", true);
  99. table->add_column(type_Binary, "data?", true);
  100. table->add_column(type_Timestamp, "date?", true);
  101. table->add_column(type_Table, "subtable 1");
  102. size_t col = table->add_column(type_Table, "subtable 2");
  103. table->get_subdescriptor(col)->add_column(type_Int, "value");
  104. auto add_list = [](TableRef table, DataType type, StringData name, bool nullable) {
  105. size_t col = table->add_column(type_Table, name);
  106. table->get_subdescriptor(col)->add_column(type, ObjectStore::ArrayColumnName, nullptr, nullable);
  107. };
  108. add_list(table, type_Int, "int array", false);
  109. add_list(table, type_Bool, "bool array", false);
  110. add_list(table, type_Float, "float array", false);
  111. add_list(table, type_Double, "double array", false);
  112. add_list(table, type_String, "string array", false);
  113. add_list(table, type_Binary, "data array", false);
  114. add_list(table, type_Timestamp, "date array", false);
  115. add_list(table, type_Int, "int? array", true);
  116. add_list(table, type_Bool, "bool? array", true);
  117. add_list(table, type_Float, "float? array", true);
  118. add_list(table, type_Double, "double? array", true);
  119. add_list(table, type_String, "string? array", true);
  120. add_list(table, type_Binary, "data? array", true);
  121. add_list(table, type_Timestamp, "date? array", true);
  122. size_t indexed_start = table->get_column_count();
  123. table->add_column(type_Int, "indexed int");
  124. table->add_column(type_Bool, "indexed bool");
  125. table->add_column(type_String, "indexed string");
  126. table->add_column(type_Timestamp, "indexed date");
  127. table->add_column(type_Int, "indexed int?", true);
  128. table->add_column(type_Bool, "indexed bool?", true);
  129. table->add_column(type_String, "indexed string?", true);
  130. table->add_column(type_Timestamp, "indexed date?", true);
  131. for (size_t i = indexed_start; i < table->get_column_count(); ++i)
  132. table->add_search_index(i);
  133. ObjectSchema os(g, "table");
  134. #define REQUIRE_PROPERTY(name, type, ...) do { \
  135. Property* prop; \
  136. REQUIRE((prop = os.property_for_name(name))); \
  137. REQUIRE((*prop == Property{name, PropertyType::type, __VA_ARGS__})); \
  138. REQUIRE(prop->table_column == expected_col++); \
  139. } while (0)
  140. size_t expected_col = 0;
  141. REQUIRE(os.property_for_name("nonexistent property") == nullptr);
  142. REQUIRE_PROPERTY("pk", Int, Property::IsPrimary{true});
  143. REQUIRE_PROPERTY("int", Int);
  144. REQUIRE_PROPERTY("bool", Bool);
  145. REQUIRE_PROPERTY("float", Float);
  146. REQUIRE_PROPERTY("double", Double);
  147. REQUIRE_PROPERTY("string", String);
  148. REQUIRE_PROPERTY("data", Data);
  149. REQUIRE_PROPERTY("date", Date);
  150. REQUIRE_PROPERTY("object", Object|PropertyType::Nullable, "target");
  151. REQUIRE_PROPERTY("array", Array|PropertyType::Object, "target");
  152. REQUIRE_PROPERTY("int?", Int|PropertyType::Nullable);
  153. REQUIRE_PROPERTY("bool?", Bool|PropertyType::Nullable);
  154. REQUIRE_PROPERTY("float?", Float|PropertyType::Nullable);
  155. REQUIRE_PROPERTY("double?", Double|PropertyType::Nullable);
  156. REQUIRE_PROPERTY("string?", String|PropertyType::Nullable);
  157. REQUIRE_PROPERTY("data?", Data|PropertyType::Nullable);
  158. REQUIRE_PROPERTY("date?", Date|PropertyType::Nullable);
  159. // Unsupported column type should be skipped entirely
  160. REQUIRE(os.property_for_name("subtable 1") == nullptr);
  161. REQUIRE(os.property_for_name("subtable 2") == nullptr);
  162. expected_col += 2;
  163. REQUIRE_PROPERTY("int array", Int|PropertyType::Array);
  164. REQUIRE_PROPERTY("bool array", Bool|PropertyType::Array);
  165. REQUIRE_PROPERTY("float array", Float|PropertyType::Array);
  166. REQUIRE_PROPERTY("double array", Double|PropertyType::Array);
  167. REQUIRE_PROPERTY("string array", String|PropertyType::Array);
  168. REQUIRE_PROPERTY("data array", Data|PropertyType::Array);
  169. REQUIRE_PROPERTY("date array", Date|PropertyType::Array);
  170. REQUIRE_PROPERTY("int? array", Int|PropertyType::Array|PropertyType::Nullable);
  171. REQUIRE_PROPERTY("bool? array", Bool|PropertyType::Array|PropertyType::Nullable);
  172. REQUIRE_PROPERTY("float? array", Float|PropertyType::Array|PropertyType::Nullable);
  173. REQUIRE_PROPERTY("double? array", Double|PropertyType::Array|PropertyType::Nullable);
  174. REQUIRE_PROPERTY("string? array", String|PropertyType::Array|PropertyType::Nullable);
  175. REQUIRE_PROPERTY("data? array", Data|PropertyType::Array|PropertyType::Nullable);
  176. REQUIRE_PROPERTY("date? array", Date|PropertyType::Array|PropertyType::Nullable);
  177. REQUIRE_PROPERTY("indexed int", Int, Property::IsPrimary{false}, Property::IsIndexed{true});
  178. REQUIRE_PROPERTY("indexed bool", Bool, Property::IsPrimary{false}, Property::IsIndexed{true});
  179. REQUIRE_PROPERTY("indexed string", String, Property::IsPrimary{false}, Property::IsIndexed{true});
  180. REQUIRE_PROPERTY("indexed date", Date, Property::IsPrimary{false}, Property::IsIndexed{true});
  181. REQUIRE_PROPERTY("indexed int?", Int|PropertyType::Nullable, Property::IsPrimary{false}, Property::IsIndexed{true});
  182. REQUIRE_PROPERTY("indexed bool?", Bool|PropertyType::Nullable, Property::IsPrimary{false}, Property::IsIndexed{true});
  183. REQUIRE_PROPERTY("indexed string?", String|PropertyType::Nullable, Property::IsPrimary{false}, Property::IsIndexed{true});
  184. REQUIRE_PROPERTY("indexed date?", Date|PropertyType::Nullable, Property::IsPrimary{false}, Property::IsIndexed{true});
  185. pk->set_string(1, 0, "nonexistent property");
  186. REQUIRE(ObjectSchema(g, "table").primary_key_property() == nullptr);
  187. }
  188. }
  189. TEST_CASE("Schema") {
  190. SECTION("validate()") {
  191. SECTION("rejects link properties with no target object") {
  192. Schema schema = {
  193. {"object", {
  194. {"link", PropertyType::Object|PropertyType::Nullable}
  195. }},
  196. };
  197. REQUIRE_THROWS_CONTAINING(schema.validate(), "Property 'object.link' of type 'object' has unknown object type ''");
  198. }
  199. SECTION("rejects array properties with no target object") {
  200. Schema schema = {
  201. {"object", {
  202. {"array", PropertyType::Array|PropertyType::Object}
  203. }},
  204. };
  205. REQUIRE_THROWS_CONTAINING(schema.validate(), "Property 'object.array' of type 'array' has unknown object type ''");
  206. }
  207. SECTION("rejects link properties with a target not in the schema") {
  208. Schema schema = {
  209. {"object", {
  210. {"link", PropertyType::Object|PropertyType::Nullable, "invalid target"}
  211. }}
  212. };
  213. REQUIRE_THROWS_CONTAINING(schema.validate(), "Property 'object.link' of type 'object' has unknown object type 'invalid target'");
  214. }
  215. SECTION("rejects array properties with a target not in the schema") {
  216. Schema schema = {
  217. {"object", {
  218. {"array", PropertyType::Array|PropertyType::Object, "invalid target"}
  219. }}
  220. };
  221. REQUIRE_THROWS_CONTAINING(schema.validate(), "Property 'object.array' of type 'array' has unknown object type 'invalid target'");
  222. }
  223. SECTION("rejects linking objects without a source object") {
  224. Schema schema = {
  225. {"object", {
  226. {"value", PropertyType::Int},
  227. }, {
  228. {"incoming", PropertyType::Array|PropertyType::LinkingObjects, "", ""}
  229. }}
  230. };
  231. REQUIRE_THROWS_CONTAINING(schema.validate(), "Property 'object.incoming' of type 'linking objects' has unknown object type ''");
  232. }
  233. SECTION("rejects linking objects without a source property") {
  234. Schema schema = {
  235. {"object", {
  236. {"value", PropertyType::Int},
  237. }, {
  238. {"incoming", PropertyType::Array|PropertyType::LinkingObjects, "object", ""}
  239. }}
  240. };
  241. REQUIRE_THROWS_CONTAINING(schema.validate(), "Property 'object.incoming' of type 'linking objects' must have an origin property name.");
  242. }
  243. SECTION("rejects linking objects with invalid source object") {
  244. Schema schema = {
  245. {"object", {
  246. {"value", PropertyType::Int},
  247. }, {
  248. {"incoming", PropertyType::Array|PropertyType::LinkingObjects, "not an object type", ""}
  249. }}
  250. };
  251. REQUIRE_THROWS_CONTAINING(schema.validate(), "Property 'object.incoming' of type 'linking objects' has unknown object type 'not an object type'");
  252. }
  253. SECTION("rejects linking objects with invalid source property") {
  254. Schema schema = {
  255. {"object", {
  256. {"value", PropertyType::Int},
  257. }, {
  258. {"incoming", PropertyType::Array|PropertyType::LinkingObjects, "object", "value"}
  259. }}
  260. };
  261. REQUIRE_THROWS_CONTAINING(schema.validate(), "Property 'object.value' declared as origin of linking objects property 'object.incoming' is not a link");
  262. schema = {
  263. {"object", {
  264. {"value", PropertyType::Int},
  265. {"link", PropertyType::Object|PropertyType::Nullable, "object 2"},
  266. }, {
  267. {"incoming", PropertyType::Array|PropertyType::LinkingObjects, "object", "link"}
  268. }},
  269. {"object 2", {
  270. {"value", PropertyType::Int},
  271. }}
  272. };
  273. REQUIRE_THROWS_CONTAINING(schema.validate(), "Property 'object.link' declared as origin of linking objects property 'object.incoming' links to type 'object 2'");
  274. }
  275. SECTION("rejects non-array linking objects") {
  276. Schema schema = {
  277. {"object", {
  278. {"link", PropertyType::Object|PropertyType::Nullable, "object"},
  279. }, {
  280. {"incoming", PropertyType::LinkingObjects, "object", "link"}
  281. }}
  282. };
  283. REQUIRE_THROWS_CONTAINING(schema.validate(), "Linking Objects property 'object.incoming' must be an array.");
  284. }
  285. SECTION("rejects target object types for non-link properties") {
  286. Schema schema = {
  287. {"object", {
  288. {"int", PropertyType::Int},
  289. {"bool", PropertyType::Bool},
  290. {"float", PropertyType::Float},
  291. {"double", PropertyType::Double},
  292. {"string", PropertyType::String},
  293. {"date", PropertyType::Date},
  294. }}
  295. };
  296. for (auto& prop : schema.begin()->persisted_properties) {
  297. REQUIRE_NOTHROW(schema.validate());
  298. prop.object_type = "object";
  299. REQUIRE_THROWS_CONTAINING(schema.validate(), "cannot have an object type.");
  300. prop.object_type = "";
  301. }
  302. }
  303. SECTION("rejects source property name for non-linking objects properties") {
  304. Schema schema = {
  305. {"object", {
  306. {"int", PropertyType::Int},
  307. {"bool", PropertyType::Bool},
  308. {"float", PropertyType::Float},
  309. {"double", PropertyType::Double},
  310. {"string", PropertyType::String},
  311. {"data", PropertyType::Data},
  312. {"date", PropertyType::Date},
  313. {"object", PropertyType::Object|PropertyType::Nullable, "object"},
  314. {"array", PropertyType::Object|PropertyType::Array, "object"},
  315. }}
  316. };
  317. for (auto& prop : schema.begin()->persisted_properties) {
  318. REQUIRE_NOTHROW(schema.validate());
  319. prop.link_origin_property_name = "source";
  320. auto expected = util::format("Property 'object.%1' of type '%1' cannot have an origin property name.", prop.name, prop.name);
  321. REQUIRE_THROWS_CONTAINING(schema.validate(), expected);
  322. prop.link_origin_property_name = "";
  323. }
  324. }
  325. SECTION("rejects non-nullable link properties") {
  326. Schema schema = {
  327. {"object", {
  328. {"link", PropertyType::Object, "target"}
  329. }},
  330. {"target", {
  331. {"value", PropertyType::Int}
  332. }}
  333. };
  334. REQUIRE_THROWS_CONTAINING(schema.validate(), "Property 'object.link' of type 'object' must be nullable.");
  335. }
  336. SECTION("rejects nullable array properties") {
  337. Schema schema = {
  338. {"object", {
  339. {"array", PropertyType::Array|PropertyType::Object|PropertyType::Nullable, "target"}
  340. }},
  341. {"target", {
  342. {"value", PropertyType::Int}
  343. }}
  344. };
  345. REQUIRE_THROWS_CONTAINING(schema.validate(), "Property 'object.array' of type 'array' cannot be nullable.");
  346. }
  347. SECTION("rejects nullable linking objects") {
  348. Schema schema = {
  349. {"object", {
  350. {"link", PropertyType::Object|PropertyType::Nullable, "object"},
  351. }, {
  352. {"incoming", PropertyType::LinkingObjects|PropertyType::Array|PropertyType::Nullable, "object", "link"}
  353. }}
  354. };
  355. REQUIRE_THROWS_CONTAINING(schema.validate(), "Property 'object.incoming' of type 'linking objects' cannot be nullable.");
  356. }
  357. SECTION("rejects duplicate primary keys") {
  358. Schema schema = {
  359. {"object", {
  360. {"pk1", PropertyType::Int, Property::IsPrimary{true}},
  361. {"pk2", PropertyType::Int, Property::IsPrimary{true}},
  362. }}
  363. };
  364. REQUIRE_THROWS_CONTAINING(schema.validate(), "Properties 'pk2' and 'pk1' are both marked as the primary key of 'object'.");
  365. }
  366. SECTION("rejects invalid primary key types") {
  367. Schema schema = {
  368. {"object", {
  369. {"pk", PropertyType::Float, Property::IsPrimary{true}},
  370. }}
  371. };
  372. schema.begin()->primary_key_property()->type = PropertyType::Any;
  373. REQUIRE_THROWS_CONTAINING(schema.validate(), "Property 'object.pk' of type 'any' cannot be made the primary key.");
  374. schema.begin()->primary_key_property()->type = PropertyType::Bool;
  375. REQUIRE_THROWS_CONTAINING(schema.validate(), "Property 'object.pk' of type 'bool' cannot be made the primary key.");
  376. schema.begin()->primary_key_property()->type = PropertyType::Float;
  377. REQUIRE_THROWS_CONTAINING(schema.validate(), "Property 'object.pk' of type 'float' cannot be made the primary key.");
  378. schema.begin()->primary_key_property()->type = PropertyType::Double;
  379. REQUIRE_THROWS_CONTAINING(schema.validate(), "Property 'object.pk' of type 'double' cannot be made the primary key.");
  380. schema.begin()->primary_key_property()->type = PropertyType::Object;
  381. REQUIRE_THROWS_CONTAINING(schema.validate(), "Property 'object.pk' of type 'object' cannot be made the primary key.");
  382. schema.begin()->primary_key_property()->type = PropertyType::LinkingObjects;
  383. REQUIRE_THROWS_CONTAINING(schema.validate(), "Property 'object.pk' of type 'linking objects' cannot be made the primary key.");
  384. schema.begin()->primary_key_property()->type = PropertyType::Data;
  385. REQUIRE_THROWS_CONTAINING(schema.validate(), "Property 'object.pk' of type 'data' cannot be made the primary key.");
  386. schema.begin()->primary_key_property()->type = PropertyType::Date;
  387. REQUIRE_THROWS_CONTAINING(schema.validate(), "Property 'object.pk' of type 'date' cannot be made the primary key.");
  388. }
  389. SECTION("allows valid primary key types") {
  390. Schema schema = {
  391. {"object", {
  392. {"pk", PropertyType::Int, Property::IsPrimary{true}},
  393. }}
  394. };
  395. REQUIRE_NOTHROW(schema.validate());
  396. schema.begin()->primary_key_property()->type = PropertyType::Int|PropertyType::Nullable;
  397. REQUIRE_NOTHROW(schema.validate());
  398. schema.begin()->primary_key_property()->type = PropertyType::String;
  399. REQUIRE_NOTHROW(schema.validate());
  400. schema.begin()->primary_key_property()->type = PropertyType::String|PropertyType::Nullable;
  401. REQUIRE_NOTHROW(schema.validate());
  402. }
  403. SECTION("rejects nonexistent primary key") {
  404. Schema schema = {
  405. {"object", {
  406. {"value", PropertyType::Int},
  407. }}
  408. };
  409. schema.begin()->primary_key = "nonexistent";
  410. REQUIRE_THROWS_CONTAINING(schema.validate(), "Specified primary key 'object.nonexistent' does not exist.");
  411. }
  412. SECTION("rejects indexes for types that cannot be indexed") {
  413. Schema schema = {
  414. {"object", {
  415. {"float", PropertyType::Float},
  416. {"double", PropertyType::Double},
  417. {"data", PropertyType::Data},
  418. {"object", PropertyType::Object|PropertyType::Nullable, "object"},
  419. {"array", PropertyType::Array|PropertyType::Object, "object"},
  420. }}
  421. };
  422. for (auto& prop : schema.begin()->persisted_properties) {
  423. REQUIRE_NOTHROW(schema.validate());
  424. prop.is_indexed = true;
  425. auto expected = util::format("Property 'object.%1' of type '%1' cannot be indexed.", prop.name);
  426. REQUIRE_THROWS_CONTAINING(schema.validate(), expected);
  427. prop.is_indexed = false;
  428. }
  429. }
  430. SECTION("allows indexing types that can be indexed") {
  431. Schema schema = {
  432. {"object", {
  433. {"int", PropertyType::Int, Property::IsPrimary{false}, Property::IsIndexed{true}},
  434. {"bool", PropertyType::Bool, Property::IsPrimary{false}, Property::IsIndexed{true}},
  435. {"string", PropertyType::String, Property::IsPrimary{false}, Property::IsIndexed{true}},
  436. {"date", PropertyType::Date, Property::IsPrimary{false}, Property::IsIndexed{true}},
  437. }}
  438. };
  439. REQUIRE_NOTHROW(schema.validate());
  440. }
  441. SECTION("rejects duplicate types with the same name") {
  442. Schema schema = {
  443. {"object1", {
  444. {"int", PropertyType::Int},
  445. }},
  446. {"object2", {
  447. {"int", PropertyType::Int},
  448. }},
  449. {"object3", {
  450. {"int", PropertyType::Int},
  451. }},
  452. {"object2", {
  453. {"int", PropertyType::Int},
  454. }},
  455. {"object1", {
  456. {"int", PropertyType::Int},
  457. }}
  458. };
  459. REQUIRE_THROWS_CONTAINING(schema.validate(),
  460. "- Type 'object1' appears more than once in the schema.\n"
  461. "- Type 'object2' appears more than once in the schema.");
  462. }
  463. }
  464. SECTION("compare()") {
  465. using namespace schema_change;
  466. using vec = std::vector<SchemaChange>;
  467. SECTION("add table") {
  468. Schema schema1 = {
  469. {"object 1", {
  470. {"int", PropertyType::Int},
  471. }}
  472. };
  473. Schema schema2 = {
  474. {"object 1", {
  475. {"int", PropertyType::Int},
  476. }},
  477. {"object 2", {
  478. {"int", PropertyType::Int},
  479. }}
  480. };
  481. auto obj = &*schema2.find("object 2");
  482. auto expected = vec{AddTable{obj}, AddInitialProperties{obj}};
  483. REQUIRE(schema1.compare(schema2) == expected);
  484. }
  485. SECTION("add property") {
  486. Schema schema1 = {
  487. {"object", {
  488. {"int 1", PropertyType::Int},
  489. }}
  490. };
  491. Schema schema2 = {
  492. {"object", {
  493. {"int 1", PropertyType::Int},
  494. {"int 2", PropertyType::Int},
  495. }}
  496. };
  497. REQUIRE(schema1.compare(schema2) == vec{(AddProperty{&*schema1.find("object"), &schema2.find("object")->persisted_properties[1]})});
  498. }
  499. SECTION("remove property") {
  500. Schema schema1 = {
  501. {"object", {
  502. {"int 1", PropertyType::Int},
  503. {"int 2", PropertyType::Int},
  504. }}
  505. };
  506. Schema schema2 = {
  507. {"object", {
  508. {"int 1", PropertyType::Int},
  509. }}
  510. };
  511. REQUIRE(schema1.compare(schema2) == vec{(RemoveProperty{&*schema1.find("object"), &schema1.find("object")->persisted_properties[1]})});
  512. }
  513. SECTION("change property type") {
  514. Schema schema1 = {
  515. {"object", {
  516. {"value", PropertyType::Int},
  517. }}
  518. };
  519. Schema schema2 = {
  520. {"object", {
  521. {"value", PropertyType::Double},
  522. }}
  523. };
  524. REQUIRE(schema1.compare(schema2) == vec{(ChangePropertyType{
  525. &*schema1.find("object"),
  526. &schema1.find("object")->persisted_properties[0],
  527. &schema2.find("object")->persisted_properties[0]})});
  528. };
  529. SECTION("change link target") {
  530. Schema schema1 = {
  531. {"object", {
  532. {"value", PropertyType::Object, "target 1"},
  533. }},
  534. {"target 1", {
  535. {"value", PropertyType::Int},
  536. }},
  537. {"target 2", {
  538. {"value", PropertyType::Int},
  539. }},
  540. };
  541. Schema schema2 = {
  542. {"object", {
  543. {"value", PropertyType::Object, "target 2"},
  544. }},
  545. {"target 1", {
  546. {"value", PropertyType::Int},
  547. }},
  548. {"target 2", {
  549. {"value", PropertyType::Int},
  550. }},
  551. };
  552. REQUIRE(schema1.compare(schema2) == vec{(ChangePropertyType{
  553. &*schema1.find("object"),
  554. &schema1.find("object")->persisted_properties[0],
  555. &schema2.find("object")->persisted_properties[0]})});
  556. }
  557. SECTION("add index") {
  558. Schema schema1 = {
  559. {"object", {
  560. {"int", PropertyType::Int},
  561. }}
  562. };
  563. Schema schema2 = {
  564. {"object", {
  565. {"int", PropertyType::Int, Property::IsPrimary{false}, Property::IsIndexed{true}},
  566. }}
  567. };
  568. auto object_schema = &*schema1.find("object");
  569. REQUIRE(schema1.compare(schema2) == vec{(AddIndex{object_schema, &object_schema->persisted_properties[0]})});
  570. }
  571. SECTION("remove index") {
  572. Schema schema1 = {
  573. {"object", {
  574. {"int", PropertyType::Int, Property::IsPrimary{false}, Property::IsIndexed{true}},
  575. }}
  576. };
  577. Schema schema2 = {
  578. {"object", {
  579. {"int", PropertyType::Int},
  580. }}
  581. };
  582. auto object_schema = &*schema1.find("object");
  583. REQUIRE(schema1.compare(schema2) == vec{(RemoveIndex{object_schema, &object_schema->persisted_properties[0]})});
  584. }
  585. SECTION("add index and make nullable") {
  586. Schema schema1 = {
  587. {"object", {
  588. {"int", PropertyType::Int},
  589. }}
  590. };
  591. Schema schema2 = {
  592. {"object", {
  593. {"int", PropertyType::Int|PropertyType::Nullable, Property::IsPrimary{false}, Property::IsIndexed{true}},
  594. }}
  595. };
  596. auto object_schema = &*schema1.find("object");
  597. REQUIRE(schema1.compare(schema2) == (vec{
  598. MakePropertyNullable{object_schema, &object_schema->persisted_properties[0]},
  599. AddIndex{object_schema, &object_schema->persisted_properties[0]}}));
  600. }
  601. SECTION("add index and change type") {
  602. Schema schema1 = {
  603. {"object", {
  604. {"value", PropertyType::Int},
  605. }}
  606. };
  607. Schema schema2 = {
  608. {"object", {
  609. {"value", PropertyType::Double, Property::IsPrimary{false}, Property::IsIndexed{true}},
  610. }}
  611. };
  612. REQUIRE(schema1.compare(schema2) == vec{(ChangePropertyType{
  613. &*schema1.find("object"),
  614. &schema1.find("object")->persisted_properties[0],
  615. &schema2.find("object")->persisted_properties[0]})});
  616. }
  617. SECTION("make nullable and change type") {
  618. Schema schema1 = {
  619. {"object", {
  620. {"value", PropertyType::Int},
  621. }}
  622. };
  623. Schema schema2 = {
  624. {"object", {
  625. {"value", PropertyType::Double|PropertyType::Nullable},
  626. }}
  627. };
  628. REQUIRE(schema1.compare(schema2) == vec{(ChangePropertyType{
  629. &*schema1.find("object"),
  630. &schema1.find("object")->persisted_properties[0],
  631. &schema2.find("object")->persisted_properties[0]})});
  632. }
  633. }
  634. }