diff --git a/release_notes.md b/release_notes.md index 8d61ab1dd25..8e50b538969 100644 --- a/release_notes.md +++ b/release_notes.md @@ -6,11 +6,20 @@ ### API breaking changes: -* Lorem ipsum. +* Table::erase() can no longer be used with unordered tables. Previously it was + allowed if the specified index was the index of the last row in the table. One + must now always use Table::move_last_over() with unordered tables. Whether a + table is ordered or unordered is entirely decided by the way it is used by the + application, and note that only unordered tables are allowed to contain link + columns. ### Enhancements: -* Lorem ipsum. +* Support added for cascading row removal. See `Descriptor::set_link_type()` for + details. All previsouly created link columns will effectively have link-type + 'weak'. +* Rows can now be removed via a row accessors (`Row::remove()`, + `Row::move_last_over()`). ----------- diff --git a/src/tightdb/column.cpp b/src/tightdb/column.cpp index eedb209edc6..292c5fe9326 100644 --- a/src/tightdb/column.cpp +++ b/src/tightdb/column.cpp @@ -31,6 +31,18 @@ void ColumnBase::update_from_parent(size_t old_baseline) TIGHTDB_NOEXCEPT } +void ColumnBase::cascade_break_backlinks_to(size_t, CascadeState&) +{ + // No-op by default +} + + +void ColumnBase::cascade_break_backlinks_to_all_rows(size_t, CascadeState&) +{ + // No-op by default +} + + #ifdef TIGHTDB_DEBUG void ColumnBase::Verify(const Table&, size_t) const @@ -483,17 +495,6 @@ Column::~Column() TIGHTDB_NOEXCEPT delete m_array; } -void Column::clear() -{ - if (m_search_index) { - static_cast(m_search_index)->clear(); - } - - m_array->clear_and_destroy_children(); - if (m_array->is_inner_bptree_node()) - m_array->set_type(Array::type_Normal); -} - void Column::move_assign(Column& col) { @@ -640,74 +641,6 @@ void Column::ReferenceSort(size_t start, size_t end, Column& ref) */ -class Column::EraseLeafElem: public EraseHandlerBase { -public: - Array m_leaf; - bool m_leaves_have_refs; - EraseLeafElem(Column& column) TIGHTDB_NOEXCEPT: - EraseHandlerBase(column), m_leaf(get_alloc()), - m_leaves_have_refs(false) {} - bool erase_leaf_elem(MemRef leaf_mem, ArrayParent* parent, - size_t leaf_ndx_in_parent, - size_t elem_ndx_in_leaf) TIGHTDB_OVERRIDE - { - m_leaf.init_from_mem(leaf_mem); - TIGHTDB_ASSERT(m_leaf.size() >= 1); - size_t last_ndx = m_leaf.size() - 1; - if (last_ndx == 0) { - m_leaves_have_refs = m_leaf.has_refs(); - return true; - } - m_leaf.set_parent(parent, leaf_ndx_in_parent); - size_t ndx = elem_ndx_in_leaf; - if (ndx == npos) - ndx = last_ndx; - m_leaf.erase(ndx); // Throws - return false; - } - void destroy_leaf(MemRef leaf_mem) TIGHTDB_NOEXCEPT TIGHTDB_OVERRIDE - { - // FIXME: Seems like this would cause file space leaks if - // m_leaves_have_refs is true, but consider carefully how - // m_leaves_have_refs get its value. - get_alloc().free_(leaf_mem); - } - void replace_root_by_leaf(MemRef leaf_mem) TIGHTDB_OVERRIDE - { - Array* leaf = new Array(get_alloc()); // Throws - leaf->init_from_mem(leaf_mem); - replace_root(leaf); // Throws, but callee takes ownership of accessor - } - void replace_root_by_empty_leaf() TIGHTDB_OVERRIDE - { - UniquePtr leaf(new Array(get_alloc())); // Throws - leaf->create(m_leaves_have_refs ? Array::type_HasRefs : - Array::type_Normal); // Throws - replace_root(leaf.release()); // Throws, but callee takes ownership of accessor - } -}; - - -void Column::erase(size_t ndx, bool is_last) -{ - TIGHTDB_ASSERT(ndx < size()); - TIGHTDB_ASSERT(is_last == (ndx == size()-1)); - - if (m_search_index) { - static_cast(m_search_index)->erase(ndx, is_last); - } - - if (!m_array->is_inner_bptree_node()) { - m_array->erase(ndx); // Throws - return; - } - - size_t ndx_2 = is_last ? npos : ndx; - EraseLeafElem handler(*this); - Array::erase_bptree_elem(m_array, ndx_2, handler); // Throws -} - - void Column::destroy_subtree(size_t ndx, bool clear_value) { int_fast64_t value = get(ndx); @@ -732,29 +665,6 @@ void Column::destroy_subtree(size_t ndx, bool clear_value) } -void Column::move_last_over(size_t target_row_ndx, size_t last_row_ndx) -{ - TIGHTDB_ASSERT(target_row_ndx < last_row_ndx); - TIGHTDB_ASSERT(last_row_ndx + 1 == size()); - - if (m_search_index) { - // remove the value to be overwritten from index - bool is_last = true; // This tells StringIndex::erase() to not adjust subsequent indexes - static_cast(m_search_index)->erase(target_row_ndx, is_last); // Throws - - // update index to point to new location - int64_t moved_value = get(last_row_ndx); - static_cast(m_search_index)->update_ref(moved_value, last_row_ndx, target_row_ndx); // Throws - } - - int_fast64_t value = get(last_row_ndx); - Column::set(target_row_ndx, value); // Throws - - bool is_last = true; - Column::erase(last_row_ndx, is_last); // Throws -} - - namespace { template struct AdjustHandler: Array::UpdateHandler { @@ -936,7 +846,7 @@ bool Column::compare_int(const Column& c) const TIGHTDB_NOEXCEPT void Column::do_insert(size_t row_ndx, int_fast64_t value, size_t num_rows) { TIGHTDB_ASSERT(row_ndx == tightdb::npos || row_ndx < size()); - + ref_type new_sibling_ref; Array::TreeInsert state; for (size_t i = 0; i != num_rows; ++i) { @@ -970,6 +880,123 @@ void Column::do_insert(size_t row_ndx, int_fast64_t value, size_t num_rows) } +class Column::EraseLeafElem: public EraseHandlerBase { +public: + Array m_leaf; + bool m_leaves_have_refs; + EraseLeafElem(Column& column) TIGHTDB_NOEXCEPT: + EraseHandlerBase(column), m_leaf(get_alloc()), + m_leaves_have_refs(false) {} + bool erase_leaf_elem(MemRef leaf_mem, ArrayParent* parent, + size_t leaf_ndx_in_parent, + size_t elem_ndx_in_leaf) TIGHTDB_OVERRIDE + { + m_leaf.init_from_mem(leaf_mem); + TIGHTDB_ASSERT(m_leaf.size() >= 1); + size_t last_ndx = m_leaf.size() - 1; + if (last_ndx == 0) { + m_leaves_have_refs = m_leaf.has_refs(); + return true; + } + m_leaf.set_parent(parent, leaf_ndx_in_parent); + size_t ndx = elem_ndx_in_leaf; + if (ndx == npos) + ndx = last_ndx; + m_leaf.erase(ndx); // Throws + return false; + } + void destroy_leaf(MemRef leaf_mem) TIGHTDB_NOEXCEPT TIGHTDB_OVERRIDE + { + // FIXME: Seems like this would cause file space leaks if + // m_leaves_have_refs is true, but consider carefully how + // m_leaves_have_refs get its value. + get_alloc().free_(leaf_mem); + } + void replace_root_by_leaf(MemRef leaf_mem) TIGHTDB_OVERRIDE + { + Array* leaf = new Array(get_alloc()); // Throws + leaf->init_from_mem(leaf_mem); + replace_root(leaf); // Throws, but callee takes ownership of accessor + } + void replace_root_by_empty_leaf() TIGHTDB_OVERRIDE + { + UniquePtr leaf(new Array(get_alloc())); // Throws + leaf->create(m_leaves_have_refs ? Array::type_HasRefs : + Array::type_Normal); // Throws + replace_root(leaf.release()); // Throws, but callee takes ownership of accessor + } +}; + + +void Column::do_erase(size_t ndx, bool is_last) +{ + TIGHTDB_ASSERT(ndx < size()); + TIGHTDB_ASSERT(is_last == (ndx == size()-1)); + + if (m_search_index) + static_cast(m_search_index)->erase(ndx, is_last); + + if (!m_array->is_inner_bptree_node()) { + m_array->erase(ndx); // Throws + return; + } + + size_t ndx_2 = is_last ? npos : ndx; + EraseLeafElem handler(*this); + Array::erase_bptree_elem(m_array, ndx_2, handler); // Throws +} + + +void Column::do_move_last_over(size_t row_ndx, size_t last_row_ndx) +{ + TIGHTDB_ASSERT(row_ndx <= last_row_ndx); + TIGHTDB_ASSERT(last_row_ndx + 1 == size()); + + if (m_search_index) { + // remove the value to be overwritten from index + bool is_last = true; // This tells StringIndex::erase() to not adjust subsequent indexes + static_cast(m_search_index)->erase(row_ndx, is_last); // Throws + + // update index to point to new location + if (row_ndx != last_row_ndx) { + int_fast64_t moved_value = get(last_row_ndx); + static_cast(m_search_index)->update_ref(moved_value, last_row_ndx, row_ndx); // Throws + } + } + + // Copy value from last row over + int_fast64_t value = get(last_row_ndx); + if (m_array->is_inner_bptree_node()) { + SetLeafElem set_leaf_elem(m_array->get_alloc(), value); + m_array->update_bptree_elem(row_ndx, set_leaf_elem); // Throws + } + else { + m_array->set(row_ndx, value); // Throws + } + + // Discard last row + if (m_array->is_inner_bptree_node()) { + size_t row_ndx_2 = tightdb::npos; + EraseLeafElem handler(*this); + Array::erase_bptree_elem(m_array, row_ndx_2, handler); // Throws + } + else { + m_array->erase(last_row_ndx); // Throws + } +} + + +void Column::do_clear() +{ + if (m_search_index) + static_cast(m_search_index)->clear(); + + m_array->clear_and_destroy_children(); + if (m_array->is_inner_bptree_node()) + m_array->set_type(Array::type_Normal); +} + + class Column::CreateHandler: public ColumnBase::CreateHandler { public: CreateHandler(Array::Type leaf_type, int_fast64_t value, Allocator& alloc): diff --git a/src/tightdb/column.hpp b/src/tightdb/column.hpp index 10165ab6aaf..e26fd71d304 100644 --- a/src/tightdb/column.hpp +++ b/src/tightdb/column.hpp @@ -22,10 +22,12 @@ #include // unint8_t etc #include // std::size_t +#include #include #include #include +#include #include #include #include @@ -71,20 +73,30 @@ class ColumnBase { /// `row_ndx` is equal to the size of the column (before insertion). virtual void insert(std::size_t row_ndx, std::size_t num_rows, bool is_append) = 0; - /// Remove all entries from this column. - virtual void clear() = 0; + /// Remove all elements from this column. + /// + /// \param num_rows The total number of rows in this column. + /// + /// \param broken_reciprocal_backlinks If true, link columns must assume + /// that reciprocal backlinks have already been removed. Non-link columns, + /// and backlink columns should ignore this argument. + virtual void clear(std::size_t num_rows, bool broken_reciprocal_backlinks) = 0; /// Remove the specified entry from this column. Set \a is_last to /// true when deleting the last element. This is important to /// avoid conversion to to general form of inner nodes of the /// B+-tree. - virtual void erase(std::size_t ndx, bool is_last) = 0; + virtual void erase(std::size_t row_ndx, bool is_last) = 0; - /// Move the last element to the specified target index. This reduces the + /// Remove the specified row by moving the last row over it. This reduces the /// number of elements by one. The specified last row index must always be - /// one less than the number of rows in the column. The target index must - /// always be strictly less that the last index. - virtual void move_last_over(std::size_t target_row_ndx, std::size_t last_row_ndx) = 0; + /// one less than the number of rows in the column. + /// + /// \param broken_reciprocal_backlinks If true, link columns must assume + /// that reciprocal backlinks have already been removed for the specified + /// row. Non-link columns, and backlink columns should ignore this argument. + virtual void move_last_over(std::size_t row_ndx, std::size_t last_row_ndx, + bool broken_reciprocal_backlinks) = 0; virtual bool IsIntColumn() const TIGHTDB_NOEXCEPT { return false; } @@ -159,6 +171,23 @@ class ColumnBase { /// table and link list accessors stay valid across a commit. virtual void update_from_parent(std::size_t old_baseline) TIGHTDB_NOEXCEPT; + //@{ + + /// cascade_break_backlinks_to() is called iteratively for each column by + /// Table::cascade_break_backlinks_to() with the same arguments as are + /// passed to Table::cascade_break_backlinks_to(). Link columns must + /// override it. The same is true for cascade_break_backlinks_to_all_rows(), + /// except that it is called from + /// Table::cascade_break_backlinks_to_all_rows(), and that it expects + /// Table::cascade_break_backlinks_to_all_rows() to pass the number of rows + /// in the table as \a num_rows. + + struct CascadeState; + virtual void cascade_break_backlinks_to(std::size_t row_ndx, CascadeState&); + virtual void cascade_break_backlinks_to_all_rows(std::size_t num_rows, CascadeState&); + + //@} + void discard_child_accessors() TIGHTDB_NOEXCEPT; /// For columns that are able to contain subtables, this function returns @@ -172,21 +201,11 @@ class ColumnBase { /// function does nothing. virtual void discard_subtable_accessor(std::size_t row_ndx) TIGHTDB_NOEXCEPT; - virtual void adj_accessors_insert_rows(std::size_t row_ndx, - std::size_t num_rows) TIGHTDB_NOEXCEPT; - virtual void adj_accessors_erase_row(std::size_t row_ndx) TIGHTDB_NOEXCEPT; - - // This function assumes that a row has moved from \a origin_ndx to \a target_ndx, - // and it updates the column accessor, and all its subordinate accessors (subtables, rows) - // accordingly. - // Subordinate accessors that are already associated with \a target_ndx will be detached. - // Link-adjacent table accessors will be marked (dirty). - // - // It is used as part of Table::refresh_accessor_tree() to bring the state of the accessors - // from Minimal Consistency into Structural Correspondence, so it must be able to execute - // without accessing the underlying array nodes. - virtual void adj_accessors_move(std::size_t target_row_ndx, - std::size_t source_row_ndx) TIGHTDB_NOEXCEPT; + virtual void adj_acc_insert_rows(std::size_t row_ndx, std::size_t num_rows) TIGHTDB_NOEXCEPT; + virtual void adj_acc_erase_row(std::size_t row_ndx) TIGHTDB_NOEXCEPT; + /// See Table::adj_acc_move_over() + virtual void adj_acc_move_over(std::size_t from_row_ndx, + std::size_t to_row_ndx) TIGHTDB_NOEXCEPT; virtual void adj_acc_clear_root_table() TIGHTDB_NOEXCEPT; enum { @@ -301,6 +320,49 @@ class ColumnBase { }; +struct ColumnBase::CascadeState { + struct row { + std::size_t table_ndx; ///< Index within group of a group-level table. + std::size_t row_ndx; + + bool operator==(const row&) const TIGHTDB_NOEXCEPT; + bool operator!=(const row&) const TIGHTDB_NOEXCEPT; + + /// Trivial lexicographic order + bool operator<(const row&) const TIGHTDB_NOEXCEPT; + }; + + typedef std::vector row_set; + + /// A sorted list of rows. The order is defined by row::operator<(), and + /// insertions must respect this order. + row_set rows; + + /// If non-null, then no recursion will be performed for rows of that + /// table. The effect is then exactly as if all the rows of that table were + /// added to \a state.rows initially, and then removed again after the + /// explicit invocations of Table::cascade_break_backlinks_to() (one for + /// each initiating row). This is used by Table::clear() to avoid + /// reentrance. + /// + /// Must never be set concurrently with stop_on_link_list_column. + Table* stop_on_table; + + /// If non-null, then Table::cascade_break_backlinks_to() will skip the + /// removal of reciprocal backlinks for the link list at + /// stop_on_link_list_row_ndx in this column, and no recursion will happen + /// on its behalf. This is used by LinkView::clear() to avoid reentrance. + /// + /// Must never be set concurrently with stop_on_table. + ColumnLinkList* stop_on_link_list_column; + + /// Is ignored if stop_on_link_list_column is null. + std::size_t stop_on_link_list_row_ndx; + + CascadeState(); +}; + + class ColumnBase::EraseHandlerBase: public Array::EraseHandler { protected: EraseHandlerBase(ColumnBase& column) TIGHTDB_NOEXCEPT: m_column(column) {} @@ -348,6 +410,9 @@ class Column: public ColumnBase, public ColumnTemplate { void adjust(std::size_t ndx, int_fast64_t diff); void add(int_fast64_t value = 0); void insert(std::size_t ndx, int_fast64_t value = 0); + void erase(std::size_t row_ndx); + void move_last_over(std::size_t row_ndx); + void clear(); std::size_t count(int64_t target) const; int64_t sum(std::size_t start = 0, std::size_t end = -1, size_t limit = size_t(-1), @@ -362,17 +427,6 @@ class Column: public ColumnBase, public ColumnTemplate { double average(std::size_t start = 0, std::size_t end = -1, size_t limit = size_t(-1), size_t* return_ndx = null_ptr) const; - /// If any element points to an array node, this function recursively - /// destroys that array node. Note that the same is **not** true for - /// Column::erase() and Column::move_last_over(). - /// - /// FIXME: Be careful, clear() currently forgets if the leaf type is - /// Array::type_HasRefs. - void clear() TIGHTDB_OVERRIDE; - - void insert(std::size_t, std::size_t, bool) TIGHTDB_OVERRIDE; - void erase(std::size_t ndx, bool is_last) TIGHTDB_OVERRIDE; - void move_last_over(std::size_t, std::size_t) TIGHTDB_OVERRIDE; void destroy_subtree(size_t ndx, bool clear_value); void adjust(int_fast64_t diff); @@ -407,11 +461,15 @@ class Column: public ColumnBase, public ColumnTemplate { ref_type write(std::size_t, std::size_t, std::size_t, _impl::OutputStream&) const TIGHTDB_OVERRIDE; + void insert(std::size_t, std::size_t, bool) TIGHTDB_OVERRIDE; + void erase(std::size_t, bool) TIGHTDB_OVERRIDE; + void move_last_over(std::size_t, std::size_t, bool) TIGHTDB_OVERRIDE; + void clear(std::size_t, bool) TIGHTDB_OVERRIDE; + void refresh_accessor_tree(std::size_t, const Spec&) TIGHTDB_OVERRIDE; + /// \param row_ndx Must be `tightdb::npos` if appending. void do_insert(std::size_t row_ndx, int_fast64_t value, std::size_t num_rows); - void refresh_accessor_tree(std::size_t, const Spec&) TIGHTDB_OVERRIDE; - #ifdef TIGHTDB_DEBUG void Verify() const TIGHTDB_OVERRIDE; using ColumnBase::Verify; @@ -428,6 +486,18 @@ class Column: public ColumnBase, public ColumnTemplate { std::size_t do_get_size() const TIGHTDB_NOEXCEPT TIGHTDB_OVERRIDE { return size(); } + void do_erase(std::size_t row_ndx, bool is_last); + + void do_move_last_over(std::size_t row_ndx, std::size_t last_row_ndx); + + /// If any element points to an array node, this function recursively + /// destroys that array node. Note that the same is **not** true for + /// Column::do_erase() and Column::do_move_last_over(). + /// + /// FIXME: Be careful, do_clear() currently forgets if the leaf type is + /// Array::type_HasRefs. + void do_clear(); + #ifdef TIGHTDB_DEBUG void leaf_to_dot(MemRef, ArrayParent*, std::size_t ndx_in_parent, std::ostream&) const TIGHTDB_OVERRIDE; @@ -499,6 +569,28 @@ inline void ColumnBase::set_search_index_allow_duplicate_values(bool) TIGHTDB_NO { } +inline bool ColumnBase::CascadeState::row::operator==(const row& r) const TIGHTDB_NOEXCEPT +{ + return table_ndx == r.table_ndx && row_ndx == r.row_ndx; +} + +inline bool ColumnBase::CascadeState::row::operator!=(const row& r) const TIGHTDB_NOEXCEPT +{ + return !(*this == r); +} + +inline bool ColumnBase::CascadeState::row::operator<(const row& r) const TIGHTDB_NOEXCEPT +{ + return table_ndx < r.table_ndx || (table_ndx == r.table_ndx && row_ndx < r.row_ndx); +} + +inline ColumnBase::CascadeState::CascadeState(): + stop_on_table(0), + stop_on_link_list_column(0), + stop_on_link_list_row_ndx(0) +{ +} + inline void ColumnBase::discard_child_accessors() TIGHTDB_NOEXCEPT { do_discard_child_accessors(); @@ -514,17 +606,17 @@ inline void ColumnBase::discard_subtable_accessor(std::size_t) TIGHTDB_NOEXCEPT // Noop } -inline void ColumnBase::adj_accessors_insert_rows(std::size_t, std::size_t) TIGHTDB_NOEXCEPT +inline void ColumnBase::adj_acc_insert_rows(std::size_t, std::size_t) TIGHTDB_NOEXCEPT { // Noop } -inline void ColumnBase::adj_accessors_erase_row(std::size_t) TIGHTDB_NOEXCEPT +inline void ColumnBase::adj_acc_erase_row(std::size_t) TIGHTDB_NOEXCEPT { // Noop } -inline void ColumnBase::adj_accessors_move(std::size_t, std::size_t) TIGHTDB_NOEXCEPT +inline void ColumnBase::adj_acc_move_over(std::size_t, std::size_t) TIGHTDB_NOEXCEPT { // Noop } @@ -712,6 +804,24 @@ inline void Column::insert(std::size_t row_ndx, int_fast64_t value) do_insert(row_ndx_2, value, num_rows); // Throws } +inline void Column::erase(std::size_t row_ndx) +{ + std::size_t last_row_ndx = size() - 1; // Note that size() is slow + bool is_last = row_ndx == last_row_ndx; + do_erase(row_ndx, is_last); // Throws +} + +inline void Column::move_last_over(std::size_t row_ndx) +{ + std::size_t last_row_ndx = size() - 1; // Note that size() is slow + do_move_last_over(row_ndx, last_row_ndx); // Throws +} + +inline void Column::clear() +{ + do_clear(); // Throws +} + // Implementing pure virtual method of ColumnBase. inline void Column::insert(std::size_t row_ndx, std::size_t num_rows, bool is_append) { @@ -720,6 +830,24 @@ inline void Column::insert(std::size_t row_ndx, std::size_t num_rows, bool is_ap do_insert(row_ndx_2, value, num_rows); // Throws } +// Implementing pure virtual method of ColumnBase. +inline void Column::erase(std::size_t row_ndx, bool is_last) +{ + do_erase(row_ndx, is_last); // Throws +} + +// Implementing pure virtual method of ColumnBase. +inline void Column::move_last_over(std::size_t row_ndx, std::size_t last_row_ndx, bool) +{ + do_move_last_over(row_ndx, last_row_ndx); // Throws +} + +// Implementing pure virtual method of ColumnBase. +inline void Column::clear(std::size_t, bool) +{ + do_clear(); // Throws +} + TIGHTDB_FORCEINLINE ref_type Column::leaf_insert(MemRef leaf_mem, ArrayParent& parent, std::size_t ndx_in_parent, Allocator& alloc, std::size_t insert_ndx, diff --git a/src/tightdb/column_backlink.cpp b/src/tightdb/column_backlink.cpp index 074da03c4b9..e9de928a830 100644 --- a/src/tightdb/column_backlink.cpp +++ b/src/tightdb/column_backlink.cpp @@ -98,7 +98,7 @@ size_t ColumnBackLink::get_backlink(size_t row_ndx, size_t backlink_ndx) const T } -void ColumnBackLink::remove_backlink(size_t row_ndx, size_t origin_row_ndx) +void ColumnBackLink::remove_one_backlink(size_t row_ndx, size_t origin_row_ndx) { int_fast64_t value = Column::get(row_ndx); TIGHTDB_ASSERT(value != 0); @@ -135,6 +135,22 @@ void ColumnBackLink::remove_backlink(size_t row_ndx, size_t origin_row_ndx) } +void ColumnBackLink::remove_all_backlinks(size_t num_rows) +{ + Allocator& alloc = m_array->get_alloc(); + for (size_t row_ndx = 0; row_ndx < num_rows; ++row_ndx) { + // List lists with more than one element are represented by a B+ tree, + // whose nodes need to be freed. + int_fast64_t value = Column::get(row_ndx); + if (value && value % 2 == 0) { + ref_type ref = to_ref(value); + Array::destroy_deep(ref, alloc); + } + Column::set(row_ndx, 0); + } +} + + void ColumnBackLink::update_backlink(size_t row_ndx, size_t old_origin_row_ndx, size_t new_origin_row_ndx) { @@ -188,62 +204,65 @@ void ColumnBackLink::nullify_links(size_t row_ndx, bool do_destroy) } -void ColumnBackLink::move_last_over(size_t target_row_ndx, size_t last_row_ndx) +void ColumnBackLink::erase(size_t, bool) { - TIGHTDB_ASSERT(target_row_ndx < last_row_ndx); + // This operation is not available for unordered tables, and only unordered + // tables may have link columns. + TIGHTDB_ASSERT(false); +} + + +void ColumnBackLink::move_last_over(size_t row_ndx, size_t last_row_ndx, bool) +{ + TIGHTDB_ASSERT(row_ndx <= last_row_ndx); TIGHTDB_ASSERT(last_row_ndx + 1 == size()); // Nullify all links pointing to the row being deleted bool do_destroy = true; - nullify_links(target_row_ndx, do_destroy); // Throws + nullify_links(row_ndx, do_destroy); // Throws // Update all links to the last row to point to the new row instead int_fast64_t value = Column::get(last_row_ndx); - if (value != 0) { - if (value % 2 != 0) { - size_t origin_row_ndx = to_size_t(value / 2); - m_origin_column->do_update_link(origin_row_ndx, last_row_ndx, target_row_ndx); // Throws - } - else { - // update entire list of links - ref_type ref = to_ref(value); - Column backlink_list(get_alloc(), ref); // Throws - - size_t n = backlink_list.size(); - for (size_t i = 0; i < n; ++i) { - int_fast64_t value_2 = backlink_list.get(i); - size_t origin_row_ndx = to_size_t(value_2); - m_origin_column->do_update_link(origin_row_ndx, last_row_ndx, target_row_ndx); // Throws + if (row_ndx != last_row_ndx) { + if (value != 0) { + if (value % 2 != 0) { + size_t origin_row_ndx = to_size_t(value / 2); + m_origin_column->do_update_link(origin_row_ndx, last_row_ndx, + row_ndx); // Throws + } + else { + // update entire list of links + ref_type ref = to_ref(value); + Column backlink_list(get_alloc(), ref); // Throws + + size_t n = backlink_list.size(); + for (size_t i = 0; i < n; ++i) { + int_fast64_t value_2 = backlink_list.get(i); + size_t origin_row_ndx = to_size_t(value_2); + m_origin_column->do_update_link(origin_row_ndx, last_row_ndx, + row_ndx); // Throws + } } } } // Do the actual move - Column::set(target_row_ndx, value); // Throws + Column::set(row_ndx, value); // Throws bool is_last = true; - Column::erase(last_row_ndx, is_last); // Throws + do_erase(last_row_ndx, is_last); // Throws } -void ColumnBackLink::erase(size_t row_ndx, bool is_last) +void ColumnBackLink::clear(std::size_t num_rows, bool) { - TIGHTDB_ASSERT(is_last); - bool do_destroy = true; - nullify_links(row_ndx, do_destroy); // Throws - Column::erase(row_ndx, is_last); // Throws -} - - -void ColumnBackLink::clear() -{ - size_t n = size(); - for (size_t i = 0; i < n; ++i) { + for (size_t row_ndx = 0; row_ndx < num_rows; ++row_ndx) { // Column::clear() handles the destruction of subtrees bool do_destroy = false; - nullify_links(i, do_destroy); // Throws + nullify_links(row_ndx, do_destroy); // Throws } - Column::clear(); // Throws - // FIXME: This one is needed because Column::clear() forgets about + + do_clear(); // Throws + // FIXME: This one is needed because Column::do_clear() forgets about // the leaf type. A better solution should probably be found. m_array->set_type(Array::type_HasRefs); } diff --git a/src/tightdb/column_backlink.hpp b/src/tightdb/column_backlink.hpp index 20fad248072..e6e0ad7739c 100644 --- a/src/tightdb/column_backlink.hpp +++ b/src/tightdb/column_backlink.hpp @@ -47,26 +47,26 @@ class ColumnBackLink: public Column, public ArrayParent { std::size_t get_backlink(std::size_t row_ndx, std::size_t backlink_ndx) const TIGHTDB_NOEXCEPT; void add_backlink(std::size_t row_ndx, std::size_t origin_row_ndx); - void remove_backlink(std::size_t row_ndx, std::size_t origin_row_ndx); - void update_backlink(std::size_t row_ndx, std::size_t old_row_ndx, std::size_t new_row_ndx); + void remove_one_backlink(std::size_t row_ndx, std::size_t origin_row_ndx); + void remove_all_backlinks(std::size_t num_rows); + void update_backlink(std::size_t row_ndx, std::size_t old_origin_row_ndx, + std::size_t new_origin_row_ndx); void add_row(); - void clear() TIGHTDB_OVERRIDE; - void erase(std::size_t, bool) TIGHTDB_OVERRIDE; - void move_last_over(std::size_t, std::size_t) TIGHTDB_OVERRIDE; - // Link origination info Table& get_origin_table() const TIGHTDB_NOEXCEPT; void set_origin_table(Table&) TIGHTDB_NOEXCEPT; ColumnLinkBase& get_origin_column() const TIGHTDB_NOEXCEPT; void set_origin_column(ColumnLinkBase&) TIGHTDB_NOEXCEPT; - void adj_accessors_insert_rows(std::size_t, std::size_t) TIGHTDB_NOEXCEPT TIGHTDB_OVERRIDE; - void adj_accessors_erase_row(std::size_t) TIGHTDB_NOEXCEPT TIGHTDB_OVERRIDE; - void adj_accessors_move(std::size_t, std::size_t) TIGHTDB_NOEXCEPT TIGHTDB_OVERRIDE; + void erase(std::size_t, bool) TIGHTDB_OVERRIDE; + void move_last_over(std::size_t, std::size_t, bool) TIGHTDB_OVERRIDE; + void clear(std::size_t, bool) TIGHTDB_OVERRIDE; + void adj_acc_insert_rows(std::size_t, std::size_t) TIGHTDB_NOEXCEPT TIGHTDB_OVERRIDE; + void adj_acc_erase_row(std::size_t) TIGHTDB_NOEXCEPT TIGHTDB_OVERRIDE; + void adj_acc_move_over(std::size_t, std::size_t) TIGHTDB_NOEXCEPT TIGHTDB_OVERRIDE; void adj_acc_clear_root_table() TIGHTDB_NOEXCEPT TIGHTDB_OVERRIDE; - void mark(int) TIGHTDB_NOEXCEPT TIGHTDB_OVERRIDE; void bump_link_origin_table_version() TIGHTDB_NOEXCEPT TIGHTDB_OVERRIDE; @@ -144,25 +144,25 @@ inline void ColumnBackLink::add_row() Column::add(0); } -inline void ColumnBackLink::adj_accessors_insert_rows(std::size_t row_ndx, - std::size_t num_rows) TIGHTDB_NOEXCEPT +inline void ColumnBackLink::adj_acc_insert_rows(std::size_t row_ndx, + std::size_t num_rows) TIGHTDB_NOEXCEPT { - Column::adj_accessors_insert_rows(row_ndx, num_rows); + Column::adj_acc_insert_rows(row_ndx, num_rows); // For tables with link-type columns, the insertion point must be after all // existsing rows, so the origin table cannot be affected by this change. } -inline void ColumnBackLink::adj_accessors_erase_row(std::size_t) TIGHTDB_NOEXCEPT +inline void ColumnBackLink::adj_acc_erase_row(std::size_t) TIGHTDB_NOEXCEPT { // Rows cannot be erased this way in tables with link-type columns TIGHTDB_ASSERT(false); } -inline void ColumnBackLink::adj_accessors_move(std::size_t target_row_ndx, - std::size_t source_row_ndx) TIGHTDB_NOEXCEPT +inline void ColumnBackLink::adj_acc_move_over(std::size_t from_row_ndx, + std::size_t to_row_ndx) TIGHTDB_NOEXCEPT { - Column::adj_accessors_move(target_row_ndx, source_row_ndx); + Column::adj_acc_move_over(from_row_ndx, to_row_ndx); typedef _impl::TableFriend tf; tf::mark(*m_origin_table); diff --git a/src/tightdb/column_basic.hpp b/src/tightdb/column_basic.hpp index 8f789b2fd9b..51d51662eed 100644 --- a/src/tightdb/column_basic.hpp +++ b/src/tightdb/column_basic.hpp @@ -53,11 +53,9 @@ class BasicColumn : public ColumnBase, public ColumnTemplate { void add(T value = T()); void set(std::size_t ndx, T value); void insert(std::size_t ndx, T value = T()); - - void insert(std::size_t, std::size_t, bool) TIGHTDB_OVERRIDE; - void erase(std::size_t ndx, bool is_last) TIGHTDB_OVERRIDE; - void clear() TIGHTDB_OVERRIDE; - void move_last_over(std::size_t, std::size_t) TIGHTDB_OVERRIDE; + void erase(std::size_t row_ndx); + void move_last_over(std::size_t row_ndx); + void clear(); std::size_t count(T value) const; @@ -94,6 +92,10 @@ class BasicColumn : public ColumnBase, public ColumnTemplate { ref_type write(std::size_t, std::size_t, std::size_t, _impl::OutputStream&) const TIGHTDB_OVERRIDE; + void insert(std::size_t, std::size_t, bool) TIGHTDB_OVERRIDE; + void erase(std::size_t, bool) TIGHTDB_OVERRIDE; + void move_last_over(std::size_t, std::size_t, bool) TIGHTDB_OVERRIDE; + void clear(std::size_t, bool) TIGHTDB_OVERRIDE; void refresh_accessor_tree(std::size_t, const Spec&) TIGHTDB_OVERRIDE; #ifdef TIGHTDB_DEBUG @@ -124,6 +126,10 @@ class BasicColumn : public ColumnBase, public ColumnTemplate { class CreateHandler; class SliceHandler; + void do_erase(std::size_t row_ndx, bool is_last); + void do_move_last_over(std::size_t row_ndx, std::size_t last_row_ndx); + void do_clear(); + #ifdef TIGHTDB_DEBUG static std::size_t verify_leaf(MemRef, Allocator&); void leaf_to_dot(MemRef, ArrayParent*, std::size_t ndx_in_parent, diff --git a/src/tightdb/column_basic_tpl.hpp b/src/tightdb/column_basic_tpl.hpp index acc239582d9..e6cff52c0f3 100644 --- a/src/tightdb/column_basic_tpl.hpp +++ b/src/tightdb/column_basic_tpl.hpp @@ -69,41 +69,6 @@ inline std::size_t BasicColumn::size() const TIGHTDB_NOEXCEPT return m_array->get_bptree_size(); } -template -void BasicColumn::clear() -{ - if (!m_array->is_inner_bptree_node()) { - static_cast*>(m_array)->clear(); // Throws - return; - } - - // Revert to generic array - util::UniquePtr > array; - array.reset(new BasicArray(m_array->get_alloc())); // Throws - array->create(); // Throws - array->set_parent(m_array->get_parent(), m_array->get_ndx_in_parent()); - array->update_parent(); // Throws - - // Remove original node - m_array->destroy_deep(); - delete m_array; - - m_array = array.release(); -} - -template -void BasicColumn::move_last_over(std::size_t target_row_ndx, std::size_t last_row_ndx) -{ - TIGHTDB_ASSERT(target_row_ndx < last_row_ndx); - TIGHTDB_ASSERT(last_row_ndx + 1 == size()); - - T value = get(last_row_ndx); - set(target_row_ndx, value); // Throws - - bool is_last = true; - erase(last_row_ndx, is_last); // Throws -} - template T BasicColumn::get(std::size_t ndx) const TIGHTDB_NOEXCEPT @@ -163,13 +128,22 @@ template inline void BasicColumn::insert(std::size_t row_ndx, T valu do_insert(row_ndx_2, value, num_rows); // Throws } -// Implementing pure virtual method of ColumnBase. -template -inline void BasicColumn::insert(std::size_t row_ndx, std::size_t num_rows, bool is_append) +template inline void BasicColumn::erase(std::size_t row_ndx) { - std::size_t row_ndx_2 = is_append ? tightdb::npos : row_ndx; - T value = T(); - do_insert(row_ndx_2, value, num_rows); // Throws + std::size_t last_row_ndx = size() - 1; // Note that size() is slow + bool is_last = row_ndx == last_row_ndx; + do_erase(row_ndx, is_last); // Throws +} + +template inline void BasicColumn::move_last_over(std::size_t row_ndx) +{ + std::size_t last_row_ndx = size() - 1; // Note that size() is slow + do_move_last_over(row_ndx, last_row_ndx); // Throws +} + +template inline void BasicColumn::clear() +{ + do_clear(); // Throws } template @@ -230,7 +204,7 @@ class BasicColumn::EraseLeafElem: public ColumnBase::EraseHandlerBase { }; template -void BasicColumn::erase(std::size_t ndx, bool is_last) +void BasicColumn::do_erase(std::size_t ndx, bool is_last) { TIGHTDB_ASSERT(ndx < size()); TIGHTDB_ASSERT(is_last == (ndx == size()-1)); @@ -240,12 +214,47 @@ void BasicColumn::erase(std::size_t ndx, bool is_last) return; } - size_t ndx_2 = is_last ? npos : ndx; + std::size_t ndx_2 = is_last ? npos : ndx; EraseLeafElem erase_leaf_elem(*this); Array::erase_bptree_elem(m_array, ndx_2, erase_leaf_elem); // Throws } +template +void BasicColumn::do_move_last_over(std::size_t row_ndx, std::size_t last_row_ndx) +{ + TIGHTDB_ASSERT(row_ndx <= last_row_ndx); + TIGHTDB_ASSERT(last_row_ndx + 1 == size()); + + T value = get(last_row_ndx); + set(row_ndx, value); // Throws + + bool is_last = true; + erase(last_row_ndx, is_last); // Throws +} + +template void BasicColumn::do_clear() +{ + if (!m_array->is_inner_bptree_node()) { + static_cast*>(m_array)->clear(); // Throws + return; + } + + // Revert to generic array + util::UniquePtr > array; + array.reset(new BasicArray(m_array->get_alloc())); // Throws + array->create(); // Throws + array->set_parent(m_array->get_parent(), m_array->get_ndx_in_parent()); + array->update_parent(); // Throws + + // Remove original node + m_array->destroy_deep(); + delete m_array; + + m_array = array.release(); +} + + template class BasicColumn::CreateHandler: public ColumnBase::CreateHandler { public: CreateHandler(Allocator& alloc): m_alloc(alloc) {} @@ -303,6 +312,35 @@ template ref_type BasicColumn::write(size_t slice_offset, size_t sli } +// Implementing pure virtual method of ColumnBase. +template +inline void BasicColumn::insert(std::size_t row_ndx, std::size_t num_rows, bool is_append) +{ + std::size_t row_ndx_2 = is_append ? tightdb::npos : row_ndx; + T value = T(); + do_insert(row_ndx_2, value, num_rows); // Throws +} + +// Implementing pure virtual method of ColumnBase. +template inline void BasicColumn::erase(std::size_t row_ndx, bool is_last) +{ + do_erase(row_ndx, is_last); // Throws +} + +// Implementing pure virtual method of ColumnBase. +template +void BasicColumn::move_last_over(std::size_t row_ndx, std::size_t last_row_ndx, bool) +{ + do_move_last_over(row_ndx, last_row_ndx); // Throws +} + +// Implementing pure virtual method of ColumnBase. +template void BasicColumn::clear(std::size_t, bool) +{ + do_clear(); // Throws +} + + template void BasicColumn::refresh_accessor_tree(std::size_t, const Spec&) { // The type of the cached root array accessor may no longer match the diff --git a/src/tightdb/column_binary.cpp b/src/tightdb/column_binary.cpp index 7e81b70a90f..a9756f9d239 100644 --- a/src/tightdb/column_binary.cpp +++ b/src/tightdb/column_binary.cpp @@ -59,39 +59,6 @@ ColumnBinary::~ColumnBinary() TIGHTDB_NOEXCEPT } -void ColumnBinary::clear() -{ - bool root_is_leaf = !m_array->is_inner_bptree_node(); - if (root_is_leaf) { - bool is_big = m_array->get_context_flag(); - if (!is_big) { - // Small blobs root leaf - ArrayBinary* leaf = static_cast(m_array); - leaf->clear(); // Throws - return; - } - // Big blobs root leaf - ArrayBigBlobs* leaf = static_cast(m_array); - leaf->clear(); // Throws - return; - } - - // Non-leaf root - revert to small blobs leaf - Allocator& alloc = m_array->get_alloc(); - UniquePtr array; - array.reset(new ArrayBinary(alloc)); // Throws - array->create(); // Throws - array->set_parent(m_array->get_parent(), m_array->get_ndx_in_parent()); - array->update_parent(); // Throws - - // Remove original node - m_array->destroy_deep(); - delete m_array; - - m_array = array.release(); -} - - namespace { struct SetLeafElem: Array::UpdateHandler { @@ -156,6 +123,93 @@ void ColumnBinary::set(size_t ndx, BinaryData value, bool add_zero_term) } +bool ColumnBinary::compare_binary(const ColumnBinary& c) const +{ + size_t n = size(); + if (c.size() != n) + return false; + for (size_t i = 0; i != n; ++i) { + if (get(i) != c.get(i)) + return false; + } + return true; +} + + +void ColumnBinary::do_insert(size_t row_ndx, BinaryData value, bool add_zero_term, size_t num_rows) +{ + TIGHTDB_ASSERT(row_ndx == tightdb::npos || row_ndx < size()); + ref_type new_sibling_ref; + InsertState state; + for (size_t i = 0; i != num_rows; ++i) { + size_t row_ndx_2 = row_ndx == tightdb::npos ? tightdb::npos : row_ndx + i; + if (root_is_leaf()) { + TIGHTDB_ASSERT(row_ndx_2 == tightdb::npos || row_ndx_2 < TIGHTDB_MAX_BPNODE_SIZE); + bool is_big = upgrade_root_leaf(value.size()); // Throws + if (!is_big) { + // Small blobs root leaf + ArrayBinary* leaf = static_cast(m_array); + new_sibling_ref = + leaf->bptree_leaf_insert(row_ndx_2, value, add_zero_term, state); // Throws + } + else { + // Big blobs root leaf + ArrayBigBlobs* leaf = static_cast(m_array); + new_sibling_ref = + leaf->bptree_leaf_insert(row_ndx_2, value, add_zero_term, state); // Throws + } + } + else { + // Non-leaf root + state.m_value = value; + state.m_add_zero_term = add_zero_term; + if (row_ndx_2 == tightdb::npos) { + new_sibling_ref = m_array->bptree_append(state); + } + else { + new_sibling_ref = m_array->bptree_insert(row_ndx_2, state); + } + } + if (TIGHTDB_UNLIKELY(new_sibling_ref)) { + bool is_append = row_ndx_2 == tightdb::npos; + introduce_new_root(new_sibling_ref, state, is_append); + } + } +} + + +ref_type ColumnBinary::leaf_insert(MemRef leaf_mem, ArrayParent& parent, + size_t ndx_in_parent, Allocator& alloc, + size_t insert_ndx, + Array::TreeInsert& state) +{ + InsertState& state_2 = static_cast(state); + bool is_big = Array::get_context_flag_from_header(leaf_mem.m_addr); + if (is_big) { + ArrayBigBlobs leaf(alloc); + leaf.init_from_mem(leaf_mem); + leaf.set_parent(&parent, ndx_in_parent); + return leaf.bptree_leaf_insert(insert_ndx, state_2.m_value, state_2.m_add_zero_term, + state); // Throws + } + ArrayBinary leaf(alloc); + leaf.init_from_mem(leaf_mem); + leaf.set_parent(&parent, ndx_in_parent); + if (state_2.m_value.size() <= small_blob_max_size) + return leaf.bptree_leaf_insert(insert_ndx, state_2.m_value, state_2.m_add_zero_term, + state); // Throws + // Upgrade leaf from small to big blobs + ArrayBigBlobs new_leaf(alloc); + new_leaf.create(); // Throws + new_leaf.set_parent(&parent, ndx_in_parent); + new_leaf.update_parent(); // Throws + copy_leaf(leaf, new_leaf); // Throws + leaf.destroy(); + return new_leaf.bptree_leaf_insert(insert_ndx, state_2.m_value, state_2.m_add_zero_term, + state); // Throws +} + + class ColumnBinary::EraseLeafElem: public ColumnBase::EraseHandlerBase { public: EraseLeafElem(ColumnBinary& column) TIGHTDB_NOEXCEPT: @@ -225,7 +279,7 @@ class ColumnBinary::EraseLeafElem: public ColumnBase::EraseHandlerBase { } }; -void ColumnBinary::erase(size_t ndx, bool is_last) +void ColumnBinary::do_erase(size_t ndx, bool is_last) { TIGHTDB_ASSERT(ndx < size()); TIGHTDB_ASSERT(is_last == (ndx == size()-1)); @@ -252,8 +306,11 @@ void ColumnBinary::erase(size_t ndx, bool is_last) } -void ColumnBinary::move_last_over(size_t target_row_ndx, size_t last_row_ndx) +void ColumnBinary::do_move_last_over(size_t row_ndx, size_t last_row_ndx) { + TIGHTDB_ASSERT(row_ndx <= last_row_ndx); + TIGHTDB_ASSERT(last_row_ndx + 1 == size()); + // FIXME: ExceptionSafety: The current implementation of this // function is not exception-safe, and it is hard to see how to // repair it. @@ -266,9 +323,6 @@ void ColumnBinary::move_last_over(size_t target_row_ndx, size_t last_row_ndx) // way that avoids the intermediate copy. This approach is also // likely to be necesseray for exception safety. - TIGHTDB_ASSERT(target_row_ndx < last_row_ndx); - TIGHTDB_ASSERT(last_row_ndx + 1 == size()); - BinaryData value = get(last_row_ndx); // Copying binary data from a column to itself requires an @@ -277,97 +331,43 @@ void ColumnBinary::move_last_over(size_t target_row_ndx, size_t last_row_ndx) copy(value.data(), value.data()+value.size(), buffer.get()); BinaryData copy_of_value(buffer.get(), value.size()); - set(target_row_ndx, copy_of_value); // Throws + set(row_ndx, copy_of_value); // Throws bool is_last = true; erase(last_row_ndx, is_last); // Throws } -bool ColumnBinary::compare_binary(const ColumnBinary& c) const +void ColumnBinary::do_clear() { - size_t n = size(); - if (c.size() != n) - return false; - for (size_t i = 0; i != n; ++i) { - if (get(i) != c.get(i)) - return false; - } - return true; -} - - -void ColumnBinary::do_insert(size_t row_ndx, BinaryData value, bool add_zero_term, size_t num_rows) -{ - TIGHTDB_ASSERT(row_ndx == tightdb::npos || row_ndx < size()); - ref_type new_sibling_ref; - InsertState state; - for (size_t i = 0; i != num_rows; ++i) { - size_t row_ndx_2 = row_ndx == tightdb::npos ? tightdb::npos : row_ndx + i; - if (root_is_leaf()) { - TIGHTDB_ASSERT(row_ndx_2 == tightdb::npos || row_ndx_2 < TIGHTDB_MAX_BPNODE_SIZE); - bool is_big = upgrade_root_leaf(value.size()); // Throws - if (!is_big) { - // Small blobs root leaf - ArrayBinary* leaf = static_cast(m_array); - new_sibling_ref = - leaf->bptree_leaf_insert(row_ndx_2, value, add_zero_term, state); // Throws - } - else { - // Big blobs root leaf - ArrayBigBlobs* leaf = static_cast(m_array); - new_sibling_ref = - leaf->bptree_leaf_insert(row_ndx_2, value, add_zero_term, state); // Throws - } - } - else { - // Non-leaf root - state.m_value = value; - state.m_add_zero_term = add_zero_term; - if (row_ndx_2 == tightdb::npos) { - new_sibling_ref = m_array->bptree_append(state); - } - else { - new_sibling_ref = m_array->bptree_insert(row_ndx_2, state); - } - } - if (TIGHTDB_UNLIKELY(new_sibling_ref)) { - bool is_append = row_ndx_2 == tightdb::npos; - introduce_new_root(new_sibling_ref, state, is_append); + bool root_is_leaf = !m_array->is_inner_bptree_node(); + if (root_is_leaf) { + bool is_big = m_array->get_context_flag(); + if (!is_big) { + // Small blobs root leaf + ArrayBinary* leaf = static_cast(m_array); + leaf->clear(); // Throws + return; } + // Big blobs root leaf + ArrayBigBlobs* leaf = static_cast(m_array); + leaf->clear(); // Throws + return; } -} + // Non-leaf root - revert to small blobs leaf + Allocator& alloc = m_array->get_alloc(); + UniquePtr array; + array.reset(new ArrayBinary(alloc)); // Throws + array->create(); // Throws + array->set_parent(m_array->get_parent(), m_array->get_ndx_in_parent()); + array->update_parent(); // Throws -ref_type ColumnBinary::leaf_insert(MemRef leaf_mem, ArrayParent& parent, - size_t ndx_in_parent, Allocator& alloc, - size_t insert_ndx, - Array::TreeInsert& state) -{ - InsertState& state_2 = static_cast(state); - bool is_big = Array::get_context_flag_from_header(leaf_mem.m_addr); - if (is_big) { - ArrayBigBlobs leaf(alloc); - leaf.init_from_mem(leaf_mem); - leaf.set_parent(&parent, ndx_in_parent); - return leaf.bptree_leaf_insert(insert_ndx, state_2.m_value, state_2.m_add_zero_term, - state); // Throws - } - ArrayBinary leaf(alloc); - leaf.init_from_mem(leaf_mem); - leaf.set_parent(&parent, ndx_in_parent); - if (state_2.m_value.size() <= small_blob_max_size) - return leaf.bptree_leaf_insert(insert_ndx, state_2.m_value, state_2.m_add_zero_term, - state); // Throws - // Upgrade leaf from small to big blobs - ArrayBigBlobs new_leaf(alloc); - new_leaf.create(); // Throws - new_leaf.set_parent(&parent, ndx_in_parent); - new_leaf.update_parent(); // Throws - copy_leaf(leaf, new_leaf); // Throws - leaf.destroy(); - return new_leaf.bptree_leaf_insert(insert_ndx, state_2.m_value, state_2.m_add_zero_term, - state); // Throws + // Remove original node + m_array->destroy_deep(); + delete m_array; + + m_array = array.release(); } diff --git a/src/tightdb/column_binary.hpp b/src/tightdb/column_binary.hpp index 1180bb9c201..ec286341ba0 100644 --- a/src/tightdb/column_binary.hpp +++ b/src/tightdb/column_binary.hpp @@ -46,11 +46,9 @@ class ColumnBinary: public ColumnBase { void add(BinaryData value = BinaryData()); void set(std::size_t ndx, BinaryData value, bool add_zero_term = false); void insert(std::size_t ndx, BinaryData value = BinaryData()); - - void insert(std::size_t, std::size_t, bool) TIGHTDB_OVERRIDE; - void erase(std::size_t ndx, bool is_last) TIGHTDB_OVERRIDE; - void clear() TIGHTDB_OVERRIDE; - void move_last_over(std::size_t, std::size_t) TIGHTDB_OVERRIDE; + void erase(std::size_t row_ndx); + void move_last_over(std::size_t row_ndx); + void clear(); // Requires that the specified entry was inserted as StringData. StringData get_string(std::size_t ndx) const TIGHTDB_NOEXCEPT; @@ -70,6 +68,11 @@ class ColumnBinary: public ColumnBase { ref_type write(std::size_t, std::size_t, std::size_t, _impl::OutputStream&) const TIGHTDB_OVERRIDE; + void insert(std::size_t, std::size_t, bool) TIGHTDB_OVERRIDE; + void erase(std::size_t, bool) TIGHTDB_OVERRIDE; + void move_last_over(std::size_t, std::size_t, bool) TIGHTDB_OVERRIDE; + void clear(std::size_t, bool) TIGHTDB_OVERRIDE; + void update_from_parent(std::size_t) TIGHTDB_NOEXCEPT TIGHTDB_OVERRIDE; void refresh_accessor_tree(std::size_t, const Spec&) TIGHTDB_OVERRIDE; #ifdef TIGHTDB_DEBUG @@ -77,7 +80,6 @@ class ColumnBinary: public ColumnBase { void to_dot(std::ostream&, StringData title) const TIGHTDB_OVERRIDE; void do_dump_node_structure(std::ostream&, int) const TIGHTDB_OVERRIDE; #endif - void update_from_parent(std::size_t old_baseline) TIGHTDB_NOEXCEPT TIGHTDB_OVERRIDE; private: std::size_t do_get_size() const TIGHTDB_NOEXCEPT TIGHTDB_OVERRIDE { return size(); } @@ -99,6 +101,10 @@ class ColumnBinary: public ColumnBase { class CreateHandler; class SliceHandler; + void do_erase(std::size_t row_ndx, bool is_last); + void do_move_last_over(std::size_t row_ndx, std::size_t last_row_ndx); + void do_clear(); + /// Root must be a leaf. Upgrades the root leaf if /// necessary. Returns true if, and only if the root is a 'big /// blobs' leaf upon return. @@ -215,6 +221,24 @@ inline void ColumnBinary::insert(std::size_t row_ndx, BinaryData value) do_insert(row_ndx_2, value, add_zero_term, num_rows); // Throws } +inline void ColumnBinary::erase(std::size_t row_ndx) +{ + std::size_t last_row_ndx = size() - 1; // Note that size() is slow + bool is_last = row_ndx == last_row_ndx; + do_erase(row_ndx, is_last); // Throws +} + +inline void ColumnBinary::move_last_over(std::size_t row_ndx) +{ + std::size_t last_row_ndx = size() - 1; // Note that size() is slow + do_move_last_over(row_ndx, last_row_ndx); // Throws +} + +inline void ColumnBinary::clear() +{ + do_clear(); // Throws +} + // Implementing pure virtual method of ColumnBase. inline void ColumnBinary::insert(std::size_t row_ndx, std::size_t num_rows, bool is_append) { @@ -224,6 +248,24 @@ inline void ColumnBinary::insert(std::size_t row_ndx, std::size_t num_rows, bool do_insert(row_ndx_2, value, add_zero_term, num_rows); // Throws } +// Implementing pure virtual method of ColumnBase. +inline void ColumnBinary::erase(std::size_t row_ndx, bool is_last) +{ + do_erase(row_ndx, is_last); // Throws +} + +// Implementing pure virtual method of ColumnBase. +inline void ColumnBinary::move_last_over(std::size_t row_ndx, std::size_t last_row_ndx, bool) +{ + do_move_last_over(row_ndx, last_row_ndx); // Throws +} + +// Implementing pure virtual method of ColumnBase. +inline void ColumnBinary::clear(std::size_t, bool) +{ + do_clear(); // Throws +} + inline void ColumnBinary::add_string(StringData value) { std::size_t row_ndx = tightdb::npos; diff --git a/src/tightdb/column_fwd.hpp b/src/tightdb/column_fwd.hpp index b6e820f400f..ca56044296d 100644 --- a/src/tightdb/column_fwd.hpp +++ b/src/tightdb/column_fwd.hpp @@ -34,6 +34,7 @@ class ColumnBinary; class ColumnTable; class ColumnMixed; class ColumnLink; +class ColumnLinkList; } // namespace tightdb diff --git a/src/tightdb/column_link.cpp b/src/tightdb/column_link.cpp index 7d90d5c35da..f65a5a98309 100644 --- a/src/tightdb/column_link.cpp +++ b/src/tightdb/column_link.cpp @@ -18,82 +18,100 @@ * **************************************************************************/ +#include + #include "column_link.hpp" using namespace std; using namespace tightdb; -void ColumnLink::set_link(size_t row_ndx, size_t target_row_ndx) +void ColumnLink::remove_backlinks(size_t row_ndx) { - size_t ref = ColumnLinkBase::get(row_ndx); - if (ref != 0) { - size_t old_target_row_ndx = ref - 1; - m_backlink_column->remove_backlink(old_target_row_ndx, row_ndx); + int_fast64_t value = ColumnLinkBase::get(row_ndx); + if (value != 0) { + size_t target_row_ndx = to_size_t(value - 1); + m_backlink_column->remove_one_backlink(target_row_ndx, row_ndx); } - - // Row pos is offset by one, to allow null refs - ColumnLinkBase::set(row_ndx, target_row_ndx + 1); - - m_backlink_column->add_backlink(target_row_ndx, row_ndx); } -void ColumnLink::nullify_link(size_t row_ndx) + +void ColumnLink::move_last_over(size_t row_ndx, size_t last_row_ndx, + bool broken_reciprocal_backlinks) { - size_t ref = ColumnLinkBase::get(row_ndx); - if (ref == 0) - return; + TIGHTDB_ASSERT(row_ndx <= last_row_ndx); + TIGHTDB_ASSERT(last_row_ndx + 1 == size()); - size_t old_target_row_ndx = ref - 1; - m_backlink_column->remove_backlink(old_target_row_ndx, row_ndx); + // Remove backlinks to deleted row + if (!broken_reciprocal_backlinks) + remove_backlinks(row_ndx); - ColumnLinkBase::set(row_ndx, 0); + // Update backlinks to last row to point to its new position + if (row_ndx != last_row_ndx) { + int_fast64_t value = ColumnLinkBase::get(last_row_ndx); + if (value != 0) { + size_t target_row_ndx = to_size_t(value - 1); + m_backlink_column->update_backlink(target_row_ndx, last_row_ndx, row_ndx); + } + } + + do_move_last_over(row_ndx, last_row_ndx); } -void ColumnLink::remove_backlinks(size_t row_ndx) + +void ColumnLink::clear(size_t, bool broken_reciprocal_backlinks) { - size_t ref = ColumnLinkBase::get(row_ndx); - if (ref != 0) { - size_t old_target_row_ndx = ref - 1; - m_backlink_column->remove_backlink(old_target_row_ndx, row_ndx); + if (!broken_reciprocal_backlinks) { + size_t num_target_rows = m_target_table->size(); + m_backlink_column->remove_all_backlinks(num_target_rows); // Throws } + + do_clear(); // Throws } -void ColumnLink::move_last_over(size_t target_row_ndx, size_t last_row_ndx) + +void ColumnLink::cascade_break_backlinks_to(size_t row_ndx, CascadeState& state) { - TIGHTDB_ASSERT(target_row_ndx < last_row_ndx); - TIGHTDB_ASSERT(last_row_ndx + 1 == size()); + int_fast64_t value = ColumnLinkBase::get(row_ndx); + bool is_null = value == 0; + if (is_null) + return; - // Remove backlinks to deleted row - remove_backlinks(target_row_ndx); + // Remove the reciprocal backlink at target_row_ndx that points to row_ndx + size_t target_row_ndx = to_size_t(value - 1); + m_backlink_column->remove_one_backlink(target_row_ndx, row_ndx); - // Update backlinks to last row to point to its new position - size_t ref2 = ColumnLinkBase::get(last_row_ndx); - if (ref2 != 0) { - size_t last_target_row_ndx = ref2 - 1; - m_backlink_column->update_backlink(last_target_row_ndx, last_row_ndx, target_row_ndx); - } + if (m_weak_links) + return; + if (m_target_table == state.stop_on_table) + return; - // Do the actual move - ColumnLinkBase::move_last_over(target_row_ndx, last_row_ndx); + // Recurse on target row when appropriate + size_t target_table_ndx = m_target_table->get_index_in_group(); + check_cascade_break_backlinks_to(target_table_ndx, target_row_ndx, state); // Throws } -void ColumnLink::erase(size_t row_ndx, bool is_last) + +void ColumnLink::cascade_break_backlinks_to_all_rows(size_t num_rows, CascadeState& state) { - TIGHTDB_ASSERT(is_last); + size_t num_target_rows = m_target_table->size(); + m_backlink_column->remove_all_backlinks(num_target_rows); - // Remove backlinks to deleted row - remove_backlinks(row_ndx); + if (m_weak_links) + return; + if (m_target_table == state.stop_on_table) + return; - ColumnLinkBase::erase(row_ndx, is_last); -} + size_t target_table_ndx = m_target_table->get_index_in_group(); + for (size_t i = 0; i < num_rows; ++i) { + int_fast64_t value = ColumnLinkBase::get(i); + bool is_null = value == 0; + if (is_null) + continue; -void ColumnLink::clear() -{ - size_t count = size(); - for (size_t i = 0; i < count; ++i) - remove_backlinks(i); - ColumnLinkBase::clear(); + size_t target_row_ndx = to_size_t(value - 1); + check_cascade_break_backlinks_to(target_table_ndx, target_row_ndx, state); // Throws + } } diff --git a/src/tightdb/column_link.hpp b/src/tightdb/column_link.hpp index 804cb7ea494..e48bc7139a8 100644 --- a/src/tightdb/column_link.hpp +++ b/src/tightdb/column_link.hpp @@ -39,16 +39,27 @@ class ColumnLink: public ColumnLinkBase { static ref_type create(Allocator&, std::size_t size = 0); - // Getting and modifying links - bool is_null_link(std::size_t row_ndx) const TIGHTDB_NOEXCEPT; + //@{ + + /// is_null_link() is shorthand for `get_link() == tightdb::npos`, + /// nullify_link() is shorthand foe `set_link(tightdb::npos)`, and + /// insert_null_link() is shorthand for + /// `insert_link(tightdb::npos)`. set_link() returns the original link, with + /// `tightdb::npos` indicating that it was null. + std::size_t get_link(std::size_t row_ndx) const TIGHTDB_NOEXCEPT; - void set_link(std::size_t row_ndx, std::size_t target_row_ndx); - void insert_link(std::size_t row_ndx, std::size_t target_row_ndx); + bool is_null_link(std::size_t row_ndx) const TIGHTDB_NOEXCEPT; + std::size_t set_link(std::size_t row_ndx, std::size_t target_row_ndx); void nullify_link(std::size_t row_ndx); + void insert_link(std::size_t row_ndx, std::size_t target_row_ndx); + void insert_null_link(std::size_t row_ndx); - void clear() TIGHTDB_OVERRIDE; - void erase(std::size_t, bool) TIGHTDB_OVERRIDE; - void move_last_over(std::size_t, std::size_t) TIGHTDB_OVERRIDE; + //@} + + void move_last_over(std::size_t, std::size_t, bool) TIGHTDB_OVERRIDE; + void clear(std::size_t, bool) TIGHTDB_OVERRIDE; + void cascade_break_backlinks_to(std::size_t, CascadeState&) TIGHTDB_OVERRIDE; + void cascade_break_backlinks_to_all_rows(std::size_t, CascadeState&) TIGHTDB_OVERRIDE; #ifdef TIGHTDB_DEBUG void Verify(const Table&, std::size_t) const TIGHTDB_OVERRIDE; @@ -81,24 +92,51 @@ inline ref_type ColumnLink::create(Allocator& alloc, std::size_t size) return Column::create(alloc, Array::type_Normal, size); // Throws } +inline std::size_t ColumnLink::get_link(std::size_t row_ndx) const TIGHTDB_NOEXCEPT +{ + // Map zero to tightdb::npos, and `n+1` to `n`, where `n` is a target row index. + return to_size_t(ColumnLinkBase::get(row_ndx)) - size_t(1); +} + inline bool ColumnLink::is_null_link(std::size_t row_ndx) const TIGHTDB_NOEXCEPT { - // Zero indicates a missing (null) link - return (ColumnLinkBase::get(row_ndx) == 0); + // Null is represented by zero + return ColumnLinkBase::get(row_ndx) == 0; } -inline std::size_t ColumnLink::get_link(std::size_t row_ndx) const TIGHTDB_NOEXCEPT +inline std::size_t ColumnLink::set_link(std::size_t row_ndx, std::size_t target_row_ndx) { - // Row pos is offset by one, to allow null refs - return to_size_t(ColumnLinkBase::get(row_ndx) - 1); + int_fast64_t old_value = ColumnLinkBase::get(row_ndx); + std::size_t old_target_row_ndx = to_size_t(old_value) - size_t(1); + if (old_value != 0) + m_backlink_column->remove_one_backlink(old_target_row_ndx, row_ndx); // Throws + + int_fast64_t new_value = int_fast64_t(size_t(1) + target_row_ndx); + ColumnLinkBase::set(row_ndx, new_value); // Throws + + if (target_row_ndx != tightdb::npos) + m_backlink_column->add_backlink(target_row_ndx, row_ndx); // Throws + + return old_target_row_ndx; +} + +inline void ColumnLink::nullify_link(size_t row_ndx) +{ + set_link(row_ndx, tightdb::npos); // Throws } inline void ColumnLink::insert_link(std::size_t row_ndx, std::size_t target_row_ndx) { - // Row pos is offsest by one, to allow null refs - ColumnLinkBase::insert(row_ndx, target_row_ndx + 1); + int_fast64_t value = int_fast64_t(size_t(1) + target_row_ndx); + ColumnLinkBase::insert(row_ndx, value); // Throws - m_backlink_column->add_backlink(target_row_ndx, row_ndx); + if (target_row_ndx != tightdb::npos) + m_backlink_column->add_backlink(target_row_ndx, row_ndx); // Throws +} + +inline void ColumnLink::insert_null_link(size_t row_ndx) +{ + insert_link(row_ndx, tightdb::npos); // Throws } inline void ColumnLink::do_nullify_link(std::size_t row_ndx, std::size_t) diff --git a/src/tightdb/column_link_base.cpp b/src/tightdb/column_link_base.cpp index bdc428b2a16..e65d7436c89 100644 --- a/src/tightdb/column_link_base.cpp +++ b/src/tightdb/column_link_base.cpp @@ -6,6 +6,49 @@ using namespace std; using namespace tightdb; + +void ColumnLinkBase::erase(size_t, bool) +{ + // This operation is not available for unordered tables, and only unordered + // tables may have link columns. + TIGHTDB_ASSERT(false); +} + + +void ColumnLinkBase::refresh_accessor_tree(size_t col_ndx, const Spec& spec) +{ + Column::refresh_accessor_tree(col_ndx, spec); // Throws + ColumnAttr attr = spec.get_column_attr(col_ndx); + m_weak_links = (attr & col_attr_StrongLinks) == 0; +} + + +void ColumnLinkBase::check_cascade_break_backlinks_to(size_t target_table_ndx, size_t target_row_ndx, + CascadeState& state) +{ + // Stop if the target row was already visited + CascadeState::row target_row; + target_row.table_ndx = target_table_ndx; + target_row.row_ndx = target_row_ndx; + typedef CascadeState::row_set::iterator iter; + iter i = ::upper_bound(state.rows.begin(), state.rows.end(), target_row); + bool already_seen = i != state.rows.begin() && i[-1] == target_row; + if (already_seen) + return; + + // Stop if there are any remaining strong links to this row (this scheme + // fails to discover orphaned cycles) + typedef _impl::TableFriend tf; + size_t num_remaining = tf::get_num_strong_backlinks(*m_target_table, target_row_ndx); + if (num_remaining > 0) + return; + + // Recurse + state.rows.insert(i, target_row); // Throws + tf::cascade_break_backlinks_to(*m_target_table, target_row_ndx, state); // Throws +} + + #ifdef TIGHTDB_DEBUG void ColumnLinkBase::Verify(const Table& table, size_t col_ndx) const diff --git a/src/tightdb/column_linkbase.hpp b/src/tightdb/column_linkbase.hpp index 1c5dbd078d3..7531bf25f28 100644 --- a/src/tightdb/column_linkbase.hpp +++ b/src/tightdb/column_linkbase.hpp @@ -31,6 +31,9 @@ class ColumnLinkBase: public Column { public: ~ColumnLinkBase() TIGHTDB_NOEXCEPT TIGHTDB_OVERRIDE; + bool get_weak_links() const TIGHTDB_NOEXCEPT; + void set_weak_links(bool) TIGHTDB_NOEXCEPT; + Table& get_target_table() const TIGHTDB_NOEXCEPT; void set_target_table(Table&) TIGHTDB_NOEXCEPT; ColumnBackLink& get_backlink_column() const TIGHTDB_NOEXCEPT; @@ -40,12 +43,13 @@ class ColumnLinkBase: public Column { virtual void do_update_link(std::size_t row_ndx, std::size_t old_target_row_ndx, std::size_t new_target_row_ndx) = 0; - void adj_accessors_insert_rows(std::size_t, std::size_t) TIGHTDB_NOEXCEPT TIGHTDB_OVERRIDE; - void adj_accessors_erase_row(std::size_t) TIGHTDB_NOEXCEPT TIGHTDB_OVERRIDE; - void adj_accessors_move(std::size_t, std::size_t) TIGHTDB_NOEXCEPT TIGHTDB_OVERRIDE; + void erase(std::size_t, bool) TIGHTDB_OVERRIDE; + void adj_acc_insert_rows(std::size_t, std::size_t) TIGHTDB_NOEXCEPT TIGHTDB_OVERRIDE; + void adj_acc_erase_row(std::size_t) TIGHTDB_NOEXCEPT TIGHTDB_OVERRIDE; + void adj_acc_move_over(std::size_t, std::size_t) TIGHTDB_NOEXCEPT TIGHTDB_OVERRIDE; void adj_acc_clear_root_table() TIGHTDB_NOEXCEPT TIGHTDB_OVERRIDE; - void mark(int) TIGHTDB_NOEXCEPT TIGHTDB_OVERRIDE; + void refresh_accessor_tree(std::size_t, const Spec&) TIGHTDB_OVERRIDE; #ifdef TIGHTDB_DEBUG void Verify(const Table&, std::size_t) const TIGHTDB_OVERRIDE; @@ -55,9 +59,16 @@ class ColumnLinkBase: public Column { protected: TableRef m_target_table; ColumnBackLink* m_backlink_column; + bool m_weak_links; // True if these links are weak (not strong) // Create unattached root array aaccessor. ColumnLinkBase(Allocator&, ref_type); + + /// Call Table::cascade_break_backlinks_to() for the specified target row if + /// it is not already in \a state.rows, and the number of strong links to it + /// has dropped to zero. + void check_cascade_break_backlinks_to(std::size_t target_table_ndx, std::size_t target_row_ndx, + CascadeState& state); }; @@ -67,7 +78,8 @@ class ColumnLinkBase: public Column { inline ColumnLinkBase::ColumnLinkBase(Allocator& alloc, ref_type ref): Column(alloc, ref), - m_backlink_column(0) + m_backlink_column(0), + m_weak_links(false) { } @@ -75,6 +87,16 @@ inline ColumnLinkBase::~ColumnLinkBase() TIGHTDB_NOEXCEPT { } +inline bool ColumnLinkBase::get_weak_links() const TIGHTDB_NOEXCEPT +{ + return m_weak_links; +} + +inline void ColumnLinkBase::set_weak_links(bool value) TIGHTDB_NOEXCEPT +{ + m_weak_links = value; +} + inline Table& ColumnLinkBase::get_target_table() const TIGHTDB_NOEXCEPT { return *m_target_table; @@ -96,10 +118,10 @@ inline void ColumnLinkBase::set_backlink_column(ColumnBackLink& column) TIGHTDB_ m_backlink_column = &column; } -inline void ColumnLinkBase::adj_accessors_insert_rows(std::size_t row_ndx, - std::size_t num_rows) TIGHTDB_NOEXCEPT +inline void ColumnLinkBase::adj_acc_insert_rows(std::size_t row_ndx, + std::size_t num_rows) TIGHTDB_NOEXCEPT { - Column::adj_accessors_insert_rows(row_ndx, num_rows); + Column::adj_acc_insert_rows(row_ndx, num_rows); // For tables with link-type columns, the insertion point must be after all // existsing rows, but since the inserted link can be non-null, the target @@ -108,16 +130,16 @@ inline void ColumnLinkBase::adj_accessors_insert_rows(std::size_t row_ndx, tf::mark(*m_target_table); } -inline void ColumnLinkBase::adj_accessors_erase_row(std::size_t) TIGHTDB_NOEXCEPT +inline void ColumnLinkBase::adj_acc_erase_row(std::size_t) TIGHTDB_NOEXCEPT { // Rows cannot be erased this way in tables with link-type columns TIGHTDB_ASSERT(false); } -inline void ColumnLinkBase::adj_accessors_move(std::size_t target_row_ndx, - std::size_t source_row_ndx) TIGHTDB_NOEXCEPT +inline void ColumnLinkBase::adj_acc_move_over(std::size_t from_row_ndx, + std::size_t to_row_ndx) TIGHTDB_NOEXCEPT { - Column::adj_accessors_move(target_row_ndx, source_row_ndx); + Column::adj_acc_move_over(from_row_ndx, to_row_ndx); typedef _impl::TableFriend tf; tf::mark(*m_target_table); diff --git a/src/tightdb/column_linklist.cpp b/src/tightdb/column_linklist.cpp index 4609e6f2a5e..9b6ceb00076 100644 --- a/src/tightdb/column_linklist.cpp +++ b/src/tightdb/column_linklist.cpp @@ -28,97 +28,165 @@ using namespace std; using namespace tightdb; -void ColumnLinkList::clear() +void ColumnLinkList::move_last_over(size_t row_ndx, size_t last_row_ndx, + bool broken_reciprocal_backlinks) { - discard_child_accessors(); + TIGHTDB_ASSERT(row_ndx <= last_row_ndx); + TIGHTDB_ASSERT(last_row_ndx + 1 == size()); - // Remove all backlinks to the delete rows - // - // FIXME: size() is a relatively slow function. Consider passing the size - // from Table::m_size. - size_t num_rows = size(); - for (size_t row_ndx = 0; row_ndx < num_rows; ++row_ndx) { - ref_type ref = get_as_ref(row_ndx); - if (ref == 0) - continue; + // Remove backlinks to the delete row + if (!broken_reciprocal_backlinks) { + if (ref_type ref = get_as_ref(row_ndx)) { + Column link_list(get_alloc(), ref); + size_t n = link_list.size(); + for (size_t i = 0; i < n; ++i) { + size_t target_row_ndx = to_size_t(link_list.get(i)); + m_backlink_column->remove_one_backlink(target_row_ndx, row_ndx); + } + } + } - Column link_list(get_alloc(), ref); - size_t n = link_list.size(); - for (size_t i = 0; i < n; ++i) { - size_t old_target_row_ndx = to_size_t(link_list.get(i)); - m_backlink_column->remove_backlink(old_target_row_ndx, row_ndx); + // Update backlinks to last row to point to its new position + if (row_ndx != last_row_ndx) { + if (ref_type ref = get_as_ref(last_row_ndx)) { + Column link_list(get_alloc(), ref); + size_t n = link_list.size(); + for (size_t i = 0; i < n; ++i) { + size_t target_row_ndx = to_size_t(link_list.get(i)); + m_backlink_column->update_backlink(target_row_ndx, last_row_ndx, row_ndx); + } } } + // Do the actual delete and move + bool clear_value = false; + destroy_subtree(row_ndx, clear_value); + do_move_last_over(row_ndx, last_row_ndx); + + const bool fix_ndx_in_parent = true; + adj_move_over(last_row_ndx, row_ndx); +} + + +void ColumnLinkList::clear(size_t, bool broken_reciprocal_backlinks) +{ + if (!broken_reciprocal_backlinks) { + size_t num_target_rows = m_target_table->size(); + m_backlink_column->remove_all_backlinks(num_target_rows); // Throws + } + // Do the actual deletion - ColumnLinkBase::clear(); // Throws - // FIXME: This one is needed because Column::clear() forgets about the leaf - // type. A better solution should probably be sought after. + do_clear(); // Throws + // FIXME: This one is needed because Column::do_clear() forgets about the + // leaf type. A better solution should probably be sought after. m_array->set_type(Array::type_HasRefs); // Throws + + discard_child_accessors(); } -void ColumnLinkList::move_last_over(size_t target_row_ndx, size_t last_row_ndx) +void ColumnLinkList::cascade_break_backlinks_to(size_t row_ndx, CascadeState& state) { - // Remove backlinks to the delete row - if (ref_type ref = get_as_ref(target_row_ndx)) { - Column link_list(get_alloc(), ref); - size_t n = link_list.size(); - for (size_t i = 0; i < n; ++i) { - size_t old_target_row_ndx = to_size_t(link_list.get(i)); - m_backlink_column->remove_backlink(old_target_row_ndx, target_row_ndx); - } + if (row_ndx == state.stop_on_link_list_row_ndx && this == state.stop_on_link_list_column) + return; + + // Avoid the construction of both a LinkView and a Column instance, since + // both would involve heap allocations. + ref_type ref = get_as_ref(row_ndx); + if (ref == 0) + return; + Array root(get_alloc()); + root.init_from_ref(ref); + + if (!root.is_inner_bptree_node()) { + cascade_break_backlinks_to__leaf(row_ndx, root, state); // Throws + return; } - // Update backlinks to last row to point to its new position - if (ref_type ref = get_as_ref(last_row_ndx)) { - Column link_list(get_alloc(), ref); - size_t n = link_list.size(); - for (size_t i = 0; i < n; ++i) { - size_t old_target_row_ndx = to_size_t(link_list.get(i)); - m_backlink_column->update_backlink(old_target_row_ndx, last_row_ndx, target_row_ndx); - } + Array leaf(get_alloc()); + size_t link_ndx = 0; + size_t num_links = root.get_bptree_size(); + while (link_ndx < num_links) { + pair p = root.get_bptree_leaf(link_ndx); + MemRef leaf_mem = p.first; + leaf.init_from_mem(leaf_mem); + cascade_break_backlinks_to__leaf(row_ndx, leaf, state); // Throws + link_ndx += leaf.size(); } +} - // Do the actual delete and move - bool clear_value = false; - destroy_subtree(target_row_ndx, clear_value); - ColumnLinkBase::move_last_over(target_row_ndx, last_row_ndx); - const bool fix_ndx_in_parent = true; - adj_move(target_row_ndx, last_row_ndx); +void ColumnLinkList::cascade_break_backlinks_to__leaf(size_t row_ndx, const Array& link_list_leaf, + CascadeState& state) +{ + size_t target_table_ndx = m_target_table->get_index_in_group(); + + size_t num_links = link_list_leaf.size(); + for (size_t i = 0; i < num_links; ++i) { + size_t target_row_ndx = to_size_t(link_list_leaf.get(i)); + + // Remove the reciprocal backlink at target_row_ndx that points to row_ndx + m_backlink_column->remove_one_backlink(target_row_ndx, row_ndx); + + if (m_weak_links) + continue; + if (m_target_table == state.stop_on_table) + continue; + + // Recurse on target row when appropriate + check_cascade_break_backlinks_to(target_table_ndx, target_row_ndx, state); // Throws + } } -void ColumnLinkList::erase(size_t row_ndx, bool is_last) +void ColumnLinkList::cascade_break_backlinks_to_all_rows(size_t num_rows, CascadeState& state) { - TIGHTDB_ASSERT(row_ndx+1 == size()); - TIGHTDB_ASSERT(is_last); + size_t num_target_rows = m_target_table->size(); + m_backlink_column->remove_all_backlinks(num_target_rows); - // Remove backlinks to the delete row - if (ref_type ref = get_as_ref(row_ndx)) { - Column link_list(get_alloc(), ref); - size_t n = link_list.size(); - for (size_t i = 0; i < n; ++i) { - size_t old_target_row_ndx = to_size_t(link_list.get(i)); - m_backlink_column->remove_backlink(old_target_row_ndx, row_ndx); + if (m_weak_links) + return; + if (m_target_table == state.stop_on_table) + return; + + // Avoid the construction of both a LinkView and a Column instance, since + // both would involve heap allocations. + Array root(get_alloc()), leaf(get_alloc()); + for (size_t i = 0; i < num_rows; ++i) { + ref_type ref = get_as_ref(i); + if (ref == 0) + continue; + root.init_from_ref(ref); + + if (!root.is_inner_bptree_node()) { + cascade_break_backlinks_to_all_rows__leaf(root, state); // Throws + continue; + } + + size_t link_ndx = 0; + size_t num_links = root.get_bptree_size(); + while (link_ndx < num_links) { + pair p = root.get_bptree_leaf(link_ndx); + MemRef leaf_mem = p.first; + leaf.init_from_mem(leaf_mem); + cascade_break_backlinks_to_all_rows__leaf(leaf, state); // Throws + link_ndx += leaf.size(); } } +} - // Do the actual delete - bool clear_value = false; - destroy_subtree(row_ndx, clear_value); - ColumnLinkBase::erase(row_ndx, is_last); - // Detach accessor, if any - typedef list_accessors::iterator iter; - iter end = m_list_accessors.end(); - for (iter i = m_list_accessors.begin(); i != end; ++i) { - if (i->m_row_ndx == row_ndx) { - i->m_list->detach(); - m_list_accessors.erase(i); - break; - } +void ColumnLinkList::cascade_break_backlinks_to_all_rows__leaf(const Array& link_list_leaf, + CascadeState& state) +{ + size_t target_table_ndx = m_target_table->get_index_in_group(); + + size_t num_links = link_list_leaf.size(); + for (size_t i = 0; i < num_links; ++i) { + size_t target_row_ndx = to_size_t(link_list_leaf.get(i)); + + // Recurse on target row when appropriate + check_cascade_break_backlinks_to(target_table_ndx, target_row_ndx, state); // Throws } } @@ -216,13 +284,12 @@ void ColumnLinkList::refresh_accessor_tree(size_t col_ndx, const Spec& spec) } -void ColumnLinkList::adj_accessors_move(size_t target_row_ndx, - size_t source_row_ndx) TIGHTDB_NOEXCEPT +void ColumnLinkList::adj_acc_move_over(size_t from_row_ndx, size_t to_row_ndx) TIGHTDB_NOEXCEPT { - ColumnLinkBase::adj_accessors_move(target_row_ndx, source_row_ndx); + ColumnLinkBase::adj_acc_move_over(from_row_ndx, to_row_ndx); const bool fix_ndx_in_parent = false; - adj_move(target_row_ndx, source_row_ndx); + adj_move_over(from_row_ndx, to_row_ndx); } @@ -233,25 +300,25 @@ void ColumnLinkList::adj_acc_clear_root_table() TIGHTDB_NOEXCEPT } template -void ColumnLinkList::adj_move(size_t target_row_ndx, size_t source_row_ndx) TIGHTDB_NOEXCEPT +void ColumnLinkList::adj_move_over(size_t from_row_ndx, size_t to_row_ndx) TIGHTDB_NOEXCEPT { - size_t i = 0, limit = m_list_accessors.size(); - while (i < limit) { + size_t i = 0, n = m_list_accessors.size(); + while (i < n) { list_entry& e = m_list_accessors[i]; - if (TIGHTDB_UNLIKELY(e.m_row_ndx == target_row_ndx)) { + if (TIGHTDB_UNLIKELY(e.m_row_ndx == to_row_ndx)) { // Must hold a counted reference while detaching LinkViewRef list(e.m_list); list->detach(); // Delete entry by moving last over (faster and avoids invalidating // iterators) - e = m_list_accessors[--limit]; + e = m_list_accessors[--n]; m_list_accessors.pop_back(); } else { - if (TIGHTDB_UNLIKELY(e.m_row_ndx == source_row_ndx)) { - e.m_row_ndx = target_row_ndx; + if (TIGHTDB_UNLIKELY(e.m_row_ndx == from_row_ndx)) { + e.m_row_ndx = to_row_ndx; if (fix_ndx_in_parent) - e.m_list->set_origin_row_index(target_row_ndx); + e.m_list->set_origin_row_index(to_row_ndx); } ++i; } diff --git a/src/tightdb/column_linklist.hpp b/src/tightdb/column_linklist.hpp index eb4317b9ec4..172bb016d36 100644 --- a/src/tightdb/column_linklist.hpp +++ b/src/tightdb/column_linklist.hpp @@ -53,21 +53,19 @@ class ColumnLinkList: public ColumnLinkBase, public ArrayParent { ConstLinkViewRef get(std::size_t row_ndx) const; LinkViewRef get(std::size_t row_ndx); - void erase(std::size_t, bool) TIGHTDB_OVERRIDE; - void move_last_over(std::size_t, std::size_t) TIGHTDB_OVERRIDE; - void clear() TIGHTDB_OVERRIDE; - /// Compare two columns for equality. bool compare_link_list(const ColumnLinkList&) const; void to_json_row(std::size_t row_ndx, std::ostream& out) const; - void refresh_accessor_tree(std::size_t, const Spec&) TIGHTDB_OVERRIDE; - - void adj_accessors_move(std::size_t, std::size_t) TIGHTDB_NOEXCEPT TIGHTDB_OVERRIDE; - void adj_acc_clear_root_table() TIGHTDB_NOEXCEPT TIGHTDB_OVERRIDE; - + void move_last_over(std::size_t, std::size_t, bool) TIGHTDB_OVERRIDE; + void clear(std::size_t, bool) TIGHTDB_OVERRIDE; + void cascade_break_backlinks_to(std::size_t, CascadeState&) TIGHTDB_OVERRIDE; + void cascade_break_backlinks_to_all_rows(std::size_t, CascadeState&) TIGHTDB_OVERRIDE; void update_from_parent(std::size_t) TIGHTDB_NOEXCEPT TIGHTDB_OVERRIDE; + void adj_acc_move_over(std::size_t, std::size_t) TIGHTDB_NOEXCEPT TIGHTDB_OVERRIDE; + void adj_acc_clear_root_table() TIGHTDB_NOEXCEPT TIGHTDB_OVERRIDE; + void refresh_accessor_tree(std::size_t, const Spec&) TIGHTDB_OVERRIDE; #ifdef TIGHTDB_DEBUG void Verify() const TIGHTDB_OVERRIDE; @@ -107,10 +105,17 @@ class ColumnLinkList: public ColumnLinkBase, public ArrayParent { void update_child_ref(std::size_t child_ndx, ref_type new_ref) TIGHTDB_OVERRIDE; ref_type get_child_ref(std::size_t child_ndx) const TIGHTDB_NOEXCEPT TIGHTDB_OVERRIDE; + // These helpers are needed because of the way the B+-tree of links is + // traversed in cascade_break_backlinks_to() and + // cascade_break_backlinks_to_all_rows(). + void cascade_break_backlinks_to__leaf(std::size_t row_ndx, const Array& link_list_leaf, + CascadeState&); + void cascade_break_backlinks_to_all_rows__leaf(const Array& link_list_leaf, CascadeState&); + void discard_child_accessors() TIGHTDB_NOEXCEPT; template - void adj_move(std::size_t target_row_ndx, std::size_t source_row_ndx) TIGHTDB_NOEXCEPT; + void adj_move_over(std::size_t from_row_ndx, std::size_t to_row_ndx) TIGHTDB_NOEXCEPT; #ifdef TIGHTDB_DEBUG std::pair get_to_dot_parent(std::size_t) const TIGHTDB_OVERRIDE; @@ -197,17 +202,17 @@ inline ref_type ColumnLinkList::get_row_ref(std::size_t row_ndx) const TIGHTDB_N inline void ColumnLinkList::set_row_ref(std::size_t row_ndx, ref_type new_ref) { - ColumnLinkBase::set(row_ndx, new_ref); + ColumnLinkBase::set(row_ndx, new_ref); // Throws } inline void ColumnLinkList::add_backlink(std::size_t target_row, std::size_t source_row) { - m_backlink_column->add_backlink(target_row, source_row); + m_backlink_column->add_backlink(target_row, source_row); // Throws } inline void ColumnLinkList::remove_backlink(std::size_t target_row, std::size_t source_row) { - m_backlink_column->remove_backlink(target_row, source_row); + m_backlink_column->remove_one_backlink(target_row, source_row); // Throws } diff --git a/src/tightdb/column_mixed.cpp b/src/tightdb/column_mixed.cpp index 0f59713f2e2..10c8f7ce0e1 100644 --- a/src/tightdb/column_mixed.cpp +++ b/src/tightdb/column_mixed.cpp @@ -126,17 +126,7 @@ ColumnMixed::MixedColType ColumnMixed::clear_value(size_t row_ndx, MixedColType } -void ColumnMixed::clear() -{ - discard_child_accessors(); - m_types->clear(); // Throws - m_data->clear(); // Throws - if (m_binary_data) - m_binary_data->clear(); // Throws -} - - -void ColumnMixed::erase(size_t row_ndx, bool is_last) +void ColumnMixed::do_erase(size_t row_ndx, bool is_last) { TIGHTDB_ASSERT(row_ndx < m_types->size()); @@ -148,15 +138,29 @@ void ColumnMixed::erase(size_t row_ndx, bool is_last) } -void ColumnMixed::move_last_over(size_t target_row_ndx, size_t last_row_ndx) +void ColumnMixed::do_move_last_over(size_t row_ndx, size_t last_row_ndx) { - TIGHTDB_ASSERT(target_row_ndx < size()); + TIGHTDB_ASSERT(row_ndx <= last_row_ndx); + TIGHTDB_ASSERT(last_row_ndx + 1 == size()); // Remove refs or binary data - clear_value(target_row_ndx, mixcol_Int); // Throws + clear_value(row_ndx, mixcol_Int); // Throws - m_types->move_last_over(target_row_ndx, last_row_ndx); // Throws - m_data->move_last_over(target_row_ndx, last_row_ndx); // Throws + bool broken_reciprocal_backlinks = false; // Value is immaterial for these column types + m_types->move_last_over(row_ndx, last_row_ndx, broken_reciprocal_backlinks); // Throws + m_data->move_last_over(row_ndx, last_row_ndx, broken_reciprocal_backlinks); // Throws +} + + +void ColumnMixed::do_clear(size_t num_rows) +{ + discard_child_accessors(); + bool broken_reciprocal_backlinks = false; // Value is immaterial for these column types + m_types->clear(num_rows, broken_reciprocal_backlinks); // Throws + m_data->clear(num_rows, broken_reciprocal_backlinks); // Throws + if (m_binary_data) { + m_binary_data->clear(); // Throws + } } diff --git a/src/tightdb/column_mixed.hpp b/src/tightdb/column_mixed.hpp index 6fab95d6ec4..6ddff5ac8ce 100644 --- a/src/tightdb/column_mixed.hpp +++ b/src/tightdb/column_mixed.hpp @@ -62,13 +62,6 @@ class ColumnMixed: public ColumnBase { ~ColumnMixed() TIGHTDB_NOEXCEPT TIGHTDB_OVERRIDE; - void adj_accessors_insert_rows(std::size_t, std::size_t) TIGHTDB_NOEXCEPT TIGHTDB_OVERRIDE; - void adj_accessors_erase_row(std::size_t) TIGHTDB_NOEXCEPT TIGHTDB_OVERRIDE; - void adj_accessors_move(std::size_t, std::size_t) TIGHTDB_NOEXCEPT TIGHTDB_OVERRIDE; - void adj_acc_clear_root_table() TIGHTDB_NOEXCEPT TIGHTDB_OVERRIDE; - - void update_from_parent(std::size_t) TIGHTDB_NOEXCEPT TIGHTDB_OVERRIDE; - DataType get_type(std::size_t ndx) const TIGHTDB_NOEXCEPT; std::size_t size() const TIGHTDB_NOEXCEPT { return m_types->size(); } bool is_empty() const TIGHTDB_NOEXCEPT { return size() == 0; } @@ -122,10 +115,9 @@ class ColumnMixed: public ColumnBase { void insert_binary(std::size_t ndx, BinaryData value); void insert_subtable(std::size_t ndx, const Table* value); - void clear() TIGHTDB_OVERRIDE; - void insert(std::size_t, std::size_t, bool) TIGHTDB_OVERRIDE; - void erase(std::size_t, bool) TIGHTDB_OVERRIDE; - void move_last_over(std::size_t, std::size_t) TIGHTDB_OVERRIDE; + void erase(std::size_t row_ndx); + void move_last_over(std::size_t row_ndx); + void clear(); /// Compare two mixed columns for equality. bool compare_mixed(const ColumnMixed&) const; @@ -140,8 +132,16 @@ class ColumnMixed: public ColumnBase { ref_type write(std::size_t, std::size_t, std::size_t, _impl::OutputStream&) const TIGHTDB_OVERRIDE; + void insert(std::size_t, std::size_t, bool) TIGHTDB_OVERRIDE; + void erase(std::size_t, bool) TIGHTDB_OVERRIDE; + void move_last_over(std::size_t, std::size_t, bool) TIGHTDB_OVERRIDE; + void clear(std::size_t, bool) TIGHTDB_OVERRIDE; + void update_from_parent(std::size_t) TIGHTDB_NOEXCEPT TIGHTDB_OVERRIDE; + void adj_acc_insert_rows(std::size_t, std::size_t) TIGHTDB_NOEXCEPT TIGHTDB_OVERRIDE; + void adj_acc_erase_row(std::size_t) TIGHTDB_NOEXCEPT TIGHTDB_OVERRIDE; + void adj_acc_move_over(std::size_t, std::size_t) TIGHTDB_NOEXCEPT TIGHTDB_OVERRIDE; + void adj_acc_clear_root_table() TIGHTDB_NOEXCEPT TIGHTDB_OVERRIDE; void mark(int) TIGHTDB_NOEXCEPT TIGHTDB_OVERRIDE; - void refresh_accessor_tree(std::size_t, const Spec&) TIGHTDB_OVERRIDE; #ifdef TIGHTDB_DEBUG @@ -190,6 +190,10 @@ class ColumnMixed: public ColumnBase { std::size_t do_get_size() const TIGHTDB_NOEXCEPT TIGHTDB_OVERRIDE { return size(); } + void do_erase(std::size_t row_ndx, bool is_last); + void do_move_last_over(std::size_t row_ndx, std::size_t last_row_ndx); + void do_clear(std::size_t num_rows); + void create(Allocator&, ref_type, Table*, std::size_t column_ndx); void ensure_binary_data_column(); diff --git a/src/tightdb/column_mixed_tpl.hpp b/src/tightdb/column_mixed_tpl.hpp index 8e8572dfadf..f8b78ca2669 100644 --- a/src/tightdb/column_mixed_tpl.hpp +++ b/src/tightdb/column_mixed_tpl.hpp @@ -26,21 +26,21 @@ inline ColumnMixed::ColumnMixed(Allocator& alloc, ref_type ref, create(alloc, ref, table, column_ndx); } -inline void ColumnMixed::adj_accessors_insert_rows(std::size_t row_ndx, - std::size_t num_rows) TIGHTDB_NOEXCEPT +inline void ColumnMixed::adj_acc_insert_rows(std::size_t row_ndx, + std::size_t num_rows) TIGHTDB_NOEXCEPT { - m_data->adj_accessors_insert_rows(row_ndx, num_rows); + m_data->adj_acc_insert_rows(row_ndx, num_rows); } -inline void ColumnMixed::adj_accessors_erase_row(std::size_t row_ndx) TIGHTDB_NOEXCEPT +inline void ColumnMixed::adj_acc_erase_row(std::size_t row_ndx) TIGHTDB_NOEXCEPT { - m_data->adj_accessors_erase_row(row_ndx); + m_data->adj_acc_erase_row(row_ndx); } -inline void ColumnMixed::adj_accessors_move(std::size_t target_row_ndx, - std::size_t source_row_ndx) TIGHTDB_NOEXCEPT +inline void ColumnMixed::adj_acc_move_over(std::size_t from_row_ndx, + std::size_t to_row_ndx) TIGHTDB_NOEXCEPT { - m_data->adj_accessors_move(target_row_ndx, source_row_ndx); + m_data->adj_acc_move_over(from_row_ndx, to_row_ndx); } inline void ColumnMixed::adj_acc_clear_root_table() TIGHTDB_NOEXCEPT @@ -364,18 +364,23 @@ inline void ColumnMixed::insert_subtable(std::size_t ndx, const Table* t) insert_value(ndx, types_value, data_value); // Throws } -// Implementing pure virtual method of ColumnBase. -inline void ColumnMixed::insert(std::size_t row_ndx, std::size_t num_rows, bool is_append) +inline void ColumnMixed::erase(std::size_t row_ndx) { - std::size_t row_ndx_2 = is_append ? tightdb::npos : row_ndx; + std::size_t last_row_ndx = size() - 1; // Note that size() is slow + bool is_last = row_ndx == last_row_ndx; + do_erase(row_ndx, is_last); // Throws +} - int_fast64_t type_value = mixcol_Int; - m_types->do_insert(row_ndx_2, type_value, num_rows); // Throws +inline void ColumnMixed::move_last_over(std::size_t row_ndx) +{ + std::size_t last_row_ndx = size() - 1; // Note that size() is slow + do_move_last_over(row_ndx, last_row_ndx); // Throws +} - // The least significant bit indicates that the rest of the bits form an - // integer value, so 1 is actually zero. - int_fast64_t data_value = 1; - m_data->do_insert(row_ndx_2, data_value, num_rows); // Throws +inline void ColumnMixed::clear() +{ + std::size_t num_rows = size(); // Note that size() is slow + do_clear(num_rows); // Throws } inline std::size_t ColumnMixed::get_size_from_ref(ref_type root_ref, @@ -393,6 +398,38 @@ inline void ColumnMixed::clear_value_and_discard_subtab_acc(std::size_t row_ndx, m_data->discard_subtable_accessor(row_ndx); } +// Implementing pure virtual method of ColumnBase. +inline void ColumnMixed::insert(std::size_t row_ndx, std::size_t num_rows, bool is_append) +{ + std::size_t row_ndx_2 = is_append ? tightdb::npos : row_ndx; + + int_fast64_t type_value = mixcol_Int; + m_types->do_insert(row_ndx_2, type_value, num_rows); // Throws + + // The least significant bit indicates that the rest of the bits form an + // integer value, so 1 is actually zero. + int_fast64_t data_value = 1; + m_data->do_insert(row_ndx_2, data_value, num_rows); // Throws +} + +// Implementing pure virtual method of ColumnBase. +inline void ColumnMixed::erase(std::size_t row_ndx, bool is_last) +{ + do_erase(row_ndx, is_last); // Throws +} + +// Implementing pure virtual method of ColumnBase. +inline void ColumnMixed::move_last_over(std::size_t row_ndx, std::size_t last_row_ndx, bool) +{ + do_move_last_over(row_ndx, last_row_ndx); // Throws +} + +// Implementing pure virtual method of ColumnBase. +inline void ColumnMixed::clear(std::size_t num_rows, bool) +{ + do_clear(num_rows); // Throws +} + inline void ColumnMixed::mark(int type) TIGHTDB_NOEXCEPT { m_data->mark(type); diff --git a/src/tightdb/column_string.cpp b/src/tightdb/column_string.cpp index a59fd1e6ec0..79bc1e147dd 100644 --- a/src/tightdb/column_string.cpp +++ b/src/tightdb/column_string.cpp @@ -25,8 +25,8 @@ namespace { const size_t small_string_max_size = 15; // ArrayString const size_t medium_string_max_size = 63; // ArrayStringLong -// Getter function for index. For integer index, the caller must supply a buffer that we can store the -// extracted value in (it may be bitpacked, so we cannot return a pointer in to the Array as we do with +// Getter function for index. For integer index, the caller must supply a buffer that we can store the +// extracted value in (it may be bitpacked, so we cannot return a pointer in to the Array as we do with // String index). StringData get_string(void* column, size_t ndx, char*) { @@ -241,50 +241,6 @@ void AdaptiveStringColumn::update_from_parent(std::size_t old_baseline) TIGHTDB_ } -void AdaptiveStringColumn::clear() -{ - if (root_is_leaf()) { - bool long_strings = m_array->has_refs(); - if (!long_strings) { - // Small strings root leaf - ArrayString* leaf = static_cast(m_array); - leaf->clear(); // Throws - } - else { - bool is_big = m_array->get_context_flag(); - if (!is_big) { - // Medium strings root leaf - ArrayStringLong* leaf = static_cast(m_array); - leaf->clear(); // Throws - } - else { - // Big strings root leaf - ArrayBigBlobs* leaf = static_cast(m_array); - leaf->clear(); // Throws - } - } - } - else { - // Non-leaf root - revert to small strings leaf - Allocator& alloc = m_array->get_alloc(); - UniquePtr array; - array.reset(new ArrayString(alloc)); // Throws - array->create(); // Throws - array->set_parent(m_array->get_parent(), m_array->get_ndx_in_parent()); - array->update_parent(); // Throws - - // Remove original node - m_array->destroy_deep(); - delete m_array; - - m_array = array.release(); - } - - if (m_search_index) - m_search_index->clear(); // Throws -} - - namespace { class SetLeafElem: public Array::UpdateHandler { @@ -494,7 +450,7 @@ class AdaptiveStringColumn::EraseLeafElem: public ColumnBase::EraseHandlerBase { } }; -void AdaptiveStringColumn::erase(size_t ndx, bool is_last) +void AdaptiveStringColumn::do_erase(size_t ndx, bool is_last) { TIGHTDB_ASSERT(ndx < size()); TIGHTDB_ASSERT(is_last == (ndx == size()-1)); @@ -536,8 +492,11 @@ void AdaptiveStringColumn::erase(size_t ndx, bool is_last) } -void AdaptiveStringColumn::move_last_over(size_t target_row_ndx, size_t last_row_ndx) +void AdaptiveStringColumn::do_move_last_over(size_t row_ndx, size_t last_row_ndx) { + TIGHTDB_ASSERT(row_ndx <= last_row_ndx); + TIGHTDB_ASSERT(last_row_ndx + 1 == size()); + // FIXME: ExceptionSafety: The current implementation of this // function is not exception-safe, and it is hard to see how to // repair it. @@ -550,9 +509,6 @@ void AdaptiveStringColumn::move_last_over(size_t target_row_ndx, size_t last_row // that avoids the intermediate copy. This approach is also likely // to be necesseray for exception safety. - TIGHTDB_ASSERT(target_row_ndx < last_row_ndx); - TIGHTDB_ASSERT(last_row_ndx + 1 == size()); - StringData value = get(last_row_ndx); // Copying string data from a column to itself requires an @@ -564,10 +520,11 @@ void AdaptiveStringColumn::move_last_over(size_t target_row_ndx, size_t last_row if (m_search_index) { // remove the value to be overwritten from index bool is_last = true; // This tells StringIndex::erase() to not adjust subsequent indexes - m_search_index->erase(target_row_ndx, is_last); // Throws + m_search_index->erase(row_ndx, is_last); // Throws // update index to point to new location - m_search_index->update_ref(copy_of_value, last_row_ndx, target_row_ndx); // Throws + if (row_ndx != last_row_ndx) + m_search_index->update_ref(copy_of_value, last_row_ndx, row_ndx); // Throws } bool root_is_leaf = !m_array->is_inner_bptree_node(); @@ -576,7 +533,7 @@ void AdaptiveStringColumn::move_last_over(size_t target_row_ndx, size_t last_row if (!long_strings) { // Small strings root leaf ArrayString* leaf = static_cast(m_array); - leaf->set(target_row_ndx, copy_of_value); // Throws + leaf->set(row_ndx, copy_of_value); // Throws leaf->erase(last_row_ndx); // Throws return; } @@ -584,25 +541,69 @@ void AdaptiveStringColumn::move_last_over(size_t target_row_ndx, size_t last_row if (!is_big) { // Medium strings root leaf ArrayStringLong* leaf = static_cast(m_array); - leaf->set(target_row_ndx, copy_of_value); // Throws + leaf->set(row_ndx, copy_of_value); // Throws leaf->erase(last_row_ndx); // Throws return; } // Big strings root leaf ArrayBigBlobs* leaf = static_cast(m_array); - leaf->set_string(target_row_ndx, copy_of_value); // Throws + leaf->set_string(row_ndx, copy_of_value); // Throws leaf->erase(last_row_ndx); // Throws return; } // Non-leaf root SetLeafElem set_leaf_elem(m_array->get_alloc(), copy_of_value); - m_array->update_bptree_elem(target_row_ndx, set_leaf_elem); // Throws + m_array->update_bptree_elem(row_ndx, set_leaf_elem); // Throws EraseLeafElem erase_leaf_elem(*this); Array::erase_bptree_elem(m_array, tightdb::npos, erase_leaf_elem); // Throws } +void AdaptiveStringColumn::do_clear() +{ + if (root_is_leaf()) { + bool long_strings = m_array->has_refs(); + if (!long_strings) { + // Small strings root leaf + ArrayString* leaf = static_cast(m_array); + leaf->clear(); // Throws + } + else { + bool is_big = m_array->get_context_flag(); + if (!is_big) { + // Medium strings root leaf + ArrayStringLong* leaf = static_cast(m_array); + leaf->clear(); // Throws + } + else { + // Big strings root leaf + ArrayBigBlobs* leaf = static_cast(m_array); + leaf->clear(); // Throws + } + } + } + else { + // Non-leaf root - revert to small strings leaf + Allocator& alloc = m_array->get_alloc(); + UniquePtr array; + array.reset(new ArrayString(alloc)); // Throws + array->create(); // Throws + array->set_parent(m_array->get_parent(), m_array->get_ndx_in_parent()); + array->update_parent(); // Throws + + // Remove original node + m_array->destroy_deep(); + delete m_array; + + m_array = array.release(); + } + + if (m_search_index) + m_search_index->clear(); // Throws +} + + size_t AdaptiveStringColumn::count(StringData value) const { if (m_search_index) diff --git a/src/tightdb/column_string.hpp b/src/tightdb/column_string.hpp index 9372c01775b..896eb049b08 100644 --- a/src/tightdb/column_string.hpp +++ b/src/tightdb/column_string.hpp @@ -60,11 +60,9 @@ class AdaptiveStringColumn: public ColumnBase, public ColumnTemplate void set(std::size_t ndx, StringData); void add(StringData value = StringData()); void insert(std::size_t ndx, StringData value = StringData()); - - void insert(std::size_t, std::size_t, bool) TIGHTDB_OVERRIDE; - void erase(std::size_t ndx, bool is_last) TIGHTDB_OVERRIDE; - void move_last_over(std::size_t, std::size_t) TIGHTDB_OVERRIDE; - void clear() TIGHTDB_OVERRIDE; + void erase(std::size_t row_ndx); + void move_last_over(std::size_t row_ndx); + void clear(); std::size_t count(StringData value) const; std::size_t find_first(StringData value, std::size_t begin = 0, @@ -118,10 +116,13 @@ class AdaptiveStringColumn: public ColumnBase, public ColumnTemplate ref_type write(std::size_t, std::size_t, std::size_t, _impl::OutputStream&) const TIGHTDB_OVERRIDE; - void update_from_parent(std::size_t old_baseline) TIGHTDB_NOEXCEPT TIGHTDB_OVERRIDE; - bool is_string_col() const TIGHTDB_NOEXCEPT TIGHTDB_OVERRIDE; + void insert(std::size_t, std::size_t, bool) TIGHTDB_OVERRIDE; + void erase(std::size_t, bool) TIGHTDB_OVERRIDE; + void move_last_over(std::size_t, std::size_t, bool) TIGHTDB_OVERRIDE; + void clear(std::size_t, bool) TIGHTDB_OVERRIDE; + void update_from_parent(std::size_t old_baseline) TIGHTDB_NOEXCEPT TIGHTDB_OVERRIDE; void refresh_accessor_tree(std::size_t, const Spec&) TIGHTDB_OVERRIDE; #ifdef TIGHTDB_DEBUG @@ -166,6 +167,10 @@ class AdaptiveStringColumn: public ColumnBase, public ColumnTemplate class CreateHandler; class SliceHandler; + void do_erase(std::size_t row_ndx, bool is_last); + void do_move_last_over(std::size_t row_ndx, std::size_t last_row_ndx); + void do_clear(); + /// Root must be a leaf. Upgrades the root leaf as /// necessary. Returns the type of the root leaf as it is upon /// return. @@ -227,11 +232,22 @@ inline void AdaptiveStringColumn::insert(std::size_t row_ndx, StringData value) do_insert(row_ndx, value, num_rows, is_append); // Throws } -// Implementing pure virtual method of ColumnBase. -inline void AdaptiveStringColumn::insert(std::size_t row_ndx, std::size_t num_rows, bool is_append) +inline void AdaptiveStringColumn::erase(std::size_t row_ndx) { - StringData value = StringData(); - do_insert(row_ndx, value, num_rows, is_append); // Throws + std::size_t last_row_ndx = size() - 1; // Note that size() is slow + bool is_last = row_ndx == last_row_ndx; + do_erase(row_ndx, is_last); // Throws +} + +inline void AdaptiveStringColumn::move_last_over(std::size_t row_ndx) +{ + std::size_t last_row_ndx = size() - 1; // Note that size() is slow + do_move_last_over(row_ndx, last_row_ndx); // Throws +} + +inline void AdaptiveStringColumn::clear() +{ + do_clear(); // Throws } inline int AdaptiveStringColumn::compare_values(std::size_t row1, std::size_t row2) const @@ -297,6 +313,32 @@ inline bool AdaptiveStringColumn::is_string_col() const TIGHTDB_NOEXCEPT return true; } +// Implementing pure virtual method of ColumnBase. +inline void AdaptiveStringColumn::insert(std::size_t row_ndx, std::size_t num_rows, bool is_append) +{ + StringData value = StringData(); + do_insert(row_ndx, value, num_rows, is_append); // Throws +} + +// Implementing pure virtual method of ColumnBase. +inline void AdaptiveStringColumn::erase(std::size_t row_ndx, bool is_last) +{ + do_erase(row_ndx, is_last); // Throws +} + +// Implementing pure virtual method of ColumnBase. +inline void AdaptiveStringColumn::move_last_over(std::size_t row_ndx, std::size_t last_row_ndx, + bool) +{ + do_move_last_over(row_ndx, last_row_ndx); // Throws +} + +// Implementing pure virtual method of ColumnBase. +inline void AdaptiveStringColumn::clear(std::size_t, bool) +{ + do_clear(); // Throws +} + } // namespace tightdb #endif // TIGHTDB_COLUMN_STRING_HPP diff --git a/src/tightdb/column_string_enum.cpp b/src/tightdb/column_string_enum.cpp index 1b0da2ec7a2..01b978390e8 100644 --- a/src/tightdb/column_string_enum.cpp +++ b/src/tightdb/column_string_enum.cpp @@ -101,7 +101,7 @@ void ColumnStringEnum::do_insert(size_t row_ndx, StringData value, size_t num_ro } -void ColumnStringEnum::erase(size_t ndx, bool is_last) +void ColumnStringEnum::do_erase(size_t ndx, bool is_last) { TIGHTDB_ASSERT(ndx < Column::size()); @@ -109,37 +109,38 @@ void ColumnStringEnum::erase(size_t ndx, bool is_last) // (it is important here that we do it before actually setting // the value, or the index would not be able to find the correct // position to update (as it looks for the old value)) - if (m_search_index) { + if (m_search_index) m_search_index->erase(ndx, is_last); - } - Column::erase(ndx, is_last); + Column::do_erase(ndx, is_last); } -void ColumnStringEnum::move_last_over(size_t target_row_ndx, size_t last_row_ndx) +void ColumnStringEnum::do_move_last_over(size_t row_ndx, size_t last_row_ndx) { - TIGHTDB_ASSERT(target_row_ndx < last_row_ndx); + TIGHTDB_ASSERT(row_ndx <= last_row_ndx); TIGHTDB_ASSERT(last_row_ndx + 1 == size()); if (m_search_index) { // remove the value to be overwritten from index bool is_last = true; // This tells StringIndex::erase() to not adjust subsequent indexes - m_search_index->erase(target_row_ndx, is_last); // Throws + m_search_index->erase(row_ndx, is_last); // Throws // update index to point to new location - StringData moved_value = get(last_row_ndx); - m_search_index->update_ref(moved_value, last_row_ndx, target_row_ndx); // Throws + if (row_ndx != last_row_ndx) { + StringData moved_value = get(last_row_ndx); + m_search_index->update_ref(moved_value, last_row_ndx, row_ndx); // Throws + } } - Column::move_last_over(target_row_ndx, last_row_ndx); // Throws + Column::do_move_last_over(row_ndx, last_row_ndx); // Throws } -void ColumnStringEnum::clear() +void ColumnStringEnum::do_clear() { // Note that clearing a StringEnum does not remove keys - Column::clear(); + Column::do_clear(); if (m_search_index) m_search_index->clear(); diff --git a/src/tightdb/column_string_enum.hpp b/src/tightdb/column_string_enum.hpp index 5a6075fb2ff..01fcc0d64c6 100644 --- a/src/tightdb/column_string_enum.hpp +++ b/src/tightdb/column_string_enum.hpp @@ -70,11 +70,9 @@ class ColumnStringEnum: public Column { void set(std::size_t ndx, StringData value); void add(StringData value = StringData()); void insert(std::size_t ndx, StringData value = StringData()); - - void insert(std::size_t, std::size_t, bool) TIGHTDB_OVERRIDE; - void erase(std::size_t ndx, bool is_last) TIGHTDB_OVERRIDE; - void move_last_over(std::size_t, std::size_t) TIGHTDB_OVERRIDE; - void clear() TIGHTDB_OVERRIDE; + void erase(std::size_t row_ndx); + void move_last_over(std::size_t row_ndx); + void clear(); std::size_t count(StringData value) const; std::size_t find_first(StringData value, std::size_t begin = 0, std::size_t end = npos) const; @@ -97,7 +95,6 @@ class ColumnStringEnum: public Column { void set_string(std::size_t, StringData) TIGHTDB_OVERRIDE; void adjust_keys_ndx_in_parent(int diff) TIGHTDB_NOEXCEPT; - void update_from_parent(std::size_t old_baseline) TIGHTDB_NOEXCEPT TIGHTDB_OVERRIDE; // Search index bool has_search_index() const TIGHTDB_NOEXCEPT TIGHTDB_OVERRIDE; @@ -112,8 +109,19 @@ class ColumnStringEnum: public Column { bool compare_string(const AdaptiveStringColumn&) const; bool compare_string(const ColumnStringEnum&) const; + void insert(std::size_t, std::size_t, bool) TIGHTDB_OVERRIDE; + void erase(std::size_t, bool) TIGHTDB_OVERRIDE; + void move_last_over(std::size_t, std::size_t, bool) TIGHTDB_OVERRIDE; + void clear(std::size_t, bool) TIGHTDB_OVERRIDE; + void update_from_parent(std::size_t) TIGHTDB_NOEXCEPT TIGHTDB_OVERRIDE; void refresh_accessor_tree(std::size_t, const Spec&) TIGHTDB_OVERRIDE; + std::size_t GetKeyNdx(StringData value) const; + std::size_t GetKeyNdxOrAdd(StringData value); + + AdaptiveStringColumn& get_keys(); + const AdaptiveStringColumn& get_keys() const; + #ifdef TIGHTDB_DEBUG void Verify() const TIGHTDB_OVERRIDE; void Verify(const Table&, std::size_t) const TIGHTDB_OVERRIDE; @@ -121,12 +129,6 @@ class ColumnStringEnum: public Column { void to_dot(std::ostream&, StringData title) const TIGHTDB_OVERRIDE; #endif - std::size_t GetKeyNdx(StringData value) const; - std::size_t GetKeyNdxOrAdd(StringData value); - - AdaptiveStringColumn& get_keys(); - const AdaptiveStringColumn& get_keys() const; - private: // Member variables AdaptiveStringColumn m_keys; @@ -146,6 +148,10 @@ class ColumnStringEnum: public Column { /// \param is_append Must be true if, and only if `row_ndx` is equal to the /// size of the column (before insertion). void do_insert(std::size_t row_ndx, StringData value, std::size_t num_rows, bool is_append); + + void do_erase(std::size_t row_ndx, bool is_last); + void do_move_last_over(std::size_t row_ndx, std::size_t last_row_ndx); + void do_clear(); }; @@ -177,6 +183,24 @@ inline void ColumnStringEnum::insert(std::size_t row_ndx, StringData value) do_insert(row_ndx, value, num_rows, is_append); // Throws } +inline void ColumnStringEnum::erase(std::size_t row_ndx) +{ + std::size_t last_row_ndx = size() - 1; // Note that size() is slow + bool is_last = row_ndx == last_row_ndx; + do_erase(row_ndx, is_last); // Throws +} + +inline void ColumnStringEnum::move_last_over(std::size_t row_ndx) +{ + std::size_t last_row_ndx = size() - 1; // Note that size() is slow + do_move_last_over(row_ndx, last_row_ndx); // Throws +} + +inline void ColumnStringEnum::clear() +{ + do_clear(); // Throws +} + // Overriding virtual method of Column. inline void ColumnStringEnum::insert(std::size_t row_ndx, std::size_t num_rows, bool is_append) { @@ -184,6 +208,24 @@ inline void ColumnStringEnum::insert(std::size_t row_ndx, std::size_t num_rows, do_insert(row_ndx, value, num_rows, is_append); // Throws } +// Overriding virtual method of Column. +inline void ColumnStringEnum::erase(std::size_t row_ndx, bool is_last) +{ + do_erase(row_ndx, is_last); // Throws +} + +// Overriding virtual method of Column. +inline void ColumnStringEnum::move_last_over(std::size_t row_ndx, std::size_t last_row_ndx, bool) +{ + do_move_last_over(row_ndx, last_row_ndx); // Throws +} + +// Overriding virtual method of Column. +inline void ColumnStringEnum::clear(std::size_t, bool) +{ + do_clear(); // Throws +} + inline std::size_t ColumnStringEnum::lower_bound_string(StringData value) const TIGHTDB_NOEXCEPT { return ColumnBase::lower_bound(*this, value); diff --git a/src/tightdb/column_table.cpp b/src/tightdb/column_table.cpp index 88d4225fc25..502f89f76a5 100644 --- a/src/tightdb/column_table.cpp +++ b/src/tightdb/column_table.cpp @@ -7,16 +7,6 @@ using namespace std; using namespace tightdb; using namespace tightdb::util; - -void ColumnSubtableParent::clear() -{ - discard_child_accessors(); - Column::clear(); // Throws - // FIXME: This one is needed because Column::clear() forgets about the leaf - // type. A better solution should probably be sought after. - m_array->set_type(Array::type_HasRefs); -} - void ColumnSubtableParent::update_from_parent(size_t old_baseline) TIGHTDB_NOEXCEPT { if (!m_array->update_from_parent(old_baseline)) @@ -350,11 +340,14 @@ void ColumnTable::erase(size_t row_ndx, bool is_last) } -void ColumnTable::move_last_over(size_t target_row_ndx, size_t last_row_ndx) +void ColumnTable::move_last_over(size_t row_ndx, size_t last_row_ndx, + bool broken_reciprocal_backlinks) { - TIGHTDB_ASSERT(target_row_ndx < size()); - destroy_subtable(target_row_ndx); - ColumnSubtableParent::move_last_over(target_row_ndx, last_row_ndx); // Throws + TIGHTDB_ASSERT(row_ndx <= last_row_ndx); + TIGHTDB_ASSERT(last_row_ndx + 1 == size()); + destroy_subtable(row_ndx); + ColumnSubtableParent::move_last_over(row_ndx, last_row_ndx, + broken_reciprocal_backlinks); // Throws } diff --git a/src/tightdb/column_table.hpp b/src/tightdb/column_table.hpp index f0f81226e43..48ceb559895 100644 --- a/src/tightdb/column_table.hpp +++ b/src/tightdb/column_table.hpp @@ -33,22 +33,6 @@ namespace tightdb { /// Base class for any type of column that can contain subtables. class ColumnSubtableParent: public Column, public Table::Parent { public: - void insert(std::size_t, std::size_t, bool) TIGHTDB_OVERRIDE; - void erase(std::size_t, bool) TIGHTDB_OVERRIDE; - void move_last_over(std::size_t, std::size_t) TIGHTDB_OVERRIDE; - void clear() TIGHTDB_OVERRIDE; - - void mark(int) TIGHTDB_NOEXCEPT TIGHTDB_OVERRIDE; - - void adj_accessors_insert_rows(std::size_t, std::size_t) TIGHTDB_NOEXCEPT TIGHTDB_OVERRIDE; - void adj_accessors_erase_row(std::size_t) TIGHTDB_NOEXCEPT TIGHTDB_OVERRIDE; - void adj_accessors_move(std::size_t, std::size_t) TIGHTDB_NOEXCEPT TIGHTDB_OVERRIDE; - void adj_acc_clear_root_table() TIGHTDB_NOEXCEPT TIGHTDB_OVERRIDE; - - void refresh_accessor_tree(std::size_t, const Spec&) TIGHTDB_OVERRIDE; - - void update_from_parent(std::size_t) TIGHTDB_NOEXCEPT TIGHTDB_OVERRIDE; - void discard_child_accessors() TIGHTDB_NOEXCEPT; ~ColumnSubtableParent() TIGHTDB_NOEXCEPT TIGHTDB_OVERRIDE; @@ -57,7 +41,18 @@ class ColumnSubtableParent: public Column, public Table::Parent { Table* get_subtable_accessor(std::size_t) const TIGHTDB_NOEXCEPT TIGHTDB_OVERRIDE; + void insert(std::size_t, std::size_t, bool) TIGHTDB_OVERRIDE; + void erase(std::size_t, bool) TIGHTDB_OVERRIDE; + void move_last_over(std::size_t, std::size_t, bool) TIGHTDB_OVERRIDE; + void clear(std::size_t, bool) TIGHTDB_OVERRIDE; void discard_subtable_accessor(std::size_t) TIGHTDB_NOEXCEPT TIGHTDB_OVERRIDE; + void update_from_parent(std::size_t) TIGHTDB_NOEXCEPT TIGHTDB_OVERRIDE; + void adj_acc_insert_rows(std::size_t, std::size_t) TIGHTDB_NOEXCEPT TIGHTDB_OVERRIDE; + void adj_acc_erase_row(std::size_t) TIGHTDB_NOEXCEPT TIGHTDB_OVERRIDE; + void adj_acc_move_over(std::size_t, std::size_t) TIGHTDB_NOEXCEPT TIGHTDB_OVERRIDE; + void adj_acc_clear_root_table() TIGHTDB_NOEXCEPT TIGHTDB_OVERRIDE; + void mark(int) TIGHTDB_NOEXCEPT TIGHTDB_OVERRIDE; + void refresh_accessor_tree(std::size_t, const Spec&) TIGHTDB_OVERRIDE; #ifdef TIGHTDB_DEBUG void Verify() const TIGHTDB_OVERRIDE; @@ -96,7 +91,7 @@ class ColumnSubtableParent: public Column, public Table::Parent { // Returns true if, and only if an entry was found and removed, and it // was the last entry in the map. template - bool adj_move(std::size_t target_row_ndx, std::size_t source_row_ndx) + bool adj_move_over(std::size_t from_row_ndx, std::size_t to_row_ndx) TIGHTDB_NOEXCEPT; void update_accessors(const std::size_t* col_path_begin, const std::size_t* col_path_end, _impl::TableFriend::AccessorUpdater&); @@ -215,7 +210,7 @@ class ColumnTable: public ColumnSubtableParent { using ColumnSubtableParent::insert; void erase(std::size_t, bool) TIGHTDB_OVERRIDE; - void move_last_over(std::size_t, std::size_t) TIGHTDB_OVERRIDE; + void move_last_over(std::size_t, std::size_t, bool) TIGHTDB_OVERRIDE; /// Compare two subtable columns for equality. bool compare_table(const ColumnTable&) const; @@ -254,7 +249,7 @@ inline void ColumnSubtableParent::insert(std::size_t row_ndx, std::size_t num_ro inline void ColumnSubtableParent::erase(std::size_t row_ndx, bool is_last) { - Column::erase(row_ndx, is_last); // Throws + do_erase(row_ndx, is_last); // Throws const bool fix_ndx_in_parent = true; bool last_entry_removed = m_subtable_map.adj_erase_row(row_ndx); @@ -263,19 +258,28 @@ inline void ColumnSubtableParent::erase(std::size_t row_ndx, bool is_last) tf::unbind_ref(*m_table); } -inline void ColumnSubtableParent::move_last_over(std::size_t target_row_ndx, - std::size_t last_row_ndx) +inline void ColumnSubtableParent::move_last_over(std::size_t row_ndx, std::size_t last_row_ndx, + bool) { - Column::move_last_over(target_row_ndx, last_row_ndx); // Throws + do_move_last_over(row_ndx, last_row_ndx); // Throws const bool fix_ndx_in_parent = true; bool last_entry_removed = - m_subtable_map.adj_move(target_row_ndx, last_row_ndx); + m_subtable_map.adj_move_over(last_row_ndx, row_ndx); typedef _impl::TableFriend tf; if (last_entry_removed) tf::unbind_ref(*m_table); } +inline void ColumnSubtableParent::clear(std::size_t, bool) +{ + discard_child_accessors(); + do_clear(); // Throws + // FIXME: This one is needed because Column::do_clear() forgets about the + // leaf type. A better solution should probably be sought after. + m_array->set_type(Array::type_HasRefs); +} + inline void ColumnSubtableParent::mark(int type) TIGHTDB_NOEXCEPT { if (type & mark_Recursive) @@ -288,8 +292,8 @@ inline void ColumnSubtableParent::refresh_accessor_tree(std::size_t col_ndx, con m_column_ndx = col_ndx; } -inline void ColumnSubtableParent::adj_accessors_insert_rows(std::size_t row_ndx, - std::size_t num_rows) TIGHTDB_NOEXCEPT +inline void ColumnSubtableParent::adj_acc_insert_rows(std::size_t row_ndx, + std::size_t num_rows) TIGHTDB_NOEXCEPT { // This function must assume no more than minimal consistency of the // accessor hierarchy. This means in particular that it cannot access the @@ -299,7 +303,7 @@ inline void ColumnSubtableParent::adj_accessors_insert_rows(std::size_t row_ndx, m_subtable_map.adj_insert_rows(row_ndx, num_rows); } -inline void ColumnSubtableParent::adj_accessors_erase_row(std::size_t row_ndx) TIGHTDB_NOEXCEPT +inline void ColumnSubtableParent::adj_acc_erase_row(std::size_t row_ndx) TIGHTDB_NOEXCEPT { // This function must assume no more than minimal consistency of the // accessor hierarchy. This means in particular that it cannot access the @@ -312,8 +316,8 @@ inline void ColumnSubtableParent::adj_accessors_erase_row(std::size_t row_ndx) T tf::unbind_ref(*m_table); } -inline void ColumnSubtableParent::adj_accessors_move(std::size_t target_row_ndx, std::size_t source_row_ndx) - TIGHTDB_NOEXCEPT +inline void ColumnSubtableParent::adj_acc_move_over(std::size_t from_row_ndx, + std::size_t to_row_ndx) TIGHTDB_NOEXCEPT { // This function must assume no more than minimal consistency of the // accessor hierarchy. This means in particular that it cannot access the @@ -321,7 +325,7 @@ inline void ColumnSubtableParent::adj_accessors_move(std::size_t target_row_ndx, const bool fix_ndx_in_parent = false; bool last_entry_removed = - m_subtable_map.adj_move(target_row_ndx, source_row_ndx); + m_subtable_map.adj_move_over(from_row_ndx, to_row_ndx); typedef _impl::TableFriend tf; if (last_entry_removed) tf::unbind_ref(*m_table); @@ -416,36 +420,33 @@ bool ColumnSubtableParent::SubtableMap::adj_erase_row(std::size_t row_ndx) TIGHT template -bool ColumnSubtableParent::SubtableMap::adj_move(std::size_t target_row_ndx, - std::size_t source_row_ndx) TIGHTDB_NOEXCEPT +bool ColumnSubtableParent::SubtableMap::adj_move_over(std::size_t from_row_ndx, + std::size_t to_row_ndx) TIGHTDB_NOEXCEPT { typedef _impl::TableFriend tf; - std::size_t i = 0, limit = m_entries.size(); - // We return true if and only if we remove the last entry in the map. - // We need a special handling for the case, where the set of entries are already empty, - // otherwise the final return statement would also return true in this case, even - // though we didn't actually remove an entry. - if (i == limit) + std::size_t i = 0, n = m_entries.size(); + // We return true if, and only if we remove the last entry in the map. We + // need special handling for the case, where the set of entries are already + // empty, otherwise the final return statement would return true in this + // case, even though we didn't actually remove an entry. + if (i == n) return false; - while (i < limit) { - + while (i < n) { entry& e = m_entries[i]; - if (TIGHTDB_UNLIKELY(e.m_subtable_ndx == target_row_ndx)) { - + if (TIGHTDB_UNLIKELY(e.m_subtable_ndx == to_row_ndx)) { // Must hold a counted reference while detaching TableRef table(e.m_table); tf::detach(*table); // Delete entry by moving last over (faster and avoids invalidating // iterators) - e = m_entries[--limit]; + e = m_entries[--n]; m_entries.pop_back(); } else { - if (TIGHTDB_UNLIKELY(e.m_subtable_ndx == source_row_ndx)) { - - e.m_subtable_ndx = target_row_ndx; + if (TIGHTDB_UNLIKELY(e.m_subtable_ndx == from_row_ndx)) { + e.m_subtable_ndx = to_row_ndx; if (fix_ndx_in_parent) tf::set_ndx_in_parent(*(e.m_table), e.m_subtable_ndx); } diff --git a/src/tightdb/column_type.hpp b/src/tightdb/column_type.hpp index cd8acb934bc..60efbb6b146 100644 --- a/src/tightdb/column_type.hpp +++ b/src/tightdb/column_type.hpp @@ -50,13 +50,17 @@ enum ColumnAttr { col_attr_None = 0, col_attr_Indexed = 1, - // Specifies that this column forms a unique constraint. It requires - // `col_attr_Indexed`. + /// Specifies that this column forms a unique constraint. It requires + /// `col_attr_Indexed`. col_attr_Unique = 2, - // Specifies that this column forms the primary key. It implies a non-null - // constraint on the column, and it requires `col_attr_Unique`. - col_attr_PrimaryKey = 4 + /// Specifies that this column forms the primary key. It implies a non-null + /// constraint on the column, and it requires `col_attr_Unique`. + col_attr_PrimaryKey = 4, + + /// Specifies that the links of this column are strong, not weak. Applies + /// only to link columns (`type_Link` and `type_LinkList`). + col_attr_StrongLinks = 8 }; diff --git a/src/tightdb/data_type.hpp b/src/tightdb/data_type.hpp index 5cb32f868f8..ecb8eb54208 100644 --- a/src/tightdb/data_type.hpp +++ b/src/tightdb/data_type.hpp @@ -40,6 +40,11 @@ enum DataType { type_LinkList = 13 }; +/// See Descriptor::set_link_type(). +enum LinkType { + link_Strong, + link_Weak +}; } // namespace tightdb diff --git a/src/tightdb/descriptor.hpp b/src/tightdb/descriptor.hpp index e67b4352bed..d93804d82fe 100644 --- a/src/tightdb/descriptor.hpp +++ b/src/tightdb/descriptor.hpp @@ -40,7 +40,7 @@ namespace _impl { class DescriptorFriend; } /// changed. Accessors can become detached, see is_attached() for more /// on this. The descriptor itself is stored inside the database file, /// or elsewhere in case of a free-standing table or a table in a -/// free-stading group. +/// free-standing group. /// /// The dynamic type consists first, and foremost of an ordered list /// of column descriptors. Each column descriptor specifies the name @@ -96,7 +96,7 @@ class Descriptor { /// \sa Table::add_column() std::size_t add_column(DataType type, StringData name, DescriptorRef* subdesc = 0); - /// Insert a new column into all the tables associated wiuth this + /// Insert a new column into all the tables associated with this /// descriptor. If any of the tables are not empty, the new column will be /// filled with the default value associated with the specified type. This /// function cannot be used to insert link-type columns. For that, you have @@ -110,7 +110,7 @@ class Descriptor { /// /// \param subdesc If a non-null pointer is passed, and the /// specified type is `type_Table`, then this function - /// automatically reteives the descriptor associated with the new + /// automatically retrieves the descriptor associated with the new /// subtable column, and stores a reference to its accessor in /// `*subdesc`. /// @@ -121,15 +121,22 @@ class Descriptor { DescriptorRef* subdesc = 0); //@{ - /// Add link-type columns to group-level tables. It is not possible to add + /// Add a link-type column to a group-level table. It is not possible to add /// link-type columns to tables that are not group-level tables. These /// functions must be used in place of the ones without the `_link` suffix /// when the column type is `type_Link` or `type_LinkList`. A link-type /// column is associated with a particular target table. All links in a /// link-type column refer to rows in the target table of that column. The /// target table must also be a group-level table. - std::size_t add_column_link(DataType type, StringData name, Table& target); - void insert_column_link(std::size_t column_ndx, DataType type, StringData name, Table& target); + /// + /// \param link_type See set_link_type(). + /// + /// \sa Table::add_column_link() + /// \sa Table::insert_column_link() + std::size_t add_column_link(DataType type, StringData name, Table& target, + LinkType = link_Weak); + void insert_column_link(std::size_t column_ndx, DataType type, StringData name, Table& target, + LinkType = link_Weak); //@} /// Remove the specified column from each of the associated @@ -169,6 +176,75 @@ class Descriptor { /// \sa Table::rename_column() void rename_column(std::size_t column_ndx, StringData new_name); + /// There are two kinds of links, 'weak' and 'strong'. A strong link is one + /// that implies ownership, i.e., that the origin row (parent) owns the + /// target row (child). Simply stated, this means that when the origin row + /// (parent) is removed, so is the target row (child). If there are multiple + /// strong links to a target row, the origin rows share ownership, and the + /// target row is removed when the last owner disappears. Weak links do not + /// imply ownership, and will be nullified or removed when the target row + /// disappears. + /// + /// To put this in precise terms; when a strong link is broken, and the + /// target row has no other strong links to it, the target row is removed. A + /// row that is implicitly removed in this way, is said to be + /// *cascade-removed*. When a weak link is broken, nothing is + /// cascade-removed. + /// + /// A link is considered broken if + /// + /// - the link is nullified, removed, or replaced by a different link + /// (Row::nullify_link(), Row::set_link(), LinkView::remove_link(), + /// LinkView::set_link(), LinkView::clear()), or if + /// + /// - the origin row is explicitly removed (Row::move_last_over(), + /// Table::clear()), or if + /// + /// - the origin row is cascade-removed, or if + /// + /// - the origin column is removed from the table (Table::remove_column()), + /// or if + /// + /// - the origin table is removed from the group. + /// + /// Note that a link is *not* considered broken when it is replaced by a + /// link to the same target row. I.e., no no rows will be cascade-removed + /// due to such an operation. + /// + /// When a row is explicitly removed (such as by Table::move_last_over()), + /// all links to it are automatically removed or nullified. For single link + /// columns (type_Link), links to the removed row are nullified. For link + /// list columns (type_LinkList), links to the removed row are removed from + /// the list. + /// + /// When a row is cascade-removed there can no longer be any strong links to it, + /// but if there are any weak links, they will be removed or nullified. + /// + /// It is important to understand that this cascade-removal scheme is too + /// simplistic to enable detection and removal of orphaned link-cycles. In + /// this respect, it suffers from the same limitations as a reference + /// counting scheme generally does. + /// + /// It is also important to understand, that the possible presence of a link + /// cycle can cause a row to be cascade-removed as a consequence of being + /// modified. This happens, for example, if two rows, A and B, have strong + /// links to each other, and there are no other strong links to either of + /// them. In this case, if A->B is changed to A->C, then both A and B will + /// be cascade-removed. This can lead to obscure bugs in some applications, + /// such as in the following case: + /// + /// table.set_link(col_ndx_1, row_ndx, ...); + /// table.set_int(col_ndx_2, row_ndx, ...); // Oops, `row_ndx` may no longer refer to the same row + /// + /// To be safe, applications, that may encounter cycles, are advised to + /// adopt the following pattern: + /// + /// Row row = table[row_ndx]; + /// row.set_link(col_ndx_1, ...); + /// if (row) + /// row.set_int(col_ndx_2, ...); // Ok, because we check whether the row has disappeared + void set_link_type(std::size_t column_ndx, LinkType); + //@{ /// Get the descriptor for the specified subtable column. /// @@ -192,7 +268,7 @@ class Descriptor { //@{ /// Returns the parent table descriptor, if any. /// - /// If this descriptor is the *root destriptor*, then this + /// If this descriptor is the *root descriptor*, then this /// function returns null. Otherwise it returns the accessor of /// the parent descriptor. /// @@ -213,7 +289,7 @@ class Descriptor { /// Is this a root descriptor? /// /// Descriptors of tables with independent dynamic type are root - /// destriptors. Root descriptors are never shared. Tables that + /// descriptors. Root descriptors are never shared. Tables that /// are direct members of groups have independent dynamic /// types. The same is true for free-standing tables and subtables /// in columns of type 'mixed'. @@ -226,9 +302,9 @@ class Descriptor { /// /// A type descriptor can even be shared by subtables with /// different parent tables, but only if the parent tables - /// themselves have a shared type descritpor. For examaple, if a + /// themselves have a shared type descriptor. For example, if a /// table has a column `foo` of type 'table', and each of the - /// subtables in `foo` havs a column `bar` of type 'table', then + /// subtables in `foo` has a column `bar` of type 'table', then /// all the subtables in all the `bar` columns share the same /// dynamic type descriptor. /// @@ -237,7 +313,7 @@ class Descriptor { /// Determine whether this accessor is still attached. /// - /// A table descriptor accesor may get detached from the + /// A table descriptor accessor may get detached from the /// underlying descriptor for various reasons (see below). When it /// does, it no longer refers to that descriptor, and can no /// longer be used, except for calling is_attached(). The @@ -260,7 +336,7 @@ class Descriptor { /// and only if they contain the same number of columns, and each /// corresponding pair of columns have the same name and type. /// - /// The consequences of comparing a deatched descriptor are + /// The consequences of comparing a detached descriptor are /// undefined. bool operator==(const Descriptor&) const TIGHTDB_NOEXCEPT; bool operator!=(const Descriptor&) const TIGHTDB_NOEXCEPT; @@ -288,7 +364,7 @@ class Descriptor { // overlap in time, then they will all refer to the same // descriptor object. // - // It also enables the necessary resursive detaching of descriptor + // It also enables the necessary recursive detaching of descriptor // objects. struct subdesc_entry { std::size_t m_column_ndx; @@ -423,19 +499,20 @@ inline void Descriptor::insert_column(std::size_t column_ndx, DataType type, Str *subdesc = get_subdescriptor(column_ndx); } -inline std::size_t Descriptor::add_column_link(DataType type, StringData name, Table& target) +inline std::size_t Descriptor::add_column_link(DataType type, StringData name, Table& target, + LinkType link_type) { std::size_t column_ndx = m_spec->get_public_column_count(); - insert_column_link(column_ndx, type, name, target); // Throws + insert_column_link(column_ndx, type, name, target, link_type); // Throws return column_ndx; } inline void Descriptor::insert_column_link(std::size_t column_ndx, DataType type, StringData name, - Table& target) + Table& target, LinkType link_type) { - typedef _impl::TableFriend tf; TIGHTDB_ASSERT(is_attached()); TIGHTDB_ASSERT(column_ndx <= get_column_count()); + typedef _impl::TableFriend tf; TIGHTDB_ASSERT(tf::is_link_type(ColumnType(type))); // Both origin and target must be group-level tables TIGHTDB_ASSERT(is_root() && get_root_table()->is_group_level()); @@ -443,23 +520,34 @@ inline void Descriptor::insert_column_link(std::size_t column_ndx, DataType type tf::insert_column(*this, column_ndx, type, name, &target); // Throws adj_insert_column(column_ndx); + + tf::set_link_type(*get_root_table(), column_ndx, link_type); // Throws } inline void Descriptor::remove_column(std::size_t column_ndx) { - typedef _impl::TableFriend tf; TIGHTDB_ASSERT(is_attached()); + typedef _impl::TableFriend tf; tf::erase_column(*this, column_ndx); // Throws adj_erase_column(column_ndx); } inline void Descriptor::rename_column(std::size_t column_ndx, StringData name) { - typedef _impl::TableFriend tf; TIGHTDB_ASSERT(is_attached()); + typedef _impl::TableFriend tf; tf::rename_column(*this, column_ndx, name); // Throws } +inline void Descriptor::set_link_type(std::size_t column_ndx, LinkType link_type) +{ + TIGHTDB_ASSERT(is_attached()); + TIGHTDB_ASSERT(column_ndx <= get_column_count()); + typedef _impl::TableFriend tf; + TIGHTDB_ASSERT(tf::is_link_type(ColumnType(get_column_type(column_ndx)))); + tf::set_link_type(*get_root_table(), column_ndx, link_type); // Throws +} + inline ConstDescriptorRef Descriptor::get_subdescriptor(std::size_t column_ndx) const { return const_cast(this)->get_subdescriptor(column_ndx); diff --git a/src/tightdb/group.cpp b/src/tightdb/group.cpp index d3ed6bc7717..fd37b0410d1 100644 --- a/src/tightdb/group.cpp +++ b/src/tightdb/group.cpp @@ -439,9 +439,9 @@ void Group::remove_table(size_t table_ndx) // There is no easy way for Group::TransactAdvancer to handle removal of // tables that contain foreign target table link columns, because that // involves removal of the corresponding backlink columns. For that reason, - // we start by removing all columns, and that will generate individual - // replication instructions for each column, with sufficient information for - // Group::TransactAdvancer to handle them. + // we start by removing all columns, which will generate individual + // replication instructions for each column removal with sufficient + // information for Group::TransactAdvancer to handle them. size_t n = table->get_column_count(); for (size_t i = n; i > 0; --i) table->remove_column(i-1); @@ -1084,19 +1084,20 @@ class Group::TransactAdvancer { return true; } - bool insert_empty_rows(size_t row_ndx, size_t num_rows, size_t last_row_ndx, bool unordered) TIGHTDB_NOEXCEPT + bool insert_empty_rows(size_t row_ndx, size_t num_rows, size_t last_row_ndx, + bool unordered) TIGHTDB_NOEXCEPT { typedef _impl::TableFriend tf; if (unordered) { if (m_table) { while (num_rows--) { - tf::adj_accessors_move(*m_table, last_row_ndx - num_rows, row_ndx + num_rows); + tf::adj_acc_move_over(*m_table, row_ndx + num_rows, last_row_ndx - num_rows); } } } else { if (m_table) - tf::adj_accessors_insert_rows(*m_table, row_ndx, num_rows); + tf::adj_acc_insert_rows(*m_table, row_ndx, num_rows); } return true; } @@ -1108,13 +1109,13 @@ class Group::TransactAdvancer { TIGHTDB_ASSERT(num_rows == 1); typedef _impl::TableFriend tf; if (m_table) - tf::adj_accessors_move(*m_table, row_ndx, tbl_sz); + tf::adj_acc_move_over(*m_table, tbl_sz, row_ndx); } else { typedef _impl::TableFriend tf; if (m_table) { while (num_rows--) - tf::adj_accessors_erase_row(*m_table, row_ndx + num_rows); + tf::adj_acc_erase_row(*m_table, row_ndx + num_rows); } } return true; @@ -1417,6 +1418,11 @@ class Group::TransactAdvancer { return true; // No-op } + bool set_link_type(size_t, LinkType) TIGHTDB_NOEXCEPT + { + return true; // No-op + } + bool select_link_list(size_t col_ndx, size_t) TIGHTDB_NOEXCEPT { // See comments on link handling in TransactAdvancer::set_link(). @@ -1806,6 +1812,11 @@ class Group::TransactReverser { return true; // No-op } + bool set_link_type(size_t, LinkType) + { + return true; // No-op + } + bool insert_link_column(std::size_t col_idx, DataType, StringData, std::size_t target_table_idx, std::size_t backlink_col_ndx) { diff --git a/src/tightdb/index_string.hpp b/src/tightdb/index_string.hpp index c5ffb262225..fe3ea8e6ee8 100644 --- a/src/tightdb/index_string.hpp +++ b/src/tightdb/index_string.hpp @@ -91,7 +91,9 @@ class StringIndex: public Column { do_update_ref(to_str(value), old_row_ndx, new_row_ndx, 0); } - void clear() TIGHTDB_OVERRIDE; + void clear(); + using Column::clear; + void distinct(Column& result) const; bool has_duplicate_values() const TIGHTDB_NOEXCEPT; diff --git a/src/tightdb/link_view.cpp b/src/tightdb/link_view.cpp index ce848fdfed0..1fb7ddf3ba0 100644 --- a/src/tightdb/link_view.cpp +++ b/src/tightdb/link_view.cpp @@ -39,18 +39,18 @@ void LinkView::insert(size_t link_ndx, size_t target_row_ndx) typedef _impl::TableFriend tf; tf::bump_version(*m_origin_table); - size_t row_ndx = get_origin_row_index(); + size_t origin_row_ndx = get_origin_row_index(); // if there are no links yet, we have to create list if (!m_row_indexes.is_attached()) { TIGHTDB_ASSERT(link_ndx == 0); - ref_type ref = Column::create(m_origin_column.get_alloc()); - m_origin_column.set_row_ref(row_ndx, ref); + ref_type ref = Column::create(m_origin_column.get_alloc()); // Throws + m_origin_column.set_row_ref(origin_row_ndx, ref); // Throws m_row_indexes.get_root_array()->init_from_parent(); // re-attach } - m_row_indexes.insert(link_ndx, target_row_ndx); - m_origin_column.add_backlink(target_row_ndx, row_ndx); + m_row_indexes.insert(link_ndx, target_row_ndx); // Throws + m_origin_column.add_backlink(target_row_ndx, origin_row_ndx); // Throws #ifdef TIGHTDB_ENABLE_REPLICATION if (Replication* repl = get_repl()) @@ -64,20 +64,44 @@ void LinkView::set(size_t link_ndx, size_t target_row_ndx) TIGHTDB_ASSERT(is_attached()); TIGHTDB_ASSERT(m_row_indexes.is_attached() && link_ndx < m_row_indexes.size()); TIGHTDB_ASSERT(target_row_ndx < m_origin_column.get_target_table().size()); - typedef _impl::TableFriend tf; - tf::bump_version(*m_origin_table); - - // update backlinks - size_t row_ndx = get_origin_row_index(); - size_t old_target_row_ndx = m_row_indexes.get(link_ndx); - m_origin_column.remove_backlink(old_target_row_ndx, row_ndx); - m_origin_column.add_backlink(target_row_ndx, row_ndx); - m_row_indexes.set(link_ndx, target_row_ndx); #ifdef TIGHTDB_ENABLE_REPLICATION if (Replication* repl = get_repl()) repl->link_list_set(*this, link_ndx, target_row_ndx); // Throws #endif + + size_t old_target_row_ndx = do_set(link_ndx, target_row_ndx); // Throws + if (m_origin_column.m_weak_links) + return; + + Table& target_table = m_origin_column.get_target_table(); + size_t num_remaining = target_table.get_num_strong_backlinks(old_target_row_ndx); + if (num_remaining > 0) + return; + + ColumnBase::CascadeState::row target_row; + target_row.table_ndx = target_table.get_index_in_group(); + target_row.row_ndx = old_target_row_ndx; + ColumnBase::CascadeState state; + state.rows.push_back(target_row); + + typedef _impl::TableFriend tf; + tf::cascade_break_backlinks_to(target_table, old_target_row_ndx, state); // Throws + tf::remove_backlink_broken_rows(target_table, state.rows); // Throws +} + + +// Replication instruction 'link-list-set' calls this function directly. +size_t LinkView::do_set(size_t link_ndx, size_t target_row_ndx) +{ + size_t old_target_row_ndx = m_row_indexes.get(link_ndx); + size_t origin_row_ndx = get_origin_row_index(); + m_origin_column.remove_backlink(old_target_row_ndx, origin_row_ndx); // Throws + m_origin_column.add_backlink(target_row_ndx, origin_row_ndx); // Throws + m_row_indexes.set(link_ndx, target_row_ndx); // Throws + typedef _impl::TableFriend tf; + tf::bump_version(*m_origin_table); + return old_target_row_ndx; } @@ -110,26 +134,44 @@ void LinkView::remove(size_t link_ndx) { TIGHTDB_ASSERT(is_attached()); TIGHTDB_ASSERT(m_row_indexes.is_attached() && link_ndx < m_row_indexes.size()); - typedef _impl::TableFriend tf; - tf::bump_version(*m_origin_table); - - // update backlinks - size_t target_row_ndx = m_row_indexes.get(link_ndx); - size_t row_ndx = get_origin_row_index(); - m_origin_column.remove_backlink(target_row_ndx, row_ndx); - - bool is_last = (link_ndx + 1 == m_row_indexes.size()); - m_row_indexes.erase(link_ndx, is_last); - - if (m_row_indexes.is_empty()) { - m_row_indexes.detach(); - m_origin_column.set_row_ref(row_ndx, 0); - } #ifdef TIGHTDB_ENABLE_REPLICATION if (Replication* repl = get_repl()) repl->link_list_erase(*this, link_ndx); // Throws #endif + + size_t target_row_ndx = do_remove(link_ndx); // Throws + if (m_origin_column.m_weak_links) + return; + + Table& target_table = m_origin_column.get_target_table(); + size_t num_remaining = target_table.get_num_strong_backlinks(target_row_ndx); + if (num_remaining > 0) + return; + + ColumnBase::CascadeState::row target_row; + target_row.table_ndx = target_table.get_index_in_group(); + target_row.row_ndx = target_row_ndx; + ColumnBase::CascadeState state; + state.rows.push_back(target_row); + + typedef _impl::TableFriend tf; + tf::cascade_break_backlinks_to(target_table, target_row_ndx, state); // Throws + tf::remove_backlink_broken_rows(target_table, state.rows); // Throws +} + + +// Replication instruction 'link-list-erase' calls this function directly. +size_t LinkView::do_remove(size_t link_ndx) +{ + size_t target_row_ndx = m_row_indexes.get(link_ndx); + size_t origin_row_ndx = get_origin_row_index(); + m_origin_column.remove_backlink(target_row_ndx, origin_row_ndx); // Throws + bool is_last = (link_ndx + 1 == m_row_indexes.size()); + m_row_indexes.erase(link_ndx, is_last); // Throws + typedef _impl::TableFriend tf; + tf::bump_version(*m_origin_table); + return target_row_ndx; } @@ -140,26 +182,69 @@ void LinkView::clear() if (!m_row_indexes.is_attached()) return; +#ifdef TIGHTDB_ENABLE_REPLICATION + if (Replication* repl = get_repl()) + repl->link_list_clear(*this); // Throws +#endif + + if (m_origin_column.m_weak_links) { + bool broken_reciprocal_backlinks = false; + do_clear(broken_reciprocal_backlinks); // Throws + return; + } + + size_t origin_row_ndx = get_origin_row_index(); + ColumnBase::CascadeState state; + state.stop_on_link_list_column = &m_origin_column; + state.stop_on_link_list_row_ndx = origin_row_ndx; + typedef _impl::TableFriend tf; - tf::bump_version(*m_origin_table); + size_t num_links = m_row_indexes.size(); + for (size_t link_ndx = 0; link_ndx < num_links; ++link_ndx) { + size_t target_row_ndx = m_row_indexes.get(link_ndx); + m_origin_column.remove_backlink(target_row_ndx, origin_row_ndx); // Throws + Table& target_table = m_origin_column.get_target_table(); + size_t num_remaining = target_table.get_num_strong_backlinks(target_row_ndx); + if (num_remaining > 0) + continue; + ColumnBase::CascadeState::row target_row; + target_row.table_ndx = target_table.get_index_in_group(); + target_row.row_ndx = target_row_ndx; + typedef ColumnBase::CascadeState::row_set::iterator iter; + iter i = ::upper_bound(state.rows.begin(), state.rows.end(), target_row); + // This target row cannot already be in state.rows + TIGHTDB_ASSERT(i == state.rows.begin() || i[-1] != target_row); + state.rows.insert(i, target_row); + tf::cascade_break_backlinks_to(target_table, target_row_ndx, state); // Throws + } + + bool broken_reciprocal_backlinks = true; + do_clear(broken_reciprocal_backlinks); // Throws - // Update backlinks - size_t row_ndx = get_origin_row_index(); - size_t n = m_row_indexes.size(); - for (size_t i = 0; i < n; ++i) { - size_t target_row_ndx = m_row_indexes.get(i); - m_origin_column.remove_backlink(target_row_ndx, row_ndx); + tf::remove_backlink_broken_rows(*m_origin_table, state.rows); // Throws +} + + +// Replication instruction 'link-list-clear' calls this function directly. +void LinkView::do_clear(bool broken_reciprocal_backlinks) +{ + size_t origin_row_ndx = get_origin_row_index(); + if (!broken_reciprocal_backlinks) { + size_t num_links = m_row_indexes.size(); + for (size_t link_ndx = 0; link_ndx < num_links; ++link_ndx) { + size_t target_row_ndx = m_row_indexes.get(link_ndx); + m_origin_column.remove_backlink(target_row_ndx, origin_row_ndx); // Throws + } } m_row_indexes.destroy(); - m_origin_column.set_row_ref(row_ndx, 0); + m_origin_column.set_row_ref(origin_row_ndx, 0); // Throws -#ifdef TIGHTDB_ENABLE_REPLICATION - if (Replication* repl = get_repl()) - repl->link_list_clear(*this); // Throws -#endif + typedef _impl::TableFriend tf; + tf::bump_version(*m_origin_table); } + void LinkView::sort(size_t column, bool ascending) { std::vector c; @@ -169,6 +254,7 @@ void LinkView::sort(size_t column, bool ascending) sort(c, a); } + void LinkView::sort(std::vector columns, std::vector ascending) { #ifdef TIGHTDB_ENABLE_REPLICATION @@ -249,8 +335,8 @@ void LinkView::do_nullify_link(size_t old_target_row_ndx) if (m_row_indexes.is_empty()) { m_row_indexes.destroy(); - size_t row_ndx = get_origin_row_index(); - m_origin_column.set_row_ref(row_ndx, 0); + size_t origin_row_ndx = get_origin_row_index(); + m_origin_column.set_row_ref(origin_row_ndx, 0); } } diff --git a/src/tightdb/link_view.hpp b/src/tightdb/link_view.hpp index 1ba56aa82a6..202e8f8e654 100644 --- a/src/tightdb/link_view.hpp +++ b/src/tightdb/link_view.hpp @@ -31,6 +31,8 @@ namespace tightdb { class ColumnLinkList; +namespace _impl { class LinkListFriend; } + /// The effect of calling most of the link list functions on a detached accessor /// is unspecified and may lead to general corruption, or even a crash. The @@ -105,6 +107,10 @@ class LinkView : public RowIndexes { void detach(); void set_origin_row_index(std::size_t row_ndx); + std::size_t do_set(std::size_t link_ndx, std::size_t target_row_ndx); + std::size_t do_remove(std::size_t link_ndx); + void do_clear(bool broken_reciprocal_backlinks); + void do_nullify_link(std::size_t old_target_row_ndx); void do_update_link(std::size_t old_target_row_ndx, std::size_t new_target_row_ndx); @@ -125,6 +131,7 @@ class LinkView : public RowIndexes { void Verify(std::size_t row_ndx) const; #endif + friend class _impl::LinkListFriend; friend class ColumnLinkList; friend class util::bind_ptr; friend class util::bind_ptr; @@ -330,6 +337,28 @@ inline Replication* LinkView::get_repl() TIGHTDB_NOEXCEPT } #endif + +// The purpose of this class is to give internal access to some, but not all of +// the non-public parts of LinkView. +class _impl::LinkListFriend { +public: + static void do_set(LinkView& list, std::size_t link_ndx, std::size_t target_row_ndx) + { + list.do_set(link_ndx, target_row_ndx); + } + + static void do_remove(LinkView& list, std::size_t link_ndx) + { + list.do_remove(link_ndx); + } + + static void do_clear(LinkView& list) + { + bool broken_reciprocal_backlinks = false; + list.do_clear(broken_reciprocal_backlinks); + } +}; + } // namespace tightdb #endif // TIGHTDB_LINK_VIEW_HPP diff --git a/src/tightdb/replication.cpp b/src/tightdb/replication.cpp index 307ceb82bac..a63116b6ef7 100644 --- a/src/tightdb/replication.cpp +++ b/src/tightdb/replication.cpp @@ -55,7 +55,8 @@ void Replication::select_table(const Table* table) for (;;) { begin = m_subtab_path_buf.data(); end = begin + m_subtab_path_buf.size(); - end = _impl::TableFriend::record_subtable_path(*table, begin, end); + typedef _impl::TableFriend tf; + end = tf::record_subtable_path(*table, begin, end); if (end) break; size_t new_size = m_subtab_path_buf.size(); @@ -283,12 +284,10 @@ class Replication::TransactLogApplier { } } #endif - if (value == 0) { - m_table->nullify_link(col_ndx, row_ndx); // Throws - } - else { - m_table->set_link(col_ndx, row_ndx, value-1); // Throws - } + typedef _impl::TableFriend tf; + // Map zero to tightdb::npos, and `n+1` to `n`, where `n` is a target row index. + size_t target_row_ndx = value - size_t(1); + tf::do_set_link(*m_table, col_ndx, row_ndx, target_row_ndx); // Throws return true; } return false; @@ -468,35 +467,28 @@ class Replication::TransactLogApplier { bool erase_rows(size_t row_ndx, size_t num_rows, std::size_t last_row_ndx, bool unordered) { - if (TIGHTDB_LIKELY(m_table)) { - if (unordered) { - if (TIGHTDB_LIKELY(row_ndx < last_row_ndx && last_row_ndx+1 == m_table->size())) { + if (TIGHTDB_UNLIKELY(!m_table)) + return false; + if (TIGHTDB_UNLIKELY(row_ndx > last_row_ndx || last_row_ndx+1 != m_table->size())) + return false; + if (TIGHTDB_UNLIKELY(num_rows != 1)) + return false; + typedef _impl::TableFriend tf; + if (unordered) { #ifdef TIGHTDB_DEBUG - if (m_log) - *m_log << "table->move_last_over("<move_last_over("<move_last_over(row_ndx); // Throws - row_ndx++; - } - return true; - } - } - else { - if (TIGHTDB_LIKELY(row_ndx < m_table->size())) { + tf::do_move_last_over(*m_table, row_ndx); // Throws + } + else { #ifdef TIGHTDB_DEBUG - if (m_log) - *m_log << "table->remove("<remove("<remove(row_ndx); // Throws - row_ndx++; - } - return true; - } - } + tf::do_remove(*m_table, row_ndx); // Throws } - return false; + return true; } bool add_int_to_column(size_t col_ndx, int_fast64_t value) @@ -562,7 +554,8 @@ class Replication::TransactLogApplier { if (m_log) *m_log << "table->clear()\n"; #endif - m_table->clear(); // Throws + typedef _impl::TableFriend tf; + tf::do_clear(*m_table); // Throws return true; } return false; @@ -619,6 +612,23 @@ class Replication::TransactLogApplier { return false; } + bool set_link_type(size_t col_ndx, LinkType link_type) + { + if (TIGHTDB_LIKELY(m_table)) { + if (TIGHTDB_LIKELY(col_ndx < m_desc->get_column_count())) { +#ifdef TIGHTDB_DEBUG + if (m_log) + *m_log << "table->set_link_type("<insert_column("<insert_column("<insert_column_link("<get_table("<remove_column("<remove_column("<rename_column("<set("<set(link_ndx, value); // Throws + typedef _impl::LinkListFriend llf; + llf::do_set(*m_link_list, link_ndx, value); // Throws return true; } @@ -854,7 +868,8 @@ class Replication::TransactLogApplier { if (m_log) *m_log << "link_list->remove("<remove(link_ndx); // Throws + typedef _impl::LinkListFriend llf; + llf::do_remove(*m_link_list, link_ndx); // Throws return true; } @@ -866,7 +881,8 @@ class Replication::TransactLogApplier { if (m_log) *m_log << "link_list->clear()\n"; #endif - m_link_list->clear(); // Throws + typedef _impl::LinkListFriend llf; + llf::do_clear(*m_link_list); // Throws return true; } @@ -899,7 +915,7 @@ class Replication::TransactLogApplier { return false; } - const char* type_to_str(DataType type) + const char* data_type_to_str(DataType type) { switch (type) { case type_Int: @@ -928,6 +944,18 @@ class Replication::TransactLogApplier { TIGHTDB_ASSERT(false); return 0; } + + const char* link_type_to_str(LinkType type) + { + switch (type) { + case link_Strong: + return "link_Strong"; + case link_Weak: + return "link_Weak"; + } + TIGHTDB_ASSERT(false); + return 0; + } }; diff --git a/src/tightdb/replication.hpp b/src/tightdb/replication.hpp index bed407fc08a..b560e871958 100644 --- a/src/tightdb/replication.hpp +++ b/src/tightdb/replication.hpp @@ -157,12 +157,12 @@ class Replication { void row_insert_complete(const Table*); void insert_empty_rows(const Table*, std::size_t row_ndx, std::size_t num_rows); - void erase_row(const Table*, std::size_t row_ndx); - void move_last_over(const Table*, std::size_t target_row_ndx, std::size_t last_row_ndx); + void erase_row(const Table*, std::size_t row_ndx, bool move_last_over); void add_int_to_column(const Table*, std::size_t col_ndx, int_fast64_t value); void add_search_index(const Table*, std::size_t col_ndx); void add_primary_key(const Table*, std::size_t col_ndx); void remove_primary_key(const Table*); + void set_link_type(const Table*, std::size_t col_ndx, LinkType); void clear_table(const Table*); void optimize_table(const Table*); @@ -303,13 +303,14 @@ class Replication { instr_AddSearchIndex = 38, // Add a search index to a column instr_AddPrimaryKey = 39, // Add a primary key to a table instr_RemovePrimaryKey = 40, // Remove primary key from a table - instr_SelectLinkList = 41, - instr_LinkListSet = 42, // Assign to link list entry - instr_LinkListInsert = 43, // Insert entry into link list - instr_LinkListMove = 44, // Move an entry within a link list - instr_LinkListErase = 45, // Remove an entry from a link list - instr_LinkListClear = 46, // Ramove all entries from a link list - instr_LinkListSetAll = 47 // Assign to link list entry + instr_SetLinkType = 41, // Strong/weak + instr_SelectLinkList = 42, + instr_LinkListSet = 43, // Assign to link list entry + instr_LinkListInsert = 44, // Insert entry into link list + instr_LinkListMove = 45, // Move an entry within a link list + instr_LinkListErase = 46, // Remove an entry from a link list + instr_LinkListClear = 47, // Ramove all entries from a link list + instr_LinkListSetAll = 48 // Assign to link list entry }; util::Buffer m_subtab_path_buf; @@ -446,6 +447,7 @@ class Replication::TransactLogParser { /// bool add_search_index(std::size_t col_ndx) /// bool add_primary_key(std::size_t col_ndx) /// bool remove_primary_key() + /// bool set_link_type(std::size_t col_ndx, LinkType) /// bool select_link_list(std::size_t col_ndx, std::size_t row_ndx) /// bool link_list_set(std::size_t link_ndx, std::size_t value) /// bool link_list_insert(std::size_t link_ndx, std::size_t value) @@ -495,6 +497,7 @@ class Replication::TransactLogParser { bool read_char(char&); // throws bool is_valid_data_type(int type); + bool is_valid_link_type(int type); }; @@ -988,8 +991,11 @@ inline void Replication::set_mixed(const Table* t, std::size_t col_ndx, inline void Replication::set_link(const Table* t, std::size_t col_ndx, std::size_t ndx, std::size_t value) { + // Map `tightdb::npos` to zero, and `n` to `n+1`, where `n` is a target row + // index. + size_t value_2 = size_t(1) + value; check_table(t); // Throws - simple_cmd(instr_SetLink, util::tuple(col_ndx, ndx, value)); // Throws + simple_cmd(instr_SetLink, util::tuple(col_ndx, ndx, value_2)); // Throws } @@ -1086,25 +1092,18 @@ inline void Replication::insert_empty_rows(const Table* t, std::size_t row_ndx, check_table(t); // Throws // default to unordered, if we are inserting at the end: - bool unordered = row_ndx == t->size()-num_rows; + bool unordered = row_ndx == t->size()-num_rows; simple_cmd(instr_InsertEmptyRows, util::tuple(row_ndx, num_rows, t->size(), unordered)); // Throws } -inline void Replication::erase_row(const Table* t, std::size_t row_ndx) +inline void Replication::erase_row(const Table* t, std::size_t row_ndx, bool move_last_over) { check_table(t); // Throws std::size_t num_rows = 1; // FIXME: might want to make this parameter externally visible? - simple_cmd(instr_EraseRows, util::tuple(row_ndx, num_rows, t->size(), false)); // Throws -} - -inline void Replication::move_last_over(const Table* t, std::size_t target_row_ndx, std::size_t last_row_ndx) -{ - check_table(t); // Throws - static_cast(last_row_ndx); - TIGHTDB_ASSERT(t->size() == last_row_ndx); - simple_cmd(instr_EraseRows, util::tuple(target_row_ndx, 1, t->size(), true)); // Throws + std::size_t last_row_ndx = t->size() - 1; + simple_cmd(instr_EraseRows, util::tuple(row_ndx, num_rows, last_row_ndx, move_last_over)); // Throws } inline void Replication::add_int_to_column(const Table* t, std::size_t col_ndx, int_fast64_t value) @@ -1135,6 +1134,13 @@ inline void Replication::remove_primary_key(const Table* t) } +inline void Replication::set_link_type(const Table* t, std::size_t col_ndx, LinkType link_type) +{ + check_table(t); // Throws + simple_cmd(instr_SetLinkType, util::tuple(col_ndx, int(link_type))); // Throws +} + + inline void Replication::clear_table(const Table* t) { check_table(t); // Throws @@ -1149,7 +1155,8 @@ inline void Replication::optimize_table(const Table* t) } -inline void Replication::link_list_set(const LinkView& list, std::size_t link_ndx, std::size_t value) +inline void Replication::link_list_set(const LinkView& list, std::size_t link_ndx, + std::size_t value) { check_link_list(list); // Throws simple_cmd(instr_LinkListSet, util::tuple(link_ndx, value)); // Throws @@ -1553,6 +1560,15 @@ bool Replication::TransactLogParser::do_parse(InstructionHandler& handler) return false; continue; } + case instr_SetLinkType: { + std::size_t col_ndx = read_int(); // Throws + int link_type = read_int(); // Throws + if (!is_valid_link_type(link_type)) + return false; + if (!handler.set_link_type(col_ndx, LinkType(link_type))) // Throws + return false; + continue; + } case instr_InsertColumn: { std::size_t col_ndx = read_int(); // Throws int type = read_int(); // Throws @@ -1841,6 +1857,17 @@ inline bool Replication::TransactLogParser::is_valid_data_type(int type) } +inline bool Replication::TransactLogParser::is_valid_link_type(int type) +{ + switch (LinkType(type)) { + case link_Strong: + case link_Weak: + return true; + } + return false; +} + + inline TrivialReplication::TrivialReplication(const std::string& database_file): m_database_file(database_file) { diff --git a/src/tightdb/row.hpp b/src/tightdb/row.hpp index b4951354570..b06677dd10f 100644 --- a/src/tightdb/row.hpp +++ b/src/tightdb/row.hpp @@ -41,10 +41,10 @@ template class BasicRow; /// table[i].get_int(j) == table.get_int(i,j) /// /// The effect of calling most of the row accessor functions on a detached -/// accessor is unspecified and may lead to general corruption, or a crash. The -/// exceptions are is_attached(), detach(), get_table(), get_index(), and the -/// destructor. Note however, that get_index() will still return an unspecified -/// value for a deatched accessor. +/// accessor is unspecified and may lead to general corruption, and/or a +/// crash. The exceptions are is_attached(), detach(), get_table(), get_index(), +/// and the destructor. Note however, that get_index() will still return an +/// unspecified value for a deatched accessor. /// /// When a row accessor is evaluated in a boolean context, it evaluates to true /// if, and only if it is attached. @@ -101,6 +101,12 @@ template class RowFuncs { void set_mixed(std::size_t col_ndx, Mixed value); void set_mixed_subtable(std::size_t col_ndx, const Table* value); + //@{ + /// Note that these operations will cause the row accessor to be detached. + void remove(); + void move_last_over(); + //@} + std::size_t get_backlink_count(const Table& src_table, std::size_t src_col_ndx) const TIGHTDB_NOEXCEPT; std::size_t get_backlink(const Table& src_table, std::size_t src_col_ndx, @@ -463,6 +469,16 @@ inline void RowFuncs::set_mixed_subtable(std::size_t col_ndx, const Table* table()->set_mixed_subtable(col_ndx, row_ndx(), value); // Throws } +template inline void RowFuncs::remove() +{ + table()->remove(row_ndx()); // Throws +} + +template inline void RowFuncs::move_last_over() +{ + table()->move_last_over(row_ndx()); // Throws +} + template inline std::size_t RowFuncs::get_backlink_count(const Table& src_table, std::size_t src_col_ndx) const TIGHTDB_NOEXCEPT diff --git a/src/tightdb/table.cpp b/src/tightdb/table.cpp index d4c6e2301ad..1bc35763512 100644 --- a/src/tightdb/table.cpp +++ b/src/tightdb/table.cpp @@ -285,15 +285,16 @@ size_t Table::add_column(DataType type, StringData name, DescriptorRef* subdesc) return get_descriptor()->add_column(type, name, subdesc); // Throws } -size_t Table::add_column_link(DataType type, StringData name, Table& target) +size_t Table::add_column_link(DataType type, StringData name, Table& target, LinkType link_type) { - return get_descriptor()->add_column_link(type, name, target); // Throws + return get_descriptor()->add_column_link(type, name, target, link_type); // Throws } -void Table::insert_column_link(size_t col_ndx, DataType type, StringData name, Table& target) +void Table::insert_column_link(size_t col_ndx, DataType type, StringData name, Table& target, + LinkType link_type) { - get_descriptor()->insert_column_link(col_ndx, type, name, target); // Throws + get_descriptor()->insert_column_link(col_ndx, type, name, target, link_type); // Throws } @@ -329,6 +330,67 @@ void Table::connect_opposite_link_columns(size_t link_col_ndx, Table& target_tab } +size_t Table::get_num_strong_backlinks(std::size_t row_ndx) const TIGHTDB_NOEXCEPT +{ + size_t sum = 0; + size_t col_ndx_begin = m_spec.get_public_column_count(); + size_t col_ndx_end = m_cols.size(); + for (size_t i = col_ndx_begin; i < col_ndx_end; ++i) { + const ColumnBackLink& backlink_col = get_column_backlink(i); + const ColumnLinkBase& link_col = backlink_col.get_origin_column(); + if (link_col.get_weak_links()) + continue; + sum += backlink_col.get_backlink_count(row_ndx); + } + return sum; +} + + +void Table::cascade_break_backlinks_to(size_t row_ndx, CascadeState& state) +{ + size_t num_cols = m_spec.get_public_column_count(); + for (size_t col_ndx = 0; col_ndx != num_cols; ++col_ndx) { + ColumnBase& column = get_column_base(col_ndx); + column.cascade_break_backlinks_to(row_ndx, state); // Throws + } +} + + +void Table::cascade_break_backlinks_to_all_rows(CascadeState& state) +{ + size_t num_cols = m_spec.get_public_column_count(); + for (size_t col_ndx = 0; col_ndx != num_cols; ++col_ndx) { + ColumnBase& column = get_column_base(col_ndx); + column.cascade_break_backlinks_to_all_rows(m_size, state); // Throws + } +} + + +void Table::remove_backlink_broken_rows(const CascadeState::row_set& rows) +{ + Group& group = *get_parent_group(); + + // Rows are ordered by ascending row index, but we need to remove the rows + // by descending index to avoid changing the indexes of rows that are not + // removed yet. + typedef CascadeState::row_set::const_reverse_iterator iter; + iter end = rows.rend(); + for (iter i = rows.rbegin(); i != end; ++i) { + typedef _impl::GroupFriend gf; + Table& table = gf::get_table(group, i->table_ndx); + +#ifdef TIGHTDB_ENABLE_REPLICATION + if (Replication* repl = table.get_repl()) { + bool move_last_over = true; + repl->erase_row(&table, i->row_ndx, move_last_over); // Throws + } +#endif + bool broken_reciprocal_backlinks = true; + table.do_move_last_over(i->row_ndx, broken_reciprocal_backlinks); + } +} + + void Table::insert_column(size_t col_ndx, DataType type, StringData name, DescriptorRef* subdesc) { @@ -611,8 +673,10 @@ void Table::do_erase_column(Descriptor& desc, size_t col_ndx) // additional backlink columns, we need to inject a clear operation before // the column removal to correctly reproduce the desired effect, namely that // the table appears truncated after the removal of the last non-hidden - // column. This has the a regular replicated clear operation in order to get - // the right behaviour in Group::advance_transact(). + // column. The clear operation needs to be submitted to the replication + // handler as an individual operation, and precede the column removal + // operation in order to get the right behaviour in + // Group::advance_transact(). if (desc.is_root()) { if (root_table.m_spec.get_public_column_count() == 1 && root_table.m_cols.size() > 1) root_table.clear(); // Throws @@ -757,6 +821,36 @@ void Table::do_erase_root_column(size_t ndx) } +void Table::do_set_link_type(size_t col_ndx, LinkType link_type) +{ + bool weak_links = false; + switch (link_type) { + case link_Strong: + break; + case link_Weak: + weak_links = true; + break; + } + + ColumnAttr attr = m_spec.get_column_attr(col_ndx); + ColumnAttr attr_2 = attr; + attr_2 = ColumnAttr(attr_2 & ~col_attr_StrongLinks); + if (!weak_links) + attr_2 = ColumnAttr(attr_2 | col_attr_StrongLinks); + if (attr_2 == attr) + return; + m_spec.set_column_attr(col_ndx, attr); + + ColumnLinkBase& col = get_column_link_base(col_ndx); + col.set_weak_links(weak_links); + +#ifdef TIGHTDB_ENABLE_REPLICATION + if (Replication* repl = get_repl()) + repl->set_link_type(this, col_ndx, link_type); // Throws +#endif +} + + void Table::insert_backlink_column(size_t origin_table_ndx, size_t origin_col_ndx) { size_t backlink_col_ndx = m_cols.size(); @@ -1783,34 +1877,27 @@ void Table::insert_empty_row(size_t row_ndx, size_t num_rows) } -void Table::clear() +void Table::remove(size_t row_ndx) { TIGHTDB_ASSERT(is_attached()); - bump_version(); - - size_t num_cols = m_spec.get_column_count(); - for (size_t col_ndx = 0; col_ndx != num_cols; ++col_ndx) { - ColumnBase& column = get_column_base(col_ndx); - column.clear(); // Throws - } - discard_row_accessors(); - m_size = 0; + TIGHTDB_ASSERT(row_ndx < m_size); #ifdef TIGHTDB_ENABLE_REPLICATION - if (Replication* repl = get_repl()) - repl->clear_table(this); // Throws + if (Replication* repl = get_repl()) { + bool move_last_over = false; + repl->erase_row(this, row_ndx, move_last_over); // Throws + } #endif + + do_remove(row_ndx); } -void Table::remove(size_t row_ndx) +// Replication instruction 'erase-row(unordered=false)' calls this function +// directly. +void Table::do_remove(size_t row_ndx) { - TIGHTDB_ASSERT(is_attached()); - TIGHTDB_ASSERT(row_ndx < m_size); - bump_version(); - bool is_last = row_ndx == m_size - 1; - size_t num_cols = m_spec.get_column_count(); for (size_t col_ndx = 0; col_ndx != num_cols; ++col_ndx) { ColumnBase& column = get_column_base(col_ndx); @@ -1818,41 +1905,101 @@ void Table::remove(size_t row_ndx) } adj_row_acc_erase_row(row_ndx); --m_size; + bump_version(); +} + +void Table::move_last_over(size_t row_ndx) +{ + TIGHTDB_ASSERT(is_attached()); + TIGHTDB_ASSERT(row_ndx < m_size); + + size_t table_ndx = get_index_in_group(); + if (table_ndx == tightdb::npos) { #ifdef TIGHTDB_ENABLE_REPLICATION - if (Replication* repl = get_repl()) - repl->erase_row(this, row_ndx); // Throws + if (Replication* repl = get_repl()) { + bool move_last_over = true; + repl->erase_row(this, row_ndx, move_last_over); // Throws + } #endif + + bool broken_reciprocal_backlinks = false; + do_move_last_over(row_ndx, broken_reciprocal_backlinks); + return; + } + + // Group-level tables may have links, so in those cases we need to discover + // all the rows that need to be cascade-removed. + CascadeState::row row; + row.table_ndx = table_ndx; + row.row_ndx = row_ndx; + CascadeState state; + state.rows.push_back(row); + + cascade_break_backlinks_to(row_ndx, state); // Throws + + remove_backlink_broken_rows(state.rows); // Throws } -void Table::move_last_over(size_t target_row_ndx) +// Replication instruction 'erase-row(unordered=true)' calls this function +// directly with broken_reciprocal_backlinks=false. +void Table::do_move_last_over(size_t row_ndx, bool broken_reciprocal_backlinks) { - TIGHTDB_ASSERT(target_row_ndx < m_size); - bump_version(); - size_t last_row_ndx = m_size - 1; size_t num_cols = m_spec.get_column_count(); - if (target_row_ndx != last_row_ndx) { - for (size_t col_ndx = 0; col_ndx != num_cols; ++col_ndx) { - ColumnBase& column = get_column_base(col_ndx); - column.move_last_over(target_row_ndx, last_row_ndx); // Throws - } - } - else { - for (size_t col_ndx = 0; col_ndx != num_cols; ++col_ndx) { - ColumnBase& column = get_column_base(col_ndx); - bool is_last = true; - column.erase(target_row_ndx, is_last); // Throws - } + for (size_t col_ndx = 0; col_ndx != num_cols; ++col_ndx) { + ColumnBase& column = get_column_base(col_ndx); + column.move_last_over(row_ndx, last_row_ndx, broken_reciprocal_backlinks); // Throws } - adj_row_acc_move(target_row_ndx, last_row_ndx); + adj_row_acc_move_over(last_row_ndx, row_ndx); --m_size; + bump_version(); +} + + +void Table::clear() +{ + TIGHTDB_ASSERT(is_attached()); #ifdef TIGHTDB_ENABLE_REPLICATION if (Replication* repl = get_repl()) - repl->move_last_over(this, target_row_ndx, last_row_ndx); // Throws + repl->clear_table(this); // Throws #endif + + size_t table_ndx = get_index_in_group(); + if (table_ndx == tightdb::npos) { + bool broken_reciprocal_backlinks = false; + do_clear(broken_reciprocal_backlinks); + return; + } + + // Group-level tables may have links, so in those cases we need to discover + // all the rows that need to be cascade-removed. + CascadeState state; + state.stop_on_table = this; + cascade_break_backlinks_to_all_rows(state); // Throws + + bool broken_reciprocal_backlinks = true; + do_clear(broken_reciprocal_backlinks); + + remove_backlink_broken_rows(state.rows); // Throws +} + + +// Replication instruction 'clear-table' calls this function +// directly with broken_reciprocal_backlinks=false. +void Table::do_clear(bool broken_reciprocal_backlinks) +{ + size_t num_cols = m_spec.get_column_count(); + for (size_t col_ndx = 0; col_ndx != num_cols; ++col_ndx) { + ColumnBase& column = get_column_base(col_ndx); + column.clear(m_size, broken_reciprocal_backlinks); // Throws + } + m_size = 0; + + discard_row_accessors(); + bump_version(); } @@ -2536,52 +2683,60 @@ TableRef Table::get_link_target(size_t col_ndx) TIGHTDB_NOEXCEPT void Table::set_link(size_t col_ndx, size_t row_ndx, size_t target_row_ndx) { + TIGHTDB_ASSERT(is_attached()); TIGHTDB_ASSERT(row_ndx < m_size); - bump_version(); - ColumnLink& column = get_column_link(col_ndx); - column.set_link(row_ndx, target_row_ndx); #ifdef TIGHTDB_ENABLE_REPLICATION - if (Replication* repl = get_repl()) { - size_t link = 1 + target_row_ndx; - repl->set_link(this, col_ndx, row_ndx, link); // Throws - } + if (Replication* repl = get_repl()) + repl->set_link(this, col_ndx, row_ndx, target_row_ndx); // Throws #endif -} -void Table::insert_link(size_t col_ndx, size_t row_ndx, size_t target_row_ndx) -{ - TIGHTDB_ASSERT(row_ndx == m_size); // can only append to unorded tables + size_t old_target_row_ndx = do_set_link(col_ndx, row_ndx, target_row_ndx); // Throws + if (old_target_row_ndx == tightdb::npos) + return; - ColumnLink& column = get_column_link(col_ndx); - column.insert_link(row_ndx, target_row_ndx); + ColumnLink& col = get_column_link(col_ndx); + if (col.get_weak_links()) + return; -#ifdef TIGHTDB_ENABLE_REPLICATION - if (Replication* repl = get_repl()) { - size_t link = 1 + target_row_ndx; - repl->insert_link(this, col_ndx, row_ndx, link); // Throws - } -#endif -} + Table& target_table = col.get_target_table(); + size_t num_remaining = target_table.get_num_strong_backlinks(old_target_row_ndx); + if (num_remaining > 0) + return; -bool Table::is_null_link(size_t col_ndx, size_t ndx) const TIGHTDB_NOEXCEPT -{ - TIGHTDB_ASSERT(ndx < m_size); - const ColumnLink& column = get_column_link(col_ndx); - return column.is_null_link(ndx); + CascadeState::row target_row; + target_row.table_ndx = target_table.get_index_in_group(); + target_row.row_ndx = old_target_row_ndx; + CascadeState state; + state.rows.push_back(target_row); + target_table.cascade_break_backlinks_to(old_target_row_ndx, state); // Throws + + remove_backlink_broken_rows(state.rows); // Throws } -void Table::nullify_link(size_t col_ndx, size_t row_ndx) + +// Replication instruction 'set_link' calls this function directly. +size_t Table::do_set_link(size_t col_ndx, size_t row_ndx, size_t target_row_ndx) { TIGHTDB_ASSERT(row_ndx < m_size); + ColumnLink& col = get_column_link(col_ndx); + size_t old_target_row_ndx = col.set_link(row_ndx, target_row_ndx); bump_version(); + return old_target_row_ndx; +} + + +void Table::insert_link(size_t col_ndx, size_t row_ndx, size_t target_row_ndx) +{ + TIGHTDB_ASSERT(row_ndx == m_size); // can only append to unorded tables + ColumnLink& column = get_column_link(col_ndx); - column.nullify_link(row_ndx); + column.insert_link(row_ndx, target_row_ndx); #ifdef TIGHTDB_ENABLE_REPLICATION if (Replication* repl = get_repl()) { - size_t link = 0; // Null-link - repl->set_link(this, col_ndx, row_ndx, link); // Throws + size_t link = 1 + target_row_ndx; + repl->insert_link(this, col_ndx, row_ndx, link); // Throws } #endif } @@ -4414,7 +4569,7 @@ Table* Table::Parent::get_parent_table(size_t*) TIGHTDB_NOEXCEPT } -void Table::adj_accessors_insert_rows(size_t row_ndx, size_t num_rows) TIGHTDB_NOEXCEPT +void Table::adj_acc_insert_rows(size_t row_ndx, size_t num_rows) TIGHTDB_NOEXCEPT { // This function must assume no more than minimal consistency of the // accessor hierarchy. This means in particular that it cannot access the @@ -4426,12 +4581,12 @@ void Table::adj_accessors_insert_rows(size_t row_ndx, size_t num_rows) TIGHTDB_N size_t n = m_cols.size(); for (size_t i = 0; i != n; ++i) { if (ColumnBase* col = m_cols[i]) - col->adj_accessors_insert_rows(row_ndx, num_rows); + col->adj_acc_insert_rows(row_ndx, num_rows); } } -void Table::adj_accessors_erase_row(size_t row_ndx) TIGHTDB_NOEXCEPT +void Table::adj_acc_erase_row(size_t row_ndx) TIGHTDB_NOEXCEPT { // This function must assume no more than minimal consistency of the // accessor hierarchy. This means in particular that it cannot access the @@ -4443,25 +4598,24 @@ void Table::adj_accessors_erase_row(size_t row_ndx) TIGHTDB_NOEXCEPT size_t n = m_cols.size(); for (size_t i = 0; i != n; ++i) { if (ColumnBase* col = m_cols[i]) - col->adj_accessors_erase_row(row_ndx); + col->adj_acc_erase_row(row_ndx); } } -void Table::adj_accessors_move(size_t target_row_ndx, size_t source_row_ndx) +void Table::adj_acc_move_over(size_t from_row_ndx, size_t to_row_ndx) TIGHTDB_NOEXCEPT { // This function must assume no more than minimal consistency of the // accessor hierarchy. This means in particular that it cannot access the // underlying node structure. See AccessorConsistencyLevels. - adj_row_acc_move(target_row_ndx, source_row_ndx); + adj_row_acc_move_over(from_row_ndx, to_row_ndx); - // Adjust subtable accessors after 'move last over' removal of a row size_t n = m_cols.size(); for (size_t i = 0; i != n; ++i) { if (ColumnBase* col = m_cols[i]) - col->adj_accessors_move(target_row_ndx, source_row_ndx); + col->adj_acc_move_over(from_row_ndx, to_row_ndx); } } @@ -4530,23 +4684,21 @@ void Table::adj_row_acc_erase_row(size_t row_ndx) TIGHTDB_NOEXCEPT } -void Table::adj_row_acc_move(size_t target_row_ndx, size_t source_row_ndx) +void Table::adj_row_acc_move_over(size_t from_row_ndx, size_t to_row_ndx) TIGHTDB_NOEXCEPT { // This function must assume no more than minimal consistency of the // accessor hierarchy. This means in particular that it cannot access the // underlying node structure. See AccessorConsistencyLevels. - - // Adjust row accessors after 'move last over' removal of a row RowBase* row = m_row_accessors; while (row) { RowBase* next = row->m_next; - if (row->m_row_ndx == target_row_ndx) { + if (row->m_row_ndx == to_row_ndx) { row->m_table.reset(); unregister_row_accessor(row); } - else if (row->m_row_ndx == source_row_ndx) { - row->m_row_ndx = target_row_ndx; + else if (row->m_row_ndx == from_row_ndx) { + row->m_row_ndx = to_row_ndx; } row = next; } @@ -4722,6 +4874,10 @@ void Table::refresh_column_accessors(size_t col_ndx_begin) // of the connection is postponed. typedef _impl::GroupFriend gf; if (is_link_type(col_type)) { + ColumnAttr attr = m_spec.get_column_attr(col_ndx); + bool weak_links = (attr & col_attr_StrongLinks) == 0; + ColumnLinkBase* link_col = static_cast(col); + link_col->set_weak_links(weak_links); Group& group = *get_parent_group(); size_t target_table_ndx = m_spec.get_opposite_link_table_ndx(col_ndx); Table& target_table = gf::get_table(group, target_table_ndx); // Throws diff --git a/src/tightdb/table.hpp b/src/tightdb/table.hpp index 44a8b5c3d55..72b83d6a03b 100644 --- a/src/tightdb/table.hpp +++ b/src/tightdb/table.hpp @@ -187,6 +187,8 @@ class Table { /// descriptor associated with the new subtable column, and stores a /// reference to its accessor in `*subdesc`. /// + /// \param link_type See Descriptor::set_link_type(). + /// /// \return The value returned by add_column() and add_column_link(), is the /// index of the added column. /// @@ -196,8 +198,9 @@ class Table { std::size_t add_column(DataType type, StringData name, DescriptorRef* subdesc = 0); void insert_column(std::size_t column_ndx, DataType type, StringData name, DescriptorRef* subdesc = 0); - std::size_t add_column_link(DataType type, StringData name, Table& target); - void insert_column_link(std::size_t column_ndx, DataType type, StringData name, Table& target); + std::size_t add_column_link(DataType type, StringData name, Table& target, LinkType link_type = link_Weak); + void insert_column_link(std::size_t column_ndx, DataType type, StringData name, Table& target, + LinkType link_type = link_Weak); void remove_column(std::size_t column_ndx); void rename_column(std::size_t column_ndx, StringData new_name); //@} @@ -325,9 +328,8 @@ class Table { template Columns column(std::size_t column); // FIXME: Should this one have been declared TIGHTDB_NOEXCEPT? // Table size and deletion - bool is_empty() const TIGHTDB_NOEXCEPT; + bool is_empty() const TIGHTDB_NOEXCEPT; std::size_t size() const TIGHTDB_NOEXCEPT; - void clear(); typedef BasicRowExpr RowExpr; typedef BasicRowExpr ConstRowExpr; @@ -347,7 +349,23 @@ class Table { //@{ - /// Row handling + /// Row handling. + /// + /// remove() removes the specified row from the table and shifts all rows at + /// higher index to fill the vacated slot. This operation assumes that the + /// table is ordered, and it is therefore allowed only on tables **without** + /// link columns, as link columns are only allowed in unordered tables. + /// + /// move_last_over() removes the specified row from the table, and if it is + /// not the last row in the table, it then moves the last row into the + /// vacated slot. This operation assumes that the table is unordered, and it + /// may therfore be used on tables with link columns. + /// + /// The removal of a row from an unordered table (move_last_over()) may + /// cause other linked rows to be cascade-removed. The clearing of a table + /// may also cause linked rows to be cascade-removed, but in this respect, + /// the effect is exactly as if each row had been removed individually. See + /// Descriptor::set_link_type() for details. /// /// It is an error to call add_empty_row() or insert_empty_row() on a table /// with a primary key, if that would result in a violation the implied @@ -358,13 +376,11 @@ class Table { void insert_empty_row(std::size_t row_ndx, std::size_t num_rows = 1); void remove(std::size_t row_ndx); void remove_last(); + void move_last_over(std::size_t row_ndx); + void clear(); //@} - /// Move the last row to the specified index. This overwrites the target row - /// and reduces the number of rows by one. If the target row is the last one - /// it will just be deleted. - void move_last_over(std::size_t target_row_ndx); //@{ @@ -404,6 +420,11 @@ class Table { Mixed get_mixed(std::size_t column_ndx, std::size_t row_ndx) const TIGHTDB_NOEXCEPT; DataType get_mixed_type(std::size_t column_ndx, std::size_t row_ndx) const TIGHTDB_NOEXCEPT; std::size_t get_link(std::size_t column_ndx, std::size_t row_ndx) const TIGHTDB_NOEXCEPT; + bool is_null_link(std::size_t column_ndx, std::size_t row_ndx) const TIGHTDB_NOEXCEPT; + LinkViewRef get_linklist(std::size_t column_ndx, std::size_t row_ndx); + ConstLinkViewRef get_linklist(std::size_t column_ndx, std::size_t row_ndx) const; + std::size_t get_link_count(std::size_t column_ndx, std::size_t row_ndx) const TIGHTDB_NOEXCEPT; + bool linklist_is_empty(std::size_t column_ndx, std::size_t row_ndx) const TIGHTDB_NOEXCEPT; TableRef get_link_target(std::size_t column_ndx) TIGHTDB_NOEXCEPT; ConstTableRef get_link_target(std::size_t column_ndx) const TIGHTDB_NOEXCEPT; @@ -417,7 +438,7 @@ class Table { /// /// It is an error to assign a value to a column that is part of a primary /// key, if that would result in a violation the implied *unique constraint* - /// of the primary key. The consequenses of doing so are unspecified. + /// of that primary key. The consequenses of doing so are unspecified. void set_int(std::size_t column_ndx, std::size_t row_ndx, int_fast64_t value); void set_bool(std::size_t column_ndx, std::size_t row_ndx, bool value); @@ -429,18 +450,9 @@ class Table { void set_binary(std::size_t column_ndx, std::size_t row_ndx, BinaryData value); void set_mixed(std::size_t column_ndx, std::size_t row_ndx, Mixed value); void set_link(std::size_t column_ndx, std::size_t row_ndx, std::size_t target_row_ndx); - - //@} - - // Links - bool is_null_link(std::size_t column_ndx, std::size_t row_ndx) const TIGHTDB_NOEXCEPT; void nullify_link(std::size_t column_ndx, std::size_t row_ndx); - // Link lists - ConstLinkViewRef get_linklist(std::size_t column_ndx, std::size_t row_ndx) const; - LinkViewRef get_linklist(std::size_t column_ndx, std::size_t row_ndx); - bool linklist_is_empty(std::size_t column_ndx, std::size_t row_ndx) const TIGHTDB_NOEXCEPT; - std::size_t get_link_count(std::size_t column_ndx, std::size_t row_ndx) const TIGHTDB_NOEXCEPT; + //@} void add_int(std::size_t column_ndx, int64_t value); @@ -835,6 +847,11 @@ class Table { mutable uint_fast64_t m_version; #endif + void do_remove(std::size_t row_ndx); + void do_move_last_over(std::size_t row_ndx, bool broken_reciprocal_backlinks); + void do_clear(bool broken_reciprocal_backlinks); + std::size_t do_set_link(std::size_t col_ndx, std::size_t row_ndx, std::size_t target_row_ndx); + /// Update the version of this table and all tables which have links to it. /// This causes all views referring to those tables to go out of sync, so that /// calls to sync_if_needed() will bring the view up to date by reexecuting the @@ -898,6 +915,7 @@ class Table { void erase_root_column(std::size_t col_ndx); void do_insert_root_column(std::size_t col_ndx, ColumnType, StringData name); void do_erase_root_column(std::size_t col_ndx); + void do_set_link_type(std::size_t col_ndx, LinkType); void insert_backlink_column(std::size_t origin_table_ndx, std::size_t origin_col_ndx); void erase_backlink_column(std::size_t origin_table_ndx, std::size_t origin_col_ndx); void update_link_target_tables(std::size_t old_col_ndx_begin, std::size_t new_col_ndx_begin); @@ -1065,6 +1083,92 @@ class Table { void connect_opposite_link_columns(std::size_t link_col_ndx, Table& target_table, std::size_t backlink_col_ndx) TIGHTDB_NOEXCEPT; + std::size_t get_num_strong_backlinks(std::size_t row_ndx) const TIGHTDB_NOEXCEPT; + + //@{ + + /// Cascading removal of strong links. + /// + /// cascade_break_backlinks_to() removes all backlinks pointing to the row + /// at \a row_ndx. Additionally, if this causes the number of **strong** + /// backlinks originating from a particular opposite row (target row of + /// corresponding forward link) to drop to zero, and that row is not already + /// in \a state.rows, then that row is added to \a state.rows, and + /// cascade_break_backlinks_to() is called recursively for it. This + /// operation is the first half of the cascading row removal operation. The + /// second half is performed by passing the resulting contents of \a + /// state.rows to remove_backlink_broken_rows(). + /// + /// Operations that trigger cascading row removal due to explicit removal of + /// one or more rows (the *initiating rows*), should add those rows to \a + /// rows initially, and then call cascade_break_backlinks_to() once for each + /// of them in turn. This is opposed to carrying out the explicit row + /// removals independently, which is also possible, but does require that + /// any initiating rows, that end up in \a state.rows due to link cycles, + /// are removed before passing \a state.rows to + /// remove_backlink_broken_rows(). In the case of clear(), where all rows of + /// a table are explicitly removed, it is better to use + /// cascade_break_backlinks_to_all_rows(), and then carry out the table + /// clearing as an independent step. For operations that trigger cascading + /// row removal for other reasons than explicit row removal, \a state.rows + /// must be empty initially, but cascade_break_backlinks_to() must still be + /// called for each of the initiating rows. + /// + /// When the last non-recursive invocation of cascade_break_backlinks_to() + /// returns, all forward links originating from a row in \a state.rows have + /// had their reciprocal backlinks removed, so remove_backlink_broken_rows() + /// does not perform reciprocal backlink removal at all. Additionally, all + /// remaining backlinks originating from rows in \a state.rows are + /// guaranteed to point to rows that are **not** in \a state.rows. This is + /// true because any backlink that was pointing to a row in \a state.rows + /// has been removed by one of the invocations of + /// cascade_break_backlinks_to(). The set of forward links, that correspond + /// to these remaining backlinks, is precisely the set of forward links that + /// need to be removed/nullified by remove_backlink_broken_rows(), which it + /// does by way of reciprocal forward link removal. Note also, that while + /// all the rows in \a state.rows can have remaining **weak** backlinks + /// originating from them, only the initiating rows in \a state.rows can + /// have remaining **strong** backlinks originating from them. This is true + /// because a non-initiating row is added to \a state.rows only when the + /// last backlink originating from it is lost. + /// + /// Each row removal is replicated individually (as opposed to one + /// replication instruction for the entire cascading operation). This is + /// done because it provides an easy way for Group::advance_transact() to + /// know which tables are affected by the cascade. Note that this has + /// several important consequences: First of all, the replication log + /// receiver must execute the row removal instructions in a non-cascading + /// fashion, meaning that there will be an asymmetry between the two sides + /// in how the effect of the cascade is brought about. While this is fine + /// for simple 1-to-1 replication, it may end up interfering badly with + /// *transaction merging*, when that feature is introduced. Imagine for + /// example that the cascade initiating operation gets canceled during + /// conflict resolution, but some, or all of the induced row removals get to + /// stay. That would break causal consistency. It is important, however, for + /// transaction merging that the cascaded row removals are explicitly + /// mentioned in the replication log, such that they can be used to adjust + /// row indexes during the *operational transform*. + /// + /// cascade_break_backlinks_to_all_rows() has the same affect as calling + /// cascade_break_backlinks_to() once for each row in the table. When + /// calling this function, \a state.stop_on_table must be set to the origin + /// table (origin table of corresponding forward links), and \a + /// state.stop_on_link_list_column must be null. + /// + /// It is immaterial which table remove_backlink_broken_rows() is called on, + /// as long it that table is in the same group as the specified rows. + + typedef ColumnBase::CascadeState CascadeState; + void cascade_break_backlinks_to(std::size_t row_ndx, CascadeState& state); + void cascade_break_backlinks_to_all_rows(CascadeState& state); + void remove_backlink_broken_rows(const CascadeState::row_set&); + + //@} + + /// Remove the specified row by the 'move last over' method, and submit the + /// operation to the replication subsystem. + void do_move_last_over(std::size_t row_ndx); + // Precondition: 1 <= end - begin std::size_t* record_subtable_path(std::size_t* begin, std::size_t* end) const TIGHTDB_NOEXCEPT; @@ -1090,16 +1194,50 @@ class Table { void discard_subtable_accessor(std::size_t col_ndx, std::size_t row_ndx) TIGHTDB_NOEXCEPT; - void adj_accessors_insert_rows(std::size_t row_ndx, std::size_t num_rows) TIGHTDB_NOEXCEPT; - void adj_accessors_erase_row(std::size_t row_ndx) TIGHTDB_NOEXCEPT; - void adj_accessors_move(std::size_t target_row_ndx, std::size_t source_row_ndx) - TIGHTDB_NOEXCEPT; + void adj_acc_insert_rows(std::size_t row_ndx, std::size_t num_rows) TIGHTDB_NOEXCEPT; + void adj_acc_erase_row(std::size_t row_ndx) TIGHTDB_NOEXCEPT; + + /// Adjust this table accessor and its subordinates after move_last_over() + /// (or its inverse). + /// + /// First, any row, subtable, or link list accessors registered as being at + /// \a to_row_ndx will be detached, as that row is assumed to have been + /// replaced. Next, any row, subtable, or link list accessors registered as + /// being at \a from_row_ndx, will be reregistered as being at \a + /// to_row_ndx, as the row at \a from_row_ndx is assumed to have been moved + /// to \a to_row_ndx. + /// + /// Crucially, if \a to_row_ndx is equal to \a from_row_ndx, then row, + /// subtable, or link list accessors at that row are **still detached**. + /// + /// Additionally, this function causes all link-adjacent tables to be marked + /// (dirty). Two tables are link-adjacent if one is the target table of a + /// link column of the other table. Note that this marking follows these + /// relations in both directions, but only to a depth of one. + /// + /// When this function is used in connection with move_last_over(), set \a + /// to_row_ndx to the index of the row to be removed, and set \a + /// from_row_ndx to the index of the last row in the table. As mentioned + /// earlier, this function can also be used in connection with the **inverse + /// of** move_last_over(), which is an operation that vacates a row by + /// moving its contents into a new last row of the table. In that case, set + /// \a to_row_ndx to one plus the index of the last row in the table, and + /// set \a from_row_ndx to the index of the row to be vacated. + /// + /// This function is used as part of Table::refresh_accessor_tree() to + /// promote the state of the accessors from Minimal Consistency into + /// Structural Correspondence, so it must be able to execute without + /// accessing the underlying array nodes. + void adj_acc_move_over(std::size_t from_row_ndx, std::size_t to_row_ndx) TIGHTDB_NOEXCEPT; + void adj_acc_clear_root_table() TIGHTDB_NOEXCEPT; void adj_acc_clear_nonroot_table() TIGHTDB_NOEXCEPT; void adj_row_acc_insert_rows(std::size_t row_ndx, std::size_t num_rows) TIGHTDB_NOEXCEPT; void adj_row_acc_erase_row(std::size_t row_ndx) TIGHTDB_NOEXCEPT; - void adj_row_acc_move(std::size_t target_row_ndx, std::size_t source_row_ndx) - TIGHTDB_NOEXCEPT; + + /// Called by adj_acc_move_over() to adjust row accessors. + void adj_row_acc_move_over(std::size_t from_row_ndx, std::size_t to_row_ndx) TIGHTDB_NOEXCEPT; + void adj_insert_column(std::size_t col_ndx); void adj_erase_column(std::size_t col_ndx) TIGHTDB_NOEXCEPT; @@ -1551,6 +1689,11 @@ inline void Table::insert_subtable(std::size_t col_ndx, std::size_t row_ndx) insert_subtable(col_ndx, row_ndx, 0); // Null stands for an empty table } +inline bool Table::is_null_link(std::size_t col_ndx, std::size_t row_ndx) const TIGHTDB_NOEXCEPT +{ + return get_link(col_ndx, row_ndx) == tightdb::npos; +} + inline ConstTableRef Table::get_link_target(std::size_t col_ndx) const TIGHTDB_NOEXCEPT { return const_cast(this)->get_link_target(col_ndx); @@ -1562,6 +1705,11 @@ inline void Table::set_enum(std::size_t column_ndx, std::size_t row_ndx, E value set_int(column_ndx, row_ndx, value); } +inline void Table::nullify_link(std::size_t col_ndx, std::size_t row_ndx) +{ + set_link(col_ndx, row_ndx, tightdb::npos); +} + inline TableRef Table::get_subtable(std::size_t column_ndx, std::size_t row_ndx) { return TableRef(get_subtable_ptr(column_ndx, row_ndx)); @@ -1859,6 +2007,46 @@ class _impl::TableFriend { return *table.m_cols[col_ndx]; } + static void do_remove(Table& table, std::size_t row_ndx) + { + table.do_remove(row_ndx); // Throws + } + + static void do_move_last_over(Table& table, std::size_t row_ndx) + { + bool broken_reciprocal_backlinks = false; + table.do_move_last_over(row_ndx, broken_reciprocal_backlinks); // Throws + } + + static void do_clear(Table& table) + { + bool broken_reciprocal_backlinks = false; + table.do_clear(broken_reciprocal_backlinks); // Throws + } + + static void do_set_link(Table& table, std::size_t col_ndx, std::size_t row_ndx, + std::size_t target_row_ndx) + { + table.do_set_link(col_ndx, row_ndx, target_row_ndx); // Throws + } + + static std::size_t get_num_strong_backlinks(const Table& table, + std::size_t row_ndx) TIGHTDB_NOEXCEPT + { + return table.get_num_strong_backlinks(row_ndx); + } + + static void cascade_break_backlinks_to(Table& table, std::size_t row_ndx, + Table::CascadeState& state) + { + table.cascade_break_backlinks_to(row_ndx, state); // Throws + } + + static void remove_backlink_broken_rows(Table& table, const Table::CascadeState::row_set& rows) + { + table.remove_backlink_broken_rows(rows); // Throws + } + static std::size_t* record_subtable_path(const Table& table, std::size_t* begin, std::size_t* end) TIGHTDB_NOEXCEPT { @@ -1881,6 +2069,11 @@ class _impl::TableFriend { Table::do_rename_column(desc, column_ndx, name); // Throws } + static void set_link_type(Table& table, std::size_t column_ndx, LinkType link_type) + { + table.do_set_link_type(column_ndx, link_type); // Throws + } + static void clear_root_table_desc(const Table& root_table) TIGHTDB_NOEXCEPT { TIGHTDB_ASSERT(!root_table.has_shared_type()); @@ -1904,21 +2097,21 @@ class _impl::TableFriend { return table.get_link_target_table_accessor(col_ndx); } - static void adj_accessors_insert_rows(Table& table, std::size_t row_ndx, - std::size_t num_rows) TIGHTDB_NOEXCEPT + static void adj_acc_insert_rows(Table& table, std::size_t row_ndx, + std::size_t num_rows) TIGHTDB_NOEXCEPT { - table.adj_accessors_insert_rows(row_ndx, num_rows); + table.adj_acc_insert_rows(row_ndx, num_rows); } - static void adj_accessors_erase_row(Table& table, std::size_t row_ndx) TIGHTDB_NOEXCEPT + static void adj_acc_erase_row(Table& table, std::size_t row_ndx) TIGHTDB_NOEXCEPT { - table.adj_accessors_erase_row(row_ndx); + table.adj_acc_erase_row(row_ndx); } - static void adj_accessors_move(Table& table, std::size_t target_row_ndx, - std::size_t source_row_ndx) TIGHTDB_NOEXCEPT + static void adj_acc_move_over(Table& table, std::size_t from_row_ndx, + std::size_t to_row_ndx) TIGHTDB_NOEXCEPT { - table.adj_accessors_move(target_row_ndx, source_row_ndx); + table.adj_acc_move_over(from_row_ndx, to_row_ndx); } static void adj_acc_clear_root_table(Table& table) TIGHTDB_NOEXCEPT diff --git a/test/test_links.cpp b/test/test_links.cpp index 87008103efd..5a228437724 100644 --- a/test/test_links.cpp +++ b/test/test_links.cpp @@ -194,9 +194,8 @@ TEST(Links_Deletes) table2->insert_link(col_link, 1, 0); table2->insert_done(); - while (!table2->is_empty()) { + while (!table2->is_empty()) table2->move_last_over(0); - } CHECK(table2->is_empty()); CHECK_EQUAL(0, table1->get_backlink_count(0, *table2, col_link)); @@ -209,9 +208,8 @@ TEST(Links_Deletes) table2->insert_done(); // remove all rows in target table - while (!table1->is_empty()) { + while (!table1->is_empty()) table1->move_last_over(0); - } // verify that originating links was nullified CHECK(table2->is_null_link(col_link, 0)); @@ -923,4 +921,159 @@ TEST(Links_RandomizedOperations) } } + +TEST(Links_CascadeRemove_ColumnLink) +{ + Group group; + TableRef origin = group.add_table("origin"); + TableRef target = group.add_table("target"); + origin->add_column_link(type_Link, "o_1", *target, link_Strong); + target->add_column(type_Int, "t_1"); + origin->add_empty_row(2); + target->add_empty_row(2); + Row origin_row_0 = origin->get(0); + Row origin_row_1 = origin->get(1); + Row target_row_0 = target->get(0); + Row target_row_1 = target->get(1); + origin_row_0.set_link(0,0); // origin[0].o_1 -> target[0] + origin_row_1.set_link(0,1); // origin[1].o_1 -> target[1] + + // Break link by nullifying + CHECK(origin_row_0.get_link(0) == 0 && origin_row_1.get_link(0) == 1); + CHECK(target_row_0 && target_row_1); + origin_row_1.nullify_link(0); // origin[1].o_1 -> null + CHECK(target_row_0 && !target_row_1); + target->add_empty_row(); + target_row_1 = target->get(1); + origin_row_1.set_link(0,1); // origin[1].o_1 -> target[1] + + // Break link by reassign + CHECK(origin_row_0.get_link(0) == 0 && origin_row_1.get_link(0) == 1); + CHECK(target_row_0 && target_row_1); + origin_row_1.set_link(0,0); // origin[1].o_1 -> target[0] + CHECK(target_row_0 && !target_row_1); + target->add_empty_row(); + target_row_1 = target->get(1); + origin_row_1.set_link(0,1); // origin[1].o_1 -> target[1] + + // Avoid breaking link by reassigning self + CHECK(origin_row_0.get_link(0) == 0 && origin_row_1.get_link(0) == 1); + CHECK(target_row_0 && target_row_1); + origin_row_1.set_link(0,1); // No effective change! + CHECK(target_row_0 && target_row_1); + + // Break link by explicit row removal + CHECK(origin_row_0.get_link(0) == 0 && origin_row_1.get_link(0) == 1); + CHECK(target_row_0 && target_row_1); + origin_row_1.move_last_over(); + CHECK(target_row_0 && !target_row_1); + origin->add_empty_row(); + target->add_empty_row(); + origin_row_1 = origin->get(1); + target_row_1 = target->get(1); + origin_row_1.set_link(0,1); // origin[1].o_1 -> target[1] + + // Break link by clearing table + CHECK(origin_row_0.get_link(0) == 0 && origin_row_1.get_link(0) == 1); + CHECK(target_row_0 && target_row_1); + origin->clear(); + CHECK(!target_row_0 && !target_row_1); +} + + +TEST(Links_CascadeRemove_ColumnLinkList) +{ + Group group; + TableRef origin = group.add_table("origin"); + TableRef target = group.add_table("target"); + origin->add_column_link(type_LinkList, "o_1", *target, link_Strong); + target->add_column(type_Int, "t_1"); + origin->add_empty_row(2); + target->add_empty_row(2); + Row origin_row_0 = origin->get(0); + Row origin_row_1 = origin->get(1); + Row target_row_0 = target->get(0); + Row target_row_1 = target->get(1); + LinkViewRef link_list_0 = origin_row_0.get_linklist(0); + LinkViewRef link_list_1 = origin_row_1.get_linklist(0); + link_list_0->add(0); // origin[0].o_1 -> [ target[0] ] + link_list_1->add(0); // origin[1].o_1 -> [ target[0] ] + link_list_1->add(1); // origin[1].o_1 -> [ target[0], target[1] ] + + // Break link by clearing list + CHECK(link_list_0->size() == 1 && link_list_0->get(0).get_index() == 0); + CHECK(link_list_1->size() == 2 && link_list_1->get(0).get_index() == 0 && + link_list_1->get(1).get_index() == 1); + CHECK(target_row_0 && target_row_1); + link_list_1->clear(); // origin[1].o_1 -> [] + CHECK(target_row_0 && !target_row_1); + target->add_empty_row(); + target_row_1 = target->get(1); + link_list_1->add(0); // origin[1].o_1 -> [ target[0] ] + link_list_1->add(1); // origin[1].o_1 -> [ target[0], target[1] ] + + // Break link by removal from list + CHECK(link_list_0->size() == 1 && link_list_0->get(0).get_index() == 0); + CHECK(link_list_1->size() == 2 && link_list_1->get(0).get_index() == 0 && + link_list_1->get(1).get_index() == 1); + CHECK(target_row_0 && target_row_1); + link_list_1->remove(1); // origin[1].o_1 -> [ target[0] ] + CHECK(target_row_0 && !target_row_1); + target->add_empty_row(); + target_row_1 = target->get(1); + link_list_1->add(1); // origin[1].o_1 -> [ target[0], target[1] ] + + // Break link by reassign + CHECK(link_list_0->size() == 1 && link_list_0->get(0).get_index() == 0); + CHECK(link_list_1->size() == 2 && link_list_1->get(0).get_index() == 0 && + link_list_1->get(1).get_index() == 1); + CHECK(target_row_0 && target_row_1); + link_list_1->set(1,0); // origin[1].o_1 -> [ target[0], target[0] ] + CHECK(target_row_0 && !target_row_1); + target->add_empty_row(); + target_row_1 = target->get(1); + link_list_1->set(1,1); // origin[1].o_1 -> [ target[0], target[1] ] + + // Avoid breaking link by reassigning self + CHECK(link_list_0->size() == 1 && link_list_0->get(0).get_index() == 0); + CHECK(link_list_1->size() == 2 && link_list_1->get(0).get_index() == 0 && + link_list_1->get(1).get_index() == 1); + CHECK(target_row_0 && target_row_1); + link_list_1->set(1,1); // No effective change! + CHECK(target_row_0 && target_row_1); + + // Break link by explicit row removal + CHECK(link_list_0->size() == 1 && link_list_0->get(0).get_index() == 0); + CHECK(link_list_1->size() == 2 && link_list_1->get(0).get_index() == 0 && + link_list_1->get(1).get_index() == 1); + CHECK(target_row_0 && target_row_1); + origin_row_1.move_last_over(); + CHECK(target_row_0 && !target_row_1); + origin->add_empty_row(); + target->add_empty_row(); + origin_row_1 = origin->get(1); + target_row_1 = target->get(1); + link_list_1 = origin_row_1.get_linklist(0); + link_list_1->add(0); // origin[1].o_1 -> [ target[0] ] + link_list_1->add(1); // origin[1].o_1 -> [ target[0], target[1] ] + + // Break link by clearing table + CHECK(link_list_0->size() == 1 && link_list_0->get(0).get_index() == 0); + CHECK(link_list_1->size() == 2 && link_list_1->get(0).get_index() == 0 && + link_list_1->get(1).get_index() == 1); + CHECK(target_row_0 && target_row_1); + origin->clear(); + CHECK(!target_row_0 && !target_row_1); +} + + +TEST(Links_CascadeRemove_MultiLevel) +{ +} + + +TEST(Links_CascadeRemove_Cycles) +{ +} + #endif // TEST_LINKS diff --git a/test/test_table.cpp b/test/test_table.cpp index 48a9995f459..8929a6ac1fc 100644 --- a/test/test_table.cpp +++ b/test/test_table.cpp @@ -4629,23 +4629,23 @@ TEST(Table_RowAccessorLinks) TableRef target_table = group.add_table("target"); target_table->add_column(type_Int, ""); target_table->add_empty_row(16); - TableRef source_table = group.add_table("source"); - source_table->add_column_link(type_Link, "", *target_table); - source_table->add_column_link(type_LinkList, "", *target_table); - source_table->add_empty_row(2); + TableRef origin_table = group.add_table("origin"); + origin_table->add_column_link(type_Link, "", *target_table); + origin_table->add_column_link(type_LinkList, "", *target_table); + origin_table->add_empty_row(2); - Row source_row_1 = source_table->get(0); - Row source_row_2 = source_table->get(1); + Row source_row_1 = origin_table->get(0); + Row source_row_2 = origin_table->get(1); CHECK(source_row_1.is_null_link(0)); CHECK(source_row_2.is_null_link(0)); CHECK(source_row_1.linklist_is_empty(1)); CHECK(source_row_2.linklist_is_empty(1)); CHECK_EQUAL(0, source_row_1.get_link_count(1)); CHECK_EQUAL(0, source_row_2.get_link_count(1)); - CHECK_EQUAL(0, target_table->get(7).get_backlink_count(*source_table, 0)); - CHECK_EQUAL(0, target_table->get(13).get_backlink_count(*source_table, 0)); - CHECK_EQUAL(0, target_table->get(11).get_backlink_count(*source_table, 1)); - CHECK_EQUAL(0, target_table->get(15).get_backlink_count(*source_table, 1)); + CHECK_EQUAL(0, target_table->get(7).get_backlink_count(*origin_table, 0)); + CHECK_EQUAL(0, target_table->get(13).get_backlink_count(*origin_table, 0)); + CHECK_EQUAL(0, target_table->get(11).get_backlink_count(*origin_table, 1)); + CHECK_EQUAL(0, target_table->get(15).get_backlink_count(*origin_table, 1)); // Set links source_row_1.set_link(0, 7); @@ -4654,18 +4654,18 @@ TEST(Table_RowAccessorLinks) CHECK(!source_row_2.is_null_link(0)); CHECK_EQUAL(7, source_row_1.get_link(0)); CHECK_EQUAL(13, source_row_2.get_link(0)); - CHECK_EQUAL(1, target_table->get(7).get_backlink_count(*source_table, 0)); - CHECK_EQUAL(1, target_table->get(13).get_backlink_count(*source_table, 0)); - CHECK_EQUAL(0, target_table->get(7).get_backlink(*source_table, 0, 0)); - CHECK_EQUAL(1, target_table->get(13).get_backlink(*source_table, 0, 0)); + CHECK_EQUAL(1, target_table->get(7).get_backlink_count(*origin_table, 0)); + CHECK_EQUAL(1, target_table->get(13).get_backlink_count(*origin_table, 0)); + CHECK_EQUAL(0, target_table->get(7).get_backlink(*origin_table, 0, 0)); + CHECK_EQUAL(1, target_table->get(13).get_backlink(*origin_table, 0, 0)); // Nullify links source_row_1.nullify_link(0); source_row_2.nullify_link(0); CHECK(source_row_1.is_null_link(0)); CHECK(source_row_2.is_null_link(0)); - CHECK_EQUAL(0, target_table->get(7).get_backlink_count(*source_table, 0)); - CHECK_EQUAL(0, target_table->get(13).get_backlink_count(*source_table, 0)); + CHECK_EQUAL(0, target_table->get(7).get_backlink_count(*origin_table, 0)); + CHECK_EQUAL(0, target_table->get(13).get_backlink_count(*origin_table, 0)); // Add stuff to link lists LinkViewRef link_list_1 = source_row_1.get_linklist(1); @@ -4677,11 +4677,11 @@ TEST(Table_RowAccessorLinks) CHECK(!source_row_2.linklist_is_empty(1)); CHECK_EQUAL(1, source_row_1.get_link_count(1)); CHECK_EQUAL(2, source_row_2.get_link_count(1)); - CHECK_EQUAL(1, target_table->get(11).get_backlink_count(*source_table, 1)); - CHECK_EQUAL(2, target_table->get(15).get_backlink_count(*source_table, 1)); - CHECK_EQUAL(1, target_table->get(11).get_backlink(*source_table, 1, 0)); - size_t back_link_1 = target_table->get(15).get_backlink(*source_table, 1, 0); - size_t back_link_2 = target_table->get(15).get_backlink(*source_table, 1, 1); + CHECK_EQUAL(1, target_table->get(11).get_backlink_count(*origin_table, 1)); + CHECK_EQUAL(2, target_table->get(15).get_backlink_count(*origin_table, 1)); + CHECK_EQUAL(1, target_table->get(11).get_backlink(*origin_table, 1, 0)); + size_t back_link_1 = target_table->get(15).get_backlink(*origin_table, 1, 0); + size_t back_link_2 = target_table->get(15).get_backlink(*origin_table, 1, 1); CHECK((back_link_1 == 0 && back_link_2 == 1) || (back_link_1 == 1 && back_link_2 == 0)); // Clear link lists @@ -4691,8 +4691,8 @@ TEST(Table_RowAccessorLinks) CHECK(source_row_2.linklist_is_empty(1)); CHECK_EQUAL(0, source_row_1.get_link_count(1)); CHECK_EQUAL(0, source_row_2.get_link_count(1)); - CHECK_EQUAL(0, target_table->get(11).get_backlink_count(*source_table, 1)); - CHECK_EQUAL(0, target_table->get(15).get_backlink_count(*source_table, 1)); + CHECK_EQUAL(0, target_table->get(11).get_backlink_count(*origin_table, 1)); + CHECK_EQUAL(0, target_table->get(15).get_backlink_count(*origin_table, 1)); }