/* @file BplusTree.hpp */ /* provides B+ tree with order statistics */ /* NOTES: * - expect optimimum node size to be OS page size. * */ #pragma once //#include "bplustree/BplusTreeNode.hpp" #include "bplustree/LeafNode.hpp" #include "bplustree/InternalNode.hpp" #include "bplustree/Iterator.hpp" #include "bplustree/Lhs.hpp" #include "bplustree/bplustree_tags.hpp" #include "xo/indentlog/scope.hpp" #include "xo/indentlog/print/tag.hpp" #include "xo/indentlog/print/pad.hpp" #include /* for std::unqiue_ptr */ #include /* for std::max */ #include /* for std::numeric_limits */ #include #include #include #if __APPLE__ && __MACH__ # include #endif namespace xo { namespace tree { /* * +-------------+ * | BplusTree | +--------------------+ * | .properties +-----| BplusStdProperties | * | .n_element | | .branching_factor | * | .root | | .debug_flag | * +------+------+ +--------------------+ * | * | .root * | * +-------------------+ +--------------+ +--------------+ * | GenericNode | isa | LeafNode | .elt_v[i] | LeafNodeItem | * | .node_type |<---+----| .elt_v[] +-------------| .kv_pair | * | .parent | | | | | | * | .n_elt | | +--------------+ +--------------+ * | .branching_factor | | * +-------------------+ | * | * | +--------------+ +------------------+ * | | InternalNode | .elt_v[i] | InternalNodeItem | * \----| .elt_v[] +-------------| .key | * | | | .child | * +--------------+ +------------------+ * * Invariants: * - tree is always balanced -- every path from root to a LeafNode, visits the same number of InternalNodes. * - all Nodes (both LeafNodes and InternalNodes) satisfy bf/2 <= .n_elt <= bf (where bf = BplusTree.properties.branching_factor) * Details * - if InternalNode p has p.elt_v[i].child = q, then q.parent = p * - GenericNode.branching_factor = BplusTree.properties.branching_factor for all nodes in the same BplusTree * * Tree with 0 key/value pairs * * +--------------+ * | BplusTree | * | .root = null | * +--------------+ * * Tree with [1 .. b] key/value pairs (with b = BplusTree.properties.branching_factor) * * +---------------+ * | BplusTree | * | .root = node1 | * +--------+------+ * | * node1 | * +----------------------------------------------+ * | LeafNode | * | .parent = null | * | | * | .elt_v[0] .elt_v[b-1] | b = BplusTree.properties.branching_factor - 1 * | +----------------+- ... -+-----------------+ | .elt_v[i].kv_pair.first = i'th key * | | k0 | v0 | | k(b-1) | v(b-1) | | .elt_v[i].kv_pair.second = i'th value * | +----------------+- ... -+-----------------+ | * +----------------------------------------------+ * * Tree with [b+1 ..] key/value pairs * * +---------------+ * | BplusTree | * | .root = node1 | * +--------+------+ * | * node1 | * +----------------------------------------------+ * | InternalNode | * | .parent = null | * | | * | .elt_v[0] .elt_v[b-1] | * | +----------------+- ... -+-----------------+ | .elt_v[i].key = minimum key in subtree .elt_v[i].child * | | k0 | node2 | | k(b-1) | node(b)| | * | +----------------+- ... -+-----------------+ | * | .key .child .key .child | * +-------------------+-----+--------------------+ * | | * | | ... * | | * .elt_v[0].child | | .elt_v[1].child * /--------------------/ \----------------------------\ * | | * node2 | node3 | * +----------------------------------------------+ +-------------------------------------------------+ * | LeafNode | | LeafNode | * | .parent = node1 | | .parent = node1 | * | | | | * | .elt_v[0] .elt_v[b-1] | | .elt_v[0] .elt_v[b-1] | ..... * | +----------------+- ... -+-----------------+ | | +--------+--------+- ... -+---------+---------+ | * | | k0 | v0 | | k(b-1) | v(b-1) | | | | kb | vb | | k(2b-1) | v(2b-1) | | * | +----------------+- ... -+-----------------+ | | +-----------------+- ... -+---------+---------+ | * +----------------------------------------------+ +-------------------------------------------------+ * * * Larger trees havedadditional levels comprising InternalNodes. * */ /* NullReduce: 0-size reduce function (disappears at compile time) */ template struct NullReduce; struct Machdep { /* current page size (on a linux system). Probably 4K * * (a) need this to be at least large enough to * hold 3 keys * (b) note linux page size isn't fixed at compile time */ static inline std::size_t get_page_size() { return ::sysconf(_SC_PAGESIZE); } /* L1 cache line size (on a linux system). Probably 64 bytes */ static inline std::size_t get_cache_line_size() { // https://sourceforge.net/p/predef/wiki/OperatingSystems/ # if __APPLE__ && __MACH__ std::size_t line_size = 0; std::size_t sizeof_line_size = sizeof(line_size); ::sysctlbyname("hw.cachelinesize", &line_size, &sizeof_line_size, 0, 0); return line_size; # else return ::sysconf(_SC_LEVEL1_DCACHE_LINESIZE); # endif } }; /*Machdep*/ /* B+ tree nodes come in several flavors: {root | internal | leaf}: * * - root. each B+ tree has exactly one node of this type, * representing the root of the B+ tree. * The root node is subject to fewer restrictions than other * nodes in a B+ tree: * - can have 2..b elements (where b is branching factor for this B+ tree). * - can function as tree's one and only leaf node (if tree has <= b items). * - can function as an internal node (if tree has > b items) * * - internal. an internal node has: * - n child node pointers, subject to ceil(b/2) <= n <= b, * where b is tree's branching factor * - n keys. key[j] is the smallest key value in subtree j. * see InternalNode * * - leaf. a leaf node has: * - n keys, subject to ceil(b/2) <= n <= b, * where b is tree's branching factor * - n values. values are stored as pointers. * - pointer to next leaf node, to streamline inorder traversal */ template struct BplusStdProperties { public: using KeyType = Key; using ValueType = Value; public: BplusStdProperties() = default; explicit BplusStdProperties(std::size_t bf, bool debug_flag) : branching_factor_{bf}, debug_flag_{debug_flag} {} static constexpr tags::ordinal_tag ordinal_tag_value() { return OrdinalTag; } static constexpr bool ordinal_enabled() { return OrdinalTag == tags::ordinal_enabled; } static constexpr std::size_t c_min_branching_factor = 3; /* compute branching factor for given (leaf) node size */ static constexpr std::size_t branching_factor_for_size(std::size_t z) { return std::max(c_min_branching_factor, (z - sizeof(LeafNode)) / (sizeof(LeafNodeItemPlaceholder))); } /*branching_factor_for_size*/ /* default branching factor. * attempt to optimize for cache efficiency of 'internal' nodes * * minimum branching factor always 3 */ static constexpr std::size_t default_branching_factor() { return branching_factor_for_size(Machdep::get_page_size()); } /* expect this will be min branching factor * (i.e. smallest allowed LeafNode size likely won't fit in cache line): * * - cache line size = 64 bytes * - leaf node overhead = 56 bytes * - leaf node item size = 16 bytes */ static constexpr std::size_t default_cacheline_branching_factor() { return branching_factor_for_size(Machdep::get_cache_line_size()); } std::size_t branching_factor() const { return branching_factor_; } bool debug_flag() const { return debug_flag_; } void set_debug_flag(bool x) { debug_flag_ = x; } private: /* branching factor to use for both leaf an inteernal B+ tree nodes */ std::size_t branching_factor_ = default_branching_factor(); /* if true enable verbose logging during B+ tree operations */ bool debug_flag_ = false; }; /*BplusStdProperties*/ template inline std::ostream & operator<<(std::ostream & os, BplusStdProperties const & p) { using xo::xtag; os << ""; return os; } /*operator<<*/ /* B+ tree with order statistics * * require: * - Key is equality comparable, and imposes total ordering on keys. * - Key, Value, Reduce, Properties are copyable and null-constructible * - Reduce.value_type = Accumulator * - Reduce.operator() :: (Accumulator x Key) -> Accumulator */ template , typename Properties = BplusStdProperties> class BplusTree { public: using GenericNodeType = GenericNode; using InternalNodeType = InternalNode; using LeafNodeType = LeafNode; using InternalNodeItemType = InternalNodeItem; using LeafNodeItemType = LeafNodeItem; using BpTreeConstLhs = detail::BplusTreeConstLhs>; using BpTreeUtil = BplusTreeUtil; using key_type = Key; using mapped_type = Value; using value_type = std::pair; using size_type = std::size_t; using difference_type = std::ptrdiff_t; // key_compare // allocator_type using reference = value_type &; using const_reference = value_type const &; // pointer = std::allocator_traits::pointer; // const_pointer = std::allocator_traits::const_pointer; using const_iterator = detail::ConstIterator; // reverse_iterator // const_reverse_iterator // value_compare (compares value_type objects by comparing their first elements) public: BplusTree() = default; explicit BplusTree(Properties const & properties) : properties_{properties} {} bool empty() const { return this->n_element_ == 0; } size_type size() const { return this->n_element_; } size_type max_size() const { return std::numeric_limits::max(); } std::size_t branching_factor() const { return this->properties_.branching_factor(); } bool debug_flag() const { return this->properties_.debug_flag(); } void set_debug_flag(bool x) { this->properties_.set_debug_flag(x); } /* verify b+ tree invariants. * if invariants satisfied, return true. * if not satisfied, * - throw_flag=true -> throw execption * - throw_flag=false -> return false; */ bool verify_ok(bool throw_flag = true) const { using xo::scope; using xo::xtag; //scope x("verify_ok"); //x.log(xtag("n_element", this->n_element_)); std::size_t z = 0; try { z = this->verify_helper(throw_flag); } catch (...) { if (throw_flag) throw; return false; } //x.log(xtag("z", z)); if (z != this->n_element_) { if (throw_flag) { std::string err = tostr("BplusTree::verify_ok" ": bad key count", xtag("expected", this->n_element_), xtag("counted", z)); throw std::runtime_error(err); } return false; } return true; } /*verify_ok*/ /* cxxx: const iterator * rxxx: reverse iterator * crxxx: const reverse iterator */ const_iterator cprebegin() const { return const_iterator::prebegin_aux(this->leafnode_begin_); } const_iterator cbegin() const { return const_iterator::begin_aux(this->leafnode_begin_); } const_iterator cend() const { return const_iterator::end_aux(this->leafnode_end_); } const_iterator begin() const { return this->cbegin(); } const_iterator end() const { return this->cend(); } const_iterator crprebegin() const { return const_iterator::rprebegin_aux(this->leafnode_end_); } const_iterator crbegin() const { return const_iterator::rbegin_aux(this->leafnode_end_); } const_iterator crend() const { return const_iterator::rend_aux(this->leafnode_begin_); } const_iterator rbegin() const { return this->crbegin(); } const_iterator rend() const { return this->crend(); } /* find item with key equal to x in this tree. * success -> return iterator ix with ix->first = x * failure -> return iterator this->cend() */ const_iterator find(Key const & x) const { FindNodeResult leaffindresult = this->find_leaf_node(x); LeafNodeType const * leaf = leaffindresult.node(); std::pair lub_ix_recd = leaf->find_lub_ix(x); if (lub_ix_recd.first) { return const_iterator(detail::ID_Forward /*dirn*/, detail::IL_Regular /*loc*/, leaf, lub_ix_recd.second - 1); } else { return this->cend(); } } /*find*/ /* find i'th key/value pair (in key order) in this tree. * * Require: * - 0 <= i < .size */ const_iterator find_ith(std::size_t i_tree) const { using xo::tostr; using xo::xtag; if (i_tree >= this->size()) { throw std::runtime_error(tostr("BplusTree::find_ith: expected index i in range [0..n)", xtag("i", i_tree), xtag("n", this->size()))); } GenericNodeType * generic_node = this->root_.get(); return BplusTreeUtil::find_ith(generic_node, i_tree, this->cend()); } /*find_ith*/ BpTreeConstLhs at(Key const & k) const { const_iterator ix = this->find(k); if (ix == this->cend()) { throw std::out_of_range(tostr("BplusTree::at: expected key argument to appear in tree", xtag("key", k))); } return BpTreeConstLhs(this, ix.item_addr()); } /*at*/ /* e.g. * BplusTree bptree = ...; * Key key = ...; * BplusTree::value_type x = bptree[key]; */ BpTreeConstLhs operator[](Key const & k) const { const_iterator ix = this->find(k); return BpTreeConstLhs(this, ix.item_addr()); } /*operator[]*/ void clear() { this->n_element_ = 0; this->leafnode_begin_ = nullptr; this->leafnode_end_ = nullptr; this->root_.reset(nullptr); } /*clear*/ /* TODO: * std::pair insert(value_type const & kv_pair); * * template * std::pair insert(P && value) * * std::pair insert(value_type && kv_pair); * * iterator insert(iterator pos, value_type const & kv_pair); * iterator insert(const_iterator pos, value_type const & kv_pair); * * template * iterator insert(const_iterator pos, P && value); * * iterator insert(const_iterator pos, value_type && value); * * template * void insert(InputIterator lo, InputIterator hi); * * void insert(std::initializer_list initlist); */ /* return: true key already existed (tree size increases by 1) * false if existing key (tree size unchanged) */ std::pair insert(std::pair const & kv_pair) { using xo::scope; using xo::xtag; scope log(XO_DEBUG(this->debug_flag()), xtag("key", kv_pair.first), xtag("value", kv_pair.second), xtag("root", this->root_.get()) //xtag("nesting", x.nesting_level()) ); log && log(xtag("bptree[before-insert]", (char const *)"...")); if (log) this->print(std::clog, log.nesting_level()+2); std::pair retval; if (this->root_) { NodeType root_type = this->root_->node_type(); log && log(xtag("root_type", root_type)); switch (root_type) { case NodeType::leaf: retval = this->leaf_insert_aux(kv_pair); break; case NodeType::internal: retval = this->internal_insert_aux(kv_pair); break; } /*switch*/ } else { retval = this->create_root_aux(kv_pair); } log && log(xtag("bptree[after-insert]", (char const *)"...")); if (log) this->print(std::clog, log.nesting_level() + 2); log.end_scope(); return retval; } /*insert*/ /* e.g: * std::map m = ...; * BplusTree bptree; * * bptree.insert(m.begin(), m.end()); */ template void insert(InputIterator lo, InputIterator hi) { for (InputIterator ix = lo; ix != hi; ++ix) this->insert(*ix); } /*insert*/ /* return: true if key existed (tree size decreased by 1) * false if key not found */ bool erase(Key const & key) { using xo::scope; using xo::xtag; scope log(XO_DEBUG(this->debug_flag()), xtag("key", key), xtag("root", this->root_.get())); log && log(xtag("bptree[before-erase]", (char const *)"...")); if (log) this->print(std::clog, log.nesting_level()+2); bool retval = false; if (this->root_) { NodeType root_type = this->root_->node_type(); log && log(xtag("root_type", root_type)); switch (root_type) { case NodeType::leaf: retval = leaf_erase_aux(key); break; case NodeType::internal: retval = internal_erase_aux(key); break; } /*switch*/ } else { /* tree empty, certainly doesn't contain key */ } log && log(xtag("bptree[after-erase]", (char const *)"...")); if (log) this->print(std::clog, log.nesting_level()+2); log.end_scope(); return retval; } /*erase*/ void print(std::ostream & os, std::int32_t indent = 0) const { using xo::xtag; using xo::pad; os << pad(indent) << "print_aux(os, this->root_.get(), indent+2); os << ">"; os << std::endl; } /*print*/ private: /* find leaf node associated with given key, within given subtree * * .first: index position of leaf in immediate parent of leaf node. 0 when leaf is also root node. * .seecond: leaf node */ static FindNodeResult find_leaf_node_aux(Key const & key, InternalNodeType * subtree_arg) { FindNodeResult> findresult(0, subtree_arg); while (findresult.node() && (findresult.node()->node_type() == NodeType::internal)) { findresult = (reinterpret_cast(findresult.node()))->find_child(key); } /* findresult.node().node_type() == NodeType::leaf (if non-null) */ if (!findresult.node()) { assert(false); return FindNodeResult(); } assert(findresult.node()->node_type() == NodeType::leaf); /* subtree.canonical_node_type = leaf */ return FindNodeResult(findresult.ix(), reinterpret_cast(findresult.node())); } /*find_leaf_node_aux*/ /* count #of keys present in this b+ tree, by visiting every node; * but short-circuit if internal inconsistency detected */ std::size_t verify_helper(bool throw_flag) const { using xo::scope; using xo::xtag; //scope x("BplusTree.verify_helper"); if (Properties::ordinal_tag_value() == tags::ordinal_enabled) { /* verify tree size (maintained in each node) matches toplevel tree size) */ if (this->root_ != nullptr) { if (this->size() != BplusTreeUtil::get_node_size(this->root_.get())) { if (throw_flag) { throw std::runtime_error(tostr("BplusTree::verify_helper" ": mismatched tree size computation", xtag("root", this->root_.get()), xtag("bptree.n_element", this->size()), xtag("bptree.root.size", logutil::nodesize(this->root_.get())))); } else { return -1; } } } } else { /* subtree size not maintained; skip test */ } /* verify leafnode iterator endpoints */ if (this->root_ == nullptr) { if (this->leafnode_begin_ != nullptr || this->leafnode_end_ != nullptr) { if (throw_flag) { throw std::runtime_error(tostr("BplusTree::verify_helper" ": expected null .leafnode_begin / .leafnode_end pointers" " with empty tree", xtag("root", this->root_.get()), xtag("leafnode_begin", this->leafnode_begin_), xtag("leafnode_end", this->leafnode_end_))); } else { return -1; } } return 0; } else { auto leftmost_fr = this->root_->find_min_leaf_node(); auto rightmost_fr = this->root_->find_max_leaf_node(); if ((leftmost_fr.node() != this->leafnode_begin_) || (rightmost_fr.node() != this->leafnode_end_)) { if (throw_flag) { throw std::runtime_error(tostr("BplusTree::verify_helper" ": expected .leafnode_begin / .leafnode_end pointers" " to match computed first/last leaf nodes", xtag("root", this->root_.get()), xtag("leafnode_begin[stored]", this->leafnode_begin_), xtag("leafnode_begin[computed]", leftmost_fr.node()), xtag("leafnode_end[stored]", this->leafnode_end_), xtag("leafnode_end[computed]", rightmost_fr.node()))); } else { return -1; } } } return this->root_->verify_helper(nullptr /*parent*/, false /*!with_lub_flag*/, Key() /*lub_key*/, nullptr /*lh_leaf*/, nullptr /*rh_leaf*/); } /*verify_helper*/ /* find leaf node associated with given key; * this is the node that would contain target key, if it is present. */ FindNodeResult find_leaf_node(Key const & key) { if (!root_.get()) return FindNodeResult(); switch (root_->node_type()) { case NodeType::leaf: return FindNodeResult(0, reinterpret_cast(root_.get())); case NodeType::internal: return find_leaf_node_aux(key, reinterpret_cast(root_.get())); } assert(false); return FindNodeResult(); } /*find_leaf_node*/ FindNodeResult find_leaf_node(Key const & key) const { FindNodeResult findresult = const_cast(this)->find_leaf_node(key); return FindNodeResult(findresult.ix(), findresult.node()); } /*find_leaf_node*/ /* insert helper. * * require: * - root node is NodeType::leaf * - returns true if new key; false if replace value associated with existing key */ std::pair leaf_insert_aux(std::pair const & kv_pair) { using xo::scope; using xo::xtag; /* will add/replace key,value pair in existing root (which is a leaf) node */ scope log(XO_DEBUG(this->debug_flag()), xtag("key", kv_pair.first), xtag("value", kv_pair.second)); /* root node is a leaf node: * - tree has between 1 and b elements (where b = branching factor) */ LeafNodeType * leaf = reinterpret_cast(this->root_.get()); log && log(xtag("leaf", leaf), xtag("leaf.n_elt", leaf->n_elt()), xtag("leaf.bf", leaf->branching_factor())); /* .elt_v[] * * 0 k n-1 with: n <= b = branching factor * +---+---+- ... -+---+- ... -+---+---+ k = lub(key) in {e1..en} * | e1| e2| | ek| | | en| * +---+---+- ... -+---+- ... -+---+---+ * * lub_ix_recd.first: true if key already present in tree. implies lub_ix_recd.second >= 1 * lub_ix_recd.second: upper bound (strict) index position in .elt_v[] of key */ std::pair lub_ix_recd = leaf->find_lub_ix(kv_pair.first); log && log(xtag("lub_ix_recd.first", lub_ix_recd.first), xtag("lub_ix_recd.second", lub_ix_recd.second)); if (lub_ix_recd.first) { leaf->assign_leaf_value(lub_ix_recd.second - 1, kv_pair.second); return (std::pair (const_iterator(detail::ID_Forward /*dirn*/, detail::IL_Regular /*loc*/, leaf, lub_ix_recd.second - 1), false)); } /* key not present in tree, so will be incrementing tree size */ if (leaf->n_elt() == leaf->branching_factor()) { log && log("split root (leaf) node"); /* root node is full: * 1. split into two leaf nodes * 2. create new root node (internal instead of leaf) */ ++(this->n_element_); std::unique_ptr lower(reinterpret_cast(this->root_.release())); std::unique_ptr upper(lower->split_leaf_upper()); /* insert is made into this leaf */ LeafNodeType * leaf_node = nullptr; /* new (key, value) pair placed at this index poseition in leaf_node */ std::size_t leaf_ix = 0; /* note corner case: * when lub_ix_recd.second == lower->n_elt(), * could either insert (key, value) as smallest key in upper subtree, or largest key in lower subtree. * however if we put into upper, then also need to correct .root.elt_v_[1].key; * slightly simpler to insert into lower subtree. * */ if (lub_ix_recd.second <= lower->n_elt()) { log && log("insert into new LH leaf node"); leaf_node = lower.get(); leaf_ix = lub_ix_recd.second; } else { log && log("insert into new RH leaf node"); leaf_node = upper.get(); leaf_ix = lub_ix_recd.second - lower->n_elt(); } leaf_node->insert_leaf_item(leaf_ix, std::move(kv_pair), this->debug_flag()); /* create new root node (now with node-type = internal), * having two child (leaf) nodes */ std::unique_ptr root_2 = InternalNodeType::make_2(std::move(lower), std::move(upper)); /* new root node replaces existing root node */ this->root_ = std::move(root_2); this->post_modify_correct_leafnode_endpoints(); return (std::pair (const_iterator(detail::ID_Forward /*dirn*/, detail::IL_Regular /*loc*/, leaf_node, leaf_ix), true)); } else if (leaf->n_elt() < this->properties_.branching_factor()) { /* 1. key is not already present in b+ tree * 2. leaf_ix+1 is lub on key; move elements [lub .. n_elt) one step to the right * hope to move elements leaf_ix+1 .. n_elt-1 to the right, */ /* leaf node has room for one more item */ ++(this->n_element_); /* insert in root node, moving items [lub_ix .. .n_elt) one to the right */ leaf->insert_leaf_item(lub_ix_recd.second, kv_pair, this->debug_flag()); return (std::pair (const_iterator(detail::ID_Forward /*dirn*/, detail::IL_Regular /*loc*/, leaf, lub_ix_recd.second), true)); } else { /* impossible! */ assert(false); return (std::pair (const_iterator(), false)); } } /*leaf_insert_aux*/ /* insert helper. * * require: * - root node is NodeType::internal * * kv_pair: establish assocation kv_pair.first(=key) -> kv_pair.second(=value) * return: true if insert performed; false if update on existing key */ std::pair internal_insert_aux(std::pair const & kv_pair) { using xo::scope; using xo::xtag; scope log(XO_DEBUG(this->debug_flag()), xtag("key", kv_pair.first), xtag("value", kv_pair.second)); /* root node is an internal node: * - tree has at least b elements (where b = branching factor) */ FindNodeResult leaffindresult = this->find_leaf_node(kv_pair.first); LeafNodeType * leaf = leaffindresult.node(); log && log(xtag("leaf", leaf), xtag("leaf.n_elt", leaf->n_elt()), xtag("leaf.bf", leaf->branching_factor())); std::pair lub_ix_recd = leaf->find_lub_ix(kv_pair.first); log && log(xtag("lub_ix_recd.first", lub_ix_recd.first), xtag("lub_ix_recd.second", lub_ix_recd.second)); if (lub_ix_recd.first) { /* key already in tree, just updating associated value */ leaf->assign_leaf_value(lub_ix_recd.second - 1, kv_pair.second); return (std::pair (const_iterator(detail::ID_Forward /*dirn*/, detail::IL_Regular /*loc*/, leaf, lub_ix_recd.second - 1), false)); } /* key not present in tree, will be incrementing tree size */ ++(this->n_element_); if (leaf->n_elt() < leaf->branching_factor()) { log && log("insert into existing leaf, since it has room"); /* leaf has room for 1 more item */ leaf->insert_leaf_item(lub_ix_recd.second, kv_pair, this->debug_flag()); this->post_modify_add_ancestor_size(leaf->parent(), +1); /* whenever we insert at first key position, * need also to update ancestor glb_key values */ { InternalNodeType * ancestor = leaf->parent(); while (ancestor && (kv_pair.first < ancestor->glb_key())) { ancestor->set_glb_key(kv_pair.first); ancestor = ancestor->parent(); } } return (std::pair (const_iterator(detail::ID_Forward /*dirn*/, detail::IL_Regular /*loc*/, leaf, lub_ix_recd.second), true)); } /* leaf is full. * 1. split into two half-full leaf nodes * 2. insert new (key, value) pair into one of the two * half-full nodes. * 3. recursively insert entry for new node into parent; * possibly splitting parent and additional ancestor * nodes as need be */ std::unique_ptr new_node; /* key,value pair will be inserted into this leaf */ LeafNodeType * leaf_node = nullptr; /* key,value pair inserted into leaf at this index position */ std::size_t leaf_ix = 0; if (lub_ix_recd.second < leaf->n_elt() / 2) { /* will insert into lower_leaf */ std::unique_ptr lower_leaf(leaf->split_leaf_lower()); /* lower_leaf holds lower half of leaf's original set of items. * leaf now holds upper half of leaf's original set of items. */ log && log("split leaf to get lower_leaf", xtag("lower_leaf", lower_leaf.get()), xtag("leaf.n_elt", leaf->n_elt()), xtag("lower_leaf.n_elt", lower_leaf->n_elt())); assert(lub_ix_recd.second <= lower_leaf->n_elt()); /* this size temporarily excluded from tree */ std::size_t decr_z = lower_leaf->size(); log && log("insert new key into (new) LH leaf"); lower_leaf->insert_leaf_item(lub_ix_recd.second, kv_pair, this->debug_flag()); leaf_node = lower_leaf.get(); leaf_ix = lub_ix_recd.second; new_node = std::move(lower_leaf); /* new_node may get attached to ree at non-obvious location. * at this point it is not in tree. * * to bookkeep node sizes, decrement now, then increment where new_node is reintroduced */ { InternalNodeType * parent = leaf->parent(); BplusTreeUtil::post_modify_sub_ancestor_size(parent, decr_z, this->debug_flag()); } /* however: leaf's glb increased -> * need to patch state in (at least parent, possibly more) ancestors */ { GenericNodeType * target = leaf; InternalNodeType * parent = target->parent(); while (parent) { std::size_t ix = parent->locate_child_by_address(target); assert(ix != static_cast(-1)); InternalNodeItemType & slot = parent->lookup_elt(ix); if (slot.key() == target->glb_key()) { /* done with fixup */ break; } slot.set_key(target->glb_key()); target = parent; parent = parent->parent(); } } } else { /* leaf is full: * * note that leaf->n_elt() shrinks across this call * * before: * leaf: * <-- b elements -> * 0 1 b-1 * +---+- ... -+---+ * | e1| | eb| * +---+- ... -+---+ * * after: * leaf: upper_leaf: * <-- b/2 elements -> <-- b/2 elements --> * 0 1 h 0 * +---+- ... -+---+ +---+- ... -+---+ * | e1| | eh| |eh'| | eb| with eh'=e(h+1) * +---+- ... -+---+ +---+- ... -+---+ * * note: if b odd, then: * - leaf gets (b-1)/2 elements, * - upper_leaf gets (b+1)/2 elements */ /* will insert into upper_leaf */ std::unique_ptr upper_leaf(leaf->split_leaf_upper()); /* leaf now holds lower half of its original set of items; * upper holds upper half of leaf's original set of items */ log && log("split leaf to get upper_leaf", xtag("upper_leaf", upper_leaf.get()), xtag("leaf.n_elt", leaf->n_elt()), xtag("upper_leaf.n_elt", upper_leaf->n_elt())); assert(lub_ix_recd.second >= leaf->n_elt()); /* this size temporarily excluded from tree */ std::size_t decr_z = upper_leaf->size(); log && log("insert new key into (new) RH leaf"); upper_leaf->insert_leaf_item(lub_ix_recd.second - leaf->n_elt(), kv_pair, this->debug_flag()); leaf_node = upper_leaf.get(); leaf_ix = lub_ix_recd.second - leaf->n_elt(); new_node = std::move(upper_leaf); /* new_node may get attached to tree at non-obvious location. * at this point it is not in tree. * * to bookkeep node sizes, decrement now, then increment where new_node is reintroduced */ { InternalNodeType * parent = leaf->parent(); BplusTreeUtil::post_modify_sub_ancestor_size(parent, decr_z, this->debug_flag()); } /* leaf's glb unchanged, no glb fixup required here */ } Key new_key = new_node->glb_key(); std::size_t lub_ix = 0; InternalNodeType * ancestor = leaf->parent(); while (ancestor) { /* invariant: need to add new_node to tree somewhere on path to ancestor. * new_node is a leaf|internal node with already-correct size, * that isn't yet accounted for in this B+ tree */ lub_ix = ancestor->find_lub_ix(new_key); log && log("fixup ancestors", xtag("new_key", new_key), xtag("new_node", new_node.get()), xtag("new_node.size", logutil::nodesize(new_node.get())), xtag("ancestor", ancestor), xtag("lub_ix", lub_ix)); /* on this iteration, need to introduce (new_key, new_node) to ancestor */ if (ancestor->n_elt() < ancestor->branching_factor()) { /* ordinal_enabled: #of elements in subtree new_node. * ordinal_disabled: 0 */ std::size_t new_z = BplusTreeUtil::get_node_size(new_node.get()); log && log("insert into ancestor, since it has room", xtag("ancestor.size[pre-insert]", logutil::nodesize(ancestor)), xtag("new_z", logutil::nodesize(new_node.get()))); /* room for 1 more child */ ancestor->insert_node(lub_ix, std::move(new_node), this->debug_flag()); /* if ordinal_enabled: increase .size on path from root down to and including ancestor * otherwise no-op */ BplusTreeUtil::post_modify_add_ancestor_size(ancestor, new_z, this->debug_flag()); this->post_modify_correct_ancestor_glb_keys(ancestor); this->post_modify_correct_leafnode_endpoints(); return (std::pair (const_iterator(detail::ID_Forward /*dirn*/, detail::IL_Regular /*loc*/, leaf_node, leaf_ix), true)); } else { log && log("pre-split (will split ancestor to make room for new node)", xtag("ancestor", ancestor), xtag("ancestor.size", logutil::nodesize(ancestor)), xtag("new_node", new_node.get()), xtag("new_node.size", logutil::nodesize(new_node.get()))); /* no room in ancestor, need to split */ std::unique_ptr upper_ancestor(ancestor->split_internal()); log && log("post-split", xtag("ancestor", ancestor), xtag("ancestor.size", logutil::nodesize(ancestor)), xtag("upper_ancestor", upper_ancestor.get()), xtag("upper_ancestor.size", logutil::nodesize(upper_ancestor.get()))); /* this size temporarily excluded from tree */ std::size_t decr_z = BplusTreeUtil::get_node_size(upper_ancestor.get()); /* will add back size fom upper_ancestor (w/ +1 for insert), * once we figure out where to attach it. * ancestor.size already decreased via ancestor.split_internal() */ BplusTreeUtil::post_modify_sub_ancestor_size(ancestor->parent(), decr_z, this->debug_flag()); /* ancestor.n_elt reduced to 1/2 value before call to split_internal() */ std::size_t new_z = BplusTreeUtil::get_node_size(new_node.get()); if (lub_ix <= ancestor->n_elt()) { log && log("insert into (existing post-split) LH ancestor"); log && log(xtag("lub_ix", lub_ix), xtag("new_node", new_node.get()), xtag("new_z", new_z)); ancestor->insert_node(lub_ix, std::move(new_node), this->debug_flag()); /* note: updating entire ancestor chain, * since next iteration will operate on upper_ancestor != ancestor */ BplusTreeUtil::post_modify_add_ancestor_size(ancestor, new_z, this->debug_flag()); log && log("LH ancestor size", xtag("ancestor", ancestor), xtag("ancestor.size", logutil::nodesize(ancestor))); /* note next loop iteration will fixup upper_ancestor. * upper_ancestor != ancestor */ this->post_modify_correct_ancestor_glb_keys(ancestor); } else { log && log("insert into (new) RH ancestor"); log && log(xtag("ix", lub_ix - ancestor->n_elt()), xtag("new_node", new_node.get()), xtag("new_z", new_z)); upper_ancestor->insert_node(lub_ix - ancestor->n_elt(), std::move(new_node), this->debug_flag()); /* note: deferring update for ancestor's ancestors until next loop iter */ BplusTreeUtil::node_add_size(upper_ancestor.get(), new_z); log && log("upper ancestor size", xtag("upper_ancestor", ancestor), xtag("upper_ancestor.size", logutil::nodesize(ancestor))); } /* setup for next loop iteration * reminder: upper_ancestor.size was removed from computed treesize, * will add back on subsequent iteration (when attaching new_node) */ new_key = upper_ancestor->glb_key(); new_node = std::move(upper_ancestor); ancestor = ancestor->parent(); } } log && log("root node was split -> create new root, adding one level"); /* if control comes here: * 1. ancestor is null * 2. root node was full + has been split. . * root will become LH subtree of new root * new_node will become RH subtree of new root * 3. new_node is not present in .root */ log && log(xtag("root.n_elt", this->root_->n_elt()), xtag("new_node.n_elt", new_node->n_elt())); this->root_ = std::move(InternalNodeType::make_2(std::move(this->root_), std::move(new_node))); this->post_modify_correct_leafnode_endpoints(); return (std::pair (const_iterator(detail::ID_Forward /*dirn*/, detail::IL_Regular /*loc*/, leaf_node, leaf_ix), true)); } /*internal_insert_aux*/ std::pair create_root_aux(std::pair const & kv_pair) { /* create root, with one element */ this->n_element_ = 1; std::unique_ptr leaf_node = LeafNodeType::make(kv_pair, this->properties_); this->leafnode_begin_ = leaf_node.get(); this->leafnode_end_ = leaf_node.get(); std::pair retval = (std::pair (const_iterator(detail::ID_Forward /*dirn*/, detail::IL_Regular /*loc*/, leaf_node.get(), 0), true)); this->root_.reset(leaf_node.release()); return retval; } /*create_root_aux*/ /* remove helper. * * require: * - root node is NodeType::leaf * - return true iff key found (in which case #of leaf node items decremented) */ bool leaf_erase_aux(Key const & key) { using xo::scope; using xo::xtag; LeafNodeType * leaf = reinterpret_cast(this->root_.get()); scope log(XO_DEBUG(this->debug_flag()), xtag("leaf", leaf), xtag("leaf.n_elt", leaf->n_elt()), xtag("leaf.bf", leaf->branching_factor())); /* .elt_v[] * * 0 k n-1 with: n <= b = branching factor * +---+---+- ... -+---+- ... -+---+---+ k = lub(key) in {e1..en} * | e1| e2| | ek| | | en| * +---+---+- ... -+---+- ... -+---+---+ * * lub_ix_recd.first: true if key already present in tree. implies lub_ix_recd.second >= 1 * lub_ix_recd.second: upper bound (strict) index position in .elt_v[] of key */ std::pair lub_ix_recd = leaf->find_lub_ix(key); log && log(xtag("lub_ix_recd.first", lub_ix_recd.first), xtag("lub_ix_recd.second", lub_ix_recd.second)); if (!lub_ix_recd.first) { /* key is not present in tree --> don't modify anything */ return false; } /* key is present in tree --> will decrement tree size */ if (leaf->n_elt() > 1) { --(this->n_element_); /* reminder: lub_ix_recd.second is strict upper bound */ leaf->remove_leaf(lub_ix_recd.second - 1, this->debug_flag()); } else { --(this->n_element_); /* removed last node -> tree now empty */ this->root_.reset(); } this->post_modify_correct_leafnode_endpoints(); log.end_scope(); return true; } /*leaf_erase_aux*/ /* remove helper. * * require: * - root node is NodeType::internal * - return true iff key found (in which case #of key,value pairs decremented) */ bool internal_erase_aux(Key const & key) { using xo::scope; using xo::xtag; scope log(XO_DEBUG(this->debug_flag()), xtag("key", key)); std::size_t const bf = this->branching_factor(); /* root node is an internal node: * - tree has at least b elements (where b = branching zfactor) * * this * +------+ * | | * +------+ * . * . * +------+ * | i| i = leaffindresult.ix() * +------+ * / | \ * /-------/ | \--------\ * | | | * +------+ +------+ +------+ * | | | | | j | j = lub_ix_recd.second - 1 * +------+ +------+ +------+ * leaf */ FindNodeResult leaffindresult = this->find_leaf_node(key); LeafNodeType * leaf = leaffindresult.node(); log && log(xtag("leaf", leaffindresult.node()), xtag("leaf.n_elt", leaffindresult.node()->n_elt()), xtag("leaf.loc", leaffindresult.ix()), xtag("bf", bf)); std::pair lub_ix_recd = leaffindresult.node()->find_lub_ix(key); log && log(xtag("lub_ix_recd.first", lub_ix_recd.first), xtag("lub_ix_recd.second", lub_ix_recd.second)); if (!lub_ix_recd.first) { /* key not in tree */ return false; } /* key present in tree at leaf.elt_v[lub_ix_recd.second - 1] */ /* B+ balance invariant is sustained across remove * * if glb key changed, then have to propagate up ancestor chain */ --(this->n_element_); /* reminder: lub_ix_recd.second is strict upper bound */ leaf->remove_leaf(lub_ix_recd.second - 1, this->debug_flag()); InternalNodeType * parent = leaf->parent(); /* whenever we remove at first key position (with strict upper bound index 1), * then glb key changed, so need also to update ancestor glb_key values */ if (lub_ix_recd.second == 1) { /* glb_key for this leaf node changed (to larger value) */ log && log("fix glb", xtag("@", parent), xtag("old-glb", parent->glb_key()), xtag("new-glb", leaf->glb_key())); /* we dropped smallest key from [leaf] --> correct glb key for leaf in its immediate parent */ parent->lookup_elt(leaffindresult.ix()).set_key(leaf->glb_key()); this->post_modify_correct_ancestor_glb_keys(parent); } else { /* removal from position >0 doesn't change glb key * -> doesn't require ancestor updates */ } if (2 * leaf->n_elt() >= bf) { /* after removal, leaf still has acceptable #of children */ /* must decrement tree size on path from root down to and including parent */ this->post_modify_sub_ancestor_size(parent, +1); return true; } else { /* after removal, leaf will be too small. plan: * - try redistributing from a neighboring leaf * - if result too small, then merge with one of neighboring leaves; * in this case merges may cascade upward to root * - if root node shrinks to 1 child, that child becomes new root */ log && log("leaf too small after remove -> redistribute or shrink tree"); std::size_t leaf_ix = leaffindresult.ix(); /* right_sibling_ix: position of sibling immediately after (leaf_ix, key), in parent */ std::size_t right_sibling_ix = leaf_ix + 1; LeafNodeType * right_sibling = nullptr; if (right_sibling_ix < parent->n_elt()) { /* consider merge with right sibling */ right_sibling = reinterpret_cast(parent->lookup_elt(right_sibling_ix).child()); std::size_t n = leaf->n_elt() + right_sibling->n_elt(); if (n >= 2 * ((bf + 1) / 2)) { /* can redistribute one or more nodes from right_sibling -> leaf * e.g. * if bf=3, require 4 nodes between leaf and rh sibling * if bf=4, also require 4 nodes between leaf and rh sibling. * * after redistribution: * - leaf will have n/2 elements * - right_sibling will have n - n/2 elements */ leaf->append_from_rh_sibling(n/2 - leaf->n_elt(), right_sibling); /* glb_key for right sibling changed, need to fix ancestor book-keeping */ parent->lookup_elt(right_sibling_ix).set_key(right_sibling->glb_key()); this->post_modify_sub_ancestor_size(parent, +1); this->post_modify_correct_ancestor_glb_keys(parent); return true; } else { log && log("reject redistrib from right sibling, not enough capacity"); } } else { log && log("reject redistrib from right sibling, doesn't exist"); } std::size_t left_sibling_ix = leaf_ix - 1; LeafNodeType * left_sibling = nullptr; if (leaf_ix > 0) { /* consider redistribution from left sibling */ left_sibling = reinterpret_cast(parent->lookup_elt(left_sibling_ix).child()); std::size_t n = leaf->n_elt() + left_sibling->n_elt(); if (n >= 2 * ((bf + 1) / 2)) { log && log("redistrib from left sibling"); std::size_t n_redistrib = n/2 - leaf->n_elt(); log && log(xtag("n/2", n/2), xtag("leaf.n", leaf->n_elt()), xtag("n_redistrib", n_redistrib)); /* can redistribute one or more nodes from left_sibling -> leaf * after redistribution: * - leaf will have n/2 elements * - left_sibling will have n - n/2 elements */ leaf->prepend_from_lh_sibling(left_sibling, n_redistrib, this->debug_flag()); /* glb key for leaf changed, need to fix ancestor book-keeping */ parent->lookup_elt(leaf_ix).set_key(leaf->glb_key()); this->post_modify_sub_ancestor_size(parent, +1); this->post_modify_correct_ancestor_glb_keys(parent); return true; } else { log && log("reject redistib from left sibling, not enough capacity"); } } else { log && log("reject redistrib from left sibling, doesn't exist"); } /* control here * -> not enough nodes to redistribute from either sibling * -> must shrink #nodes in tree */ if (right_sibling_ix < parent->n_elt()) { assert(right_sibling); log && log("merge right sibling"); /* RH sibling exists -> merge with it (arbitrary choice if leaf_ix > 0) */ leaf->append_rh_sibling(right_sibling); /* right_sibling is now (effectively) empty, drop from parent; * also fixup next_leafnode/prev_leafnode links to bypass */ parent->remove_node(right_sibling_ix, this->debug_flag()); /* note that glb_key for leaf did not change */ /* -1 .size on path from root down to and including parent */ this->post_modify_sub_ancestor_size(parent, +1); /* since we reduced #of children at parent, it may have fallen below b/2 lower bound */ this->post_remove_shrink_ancestor_path(parent); /* since we removed a leaf node, may have invalidated iterator begin/end endpoints */ this->post_modify_correct_leafnode_endpoints(); return true; } if (leaf_ix > 0) { assert(left_sibling); /* LH sibling exists -> merge with it (arbitrary choice if right_sibling_ix < parent.n_elt */ left_sibling->append_rh_sibling(leaf); /* leaf is now (effectively) empty, drop from parent; * also fixup next_leafnode/prev_leafnode links to bypass */ parent->remove_node(leaf_ix, this->debug_flag()); /* note that glb_key for left_sibling did not change */ this->post_modify_sub_ancestor_size(parent, +1); /* since we reduced #of children at parent, it may have fallen below b/2 lower bound */ this->post_remove_shrink_ancestor_path(parent); /* since we removed a leaf node, may have invalidated iterator begin/end endpoints */ this->post_modify_correct_leafnode_endpoints(); return true; } /* must have at least one sibling (else prior visit would have shrunk tree height) */ } log.end_scope(); assert(false); return false; } /*internal_erase_aux*/ void post_modify_add_ancestor_size(InternalNodeType * parent, std::size_t incr_z) { BplusTreeUtil::post_modify_add_ancestor_size(parent, incr_z, this->debug_flag()); } /*post_modify_add_ancestor_size*/ void post_modify_sub_ancestor_size(InternalNodeType * parent, std::size_t decr_z) { BplusTreeUtil::post_modify_sub_ancestor_size(parent, decr_z, this->debug_flag()); } /*post_modify_sub_ancestor_size*/ void post_modify_correct_ancestor_glb_keys(InternalNodeType * parent) { using xo::scope; using xo::xtag; scope log(XO_DEBUG(this->debug_flag()), xtag("parent", parent)); InternalNodeType * grandparent = parent->parent(); std::size_t i_ancestor = 0;; while (grandparent) { log && log(xtag("i_ancestor", i_ancestor), xtag("grandparent", grandparent)); /* find index position of parent subtree, as child of grandparent * Can only use .find_ix() when key-invariants are satisfied. * * Warning: O(bf) call here */ std::size_t parent_ix = grandparent->locate_child_by_address(parent); log && log(xtag("parent.loc", parent_ix)); if (grandparent->lookup_elt(parent_ix).key() == parent->glb_key()) { log && log("grandparent[parent.loc].key == parent.glb_key --> done"); break; } log && log("fix glb key in grandparent"); grandparent->lookup_elt(parent_ix).set_key(parent->glb_key()); /* + repeat 1 level up.. */ parent = grandparent; grandparent = parent->parent(); } } /*post_modify_correct_ancestor_glb_keys*/ /* reset .leafnode_begin, .leafnode_end after changing the set of nodes in a b+ tree */ void post_modify_correct_leafnode_endpoints() { if (root_) { this->leafnode_begin_ = root_->find_min_leaf_node().node(); this->leafnode_end_ = root_->find_max_leaf_node().node(); } else { this->leafnode_begin_ = nullptr; this->leafnode_end_ = nullptr; } } /*post_modify_correct_leafnode_endpoints*/ void post_remove_shrink_ancestor_path(InternalNodeType * node) { using xo::scope; using xo::xtag; scope log(XO_DEBUG(this->debug_flag())); std::size_t const bf = node->branching_factor(); while (node && (node != this->root_.get())) { log && log(xtag("node", node), xtag("node.n_elt", node->n_elt())); if (2 * node->n_elt() >= bf) break; /* node has fewer children than B+ minimum. * either: * - redistribute nodes from sibling * (so that merged node satisfies bf/2 <= n <= bf) * - merge with sibling */ InternalNodeType * parent = node->parent(); /* O(bf), but doesn't rely on satisfied key invariants */ std::size_t node_ix = parent->locate_child_by_address(node); std::size_t right_sibling_ix = node_ix + 1; InternalNodeType * right_sibling = nullptr; if (right_sibling_ix < parent->n_elt()) { /* consider redistributng from right sibling */ right_sibling = reinterpret_cast(parent->lookup_elt(right_sibling_ix).child()); std::size_t n = node->n_elt() + right_sibling->n_elt(); if (n >= 2 * ((bf + 1) / 2)) { log && log("redistribute from right_sibling", xtag("lh.n", node->n_elt()), xtag("rh.n", right_sibling->n_elt())); /* can redistribute one or more nodes from right_sibling -> node * * after redistribution: * - node will have floor(n/2) elements * - right_sibling will have ceil(n/2) = n - floor(n/2) elements */ node->append_from_rh_sibling(n/2 - node->n_elt(), right_sibling); /* glb_key for right sibling changed, need to fixup ancestor book-keeping */ this->post_modify_correct_ancestor_glb_keys(right_sibling); return; } } std::size_t left_sibling_ix = node_ix - 1; /* but beware underflow when node_ix=0 */ InternalNodeType * left_sibling = nullptr; if (node_ix > 0) { /* consider redistributing from left sibling */ left_sibling = reinterpret_cast(parent->lookup_elt(left_sibling_ix).child()); std::size_t n = node->n_elt() + left_sibling->n_elt(); if (n >= 2 * ((bf + 1) / 2)) { log && log("redistribute from left_sibling", xtag("lh.n", left_sibling->n_elt()), xtag("rh.n", node->n_elt())); /* redistribute one or more nodes from left_sibling -> node */ node->prepend_from_lh_sibling(left_sibling, n/2 - node->n_elt(), this->debug_flag()); /* glb_key for node changed, need to fixup ancestor book-keeping */ this->post_modify_correct_ancestor_glb_keys(node); return; } } log && log("cannot redistribute -> drop a node"); /* control here * -> not enough nodes to redistribute from either sibling * -> must shrink number of nodes in tree */ if (right_sibling_ix < parent->n_elt()) { assert(right_sibling); /* RH sibling exists -> merge with it */ node->append_rh_sibling(right_sibling); /* right sibling now empty, drop from parent */ parent->remove_node(right_sibling_ix, this->debug_flag()); } else if (node_ix > 0) { assert(left_sibling); /* LH sibling exists -> merge with it */ left_sibling->append_rh_sibling(node); /* node is now empty, drop from parent */ parent->remove_node(node_ix, this->debug_flag()); } /* continue tree fixup at parent node */ node = parent; } /* if node != root: tree shrank successfully, without propgating to root */ if ((node == this->root_.get()) && (node->n_elt() == 1)) { /* replace root with its single child element; tree height shrinks by one */ this->root_ = std::move(node->lookup_elt(0).release_child()); this->root_->set_parent(nullptr); } } /*post_remove_shrink_ancestor_path*/ void print_aux(std::ostream & os, GenericNodeType const * node, std::uint32_t indent) const { using xo::xtag; if (node) { switch(node->node_type()) { case NodeType::internal: { using xo::pad; InternalNodeType const * internal = reinterpret_cast(node); for (std::uint32_t i=0, n=internal->n_elt(); ilookup_elt(i).child()->n_elt()) << xtag("treez", logutil::nodesize(internal->lookup_elt(i).child())) << xtag("glb", internal->lookup_elt(i).key()) << xtag("@", internal->lookup_elt(i).child()); this->print_aux(os, internal->lookup_elt(i).child(), indent+1); } } break; case NodeType::leaf: { using xo::pad; LeafNodeType const * leaf = reinterpret_cast(node); for (std::uint32_t i=0, n=leaf->n_elt(); ilookup_elt(i).key() << ": " << leaf->lookup_elt(i).value(); } } break; } } else { //os << std::endl; } } /*print_aux*/ private: /* tree properties, in particular: branching factor */ Properties properties_; /* #of items in this tree */ std::size_t n_element_ = 0; /* left-most leaf node for inorder traversal */ LeafNodeType * leafnode_begin_ = nullptr; /* right-most leaf node for inorder traversal */ LeafNodeType * leafnode_end_ = nullptr; /* tree * size root depth * ------------------------- * 0 nullptr 0 * 1..b LeafNode 1 * >b InternalNode >1 */ std::unique_ptr root_; }; /*BplusTree*/ } /*namespace tree*/ } /*namespace xo*/ /* end BplusTree.hpp */