object.cpp 40 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007
  1. ////////////////////////////////////////////////////////////////////////////
  2. //
  3. // Copyright 2017 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/event_loop.hpp"
  20. #include "util/index_helpers.hpp"
  21. #include "util/test_file.hpp"
  22. #include "feature_checks.hpp"
  23. #include "collection_notifications.hpp"
  24. #include "object_accessor.hpp"
  25. #include "property.hpp"
  26. #include "schema.hpp"
  27. #include "impl/realm_coordinator.hpp"
  28. #include "impl/object_accessor_impl.hpp"
  29. #include <realm/group_shared.hpp>
  30. #include <realm/util/any.hpp>
  31. #include <cstdint>
  32. using namespace realm;
  33. namespace {
  34. using AnyDict = std::map<std::string, util::Any>;
  35. using AnyVec = std::vector<util::Any>;
  36. }
  37. struct TestContext : CppContext {
  38. std::map<std::string, AnyDict> defaults;
  39. using CppContext::CppContext;
  40. TestContext(TestContext& parent, realm::Property const& prop)
  41. : CppContext(parent, prop)
  42. , defaults(parent.defaults)
  43. { }
  44. util::Optional<util::Any>
  45. default_value_for_property(ObjectSchema const& object, Property const& prop)
  46. {
  47. auto obj_it = defaults.find(object.name);
  48. if (obj_it == defaults.end())
  49. return util::none;
  50. auto prop_it = obj_it->second.find(prop.name);
  51. if (prop_it == obj_it->second.end())
  52. return util::none;
  53. return prop_it->second;
  54. }
  55. void will_change(Object const&, Property const&) {}
  56. void did_change() {}
  57. std::string print(util::Any) { return "not implemented"; }
  58. bool allow_missing(util::Any) { return false; }
  59. };
  60. TEST_CASE("object") {
  61. using namespace std::string_literals;
  62. _impl::RealmCoordinator::assert_no_open_realms();
  63. InMemoryTestFile config;
  64. config.automatic_change_notifications = false;
  65. config.cache = false;
  66. config.schema = Schema{
  67. {"table", {
  68. {"value 1", PropertyType::Int},
  69. {"value 2", PropertyType::Int},
  70. }},
  71. {"all types", {
  72. {"pk", PropertyType::Int, Property::IsPrimary{true}},
  73. {"bool", PropertyType::Bool},
  74. {"int", PropertyType::Int},
  75. {"float", PropertyType::Float},
  76. {"double", PropertyType::Double},
  77. {"string", PropertyType::String},
  78. {"data", PropertyType::Data},
  79. {"date", PropertyType::Date},
  80. {"object", PropertyType::Object|PropertyType::Nullable, "link target"},
  81. {"bool array", PropertyType::Array|PropertyType::Bool},
  82. {"int array", PropertyType::Array|PropertyType::Int},
  83. {"float array", PropertyType::Array|PropertyType::Float},
  84. {"double array", PropertyType::Array|PropertyType::Double},
  85. {"string array", PropertyType::Array|PropertyType::String},
  86. {"data array", PropertyType::Array|PropertyType::Data},
  87. {"date array", PropertyType::Array|PropertyType::Date},
  88. {"object array", PropertyType::Array|PropertyType::Object, "array target"},
  89. }},
  90. {"all optional types", {
  91. {"pk", PropertyType::Int|PropertyType::Nullable, Property::IsPrimary{true}},
  92. {"bool", PropertyType::Bool|PropertyType::Nullable},
  93. {"int", PropertyType::Int|PropertyType::Nullable},
  94. {"float", PropertyType::Float|PropertyType::Nullable},
  95. {"double", PropertyType::Double|PropertyType::Nullable},
  96. {"string", PropertyType::String|PropertyType::Nullable},
  97. {"data", PropertyType::Data|PropertyType::Nullable},
  98. {"date", PropertyType::Date|PropertyType::Nullable},
  99. {"bool array", PropertyType::Array|PropertyType::Bool|PropertyType::Nullable},
  100. {"int array", PropertyType::Array|PropertyType::Int|PropertyType::Nullable},
  101. {"float array", PropertyType::Array|PropertyType::Float|PropertyType::Nullable},
  102. {"double array", PropertyType::Array|PropertyType::Double|PropertyType::Nullable},
  103. {"string array", PropertyType::Array|PropertyType::String|PropertyType::Nullable},
  104. {"data array", PropertyType::Array|PropertyType::Data|PropertyType::Nullable},
  105. {"date array", PropertyType::Array|PropertyType::Date|PropertyType::Nullable},
  106. }},
  107. {"link target", {
  108. {"value", PropertyType::Int},
  109. }, {
  110. {"origin", PropertyType::LinkingObjects|PropertyType::Array, "all types", "object"},
  111. }},
  112. {"array target", {
  113. {"value", PropertyType::Int},
  114. }},
  115. {"pk after list", {
  116. {"array 1", PropertyType::Array|PropertyType::Object, "array target"},
  117. {"int 1", PropertyType::Int},
  118. {"pk", PropertyType::Int, Property::IsPrimary{true}},
  119. {"int 2", PropertyType::Int},
  120. {"array 2", PropertyType::Array|PropertyType::Object, "array target"},
  121. }},
  122. {"nullable int pk", {
  123. {"pk", PropertyType::Int|PropertyType::Nullable, Property::IsPrimary{true}},
  124. }},
  125. {"nullable string pk", {
  126. {"pk", PropertyType::String|PropertyType::Nullable, Property::IsPrimary{true}},
  127. }},
  128. {"person", {
  129. {"name", PropertyType::String, Property::IsPrimary{true}},
  130. {"age", PropertyType::Int},
  131. {"scores", PropertyType::Array|PropertyType::Int},
  132. {"assistant", PropertyType::Object|PropertyType::Nullable, "person"},
  133. {"team", PropertyType::Array|PropertyType::Object, "person"},
  134. }},
  135. };
  136. config.schema_version = 0;
  137. auto r = Realm::get_shared_realm(config);
  138. auto& coordinator = *_impl::RealmCoordinator::get_existing_coordinator(config.path);
  139. SECTION("add_notification_callback()") {
  140. auto table = r->read_group().get_table("class_table");
  141. r->begin_transaction();
  142. table->add_empty_row(10);
  143. for (int i = 0; i < 10; ++i)
  144. table->set_int(0, i, i);
  145. r->commit_transaction();
  146. auto r2 = coordinator.get_realm();
  147. CollectionChangeSet change;
  148. Row row = table->get(0);
  149. Object object(r, *r->schema().find("table"), row);
  150. auto write = [&](auto&& f) {
  151. r->begin_transaction();
  152. f();
  153. r->commit_transaction();
  154. advance_and_notify(*r);
  155. };
  156. auto require_change = [&] {
  157. auto token = object.add_notification_callback([&](CollectionChangeSet c, std::exception_ptr) {
  158. change = c;
  159. });
  160. advance_and_notify(*r);
  161. return token;
  162. };
  163. auto require_no_change = [&] {
  164. bool first = true;
  165. auto token = object.add_notification_callback([&](CollectionChangeSet, std::exception_ptr) {
  166. REQUIRE(first);
  167. first = false;
  168. });
  169. advance_and_notify(*r);
  170. return token;
  171. };
  172. SECTION("deleting the object sends a change notification") {
  173. auto token = require_change();
  174. write([&] { row.move_last_over(); });
  175. REQUIRE_INDICES(change.deletions, 0);
  176. }
  177. SECTION("modifying the object sends a change notification") {
  178. auto token = require_change();
  179. write([&] { row.set_int(0, 10); });
  180. REQUIRE_INDICES(change.modifications, 0);
  181. REQUIRE(change.columns.size() == 1);
  182. REQUIRE_INDICES(change.columns[0], 0);
  183. write([&] { row.set_int(1, 10); });
  184. REQUIRE_INDICES(change.modifications, 0);
  185. REQUIRE(change.columns.size() == 2);
  186. REQUIRE(change.columns[0].empty());
  187. REQUIRE_INDICES(change.columns[1], 0);
  188. }
  189. SECTION("modifying a different object") {
  190. auto token = require_no_change();
  191. write([&] { table->get(1).set_int(0, 10); });
  192. }
  193. SECTION("moving the object") {
  194. auto token = require_no_change();
  195. write([&] { table->swap_rows(0, 5); });
  196. }
  197. SECTION("subsuming the object") {
  198. auto token = require_change();
  199. write([&] {
  200. table->insert_empty_row(0);
  201. table->merge_rows(row.get_index(), 0);
  202. row.set_int(0, 10);
  203. });
  204. REQUIRE(change.columns.size() == 1);
  205. REQUIRE_INDICES(change.columns[0], 0);
  206. }
  207. SECTION("multiple write transactions") {
  208. auto token = require_change();
  209. auto r2row = r2->read_group().get_table("class_table")->get(0);
  210. r2->begin_transaction();
  211. r2row.set_int(0, 1);
  212. r2->commit_transaction();
  213. r2->begin_transaction();
  214. r2row.set_int(1, 2);
  215. r2->commit_transaction();
  216. advance_and_notify(*r);
  217. REQUIRE(change.columns.size() == 2);
  218. REQUIRE_INDICES(change.columns[0], 0);
  219. REQUIRE_INDICES(change.columns[1], 0);
  220. }
  221. SECTION("skipping a notification") {
  222. auto token = require_no_change();
  223. write([&] {
  224. row.set_int(0, 1);
  225. token.suppress_next();
  226. });
  227. }
  228. SECTION("skipping only effects the current transaction even if no notification would occur anyway") {
  229. auto token = require_change();
  230. // would not produce a notification even if it wasn't skipped because no changes were made
  231. write([&] {
  232. token.suppress_next();
  233. });
  234. REQUIRE(change.empty());
  235. // should now produce a notification
  236. write([&] {
  237. row.set_int(0, 1);
  238. });
  239. REQUIRE_INDICES(change.modifications, 0);
  240. }
  241. SECTION("add notification callback, remove it, then add another notification callback") {
  242. {
  243. auto token = object.add_notification_callback([&](CollectionChangeSet, std::exception_ptr) {
  244. FAIL("This should never happen");
  245. });
  246. }
  247. auto token = require_change();
  248. write([&] { row.move_last_over(); });
  249. REQUIRE_INDICES(change.deletions, 0);
  250. }
  251. SECTION("observing deleted object throws") {
  252. write([&] {
  253. row.move_last_over();
  254. });
  255. REQUIRE_THROWS(require_change());
  256. }
  257. }
  258. TestContext d(r);
  259. auto create = [&](util::Any&& value, CreatePolicy policy = CreatePolicy::ForceCreate) {
  260. r->begin_transaction();
  261. auto obj = Object::create(d, r, *r->schema().find("all types"), value, policy);
  262. r->commit_transaction();
  263. return obj;
  264. };
  265. auto create_sub = [&](util::Any&& value, CreatePolicy policy = CreatePolicy::ForceCreate) {
  266. r->begin_transaction();
  267. auto obj = Object::create(d, r, *r->schema().find("link target"), value, policy);
  268. r->commit_transaction();
  269. return obj;
  270. };
  271. auto create_company = [&](util::Any&& value, CreatePolicy policy = CreatePolicy::ForceCreate) {
  272. r->begin_transaction();
  273. auto obj = Object::create(d, r, *r->schema().find("person"), value, policy);
  274. r->commit_transaction();
  275. return obj;
  276. };
  277. SECTION("create object") {
  278. auto obj = create(AnyDict{
  279. {"pk", INT64_C(1)},
  280. {"bool", true},
  281. {"int", INT64_C(5)},
  282. {"float", 2.2f},
  283. {"double", 3.3},
  284. {"string", "hello"s},
  285. {"data", "olleh"s},
  286. {"date", Timestamp(10, 20)},
  287. {"object", AnyDict{{"value", INT64_C(10)}}},
  288. {"bool array", AnyVec{true, false}},
  289. {"int array", AnyVec{INT64_C(5), INT64_C(6)}},
  290. {"float array", AnyVec{1.1f, 2.2f}},
  291. {"double array", AnyVec{3.3, 4.4}},
  292. {"string array", AnyVec{"a"s, "b"s, "c"s}},
  293. {"data array", AnyVec{"d"s, "e"s, "f"s}},
  294. {"date array", AnyVec{}},
  295. {"object array", AnyVec{AnyDict{{"value", INT64_C(20)}}}},
  296. });
  297. auto row = obj.row();
  298. REQUIRE(row.get_int(0) == 1);
  299. REQUIRE(row.get_bool(1) == true);
  300. REQUIRE(row.get_int(2) == 5);
  301. REQUIRE(row.get_float(3) == 2.2f);
  302. REQUIRE(row.get_double(4) == 3.3);
  303. REQUIRE(row.get_string(5) == "hello");
  304. REQUIRE(row.get_binary(6) == BinaryData("olleh", 5));
  305. REQUIRE(row.get_timestamp(7) == Timestamp(10, 20));
  306. REQUIRE(row.get_link(8) == 0);
  307. auto link_target = r->read_group().get_table("class_link target")->get(0);
  308. REQUIRE(link_target.get_int(0) == 10);
  309. auto check_array = [&](size_t col, auto... values) {
  310. auto table = row.get_subtable(col);
  311. size_t i = 0;
  312. for (auto& value : {values...}) {
  313. CAPTURE(i);
  314. REQUIRE(i < row.get_subtable_size(col));
  315. REQUIRE(value == table->get<typename std::decay<decltype(value)>::type>(0, i));
  316. ++i;
  317. }
  318. };
  319. check_array(9, true, false);
  320. check_array(10, INT64_C(5), INT64_C(6));
  321. check_array(11, 1.1f, 2.2f);
  322. check_array(12, 3.3, 4.4);
  323. check_array(13, StringData("a"), StringData("b"), StringData("c"));
  324. check_array(14, BinaryData("d", 1), BinaryData("e", 1), BinaryData("f", 1));
  325. auto list = row.get_linklist(16);
  326. REQUIRE(list->size() == 1);
  327. REQUIRE(list->get(0).get_int(0) == 20);
  328. }
  329. SECTION("create uses defaults for missing values") {
  330. d.defaults["all types"] = {
  331. {"bool", true},
  332. {"int", INT64_C(5)},
  333. {"float", 2.2f},
  334. {"double", 3.3},
  335. {"string", "hello"s},
  336. {"data", "olleh"s},
  337. {"date", Timestamp(10, 20)},
  338. {"object", AnyDict{{"value", INT64_C(10)}}},
  339. {"bool array", AnyVec{true, false}},
  340. {"int array", AnyVec{INT64_C(5), INT64_C(6)}},
  341. {"float array", AnyVec{1.1f, 2.2f}},
  342. {"double array", AnyVec{3.3, 4.4}},
  343. {"string array", AnyVec{"a"s, "b"s, "c"s}},
  344. {"data array", AnyVec{"d"s, "e"s, "f"s}},
  345. {"date array", AnyVec{}},
  346. {"object array", AnyVec{AnyDict{{"value", INT64_C(20)}}}},
  347. };
  348. auto obj = create(AnyDict{
  349. {"pk", INT64_C(1)},
  350. {"float", 6.6f},
  351. });
  352. auto row = obj.row();
  353. REQUIRE(row.get_int(0) == 1);
  354. REQUIRE(row.get_bool(1) == true);
  355. REQUIRE(row.get_int(2) == 5);
  356. REQUIRE(row.get_float(3) == 6.6f);
  357. REQUIRE(row.get_double(4) == 3.3);
  358. REQUIRE(row.get_string(5) == "hello");
  359. REQUIRE(row.get_binary(6) == BinaryData("olleh", 5));
  360. REQUIRE(row.get_timestamp(7) == Timestamp(10, 20));
  361. REQUIRE(row.get_subtable(9)->size() == 2);
  362. REQUIRE(row.get_subtable(10)->size() == 2);
  363. REQUIRE(row.get_subtable(11)->size() == 2);
  364. REQUIRE(row.get_subtable(12)->size() == 2);
  365. REQUIRE(row.get_subtable(13)->size() == 3);
  366. REQUIRE(row.get_subtable(14)->size() == 3);
  367. REQUIRE(row.get_subtable(15)->size() == 0);
  368. REQUIRE(row.get_linklist(16)->size() == 1);
  369. }
  370. SECTION("create can use defaults for primary key") {
  371. d.defaults["all types"] = {
  372. {"pk", INT64_C(10)},
  373. };
  374. auto obj = create(AnyDict{
  375. {"bool", true},
  376. {"int", INT64_C(5)},
  377. {"float", 2.2f},
  378. {"double", 3.3},
  379. {"string", "hello"s},
  380. {"data", "olleh"s},
  381. {"date", Timestamp(10, 20)},
  382. {"object", AnyDict{{"value", INT64_C(10)}}},
  383. {"array", AnyVector{AnyDict{{"value", INT64_C(20)}}}},
  384. });
  385. auto row = obj.row();
  386. REQUIRE(row.get_int(0) == 10);
  387. }
  388. SECTION("create does not complain about missing values for nullable fields") {
  389. r->begin_transaction();
  390. realm::Object obj;
  391. REQUIRE_NOTHROW(obj = Object::create(d, r, *r->schema().find("all optional types"), util::Any(AnyDict{})));
  392. r->commit_transaction();
  393. REQUIRE_FALSE(obj.get_property_value<util::Any>(d, "pk").has_value());
  394. REQUIRE_FALSE(obj.get_property_value<util::Any>(d, "bool").has_value());
  395. REQUIRE_FALSE(obj.get_property_value<util::Any>(d, "int").has_value());
  396. REQUIRE_FALSE(obj.get_property_value<util::Any>(d, "float").has_value());
  397. REQUIRE_FALSE(obj.get_property_value<util::Any>(d, "double").has_value());
  398. REQUIRE_FALSE(obj.get_property_value<util::Any>(d, "string").has_value());
  399. REQUIRE_FALSE(obj.get_property_value<util::Any>(d, "data").has_value());
  400. REQUIRE_FALSE(obj.get_property_value<util::Any>(d, "date").has_value());
  401. REQUIRE(any_cast<List&&>(obj.get_property_value<util::Any>(d, "bool array")).size() == 0);
  402. REQUIRE(any_cast<List&&>(obj.get_property_value<util::Any>(d, "int array")).size() == 0);
  403. REQUIRE(any_cast<List&&>(obj.get_property_value<util::Any>(d, "float array")).size() == 0);
  404. REQUIRE(any_cast<List&&>(obj.get_property_value<util::Any>(d, "double array")).size() == 0);
  405. REQUIRE(any_cast<List&&>(obj.get_property_value<util::Any>(d, "string array")).size() == 0);
  406. REQUIRE(any_cast<List&&>(obj.get_property_value<util::Any>(d, "data array")).size() == 0);
  407. REQUIRE(any_cast<List&&>(obj.get_property_value<util::Any>(d, "date array")).size() == 0);
  408. }
  409. SECTION("create throws for missing values if there is no default") {
  410. REQUIRE_THROWS(create(AnyDict{
  411. {"pk", INT64_C(1)},
  412. {"float", 6.6f},
  413. }));
  414. }
  415. SECTION("create always sets the PK first") {
  416. AnyDict value{
  417. {"array 1", AnyVector{AnyDict{{"value", INT64_C(1)}}}},
  418. {"array 2", AnyVector{AnyDict{{"value", INT64_C(2)}}}},
  419. {"int 1", INT64_C(0)},
  420. {"int 2", INT64_C(0)},
  421. {"pk", INT64_C(7)},
  422. };
  423. // Core will throw if the list is populated before the PK is set
  424. r->begin_transaction();
  425. REQUIRE_NOTHROW(Object::create(d, r, *r->schema().find("pk after list"), util::Any(value)));
  426. }
  427. SECTION("create with update") {
  428. CollectionChangeSet change;
  429. bool callback_called;
  430. Object obj = create(AnyDict{
  431. {"pk", INT64_C(1)},
  432. {"bool", true},
  433. {"int", INT64_C(5)},
  434. {"float", 2.2f},
  435. {"double", 3.3},
  436. {"string", "hello"s},
  437. {"data", "olleh"s},
  438. {"date", Timestamp(10, 20)},
  439. {"object", AnyDict{{"value", INT64_C(10)}}},
  440. {"bool array", AnyVec{true, false}},
  441. {"int array", AnyVec{INT64_C(5), INT64_C(6)}},
  442. {"float array", AnyVec{1.1f, 2.2f}},
  443. {"double array", AnyVec{3.3, 4.4}},
  444. {"string array", AnyVec{"a"s, "b"s, "c"s}},
  445. {"data array", AnyVec{"d"s, "e"s, "f"s}},
  446. {"date array", AnyVec{}},
  447. {"object array", AnyVec{AnyDict{{"value", INT64_C(20)}}}},
  448. });
  449. auto token = obj.add_notification_callback([&](CollectionChangeSet c, std::exception_ptr) {
  450. change = c;
  451. callback_called = true;
  452. });
  453. advance_and_notify(*r);
  454. create(AnyDict{
  455. {"pk", INT64_C(1)},
  456. {"int", INT64_C(6)},
  457. {"string", "a"s},
  458. }, CreatePolicy::UpdateAll);
  459. callback_called = false;
  460. advance_and_notify(*r);
  461. REQUIRE(callback_called);
  462. REQUIRE_INDICES(change.modifications, 0);
  463. auto row = obj.row();
  464. REQUIRE(row.get_int(0) == 1);
  465. REQUIRE(row.get_bool(1) == true);
  466. REQUIRE(row.get_int(2) == 6);
  467. REQUIRE(row.get_float(3) == 2.2f);
  468. REQUIRE(row.get_double(4) == 3.3);
  469. REQUIRE(row.get_string(5) == "a");
  470. REQUIRE(row.get_binary(6) == BinaryData("olleh", 5));
  471. REQUIRE(row.get_timestamp(7) == Timestamp(10, 20));
  472. }
  473. SECTION("create with update - only with diffs") {
  474. CollectionChangeSet change;
  475. bool callback_called;
  476. AnyDict adam {
  477. {"name", "Adam"s},
  478. {"age", INT64_C(32)},
  479. {"scores", AnyVec{INT64_C(1), INT64_C(2)}},
  480. };
  481. AnyDict brian {
  482. {"name", "Brian"s},
  483. {"age", INT64_C(33)},
  484. };
  485. AnyDict charley {
  486. {"name", "Charley"s},
  487. {"age", INT64_C(34)},
  488. {"team", AnyVec{adam, brian}}
  489. };
  490. AnyDict donald {
  491. {"name", "Donald"s},
  492. {"age", INT64_C(35)},
  493. };
  494. AnyDict eddie {
  495. {"name", "Eddie"s},
  496. {"age", INT64_C(36)},
  497. {"assistant", donald},
  498. {"team", AnyVec{donald, charley}}
  499. };
  500. Object obj = create_company(eddie, CreatePolicy::UpdateAll);
  501. auto table = r->read_group().get_table("class_person");
  502. REQUIRE(table->size() == 5);
  503. Results result(r, *table);
  504. auto token = result.add_notification_callback([&](CollectionChangeSet c, std::exception_ptr) {
  505. change = c;
  506. callback_called = true;
  507. });
  508. advance_and_notify(*r);
  509. // First update unconditionally
  510. create_company(eddie, CreatePolicy::UpdateAll);
  511. callback_called = false;
  512. advance_and_notify(*r);
  513. REQUIRE(callback_called);
  514. REQUIRE_INDICES(change.modifications, 0, 1, 2, 3, 4);
  515. // Now, only update where differences (there should not be any diffs - so no update)
  516. create_company(eddie, CreatePolicy::UpdateModified);
  517. REQUIRE(table->size() == 5);
  518. callback_called = false;
  519. advance_and_notify(*r);
  520. REQUIRE(!callback_called);
  521. // Now, only update sub-object)
  522. donald["scores"] = AnyVec{INT64_C(3), INT64_C(4), INT64_C(5)};
  523. // Insert the new donald
  524. eddie["assistant"] = donald;
  525. create_company(eddie, CreatePolicy::UpdateModified);
  526. REQUIRE(table->size() == 5);
  527. callback_called = false;
  528. advance_and_notify(*r);
  529. REQUIRE(callback_called);
  530. REQUIRE_INDICES(change.modifications, 1);
  531. // Shorten list
  532. donald["scores"] = AnyVec{INT64_C(3), INT64_C(4)};
  533. eddie["assistant"] = donald;
  534. create_company(eddie, CreatePolicy::UpdateModified);
  535. REQUIRE(table->size() == 5);
  536. callback_called = false;
  537. advance_and_notify(*r);
  538. REQUIRE(callback_called);
  539. REQUIRE_INDICES(change.modifications, 1);
  540. }
  541. SECTION("create with update - identical sub-object") {
  542. Object sub_obj = create_sub(AnyDict{{"value", INT64_C(10)}});
  543. Object obj = create(AnyDict{
  544. {"pk", INT64_C(1)},
  545. {"bool", true},
  546. {"int", INT64_C(5)},
  547. {"float", 2.2f},
  548. {"double", 3.3},
  549. {"string", "hello"s},
  550. {"data", "olleh"s},
  551. {"date", Timestamp(10, 20)},
  552. {"object", sub_obj},
  553. });
  554. auto obj_table = r->read_group().get_table("class_all types");
  555. Results result(r, *obj_table);
  556. bool callback_called;
  557. bool results_callback_called;
  558. bool sub_callback_called;
  559. auto token1 = obj.add_notification_callback([&](CollectionChangeSet, std::exception_ptr) {
  560. callback_called = true;
  561. });
  562. auto token2 = result.add_notification_callback([&](CollectionChangeSet, std::exception_ptr) {
  563. results_callback_called = true;
  564. });
  565. auto token3 = sub_obj.add_notification_callback([&](CollectionChangeSet, std::exception_ptr) {
  566. sub_callback_called = true;
  567. });
  568. advance_and_notify(*r);
  569. auto table = r->read_group().get_table("class_link target");
  570. REQUIRE(table->size() == 1);
  571. create(AnyDict{
  572. {"pk", INT64_C(1)},
  573. {"bool", true},
  574. {"int", INT64_C(5)},
  575. {"float", 2.2f},
  576. {"double", 3.3},
  577. {"string", "hello"s},
  578. {"data", "olleh"s},
  579. {"date", Timestamp(10, 20)},
  580. {"object", AnyDict{{"value", INT64_C(10)}}},
  581. }, CreatePolicy::UpdateModified);
  582. REQUIRE(table->size() == 1);
  583. callback_called = false;
  584. results_callback_called = false;
  585. sub_callback_called = false;
  586. advance_and_notify(*r);
  587. REQUIRE(!callback_called);
  588. REQUIRE(!results_callback_called);
  589. REQUIRE(!sub_callback_called);
  590. // Now change sub object
  591. create(AnyDict{
  592. {"pk", INT64_C(1)},
  593. {"bool", true},
  594. {"int", INT64_C(5)},
  595. {"float", 2.2f},
  596. {"double", 3.3},
  597. {"string", "hello"s},
  598. {"data", "olleh"s},
  599. {"date", Timestamp(10, 20)},
  600. {"object", AnyDict{{"value", INT64_C(11)}}},
  601. }, CreatePolicy::UpdateModified);
  602. callback_called = false;
  603. results_callback_called = false;
  604. sub_callback_called = false;
  605. advance_and_notify(*r);
  606. REQUIRE(!callback_called);
  607. REQUIRE(results_callback_called);
  608. REQUIRE(sub_callback_called);
  609. }
  610. SECTION("create with update - identical array of sub-objects") {
  611. bool callback_called;
  612. auto dict = AnyDict{
  613. {"pk", INT64_C(1)},
  614. {"bool", true},
  615. {"int", INT64_C(5)},
  616. {"float", 2.2f},
  617. {"double", 3.3},
  618. {"string", "hello"s},
  619. {"data", "olleh"s},
  620. {"date", Timestamp(10, 20)},
  621. {"object array", AnyVec{AnyDict{{"value", INT64_C(20)}}, AnyDict{{"value", INT64_C(21)}}}},
  622. };
  623. Object obj = create(dict);
  624. auto obj_table = r->read_group().get_table("class_all types");
  625. Results result(r, *obj_table);
  626. auto token1 = result.add_notification_callback([&](CollectionChangeSet, std::exception_ptr) {
  627. callback_called = true;
  628. });
  629. advance_and_notify(*r);
  630. create(dict, CreatePolicy::UpdateModified);
  631. callback_called = false;
  632. advance_and_notify(*r);
  633. REQUIRE(!callback_called);
  634. // Now change list
  635. dict["object array"] = AnyVec{AnyDict{{"value", INT64_C(23)}}};
  636. create(dict, CreatePolicy::UpdateModified);
  637. callback_called = false;
  638. advance_and_notify(*r);
  639. REQUIRE(callback_called);
  640. }
  641. for (auto policy : {CreatePolicy::UpdateAll, CreatePolicy::UpdateModified}) {
  642. SECTION("set existing fields to null with update "s + (policy == CreatePolicy::UpdateModified ? "(diffed)" : "(all)")) {
  643. AnyDict initial_values{
  644. {"pk", INT64_C(1)},
  645. {"bool", true},
  646. {"int", INT64_C(5)},
  647. {"float", 2.2f},
  648. {"double", 3.3},
  649. {"string", "hello"s},
  650. {"data", "olleh"s},
  651. {"date", Timestamp(10, 20)},
  652. {"bool array", AnyVec{true, false}},
  653. {"int array", AnyVec{INT64_C(5), INT64_C(6)}},
  654. {"float array", AnyVec{1.1f, 2.2f}},
  655. {"double array", AnyVec{3.3, 4.4}},
  656. {"string array", AnyVec{"a"s, "b"s, "c"s}},
  657. {"data array", AnyVec{"d"s, "e"s, "f"s}},
  658. {"date array", AnyVec{}},
  659. {"object array", AnyVec{AnyDict{{"value", INT64_C(20)}}}},
  660. };
  661. r->begin_transaction();
  662. auto obj = Object::create(d, r, *r->schema().find("all optional types"), util::Any(initial_values));
  663. // Missing fields in dictionary do not update anything
  664. Object::create(d, r, *r->schema().find("all optional types"),
  665. util::Any(AnyDict{{"pk", INT64_C(1)}}), policy);
  666. REQUIRE(any_cast<bool>(obj.get_property_value<util::Any>(d, "bool")) == true);
  667. REQUIRE(any_cast<int64_t>(obj.get_property_value<util::Any>(d, "int")) == 5);
  668. REQUIRE(any_cast<float>(obj.get_property_value<util::Any>(d, "float")) == 2.2f);
  669. REQUIRE(any_cast<double>(obj.get_property_value<util::Any>(d, "double")) == 3.3);
  670. REQUIRE(any_cast<std::string>(obj.get_property_value<util::Any>(d, "string")) == "hello");
  671. REQUIRE(any_cast<Timestamp>(obj.get_property_value<util::Any>(d, "date")) == Timestamp(10, 20));
  672. REQUIRE(any_cast<List&&>(obj.get_property_value<util::Any>(d, "bool array")).get<util::Optional<bool>>(0) == true);
  673. REQUIRE(any_cast<List&&>(obj.get_property_value<util::Any>(d, "int array")).get<util::Optional<int64_t>>(0) == 5);
  674. REQUIRE(any_cast<List&&>(obj.get_property_value<util::Any>(d, "float array")).get<util::Optional<float>>(0) == 1.1f);
  675. REQUIRE(any_cast<List&&>(obj.get_property_value<util::Any>(d, "double array")).get<util::Optional<double>>(0) == 3.3);
  676. REQUIRE(any_cast<List&&>(obj.get_property_value<util::Any>(d, "string array")).get<StringData>(0) == "a");
  677. REQUIRE(any_cast<List&&>(obj.get_property_value<util::Any>(d, "date array")).size() == 0);
  678. // Set all properties to null
  679. AnyDict null_values{
  680. {"pk", INT64_C(1)},
  681. {"bool", util::Any()},
  682. {"int", util::Any()},
  683. {"float", util::Any()},
  684. {"double", util::Any()},
  685. {"string", util::Any()},
  686. {"data", util::Any()},
  687. {"date", util::Any()},
  688. {"bool array", AnyVec{util::Any()}},
  689. {"int array", AnyVec{util::Any()}},
  690. {"float array", AnyVec{util::Any()}},
  691. {"double array", AnyVec{util::Any()}},
  692. {"string array", AnyVec{util::Any()}},
  693. {"data array", AnyVec{util::Any()}},
  694. {"date array", AnyVec{Timestamp()}},
  695. };
  696. Object::create(d, r, *r->schema().find("all optional types"), util::Any(null_values), policy);
  697. REQUIRE_FALSE(obj.get_property_value<util::Any>(d, "bool").has_value());
  698. REQUIRE_FALSE(obj.get_property_value<util::Any>(d, "int").has_value());
  699. REQUIRE_FALSE(obj.get_property_value<util::Any>(d, "float").has_value());
  700. REQUIRE_FALSE(obj.get_property_value<util::Any>(d, "double").has_value());
  701. REQUIRE_FALSE(obj.get_property_value<util::Any>(d, "string").has_value());
  702. REQUIRE_FALSE(obj.get_property_value<util::Any>(d, "data").has_value());
  703. REQUIRE_FALSE(obj.get_property_value<util::Any>(d, "date").has_value());
  704. REQUIRE(any_cast<List&&>(obj.get_property_value<util::Any>(d, "bool array")).get<util::Optional<bool>>(0) == util::none);
  705. REQUIRE(any_cast<List&&>(obj.get_property_value<util::Any>(d, "int array")).get<util::Optional<int64_t>>(0) == util::none);
  706. REQUIRE(any_cast<List&&>(obj.get_property_value<util::Any>(d, "float array")).get<util::Optional<float>>(0) == util::none);
  707. REQUIRE(any_cast<List&&>(obj.get_property_value<util::Any>(d, "double array")).get<util::Optional<double>>(0) == util::none);
  708. REQUIRE(any_cast<List&&>(obj.get_property_value<util::Any>(d, "string array")).get<StringData>(0) == StringData());
  709. REQUIRE(any_cast<List&&>(obj.get_property_value<util::Any>(d, "data array")).get<BinaryData>(0) == BinaryData());
  710. REQUIRE(any_cast<List&&>(obj.get_property_value<util::Any>(d, "date array")).get<Timestamp>(0) == Timestamp());
  711. // Set all properties back to non-null
  712. Object::create(d, r, *r->schema().find("all optional types"), util::Any(initial_values), policy);
  713. REQUIRE(any_cast<bool>(obj.get_property_value<util::Any>(d, "bool")) == true);
  714. REQUIRE(any_cast<int64_t>(obj.get_property_value<util::Any>(d, "int")) == 5);
  715. REQUIRE(any_cast<float>(obj.get_property_value<util::Any>(d, "float")) == 2.2f);
  716. REQUIRE(any_cast<double>(obj.get_property_value<util::Any>(d, "double")) == 3.3);
  717. REQUIRE(any_cast<std::string>(obj.get_property_value<util::Any>(d, "string")) == "hello");
  718. REQUIRE(any_cast<Timestamp>(obj.get_property_value<util::Any>(d, "date")) == Timestamp(10, 20));
  719. REQUIRE(any_cast<List&&>(obj.get_property_value<util::Any>(d, "bool array")).get<util::Optional<bool>>(0) == true);
  720. REQUIRE(any_cast<List&&>(obj.get_property_value<util::Any>(d, "int array")).get<util::Optional<int64_t>>(0) == 5);
  721. REQUIRE(any_cast<List&&>(obj.get_property_value<util::Any>(d, "float array")).get<util::Optional<float>>(0) == 1.1f);
  722. REQUIRE(any_cast<List&&>(obj.get_property_value<util::Any>(d, "double array")).get<util::Optional<double>>(0) == 3.3);
  723. REQUIRE(any_cast<List&&>(obj.get_property_value<util::Any>(d, "string array")).get<StringData>(0) == "a");
  724. REQUIRE(any_cast<List&&>(obj.get_property_value<util::Any>(d, "date array")).size() == 0);
  725. }
  726. }
  727. SECTION("create throws for duplicate pk if update is not specified") {
  728. create(AnyDict{
  729. {"pk", INT64_C(1)},
  730. {"bool", true},
  731. {"int", INT64_C(5)},
  732. {"float", 2.2f},
  733. {"double", 3.3},
  734. {"string", "hello"s},
  735. {"data", "olleh"s},
  736. {"date", Timestamp(10, 20)},
  737. {"object", AnyDict{{"value", INT64_C(10)}}},
  738. {"array", AnyVector{AnyDict{{"value", INT64_C(20)}}}},
  739. });
  740. REQUIRE_THROWS(create(AnyDict{
  741. {"pk", INT64_C(1)},
  742. {"bool", true},
  743. {"int", INT64_C(5)},
  744. {"float", 2.2f},
  745. {"double", 3.3},
  746. {"string", "hello"s},
  747. {"data", "olleh"s},
  748. {"date", Timestamp(10, 20)},
  749. {"object", AnyDict{{"value", INT64_C(10)}}},
  750. {"array", AnyVector{AnyDict{{"value", INT64_C(20)}}}},
  751. }));
  752. }
  753. SECTION("create with explicit null pk does not fall back to default") {
  754. d.defaults["nullable int pk"] = {
  755. {"pk", INT64_C(10)},
  756. };
  757. d.defaults["nullable string pk"] = {
  758. {"pk", "value"s},
  759. };
  760. auto create = [&](util::Any&& value, StringData type) {
  761. r->begin_transaction();
  762. auto obj = Object::create(d, r, *r->schema().find(type), value);
  763. r->commit_transaction();
  764. return obj;
  765. };
  766. auto obj = create(AnyDict{{"pk", d.null_value()}}, "nullable int pk");
  767. REQUIRE(obj.row().is_null(0));
  768. obj = create(AnyDict{{"pk", d.null_value()}}, "nullable string pk");
  769. REQUIRE(obj.row().is_null(0));
  770. obj = create(AnyDict{{}}, "nullable int pk");
  771. REQUIRE(obj.row().get_int(0) == 10);
  772. obj = create(AnyDict{{}}, "nullable string pk");
  773. REQUIRE(obj.row().get_string(0) == "value");
  774. }
  775. SECTION("getters and setters") {
  776. r->begin_transaction();
  777. auto& table = *r->read_group().get_table("class_all types");
  778. table.add_empty_row();
  779. Object obj(r, *r->schema().find("all types"), table[0]);
  780. auto& link_table = *r->read_group().get_table("class_link target");
  781. link_table.add_empty_row();
  782. Object linkobj(r, *r->schema().find("link target"), link_table[0]);
  783. obj.set_property_value(d, "bool", util::Any(true));
  784. REQUIRE(any_cast<bool>(obj.get_property_value<util::Any>(d, "bool")) == true);
  785. obj.set_property_value(d, "int", util::Any(INT64_C(5)));
  786. REQUIRE(any_cast<int64_t>(obj.get_property_value<util::Any>(d, "int")) == 5);
  787. obj.set_property_value(d, "float", util::Any(1.23f));
  788. REQUIRE(any_cast<float>(obj.get_property_value<util::Any>(d, "float")) == 1.23f);
  789. obj.set_property_value(d, "double", util::Any(1.23));
  790. REQUIRE(any_cast<double>(obj.get_property_value<util::Any>(d, "double")) == 1.23);
  791. obj.set_property_value(d, "string", util::Any("abc"s));
  792. REQUIRE(any_cast<std::string>(obj.get_property_value<util::Any>(d, "string")) == "abc");
  793. obj.set_property_value(d, "data", util::Any("abc"s));
  794. REQUIRE(any_cast<std::string>(obj.get_property_value<util::Any>(d, "data")) == "abc");
  795. obj.set_property_value(d, "date", util::Any(Timestamp(1, 2)));
  796. REQUIRE(any_cast<Timestamp>(obj.get_property_value<util::Any>(d, "date")) == Timestamp(1, 2));
  797. REQUIRE_FALSE(obj.get_property_value<util::Any>(d, "object").has_value());
  798. obj.set_property_value(d, "object", util::Any(linkobj));
  799. REQUIRE(any_cast<Object>(obj.get_property_value<util::Any>(d, "object")).row().get_index() == linkobj.row().get_index());
  800. auto linking = any_cast<Results>(linkobj.get_property_value<util::Any>(d, "origin"));
  801. REQUIRE(linking.size() == 1);
  802. REQUIRE_THROWS(obj.set_property_value(d, "pk", util::Any(INT64_C(5))));
  803. REQUIRE_THROWS(obj.set_property_value(d, "not a property", util::Any(INT64_C(5))));
  804. r->commit_transaction();
  805. REQUIRE_THROWS(obj.get_property_value<util::Any>(d, "not a property"));
  806. REQUIRE_THROWS(obj.set_property_value(d, "int", util::Any(INT64_C(5))));
  807. }
  808. SECTION("list property self-assign is a no-op") {
  809. auto obj = create(AnyDict{
  810. {"pk", INT64_C(1)},
  811. {"bool", true},
  812. {"int", INT64_C(5)},
  813. {"float", 2.2f},
  814. {"double", 3.3},
  815. {"string", "hello"s},
  816. {"data", "olleh"s},
  817. {"date", Timestamp(10, 20)},
  818. {"bool array", AnyVec{true, false}},
  819. {"object array", AnyVec{AnyDict{{"value", INT64_C(20)}}}},
  820. });
  821. REQUIRE(any_cast<List&&>(obj.get_property_value<util::Any>(d, "bool array")).size() == 2);
  822. REQUIRE(any_cast<List&&>(obj.get_property_value<util::Any>(d, "object array")).size() == 1);
  823. r->begin_transaction();
  824. obj.set_property_value(d, "bool array", obj.get_property_value<util::Any>(d, "bool array"));
  825. obj.set_property_value(d, "object array", obj.get_property_value<util::Any>(d, "object array"));
  826. r->commit_transaction();
  827. REQUIRE(any_cast<List&&>(obj.get_property_value<util::Any>(d, "bool array")).size() == 2);
  828. REQUIRE(any_cast<List&&>(obj.get_property_value<util::Any>(d, "object array")).size() == 1);
  829. }
  830. #if REALM_ENABLE_SYNC
  831. if (!util::EventLoop::has_implementation())
  832. return;
  833. SyncServer server(false);
  834. SyncTestFile config1(server, "shared");
  835. config1.schema = config.schema;
  836. SyncTestFile config2(server, "shared");
  837. config2.schema = config.schema;
  838. SECTION("defaults do not override values explicitly passed to create()") {
  839. AnyDict v1{
  840. {"pk", INT64_C(7)},
  841. {"array 1", AnyVector{AnyDict{{"value", INT64_C(1)}}}},
  842. {"array 2", AnyVector{AnyDict{{"value", INT64_C(2)}}}},
  843. };
  844. auto v2 = v1;
  845. v1["int 1"] = INT64_C(1);
  846. v2["int 2"] = INT64_C(2);
  847. auto r1 = Realm::get_shared_realm(config1);
  848. auto r2 = Realm::get_shared_realm(config2);
  849. TestContext c1(r1);
  850. TestContext c2(r2);
  851. c1.defaults["pk after list"] = {
  852. {"int 1", INT64_C(10)},
  853. {"int 2", INT64_C(10)},
  854. };
  855. c2.defaults = c1.defaults;
  856. r1->begin_transaction();
  857. r2->begin_transaction();
  858. auto obj = Object::create(c1, r1, *r1->schema().find("pk after list"), util::Any(v1));
  859. Object::create(c2, r2, *r2->schema().find("pk after list"), util::Any(v2));
  860. r2->commit_transaction();
  861. r1->commit_transaction();
  862. server.start();
  863. util::EventLoop::main().run_until([&] {
  864. return r1->read_group().get_table("class_array target")->size() == 4;
  865. });
  866. // With stable IDs, sync creates the primary key column at index 0.
  867. REQUIRE(obj.row().get_int(0) == 7); // pk
  868. REQUIRE(obj.row().get_linklist(1)->size() == 2);
  869. REQUIRE(obj.row().get_int(2) == 1); // non-default from r1
  870. REQUIRE(obj.row().get_int(3) == 2); // non-default from r2
  871. REQUIRE(obj.row().get_linklist(4)->size() == 2);
  872. }
  873. #endif
  874. }