Skip to content

Commit

Permalink
Support assigning nested collections via templated API
Browse files Browse the repository at this point in the history
  • Loading branch information
jedelbo committed Mar 14, 2024
1 parent ca1eeb6 commit eef764f
Show file tree
Hide file tree
Showing 6 changed files with 216 additions and 44 deletions.
2 changes: 1 addition & 1 deletion src/realm/collection_parent.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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");
}
}
Expand Down
49 changes: 36 additions & 13 deletions src/realm/object-store/dictionary.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,12 @@ class Dictionary : public object_store::Collection {
Obj get_object(StringData key) const;
};

} // namespace object_store
} // namespace realm

#include <realm/object-store/list.hpp>

namespace realm::object_store {

template <typename Fn>
auto Dictionary::dispatch(Fn&& fn) const
Expand Down Expand Up @@ -191,9 +197,27 @@ void Dictionary::insert(Context& ctx, StringData key, T&& value, CreatePolicy po
ctx.template unbox<Obj>(value, policy, obj_key);
return;
}
dispatch([&](auto t) {
this->insert(key, ctx.template unbox<std::decay_t<decltype(*t)>>(value, policy));
});
if (m_type == PropertyType::Mixed) {
Mixed new_val = ctx.template unbox<Mixed>(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<std::decay_t<decltype(*t)>>(value, policy));
});
}
}

template <typename Context>
Expand All @@ -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<Mixed> old_value = dict().try_get(key);
auto new_value = ctx.template unbox<Mixed>(value);
if (!old_value || *old_value != new_value) {
dict().insert(key, new_value);
Mixed new_value = ctx.template unbox<Mixed>(value);
if (!new_value.is_type(type_Dictionary, type_List)) {
util::Optional<Mixed> 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 */
6 changes: 6 additions & 0 deletions src/realm/object-store/impl/object_accessor_impl.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<UUID>(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{};
}
Expand Down
115 changes: 85 additions & 30 deletions src/realm/object-store/list.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,11 @@ class List : public object_store::Collection {

friend struct std::hash<List>;
};
} // namespace realm

#include <realm/object-store/dictionary.hpp>

namespace realm {
template <>
Obj List::get(size_t row_ndx) const;

Expand Down Expand Up @@ -183,15 +187,7 @@ size_t List::find(Context& ctx, T&& value) const
template <typename T, typename Context>
void List::add(Context& ctx, T&& value, CreatePolicy policy)
{
if (m_is_embedded) {
validate_embedded(ctx, value, policy);
auto key = as<Obj>().create_and_insert_linked_object(size()).get_key();
ctx.template unbox<Obj>(value, policy, key);
return;
}
dispatch([&](auto t) {
this->add(ctx.template unbox<std::decay_t<decltype(*t)>>(value, policy));
});
this->insert(ctx, size(), std::move(value), policy);
}

template <typename T, typename Context>
Expand All @@ -203,9 +199,28 @@ void List::insert(Context& ctx, size_t list_ndx, T&& value, CreatePolicy policy)
ctx.template unbox<Obj>(value, policy, key);
return;
}
dispatch([&](auto t) {
this->insert(list_ndx, ctx.template unbox<std::decay_t<decltype(*t)>>(value, policy));
});
if (m_type == PropertyType::Mixed) {
Mixed new_val = ctx.template unbox<Mixed>(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<std::decay_t<decltype(*t)>>(value, policy));
});
}
}

template <typename T, typename Context>
Expand All @@ -219,35 +234,75 @@ void List::set(Context& ctx, size_t list_ndx, T&& value, CreatePolicy policy)
ctx.template unbox<Obj>(value, policy, key);
return;
}
dispatch([&](auto t) {
this->set(list_ndx, ctx.template unbox<std::decay_t<decltype(*t)>>(value, policy));
});
if (m_type == PropertyType::Mixed) {
Mixed new_val = ctx.template unbox<Mixed>(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<std::decay_t<decltype(*t)>>(value, policy));
});
}
}

template <typename T, typename Context>
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<Obj>(row_ndx) : as<Obj>().create_and_set_linked_object(row_ndx);
auto key = policy.diff ? this->get<Obj>(list_ndx) : as<Obj>().create_and_set_linked_object(list_ndx);
ctx.template unbox<Obj>(value, policy, key.get_key());
return;
}
dispatch([&](auto t) {
using U = std::decay_t<decltype(*t)>;
if constexpr (std::is_same_v<U, Obj>) {
auto old_value = this->get<U>(row_ndx);
auto new_value = ctx.template unbox<U>(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<Mixed>(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<U>(row_ndx);
auto new_value = ctx.template unbox<U>(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<Mixed>(list_ndx);
Mixed new_value = ctx.template unbox<Mixed>(value, policy);
if (old_value != new_value)
this->set(list_ndx, new_value);
}
else {
dispatch([&](auto t) {
using U = std::decay_t<decltype(*t)>;
if constexpr (std::is_same_v<U, Obj>) {
auto old_value = this->get<U>(list_ndx);
auto new_value = ctx.template unbox<U>(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<U>(list_ndx);
auto new_value = ctx.template unbox<U>(value, policy);
if (old_value != new_value)
this->set(list_ndx, new_value);
}
});
}
}

template <typename T, typename Context>
Expand Down
21 changes: 21 additions & 0 deletions src/realm/object-store/object_accessor.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<Mixed>(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<ValueType, ContextType> updater{ctx, property, value, m_obj, col, policy, is_default};
switch_on_type(property.type, updater);
ctx.did_change();
Expand Down
67 changes: 67 additions & 0 deletions test/object-store/dictionary.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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{
Expand Down

0 comments on commit eef764f

Please sign in to comment.