diff --git a/src/realm/collection.hpp b/src/realm/collection.hpp index 04e52c70eac..36c1e6d0539 100644 --- a/src/realm/collection.hpp +++ b/src/realm/collection.hpp @@ -14,6 +14,41 @@ namespace realm { template struct CollectionIterator; +// Used in Cluster when removing owning object +class DummyParent : public CollectionParent { +public: + DummyParent(TableRef t, ref_type ref) + : m_obj(t, MemRef(), ObjKey(), 0) + , m_ref(ref) + { + } + TableRef get_table() const noexcept final + { + return m_obj.get_table(); + } + const Obj& get_object() const noexcept final + { + return m_obj; + } + +protected: + Obj m_obj; + ref_type m_ref; + UpdateStatus update_if_needed_with_status() const noexcept final + { + return UpdateStatus::Updated; + } + bool update_if_needed() const final + { + return true; + } + ref_type get_collection_ref(Index, CollectionType) const final + { + return m_ref; + } + void set_collection_ref(Index, ref_type, CollectionType) {} +}; + class Collection { public: virtual ~Collection(); @@ -109,7 +144,7 @@ class CollectionBase : public Collection { } /// Get the table of the object that owns this collection. - virtual ConstTableRef get_table() const noexcept final + ConstTableRef get_table() const noexcept { return get_obj().get_table(); } @@ -463,6 +498,13 @@ class CollectionBaseImpl : public Interface, protected ArrayParent { { } + CollectionBaseImpl(DummyParent& parent) noexcept + : m_obj_mem(parent.get_object()) + , m_parent(&parent) + , m_alloc(&m_obj_mem.get_alloc()) + { + } + CollectionBaseImpl& operator=(const CollectionBaseImpl& other) { if (this != &other) { diff --git a/src/realm/collection_list.cpp b/src/realm/collection_list.cpp index 1e0f94af623..bfd1840ad82 100644 --- a/src/realm/collection_list.cpp +++ b/src/realm/collection_list.cpp @@ -31,10 +31,10 @@ namespace realm { CollectionList::CollectionList(std::shared_ptr parent, ColKey col_key, Index index, CollectionType coll_type) - : m_owned_parent(parent) + : CollectionParent(parent->get_level() + 1) + , m_owned_parent(parent) , m_parent(m_owned_parent.get()) , m_index(index) - , m_level(parent->get_level() + 1) , m_alloc(&get_table()->get_alloc()) , m_col_key(col_key) , m_top(*m_alloc) diff --git a/src/realm/collection_list.hpp b/src/realm/collection_list.hpp index 1c4029ee666..fe83a3e6cc3 100644 --- a/src/realm/collection_list.hpp +++ b/src/realm/collection_list.hpp @@ -33,10 +33,7 @@ using CollectionListPtr = std::shared_ptr; * by either an integer index or a string key. */ -class CollectionList final : public Collection, - public CollectionParent, - protected ArrayParent, - public std::enable_shared_from_this { +class CollectionList final : public Collection, public CollectionParent, protected ArrayParent { public: [[nodiscard]] static CollectionListPtr create(std::shared_ptr parent, ColKey col_key, Index index, CollectionType coll_type) @@ -59,10 +56,6 @@ class CollectionList final : public Collection, bool init_from_parent(bool allow_create) const; - size_t get_level() const noexcept final - { - return m_level; - } UpdateStatus update_if_needed_with_status() const noexcept final; bool update_if_needed() const final; TableRef get_table() const noexcept final @@ -106,7 +99,6 @@ class CollectionList final : public Collection, std::shared_ptr m_owned_parent; CollectionParent* m_parent; CollectionParent::Index m_index; - size_t m_level = 0; Allocator* m_alloc; ColKey m_col_key; mutable Array m_top; diff --git a/src/realm/collection_parent.cpp b/src/realm/collection_parent.cpp index c0e2f338dfc..e42eff0d455 100644 --- a/src/realm/collection_parent.cpp +++ b/src/realm/collection_parent.cpp @@ -241,11 +241,6 @@ SetBasePtr CollectionParent::get_setbase_ptr(ColKey col_key) const REALM_TERMINATE("Unsupported column type."); } -DictionaryPtr CollectionParent::get_dictionary_ptr(ColKey col_key) const -{ - return std::make_unique(col_key); -} - CollectionBasePtr CollectionParent::get_collection_ptr(ColKey col_key) const { if (col_key.is_list()) { @@ -255,16 +250,9 @@ CollectionBasePtr CollectionParent::get_collection_ptr(ColKey col_key) const return get_setbase_ptr(col_key); } else if (col_key.is_dictionary()) { - return get_dictionary_ptr(col_key); + return std::make_unique(col_key); } return {}; } - -const Obj& DummyParent::get_object() const noexcept -{ - static Obj dummy_obj; - return dummy_obj; -} - } // namespace realm diff --git a/src/realm/collection_parent.hpp b/src/realm/collection_parent.hpp index 29d560fa419..0adc9f11985 100644 --- a/src/realm/collection_parent.hpp +++ b/src/realm/collection_parent.hpp @@ -44,7 +44,7 @@ using LstBasePtr = std::unique_ptr; using SetBasePtr = std::unique_ptr; using CollectionBasePtr = std::unique_ptr; using CollectionListPtr = std::shared_ptr; -using DictionaryPtr = std::unique_ptr; +using DictionaryPtr = std::shared_ptr; /// The status of an accessor after a call to `update_if_needed()`. enum class UpdateStatus { @@ -75,12 +75,14 @@ struct FullPath { Path path_from_top; }; -class CollectionParent { +class CollectionParent : public std::enable_shared_from_this { public: using Index = mpark::variant; - // Return the nesting level of the parent - virtual size_t get_level() const noexcept = 0; + size_t get_level() const noexcept + { + return m_level; + } /// Get table of owning object virtual TableRef get_table() const noexcept = 0; @@ -89,6 +91,13 @@ class CollectionParent { friend class CollectionBaseImpl; friend class CollectionList; + size_t m_level = 0; + + constexpr CollectionParent(size_t level = 0) + : m_level(level) + { + } + virtual ~CollectionParent(); /// Update the accessor (and return `UpdateStatus::Detached` if the parent /// is no longer valid, rather than throwing an exception). @@ -112,46 +121,9 @@ class CollectionParent { LstBasePtr get_listbase_ptr(ColKey col_key) const; SetBasePtr get_setbase_ptr(ColKey col_key) const; - DictionaryPtr get_dictionary_ptr(ColKey col_key) const; CollectionBasePtr get_collection_ptr(ColKey col_key) const; }; -// Used in Cluster when removing owning object -class DummyParent : public CollectionParent { -public: - DummyParent(TableRef t, ref_type ref) - : m_table(t) - , m_ref(ref) - { - } - size_t get_level() const noexcept final - { - return 0; - } - TableRef get_table() const noexcept final - { - return m_table; - } - -protected: - TableRef m_table; - ref_type m_ref; - UpdateStatus update_if_needed_with_status() const noexcept final - { - return UpdateStatus::Updated; - } - bool update_if_needed() const final - { - return true; - } - const Obj& get_object() const noexcept final; - ref_type get_collection_ref(Index, CollectionType) const final - { - return m_ref; - } - void set_collection_ref(Index, ref_type, CollectionType) {} -}; - } // namespace realm #endif diff --git a/src/realm/dictionary.cpp b/src/realm/dictionary.cpp index d5c8047690d..25cf4530f95 100644 --- a/src/realm/dictionary.cpp +++ b/src/realm/dictionary.cpp @@ -21,6 +21,7 @@ #include #include #include +#include #include #include @@ -49,7 +50,7 @@ void validate_key_value(const Mixed& key) Dictionary::Dictionary(ColKey col_key) : Base(col_key) { - if (!col_key.is_dictionary()) { + if (!(col_key.is_dictionary() || col_key.get_type() == col_type_Mixed)) { throw InvalidArgument(ErrorCodes::TypeMismatch, "Property not a dictionary"); } } @@ -423,6 +424,38 @@ Obj Dictionary::create_and_insert_linked_object(Mixed key) return o; } +DictionaryPtr Dictionary::insert_dictionary(StringData key) +{ + insert(key, Mixed(0, CollectionType::Dictionary)); + return get_dictionary(key); +} + +DictionaryPtr Dictionary::get_dictionary(StringData key) const +{ + auto weak = const_cast(this)->weak_from_this(); + REALM_ASSERT(!weak.expired()); + auto shared = weak.lock(); + DictionaryPtr ret = std::make_shared(m_col_key); + ret->set_owner(shared, key); + return ret; +} + +std::shared_ptr> Dictionary::insert_list(StringData key) +{ + insert(key, Mixed(0, CollectionType::List)); + return get_list(key); +} + +std::shared_ptr> Dictionary::get_list(StringData key) const +{ + auto weak = const_cast(this)->weak_from_this(); + REALM_ASSERT(!weak.expired()); + auto shared = weak.lock(); + std::shared_ptr> ret = std::make_shared>(m_col_key); + ret->set_owner(shared, key); + return ret; +} + Mixed Dictionary::get(Mixed key) const { if (auto opt_val = try_get(key)) { @@ -542,7 +575,7 @@ std::pair Dictionary::insert(Mixed key, Mixed value) if (new_link != old_link) { CascadeState cascade_state(CascadeState::Mode::Strong); - bool recurse = replace_backlink(m_col_key, old_link, new_link, cascade_state); + bool recurse = Base::replace_backlink(m_col_key, old_link, new_link, cascade_state); if (recurse) _impl::TableFriend::remove_recursive(*my_table, cascade_state); // Throws } @@ -704,7 +737,7 @@ void Dictionary::clear() bool Dictionary::init_from_parent(bool allow_create) const { - auto ref = get_collection_ref(); + auto ref = Base::get_collection_ref(); if ((ref || allow_create) && !m_dictionary_top) { Allocator& alloc = get_alloc(); @@ -728,7 +761,7 @@ bool Dictionary::init_from_parent(bool allow_create) const } if (ref) { - m_dictionary_top->init_from_parent(); + m_dictionary_top->init_from_ref(ref); m_keys->init_from_parent(); m_values->init_from_parent(); } @@ -852,7 +885,7 @@ std::pair Dictionary::do_get_pair(size_t ndx) const bool Dictionary::clear_backlink(Mixed value, CascadeState& state) const { if (value.is_type(type_TypedLink)) { - return remove_backlink(m_col_key, value.get_link(), state); + return Base::remove_backlink(m_col_key, value.get_link(), state); } return false; } @@ -935,12 +968,12 @@ void Dictionary::migrate() ArrayParent* m_owner; }; - if (auto dict_ref = get_collection_ref()) { + if (auto dict_ref = Base::get_collection_ref()) { Allocator& alloc = get_alloc(); DictionaryClusterTree cluster_tree(this, alloc, 0); if (cluster_tree.init_from_parent()) { // Create an empty dictionary in the old ones place - set_collection_ref(0); + Base::set_collection_ref(0); ensure_created(); ArrayString keys(alloc); // We only support string type keys. @@ -996,6 +1029,16 @@ void Dictionary::to_json(std::ostream& out, size_t link_depth, JSONOutputMode ou if (val.is_type(type_TypedLink)) { fn(val); } + else if (val.is_type(type_Dictionary)) { + DummyParent parent(this->get_table(), val.get_ref()); + Dictionary dict(parent); + dict.to_json(out, link_depth, output_mode, fn); + } + else if (val.is_type(type_List)) { + DummyParent parent(this->get_table(), val.get_ref()); + Lst list(parent); + list.to_json(out, link_depth, output_mode, fn); + } else { val.to_json(out, output_mode); } @@ -1005,6 +1048,38 @@ void Dictionary::to_json(std::ostream& out, size_t link_depth, JSONOutputMode ou out << close_str; } +ref_type Dictionary::get_collection_ref(Index index, CollectionType type) const +{ + auto ndx = do_find_key(StringData(mpark::get(index))); + if (ndx != realm::not_found) { + auto val = m_values->get(ndx); + if (val.is_null() || !val.is_type(DataType(int(type)))) { + throw IllegalOperation("Not proper collection type"); + } + return val.get_ref(); + } + + return 0; +} + +void Dictionary::set_collection_ref(Index index, ref_type ref, CollectionType type) +{ + auto ndx = do_find_key(StringData(mpark::get(index))); + if (ndx == realm::not_found) { + throw StaleAccessor("Collection has been deleted"); + } + m_values->set(ndx, Mixed(ref, type)); +} + +bool Dictionary::update_if_needed() const +{ + auto status = update_if_needed_with_status(); + if (status == UpdateStatus::Detached) { + throw StaleAccessor("CollectionList no longer exists"); + } + return status == UpdateStatus::Updated; +} + /************************* DictionaryLinkValues *************************/ DictionaryLinkValues::DictionaryLinkValues(const Obj& obj, ColKey col_key) diff --git a/src/realm/dictionary.hpp b/src/realm/dictionary.hpp index eb39706972b..a705876c86a 100644 --- a/src/realm/dictionary.hpp +++ b/src/realm/dictionary.hpp @@ -34,12 +34,15 @@ class DictionaryBase : public CollectionBase { static constexpr CollectionType s_collection_type = CollectionType::Dictionary; }; -class Dictionary final : public CollectionBaseImpl { +class Dictionary final : public CollectionBaseImpl, public CollectionParent { public: using Base = CollectionBaseImpl; class Iterator; - Dictionary() {} + Dictionary() + : CollectionParent(0) + { + } ~Dictionary(); Dictionary(const Obj& obj, ColKey col_key) @@ -47,9 +50,14 @@ class Dictionary final : public CollectionBaseImpl { { this->set_owner(obj, col_key); } + Dictionary(DummyParent& parent) + : Base(parent) + { + } Dictionary(ColKey col_key); Dictionary(const Dictionary& other) : Base(static_cast(other)) + , CollectionParent(other.get_level()) , m_key_type(other.m_key_type) { *this = other; @@ -80,6 +88,17 @@ class Dictionary final : public CollectionBaseImpl { void sort_keys(std::vector& indices, bool ascending = true) const; void distinct_keys(std::vector& indices, util::Optional sort_order = util::none) const; + void set_owner(const Obj& obj, ColKey ck) override + { + Base::set_owner(obj, ck); + get_key_type(); + } + void set_owner(std::shared_ptr parent, CollectionParent::Index index) override + { + Base::set_owner(std::move(parent), index); + get_key_type(); + } + // first points to inserted/updated element. // second is true if the element was inserted std::pair insert(Mixed key, Mixed value); @@ -87,6 +106,11 @@ class Dictionary final : public CollectionBaseImpl { Obj create_and_insert_linked_object(Mixed key); + DictionaryPtr insert_dictionary(StringData key); + DictionaryPtr get_dictionary(StringData key) const; + std::shared_ptr> insert_list(StringData key); + std::shared_ptr> get_list(StringData key) const; + // throws std::out_of_range if key is not found Mixed get(Mixed key) const; // Noexcept version @@ -153,17 +177,19 @@ class Dictionary final : public CollectionBaseImpl { void migrate(); - void set_owner(const Obj& obj, ColKey ck) override + // Overriding members in CollectionParent + TableRef get_table() const noexcept override { - Base::set_owner(obj, ck); - get_key_type(); + return get_obj().get_table(); } - - void set_owner(std::shared_ptr parent, CollectionParent::Index index) override + UpdateStatus update_if_needed_with_status() const noexcept override; + bool update_if_needed() const override; + const Obj& get_object() const noexcept override { - Base::set_owner(std::move(parent), index); - get_key_type(); + return get_obj(); } + ref_type get_collection_ref(Index, CollectionType) const override; + void set_collection_ref(Index, ref_type ref, CollectionType) override; void to_json(std::ostream&, size_t, JSONOutputMode, util::FunctionRef) const override; @@ -172,7 +198,6 @@ class Dictionary final : public CollectionBaseImpl { friend class CollectionColumnAggregate; friend class DictionaryLinkValues; friend class Cluster; - friend void Obj::assign_pk_and_backlinks(const Obj& other); mutable std::unique_ptr m_dictionary_top; mutable std::unique_ptr m_keys; @@ -202,7 +227,6 @@ class Dictionary final : public CollectionBaseImpl { template void do_accumulate(size_t* return_ndx, AggregateType& agg) const; - UpdateStatus update_if_needed_with_status() const noexcept final; void ensure_created(); inline bool update() const { @@ -437,7 +461,7 @@ inline std::pair Dictionary::insert(Mixed key, const inline std::unique_ptr Dictionary::clone_collection() const { - return m_obj_mem.get_dictionary_ptr(this->get_col_key()); + return std::make_unique(m_obj_mem, this->get_col_key()); } diff --git a/src/realm/list.hpp b/src/realm/list.hpp index 4ce9ec243e8..949f64cccfc 100644 --- a/src/realm/list.hpp +++ b/src/realm/list.hpp @@ -88,12 +88,16 @@ class Lst final : public CollectionBaseImpl { Lst(ColKey col_key) : Base(col_key) { - if (!col_key.is_list()) { + if (!(col_key.is_list() || col_key.get_type() == col_type_Mixed)) { throw InvalidArgument(ErrorCodes::TypeMismatch, "Property not a list"); } check_column_type(m_col_key); } + Lst(DummyParent& parent) + : Base(parent) + { + } Lst(const Lst& other); Lst(Lst&&) noexcept; Lst& operator=(const Lst& other); diff --git a/src/realm/obj.cpp b/src/realm/obj.cpp index fb8f9249a96..6e653056946 100644 --- a/src/realm/obj.cpp +++ b/src/realm/obj.cpp @@ -1095,6 +1095,11 @@ void Obj::to_json(std::ostream& out, size_t link_depth, const std::map Obj::set_list_ptr(ColKey col_key) +{ + REALM_ASSERT(col_key.get_type() == col_type_Mixed); + update_if_needed(); + auto old_val = get(col_key); + if (!old_val.is_type(type_List)) { + set(col_key, Mixed(0, CollectionType::List)); + } + return get_list_ptr(col_key); +} + +DictionaryPtr Obj::set_dictionary_ptr(ColKey col_key) { - auto dict = CollectionParent::get_dictionary_ptr(col_key); - dict->set_owner(*this, col_key); - return dict; + REALM_ASSERT(col_key.get_type() == col_type_Mixed); + update_if_needed(); + auto old_val = get(col_key); + if (!old_val.is_type(type_Dictionary)) { + set(col_key, Mixed(ref_type(0), CollectionType::Dictionary)); + } + return get_dictionary_ptr(col_key); } -std::shared_ptr Obj::get_dictionary_ptr(const std::vector& path) const +DictionaryPtr Obj::get_dictionary_ptr(ColKey col_key) const { - REALM_ASSERT(mpark::get(path[0]).is_dictionary()); - return std::dynamic_pointer_cast(get_collection_ptr(path)); + return std::make_shared(get_dictionary(col_key)); } Dictionary Obj::get_dictionary(StringData col_name) const diff --git a/src/realm/obj.hpp b/src/realm/obj.hpp index 45471b74d03..0be1d10a4e6 100644 --- a/src/realm/obj.hpp +++ b/src/realm/obj.hpp @@ -69,10 +69,6 @@ class Obj : public CollectionParent { Obj(TableRef table, MemRef mem, ObjKey key, size_t row_ndx); // Overriding members of CollectionParent: - size_t get_level() const noexcept final - { - return 0; - } UpdateStatus update_if_needed_with_status() const noexcept final; bool update_if_needed() const final; TableRef get_table() const noexcept final @@ -254,6 +250,7 @@ class Obj : public CollectionParent { template Lst get_list(ColKey col_key) const; + LstPtr set_list_ptr(ColKey col_key); template LstPtr get_list_ptr(ColKey col_key) const; template @@ -300,10 +297,11 @@ class Obj : public CollectionParent { LnkSetPtr get_linkset_ptr(ColKey col_key) const; SetBasePtr get_setbase_ptr(ColKey col_key) const; Dictionary get_dictionary(ColKey col_key) const; - DictionaryPtr get_dictionary_ptr(ColKey col_key) const; - std::shared_ptr get_dictionary_ptr(const std::vector& path) const; Dictionary get_dictionary(StringData col_name) const; + DictionaryPtr set_dictionary_ptr(ColKey col_key); + DictionaryPtr get_dictionary_ptr(ColKey col_key) const; + CollectionBasePtr get_collection_ptr(ColKey col_key) const; CollectionBasePtr get_collection_ptr(StringData col_name) const; LinkCollectionPtr get_linkcollection_ptr(ColKey col_key) const; diff --git a/src/realm/table.hpp b/src/realm/table.hpp index c27dd048e52..1eb94a5a544 100644 --- a/src/realm/table.hpp +++ b/src/realm/table.hpp @@ -1215,9 +1215,12 @@ inline ColumnAttrMask Table::get_column_attr(ColKey column_key) const noexcept inline DataType Table::get_dictionary_key_type(ColKey column_key) const noexcept { - auto spec_ndx = colkey2spec_ndx(column_key); - REALM_ASSERT_3(spec_ndx, <, get_column_count()); - return m_spec.get_dictionary_key_type(spec_ndx); + if (column_key.is_dictionary()) { + auto spec_ndx = colkey2spec_ndx(column_key); + REALM_ASSERT_3(spec_ndx, <, get_column_count()); + return m_spec.get_dictionary_key_type(spec_ndx); + } + return type_String; } diff --git a/test/test_list.cpp b/test/test_list.cpp index 020983c0993..2809ee36526 100644 --- a/test/test_list.cpp +++ b/test/test_list.cpp @@ -30,6 +30,7 @@ using namespace std::chrono; #include +#include #include "test.hpp" #include "test_types_helper.hpp" @@ -741,9 +742,92 @@ TEST(List_Nested_InMixed) auto col_any = table->add_column(type_Mixed, "something"); Obj obj = table->create_object(); - obj.set(col_any, Mixed(ref_type(0), CollectionType::List)); + + auto dict = obj.set_dictionary_ptr(col_any); + CHECK(dict->is_empty()); + dict->insert("Four", 4); + tr->verify(); + tr->commit_and_continue_as_read(); + /* + { + "table": [ + { + "_key": 0, + "something": { + "Four": 4 + } + } + ] + } + */ + CHECK_EQUAL(dict->get("Four"), Mixed(4)); + + tr->promote_to_write(); + auto dict2 = dict->insert_dictionary("Dict"); + CHECK(dict2->is_empty()); + dict2->insert("Five", 5); + tr->verify(); + tr->commit_and_continue_as_read(); + /* + { + "table": [ + { + "_key": 0, + "something": { + "Dict": { + "Five": 5 + }, + "Four": 4 + } + } + ] + } + */ + + tr->promote_to_write(); + auto list = dict2->insert_list("List"); + CHECK(list->is_empty()); + list->add(8); + list->add(9); + tr->verify(); + std::stringstream ss; + tr->to_json(ss, 0, nullptr, JSONOutputMode::output_mode_xjson_plus); + auto j = nlohmann::json::parse(ss.str()); + // std::cout << std::setw(2) << j << std::endl; + tr->commit_and_continue_as_read(); + /* + { + "table": [ + { + "_key": 0, + "something": { + "Dict": { + "Five": 5, + "List": [ + 8, + 9 + ] + }, + "Four": 4 + } + } + ] + } + */ + + tr->promote_to_write(); + obj.set(col_any, Mixed(5)); + tr->verify(); + tr->commit_and_continue_as_read(); + + tr->promote_to_write(); + // Assign another collection type. The old dictionary should be disposed. + auto list2 = obj.set_list_ptr(col_any); + CHECK(list2->is_empty()); + list2->add("Hello"); + tr->verify(); tr->commit_and_continue_as_read(); - CHECK(obj.get_any(col_any).is_type(type_List)); + CHECK_EQUAL(list2->get(0), Mixed("Hello")); } TEST(List_NestedList_Remove)