From e239793ac9226d36823bad9a4af2435e565f49f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B8rgen=20Edelbo?= Date: Tue, 28 Mar 2023 12:01:55 +0200 Subject: [PATCH] Allow Collections to be owned by Collections --- Package.swift | 1 + src/realm/CMakeLists.txt | 2 + src/realm/collection.hpp | 127 +++++++++++-- src/realm/collection_parent.cpp | 263 ++++++++++++++++++++++++++ src/realm/collection_parent.hpp | 106 +++++++++++ src/realm/dictionary.cpp | 95 ++++------ src/realm/dictionary.hpp | 38 +++- src/realm/list.cpp | 110 ++--------- src/realm/list.hpp | 81 ++++---- src/realm/obj.cpp | 103 +++------- src/realm/obj.hpp | 90 ++++----- src/realm/object-store/dictionary.cpp | 8 + src/realm/set.cpp | 93 ++------- src/realm/set.hpp | 57 ++++-- src/realm/table.hpp | 1 + test/test_links.cpp | 7 + test/test_table.cpp | 5 + 17 files changed, 750 insertions(+), 437 deletions(-) create mode 100644 src/realm/collection_parent.cpp create mode 100644 src/realm/collection_parent.hpp diff --git a/Package.swift b/Package.swift index aae5ec63683..7931b33fda8 100644 --- a/Package.swift +++ b/Package.swift @@ -62,6 +62,7 @@ let notSyncServerSources: [String] = [ "realm/cluster.cpp", "realm/cluster_tree.cpp", "realm/collection.cpp", + "realm/collection_parent.cpp", "realm/column_binary.cpp", "realm/db.cpp", "realm/decimal128.cpp", diff --git a/src/realm/CMakeLists.txt b/src/realm/CMakeLists.txt index 62dfa4c3757..efe8b79f299 100644 --- a/src/realm/CMakeLists.txt +++ b/src/realm/CMakeLists.txt @@ -24,6 +24,7 @@ set(REALM_SOURCES chunked_binary.cpp cluster.cpp collection.cpp + collection_parent.cpp cluster_tree.cpp error_codes.cpp column_binary.cpp @@ -138,6 +139,7 @@ set(REALM_INSTALL_HEADERS cluster.hpp cluster_tree.hpp collection.hpp + collection_parent.hpp column_binary.hpp column_fwd.hpp column_integer.hpp diff --git a/src/realm/collection.hpp b/src/realm/collection.hpp index 311bd6658cc..49f33bb4be4 100644 --- a/src/realm/collection.hpp +++ b/src/realm/collection.hpp @@ -119,6 +119,10 @@ class CollectionBase { return ndx; } + virtual void set_owner(const Obj& obj, CollectionParent::Index index) = 0; + virtual void set_owner(std::shared_ptr parent, CollectionParent::Index index) = 0; + + StringData get_property_name() const { return get_table()->get_column_name(get_col_key()); @@ -339,9 +343,23 @@ class CollectionBaseImpl : public Interface, protected ArrayParent { const Obj& get_obj() const noexcept final { - return m_obj; + return m_obj_mem; } + ref_type get_collection_ref() const noexcept + { + try { + return m_parent->get_collection_ref(m_index); + } + catch (const KeyNotFound&) { + return ref_type(0); + } + } + + void set_collection_ref(ref_type ref) + { + m_parent->set_collection_ref(m_index, ref); + } /// Returns true if the accessor has changed since the last time /// `has_changed()` was called. @@ -364,12 +382,37 @@ class CollectionBaseImpl : public Interface, protected ArrayParent { return false; } + void set_owner(const Obj& obj, CollectionParent::Index index) override + { + m_obj_mem = obj; + m_parent = &m_obj_mem; + m_index = index; + if (obj) { + m_alloc = &obj.get_alloc(); + } + } + + void set_owner(std::shared_ptr parent, CollectionParent::Index index) override + { + m_obj_mem = parent->get_object(); + m_col_parent = std::move(parent); + m_parent = m_col_parent.get(); + m_index = index; + if (m_obj_mem) { + m_alloc = &m_obj_mem.get_alloc(); + } + } + using Interface::get_owner_key; using Interface::get_table; using Interface::get_target_table; protected: - Obj m_obj; + Obj m_obj_mem; + std::shared_ptr m_col_parent; + CollectionParent* m_parent = nullptr; + CollectionParent::Index m_index; + Allocator* m_alloc = nullptr; ColKey m_col_key; bool m_nullable = false; @@ -379,18 +422,49 @@ class CollectionBaseImpl : public Interface, protected ArrayParent { mutable uint_fast64_t m_last_content_version = 0; CollectionBaseImpl() = default; - CollectionBaseImpl(const CollectionBaseImpl& other) = default; - CollectionBaseImpl(CollectionBaseImpl&& other) = default; + CollectionBaseImpl(const CollectionBaseImpl& other) + : m_obj_mem(other.m_obj_mem) + , m_col_parent(other.m_col_parent) + , m_parent(m_col_parent ? m_col_parent.get() : &m_obj_mem) + , m_index(other.m_index) + , m_alloc(other.m_alloc) + , m_col_key(other.m_col_key) + , m_nullable(other.m_nullable) + { + } CollectionBaseImpl(const Obj& obj, ColKey col_key) noexcept - : m_obj(obj) + : m_obj_mem(obj) + , m_parent(&m_obj_mem) + , m_index(col_key) , m_col_key(col_key) , m_nullable(col_key.is_nullable()) { + if (obj) { + m_alloc = &m_obj_mem.get_alloc(); + } } - CollectionBaseImpl& operator=(const CollectionBaseImpl& other) = default; - CollectionBaseImpl& operator=(CollectionBaseImpl&& other) = default; + CollectionBaseImpl(ColKey col_key) noexcept + : m_col_key(col_key) + , m_nullable(col_key.is_nullable()) + { + } + + CollectionBaseImpl& operator=(const CollectionBaseImpl& other) + { + if (this != &other) { + m_obj_mem = other.m_obj_mem; + m_col_parent = other.m_col_parent; + m_parent = m_col_parent ? m_col_parent.get() : &m_obj_mem; + m_alloc = other.m_alloc; + m_index = other.m_index; + m_col_key = other.m_col_key; + m_nullable = other.m_nullable; + } + + return *this; + } bool operator==(const Derived& other) const noexcept { @@ -420,10 +494,10 @@ class CollectionBaseImpl : public Interface, protected ArrayParent { /// `UpdateStatus::NoChange`, and the caller is allowed to not do anything. virtual UpdateStatus update_if_needed() const { - UpdateStatus status = m_obj.update_if_needed_with_status(); + UpdateStatus status = m_parent ? m_parent->update_if_needed_with_status() : UpdateStatus::Detached; if (status != UpdateStatus::Detached) { - auto content_version = m_obj.get_alloc().get_content_version(); + auto content_version = m_alloc->get_content_version(); if (content_version != m_content_version) { m_content_version = content_version; status = UpdateStatus::Updated; @@ -445,8 +519,8 @@ class CollectionBaseImpl : public Interface, protected ArrayParent { /// method will never return `UpdateStatus::Detached`. virtual UpdateStatus ensure_created() { - bool changed = m_obj.update_if_needed(); // Throws if the object does not exist. - auto content_version = m_obj.get_alloc().get_content_version(); + bool changed = m_parent->update_if_needed(); // Throws if the object does not exist. + auto content_version = m_alloc->get_content_version(); if (changed || content_version != m_content_version) { m_content_version = content_version; @@ -457,7 +531,7 @@ class CollectionBaseImpl : public Interface, protected ArrayParent { void bump_content_version() { - m_content_version = m_obj.bump_content_version(); + m_content_version = m_parent->bump_content_version(); } /// Reset the accessor's tracking of the content version. Derived classes @@ -474,18 +548,13 @@ class CollectionBaseImpl : public Interface, protected ArrayParent { ref_type get_child_ref(size_t child_ndx) const noexcept final { static_cast(child_ndx); - try { - return to_ref(m_obj._get(m_col_key.get_index())); - } - catch (const KeyNotFound&) { - return ref_type(0); - } + return get_collection_ref(); } void update_child_ref(size_t child_ndx, ref_type new_ref) final { static_cast(child_ndx); - m_obj.set_int(m_col_key, from_ref(new_ref)); + set_collection_ref(new_ref); } }; @@ -765,6 +834,26 @@ struct CollectionIterator { size_t m_ndx = size_t(-1); }; +template +class IteratorAdapter { +public: + IteratorAdapter(T* keys) + : m_list(keys) + { + } + CollectionIterator begin() const + { + return CollectionIterator(m_list, 0); + } + CollectionIterator end() const + { + return CollectionIterator(m_list, m_list->size()); + } + +private: + T* m_list; +}; + } // namespace realm #endif // REALM_COLLECTION_HPP diff --git a/src/realm/collection_parent.cpp b/src/realm/collection_parent.cpp new file mode 100644 index 00000000000..240e351b47b --- /dev/null +++ b/src/realm/collection_parent.cpp @@ -0,0 +1,263 @@ +/************************************************************************* + * + * Copyright 2023 Realm Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + **************************************************************************/ + +#include +#include "realm/list.hpp" +#include "realm/set.hpp" +#include "realm/dictionary.hpp" + +namespace realm { + +/***************************** CollectionParent ******************************/ + +CollectionParent::~CollectionParent() {} + +void CollectionParent::set_backlink(ColKey col_key, ObjLink new_link) const +{ + if (new_link && new_link.get_obj_key()) { + auto t = get_table(); + auto target_table = t->get_parent_group()->get_table(new_link.get_table_key()); + ColKey backlink_col_key; + auto type = col_key.get_type(); + if (type == col_type_TypedLink || type == col_type_Mixed || col_key.is_dictionary()) { + // This may modify the target table + backlink_col_key = target_table->find_or_add_backlink_column(col_key, t->get_key()); + // it is possible that this was a link to the same table and that adding a backlink column has + // caused the need to update this object as well. + update_if_needed(); + } + else { + backlink_col_key = t->get_opposite_column(col_key); + } + auto obj_key = new_link.get_obj_key(); + auto target_obj = obj_key.is_unresolved() ? target_table->try_get_tombstone(obj_key) + : target_table->try_get_object(obj_key); + if (!target_obj) { + throw InvalidArgument(ErrorCodes::KeyNotFound, "Target object not found"); + } + target_obj.add_backlink(backlink_col_key, get_object().get_key()); + } +} + +bool CollectionParent::replace_backlink(ColKey col_key, ObjLink old_link, ObjLink new_link, CascadeState& state) const +{ + bool recurse = remove_backlink(col_key, old_link, state); + set_backlink(col_key, new_link); + + return recurse; +} + +bool CollectionParent::remove_backlink(ColKey col_key, ObjLink old_link, CascadeState& state) const +{ + if (old_link && old_link.get_obj_key()) { + auto t = get_table(); + REALM_ASSERT(t->valid_column(col_key)); + ObjKey old_key = old_link.get_obj_key(); + auto target_obj = t->get_parent_group()->get_object(old_link); + TableRef target_table = target_obj.get_table(); + ColKey backlink_col_key; + auto type = col_key.get_type(); + if (type == col_type_TypedLink || type == col_type_Mixed || col_key.is_dictionary()) { + backlink_col_key = target_table->find_or_add_backlink_column(col_key, t->get_key()); + } + else { + backlink_col_key = t->get_opposite_column(col_key); + } + + bool strong_links = target_table->is_embedded(); + bool is_unres = old_key.is_unresolved(); + + bool last_removed = target_obj.remove_one_backlink(backlink_col_key, get_object().get_key()); // Throws + if (is_unres) { + if (last_removed) { + // Check is there are more backlinks + if (!target_obj.has_backlinks(false)) { + // Tombstones can be erased right away - there is no cascading effect + target_table->m_tombstones->erase(old_key, state); + } + } + } + else { + return state.enqueue_for_cascade(target_obj, strong_links, last_removed); + } + } + + return false; +} + +LstBasePtr CollectionParent::get_listbase_ptr(ColKey col_key) const +{ + auto table = get_table(); + auto attr = table->get_column_attr(col_key); + REALM_ASSERT(attr.test(col_attr_List)); + bool nullable = attr.test(col_attr_Nullable); + + switch (table->get_column_type(col_key)) { + case type_Int: { + if (nullable) + return std::make_unique>>(col_key); + else + return std::make_unique>(col_key); + } + case type_Bool: { + if (nullable) + return std::make_unique>>(col_key); + else + return std::make_unique>(col_key); + } + case type_Float: { + if (nullable) + return std::make_unique>>(col_key); + else + return std::make_unique>(col_key); + } + case type_Double: { + if (nullable) + return std::make_unique>>(col_key); + else + return std::make_unique>(col_key); + } + case type_String: { + return std::make_unique>(col_key); + } + case type_Binary: { + return std::make_unique>(col_key); + } + case type_Timestamp: { + return std::make_unique>(col_key); + } + case type_Decimal: { + return std::make_unique>(col_key); + } + case type_ObjectId: { + if (nullable) + return std::make_unique>>(col_key); + else + return std::make_unique>(col_key); + } + case type_UUID: { + if (nullable) + return std::make_unique>>(col_key); + else + return std::make_unique>(col_key); + } + case type_TypedLink: { + return std::make_unique>(col_key); + } + case type_Mixed: { + return std::make_unique>(col_key); + } + case type_LinkList: + return std::make_unique(col_key); + case type_Link: + break; + } + REALM_TERMINATE("Unsupported column type"); +} + +SetBasePtr CollectionParent::get_setbase_ptr(ColKey col_key) const +{ + auto table = get_table(); + auto attr = table->get_column_attr(col_key); + REALM_ASSERT(attr.test(col_attr_Set)); + bool nullable = attr.test(col_attr_Nullable); + + switch (table->get_column_type(col_key)) { + case type_Int: { + if (nullable) + return std::make_unique>>(col_key); + else + return std::make_unique>(col_key); + } + case type_Bool: { + if (nullable) + return std::make_unique>>(col_key); + else + return std::make_unique>(col_key); + } + case type_Float: { + if (nullable) + return std::make_unique>>(col_key); + else + return std::make_unique>(col_key); + } + case type_Double: { + if (nullable) + return std::make_unique>>(col_key); + else + return std::make_unique>(col_key); + } + case type_String: { + return std::make_unique>(col_key); + } + case type_Binary: { + return std::make_unique>(col_key); + } + case type_Timestamp: { + return std::make_unique>(col_key); + } + case type_Decimal: { + return std::make_unique>(col_key); + } + case type_ObjectId: { + if (nullable) + return std::make_unique>>(col_key); + else + return std::make_unique>(col_key); + } + case type_UUID: { + if (nullable) + return std::make_unique>>(col_key); + else + return std::make_unique>(col_key); + } + case type_TypedLink: { + return std::make_unique>(col_key); + } + case type_Mixed: { + return std::make_unique>(col_key); + } + case type_Link: { + return std::make_unique(col_key); + } + case type_LinkList: + break; + } + 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()) { + return get_listbase_ptr(col_key); + } + else if (col_key.is_set()) { + return get_setbase_ptr(col_key); + } + else if (col_key.is_dictionary()) { + return get_dictionary_ptr(col_key); + } + return {}; +} + +} // namespace realm \ No newline at end of file diff --git a/src/realm/collection_parent.hpp b/src/realm/collection_parent.hpp new file mode 100644 index 00000000000..dd07cb465ab --- /dev/null +++ b/src/realm/collection_parent.hpp @@ -0,0 +1,106 @@ +/************************************************************************* + * + * Copyright 2023 Realm Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + **************************************************************************/ + +#ifndef REALM_COLLECTION_PARENT_HPP +#define REALM_COLLECTION_PARENT_HPP + +#include +#include +#include + +#include + +namespace realm { + +class Obj; +class Replication; +class CascadeState; + +class CollectionBase; +class CollectionList; +class LstBase; +class SetBase; +class Dictionary; + +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; + +/// The status of an accessor after a call to `update_if_needed()`. +enum class UpdateStatus { + /// The owning object or column no longer exist, and the accessor could + /// not be updated. The accessor should be left in a detached state + /// after this, and further calls to `update_if_needed()` are not + /// guaranteed to reattach the accessor. + Detached, + + /// The underlying data of the accessor was changed since the last call + /// to `update_if_needed()`. The accessor is still valid. + Updated, + + /// The underlying data of the accessor did not change since the last + /// call to `update_if_needed()`, and the accessor is still valid in its + /// current state. + NoChange, +}; + +class CollectionParent { +public: + using Index = mpark::variant; + + // Return the nesting level of the parent + virtual size_t get_level() const noexcept = 0; + virtual Replication* get_replication() const = 0; + /// Update the accessor (and return `UpdateStatus::Detached` if the parent + /// is no longer valid, rather than throwing an exception). + virtual UpdateStatus update_if_needed_with_status() const = 0; + /// Check if the storage version has changed and update if it has + /// Return true if the object was updated + virtual bool update_if_needed() const = 0; + virtual int_fast64_t bump_content_version() = 0; + virtual void bump_both_versions() = 0; + /// Get table of owning object + virtual TableRef get_table() const noexcept = 0; + /// Get owning object + virtual const Obj& get_object() const noexcept = 0; + /// Get the top ref from pareht + virtual ref_type get_collection_ref(Index) const noexcept = 0; + /// Set the top ref from pareht + virtual void set_collection_ref(Index, ref_type ref) = 0; + + // Used when inserting a new link. You will not remove existing links in this process + void set_backlink(ColKey col_key, ObjLink new_link) const; + // Used when replacing a link, return true if CascadeState contains objects to remove + bool replace_backlink(ColKey col_key, ObjLink old_link, ObjLink new_link, CascadeState& state) const; + // Used when removing a backlink, return true if CascadeState contains objects to remove + bool remove_backlink(ColKey col_key, ObjLink old_link, CascadeState& state) const; + +protected: + virtual ~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; +}; + +} // namespace realm + +#endif diff --git a/src/realm/dictionary.cpp b/src/realm/dictionary.cpp index e83cb538799..fd59f8a957d 100644 --- a/src/realm/dictionary.cpp +++ b/src/realm/dictionary.cpp @@ -41,45 +41,24 @@ void validate_key_value(const Mixed& key) } } -template -class SortedKeys { -public: - SortedKeys(T* keys) - : m_list(keys) - { - } - CollectionIterator begin() const - { - return CollectionIterator(m_list, 0); - } - CollectionIterator end() const - { - return CollectionIterator(m_list, m_list->size()); - } - -private: - T* m_list; -}; } // namespace /******************************** Dictionary *********************************/ -Dictionary::Dictionary(const Obj& obj, ColKey col_key) - : Base(obj, col_key) - , m_key_type(m_obj.get_table()->get_dictionary_key_type(m_col_key)) +Dictionary::Dictionary(ColKey col_key) + : Base(col_key) { if (!col_key.is_dictionary()) { throw InvalidArgument(ErrorCodes::TypeMismatch, "Property not a dictionary"); } - if (!(m_key_type == type_String || m_key_type == type_Int)) - throw Exception(ErrorCodes::InvalidDictionaryKey, "Dictionary keys can only be strings or integers"); } Dictionary::Dictionary(Allocator& alloc, ColKey col_key, ref_type ref) : Base(Obj{}, col_key) , m_key_type(type_String) { + this->m_alloc = &alloc; REALM_ASSERT(ref); m_dictionary_top.reset(new Array(alloc)); m_dictionary_top->init_from_ref(ref); @@ -405,13 +384,13 @@ void Dictionary::sort_keys(std::vector& indices, bool ascending) const // We rely in the design that the keys are already sorted switch (m_key_type) { case type_String: { - SortedKeys help(static_cast*>(m_keys.get())); + IteratorAdapter help(static_cast*>(m_keys.get())); auto is_sorted = std::is_sorted(help.begin(), help.end()); REALM_ASSERT(is_sorted); break; } case type_Int: { - SortedKeys help(static_cast*>(m_keys.get())); + IteratorAdapter help(static_cast*>(m_keys.get())); auto is_sorted = std::is_sorted(help.begin(), help.end()); REALM_ASSERT(is_sorted); break; @@ -488,7 +467,7 @@ std::pair Dictionary::insert(Mixed key, Mixed value) } else { if (m_col_key.get_type() == col_type_Link && value.get_type() == type_TypedLink) { - if (m_obj.get_table()->get_opposite_table_key(m_col_key) != value.get().get_table_key()) { + if (m_parent->get_table()->get_opposite_table_key(m_col_key) != value.get().get_table_key()) { throw InvalidArgument(ErrorCodes::InvalidDictionaryValue, "Dictionary::insert: Wrong object type"); } } @@ -504,10 +483,10 @@ std::pair Dictionary::insert(Mixed key, Mixed value) if (value.is_type(type_TypedLink)) { new_link = value.get(); if (!new_link.is_unresolved()) - m_obj.get_table()->get_parent_group()->validate(new_link); + m_parent->get_table()->get_parent_group()->validate(new_link); } else if (value.is_type(type_Link)) { - auto target_table = m_obj.get_table()->get_opposite_table(m_col_key); + auto target_table = m_parent->get_table()->get_opposite_table(m_col_key); auto key = value.get(); if (!key.is_unresolved() && !target_table->is_valid(key)) { throw InvalidArgument(ErrorCodes::KeyNotFound, "Target object not found"); @@ -540,7 +519,7 @@ std::pair Dictionary::insert(Mixed key, Mixed value) old_entry = true; } - if (Replication* repl = this->m_obj.get_replication()) { + if (Replication* repl = this->m_parent->get_replication()) { if (old_entry) { repl->dictionary_set(*this, ndx, key, value); } @@ -562,9 +541,9 @@ std::pair Dictionary::insert(Mixed key, Mixed value) if (new_link != old_link) { CascadeState cascade_state(CascadeState::Mode::Strong); - bool recurse = m_obj.replace_backlink(m_col_key, old_link, new_link, cascade_state); + bool recurse = m_parent->replace_backlink(m_col_key, old_link, new_link, cascade_state); if (recurse) - _impl::TableFriend::remove_recursive(*m_obj.get_table(), cascade_state); // Throws + _impl::TableFriend::remove_recursive(*m_parent->get_table(), cascade_state); // Throws } return {Iterator(this, ndx), !old_entry}; @@ -694,7 +673,7 @@ void Dictionary::nullify(Mixed key) auto ndx = do_find_key(key); REALM_ASSERT(ndx != realm::npos); - if (Replication* repl = this->m_obj.get_replication()) { + if (Replication* repl = m_parent->get_replication()) { repl->dictionary_set(*this, ndx, key, Mixed()); } @@ -711,7 +690,8 @@ void Dictionary::remove_backlinks(CascadeState& state) const void Dictionary::clear() { if (size() > 0) { - Replication* repl = m_obj.get_replication(); + // TODO: Should we have a "dictionary_clear" instruction? + Replication* repl = m_parent->get_replication(); bool recurse = false; CascadeState cascade_state(CascadeState::Mode::Strong); if (repl) { @@ -728,31 +708,31 @@ void Dictionary::clear() update_child_ref(0, 0); if (recurse) - _impl::TableFriend::remove_recursive(*m_obj.get_table(), cascade_state); // Throws + _impl::TableFriend::remove_recursive(*m_parent->get_table(), cascade_state); // Throws } } bool Dictionary::init_from_parent(bool allow_create) const { - auto ref = m_obj._get(m_col_key.get_index()); + auto ref = get_collection_ref(); if ((ref || allow_create) && !m_dictionary_top) { - m_dictionary_top.reset(new Array(m_obj.get_alloc())); - m_dictionary_top->set_parent(const_cast(this), m_obj.get_row_ndx()); + m_dictionary_top.reset(new Array(*m_alloc)); + m_dictionary_top->set_parent(const_cast(this), m_parent->get_object().get_row_ndx()); switch (m_key_type) { case type_String: { - m_keys.reset(new BPlusTree(m_obj.get_alloc())); + m_keys.reset(new BPlusTree(*m_alloc)); break; } case type_Int: { - m_keys.reset(new BPlusTree(m_obj.get_alloc())); + m_keys.reset(new BPlusTree(*m_alloc)); break; } default: break; } m_keys->set_parent(m_dictionary_top.get(), 0); - m_values.reset(new BPlusTree(m_obj.get_alloc())); + m_values.reset(new BPlusTree(*m_alloc)); m_values->set_parent(m_dictionary_top.get(), 1); } @@ -796,7 +776,7 @@ std::pair Dictionary::find_impl(Mixed key) const noexcept case type_String: { auto keys = static_cast*>(m_keys.get()); StringData val = key.get(); - SortedKeys help(keys); + IteratorAdapter help(keys); auto it = std::lower_bound(help.begin(), help.end(), val); if (it.index() < sz) { actual = *it; @@ -807,7 +787,7 @@ std::pair Dictionary::find_impl(Mixed key) const noexcept case type_Int: { auto keys = static_cast*>(m_keys.get()); Int val = key.get(); - SortedKeys help(keys); + IteratorAdapter help(keys); auto it = std::lower_bound(help.begin(), help.end(), val); if (it.index() < sz) { actual = *it; @@ -845,9 +825,9 @@ void Dictionary::do_erase(size_t ndx, Mixed key) CascadeState cascade_state(CascadeState::Mode::Strong); bool recurse = clear_backlink(old_value, cascade_state); if (recurse) - _impl::TableFriend::remove_recursive(*m_obj.get_table(), cascade_state); // Throws + _impl::TableFriend::remove_recursive(*m_parent->get_table(), cascade_state); // Throws - if (Replication* repl = this->m_obj.get_replication()) { + if (Replication* repl = this->m_parent->get_replication()) { repl->dictionary_erase(*this, ndx, key); } @@ -881,7 +861,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 m_obj.remove_backlink(m_col_key, value.get_link(), state); + return m_parent->remove_backlink(m_col_key, value.get_link(), state); } return false; } @@ -892,7 +872,7 @@ void Dictionary::swap_content(Array& fields1, Array& fields2, size_t index1, siz // Swap keys REALM_ASSERT(m_key_type == type_String); - ArrayString keys(m_obj.get_alloc()); + ArrayString keys(*m_alloc); keys.set_parent(&fields1, 1); keys.init_from_parent(); buf1 = keys.get(index1); @@ -907,7 +887,7 @@ void Dictionary::swap_content(Array& fields1, Array& fields2, size_t index1, siz keys.set(index1, buf2); // Swap values - ArrayMixed values(m_obj.get_alloc()); + ArrayMixed values(*m_alloc); values.set_parent(&fields1, 2); values.init_from_parent(); Mixed val1 = values.get(index1); @@ -937,6 +917,13 @@ void Dictionary::verify() const REALM_ASSERT(m_keys->size() == m_values->size()); } +void Dictionary::get_key_type() +{ + m_key_type = m_parent->get_table()->get_dictionary_key_type(m_col_key); + if (!(m_key_type == type_String || m_key_type == type_Int)) + throw Exception(ErrorCodes::InvalidDictionaryKey, "Dictionary keys can only be strings or integers"); +} + void Dictionary::migrate() { // Dummy implementation of legacy dictionary cluster tree @@ -957,15 +944,15 @@ void Dictionary::migrate() ArrayParent* m_owner; }; - if (auto dict_ref = m_obj._get(get_col_key().get_index())) { - DictionaryClusterTree cluster_tree(this, m_obj.get_alloc(), m_obj.get_row_ndx()); + if (auto dict_ref = get_collection_ref()) { + DictionaryClusterTree cluster_tree(this, *m_alloc, m_parent->get_object().get_row_ndx()); if (cluster_tree.init_from_parent()) { // Create an empty dictionary in the old ones place - m_obj.set_int(get_col_key(), 0); + set_collection_ref(0); ensure_created(); - ArrayString keys(m_obj.get_alloc()); // We only support string type keys. - ArrayMixed values(m_obj.get_alloc()); + ArrayString keys(*m_alloc); // We only support string type keys. + ArrayMixed values(*m_alloc); constexpr ColKey key_col(ColKey::Idx{0}, col_type_String, ColumnAttrMask(), 0); constexpr ColKey value_col(ColKey::Idx{1}, col_type_Mixed, ColumnAttrMask(), 0); size_t nb_elements = cluster_tree.size(); @@ -986,7 +973,7 @@ void Dictionary::migrate() return IteratorControl::AdvanceToNext; }); REALM_ASSERT(size() == nb_elements); - Array::destroy_deep(to_ref(dict_ref), m_obj.get_alloc()); + Array::destroy_deep(to_ref(dict_ref), *m_alloc); } else { REALM_UNREACHABLE(); diff --git a/src/realm/dictionary.hpp b/src/realm/dictionary.hpp index b2856f05c3e..c90ddaf88a8 100644 --- a/src/realm/dictionary.hpp +++ b/src/realm/dictionary.hpp @@ -34,7 +34,12 @@ class Dictionary final : public CollectionBaseImpl { Dictionary() {} ~Dictionary(); - Dictionary(const Obj& obj, ColKey col_key); + Dictionary(const Obj& obj, ColKey col_key) + : Dictionary(col_key) + { + this->set_owner(obj, col_key); + } + Dictionary(ColKey col_key); Dictionary(const Dictionary& other) : Base(static_cast(other)) , m_key_type(other.m_key_type) @@ -101,7 +106,7 @@ class Dictionary final : public CollectionBaseImpl { void for_all_values(T&& f) { if (update()) { - BPlusTree values(m_obj.get_alloc()); + BPlusTree values(*m_alloc); values.init_from_ref(m_dictionary_top->get_as_ref(1)); auto func = [&f](BPlusTreeNode* node, size_t) { auto leaf = static_cast::LeafNode*>(node); @@ -120,7 +125,7 @@ class Dictionary final : public CollectionBaseImpl { void for_all_keys(Func&& f) { if (update()) { - BPlusTree keys(m_obj.get_alloc()); + BPlusTree keys(*m_alloc); keys.init_from_ref(m_dictionary_top->get_as_ref(0)); auto func = [&f](BPlusTreeNode* node, size_t) { auto leaf = static_cast::LeafNode*>(node); @@ -141,6 +146,18 @@ class Dictionary final : public CollectionBaseImpl { void migrate(); + void set_owner(const Obj& obj, CollectionParent::Index index) override + { + Base::set_owner(obj, index); + get_key_type(); + } + + void set_owner(std::shared_ptr parent, CollectionParent::Index index) override + { + Base::set_owner(std::move(parent), index); + get_key_type(); + } + private: template friend class CollectionColumnAggregate; @@ -183,6 +200,7 @@ class Dictionary final : public CollectionBaseImpl { return update_if_needed() != UpdateStatus::Detached; } void verify() const; + void get_key_type(); }; class Dictionary::Iterator { @@ -373,7 +391,7 @@ class DictionaryLinkValues final : public ObjCollectionBase { { return m_source.update_if_needed(); } - BPlusTree* get_mutable_tree() const + BPlusTree* get_mutable_tree() const final { // We are faking being an ObjList because the underlying storage is not // actually a BPlusTree for dictionaries it is all mixed values. @@ -384,6 +402,16 @@ class DictionaryLinkValues final : public ObjCollectionBase { return nullptr; } + void set_owner(const Obj& obj, CollectionParent::Index index) override + { + m_source.set_owner(obj, index); + } + + void set_owner(std::shared_ptr parent, CollectionParent::Index index) override + { + m_source.set_owner(std::move(parent), index); + } + private: Dictionary m_source; }; @@ -396,7 +424,7 @@ inline std::pair Dictionary::insert(Mixed key, const inline std::unique_ptr Dictionary::clone_collection() const { - return m_obj.get_dictionary_ptr(m_col_key); + return m_obj_mem.get_dictionary_ptr(this->get_col_key()); } diff --git a/src/realm/list.cpp b/src/realm/list.cpp index f1e2f93d546..35d41bf690d 100644 --- a/src/realm/list.cpp +++ b/src/realm/list.cpp @@ -38,76 +38,6 @@ namespace realm { -// FIXME: This method belongs in obj.cpp. -LstBasePtr Obj::get_listbase_ptr(ColKey col_key) const -{ - auto attr = get_table()->get_column_attr(col_key); - REALM_ASSERT(attr.test(col_attr_List)); - bool nullable = attr.test(col_attr_Nullable); - - switch (get_table()->get_column_type(col_key)) { - case type_Int: { - if (nullable) - return std::make_unique>>(*this, col_key); - else - return std::make_unique>(*this, col_key); - } - case type_Bool: { - if (nullable) - return std::make_unique>>(*this, col_key); - else - return std::make_unique>(*this, col_key); - } - case type_Float: { - if (nullable) - return std::make_unique>>(*this, col_key); - else - return std::make_unique>(*this, col_key); - } - case type_Double: { - if (nullable) - return std::make_unique>>(*this, col_key); - else - return std::make_unique>(*this, col_key); - } - case type_String: { - return std::make_unique>(*this, col_key); - } - case type_Binary: { - return std::make_unique>(*this, col_key); - } - case type_Timestamp: { - return std::make_unique>(*this, col_key); - } - case type_Decimal: { - return std::make_unique>(*this, col_key); - } - case type_ObjectId: { - if (nullable) - return std::make_unique>>(*this, col_key); - else - return std::make_unique>(*this, col_key); - } - case type_UUID: { - if (nullable) - return std::make_unique>>(*this, col_key); - else - return std::make_unique>(*this, col_key); - } - case type_TypedLink: { - return std::make_unique>(*this, col_key); - } - case type_Mixed: { - return std::make_unique>(*this, col_key); - } - case type_LinkList: - return get_linklist_ptr(col_key); - case type_Link: - break; - } - REALM_TERMINATE("Unsupported column type"); -} - /****************************** Lst aggregates *******************************/ namespace { @@ -197,12 +127,12 @@ void Lst::distinct(std::vector& indices, util::Optional sort_or template <> void Lst::do_set(size_t ndx, ObjKey target_key) { - auto origin_table = m_obj.get_table(); + auto origin_table = m_parent->get_table(); auto target_table_key = origin_table->get_opposite_table_key(m_col_key); ObjKey old_key = this->get(ndx); CascadeState state(CascadeState::Mode::Strong); bool recurse = - m_obj.replace_backlink(m_col_key, {target_table_key, old_key}, {target_table_key, target_key}, state); + m_parent->replace_backlink(m_col_key, {target_table_key, old_key}, {target_table_key, target_key}, state); m_tree->set(ndx, target_key); @@ -222,9 +152,9 @@ void Lst::do_set(size_t ndx, ObjKey target_key) template <> void Lst::do_insert(size_t ndx, ObjKey target_key) { - auto origin_table = m_obj.get_table(); + auto origin_table = m_parent->get_table(); auto target_table_key = origin_table->get_opposite_table_key(m_col_key); - m_obj.set_backlink(m_col_key, {target_table_key, target_key}); + m_parent->set_backlink(m_col_key, {target_table_key, target_key}); m_tree->insert(ndx, target_key); if (target_key.is_unresolved()) { m_tree->set_context_flag(true); @@ -234,12 +164,12 @@ void Lst::do_insert(size_t ndx, ObjKey target_key) template <> void Lst::do_remove(size_t ndx) { - auto origin_table = m_obj.get_table(); + auto origin_table = m_parent->get_table(); auto target_table_key = origin_table->get_opposite_table_key(m_col_key); ObjKey old_key = get(ndx); CascadeState state(old_key.is_unresolved() ? CascadeState::Mode::All : CascadeState::Mode::Strong); - bool recurse = m_obj.remove_backlink(m_col_key, {target_table_key, old_key}, state); + bool recurse = m_parent->remove_backlink(m_col_key, {target_table_key, old_key}, state); m_tree->erase(ndx); @@ -255,8 +185,8 @@ void Lst::do_remove(size_t ndx) template <> void Lst::do_clear() { - auto origin_table = m_obj.get_table(); - TableRef target_table = m_obj.get_target_table(m_col_key); + auto origin_table = m_parent->get_table(); + TableRef target_table = m_parent->get_object().get_target_table(m_col_key); size_t sz = size(); if (!target_table->is_embedded()) { @@ -278,7 +208,7 @@ void Lst::do_clear() for (size_t ndx = 0; ndx < sz; ++ndx) { ObjKey target_key = m_tree->get(ndx); Obj target_obj = target_table->get_object(target_key); - target_obj.remove_one_backlink(backlink_col, m_obj.get_key()); // Throws + target_obj.remove_one_backlink(backlink_col, m_parent->get_object().get_key()); // Throws // embedded objects should only have one incoming link REALM_ASSERT_EX(target_obj.get_backlink_count() == 0, target_obj.get_backlink_count()); state.m_to_be_deleted.emplace_back(target_table_key, target_key); @@ -295,12 +225,12 @@ void Lst::do_set(size_t ndx, ObjLink target_link) { ObjLink old_link = get(ndx); CascadeState state(old_link.get_obj_key().is_unresolved() ? CascadeState::Mode::All : CascadeState::Mode::Strong); - bool recurse = m_obj.replace_backlink(m_col_key, old_link, target_link, state); + bool recurse = m_parent->replace_backlink(m_col_key, old_link, target_link, state); m_tree->set(ndx, target_link); if (recurse) { - auto origin_table = m_obj.get_table(); + auto origin_table = m_parent->get_table(); _impl::TableFriend::remove_recursive(*origin_table, state); // Throws } } @@ -308,7 +238,7 @@ void Lst::do_set(size_t ndx, ObjLink target_link) template <> void Lst::do_insert(size_t ndx, ObjLink target_link) { - m_obj.set_backlink(m_col_key, target_link); + m_parent->set_backlink(m_col_key, target_link); m_tree->insert(ndx, target_link); } @@ -318,12 +248,12 @@ void Lst::do_remove(size_t ndx) ObjLink old_link = get(ndx); CascadeState state(old_link.get_obj_key().is_unresolved() ? CascadeState::Mode::All : CascadeState::Mode::Strong); - bool recurse = m_obj.remove_backlink(m_col_key, old_link, state); + bool recurse = m_parent->remove_backlink(m_col_key, old_link, state); m_tree->erase(ndx); if (recurse) { - auto table = m_obj.get_table(); + auto table = m_parent->get_table(); _impl::TableFriend::remove_recursive(*table, state); // Throws } } @@ -340,16 +270,16 @@ void Lst::do_set(size_t ndx, Mixed value) } if (value.is_type(type_TypedLink)) { target_link = value.get(); - m_obj.get_table()->get_parent_group()->validate(target_link); + m_parent->get_table()->get_parent_group()->validate(target_link); } CascadeState state(old_link.get_obj_key().is_unresolved() ? CascadeState::Mode::All : CascadeState::Mode::Strong); - bool recurse = m_obj.replace_backlink(m_col_key, old_link, target_link, state); + bool recurse = m_parent->replace_backlink(m_col_key, old_link, target_link, state); m_tree->set(ndx, value); if (recurse) { - auto origin_table = m_obj.get_table(); + auto origin_table = m_parent->get_table(); _impl::TableFriend::remove_recursive(*origin_table, state); // Throws } } @@ -358,7 +288,7 @@ template <> void Lst::do_insert(size_t ndx, Mixed value) { if (value.is_type(type_TypedLink)) { - m_obj.set_backlink(m_col_key, value.get()); + m_parent->set_backlink(m_col_key, value.get()); } m_tree->insert(ndx, value); } @@ -371,12 +301,12 @@ void Lst::do_remove(size_t ndx) CascadeState state(old_link.get_obj_key().is_unresolved() ? CascadeState::Mode::All : CascadeState::Mode::Strong); - bool recurse = m_obj.remove_backlink(m_col_key, old_link, state); + bool recurse = m_parent->remove_backlink(m_col_key, old_link, state); m_tree->erase(ndx); if (recurse) { - auto table = m_obj.get_table(); + auto table = m_parent->get_table(); _impl::TableFriend::remove_recursive(*table, state); // Throws } } diff --git a/src/realm/list.hpp b/src/realm/list.hpp index 2613161298e..f0c2c317117 100644 --- a/src/realm/list.hpp +++ b/src/realm/list.hpp @@ -79,7 +79,20 @@ class Lst final : public CollectionBaseImpl> { using value_type = T; Lst() = default; - Lst(const Obj& owner, ColKey col_key); + Lst(const Obj& owner, ColKey col_key) + : Lst(col_key) + { + this->set_owner(owner, col_key); + } + Lst(ColKey col_key) + : Base(col_key) + { + if (!col_key.is_list()) { + throw InvalidArgument(ErrorCodes::TypeMismatch, "Property not a list"); + } + + check_column_type(m_col_key); + } Lst(const Lst& other); Lst(Lst&&) noexcept; Lst& operator=(const Lst& other); @@ -240,14 +253,15 @@ class Lst final : public CollectionBaseImpl> { mutable std::unique_ptr> m_tree; using Base::bump_content_version; + using Base::m_alloc; using Base::m_col_key; using Base::m_nullable; - using Base::m_obj; + using Base::m_parent; bool init_from_parent(bool allow_create) const { if (!m_tree) { - m_tree.reset(new BPlusTree(m_obj.get_alloc())); + m_tree.reset(new BPlusTree(*m_alloc)); const ArrayParent* parent = this; m_tree->set_parent(const_cast(parent), 0); } @@ -356,6 +370,10 @@ class LnkLst final : public ObjCollectionBase { : m_list(owner, col_key) { } + LnkLst(ColKey col_key) + : m_list(col_key) + { + } LnkLst(const LnkLst& other) = default; LnkLst(LnkLst&& other) = default; @@ -399,24 +417,14 @@ class LnkLst final : public ObjCollectionBase { ColKey get_col_key() const noexcept final; // Overriding members of LstBase: - std::unique_ptr clone() const + std::unique_ptr clone() const override { - if (get_obj().is_valid()) { - return std::make_unique(get_obj(), get_col_key()); - } - else { - return std::make_unique(); - } + return clone_linklist(); } // Overriding members of ObjList: - LinkCollectionPtr clone_obj_list() const + LinkCollectionPtr clone_obj_list() const override { - if (get_obj().is_valid()) { - return std::make_unique(get_obj(), get_col_key()); - } - else { - return std::make_unique(); - } + return clone_linklist(); } void set_null(size_t ndx) final; void set_any(size_t ndx, Mixed val) final; @@ -491,6 +499,16 @@ class LnkLst final : public ObjCollectionBase { return m_list.get_tree(); } + void set_owner(const Obj& obj, CollectionParent::Index index) override + { + m_list.set_owner(obj, index); + } + + void set_owner(std::shared_ptr parent, CollectionParent::Index index) override + { + m_list.set_owner(std::move(parent), index); + } + private: friend class TableView; friend class Query; @@ -522,17 +540,6 @@ inline void LstBase::swap_repl(Replication* repl, size_t ndx1, size_t ndx2) cons repl->list_move(*this, ndx1 + 1, ndx2); } -template -inline Lst::Lst(const Obj& obj, ColKey col_key) - : Base(obj, col_key) -{ - if (!col_key.is_list()) { - throw InvalidArgument(ErrorCodes::TypeMismatch, "Property not a list"); - } - - check_column_type(m_col_key); -} - template inline Lst::Lst(const Lst& other) : Base(static_cast(other)) @@ -661,7 +668,7 @@ template void Lst::clear() { if (size() > 0) { - if (Replication* repl = this->m_obj.get_replication()) { + if (Replication* repl = this->m_parent->get_replication()) { repl->list_clear(*this); } do_clear(); @@ -672,7 +679,7 @@ void Lst::clear() template inline CollectionBasePtr Lst::clone_collection() const { - return std::make_unique>(m_obj, m_col_key); + return std::make_unique>(*this); } template @@ -749,7 +756,7 @@ inline util::Optional Lst::avg(size_t* return_cnt) const template inline LstBasePtr Lst::clone() const { - return std::make_unique>(m_obj, m_col_key); + return std::make_unique>(*this); } template @@ -821,7 +828,7 @@ void Lst::resize(size_t new_size) insert_null(current_size++); } remove(new_size, current_size); - m_obj.bump_both_versions(); + m_parent->bump_both_versions(); } template @@ -840,7 +847,7 @@ void Lst::move(size_t from, size_t to) CollectionBase::validate_index("move()", to, sz); if (from != to) { - if (Replication* repl = this->m_obj.get_replication()) { + if (Replication* repl = this->m_parent->get_replication()) { repl->list_move(*this, from, to); } if (to > from) { @@ -869,7 +876,7 @@ void Lst::swap(size_t ndx1, size_t ndx2) CollectionBase::validate_index("swap()", ndx2, sz); if (ndx1 != ndx2) { - if (Replication* repl = this->m_obj.get_replication()) { + if (Replication* repl = this->m_parent->get_replication()) { LstBase::swap_repl(repl, ndx1, ndx2); } m_tree->swap(ndx1, ndx2); @@ -886,7 +893,7 @@ T Lst::set(size_t ndx, T value) // get will check for ndx out of bounds T old = do_get(ndx, "set()"); - if (Replication* repl = this->m_obj.get_replication()) { + if (Replication* repl = this->m_parent->get_replication()) { repl->list_set(*this, ndx, value); } if constexpr (std::is_same_v) { @@ -916,7 +923,7 @@ void Lst::insert(size_t ndx, T value) ensure_created(); - if (Replication* repl = this->m_obj.get_replication()) { + if (Replication* repl = this->m_parent->get_replication()) { repl->list_insert(*this, ndx, value, sz); } do_insert(ndx, value); @@ -928,7 +935,7 @@ T Lst::remove(size_t ndx) { // get will check for ndx out of bounds T old = do_get(ndx, "remove()"); - if (Replication* repl = this->m_obj.get_replication()) { + if (Replication* repl = this->m_parent->get_replication()) { repl->list_erase(*this, ndx); } diff --git a/src/realm/obj.cpp b/src/realm/obj.cpp index e9b73319464..c33f8c22ecc 100644 --- a/src/realm/obj.cpp +++ b/src/realm/obj.cpp @@ -31,19 +31,20 @@ #include "realm/array_typed_link.hpp" #include "realm/cluster_tree.hpp" #include "realm/column_type_traits.hpp" +#include "realm/list.hpp" +#include "realm/set.hpp" #include "realm/dictionary.hpp" #include "realm/link_translator.hpp" #include "realm/index_string.hpp" #include "realm/object_converter.hpp" #include "realm/replication.hpp" -#include "realm/set.hpp" #include "realm/spec.hpp" #include "realm/table_view.hpp" #include "realm/util/base64.hpp" namespace realm { -/********************************* Obj **********************************/ +/*********************************** Obj *************************************/ Obj::Obj(TableRef table, MemRef mem, ObjKey key, size_t row_ndx) : m_table(table) @@ -1914,76 +1915,6 @@ void Obj::nullify_link(ColKey origin_col_key, ObjLink target_link) && get_alloc().bump_content_version(); } -void Obj::set_backlink(ColKey col_key, ObjLink new_link) const -{ - if (new_link && new_link.get_obj_key()) { - auto target_table = m_table->get_parent_group()->get_table(new_link.get_table_key()); - ColKey backlink_col_key; - auto type = col_key.get_type(); - if (type == col_type_TypedLink || type == col_type_Mixed || col_key.is_dictionary()) { - // This may modify the target table - backlink_col_key = target_table->find_or_add_backlink_column(col_key, get_table_key()); - // it is possible that this was a link to the same table and that adding a backlink column has - // caused the need to update this object as well. - update_if_needed(); - } - else { - backlink_col_key = m_table->get_opposite_column(col_key); - } - auto obj_key = new_link.get_obj_key(); - auto target_obj = obj_key.is_unresolved() ? target_table->try_get_tombstone(obj_key) - : target_table->try_get_object(obj_key); - if (!target_obj) { - throw InvalidArgument(ErrorCodes::KeyNotFound, "Target object not found"); - } - target_obj.add_backlink(backlink_col_key, m_key); - } -} - -bool Obj::replace_backlink(ColKey col_key, ObjLink old_link, ObjLink new_link, CascadeState& state) const -{ - bool recurse = remove_backlink(col_key, old_link, state); - set_backlink(col_key, new_link); - - return recurse; -} - -bool Obj::remove_backlink(ColKey col_key, ObjLink old_link, CascadeState& state) const -{ - if (old_link && old_link.get_obj_key()) { - REALM_ASSERT(m_table->valid_column(col_key)); - ObjKey old_key = old_link.get_obj_key(); - auto target_obj = m_table->get_parent_group()->get_object(old_link); - TableRef target_table = target_obj.get_table(); - ColKey backlink_col_key; - auto type = col_key.get_type(); - if (type == col_type_TypedLink || type == col_type_Mixed || col_key.is_dictionary()) { - backlink_col_key = target_table->find_or_add_backlink_column(col_key, get_table_key()); - } - else { - backlink_col_key = m_table->get_opposite_column(col_key); - } - - bool strong_links = target_table->is_embedded(); - bool is_unres = old_key.is_unresolved(); - - bool last_removed = target_obj.remove_one_backlink(backlink_col_key, m_key); // Throws - if (is_unres) { - if (last_removed) { - // Check is there are more backlinks - if (!target_obj.has_backlinks(false)) { - // Tombstones can be erased right away - there is no cascading effect - target_table->m_tombstones->erase(old_key, state); - } - } - } - else { - return state.enqueue_for_cascade(target_obj, strong_links, last_removed); - } - } - - return false; -} struct EmbeddedObjectLinkMigrator : public LinkTranslator { EmbeddedObjectLinkMigrator(Obj origin, ColKey origin_col, Obj dest_orig, Obj dest_replace) @@ -2081,6 +2012,20 @@ void Obj::handle_multiple_backlinks_during_schema_migration() m_table->for_each_backlink_column(copy_links); } +LstBasePtr Obj::get_listbase_ptr(ColKey col_key) const +{ + auto list = CollectionParent::get_listbase_ptr(col_key); + list->set_owner(*this, col_key); + return list; +} + +SetBasePtr Obj::get_setbase_ptr(ColKey col_key) const +{ + auto set = CollectionParent::get_setbase_ptr(col_key); + set->set_owner(*this, col_key); + return set; +} + Dictionary Obj::get_dictionary(ColKey col_key) const { REALM_ASSERT(col_key.is_dictionary()); @@ -2090,7 +2035,9 @@ Dictionary Obj::get_dictionary(ColKey col_key) const DictionaryPtr Obj::get_dictionary_ptr(ColKey col_key) const { - return std::make_unique(Obj(*this), col_key); + auto dict = CollectionParent::get_dictionary_ptr(col_key); + dict->set_owner(*this, col_key); + return dict; } Dictionary Obj::get_dictionary(StringData col_name) const @@ -2380,4 +2327,14 @@ ref_type Obj::Internal::get_ref(const Obj& obj, ColKey col_key) return to_ref(obj._get(col_key.get_index())); } +ref_type Obj::get_collection_ref(Index index) const noexcept +{ + return _get(mpark::get(index).get_index()); +} + +void Obj::set_collection_ref(Index index, ref_type ref) +{ + set_int(mpark::get(index), ref); +} + } // namespace realm diff --git a/src/realm/obj.hpp b/src/realm/obj.hpp index c17496883a7..40892fd65c5 100644 --- a/src/realm/obj.hpp +++ b/src/realm/obj.hpp @@ -20,8 +20,7 @@ #define REALM_OBJ_HPP #include -#include -#include +#include #include #include @@ -30,12 +29,8 @@ namespace realm { class ClusterTree; -class Replication; class TableView; -class CollectionBase; class CascadeState; -class LstBase; -class SetBase; class ObjList; struct GlobalKey; @@ -45,11 +40,9 @@ template class Set; template using LstPtr = std::unique_ptr>; -using LstBasePtr = std::unique_ptr; template using SetPtr = std::unique_ptr>; -using SetBasePtr = std::unique_ptr; -using CollectionBasePtr = std::unique_ptr; + using LinkCollectionPtr = std::unique_ptr; class LnkLst; @@ -57,11 +50,8 @@ using LnkLstPtr = std::unique_ptr; class LnkSet; using LnkSetPtr = std::unique_ptr; -template -class Set; -class Dictionary; +class CollectionList; class DictionaryLinkValues; -using DictionaryPtr = std::unique_ptr; namespace _impl { class DeepChangeChecker; @@ -73,26 +63,8 @@ enum JSONOutputMode { output_mode_xjson_plus, // extended json as described in the spec with additional modifier used for sync }; -/// The status of an accessor after a call to `update_if_needed()`. -enum class UpdateStatus { - /// The owning object or column no longer exist, and the accessor could - /// not be updated. The accessor should be left in a detached state - /// after this, and further calls to `update_if_needed()` are not - /// guaranteed to reattach the accessor. - Detached, - - /// The underlying data of the accessor was changed since the last call - /// to `update_if_needed()`. The accessor is still valid. - Updated, - - /// The underlying data of the accessor did not change since the last - /// call to `update_if_needed()`, and the accessor is still valid in its - /// current state. - NoChange, -}; - // 'Object' would have been a better name, but it clashes with a class in ObjectStore -class Obj { +class Obj : public CollectionParent { public: constexpr Obj() : m_table(nullptr) @@ -103,24 +75,29 @@ class Obj { } Obj(TableRef table, MemRef mem, ObjKey key, size_t row_ndx); - TableRef get_table() const noexcept + // Overriding members of CollectionParent: + size_t get_level() const noexcept final + { + return 0; + } + Replication* get_replication() const override; + UpdateStatus update_if_needed_with_status() const override; + bool update_if_needed() const override; + int_fast64_t bump_content_version() override; + void bump_both_versions() override; + TableRef get_table() const noexcept final { return m_table.cast_away_const(); } - - Allocator& get_alloc() const; - - bool operator==(const Obj& other) const; - - ObjKey get_key() const noexcept + const Obj& get_object() const noexcept final { - return m_key; + return *this; } + ref_type get_collection_ref(Index index) const noexcept override; + void set_collection_ref(Index index, ref_type ref) override; - GlobalKey get_object_id() const; - ObjLink get_link() const; - - Replication* get_replication() const; + // Operator overloads + bool operator==(const Obj& other) const; // Check if this object is default constructed explicit operator bool() const noexcept @@ -128,8 +105,18 @@ class Obj { return m_table != nullptr; } + // Simple getters + Allocator& get_alloc() const; + ObjKey get_key() const noexcept + { + return m_key; + } + GlobalKey get_object_id() const; + ObjLink get_link() const; + /// Check if the object is still alive bool is_valid() const noexcept; + /// Delete object from table. Object is invalid afterwards. void remove(); /// Invalidate @@ -351,6 +338,7 @@ class Obj { friend class Set; friend class Table; friend class Transaction; + friend class CollectionParent; mutable TableRef m_table; ObjKey m_key; @@ -365,14 +353,8 @@ class Obj { /// Update the accessor. Returns true when the accessor was updated to /// reflect new changes to the underlying state. bool update() const; - // update if needed - with and without check of table instance version: - bool update_if_needed() const; bool _update_if_needed() const; // no check, use only when already checked - /// Update the accessor (and return `UpdateStatus::Detached` if the Obj is - /// no longer valid, rather than throwing an exception). - UpdateStatus update_if_needed_with_status() const; - template bool do_is_null(ColKey::Idx col_ndx) const; @@ -401,8 +383,6 @@ class Obj { size_t colkey2spec_ndx(ColKey); bool ensure_writeable(); void sync(Node& arr); - int_fast64_t bump_content_version(); - void bump_both_versions(); template void do_set_null(ColKey col_key); @@ -422,12 +402,6 @@ class Obj { void add_backlink(ColKey backlink_col, ObjKey origin_key); bool remove_one_backlink(ColKey backlink_col, ObjKey origin_key); void nullify_link(ColKey origin_col, ObjLink target_key) &&; - // Used when inserting a new link. You will not remove existing links in this process - void set_backlink(ColKey col_key, ObjLink new_link) const; - // Used when replacing a link, return true if CascadeState contains objects to remove - bool replace_backlink(ColKey col_key, ObjLink old_link, ObjLink new_link, CascadeState& state) const; - // Used when removing a backlink, return true if CascadeState contains objects to remove - bool remove_backlink(ColKey col_key, ObjLink old_link, CascadeState& state) const; template inline void set_spec(T&, ColKey); template diff --git a/src/realm/object-store/dictionary.cpp b/src/realm/object-store/dictionary.cpp index 698420be86e..1dc8ec57861 100644 --- a/src/realm/object-store/dictionary.cpp +++ b/src/realm/object-store/dictionary.cpp @@ -108,6 +108,14 @@ class DictionaryKeyAdapter : public CollectionBase { { REALM_TERMINATE("not implemented"); } + void set_owner(const Obj& obj, CollectionParent::Index index) override + { + m_dictionary->set_owner(obj, index); + } + void set_owner(std::shared_ptr parent, CollectionParent::Index index) override + { + m_dictionary->set_owner(std::move(parent), index); + } private: std::shared_ptr m_dictionary; diff --git a/src/realm/set.cpp b/src/realm/set.cpp index ac134c85ada..1ccd767a68e 100644 --- a/src/realm/set.cpp +++ b/src/realm/set.cpp @@ -32,77 +32,6 @@ namespace realm { -// FIXME: This method belongs in obj.cpp. -SetBasePtr Obj::get_setbase_ptr(ColKey col_key) const -{ - auto attr = get_table()->get_column_attr(col_key); - REALM_ASSERT(attr.test(col_attr_Set)); - bool nullable = attr.test(col_attr_Nullable); - - switch (get_table()->get_column_type(col_key)) { - case type_Int: { - if (nullable) - return std::make_unique>>(*this, col_key); - else - return std::make_unique>(*this, col_key); - } - case type_Bool: { - if (nullable) - return std::make_unique>>(*this, col_key); - else - return std::make_unique>(*this, col_key); - } - case type_Float: { - if (nullable) - return std::make_unique>>(*this, col_key); - else - return std::make_unique>(*this, col_key); - } - case type_Double: { - if (nullable) - return std::make_unique>>(*this, col_key); - else - return std::make_unique>(*this, col_key); - } - case type_String: { - return std::make_unique>(*this, col_key); - } - case type_Binary: { - return std::make_unique>(*this, col_key); - } - case type_Timestamp: { - return std::make_unique>(*this, col_key); - } - case type_Decimal: { - return std::make_unique>(*this, col_key); - } - case type_ObjectId: { - if (nullable) - return std::make_unique>>(*this, col_key); - else - return std::make_unique>(*this, col_key); - } - case type_UUID: { - if (nullable) - return std::make_unique>>(*this, col_key); - else - return std::make_unique>(*this, col_key); - } - case type_TypedLink: { - return std::make_unique>(*this, col_key); - } - case type_Mixed: { - return std::make_unique>(*this, col_key); - } - case type_Link: { - return std::make_unique(*this, col_key); - } - case type_LinkList: - break; - } - REALM_TERMINATE("Unsupported column type."); -} - void SetBase::insert_repl(Replication* repl, size_t index, Mixed value) const { repl->set_insert(*this, index, value); @@ -133,9 +62,9 @@ std::vector SetBase::convert_to_mixed_set(const CollectionBase& rhs) template <> void Set::do_insert(size_t ndx, ObjKey target_key) { - auto origin_table = m_obj.get_table(); + auto origin_table = m_parent->get_table(); auto target_table_key = origin_table->get_opposite_table_key(m_col_key); - m_obj.set_backlink(m_col_key, {target_table_key, target_key}); + m_parent->set_backlink(m_col_key, {target_table_key, target_key}); m_tree->insert(ndx, target_key); if (target_key.is_unresolved()) { m_tree->set_context_flag(true); @@ -145,12 +74,12 @@ void Set::do_insert(size_t ndx, ObjKey target_key) template <> void Set::do_erase(size_t ndx) { - auto origin_table = m_obj.get_table(); + auto origin_table = m_parent->get_table(); auto target_table_key = origin_table->get_opposite_table_key(m_col_key); ObjKey old_key = get(ndx); CascadeState state(old_key.is_unresolved() ? CascadeState::Mode::All : CascadeState::Mode::Strong); - bool recurse = m_obj.remove_backlink(m_col_key, {target_table_key, old_key}, state); + bool recurse = m_parent->remove_backlink(m_col_key, {target_table_key, old_key}, state); m_tree->erase(ndx); @@ -180,7 +109,7 @@ void Set::do_clear() template <> void Set::do_insert(size_t ndx, ObjLink target_link) { - m_obj.set_backlink(m_col_key, target_link); + m_parent->set_backlink(m_col_key, target_link); m_tree->insert(ndx, target_link); } @@ -190,12 +119,12 @@ void Set::do_erase(size_t ndx) ObjLink old_link = get(ndx); CascadeState state(old_link.get_obj_key().is_unresolved() ? CascadeState::Mode::All : CascadeState::Mode::Strong); - bool recurse = m_obj.remove_backlink(m_col_key, old_link, state); + bool recurse = m_parent->remove_backlink(m_col_key, old_link, state); m_tree->erase(ndx); if (recurse) { - auto table = m_obj.get_table(); + auto table = m_parent->get_table(); _impl::TableFriend::remove_recursive(*table, state); // Throws } } @@ -205,8 +134,8 @@ void Set::do_insert(size_t ndx, Mixed value) { if (value.is_type(type_TypedLink)) { auto target_link = value.get(); - m_obj.get_table()->get_parent_group()->validate(target_link); - m_obj.set_backlink(m_col_key, target_link); + m_parent->get_table()->get_parent_group()->validate(target_link); + m_parent->set_backlink(m_col_key, target_link); } m_tree->insert(ndx, value); } @@ -219,12 +148,12 @@ void Set::do_erase(size_t ndx) CascadeState state(old_link.get_obj_key().is_unresolved() ? CascadeState::Mode::All : CascadeState::Mode::Strong); - bool recurse = m_obj.remove_backlink(m_col_key, old_link, state); + bool recurse = m_parent->remove_backlink(m_col_key, old_link, state); m_tree->erase(ndx); if (recurse) { - auto table = m_obj.get_table(); + auto table = m_parent->get_table(); _impl::TableFriend::remove_recursive(*table, state); // Throws } } diff --git a/src/realm/set.hpp b/src/realm/set.hpp index fb8bd317618..729b671f0c5 100644 --- a/src/realm/set.hpp +++ b/src/realm/set.hpp @@ -53,7 +53,21 @@ class Set final : public CollectionBaseImpl> { using iterator = CollectionIterator>; Set() = default; - Set(const Obj& owner, ColKey col_key); + Set(const Obj& owner, ColKey col_key) + : Set(col_key) + { + this->set_owner(owner, col_key); + } + + Set(ColKey col_key) + : Base(col_key) + { + if (!col_key.is_set()) { + throw InvalidArgument(ErrorCodes::TypeMismatch, "Property not a set"); + } + + check_column_type(m_col_key); + } Set(const Set& other); Set(Set&& other) noexcept; Set& operator=(const Set& other); @@ -208,14 +222,15 @@ class Set final : public CollectionBaseImpl> { mutable std::unique_ptr> m_tree; using Base::bump_content_version; + using Base::m_alloc; using Base::m_col_key; using Base::m_nullable; - using Base::m_obj; + using Base::m_parent; bool init_from_parent(bool allow_create) const { if (!m_tree) { - m_tree.reset(new BPlusTree(m_obj.get_alloc())); + m_tree.reset(new BPlusTree(*m_alloc)); const ArrayParent* parent = this; m_tree->set_parent(const_cast(parent), 0); } @@ -284,6 +299,11 @@ class LnkSet final : public ObjCollectionBase { : m_set(owner, col_key) { } + LnkSet(ColKey col_key) + : m_set(col_key) + { + } + LnkSet(const LnkSet&) = default; LnkSet(LnkSet&&) = default; @@ -311,7 +331,7 @@ class LnkSet final : public ObjCollectionBase { // Overriding members of CollectionBase: using CollectionBase::get_owner_key; - CollectionBasePtr clone_collection() const + CollectionBasePtr clone_collection() const override { return clone_linkset(); } @@ -331,7 +351,7 @@ class LnkSet final : public ObjCollectionBase { ColKey get_col_key() const noexcept final; // Overriding members of SetBase: - SetBasePtr clone() const + SetBasePtr clone() const override { return clone_linkset(); } @@ -386,6 +406,16 @@ class LnkSet final : public ObjCollectionBase { return iterator{this, size()}; } + void set_owner(const Obj& obj, CollectionParent::Index index) override + { + m_set.set_owner(obj, index); + } + + void set_owner(std::shared_ptr parent, CollectionParent::Index index) override + { + m_set.set_owner(std::move(parent), index); + } + private: Set m_set; @@ -507,17 +537,6 @@ struct SetElementEquals { } }; -template -inline Set::Set(const Obj& obj, ColKey col_key) - : Base(obj, col_key) -{ - if (!col_key.is_set()) { - throw InvalidArgument(ErrorCodes::TypeMismatch, "Property not a set"); - } - - check_column_type(m_col_key); -} - template inline Set::Set(const Set& other) : Base(static_cast(other)) @@ -649,7 +668,7 @@ std::pair Set::insert(T value) return {it.index(), false}; } - if (Replication* repl = m_obj.get_replication()) { + if (Replication* repl = m_parent->get_replication()) { // FIXME: We should emit an instruction regardless of element presence for the purposes of conflict // resolution in synchronized databases. The reason is that the new insertion may come at a later time // than an interleaving erase instruction, so emitting the instruction ensures that last "write" wins. @@ -686,7 +705,7 @@ std::pair Set::erase(T value) return {npos, false}; } - if (Replication* repl = m_obj.get_replication()) { + if (Replication* repl = m_parent->get_replication()) { this->erase_repl(repl, it.index(), value); } do_erase(it.index()); @@ -738,7 +757,7 @@ template inline void Set::clear() { if (size() > 0) { - if (Replication* repl = this->m_obj.get_replication()) { + if (Replication* repl = this->m_parent->get_replication()) { this->clear_repl(repl); } do_clear(); diff --git a/src/realm/table.hpp b/src/realm/table.hpp index a4597a5feff..be79a359d4b 100644 --- a/src/realm/table.hpp +++ b/src/realm/table.hpp @@ -860,6 +860,7 @@ class Table { friend class ClusterTree; friend class ColKeyIterator; friend class Obj; + friend class CollectionParent; friend class LnkLst; friend class Dictionary; friend class IncludeDescriptor; diff --git a/test/test_links.cpp b/test/test_links.cpp index 678dcf26210..3c4c942f878 100644 --- a/test/test_links.cpp +++ b/test/test_links.cpp @@ -743,6 +743,13 @@ TEST(ListList_Clear) if (links.size() > 1) links.set(1, key0); links.clear(); + + group->commit_and_continue_as_read(); + group->promote_to_write(); + + obj3.remove(); + auto links2 = links.clone(); + CHECK_EQUAL(links2->size(), 0); } TEST(Links_AddBacklinkToTableWithEnumColumns) diff --git a/test/test_table.cpp b/test/test_table.cpp index a5033942c24..d37a5f0ea80 100644 --- a/test/test_table.cpp +++ b/test/test_table.cpp @@ -3152,8 +3152,13 @@ TEST(Table_ListOfPrimitives) timestamp_list.max(&return_ndx); CHECK_EQUAL(return_ndx, 1); + auto timestamp_list2 = timestamp_list.clone(); + CHECK_EQUAL(timestamp_list2->size(), timestamp_list.size()); + t->remove_object(ObjKey(7)); + auto timestamp_list3 = timestamp_list.clone(); CHECK_NOT(timestamp_list.is_attached()); + CHECK_EQUAL(timestamp_list3->size(), 0); } TEST_TYPES(Table_ListOfPrimitivesSort, Prop, Prop, Prop, Prop, Prop,