|
27 | 27 |
|
28 | 28 | #include <realm/object_id.hpp> |
29 | 29 | #include <realm/query_expression.hpp> |
| 30 | +#include <realm/list.hpp> |
30 | 31 |
|
31 | 32 | #include <realm/object-store/binding_context.hpp> |
32 | 33 | #include <realm/object-store/impl/object_accessor_impl.hpp> |
33 | 34 | #include <realm/object-store/impl/realm_coordinator.hpp> |
| 35 | +#include <realm/object-store/list.hpp> |
34 | 36 | #include <realm/object-store/schema.hpp> |
35 | 37 | #include <realm/object-store/sync/generic_network_transport.hpp> |
36 | 38 | #include <realm/object-store/sync/mongo_client.hpp> |
@@ -4788,6 +4790,140 @@ TEST_CASE("flx: pause and resume bootstrapping at query version 0", "[sync][flx] |
4788 | 4790 | REQUIRE(active_sub_set.state() == sync::SubscriptionSet::State::Complete); |
4789 | 4791 | } |
4790 | 4792 |
|
| 4793 | +TEST_CASE("flx: collections in mixed - merge lists", "[sync][flx][baas]") { |
| 4794 | + Schema schema{{"TopLevel", |
| 4795 | + {{"_id", PropertyType::ObjectId, Property::IsPrimary{true}}, |
| 4796 | + {"queryable_str_field", PropertyType::String | PropertyType::Nullable}, |
| 4797 | + {"any", PropertyType::Mixed | PropertyType::Nullable}}}}; |
| 4798 | + |
| 4799 | + FLXSyncTestHarness::ServerSchema server_schema{schema, {"queryable_str_field"}}; |
| 4800 | + FLXSyncTestHarness harness("flx_collections_in_mixed", server_schema); |
| 4801 | + SyncTestFile config(harness.app()->current_user(), harness.schema(), SyncConfig::FLXSyncEnabled{}); |
| 4802 | + |
| 4803 | + auto set_list_and_insert_element = [](Obj& obj, ColKey col_any, Mixed value) { |
| 4804 | + obj.set_collection(col_any, CollectionType::List); |
| 4805 | + auto list = obj.get_list_ptr<Mixed>(col_any); |
| 4806 | + list->add(value); |
| 4807 | + }; |
| 4808 | + |
| 4809 | + // Client 1 creates an object and sets property 'any' to an integer value. |
| 4810 | + auto foo_obj_id = ObjectId::gen(); |
| 4811 | + harness.load_initial_data([&](SharedRealm realm) { |
| 4812 | + CppContext c(realm); |
| 4813 | + Object::create(c, realm, "TopLevel", |
| 4814 | + std::any(AnyDict{{"_id", foo_obj_id}, {"queryable_str_field", "foo"s}, {"any", 42}})); |
| 4815 | + }); |
| 4816 | + |
| 4817 | + // Client 2 opens the realm and downloads schema and object created by Client 1. |
| 4818 | + auto realm = Realm::get_shared_realm(config); |
| 4819 | + subscribe_to_all_and_bootstrap(*realm); |
| 4820 | + realm->sync_session()->pause(); |
| 4821 | + |
| 4822 | + // Client 3 sets property 'any' to List and inserts two integers in the list. |
| 4823 | + harness.load_initial_data([&](SharedRealm realm) { |
| 4824 | + auto table = realm->read_group().get_table("class_TopLevel"); |
| 4825 | + auto obj = table->get_object_with_primary_key(Mixed{foo_obj_id}); |
| 4826 | + auto col_any = table->get_column_key("any"); |
| 4827 | + set_list_and_insert_element(obj, col_any, 1); |
| 4828 | + set_list_and_insert_element(obj, col_any, 2); |
| 4829 | + }); |
| 4830 | + |
| 4831 | + // While its session is paused, Client 2 sets property 'any' to List and inserts two integers in the list. |
| 4832 | + CppContext c(realm); |
| 4833 | + realm->begin_transaction(); |
| 4834 | + auto table = realm->read_group().get_table("class_TopLevel"); |
| 4835 | + auto obj = table->get_object_with_primary_key(Mixed{foo_obj_id}); |
| 4836 | + auto col_any = table->get_column_key("any"); |
| 4837 | + set_list_and_insert_element(obj, col_any, 3); |
| 4838 | + set_list_and_insert_element(obj, col_any, 4); |
| 4839 | + realm->commit_transaction(); |
| 4840 | + |
| 4841 | + realm->sync_session()->resume(); |
| 4842 | + wait_for_upload(*realm); |
| 4843 | + wait_for_download(*realm); |
| 4844 | + wait_for_advance(*realm); |
| 4845 | + |
| 4846 | + // Client 2 ends up with four integers in the list (in the correct order). |
| 4847 | + auto list = obj.get_list_ptr<Mixed>(col_any); |
| 4848 | + CHECK(list->size() == 4); |
| 4849 | + CHECK(list->get(0) == 1); |
| 4850 | + CHECK(list->get(1) == 2); |
| 4851 | + CHECK(list->get(2) == 3); |
| 4852 | + CHECK(list->get(3) == 4); |
| 4853 | +} |
| 4854 | + |
| 4855 | +TEST_CASE("flx: nested collections in mixed", "[sync][flx][baas]") { |
| 4856 | + Schema schema{{"TopLevel", |
| 4857 | + {{"_id", PropertyType::Int, Property::IsPrimary{true}}, |
| 4858 | + {"queryable_str_field", PropertyType::String | PropertyType::Nullable}, |
| 4859 | + {"any", PropertyType::Mixed | PropertyType::Nullable}}}}; |
| 4860 | + |
| 4861 | + FLXSyncTestHarness::ServerSchema server_schema{schema, {"queryable_str_field"}}; |
| 4862 | + FLXSyncTestHarness harness("flx_collections_in_mixed", server_schema); |
| 4863 | + SyncTestFile config(harness.app()->current_user(), harness.schema(), SyncConfig::FLXSyncEnabled{}); |
| 4864 | + |
| 4865 | + // Client 1: {_id: 1, any: ["abc", [42]]} |
| 4866 | + harness.load_initial_data([&](SharedRealm realm) { |
| 4867 | + CppContext c(realm); |
| 4868 | + auto obj = Object::create(c, realm, "TopLevel", |
| 4869 | + std::any(AnyDict{{"_id", INT64_C(1)}, {"queryable_str_field", "foo"s}})); |
| 4870 | + auto table = realm->read_group().get_table("class_TopLevel"); |
| 4871 | + auto col_any = table->get_column_key("any"); |
| 4872 | + obj.get_obj().set_collection(col_any, CollectionType::List); |
| 4873 | + List list(obj, obj.get_object_schema().property_for_name("any")); |
| 4874 | + list.insert_any(0, "abc"); |
| 4875 | + list.insert_collection(1, CollectionType::List); |
| 4876 | + auto list2 = list.get_list(1); |
| 4877 | + list2.insert_any(0, 42); |
| 4878 | + }); |
| 4879 | + |
| 4880 | + // Client 2 opens the realm and downloads schema and object created by Client 1. |
| 4881 | + // {_id: 1, any: ["abc", [42]]} |
| 4882 | + auto realm = Realm::get_shared_realm(config); |
| 4883 | + subscribe_to_all_and_bootstrap(*realm); |
| 4884 | + realm->sync_session()->pause(); |
| 4885 | + |
| 4886 | + // Client 3 adds a dictionary with an element to list 'any' |
| 4887 | + // {_id: 1, any: [{{"key": 6}}, "abc", [42]]} |
| 4888 | + harness.load_initial_data([&](SharedRealm realm) { |
| 4889 | + CppContext c(realm); |
| 4890 | + auto obj = Object::get_for_primary_key(c, realm, "TopLevel", std::any(INT64_C(1))); |
| 4891 | + List list(obj, obj.get_object_schema().property_for_name("any")); |
| 4892 | + list.insert_collection(PathElement(0), CollectionType::Dictionary); |
| 4893 | + auto dict = list.get_dictionary(PathElement(0)); |
| 4894 | + dict.insert_any("key", INT64_C(6)); |
| 4895 | + }); |
| 4896 | + |
| 4897 | + // While its session is paused, Client 2 makes a change to a nested list |
| 4898 | + // {_id: 1, any: ["abc", [42, "foo"]]} |
| 4899 | + CppContext c(realm); |
| 4900 | + realm->begin_transaction(); |
| 4901 | + auto obj = Object::get_for_primary_key(c, realm, "TopLevel", std::any(INT64_C(1))); |
| 4902 | + List list(obj, obj.get_object_schema().property_for_name("any")); |
| 4903 | + List list2 = list.get_list(PathElement(1)); |
| 4904 | + list2.insert_any(list2.size(), "foo"); |
| 4905 | + realm->commit_transaction(); |
| 4906 | + |
| 4907 | + realm->sync_session()->resume(); |
| 4908 | + wait_for_upload(*realm); |
| 4909 | + wait_for_download(*realm); |
| 4910 | + wait_for_advance(*realm); |
| 4911 | + |
| 4912 | + // Client 2 after the session is resumed |
| 4913 | + // {_id: 1, any: [{{"key": 6}}, "abc", [42, "foo"]]} |
| 4914 | + CHECK(list.size() == 3); |
| 4915 | + auto nested_dict = list.get_dictionary(0); |
| 4916 | + CHECK(nested_dict.size() == 1); |
| 4917 | + CHECK(nested_dict.get<Int>("key") == 6); |
| 4918 | + |
| 4919 | + CHECK(list.get_any(1) == "abc"); |
| 4920 | + |
| 4921 | + auto nested_list = list.get_list(2); |
| 4922 | + CHECK(nested_list.size() == 2); |
| 4923 | + CHECK(nested_list.get_any(0) == 42); |
| 4924 | + CHECK(nested_list.get_any(1) == "foo"); |
| 4925 | +} |
| 4926 | + |
4791 | 4927 | } // namespace realm::app |
4792 | 4928 |
|
4793 | 4929 | #endif // REALM_ENABLE_AUTH_TESTS |
0 commit comments