1 /*************************************************************************
3 * Copyright 2016 Realm Inc.
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
9 * http://www.apache.org/licenses/LICENSE-2.0
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
17 **************************************************************************/
19 #ifndef REALM_BPTREE_HPP
20 #define REALM_BPTREE_HPP
22 #include <memory> // std::unique_ptr
23 #include <realm/array.hpp>
24 #include <realm/array_basic.hpp>
25 #include <realm/column_type_traits.hpp>
26 #include <realm/impl/destroy_guard.hpp>
27 #include <realm/impl/output_stream.hpp>
31 /// Specialize BpTree to implement column types.
38 class BpTreeNode : public Array {
41 /// Get the number of elements in the B+-tree rooted at this array
42 /// node. The root must not be a leaf.
44 /// Please avoid using this function (consider it deprecated). It
45 /// will have to be removed if we choose to get rid of the last
46 /// element of the main array of an inner B+-tree node that stores
47 /// the total number of elements in the subtree. The motivation
48 /// for removing it, is that it will significantly improve the
49 /// efficiency when inserting after, and erasing the last element.
50 size_t get_bptree_size() const noexcept;
52 /// The root must not be a leaf.
53 static size_t get_bptree_size_from_header(const char* root_header) noexcept;
56 /// Find the leaf node corresponding to the specified element
57 /// index index. The specified element index must refer to an
58 /// element that exists in the tree. This function must be called
59 /// on an inner B+-tree node, never a leaf. Note that according to
60 /// invar:bptree-nonempty-inner and invar:bptree-nonempty-leaf, an
61 /// inner B+-tree node can never be empty.
63 /// This function is not obliged to instantiate intermediate array
64 /// accessors. For this reason, this function cannot be used for
65 /// operations that modify the tree, as that requires an unbroken
66 /// chain of parent array accessors between the root and the
67 /// leaf. Thus, despite the fact that the returned MemRef object
68 /// appears to allow modification of the referenced memory, the
69 /// caller must handle the memory reference as if it was
72 /// \return (`leaf_header`, `ndx_in_leaf`) where `leaf_header`
73 /// points to the the header of the located leaf, and
74 /// `ndx_in_leaf` is the local index within that leaf
75 /// corresponding to the specified element index.
76 std::pair<MemRef, size_t> get_bptree_leaf(size_t elem_ndx) const noexcept;
82 /// Visit leaves of the B+-tree rooted at this inner node,
83 /// starting with the leaf that contains the element at the
84 /// specified element index start offset, and ending when the
85 /// handler returns false. The specified element index offset must
86 /// refer to an element that exists in the tree. This function
87 /// must be called on an inner B+-tree node, never a leaf. Note
88 /// that according to invar:bptree-nonempty-inner and
89 /// invar:bptree-nonempty-leaf, an inner B+-tree node can never be
92 /// \param elem_ndx_offset The start position (must be valid).
94 /// \param elems_in_tree The total number of elements in the tree.
96 /// \param handler The callback which will get called for each leaf.
98 /// \return True if, and only if the handler has returned true for
99 /// all visited leafs.
100 bool visit_bptree_leaves(size_t elem_ndx_offset, size_t elems_in_tree, VisitHandler& handler);
105 /// Call the handler for every leaf. This function must be called
106 /// on an inner B+-tree node, never a leaf.
107 void update_bptree_leaves(UpdateHandler&);
109 /// Call the handler for the leaf that contains the element at the
110 /// specified index. This function must be called on an inner
111 /// B+-tree node, never a leaf.
112 void update_bptree_elem(size_t elem_ndx, UpdateHandler&);
117 /// Erase the element at the specified index in the B+-tree with
118 /// the specified root. When erasing the last element, you must
119 /// pass npos in place of the index. This function must be called
120 /// with a root that is an inner B+-tree node, never a leaf.
122 /// This function is guaranteed to succeed (not throw) if the
123 /// specified element was inserted during the current transaction,
124 /// and no other modifying operation has been carried out since
125 /// then (noexcept:bptree-erase-alt).
127 /// FIXME: ExceptionSafety: The exception guarantee explained
128 /// above is not as powerfull as we would like it to be. Here is
129 /// what we would like: This function is guaranteed to succeed
130 /// (not throw) if the specified element was inserted during the
131 /// current transaction (noexcept:bptree-erase). This must be true
132 /// even if the element is modified after insertion, and/or if
133 /// other elements are inserted or erased around it. There are two
134 /// aspects of the current design that stand in the way of this
135 /// guarantee: (A) The fact that the node accessor, that is cached
136 /// in the column accessor, has to be reallocated/reinstantiated
137 /// when the root switches between being a leaf and an inner
138 /// node. This problem would go away if we always cached the last
139 /// used leaf accessor in the column accessor instead. (B) The
140 /// fact that replacing one child ref with another can fail,
141 /// because it may require reallocation of memory to expand the
142 /// bit-width. This can be fixed in two ways: Either have the
143 /// inner B+-tree nodes always have a bit-width of 64, or allow
144 /// the root node to be discarded and the column ref to be set to
145 /// zero in Table::m_columns.
146 static void erase_bptree_elem(BpTreeNode* root, size_t elem_ndx, EraseHandler&);
148 template <class TreeTraits>
149 struct TreeInsert : TreeInsertBase {
150 typename TreeTraits::value_type m_value;
154 /// Same as bptree_insert() but insert after the last element.
155 template <class TreeTraits>
156 ref_type bptree_append(TreeInsert<TreeTraits>& state);
158 /// Insert an element into the B+-subtree rooted at this array
159 /// node. The element is inserted before the specified element
160 /// index. This function must be called on an inner B+-tree node,
161 /// never a leaf. If this inner node had to be split, this
162 /// function returns the `ref` of the new sibling.
163 template <class TreeTraits>
164 ref_type bptree_insert(size_t elem_ndx, TreeInsert<TreeTraits>& state);
167 /// Insert a new child after original. If the parent has to be
168 /// split, this function returns the `ref` of the new parent node.
169 ref_type insert_bptree_child(Array& offsets, size_t orig_child_ndx, ref_type new_sibling_ref,
170 TreeInsertBase& state);
172 void ensure_bptree_offsets(Array& offsets);
173 void create_bptree_offsets(Array& offsets, int_fast64_t first_value);
175 bool do_erase_bptree_elem(size_t elem_ndx, EraseHandler&);
180 struct unattached_tag {
183 // Disable copying, this is not allowed.
184 BpTreeBase& operator=(const BpTreeBase&) = delete;
185 BpTreeBase(const BpTreeBase&) = delete;
188 Allocator& get_alloc() const noexcept;
189 void destroy() noexcept;
191 bool is_attached() const noexcept;
192 void set_parent(ArrayParent* parent, size_t ndx_in_parent) noexcept;
193 size_t get_ndx_in_parent() const noexcept;
194 void set_ndx_in_parent(size_t ndx) noexcept;
195 void update_from_parent(size_t old_baseline) noexcept;
196 MemRef clone_deep(Allocator& alloc) const;
199 const Array& root() const noexcept;
200 Array& root() noexcept;
201 bool root_is_leaf() const noexcept;
202 BpTreeNode& root_as_node();
203 const BpTreeNode& root_as_node() const;
204 void introduce_new_root(ref_type new_sibling_ref, TreeInsertBase& state, bool is_append);
205 void replace_root(std::unique_ptr<Array> leaf);
208 explicit BpTreeBase(std::unique_ptr<Array> root);
209 explicit BpTreeBase(BpTreeBase&&) = default;
210 BpTreeBase& operator=(BpTreeBase&&) = default;
211 std::unique_ptr<Array> m_root;
213 struct SliceHandler {
214 virtual MemRef slice_leaf(MemRef leaf_mem, size_t offset, size_t size, Allocator& target_alloc) = 0;
215 ~SliceHandler() noexcept
219 static ref_type write_subtree(const BpTreeNode& root, size_t slice_offset, size_t slice_size, size_t table_size,
220 SliceHandler&, _impl::OutputStream&);
221 friend class ColumnBase;
222 friend class ColumnBaseSimple;
225 struct WriteSliceHandler;
227 // FIXME: Move B+Tree functionality from Array to this class.
231 // Default implementation of BpTree. This should work for all types that have monomorphic
232 // leaves (i.e. all leaves are of the same type).
234 class BpTree : public BpTreeBase {
236 using value_type = T;
237 using LeafType = typename ColumnTypeTraits<T>::leaf_type;
239 /// LeafInfo is used by get_leaf() to provide access to a leaf
240 /// without instantiating unnecessary nodes along the way.
241 /// Upon return, out_leaf with hold a pointer to the leaf containing
242 /// the index given to get_leaf(). If the index happens to be
243 /// in the root node (i.e., the root is a leaf), it will point
244 /// to the root node.
245 /// If the index isn't in the root node, fallback will be initialized
246 /// to represent the leaf holding the node, and out_leaf will be set
247 /// to point to fallback.
249 const LeafType** out_leaf;
254 explicit BpTree(BpTreeBase::unattached_tag);
255 explicit BpTree(Allocator& alloc);
256 REALM_DEPRECATED("Initialize with MemRef instead")
257 explicit BpTree(std::unique_ptr<Array> init_root)
258 : BpTreeBase(std::move(init_root))
261 explicit BpTree(Allocator& alloc, MemRef mem)
262 : BpTreeBase(std::unique_ptr<Array>(new LeafType(alloc)))
264 init_from_mem(alloc, mem);
266 BpTree(BpTree&&) = default;
267 BpTree& operator=(BpTree&&) = default;
269 // Disable copying, this is not allowed.
270 BpTree& operator=(const BpTree&) = delete;
271 BpTree(const BpTree&) = delete;
273 void init_from_ref(Allocator& alloc, ref_type ref);
274 void init_from_mem(Allocator& alloc, MemRef mem);
275 void init_from_parent();
277 size_t size() const noexcept;
278 bool is_empty() const noexcept
283 T get(size_t ndx) const noexcept;
284 bool is_null(size_t ndx) const noexcept;
285 void set(size_t, T value);
286 void set_null(size_t);
287 void insert(size_t ndx, T value, size_t num_rows = 1);
288 void erase(size_t ndx, bool is_last = false);
289 void move_last_over(size_t ndx, size_t last_row_ndx);
291 T front() const noexcept;
292 T back() const noexcept;
294 size_t find_first(T value, size_t begin = 0, size_t end = npos) const;
295 void find_all(IntegerColumn& out_indices, T value, size_t begin = 0, size_t end = npos) const;
297 static MemRef create_leaf(Array::Type leaf_type, size_t size, T value, Allocator&);
299 /// See LeafInfo for information about what to put in the inout_leaf
302 /// This function cannot be used for modifying operations as it
303 /// does not ensure the presence of an unbroken chain of parent
304 /// accessors. For this reason, the identified leaf should always
305 /// be accessed through the returned const-qualified reference,
306 /// and never directly through the specfied fallback accessor.
307 void get_leaf(size_t ndx, size_t& out_ndx_in_leaf, LeafInfo& inout_leaf) const noexcept;
309 void update_each(BpTreeNode::UpdateHandler&);
310 void update_elem(size_t, BpTreeNode::UpdateHandler&);
312 void adjust(size_t ndx, T diff);
314 void adjust_ge(T limit, T diff);
316 ref_type write(size_t slice_offset, size_t slice_size, size_t table_size, _impl::OutputStream& out) const;
318 #if defined(REALM_DEBUG)
320 static size_t verify_leaf(MemRef mem, Allocator& alloc);
322 static void leaf_to_dot(MemRef mem, ArrayParent* parent, size_t ndx_in_parent, std::ostream& out,
326 LeafType& root_as_leaf();
327 const LeafType& root_as_leaf() const;
329 std::unique_ptr<Array> create_root_from_ref(Allocator& alloc, ref_type ref);
330 std::unique_ptr<Array> create_root_from_mem(Allocator& alloc, MemRef mem);
333 struct UpdateHandler;
334 struct SetNullHandler;
336 struct AdjustHandler;
337 struct AdjustGEHandler;
339 struct LeafValueInserter;
340 struct LeafNullInserter;
342 template <class TreeTraits>
343 void bptree_insert(size_t row_ndx, BpTreeNode::TreeInsert<TreeTraits>& state, size_t num_rows);
347 class BpTreeNode::NodeInfo {
351 size_t m_ndx_in_parent;
352 size_t m_offset, m_size;
355 class BpTreeNode::VisitHandler {
357 virtual bool visit(const NodeInfo& leaf_info) = 0;
358 virtual ~VisitHandler() noexcept
364 class BpTreeNode::UpdateHandler {
366 virtual void update(MemRef, ArrayParent*, size_t leaf_ndx_in_parent, size_t elem_ndx_in_leaf) = 0;
367 virtual ~UpdateHandler() noexcept
373 class BpTreeNode::EraseHandler {
375 /// If the specified leaf has more than one element, this function
376 /// must erase the specified element from the leaf and return
377 /// false. Otherwise, when the leaf has a single element, this
378 /// function must return true without modifying the leaf. If \a
379 /// elem_ndx_in_leaf is `npos`, it refers to the last element in
380 /// the leaf. The implementation of this function must be
381 /// exception safe. This function is guaranteed to be called at
382 /// most once during each execution of Array::erase_bptree_elem(),
383 /// and *exactly* once during each *successful* execution of
384 /// Array::erase_bptree_elem().
385 virtual bool erase_leaf_elem(MemRef, ArrayParent*, size_t leaf_ndx_in_parent, size_t elem_ndx_in_leaf) = 0;
387 virtual void destroy_leaf(MemRef leaf_mem) noexcept = 0;
389 /// Must replace the current root with the specified leaf. The
390 /// implementation of this function must not destroy the
391 /// underlying root node, or any of its children, as that will be
392 /// done by Array::erase_bptree_elem(). The implementation of this
393 /// function must be exception safe.
394 virtual void replace_root_by_leaf(MemRef leaf_mem) = 0;
396 /// Same as replace_root_by_leaf(), but must replace the root with
397 /// an empty leaf. Also, if this function is called during an
398 /// execution of Array::erase_bptree_elem(), it is guaranteed that
399 /// it will be preceeded by a call to erase_leaf_elem().
400 virtual void replace_root_by_empty_leaf() = 0;
402 virtual ~EraseHandler() noexcept
410 inline BpTreeBase::BpTreeBase(std::unique_ptr<Array> init_root)
411 : m_root(std::move(init_root))
415 inline Allocator& BpTreeBase::get_alloc() const noexcept
417 return m_root->get_alloc();
420 inline void BpTreeBase::destroy() noexcept
423 m_root->destroy_deep();
426 inline void BpTreeBase::detach()
431 inline bool BpTreeBase::is_attached() const noexcept
433 return m_root->is_attached();
436 inline bool BpTreeBase::root_is_leaf() const noexcept
438 return !m_root->is_inner_bptree_node();
441 inline BpTreeNode& BpTreeBase::root_as_node()
443 REALM_ASSERT_DEBUG(!root_is_leaf());
444 REALM_ASSERT_DEBUG(dynamic_cast<BpTreeNode*>(m_root.get()) != nullptr);
445 return static_cast<BpTreeNode&>(root());
448 inline const BpTreeNode& BpTreeBase::root_as_node() const
450 Array* arr = m_root.get();
451 REALM_ASSERT_DEBUG(!root_is_leaf());
452 REALM_ASSERT_DEBUG(dynamic_cast<const BpTreeNode*>(arr) != nullptr);
453 return static_cast<const BpTreeNode&>(*arr);
456 inline void BpTreeBase::set_parent(ArrayParent* parent, size_t ndx_in_parent) noexcept
458 m_root->set_parent(parent, ndx_in_parent);
461 inline size_t BpTreeBase::get_ndx_in_parent() const noexcept
463 return m_root->get_ndx_in_parent();
466 inline void BpTreeBase::set_ndx_in_parent(size_t ndx) noexcept
468 m_root->set_ndx_in_parent(ndx);
471 inline void BpTreeBase::update_from_parent(size_t old_baseline) noexcept
473 m_root->update_from_parent(old_baseline);
476 inline MemRef BpTreeBase::clone_deep(Allocator& alloc) const
478 return m_root->clone_deep(alloc);
481 inline const Array& BpTreeBase::root() const noexcept
486 inline Array& BpTreeBase::root() noexcept
491 inline size_t BpTreeNode::get_bptree_size() const noexcept
493 REALM_ASSERT_DEBUG(is_inner_bptree_node());
494 int_fast64_t v = back();
495 return size_t(v / 2); // v = 1 + 2*total_elems_in_tree
498 inline size_t BpTreeNode::get_bptree_size_from_header(const char* root_header) noexcept
500 REALM_ASSERT_DEBUG(get_is_inner_bptree_node_from_header(root_header));
501 size_t root_size = get_size_from_header(root_header);
502 int_fast64_t v = get(root_header, root_size - 1);
503 return size_t(v / 2); // v = 1 + 2*total_elems_in_tree
506 inline void BpTreeNode::ensure_bptree_offsets(Array& offsets)
508 int_fast64_t first_value = get(0);
509 if (first_value % 2 == 0) {
510 offsets.init_from_ref(to_ref(first_value));
513 create_bptree_offsets(offsets, first_value); // Throws
515 offsets.set_parent(this, 0);
519 template <class TreeTraits>
520 ref_type BpTreeNode::bptree_append(TreeInsert<TreeTraits>& state)
522 // FIXME: Consider exception safety. Especially, how can the split
523 // be carried out in an exception safe manner?
525 // Can split be done as a separate preparation step, such that if
526 // the actual insert fails, the split will still have occured.
528 // Unfortunately, it requires a rather significant rearrangement
529 // of the insertion flow. Instead of returning the sibling ref
530 // from insert functions, the leaf-insert functions must instead
531 // call the special bptree_insert() function on the parent, which
532 // will then cascade the split towards the root as required.
534 // At each level where a split is required (starting at the leaf):
536 // 1. Create the new sibling.
538 // 2. Copy relevant entries over such that new sibling is in
541 // 3. Call Array::bptree_insert() on parent with sibling ref.
543 // 4. Rearrange entries in original sibling and truncate as
544 // required (must not throw).
546 // What about the 'offsets' array? It will always be
547 // present. Consider this carefully.
549 REALM_ASSERT_DEBUG(size() >= 1 + 1 + 1); // At least one child
551 ArrayParent& childs_parent = *this;
552 size_t child_ref_ndx = size() - 2;
553 ref_type child_ref = get_as_ref(child_ref_ndx), new_sibling_ref;
554 char* child_header = static_cast<char*>(m_alloc.translate(child_ref));
556 bool child_is_leaf = !get_is_inner_bptree_node_from_header(child_header);
558 size_t elem_ndx_in_child = npos; // Append
559 new_sibling_ref = TreeTraits::leaf_insert(MemRef(child_header, child_ref, m_alloc), childs_parent,
560 child_ref_ndx, m_alloc, elem_ndx_in_child, state); // Throws
563 BpTreeNode child(m_alloc);
564 child.init_from_mem(MemRef(child_header, child_ref, m_alloc));
565 child.set_parent(&childs_parent, child_ref_ndx);
566 new_sibling_ref = child.bptree_append(state); // Throws
569 if (REALM_LIKELY(!new_sibling_ref)) {
570 // +2 because stored value is 1 + 2*total_elems_in_subtree
571 adjust(size() - 1, +2); // Throws
572 return 0; // Child was not split, so parent was not split either
575 Array offsets(m_alloc);
576 int_fast64_t first_value = get(0);
577 if (first_value % 2 == 0) {
578 // Offsets array is present (general form)
579 offsets.init_from_ref(to_ref(first_value));
580 offsets.set_parent(this, 0);
582 size_t child_ndx = child_ref_ndx - 1;
583 return insert_bptree_child(offsets, child_ndx, new_sibling_ref, state); // Throws
587 template <class TreeTraits>
588 ref_type BpTreeNode::bptree_insert(size_t elem_ndx, TreeInsert<TreeTraits>& state)
590 REALM_ASSERT_3(size(), >=, 1 + 1 + 1); // At least one child
592 // Conversion to general form if in compact form. Since this
593 // conversion will occur from root to leaf, it will maintain
594 // invar:bptree-node-form.
595 Array offsets(m_alloc);
596 ensure_bptree_offsets(offsets); // Throws
598 size_t child_ndx, elem_ndx_in_child;
600 // Optimization for prepend
602 elem_ndx_in_child = 0;
605 // There is a choice to be made when the element is to be
606 // inserted between two subtrees. It can either be appended to
607 // the first subtree, or it can be prepended to the second
608 // one. We currently always append to the first subtree. It is
609 // essentially a matter of using the lower vs. the upper bound
610 // when searching through the offsets array.
611 child_ndx = offsets.lower_bound_int(elem_ndx);
612 REALM_ASSERT_3(child_ndx, <, size() - 2);
613 size_t elem_ndx_offset = child_ndx == 0 ? 0 : to_size_t(offsets.get(child_ndx - 1));
614 elem_ndx_in_child = elem_ndx - elem_ndx_offset;
617 ArrayParent& childs_parent = *this;
618 size_t child_ref_ndx = child_ndx + 1;
619 ref_type child_ref = get_as_ref(child_ref_ndx), new_sibling_ref;
620 char* child_header = static_cast<char*>(m_alloc.translate(child_ref));
621 bool child_is_leaf = !get_is_inner_bptree_node_from_header(child_header);
623 REALM_ASSERT_3(elem_ndx_in_child, <=, REALM_MAX_BPNODE_SIZE);
624 new_sibling_ref = TreeTraits::leaf_insert(MemRef(child_header, child_ref, m_alloc), childs_parent,
625 child_ref_ndx, m_alloc, elem_ndx_in_child, state); // Throws
628 BpTreeNode child(m_alloc);
629 child.init_from_mem(MemRef(child_header, child_ref, m_alloc));
630 child.set_parent(&childs_parent, child_ref_ndx);
631 new_sibling_ref = child.bptree_insert(elem_ndx_in_child, state); // Throws
634 if (REALM_LIKELY(!new_sibling_ref)) {
635 // +2 because stored value is 1 + 2*total_elems_in_subtree
636 adjust(size() - 1, +2); // Throws
637 offsets.adjust(child_ndx, offsets.size(), +1);
638 return 0; // Child was not split, so parent was not split either
641 return insert_bptree_child(offsets, child_ndx, new_sibling_ref, state); // Throws
646 : BpTree(Allocator::get_default())
651 BpTree<T>::BpTree(Allocator& alloc)
652 : BpTreeBase(std::unique_ptr<Array>(new LeafType(alloc)))
657 BpTree<T>::BpTree(BpTreeBase::unattached_tag)
658 : BpTreeBase(nullptr)
663 std::unique_ptr<Array> BpTree<T>::create_root_from_mem(Allocator& alloc, MemRef mem)
665 const char* header = mem.get_addr();
666 std::unique_ptr<Array> new_root;
667 bool is_inner_bptree_node = Array::get_is_inner_bptree_node_from_header(header);
669 bool can_reuse_root_accessor =
670 m_root && &m_root->get_alloc() == &alloc && m_root->is_inner_bptree_node() == is_inner_bptree_node;
671 if (can_reuse_root_accessor) {
672 if (is_inner_bptree_node) {
673 m_root->init_from_mem(mem);
676 static_cast<LeafType&>(*m_root).init_from_mem(mem);
678 return std::move(m_root); // Same root will be reinstalled.
681 // Not reusing root note, allocating a new one.
682 if (is_inner_bptree_node) {
683 new_root.reset(new BpTreeNode{alloc});
684 new_root->init_from_mem(mem);
687 std::unique_ptr<LeafType> leaf{new LeafType{alloc}};
688 leaf->init_from_mem(mem);
689 new_root = std::move(leaf);
695 std::unique_ptr<Array> BpTree<T>::create_root_from_ref(Allocator& alloc, ref_type ref)
697 MemRef mem = MemRef{alloc.translate(ref), ref, alloc};
698 return create_root_from_mem(alloc, mem);
702 void BpTree<T>::init_from_ref(Allocator& alloc, ref_type ref)
704 auto new_root = create_root_from_ref(alloc, ref);
705 replace_root(std::move(new_root));
709 void BpTree<T>::init_from_mem(Allocator& alloc, MemRef mem)
711 auto new_root = create_root_from_mem(alloc, mem);
712 replace_root(std::move(new_root));
716 void BpTree<T>::init_from_parent()
718 ref_type ref = root().get_ref_from_parent();
720 ArrayParent* parent = m_root->get_parent();
721 size_t ndx_in_parent = m_root->get_ndx_in_parent();
722 auto new_root = create_root_from_ref(get_alloc(), ref);
723 new_root->set_parent(parent, ndx_in_parent);
724 m_root = std::move(new_root);
732 typename BpTree<T>::LeafType& BpTree<T>::root_as_leaf()
734 REALM_ASSERT_DEBUG(root_is_leaf());
735 REALM_ASSERT_DEBUG(dynamic_cast<LeafType*>(m_root.get()) != nullptr);
736 return static_cast<LeafType&>(root());
740 const typename BpTree<T>::LeafType& BpTree<T>::root_as_leaf() const
742 REALM_ASSERT_DEBUG(root_is_leaf());
743 REALM_ASSERT_DEBUG(dynamic_cast<const LeafType*>(m_root.get()) != nullptr);
744 return static_cast<const LeafType&>(root());
748 size_t BpTree<T>::size() const noexcept
750 if (root_is_leaf()) {
751 return root_as_leaf().size();
753 return root_as_node().get_bptree_size();
757 T BpTree<T>::back() const noexcept
760 return get(size() - 1);
765 // NullableOrNothing encapsulates the behavior of nullable and
766 // non-nullable leaf types, so that non-nullable leaf types
767 // don't have to implement is_null/set_null but BpTree can still
768 // support the interface (and return false / assert when null
769 // is not supported).
770 template <class Leaf>
771 struct NullableOrNothing {
772 static bool is_null(const Leaf& leaf, size_t ndx)
774 return leaf.is_null(ndx);
776 static void set_null(Leaf& leaf, size_t ndx)
782 struct NullableOrNothing<ArrayInteger> {
783 static bool is_null(const ArrayInteger&, size_t)
787 static void set_null(ArrayInteger&, size_t)
789 REALM_ASSERT_RELEASE(false);
795 bool BpTree<T>::is_null(size_t ndx) const noexcept
797 if (root_is_leaf()) {
798 return _impl::NullableOrNothing<LeafType>::is_null(root_as_leaf(), ndx);
800 LeafType fallback(get_alloc());
801 const LeafType* leaf;
802 LeafInfo leaf_info{&leaf, &fallback};
804 get_leaf(ndx, ndx_in_leaf, leaf_info);
805 return _impl::NullableOrNothing<LeafType>::is_null(*leaf, ndx_in_leaf);
809 T BpTree<T>::get(size_t ndx) const noexcept
811 REALM_ASSERT_DEBUG_EX(ndx < size(), ndx, size());
812 if (root_is_leaf()) {
813 return root_as_leaf().get(ndx);
816 // Use direct getter to avoid initializing leaf array:
817 std::pair<MemRef, size_t> p = root_as_node().get_bptree_leaf(ndx);
818 const char* leaf_header = p.first.get_addr();
819 size_t ndx_in_leaf = p.second;
820 return LeafType::get(leaf_header, ndx_in_leaf);
824 template <class TreeTraits>
825 void BpTree<T>::bptree_insert(size_t row_ndx, BpTreeNode::TreeInsert<TreeTraits>& state, size_t num_rows)
827 ref_type new_sibling_ref;
828 for (size_t i = 0; i < num_rows; ++i) {
829 size_t row_ndx_2 = row_ndx == realm::npos ? realm::npos : row_ndx + i;
830 if (root_is_leaf()) {
831 REALM_ASSERT_DEBUG(row_ndx_2 == realm::npos || row_ndx_2 < REALM_MAX_BPNODE_SIZE);
832 new_sibling_ref = root_as_leaf().bptree_leaf_insert(row_ndx_2, state.m_value, state);
835 if (row_ndx_2 == realm::npos) {
836 new_sibling_ref = root_as_node().bptree_append(state); // Throws
839 new_sibling_ref = root_as_node().bptree_insert(row_ndx_2, state); // Throws
843 if (REALM_UNLIKELY(new_sibling_ref)) {
844 bool is_append = row_ndx_2 == realm::npos;
845 introduce_new_root(new_sibling_ref, state, is_append);
851 struct BpTree<T>::LeafValueInserter {
852 using value_type = T;
854 LeafValueInserter(T value)
855 : m_value(std::move(value))
859 // TreeTraits concept:
860 static ref_type leaf_insert(MemRef leaf_mem, ArrayParent& parent, size_t ndx_in_parent, Allocator& alloc,
861 size_t ndx_in_leaf, BpTreeNode::TreeInsert<LeafValueInserter>& state)
863 LeafType leaf{alloc};
864 leaf.init_from_mem(leaf_mem);
865 leaf.set_parent(&parent, ndx_in_parent);
866 // Should not move out of m_value, because the same inserter may be used to perform
867 // multiple insertions (for example, if num_rows > 1).
868 return leaf.bptree_leaf_insert(ndx_in_leaf, state.m_value, state);
873 struct BpTree<T>::LeafNullInserter {
874 using value_type = null;
875 // TreeTraits concept:
876 static ref_type leaf_insert(MemRef leaf_mem, ArrayParent& parent, size_t ndx_in_parent, Allocator& alloc,
877 size_t ndx_in_leaf, BpTreeNode::TreeInsert<LeafNullInserter>& state)
879 LeafType leaf{alloc};
880 leaf.init_from_mem(leaf_mem);
881 leaf.set_parent(&parent, ndx_in_parent);
882 return leaf.bptree_leaf_insert(ndx_in_leaf, null{}, state);
887 void BpTree<T>::insert(size_t row_ndx, T value, size_t num_rows)
889 REALM_ASSERT_DEBUG(row_ndx == npos || row_ndx < size());
890 BpTreeNode::TreeInsert<LeafValueInserter> inserter;
891 inserter.m_value = std::move(value);
892 inserter.m_nullable = std::is_same<T, util::Optional<int64_t>>::value; // FIXME
893 bptree_insert(row_ndx, inserter, num_rows); // Throws
897 struct BpTree<T>::UpdateHandler : BpTreeNode::UpdateHandler {
900 UpdateHandler(BpTreeBase& tree, T value) noexcept
901 : m_leaf(tree.get_alloc())
902 , m_value(std::move(value))
905 void update(MemRef mem, ArrayParent* parent, size_t ndx_in_parent, size_t elem_ndx_in_leaf) override
907 m_leaf.init_from_mem(mem);
908 m_leaf.set_parent(parent, ndx_in_parent);
909 m_leaf.set(elem_ndx_in_leaf, m_value); // Throws
914 struct BpTree<T>::SetNullHandler : BpTreeNode::UpdateHandler {
916 SetNullHandler(BpTreeBase& tree) noexcept
917 : m_leaf(tree.get_alloc())
920 void update(MemRef mem, ArrayParent* parent, size_t ndx_in_parent, size_t elem_ndx_in_leaf) override
922 m_leaf.init_from_mem(mem);
923 m_leaf.set_parent(parent, ndx_in_parent);
924 _impl::NullableOrNothing<LeafType>::set_null(m_leaf, elem_ndx_in_leaf); // Throws
929 void BpTree<T>::set(size_t ndx, T value)
931 if (root_is_leaf()) {
932 root_as_leaf().set(ndx, std::move(value));
935 UpdateHandler set_leaf_elem(*this, std::move(value));
936 static_cast<BpTreeNode*>(m_root.get())->update_bptree_elem(ndx, set_leaf_elem); // Throws
941 void BpTree<T>::set_null(size_t ndx)
943 if (root_is_leaf()) {
944 _impl::NullableOrNothing<LeafType>::set_null(root_as_leaf(), ndx);
947 SetNullHandler set_leaf_elem(*this);
948 static_cast<BpTreeNode*>(m_root.get())->update_bptree_elem(ndx, set_leaf_elem); // Throws;
953 struct BpTree<T>::EraseHandler : BpTreeNode::EraseHandler {
956 bool m_leaves_have_refs; // FIXME: Should be able to eliminate this.
957 EraseHandler(BpTreeBase& tree) noexcept
959 , m_leaf(tree.get_alloc())
960 , m_leaves_have_refs(false)
963 bool erase_leaf_elem(MemRef leaf_mem, ArrayParent* parent, size_t leaf_ndx_in_parent,
964 size_t elem_ndx_in_leaf) override
966 m_leaf.init_from_mem(leaf_mem);
967 REALM_ASSERT_3(m_leaf.size(), >=, 1);
968 size_t last_ndx = m_leaf.size() - 1;
970 m_leaves_have_refs = m_leaf.has_refs();
973 m_leaf.set_parent(parent, leaf_ndx_in_parent);
974 size_t ndx = elem_ndx_in_leaf;
977 m_leaf.erase(ndx); // Throws
980 void destroy_leaf(MemRef leaf_mem) noexcept override
982 // FIXME: Seems like this would cause file space leaks if
983 // m_leaves_have_refs is true, but consider carefully how
984 // m_leaves_have_refs get its value.
985 m_tree.get_alloc().free_(leaf_mem);
987 void replace_root_by_leaf(MemRef leaf_mem) override
989 std::unique_ptr<LeafType> leaf{new LeafType(m_tree.get_alloc())}; // Throws
990 leaf->init_from_mem(leaf_mem);
991 m_tree.replace_root(std::move(leaf)); // Throws
993 void replace_root_by_empty_leaf() override
995 std::unique_ptr<LeafType> leaf{new LeafType(m_tree.get_alloc())}; // Throws
996 leaf->create(m_leaves_have_refs ? Array::type_HasRefs : Array::type_Normal); // Throws
997 m_tree.replace_root(std::move(leaf)); // Throws
1002 void BpTree<T>::erase(size_t ndx, bool is_last)
1004 REALM_ASSERT_DEBUG_EX(ndx < size(), ndx, size());
1005 REALM_ASSERT_DEBUG(is_last == (ndx == size() - 1));
1006 if (root_is_leaf()) {
1007 root_as_leaf().erase(ndx);
1010 size_t ndx_2 = is_last ? npos : ndx;
1011 EraseHandler handler(*this);
1012 BpTreeNode::erase_bptree_elem(&root_as_node(), ndx_2, handler);
1017 void BpTree<T>::move_last_over(size_t row_ndx, size_t last_row_ndx)
1019 // Copy value from last row over
1020 T value = get(last_row_ndx);
1021 set(row_ndx, value);
1022 erase(last_row_ndx, true);
1026 void BpTree<T>::clear()
1028 if (root_is_leaf()) {
1029 if (std::is_same<T, int64_t>::value && root().get_type() == Array::type_HasRefs) {
1030 // FIXME: This is because some column types rely on integer columns
1032 root().clear_and_destroy_children();
1035 root_as_leaf().clear();
1039 Allocator& alloc = get_alloc();
1040 root().destroy_deep();
1042 std::unique_ptr<LeafType> new_root(new LeafType(alloc));
1044 replace_root(std::move(new_root));
1050 struct BpTree<T>::AdjustHandler : BpTreeNode::UpdateHandler {
1053 AdjustHandler(BpTreeBase& tree, T diff)
1054 : m_leaf(tree.get_alloc())
1059 void update(MemRef mem, ArrayParent* parent, size_t ndx_in_parent, size_t) final
1061 m_leaf.init_from_mem(mem);
1062 m_leaf.set_parent(parent, ndx_in_parent);
1063 m_leaf.adjust(0, m_leaf.size(), m_diff);
1068 void BpTree<T>::adjust(T diff)
1070 if (root_is_leaf()) {
1071 root_as_leaf().adjust(0, m_root->size(), std::move(diff)); // Throws
1074 AdjustHandler adjust_leaf_elem(*this, std::move(diff));
1075 root_as_node().update_bptree_leaves(adjust_leaf_elem); // Throws
1080 void BpTree<T>::adjust(size_t ndx, T diff)
1082 static_assert(std::is_arithmetic<T>::value, "adjust is undefined for non-arithmetic trees");
1083 set(ndx, get(ndx) + diff);
1087 struct BpTree<T>::AdjustGEHandler : BpTreeNode::UpdateHandler {
1089 const T m_limit, m_diff;
1091 AdjustGEHandler(BpTreeBase& tree, T limit, T diff)
1092 : m_leaf(tree.get_alloc())
1098 void update(MemRef mem, ArrayParent* parent, size_t ndx_in_parent, size_t) final
1100 m_leaf.init_from_mem(mem);
1101 m_leaf.set_parent(parent, ndx_in_parent);
1102 m_leaf.adjust_ge(m_limit, m_diff);
1107 void BpTree<T>::adjust_ge(T limit, T diff)
1109 if (root_is_leaf()) {
1110 root_as_leaf().adjust_ge(std::move(limit), std::move(diff)); // Throws
1113 AdjustGEHandler adjust_leaf_elem(*this, std::move(limit), std::move(diff));
1114 root_as_node().update_bptree_leaves(adjust_leaf_elem); // Throws
1119 struct BpTree<T>::SliceHandler : public BpTreeBase::SliceHandler {
1121 SliceHandler(Allocator& alloc)
1125 MemRef slice_leaf(MemRef leaf_mem, size_t offset, size_t size, Allocator& target_alloc) override
1127 m_leaf.init_from_mem(leaf_mem);
1128 return m_leaf.slice_and_clone_children(offset, size, target_alloc); // Throws
1136 ref_type BpTree<T>::write(size_t slice_offset, size_t slice_size, size_t table_size, _impl::OutputStream& out) const
1139 if (root_is_leaf()) {
1140 Allocator& alloc = Allocator::get_default();
1141 MemRef mem = root_as_leaf().slice_and_clone_children(slice_offset, slice_size, alloc); // Throws
1143 _impl::DeepArrayDestroyGuard dg(&slice);
1144 slice.init_from_mem(mem);
1146 bool only_when_modified = false;
1147 ref = slice.write(out, deep, only_when_modified); // Throws
1150 SliceHandler handler(get_alloc());
1151 ref = write_subtree(root_as_node(), slice_offset, slice_size, table_size, handler, out); // Throws
1157 MemRef BpTree<T>::create_leaf(Array::Type leaf_type, size_t size, T value, Allocator& alloc)
1159 bool context_flag = false;
1160 MemRef mem = LeafType::create_array(leaf_type, context_flag, size, std::move(value), alloc);
1165 void BpTree<T>::get_leaf(size_t ndx, size_t& ndx_in_leaf, LeafInfo& inout_leaf_info) const noexcept
1167 if (root_is_leaf()) {
1169 *inout_leaf_info.out_leaf = &root_as_leaf();
1172 std::pair<MemRef, size_t> p = root_as_node().get_bptree_leaf(ndx);
1173 inout_leaf_info.fallback->init_from_mem(p.first);
1174 ndx_in_leaf = p.second;
1175 *inout_leaf_info.out_leaf = inout_leaf_info.fallback;
1179 size_t BpTree<T>::find_first(T value, size_t begin, size_t end) const
1181 if (root_is_leaf()) {
1182 return root_as_leaf().find_first(value, begin, end);
1185 // FIXME: It would be better to always require that 'end' is
1186 // specified explicitly, since Table has the size readily
1187 // available, and Array::get_bptree_size() is deprecated.
1191 LeafType leaf_cache(get_alloc());
1192 size_t ndx_in_tree = begin;
1193 while (ndx_in_tree < end) {
1194 const LeafType* leaf;
1195 LeafInfo leaf_info{&leaf, &leaf_cache};
1197 get_leaf(ndx_in_tree, ndx_in_leaf, leaf_info);
1198 size_t leaf_offset = ndx_in_tree - ndx_in_leaf;
1199 size_t end_in_leaf = std::min(leaf->size(), end - leaf_offset);
1200 size_t ndx = leaf->find_first(value, ndx_in_leaf, end_in_leaf); // Throws (maybe)
1201 if (ndx != not_found)
1202 return leaf_offset + ndx;
1203 ndx_in_tree = leaf_offset + end_in_leaf;
1210 void BpTree<T>::find_all(IntegerColumn& result, T value, size_t begin, size_t end) const
1212 if (root_is_leaf()) {
1213 root_as_leaf().find_all(&result, value, 0, begin, end); // Throws
1217 // FIXME: It would be better to always require that 'end' is
1218 // specified explicitely, since Table has the size readily
1219 // available, and Array::get_bptree_size() is deprecated.
1223 LeafType leaf_cache(get_alloc());
1224 size_t ndx_in_tree = begin;
1225 while (ndx_in_tree < end) {
1226 const LeafType* leaf;
1227 LeafInfo leaf_info{&leaf, &leaf_cache};
1229 get_leaf(ndx_in_tree, ndx_in_leaf, leaf_info);
1230 size_t leaf_offset = ndx_in_tree - ndx_in_leaf;
1231 size_t end_in_leaf = std::min(leaf->size(), end - leaf_offset);
1232 leaf->find_all(&result, value, leaf_offset, ndx_in_leaf, end_in_leaf); // Throws
1233 ndx_in_tree = leaf_offset + end_in_leaf;
1237 #if defined(REALM_DEBUG)
1239 size_t BpTree<T>::verify_leaf(MemRef mem, Allocator& alloc)
1241 LeafType leaf(alloc);
1242 leaf.init_from_mem(mem);
1248 void BpTree<T>::verify() const
1250 if (root_is_leaf()) {
1251 root_as_leaf().verify();
1254 root().verify_bptree(&verify_leaf);
1257 #endif // REALM_DEBUG
1260 void BpTree<T>::leaf_to_dot(MemRef leaf_mem, ArrayParent* parent, size_t ndx_in_parent, std::ostream& out,
1263 LeafType leaf(alloc);
1264 leaf.init_from_mem(leaf_mem);
1265 leaf.set_parent(parent, ndx_in_parent);
1269 } // namespace realm
1271 #endif // REALM_BPTREE_HPP