From eef764fd76978d9096ad3ea3a713ea55f0d289c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B8rgen=20Edelbo?= Date: Thu, 14 Mar 2024 13:57:51 +0100 Subject: [PATCH] Support assigning nested collections via templated API --- src/realm/collection_parent.cpp | 2 +- src/realm/object-store/dictionary.hpp | 49 ++++++-- .../impl/object_accessor_impl.hpp | 6 + src/realm/object-store/list.hpp | 115 +++++++++++++----- src/realm/object-store/object_accessor.hpp | 21 ++++ test/object-store/dictionary.cpp | 67 ++++++++++ 6 files changed, 216 insertions(+), 44 deletions(-) diff --git a/src/realm/collection_parent.cpp b/src/realm/collection_parent.cpp index dc3ad806bc3..1677d57b910 100644 --- a/src/realm/collection_parent.cpp +++ b/src/realm/collection_parent.cpp @@ -75,7 +75,7 @@ CollectionParent::~CollectionParent() {} void CollectionParent::check_level() const { - if (m_level + 1 > s_max_level) { + if (size_t(m_level) + 1 > s_max_level) { throw LogicError(ErrorCodes::LimitExceeded, "Max nesting level reached"); } } diff --git a/src/realm/object-store/dictionary.hpp b/src/realm/object-store/dictionary.hpp index 8f0fcf52f1a..16e31ed7374 100644 --- a/src/realm/object-store/dictionary.hpp +++ b/src/realm/object-store/dictionary.hpp @@ -142,6 +142,12 @@ class Dictionary : public object_store::Collection { Obj get_object(StringData key) const; }; +} // namespace object_store +} // namespace realm + +#include + +namespace realm::object_store { template auto Dictionary::dispatch(Fn&& fn) const @@ -191,9 +197,27 @@ void Dictionary::insert(Context& ctx, StringData key, T&& value, CreatePolicy po ctx.template unbox(value, policy, obj_key); return; } - dispatch([&](auto t) { - this->insert(key, ctx.template unbox>(value, policy)); - }); + if (m_type == PropertyType::Mixed) { + Mixed new_val = ctx.template unbox(value, policy); + if (new_val.get_type() == type_Dictionary) { + insert_collection(key, CollectionType::Dictionary); + auto dict = get_dictionary(key); + dict.assign(ctx, value, policy); + return; + } + if (new_val.get_type() == type_List) { + insert_collection(key, CollectionType::List); + auto list = get_list(key); + list.assign(ctx, value, policy); + return; + } + this->insert(key, new_val); + } + else { + dispatch([&](auto t) { + this->insert(key, ctx.template unbox>(value, policy)); + }); + } } template @@ -220,20 +244,19 @@ void Dictionary::assign(Context& ctx, T&& values, CreatePolicy policy) ctx.enumerate_dictionary(values, [&](StringData key, auto&& value) { if (policy.diff) { - util::Optional old_value = dict().try_get(key); - auto new_value = ctx.template unbox(value); - if (!old_value || *old_value != new_value) { - dict().insert(key, new_value); + Mixed new_value = ctx.template unbox(value); + if (!new_value.is_type(type_Dictionary, type_List)) { + util::Optional old_value = dict().try_get(key); + if (!old_value || *old_value != new_value) { + dict().insert(key, new_value); + } + return; } } - else { - this->insert(ctx, key, value, policy); - } + this->insert(ctx, key, value, policy); }); } -} // namespace object_store -} // namespace realm - +} // namespace realm::object_store #endif /* REALM_OS_DICTIONARY_HPP */ diff --git a/src/realm/object-store/impl/object_accessor_impl.hpp b/src/realm/object-store/impl/object_accessor_impl.hpp index 8a9eacccf0e..4ea5e3a8f0b 100644 --- a/src/realm/object-store/impl/object_accessor_impl.hpp +++ b/src/realm/object-store/impl/object_accessor_impl.hpp @@ -408,6 +408,12 @@ inline Mixed CppContext::unbox(std::any& v, CreatePolicy, ObjKey) const else if (this_type == typeid(UUID)) { return Mixed(util::any_cast(v)); } + else if (this_type == typeid(AnyDict)) { + return Mixed(0, CollectionType::Dictionary); + } + else if (this_type == typeid(AnyVector)) { + return Mixed(0, CollectionType::List); + } } return Mixed{}; } diff --git a/src/realm/object-store/list.hpp b/src/realm/object-store/list.hpp index 020f3414fee..3ebbb5f229f 100644 --- a/src/realm/object-store/list.hpp +++ b/src/realm/object-store/list.hpp @@ -132,7 +132,11 @@ class List : public object_store::Collection { friend struct std::hash; }; +} // namespace realm + +#include +namespace realm { template <> Obj List::get(size_t row_ndx) const; @@ -183,15 +187,7 @@ size_t List::find(Context& ctx, T&& value) const template void List::add(Context& ctx, T&& value, CreatePolicy policy) { - if (m_is_embedded) { - validate_embedded(ctx, value, policy); - auto key = as().create_and_insert_linked_object(size()).get_key(); - ctx.template unbox(value, policy, key); - return; - } - dispatch([&](auto t) { - this->add(ctx.template unbox>(value, policy)); - }); + this->insert(ctx, size(), std::move(value), policy); } template @@ -203,9 +199,28 @@ void List::insert(Context& ctx, size_t list_ndx, T&& value, CreatePolicy policy) ctx.template unbox(value, policy, key); return; } - dispatch([&](auto t) { - this->insert(list_ndx, ctx.template unbox>(value, policy)); - }); + if (m_type == PropertyType::Mixed) { + Mixed new_val = ctx.template unbox(value, policy); + if (new_val.get_type() == type_Dictionary) { + insert_collection(list_ndx, CollectionType::Dictionary); + auto dict = get_dictionary(list_ndx); + dict.assign(ctx, value, policy); + return; + } + if (new_val.get_type() == type_List) { + insert_collection(list_ndx, CollectionType::List); + auto list = get_list(list_ndx); + list.assign(ctx, value, policy); + return; + } + + this->insert(list_ndx, new_val); + } + else { + dispatch([&](auto t) { + this->insert(list_ndx, ctx.template unbox>(value, policy)); + }); + } } template @@ -219,35 +234,75 @@ void List::set(Context& ctx, size_t list_ndx, T&& value, CreatePolicy policy) ctx.template unbox(value, policy, key); return; } - dispatch([&](auto t) { - this->set(list_ndx, ctx.template unbox>(value, policy)); - }); + if (m_type == PropertyType::Mixed) { + Mixed new_val = ctx.template unbox(value, policy); + if (new_val.get_type() == type_Dictionary) { + set_collection(list_ndx, CollectionType::Dictionary); + auto dict = get_dictionary(list_ndx); + dict.assign(ctx, value, policy); + return; + } + if (new_val.get_type() == type_List) { + set_collection(list_ndx, CollectionType::List); + auto list = get_list(list_ndx); + list.assign(ctx, value, policy); + return; + } + + this->set(list_ndx, new_val); + } + else { + dispatch([&](auto t) { + this->set(list_ndx, ctx.template unbox>(value, policy)); + }); + } } template -void List::set_if_different(Context& ctx, size_t row_ndx, T&& value, CreatePolicy policy) +void List::set_if_different(Context& ctx, size_t list_ndx, T&& value, CreatePolicy policy) { if (m_is_embedded) { validate_embedded(ctx, value, policy); - auto key = policy.diff ? this->get(row_ndx) : as().create_and_set_linked_object(row_ndx); + auto key = policy.diff ? this->get(list_ndx) : as().create_and_set_linked_object(list_ndx); ctx.template unbox(value, policy, key.get_key()); return; } - dispatch([&](auto t) { - using U = std::decay_t; - if constexpr (std::is_same_v) { - auto old_value = this->get(row_ndx); - auto new_value = ctx.template unbox(value, policy, old_value.get_key()); - if (new_value.get_key() != old_value.get_key()) - this->set(row_ndx, new_value); + if (m_type == PropertyType::Mixed) { + Mixed new_val = ctx.template unbox(value, policy); + if (new_val.get_type() == type_Dictionary) { + set_collection(list_ndx, CollectionType::Dictionary); + auto dict = get_dictionary(list_ndx); + dict.assign(ctx, value, policy); + return; } - else { - auto old_value = this->get(row_ndx); - auto new_value = ctx.template unbox(value, policy); - if (old_value != new_value) - this->set(row_ndx, new_value); + if (new_val.get_type() == type_List) { + set_collection(list_ndx, CollectionType::List); + auto list = get_list(list_ndx); + list.assign(ctx, value, policy); + return; } - }); + Mixed old_value = this->get(list_ndx); + Mixed new_value = ctx.template unbox(value, policy); + if (old_value != new_value) + this->set(list_ndx, new_value); + } + else { + dispatch([&](auto t) { + using U = std::decay_t; + if constexpr (std::is_same_v) { + auto old_value = this->get(list_ndx); + auto new_value = ctx.template unbox(value, policy, old_value.get_key()); + if (new_value.get_key() != old_value.get_key()) + this->set(list_ndx, new_value); + } + else { + auto old_value = this->get(list_ndx); + auto new_value = ctx.template unbox(value, policy); + if (old_value != new_value) + this->set(list_ndx, new_value); + } + }); + } } template diff --git a/src/realm/object-store/object_accessor.hpp b/src/realm/object-store/object_accessor.hpp index 5b6688e5fb2..0783e1b00c2 100644 --- a/src/realm/object-store/object_accessor.hpp +++ b/src/realm/object-store/object_accessor.hpp @@ -170,6 +170,27 @@ void Object::set_property_value_impl(ContextType& ctx, const Property& property, return; } + if (property.type == PropertyType::Mixed) { + Mixed new_val = ctx.template unbox(value, policy); + if (new_val.get_type() == type_Dictionary) { + ContextType child_ctx(ctx, m_obj, property); + m_obj.set_collection(col, CollectionType::Dictionary); + object_store::Dictionary dict(m_realm, m_obj, col); + dict.assign(child_ctx, value, policy); + ctx.did_change(); + return; + } + if (new_val.get_type() == type_List) { + ContextType child_ctx(ctx, m_obj, property); + m_obj.set_collection(col, CollectionType::List); + List list(m_realm, m_obj, col); + list.assign(child_ctx, value, policy); + ctx.did_change(); + return; + } + } + + ValueUpdater updater{ctx, property, value, m_obj, col, policy, is_default}; switch_on_type(property.type, updater); ctx.did_change(); diff --git a/test/object-store/dictionary.cpp b/test/object-store/dictionary.cpp index 0ae18396843..3bff1054923 100644 --- a/test/object-store/dictionary.cpp +++ b/test/object-store/dictionary.cpp @@ -1348,6 +1348,73 @@ TEST_CASE("dictionary nullify", "[dictionary]") { // r->read_group().to_json(std::cout); } +TEST_CASE("nested collection set by Object::create", "[dictionary]") { + InMemoryTestFile config; + config.schema = Schema{ + {"DictionaryObject", + { + {"_id", PropertyType::Int, Property::IsPrimary{true}}, + {"any", PropertyType::Mixed | PropertyType::Nullable}, + }}, + }; + + auto r = Realm::get_shared_realm(config); + CppContext ctx(r); + + Any value{AnyDict{{"_id", INT64_C(5)}, + {"any", AnyDict{{"0", Any(AnyDict{{"zero", INT64_C(0)}})}, + {"1", Any(AnyVector{std::string("one"), INT64_C(1)})}, + {"2", Any(AnyDict{{"two", INT64_C(2)}, {"three", INT64_C(3)}})}}}}}; + r->begin_transaction(); + auto obj = Object::create(ctx, r, *r->schema().find("DictionaryObject"), value); + r->commit_transaction(); + + object_store::Dictionary dict(obj, r->schema().find("DictionaryObject")->property_for_name("any")); + auto dict0 = dict.get_dictionary("0"); + auto list1 = dict.get_list("1"); + auto dict2 = dict.get_dictionary("2"); + CHECK(dict0.get_any("zero") == Mixed(0)); + CHECK(list1.get_any(0) == Mixed("one")); + CHECK(list1.get_any(1) == Mixed(1)); + CHECK(dict2.get_any("two") == Mixed(2)); + CHECK(dict2.get_any("three") == Mixed(3)); + + SECTION("modify list only") { + Any new_value{ + AnyDict{{"_id", INT64_C(5)}, {"any", AnyDict{{"1", Any(AnyVector{std::string("seven"), INT64_C(7)})}}}}}; + + r->begin_transaction(); + Object::create(ctx, r, *r->schema().find("DictionaryObject"), new_value, CreatePolicy::UpdateModified); + r->commit_transaction(); + CHECK(list1.get_any(0) == Mixed("seven")); + CHECK(list1.get_any(1) == Mixed(7)); + } + + SECTION("replace list with dictionary") { + Any new_value{AnyDict{{"_id", INT64_C(5)}, {"any", AnyDict{{"1", Any(AnyDict{{"seven", INT64_C(7)}})}}}}}; + + r->begin_transaction(); + Object::create(ctx, r, *r->schema().find("DictionaryObject"), new_value, CreatePolicy::UpdateModified); + r->commit_transaction(); + auto dict1 = dict.get_dictionary("1"); + CHECK(dict1.get_any("seven") == Mixed(7)); + } + + SECTION("replace dictionary with list on top level") { + Any new_value{AnyDict{ + {"_id", INT64_C(5)}, + {"any", AnyVector{Any(AnyDict{{"zero", INT64_C(0)}}), Any(AnyVector{std::string("one"), INT64_C(1)}), + Any(AnyDict{{"two", INT64_C(2)}, {"three", INT64_C(3)}})}}}}; + + r->begin_transaction(); + Object::create(ctx, r, *r->schema().find("DictionaryObject"), new_value, CreatePolicy::UpdateModified); + r->commit_transaction(); + List list(obj, r->schema().find("DictionaryObject")->property_for_name("any")); + dict0 = list.get_dictionary(0); + CHECK(dict0.get_any("zero") == Mixed(0)); + } +} + TEST_CASE("dictionary assign", "[dictionary]") { InMemoryTestFile config; config.schema = Schema{