/* @file LeafNode.hpp */ #pragma once #include "GenericNode.hpp" #include "xo/indentlog/scope.hpp" #include namespace xo { namespace tree { // ----- LeafNodeItem ----- template using LeafNodeItem = NodeItem; /* - define for symmetry with NodeItem * - LeafNodeItem doesn't contain a child pointer; * it belongs inside a leaf mode, which by definition doesn't have children */ template struct NodeItem { public: using ContentsType = std::pair; public: NodeItem() = default; NodeItem(std::pair kv) : kv_pair_{std::move(kv)} {} std::pair const & kv_pair() const { return kv_pair_; } Key const & key () const { return kv_pair_.first; } Value const & value() const { return kv_pair_.second; } void assign_value(Value x) { kv_pair_.second = std::move(x); } private: /* key+value pair */ std::pair kv_pair_; }; /*NodeItem*/ /* struct with same size as LeafNodeItem, but POD + with no ctor/dtor */ template using LeafNodeItemPlaceholder = NodeItemPlaceholder; template struct LeafNodeShim : public GenericNode { LeafNodeShim(NodeType ntype, std::size_t branching_factor) : GenericNode(ntype, branching_factor) {} /* ordinal_enabled: LeafNode will provide .size(): inherits+overrides GenericNodeBase.size() */ }; template struct LeafNodeShim : public GenericNode { LeafNodeShim(NodeType ntype, std::size_t branching_factor) : GenericNode(ntype, branching_factor) {} /* ordinal_disabled: LeafNode provides LeafNode::size(), but not used */ virtual std::size_t size() const = 0; }; // ----- LeafNode ----- /* require: * - Properties.branching_factor() */ template struct LeafNode : public LeafNodeShim { public: using GenericNodeType = GenericNode; using LeafNodeType = LeafNode; using LeafNodeItemType = LeafNodeItem; using LeafNodeItemPlaceholderType = LeafNodeItemPlaceholder; using InternalNodeType = InternalNode; using ContentsType = typename LeafNodeItemType::ContentsType; public: virtual ~LeafNode(); /* node size in bytes (increases with branching factor) */ static std::size_t node_sizeof(std::size_t branching_factor); /* named ctor idiom. enforce heap allocation + unique_ptr wrapper */ static std::unique_ptr make(std::pair kv_pair, Properties const & properties); /* create+return new leaf node that contains all the items in *src from position [lo_ix, hi_ix), * after this operation size of *src is reduced by (hi_ix - lo_ix) */ static std::unique_ptr annex(std::size_t lo_ix, std::size_t hi_ix, LeafNode * src); LeafNode * prev_leafnode() const { return prev_leafnode_; } LeafNode * next_leafnode() const { return next_leafnode_; } /* .first: true if key in tree already * .second: index position of (strict) least upper bound in .elt_v[] * if .n_elt, key has no upper bound in this node */ std::pair find_lub_ix(Key const & key) const; LeafNodeItemType & lookup_elt(std::size_t i) { return *(reinterpret_cast(&(this->elt_v_[i]))); } LeafNodeItemType const & lookup_elt(std::size_t i) const { return *(reinterpret_cast(&(this->elt_v_[i]))); } void assign_leaf_value(std::size_t elt_ix, Value value) { assert(elt_ix < this->n_elt_); this->lookup_elt(elt_ix).assign_value(std::move(value)); } /*assign_leaf_value*/ /* assign precdeing leaf node (= LH sibling if share same parent) */ void assign_prev_leafnode(LeafNode * x) { prev_leafnode_ = x; } void assign_next_leafnode(LeafNode * x) { next_leafnode_ = x; } /* insert new leaf at position ix, associating key -> value * (shuffle existing elements at ix, ix+1.. 1 position to the right) */ void insert_leaf_item(std::size_t ix, std::pair const & kv_pair, bool debug_flag); /* remove key,value pair at position ix */ void remove_leaf(std::size_t ix, bool debug_flag); /* append n items from left-hand sibling, as new left-most elements * require: combined #of items must be at most b = branching factor */ void prepend_from_lh_sibling(LeafNode * lh, std::size_t n, bool debug_flag); /* apepnd n items from right-hand sibling, as new right-most elements * require: combined #of items must be at most b = branching factor */ void append_from_rh_sibling(std::size_t n, LeafNode * rh); void append_rh_sibling(LeafNode * rh) { this->append_from_rh_sibling(rh->n_elt(), rh); } /* returns new leaf with lower half of original element vector; * original updated to retain upper half */ std::unique_ptr split_leaf_lower(); /* returns new leaf with upper half of original element vector; * original updated to retain lower half */ std::unique_ptr split_leaf_upper(); /* memory for LeafNode instances is always created using new[], * so required to use delete[] to deallocate */ void operator delete (void * mem) noexcept { ::operator delete[](mem); } // ----- Inherited from GenericNode ----- virtual std::size_t size() const override { return this->n_elt(); } virtual Key const & glb_key() const override { return this->lookup_elt(0).key(); } virtual std::size_t verify_helper(InternalNodeType const * parent, bool with_lub_flag, Key const & lub_key, LeafNodeType const * lh_leaf, LeafNodeType const * rh_leaf) const override; virtual void verify_glb_key(Key const & key) const override; virtual FindNodeResult find_min_leaf_node() override; virtual FindNodeResult find_max_leaf_node() override; virtual void notify_remove() override; private: explicit LeafNode(std::size_t branching_factor); LeafNode(std::pair const & kv_pair, std::size_t branching_factor); void assign_siblings(LeafNode * prev, LeafNode * next); private: /* previous LeafNode in key order, immediately before (all the keys in) this node. * use to streamline inorder traversal. */ LeafNode * prev_leafnode_ = nullptr; /* next LeafNode in key order, immediately after (all the keys in) this node. * streamline inorder traversal. */ LeafNode * next_leafnode_ = nullptr; /* flexible array; actual capacity will be Properties.branching_factor(); * but only members [0 .. n_elt-1] are defined. * * actual type of .elt_v[i] is LeafNodeItem; * need to use POD LeafNodeItemPlaceholder to satisfy flexible-array rules */ LeafNodeItemPlaceholderType elt_v_[]; }; /*LeafNode*/ template LeafNode::~LeafNode() { /* since we're using flexible array for .elt_v[], need to manually run destructors */ for (std::size_t i=0, n=this->branching_factor_; ilookup_elt(i).~LeafNodeItemType(); } /* hygiene */ this->n_elt_ = 0; this->branching_factor_ = 0; } /*dtor*/ template std::size_t LeafNode::node_sizeof(std::size_t branching_factor) { /* since we're using flexible array for .elt_v[], need to manually account for it's allocated size */ return (sizeof(LeafNode) + (branching_factor * sizeof(LeafNodeItem))); } /*node_sizeof*/ template std::unique_ptr> LeafNode::make(std::pair kv_pair, Properties const & properties) { using xo::scope; using xo::xtag; std::size_t mem_z = node_sizeof(properties.branching_factor()); /* storage for LeafNode, including storage cost for flexible array LeafNode.elt_v[] */ std::uint8_t * mem = new std::uint8_t[mem_z]; #ifdef NOT_IN_USE scope x("LeafNode.make"); x.log(xtag("sizeof(LeafNode)", sizeof(LeafNode)), xtag("bf", properties.branching_factor()), xtag("mem_z", mem_z), xtag("mem", (void *)mem)); #endif return std::unique_ptr(new (mem) LeafNode(std::move(kv_pair), properties.branching_factor())); } /*make*/ template std::unique_ptr> LeafNode::annex(std::size_t lo_ix, std::size_t hi_ix, LeafNode * src) { using xo::scope; using xo::xtag; std::size_t branching_factor = src->branching_factor(); assert(hi_ix >= lo_ix); assert(hi_ix - lo_ix <= branching_factor); std::size_t mem_z = node_sizeof(branching_factor); std::uint8_t * mem = new std::uint8_t[mem_z]; #ifdef NOT_IN_USE scope x("LeafNode.annex"); x.log(xtag("sizeof(LeafNode)", sizeof(LeafNode)), xtag("bf", branching_factor), xtag("mem_z", mem_z), xtag("mem", (void *)mem)); #endif std::unique_ptr new_node(new (mem) LeafNode(branching_factor)); std::size_t old_n = src->n_elt(); new_node->n_elt_ = hi_ix - lo_ix; std::size_t n_annex = hi_ix - lo_ix; /* annexing from *src into new_node */ for (std::size_t i = 0; i < n_annex; ++i) { LeafNodeItemType & new_slot = new_node->lookup_elt(i); new_slot = std::move(src->lookup_elt(lo_ix + i)); } /* shuffle over any remaining items in *src starting from hi_ix */ for (std::size_t i = lo_ix; i + n_annex < old_n; ++i) { LeafNodeItemType & slot = src->lookup_elt(i); slot = std::move(src->lookup_elt(i + n_annex)); } src->n_elt_ = old_n - n_annex; if (lo_ix == 0) { /* new node builds by taking leftmost elements from src * -> new node becomes src's predecessor */ new_node->assign_siblings(src->prev_leafnode(), src); } else { /* new node builds by taking rightmost elements from src * -> new node becomes src's successor */ new_node->assign_siblings(src, src->next_leafnode()); } return new_node; } /*annex*/ template std::pair LeafNode::find_lub_ix(Key const & key) const { if (key < this->lookup_elt(0).key()) return std::make_pair(false, 0); /* promise: return value >= 0 */ /* .elt_v[0 .. n_elt-1] are maintained in sorted key order */ std::size_t lo = 0; std::size_t hi = this->n_elt_; while (lo + 1 < hi) { /* desired child item will be in range [lo, hi) */ std::size_t mid = lo + (hi - lo) / 2; if (key < this->lookup_elt(mid).key()) hi = mid; else lo = mid; } /* invariant: * - lo is a valid index: elt_v[lo].kv_pair reflects outcome of most recent call to BplusTree.insert() * - .elt_v[lo].key <= key * - if hi<.n_elt, then key < .elt_v[hi].key */ bool presence_flag = (key == this->lookup_elt(lo).key()); return std::make_pair(presence_flag, hi); } /*find_lub_ix*/ template void LeafNode::insert_leaf_item(std::size_t ix, std::pair const & kv_pair, bool debug_flag) { using xo::scope; using xo::xtag; scope log(XO_DEBUG(debug_flag), xtag("self", this), xtag("n_elt", this->n_elt()), xtag("bf", this->branching_factor()), xtag("ix", ix), xtag("key", kv_pair.first), xtag("value", kv_pair.second)); if (this->n_elt_ >= this->branching_factor()) { assert(false); throw std::runtime_error(tostr("LeafNode::insert_leaf: leaf already full", xtag("leaf.n_elt", this->n_elt()), xtag("branching_factor", this->branching_factor()))); } std::size_t pos_ix = this->n_elt_; while (pos_ix > ix) { //scope x1("loop"); //x1.log(xtag("pos_ix", pos_ix)); this->lookup_elt(pos_ix) = std::move(this->lookup_elt(pos_ix - 1)); --pos_ix; } ++(this->n_elt_); this->lookup_elt(ix) = LeafNodeItemType(kv_pair); log.end_scope(); } /*insert_leaf*/ template void LeafNode::remove_leaf(std::size_t ix, bool debug_flag) { using xo::scope; using xo::xtag; scope log(XO_DEBUG(debug_flag), xtag("self", this), xtag("n_elt", this->n_elt()), xtag("bf", this->branching_factor()), xtag("ix", ix)); if (this->n_elt_ == 0) { throw std::runtime_error(tostr("LeafNode::remove_leaf: leaf already empty", xtag("leaf.n_elt", this->n_elt()), xtag("branching_factor", this->branching_factor()))); } /* TODO: removal action for position pos_ix (maintain reductions) */ std::size_t pos_ix = ix; std::size_t end_ix = this->n_elt_ - 1; while (pos_ix < end_ix) { //scope x1("loop"); //x1.log(xtag("pos_ix", pos_ix)); this->lookup_elt(pos_ix) = std::move(this->lookup_elt(pos_ix + 1)); ++pos_ix; } --(this->n_elt_); } /*remove_leaf*/ template void LeafNode::prepend_from_lh_sibling(LeafNode * lh, std::size_t n, bool debug_flag) { using xo::scope; using xo::xtag; scope log(XO_DEBUG(debug_flag), xtag("n", n)); if (this->n_elt() + n > this->branching_factor()) { assert(false); throw std::runtime_error(tostr("LeafNode.prepend_from_lh_sibling: expected combined #elt <= bf", xtag("self.n_elt", this->n_elt()), xtag("n", n), xtag("bf", this->branching_factor()))); } std::size_t n_lh = lh->n_elt(); std::size_t n_rh = this->n_elt(); /* move elts in *this to the right n steps */ for (std::size_t ixp1 = this->n_elt(); ixp1 > 0; --ixp1) { std::size_t ix = ixp1 - 1; this->lookup_elt(ix + n) = std::move(this->lookup_elt(ix)); } /* xfer n elts from upper end of lh, to lower end of *this */ for (std::size_t ix = 0; ix < n; ++ix) { this->lookup_elt(ix) = lh->lookup_elt(n_lh - n + ix); } this->n_elt_ += n; lh->n_elt_ -= n; /* note: since we didn't create/destroy any LeafNodes, * .prev_leafnode / .next_leafnode pointers are unchanged */ log.end_scope(); } /*prepend_from_lh_sibling*/ template void LeafNode::append_from_rh_sibling(std::size_t n, LeafNode * rh) { using xo::xtag; if (this->n_elt() + n > this->branching_factor()) { assert(false); throw std::runtime_error(tostr("LeafNode.append_from_rh_sibling: expected combined #elt <= bf", xtag("self.n_elt", this->n_elt()), xtag("n", n), xtag("bf", this->branching_factor()))); } std::size_t n_lh = this->n_elt(); for (std::size_t ix = 0; ix < n; ++ix) { this->lookup_elt(n_lh + ix) = std::move(rh->lookup_elt(ix)); /* note: leaf items are key,value pairs; * no parent pointers to fixup (cf InternalNode.append_from_rh_sibling) */ } this->n_elt_ += n; /* shuffle remaining members of rh sibling n items to the left */ for (std::size_t ix = 0; ix < rh->n_elt() - n; ++ix) { rh->lookup_elt(ix) = std::move(rh->lookup_elt(ix + n)); } rh->n_elt_ -= n; /* note: since we didn't create/destroy any LeafNodes, * .prev_leafnode / .next_leafnode pointers are unchanged */ } /*append_from_rh_sibling*/ template std::unique_ptr> LeafNode::split_leaf_lower() { std::size_t n_elt = this->n_elt_; std::size_t mid_ix = n_elt / 2; return LeafNode::annex(0, mid_ix, this); } /*split_leaf_lower*/ template std::unique_ptr> LeafNode::split_leaf_upper() { std::size_t n_elt = this->n_elt_; std::size_t mid_ix = n_elt / 2; return LeafNode::annex(mid_ix, n_elt, this); } /*split_leaf_upper*/ template std::size_t LeafNode::verify_helper(InternalNodeType const * parent, bool with_lub_flag, Key const & lub_key, LeafNodeType const * lh_leaf, LeafNodeType const * rh_leaf) const { using xo::xtag; /* verify immediate parent pointer is correct */ if (this->parent() != parent) { throw std::runtime_error(tostr("LeafNode::verify_helper" ": expected parent pointer to refer to actual parent", xtag("stored_parent", this->parent()), xtag("actual_parent", parent))); } /* verify locally stored keys appear in sorted order */ std::size_t n = this->n_elt_; for (std::size_t i=1; i < n; ++i) { LeafNodeItemType const & prev = this->lookup_elt(i-1); LeafNodeItemType const & elt = this->lookup_elt(i); if (prev.key() < elt.key()) { ; } else { throw std::runtime_error(tostr("LeafNode::verify_helper" ": expected local keys in strictly increasing order", xtag("i", i), xtag("key(i-1)", prev.key()), xtag("key(i)", elt.key()))); } } if (with_lub_flag) { if (this->lookup_elt(n-1).key() < lub_key) { ; } else { throw std::runtime_error(tostr("LeafNode::verify_helper" ": expected last local key before parent-supplied lub key", xtag("n", n), xtag("key(n-1)", this->lookup_elt(n-1).key()), xtag("lub_key", lub_key))); } } /* verify next/prev leafnode pointers are consistent */ if ((lh_leaf && (lh_leaf->next_leafnode() != this)) || (this->prev_leafnode() != lh_leaf)) { throw std::runtime_error(tostr("LeafNode::verify_helper" ": inconsistent prev/next leaf pointers", xtag("parent", parent), xtag("lh_leaf", lh_leaf), xtag("lh_leaf.next", lh_leaf ? lh_leaf->next_leafnode() : nullptr), xtag("self", this), xtag("self.prev", this->prev_leafnode()))); } if ((this->next_leafnode() != rh_leaf) || (rh_leaf && (rh_leaf->prev_leafnode() != this))) { throw std::runtime_error(tostr("LeafNode::verify_helper" ": inconsistent prev/next leaf pointers", xtag("parent", parent), xtag("self", this), xtag("self.next", this->next_leafnode()), xtag("rh_leaf", rh_leaf), xtag("rh_leaf.prev", rh_leaf ? rh_leaf->prev_leafnode() : nullptr))); } return this->n_elt(); } /*verify_helper*/ template void LeafNode::verify_glb_key(Key const & key) const { using xo::xtag; LeafNodeItemType const & elt = this->lookup_elt(0); if (elt.key() != key) { throw std::runtime_error(tostr("LeafNode::verify_glb_key" ": expected stored greatest-lower-bound key to match leftmost leaf's key", xtag("@", this), xtag("reported_key", key), xtag("actual_key", elt.key()))); } } /*verify_glb_key*/ template FindNodeResult> LeafNode::find_min_leaf_node() { return FindNodeResult>(0, this); } /*find_min_leaf_node*/ template FindNodeResult> LeafNode::find_max_leaf_node() { return FindNodeResult>(0, this); } /*c_find_max_leaf_node*/ template void LeafNode::notify_remove() { if (this->prev_leafnode_) this->prev_leafnode_->assign_next_leafnode(this->next_leafnode_); if (this->next_leafnode_) this->next_leafnode_->assign_prev_leafnode(this->prev_leafnode_); } /*notify_remove*/ template LeafNode::LeafNode(std::size_t branching_factor) : LeafNodeShim(NodeType::leaf, branching_factor) { /* must call ctor explicitly for each element. * compiler can't do this for us, b/c it doesn't know size of flexible array */ for (std::size_t i = 0, n = branching_factor; i < n; ++i) { new (&(this->lookup_elt(i))) LeafNodeItemType(); } } template LeafNode::LeafNode(std::pair const & kv_pair, std::size_t branching_factor) : LeafNodeShim(NodeType::leaf, branching_factor) { using xo::scope; using xo::xtag; #ifdef NOT_USING_DEBUG scope x("LeafNode.ctor"); #endif this->n_elt_ = 1; /* since .elt_v[] is a flexible array, need to invoke constructors explicitly * (compiler doesn't know how many elements there are -> can't do it for us */ #ifdef NOT_USING_DEBUG x.log(xtag("elt[0]", &(this->lookup_elt(0)))); #endif new (&(this->lookup_elt(0))) LeafNodeItemType(kv_pair); for (std::size_t i = 1, n = branching_factor; i < n; ++i) { #ifdef NOT_USING_DEBUG x.log(xtag("i", i), xtag("elt[i]", &(this->lookup_elt(i)))); #endif /* using placement-new to invoke ctor explicitly */ new (&(this->lookup_elt(i))) LeafNodeItemType(); } } /*ctor*/ template void LeafNode::assign_siblings(LeafNode * p, LeafNode * n) { if (p) p->assign_next_leafnode(this); this->prev_leafnode_ = p; this->next_leafnode_ = n; if (n) n->assign_prev_leafnode(this); } /*assign_siblings*/ } /*namespace tree*/ } /*namespace xo*/ /* end LeafNode.hpp */