schema.cpp 35 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811
  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/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("Aliases are still present in schema returned from the Realm") {
  75. TestFile config;
  76. config.schema_version = 1;
  77. config.schema = Schema{
  78. {"object", {
  79. {"value", PropertyType::Int, Property::IsPrimary{false}, Property::IsIndexed{false}, "alias"}
  80. }},
  81. };
  82. auto realm = Realm::get_shared_realm(config);
  83. REQUIRE(realm->schema().find("object")->property_for_name("value")->public_name == "alias");
  84. }
  85. SECTION("looking up properties by alias matches name if alias is not set") {
  86. auto schema = Schema{
  87. {"object", {
  88. {"value", PropertyType::Int, Property::IsPrimary{false}, Property::IsIndexed{false}, "alias"},
  89. {"other_value", PropertyType::Int, Property::IsPrimary{false}, Property::IsIndexed{false}}
  90. }},
  91. };
  92. REQUIRE(schema.find("object")->property_for_public_name("value") == nullptr);
  93. REQUIRE(schema.find("object")->property_for_public_name("alias")->name == "value");
  94. REQUIRE(schema.find("object")->property_for_public_name("other_value")->name == "other_value");
  95. }
  96. SECTION("from a Group") {
  97. Group g;
  98. TableRef table = g.add_table_with_primary_key("class_table", type_Int, "pk");
  99. TableRef target = g.add_table("class_target");
  100. table->add_column(type_Int, "int");
  101. table->add_column(type_Bool, "bool");
  102. table->add_column(type_Float, "float");
  103. table->add_column(type_Double, "double");
  104. table->add_column(type_String, "string");
  105. table->add_column(type_Binary, "data");
  106. table->add_column(type_Timestamp, "date");
  107. table->add_column_link(type_Link, "object", *target);
  108. table->add_column_link(type_LinkList, "array", *target);
  109. table->add_column(type_Int, "int?", true);
  110. table->add_column(type_Bool, "bool?", true);
  111. table->add_column(type_Float, "float?", true);
  112. table->add_column(type_Double, "double?", true);
  113. table->add_column(type_String, "string?", true);
  114. table->add_column(type_Binary, "data?", true);
  115. table->add_column(type_Timestamp, "date?", true);
  116. auto add_list = [](TableRef table, DataType type, StringData name, bool nullable) {
  117. table->add_column_list(type, name, nullable);
  118. };
  119. add_list(table, type_Int, "int array", false);
  120. add_list(table, type_Bool, "bool array", false);
  121. add_list(table, type_Float, "float array", false);
  122. add_list(table, type_Double, "double array", false);
  123. add_list(table, type_String, "string array", false);
  124. add_list(table, type_Binary, "data array", false);
  125. add_list(table, type_Timestamp, "date array", false);
  126. add_list(table, type_Int, "int? array", true);
  127. add_list(table, type_Bool, "bool? array", true);
  128. add_list(table, type_Float, "float? array", true);
  129. add_list(table, type_Double, "double? array", true);
  130. add_list(table, type_String, "string? array", true);
  131. add_list(table, type_Binary, "data? array", true);
  132. add_list(table, type_Timestamp, "date? array", true);
  133. std::vector<ColKey> indexed_cols;
  134. indexed_cols.push_back(table->add_column(type_Int, "indexed int"));
  135. indexed_cols.push_back(table->add_column(type_Bool, "indexed bool"));
  136. indexed_cols.push_back(table->add_column(type_String, "indexed string"));
  137. indexed_cols.push_back(table->add_column(type_Timestamp, "indexed date"));
  138. indexed_cols.push_back(table->add_column(type_Int, "indexed int?", true));
  139. indexed_cols.push_back(table->add_column(type_Bool, "indexed bool?", true));
  140. indexed_cols.push_back(table->add_column(type_String, "indexed string?", true));
  141. indexed_cols.push_back(table->add_column(type_Timestamp, "indexed date?", true));
  142. for (auto col : indexed_cols)
  143. table->add_search_index(col);
  144. ObjectSchema os(g, "table", table->get_key());
  145. REQUIRE(os.table_key == table->get_key());
  146. #define REQUIRE_PROPERTY(name, type, ...) do { \
  147. Property* prop; \
  148. REQUIRE((prop = os.property_for_name(name))); \
  149. REQUIRE((*prop == Property{name, PropertyType::type, __VA_ARGS__})); \
  150. REQUIRE(prop->column_key == *expected_col++); \
  151. } while (0)
  152. auto all_column_keys = table->get_column_keys();
  153. auto expected_col = all_column_keys.begin();
  154. REQUIRE(os.property_for_name("nonexistent property") == nullptr);
  155. REQUIRE_PROPERTY("pk", Int, Property::IsPrimary{true});
  156. REQUIRE_PROPERTY("int", Int);
  157. REQUIRE_PROPERTY("bool", Bool);
  158. REQUIRE_PROPERTY("float", Float);
  159. REQUIRE_PROPERTY("double", Double);
  160. REQUIRE_PROPERTY("string", String);
  161. REQUIRE_PROPERTY("data", Data);
  162. REQUIRE_PROPERTY("date", Date);
  163. REQUIRE_PROPERTY("object", Object|PropertyType::Nullable, "target");
  164. REQUIRE_PROPERTY("array", Array|PropertyType::Object, "target");
  165. REQUIRE_PROPERTY("int?", Int|PropertyType::Nullable);
  166. REQUIRE_PROPERTY("bool?", Bool|PropertyType::Nullable);
  167. REQUIRE_PROPERTY("float?", Float|PropertyType::Nullable);
  168. REQUIRE_PROPERTY("double?", Double|PropertyType::Nullable);
  169. REQUIRE_PROPERTY("string?", String|PropertyType::Nullable);
  170. REQUIRE_PROPERTY("data?", Data|PropertyType::Nullable);
  171. REQUIRE_PROPERTY("date?", Date|PropertyType::Nullable);
  172. REQUIRE_PROPERTY("int array", Int|PropertyType::Array);
  173. REQUIRE_PROPERTY("bool array", Bool|PropertyType::Array);
  174. REQUIRE_PROPERTY("float array", Float|PropertyType::Array);
  175. REQUIRE_PROPERTY("double array", Double|PropertyType::Array);
  176. REQUIRE_PROPERTY("string array", String|PropertyType::Array);
  177. REQUIRE_PROPERTY("data array", Data|PropertyType::Array);
  178. REQUIRE_PROPERTY("date array", Date|PropertyType::Array);
  179. REQUIRE_PROPERTY("int? array", Int|PropertyType::Array|PropertyType::Nullable);
  180. REQUIRE_PROPERTY("bool? array", Bool|PropertyType::Array|PropertyType::Nullable);
  181. REQUIRE_PROPERTY("float? array", Float|PropertyType::Array|PropertyType::Nullable);
  182. REQUIRE_PROPERTY("double? array", Double|PropertyType::Array|PropertyType::Nullable);
  183. REQUIRE_PROPERTY("string? array", String|PropertyType::Array|PropertyType::Nullable);
  184. REQUIRE_PROPERTY("data? array", Data|PropertyType::Array|PropertyType::Nullable);
  185. REQUIRE_PROPERTY("date? array", Date|PropertyType::Array|PropertyType::Nullable);
  186. REQUIRE_PROPERTY("indexed int", Int, Property::IsPrimary{false}, Property::IsIndexed{true});
  187. REQUIRE_PROPERTY("indexed bool", Bool, Property::IsPrimary{false}, Property::IsIndexed{true});
  188. REQUIRE_PROPERTY("indexed string", String, Property::IsPrimary{false}, Property::IsIndexed{true});
  189. REQUIRE_PROPERTY("indexed date", Date, Property::IsPrimary{false}, Property::IsIndexed{true});
  190. REQUIRE_PROPERTY("indexed int?", Int|PropertyType::Nullable, Property::IsPrimary{false}, Property::IsIndexed{true});
  191. REQUIRE_PROPERTY("indexed bool?", Bool|PropertyType::Nullable, Property::IsPrimary{false}, Property::IsIndexed{true});
  192. REQUIRE_PROPERTY("indexed string?", String|PropertyType::Nullable, Property::IsPrimary{false}, Property::IsIndexed{true});
  193. REQUIRE_PROPERTY("indexed date?", Date|PropertyType::Nullable, Property::IsPrimary{false}, Property::IsIndexed{true});
  194. }
  195. }
  196. TEST_CASE("Schema") {
  197. SECTION("validate()") {
  198. SECTION("rejects link properties with no target object") {
  199. Schema schema = {
  200. {"object", {
  201. {"link", PropertyType::Object|PropertyType::Nullable}
  202. }},
  203. };
  204. REQUIRE_THROWS_CONTAINING(schema.validate(), "Property 'object.link' of type 'object' has unknown object type ''");
  205. }
  206. SECTION("rejects array properties with no target object") {
  207. Schema schema = {
  208. {"object", {
  209. {"array", PropertyType::Array|PropertyType::Object}
  210. }},
  211. };
  212. REQUIRE_THROWS_CONTAINING(schema.validate(), "Property 'object.array' of type 'array' has unknown object type ''");
  213. }
  214. SECTION("rejects link properties with a target not in the schema") {
  215. Schema schema = {
  216. {"object", {
  217. {"link", PropertyType::Object|PropertyType::Nullable, "invalid target"}
  218. }}
  219. };
  220. REQUIRE_THROWS_CONTAINING(schema.validate(), "Property 'object.link' of type 'object' has unknown object type 'invalid target'");
  221. }
  222. SECTION("rejects array properties with a target not in the schema") {
  223. Schema schema = {
  224. {"object", {
  225. {"array", PropertyType::Array|PropertyType::Object, "invalid target"}
  226. }}
  227. };
  228. REQUIRE_THROWS_CONTAINING(schema.validate(), "Property 'object.array' of type 'array' has unknown object type 'invalid target'");
  229. }
  230. SECTION("rejects linking objects without a source object") {
  231. Schema schema = {
  232. {"object", {
  233. {"value", PropertyType::Int},
  234. }, {
  235. {"incoming", PropertyType::Array|PropertyType::LinkingObjects, "", ""}
  236. }}
  237. };
  238. REQUIRE_THROWS_CONTAINING(schema.validate(), "Property 'object.incoming' of type 'linking objects' has unknown object type ''");
  239. }
  240. SECTION("rejects linking objects without a source property") {
  241. Schema schema = {
  242. {"object", {
  243. {"value", PropertyType::Int},
  244. }, {
  245. {"incoming", PropertyType::Array|PropertyType::LinkingObjects, "object", ""}
  246. }}
  247. };
  248. REQUIRE_THROWS_CONTAINING(schema.validate(), "Property 'object.incoming' of type 'linking objects' must have an origin property name.");
  249. }
  250. SECTION("rejects linking objects with invalid source object") {
  251. Schema schema = {
  252. {"object", {
  253. {"value", PropertyType::Int},
  254. }, {
  255. {"incoming", PropertyType::Array|PropertyType::LinkingObjects, "not an object type", ""}
  256. }}
  257. };
  258. REQUIRE_THROWS_CONTAINING(schema.validate(), "Property 'object.incoming' of type 'linking objects' has unknown object type 'not an object type'");
  259. }
  260. SECTION("rejects linking objects with invalid source property") {
  261. Schema schema = {
  262. {"object", {
  263. {"value", PropertyType::Int},
  264. }, {
  265. {"incoming", PropertyType::Array|PropertyType::LinkingObjects, "object", "value"}
  266. }}
  267. };
  268. REQUIRE_THROWS_CONTAINING(schema.validate(), "Property 'object.value' declared as origin of linking objects property 'object.incoming' is not a link");
  269. schema = {
  270. {"object", {
  271. {"value", PropertyType::Int},
  272. {"link", PropertyType::Object|PropertyType::Nullable, "object 2"},
  273. }, {
  274. {"incoming", PropertyType::Array|PropertyType::LinkingObjects, "object", "link"}
  275. }},
  276. {"object 2", {
  277. {"value", PropertyType::Int},
  278. }}
  279. };
  280. REQUIRE_THROWS_CONTAINING(schema.validate(), "Property 'object.link' declared as origin of linking objects property 'object.incoming' links to type 'object 2'");
  281. }
  282. SECTION("rejects non-array linking objects") {
  283. Schema schema = {
  284. {"object", {
  285. {"link", PropertyType::Object|PropertyType::Nullable, "object"},
  286. }, {
  287. {"incoming", PropertyType::LinkingObjects, "object", "link"}
  288. }}
  289. };
  290. REQUIRE_THROWS_CONTAINING(schema.validate(), "Linking Objects property 'object.incoming' must be an array.");
  291. }
  292. SECTION("rejects target object types for non-link properties") {
  293. Schema schema = {
  294. {"object", {
  295. {"int", PropertyType::Int},
  296. {"bool", PropertyType::Bool},
  297. {"float", PropertyType::Float},
  298. {"double", PropertyType::Double},
  299. {"string", PropertyType::String},
  300. {"date", PropertyType::Date},
  301. }}
  302. };
  303. for (auto& prop : schema.begin()->persisted_properties) {
  304. REQUIRE_NOTHROW(schema.validate());
  305. prop.object_type = "object";
  306. REQUIRE_THROWS_CONTAINING(schema.validate(), "cannot have an object type.");
  307. prop.object_type = "";
  308. }
  309. }
  310. SECTION("rejects source property name for non-linking objects properties") {
  311. Schema schema = {
  312. {"object", {
  313. {"int", PropertyType::Int},
  314. {"bool", PropertyType::Bool},
  315. {"float", PropertyType::Float},
  316. {"double", PropertyType::Double},
  317. {"string", PropertyType::String},
  318. {"data", PropertyType::Data},
  319. {"date", PropertyType::Date},
  320. {"object", PropertyType::Object|PropertyType::Nullable, "object"},
  321. {"array", PropertyType::Object|PropertyType::Array, "object"},
  322. }}
  323. };
  324. for (auto& prop : schema.begin()->persisted_properties) {
  325. REQUIRE_NOTHROW(schema.validate());
  326. prop.link_origin_property_name = "source";
  327. auto expected = util::format("Property 'object.%1' of type '%1' cannot have an origin property name.", prop.name, prop.name);
  328. REQUIRE_THROWS_CONTAINING(schema.validate(), expected);
  329. prop.link_origin_property_name = "";
  330. }
  331. }
  332. SECTION("rejects non-nullable link properties") {
  333. Schema schema = {
  334. {"object", {
  335. {"link", PropertyType::Object, "target"}
  336. }},
  337. {"target", {
  338. {"value", PropertyType::Int}
  339. }}
  340. };
  341. REQUIRE_THROWS_CONTAINING(schema.validate(), "Property 'object.link' of type 'object' must be nullable.");
  342. }
  343. SECTION("rejects nullable array properties") {
  344. Schema schema = {
  345. {"object", {
  346. {"array", PropertyType::Array|PropertyType::Object|PropertyType::Nullable, "target"}
  347. }},
  348. {"target", {
  349. {"value", PropertyType::Int}
  350. }}
  351. };
  352. REQUIRE_THROWS_CONTAINING(schema.validate(), "Property 'object.array' of type 'array' cannot be nullable.");
  353. }
  354. SECTION("rejects nullable linking objects") {
  355. Schema schema = {
  356. {"object", {
  357. {"link", PropertyType::Object|PropertyType::Nullable, "object"},
  358. }, {
  359. {"incoming", PropertyType::LinkingObjects|PropertyType::Array|PropertyType::Nullable, "object", "link"}
  360. }}
  361. };
  362. REQUIRE_THROWS_CONTAINING(schema.validate(), "Property 'object.incoming' of type 'linking objects' cannot be nullable.");
  363. }
  364. SECTION("rejects duplicate primary keys") {
  365. Schema schema = {
  366. {"object", {
  367. {"pk1", PropertyType::Int, Property::IsPrimary{true}},
  368. {"pk2", PropertyType::Int, Property::IsPrimary{true}},
  369. }}
  370. };
  371. REQUIRE_THROWS_CONTAINING(schema.validate(), "Properties 'pk2' and 'pk1' are both marked as the primary key of 'object'.");
  372. }
  373. SECTION("rejects invalid primary key types") {
  374. Schema schema = {
  375. {"object", {
  376. {"pk", PropertyType::Float, Property::IsPrimary{true}},
  377. }}
  378. };
  379. schema.begin()->primary_key_property()->type = PropertyType::Any;
  380. REQUIRE_THROWS_CONTAINING(schema.validate(), "Property 'object.pk' of type 'any' cannot be made the primary key.");
  381. schema.begin()->primary_key_property()->type = PropertyType::Bool;
  382. REQUIRE_THROWS_CONTAINING(schema.validate(), "Property 'object.pk' of type 'bool' cannot be made the primary key.");
  383. schema.begin()->primary_key_property()->type = PropertyType::Float;
  384. REQUIRE_THROWS_CONTAINING(schema.validate(), "Property 'object.pk' of type 'float' cannot be made the primary key.");
  385. schema.begin()->primary_key_property()->type = PropertyType::Double;
  386. REQUIRE_THROWS_CONTAINING(schema.validate(), "Property 'object.pk' of type 'double' cannot be made the primary key.");
  387. schema.begin()->primary_key_property()->type = PropertyType::Object;
  388. REQUIRE_THROWS_CONTAINING(schema.validate(), "Property 'object.pk' of type 'object' cannot be made the primary key.");
  389. schema.begin()->primary_key_property()->type = PropertyType::LinkingObjects;
  390. REQUIRE_THROWS_CONTAINING(schema.validate(), "Property 'object.pk' of type 'linking objects' cannot be made the primary key.");
  391. schema.begin()->primary_key_property()->type = PropertyType::Data;
  392. REQUIRE_THROWS_CONTAINING(schema.validate(), "Property 'object.pk' of type 'data' cannot be made the primary key.");
  393. schema.begin()->primary_key_property()->type = PropertyType::Date;
  394. REQUIRE_THROWS_CONTAINING(schema.validate(), "Property 'object.pk' of type 'date' cannot be made the primary key.");
  395. }
  396. SECTION("allows valid primary key types") {
  397. Schema schema = {
  398. {"object", {
  399. {"pk", PropertyType::Int, Property::IsPrimary{true}},
  400. }}
  401. };
  402. REQUIRE_NOTHROW(schema.validate());
  403. schema.begin()->primary_key_property()->type = PropertyType::Int|PropertyType::Nullable;
  404. REQUIRE_NOTHROW(schema.validate());
  405. schema.begin()->primary_key_property()->type = PropertyType::String;
  406. REQUIRE_NOTHROW(schema.validate());
  407. schema.begin()->primary_key_property()->type = PropertyType::String|PropertyType::Nullable;
  408. REQUIRE_NOTHROW(schema.validate());
  409. }
  410. SECTION("rejects nonexistent primary key") {
  411. Schema schema = {
  412. {"object", {
  413. {"value", PropertyType::Int},
  414. }}
  415. };
  416. schema.begin()->primary_key = "nonexistent";
  417. REQUIRE_THROWS_CONTAINING(schema.validate(), "Specified primary key 'object.nonexistent' does not exist.");
  418. }
  419. SECTION("rejects indexes for types that cannot be indexed") {
  420. Schema schema = {
  421. {"object", {
  422. {"float", PropertyType::Float},
  423. {"double", PropertyType::Double},
  424. {"data", PropertyType::Data},
  425. {"object", PropertyType::Object|PropertyType::Nullable, "object"},
  426. {"array", PropertyType::Array|PropertyType::Object, "object"},
  427. }}
  428. };
  429. for (auto& prop : schema.begin()->persisted_properties) {
  430. REQUIRE_NOTHROW(schema.validate());
  431. prop.is_indexed = true;
  432. auto expected = util::format("Property 'object.%1' of type '%1' cannot be indexed.", prop.name);
  433. REQUIRE_THROWS_CONTAINING(schema.validate(), expected);
  434. prop.is_indexed = false;
  435. }
  436. }
  437. SECTION("allows indexing types that can be indexed") {
  438. Schema schema = {
  439. {"object", {
  440. {"int", PropertyType::Int, Property::IsPrimary{false}, Property::IsIndexed{true}},
  441. {"bool", PropertyType::Bool, Property::IsPrimary{false}, Property::IsIndexed{true}},
  442. {"string", PropertyType::String, Property::IsPrimary{false}, Property::IsIndexed{true}},
  443. {"date", PropertyType::Date, Property::IsPrimary{false}, Property::IsIndexed{true}},
  444. }}
  445. };
  446. REQUIRE_NOTHROW(schema.validate());
  447. }
  448. SECTION("rejects duplicate types with the same name") {
  449. Schema schema = {
  450. {"object1", {
  451. {"int", PropertyType::Int},
  452. }},
  453. {"object2", {
  454. {"int", PropertyType::Int},
  455. }},
  456. {"object3", {
  457. {"int", PropertyType::Int},
  458. }},
  459. {"object2", {
  460. {"int", PropertyType::Int},
  461. }},
  462. {"object1", {
  463. {"int", PropertyType::Int},
  464. }}
  465. };
  466. REQUIRE_THROWS_CONTAINING(schema.validate(),
  467. "- Type 'object1' appears more than once in the schema.\n"
  468. "- Type 'object2' appears more than once in the schema.");
  469. }
  470. SECTION("rejects properties with the same name") {
  471. Schema schema = {
  472. {"object", {
  473. {"child", PropertyType::Object|PropertyType::Nullable, "object"},
  474. {"parent", PropertyType::Int},
  475. {"field1", PropertyType::Int},
  476. {"field2", PropertyType::String},
  477. {"field1", PropertyType::String},
  478. {"field2", PropertyType::String},
  479. {"field1", PropertyType::Int},
  480. }, {
  481. {"parent", PropertyType::Array|PropertyType::LinkingObjects, "object", "child"}
  482. }}
  483. };
  484. REQUIRE_THROWS_CONTAINING(schema.validate(),
  485. "- Property 'field1' appears more than once in the schema for type 'object'.\n"
  486. "- Property 'field2' appears more than once in the schema for type 'object'.\n"
  487. "- Property 'parent' appears more than once in the schema for type 'object'.");
  488. }
  489. SECTION("rejects schema if all properties have the same name") {
  490. Schema schema = {
  491. {"object", {
  492. {"field", PropertyType::Int},
  493. {"otherField", PropertyType::Int},
  494. {"field", PropertyType::Int},
  495. {"otherField", PropertyType::Int},
  496. {"field", PropertyType::Int},
  497. {"otherField", PropertyType::Int},
  498. {"field", PropertyType::Int},
  499. {"otherField", PropertyType::Int},
  500. {"field", PropertyType::Int},
  501. {"otherField", PropertyType::Int},
  502. }}
  503. };
  504. REQUIRE_THROWS_CONTAINING(schema.validate(),
  505. "- Property 'field' appears more than once in the schema for type 'object'.\n"
  506. "- Property 'otherField' appears more than once in the schema for type 'object'.");
  507. }
  508. SECTION("rejects properties with the same alias") {
  509. Schema schema = {
  510. {"object", {
  511. {"child", PropertyType::Object|PropertyType::Nullable, "object"},
  512. // Alias == Name on computed property
  513. {"parentA", PropertyType::Int, Property::IsPrimary{false}, Property::IsIndexed{false}, "_parent"},
  514. // Name == Alias on other property
  515. {"fieldA", PropertyType::Int, Property::IsPrimary{false}, Property::IsIndexed{false}, "_field1"},
  516. {"fieldB", PropertyType::String, Property::IsPrimary{false}, Property::IsIndexed{false}, "_field2"},
  517. {"fieldC", PropertyType::String, Property::IsPrimary{false}, Property::IsIndexed{false}, "_field1"},
  518. {"fieldD", PropertyType::String, Property::IsPrimary{false}, Property::IsIndexed{false}, "_field2"},
  519. {"fieldE", PropertyType::Int, Property::IsPrimary{false}, Property::IsIndexed{false}, "_field1"},
  520. // Name == Alias
  521. {"fieldF", PropertyType::Int, Property::IsPrimary{false}, Property::IsIndexed{false}, "fieldF"},
  522. }, {
  523. // Computed property alias == name on persisted property
  524. {"parentB", PropertyType::Array|PropertyType::LinkingObjects, "object", "child", "_parent"}
  525. }}
  526. };
  527. REQUIRE_THROWS_CONTAINING(schema.validate(),
  528. "- Alias '_field1' appears more than once in the schema for type 'object'.\n"
  529. "- Alias '_field2' appears more than once in the schema for type 'object'.\n"
  530. "- Alias '_parent' appears more than once in the schema for type 'object'.");
  531. }
  532. SECTION("rejects properties whose name conflicts with an alias for another property") {
  533. Schema schema = {
  534. {"object", {
  535. {"child", PropertyType::Object|PropertyType::Nullable, "object"},
  536. {"field1", PropertyType::Int, Property::IsPrimary{false}, Property::IsIndexed{false}, "field2"},
  537. {"field2", PropertyType::Int, Property::IsPrimary{false}, Property::IsIndexed{false}, "parent"},
  538. }, {
  539. {"parent", PropertyType::Array|PropertyType::LinkingObjects, "object", "child", "field1"}
  540. }}
  541. };
  542. REQUIRE_THROWS_CONTAINING(schema.validate(),
  543. "- Property 'object.parent' has an alias 'field1' that conflicts with a property of the same name.\n"
  544. "- Property 'object.field1' has an alias 'field2' that conflicts with a property of the same name.\n"
  545. "- Property 'object.field2' has an alias 'parent' that conflicts with a property of the same name.");
  546. }
  547. }
  548. SECTION("compare()") {
  549. using namespace schema_change;
  550. using vec = std::vector<SchemaChange>;
  551. SECTION("add table") {
  552. Schema schema1 = {
  553. {"object 1", {
  554. {"int", PropertyType::Int},
  555. }}
  556. };
  557. Schema schema2 = {
  558. {"object 1", {
  559. {"int", PropertyType::Int},
  560. }},
  561. {"object 2", {
  562. {"int", PropertyType::Int},
  563. }}
  564. };
  565. auto obj = &*schema2.find("object 2");
  566. auto expected = vec{AddTable{obj}, AddInitialProperties{obj}};
  567. REQUIRE(schema1.compare(schema2) == expected);
  568. }
  569. SECTION("add property") {
  570. Schema schema1 = {
  571. {"object", {
  572. {"int 1", PropertyType::Int},
  573. }}
  574. };
  575. Schema schema2 = {
  576. {"object", {
  577. {"int 1", PropertyType::Int},
  578. {"int 2", PropertyType::Int},
  579. }}
  580. };
  581. REQUIRE(schema1.compare(schema2) == vec{(AddProperty{&*schema1.find("object"), &schema2.find("object")->persisted_properties[1]})});
  582. }
  583. SECTION("remove property") {
  584. Schema schema1 = {
  585. {"object", {
  586. {"int 1", PropertyType::Int},
  587. {"int 2", PropertyType::Int},
  588. }}
  589. };
  590. Schema schema2 = {
  591. {"object", {
  592. {"int 1", PropertyType::Int},
  593. }}
  594. };
  595. REQUIRE(schema1.compare(schema2) == vec{(RemoveProperty{&*schema1.find("object"), &schema1.find("object")->persisted_properties[1]})});
  596. }
  597. SECTION("change property type") {
  598. Schema schema1 = {
  599. {"object", {
  600. {"value", PropertyType::Int},
  601. }}
  602. };
  603. Schema schema2 = {
  604. {"object", {
  605. {"value", PropertyType::Double},
  606. }}
  607. };
  608. REQUIRE(schema1.compare(schema2) == vec{(ChangePropertyType{
  609. &*schema1.find("object"),
  610. &schema1.find("object")->persisted_properties[0],
  611. &schema2.find("object")->persisted_properties[0]})});
  612. };
  613. SECTION("change link target") {
  614. Schema schema1 = {
  615. {"object", {
  616. {"value", PropertyType::Object, "target 1"},
  617. }},
  618. {"target 1", {
  619. {"value", PropertyType::Int},
  620. }},
  621. {"target 2", {
  622. {"value", PropertyType::Int},
  623. }},
  624. };
  625. Schema schema2 = {
  626. {"object", {
  627. {"value", PropertyType::Object, "target 2"},
  628. }},
  629. {"target 1", {
  630. {"value", PropertyType::Int},
  631. }},
  632. {"target 2", {
  633. {"value", PropertyType::Int},
  634. }},
  635. };
  636. REQUIRE(schema1.compare(schema2) == vec{(ChangePropertyType{
  637. &*schema1.find("object"),
  638. &schema1.find("object")->persisted_properties[0],
  639. &schema2.find("object")->persisted_properties[0]})});
  640. }
  641. SECTION("add index") {
  642. Schema schema1 = {
  643. {"object", {
  644. {"int", PropertyType::Int},
  645. }}
  646. };
  647. Schema schema2 = {
  648. {"object", {
  649. {"int", PropertyType::Int, Property::IsPrimary{false}, Property::IsIndexed{true}},
  650. }}
  651. };
  652. auto object_schema = &*schema1.find("object");
  653. REQUIRE(schema1.compare(schema2) == vec{(AddIndex{object_schema, &object_schema->persisted_properties[0]})});
  654. }
  655. SECTION("remove index") {
  656. Schema schema1 = {
  657. {"object", {
  658. {"int", PropertyType::Int, Property::IsPrimary{false}, Property::IsIndexed{true}},
  659. }}
  660. };
  661. Schema schema2 = {
  662. {"object", {
  663. {"int", PropertyType::Int},
  664. }}
  665. };
  666. auto object_schema = &*schema1.find("object");
  667. REQUIRE(schema1.compare(schema2) == vec{(RemoveIndex{object_schema, &object_schema->persisted_properties[0]})});
  668. }
  669. SECTION("add index and make nullable") {
  670. Schema schema1 = {
  671. {"object", {
  672. {"int", PropertyType::Int},
  673. }}
  674. };
  675. Schema schema2 = {
  676. {"object", {
  677. {"int", PropertyType::Int|PropertyType::Nullable, Property::IsPrimary{false}, Property::IsIndexed{true}},
  678. }}
  679. };
  680. auto object_schema = &*schema1.find("object");
  681. REQUIRE(schema1.compare(schema2) == (vec{
  682. MakePropertyNullable{object_schema, &object_schema->persisted_properties[0]},
  683. AddIndex{object_schema, &object_schema->persisted_properties[0]}}));
  684. }
  685. SECTION("add index and change type") {
  686. Schema schema1 = {
  687. {"object", {
  688. {"value", PropertyType::Int},
  689. }}
  690. };
  691. Schema schema2 = {
  692. {"object", {
  693. {"value", PropertyType::Double, Property::IsPrimary{false}, Property::IsIndexed{true}},
  694. }}
  695. };
  696. REQUIRE(schema1.compare(schema2) == vec{(ChangePropertyType{
  697. &*schema1.find("object"),
  698. &schema1.find("object")->persisted_properties[0],
  699. &schema2.find("object")->persisted_properties[0]})});
  700. }
  701. SECTION("make nullable and change type") {
  702. Schema schema1 = {
  703. {"object", {
  704. {"value", PropertyType::Int},
  705. }}
  706. };
  707. Schema schema2 = {
  708. {"object", {
  709. {"value", PropertyType::Double|PropertyType::Nullable},
  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. }
  718. }