/* * Copyright 2025 Casper Dalgaard Nielsen * Adam Yasser Tallouzi * * Use of this source code is governed by the Apache 2.0 license * that can be found in the LICENSE.md file. *//// Operations on the nodes of a `BPlusTree`.mod BPlusTree.Node {import java.lang.invoke.{VarHandle => JVarHandle}import java.lang.{Object => JObject}import java.util.{Objects => JObjects}use BPlusTree.Lockuse BPlusTree.Search/// /// The Node struct defines the tree, having either children or values depending on /// whether the node is internal or a leaf, respectively. The following invariants /// hold for Node: `parent` is `None` for the root and `Some(p)` for all other nodes /// where `p` is the parent node. `next` is `None` for internal nodes and the /// rightmost leaf node and `Some(p)` for all leaf nodes where `p` is the node /// directly to the right. `isLeaf` is true if the node is a leaf and false /// otherwise. /// /// If `isLeaf == true` /// /// `children` is of size 0 /// /// `keys` and `values` have the same size (which is the `arity` of the /// tree) /// /// `keys[i]` should store the key for the value stored at `values[i]` /// /// `size` is the number of keys/values currently stored in the leaf /// /// If `isLeaf == false` /// /// `values` is of size 0 /// /// `children` is of size `arity` and `keys` have size `arity - 1` /// /// `keys[i]` stores the smallest key in the subtree `children[i+1]` /// /// `size` is the number of children currently stored in the leaf /// /// Lock invariants: /// /// `lock` is the lock associated with the node. When it is held, no other thread may /// modify the node's contents. The only exception to the previous rule is that the /// `parent` pointer is owned by the `lock` on the parent For the root node with /// parent None the parent pointer is owned by the `rootLock` on the tree. When going /// up the tree locks are forcefully acquired. When going down the tree if a lock /// cannot be acquired immediately all current locks will be released and the attempt /// restarted after the wanted lock has been released by the holder. /// struct Node[k, v, r] { keys: Array[k, r], children: Array[Node[k, v, r], r], values: Array[v, r], lock: Lock[r], mut size: Int32, mut parent: Option[Node[k, v, r]], mut next: Option[Node[k, v, r]], isLeaf: Bool }////// Returns the next in-bounds `(index, node)` while traversing right.///defseek(index: Int32, node: Node[k, v, r]): Option[(Int32, Node[k, v, r])] \ r =if (index<node->size) Some((index, node))elsematchnode->next {case None => Nonecase Some(next) => seek(0, next) }////// Compares two leaf chains from left to right in lockstep.////// The input nodes must be leaves.////// Not thread-safe: Reads the live leaf chains of both inputs.///pubdefsameElementsFromLeaf(i1: Int32,n1: Node[k, v, r1],i2: Int32,n2: Node[k, v, r2] ): Bool \ r1 + r2withOrder[k], Eq[v] = match (seek(i1, n1), seek(i2, n2)) {case (None, None) => truecase (Some((j1, m1)), Some((j2, m2))) =>let (k1, v1) = getKeyAndValueAt(j1, m1);let (k2, v2) = getKeyAndValueAt(j2, m2);if (k1==k2andv1==v2)sameElementsFromLeaf(j1+1, m1, j2+1, m2)elsefalsecase_ => false }////// Applies `f` to every mapping in the leaf chain that starts at `n`.////// The input node must be a leaf.////// Not thread-safe: Mutates the live leaf chain that starts at `n`.///pubdeftransformWithKeyFromLeaf(f: k -> v -> v \ ef,i: Int32,n: Node[k, v, r] ): Unit \ r + efwithOrder[k] = matchseek(i, n) {case None => ()case Some((index, node)) => {letv1 = getKeyAndValueAt(index, node) ||> f;Array.put(v1, index, node->values);transformWithKeyFromLeaf(f, index+1, node) } }////// Folds over the leaf chain that starts at `node`, from left to right.////// The input node must be a leaf.////// Not thread-safe: Traverses the live leaf chain that starts at `node`.///pubdeffoldLeftWithKeyFromLeaf(f: b -> k -> v -> b \ ef,acc: b,index: Int32,node: Node[k, v, r] ): b \ ef + r = matchseek(index, node) {case None => acccase Some((i, n)) => {let (k, v) = getKeyAndValueAt(i, n);foldLeftWithKeyFromLeaf(f, f(acc, k, v), i+1, n) } }////// Internal method to insert `key` and `val` in `node` at `insertionPoint`./// Increments the `size` of `node`.////// Lock invariant:////// - On invocation, the thread has a lock on `node`.///defactualInsertLeaf(key: k,val: v,insertionPoint: Int32,node: Node[k, v, r] ): Unit \ r = {letsize = node->size;arrayInsert(val, insertionPoint, size, node->values);arrayInsert(key, insertionPoint, size, node->keys);node->size = size+1 }////// Internal method to update the leaf `node` with `key => f(key, val)` if `key => val` is in `node`.////// Otherwise, `node` is unchanged.////// Thread-safe.////// Lock invariant:////// - On invocation, `stamp` is an optimistic stamp on `node->lock`.///pubdefadjustWithKey(f: k -> v -> v \ ef, key: k, stamp: Int64, t: BPlusTree[k, v, r], node: Node[k, v, r]): Unit \ ef + rwithOrder[k] = {letindex = binarySearch(key, BPlusTree.search(t), node);if (index>=0) {Lock.withWriteLockOrElse(stamp,node->lock,writeStamp -> {letval = Array.get(index, node->values);Array.put(f(key, val), index, node->values);Lock.unlockWrite(writeStamp, node->lock) },() -> BPlusTree.adjustWithKey(f, key, t) ) } else {Lock.ifValidElseNoYield(stamp, node->lock, () -> (), () -> BPlusTree.adjustWithKey(f, key, t)) } }////// Applies the predicate `f` to all pairs of direcly subsequent elements of `v` and returns the and./// E.g. f(v[0], v[1]) and f(v[1], v[2]) and ... and f(v[n-1], v[n])/// Only used by the testing functions///defapplyPredicate(v: Vector[k], f: k -> k -> Bool \ r): Bool \ r = {letzipped = Vector.map(x -> (x, true), v);letreduced = Vector.reduceLeft(old -> cur ->let (oldNum, oldBool) = old;let (curNum, _) = cur; (curNum, oldBoolandf(oldNum, curNum)),zipped );matchreduced {case None => truecase Some((_, bool)) => bool } }////// Insert `x` at position `i` in array `arr` as though it currently has `len`/// elements. Shifts elements at position `[i, .., len]` to `[i+1, .., len+1]`.///defarrayInsert(x: a, i: Int32, len: Int32, arr: Array[a, r]): Unit \ r = {if (len==i)Array.put(x, i, arr)else {Array.copyInto(srcPos = i, dstPos = i+1, len = len-i, src = arr, arr);Array.put(x, i, arr) } }////// Unwraps `opt` if it is `Some` and crashes otherwise. Used only in testing functions.///defunwrapNode(opt: Option[Node[k, v, r]]): Node[k, v, r] = matchopt {case Some(val) => valcase None => bug!("") }////// Returns `true` if and only if `node` is structurally correct.////// Only used for internal testing.///pubdefassertNodeInvariant(node: Node[k, v, r],arity: Int32,isRightMost: Bool,shouldBeBiggerThan: Option[k],search: Search ): Bool \ rwithOrder[k] = matchnode->isLeaf {casefalse =>letc = node->children;letsize = node->size;letkeys = toLength(Array.toVector(node->keys), size-1);letchildren = toLength(Array.toVector(c), size);letkeysInOrder = isSorted(keys, search);letfirstChildWorks = assertNodeInvariant(Vector.get(0, children), arity, false, shouldBeBiggerThan, search);letlastChildWorks = assertNodeInvariant(unwrapNode(Vector.last(children)), arity, isRightMost, if (size==0) shouldBeBiggerThanelseVector.last(keys), search);letzippedWithoutFirst = Vector.filter(match (index, _) -> index!=0, Vector.mapWithIndex(i -> x -> (i, x), toLength(children, size-1)));letmiddleChildrenWorks = Vector.forAll(match (i, p) ->assertNodeInvariant(p, arity, false, Some(Vector.get(i-1, keys)), search),zippedWithoutFirst );letchildrenWorks = firstChildWorksandmiddleChildrenWorksandlastChildWorks;letparentPointerCorrect = Vector.foldLeft(workingSoFar -> cur -> workingSoFarandequalByReference(node, unwrapNode(cur->parent)),true,children );letlastHasNoNext = notisRightMostorOption.isEmpty(unwrapNode(Vector.last(children))->next);letnoLeaves = Array.size(node->values) ==0;letnodeIsUnlocked = notBPlusTree.Lock.isLocked(node->lock);letisBiggerThanPrev = matchshouldBeBiggerThan {case None => truecase Some(v) => comparison(Vector.get(0, keys), v, search) ==1 };parentPointerCorrectandlastHasNoNextandnoLeavesandkeysInOrderandchildrenWorksandnodeIsUnlockedandisBiggerThanPrevcasetrue =>letsize = node->size;letkeys = toLength(Array.toVector(node->keys), size);letkeysInOrder = isSorted(toLength(keys, size), search);letsizeInRange = size<=arityand0<=size;letnoChildren = Array.size(node->children) ==0;letleafIsUnlocked = notBPlusTree.Lock.isLocked(node->lock);sizeInRangeandkeysInOrderandleafIsUnlockedandnoChildren }////// Perform a binary search and return the index of `key` in `node->keys` if it exists./// Otherwise, return `(-insertionPoint - 1)` where `insertionPoint` is the index/// where `key` would be inserted.////// `search` defines the order in which elements of the vectors are compared.///defbinarySearch(key: k, search: Search, node: Node[k, v, r]): Int32 \ rwithOrder[k] = {letlength = if (notnode->isLeaf) node->size -1elsenode->size;letarr = node->keys;deff(l: Int32, r: Int32): Int32 \ r = {if (l<=r) {letm = (l+r) `Int32.rightShift`1;letelem = Array.get(m, arr);if (Object.isNull(elem)) {// We read null. Return -1. For all callers this would mean that the element was not inserted// This can only happen in a multi-threaded environment and all functions should in this// case attempt to validate its stamp, which will fail so the result will not be used. -1 } else {letcomp = comparison(key, elem, search);if (comp==1)f(m+1, r)elseif (comp== -1)f(l, m-1)elsem } } else-l-1 };f(0, length-1) }////// Compare `val1` to `val2`. If `search` has length zero then the order on `k` is used for the comparison.////// If `search` has non-zero length `val1` and `val2` is assumed to be of type `Vector[Int64]`./// The elements will be compared in the order given by `search`.///defcomparison(val1: k, val2: k, search: Search): Int32withOrder[k] = {// For the Datalog engine we support a custom order, but only for `Vector[Int64]`.// This is checked by testing whether `search` is `null`.if (Object.isNull(search)) {letcomp = val1<=>val2;if (comp== Comparison.GreaterThan)1elseif (comp== Comparison.LessThan) -1else0 } else {letlen = Vector.length(search);vectorComp(unchecked_cast(val1asVector[Int64]), unchecked_cast(val2asVector[Int64]), len, 0, search) } }////// Compare `vec1` to `vec2` from `i` (inclusive) to `max` (exlusive) in the order defined by `search`.///defvectorComp(vec1: Vector[Int64], vec2: Vector[Int64], max: Int32, i: Int32, search: Vector[Int32]): Int32 = {if (i>=max) {0 } else {lets = Vector.get(i, search);letvec1P = Vector.get(s, vec1);letvec2P = Vector.get(s, vec2);if (vec1P<vec2P) { -1 } elseif (vec1P>vec2P) {1 } else {vectorComp(vec1, vec2, max, i+1, search) } } }////// Compute the height of the tree rooted in `node`.////// Not thread-safe: Traverses the live tree rooted at `node`.///pubdefcomputeHeight(node: Node[k, v, r]): Int32 \ r = {// Every leaf is at the same height, so just use the left most path.defloop(count, n) = matchn->isLeaf {casetrue => countcasefalse => {if (n->size ==0) {count } else {loop(count+1, Array.get(0, n->children)) } } };loop(1, node) }////// Return the mapping `k => v` at position `index` in `node`. Crashes if/// `index >= node->size`.///pubdefgetKeyAndValueAt(index: Int32, node: Node[k, v, r]): (k, v) \ r = {letk = Array.get(index, node->keys);letv = Array.get(index, node->values); (k, v) }////// Optionally return the node to the right of `node`.///pubdefnext(node: Node[k, v, r]): Option[Node[k, v, r]] \ r = node->next////// Returns `true` if the pointer of `o1` is equal to the pointer of `o2`.///defequalByReference(o1: Node[k, v, r], o2: Node[k, v, r]): Bool = {// Unchecked cast is here used to transform the values to Object.// The purpose is to be able to give them to a Java class which performs pointer comparison.letobj1 = unchecked_cast(o1asJObject);letobj2 = unchecked_cast(o2asJObject);unsafeIO { JObjects.equals(obj1, obj2) } }////// Optionally returns the first mapping satisfying `f` in the leaf chain that starts at `node`.////// The input node must be a leaf.////// Not thread-safe: Traverses the live leaf chain that starts at `node`.///pubdeffindLeftFromLeaf(f: k -> v -> Bool \ ef,index: Int32,node: Node[k, v, r] ): Option[(k, v)] \ ef + r = {if (index<node->size) {letk = Array.get(index, node->keys);letv = Array.get(index, node->values);if (f(k, v)) Some((k, v))elsefindLeftFromLeaf(f, index+1, node) } elsematchnode->next {case None => Nonecase Some(next) => findLeftFromLeaf(f, 0, next) } }////// Returns `true` if and only if there exists mappings in the leaf chain that starts at `node`/// satisfying `f`.////// The input node must be a leaf.////// Not thread-safe: Traverses the live leaf chain that starts at `node`.///pubdefexistsFromLeaf(f: k -> v -> Bool \ ef,index: Int32,node: Node[k, v, r] ): Bool \ ef + r = {if (index<node->size) {letk = Array.get(index, node->keys);letv = Array.get(index, node->values);if (f(k, v))trueelseexistsFromLeaf(f, index+1, node) } elsematchnode->next {case None => falsecase Some(next) => existsFromLeaf(f, 0, next) } }////// Provides access to the `lock` field of `node`.///pubdefgetLock(node: Node[k, v, r]): Lock[r] = node->lock////// Returns `Some(val)` if `key => val` is in `tree`. Otherwise return `None`.///pubdefget(key: k, stamp: Int64, node: Node[k, v, r], tree: BPlusTree[k, v, r]): Option[v] \ rwithOrder[k] = {letindex = binarySearch(key, BPlusTree.search(tree), node);letresult = if (index<0) Noneelse Some(Array.get(index, node->values));Lock.ifValidElse(stamp, node->lock, () -> result, () -> BPlusTree.get(key, tree)) }////// Returns `Some((key, value))` if `key => val` is in `tree`. Otherwise return `None`.///pubdefgetKeyAndValue(key: k, stamp: Int64, node: Node[k, v, r], tree: BPlusTree[k, v, r]): Option[(k, v)] \ rwithOrder[k] = {letindex = binarySearch(key, BPlusTree.search(tree), node);letresult = if (index<0) Noneelse Some((Array.get(index, node->keys), Array.get(index, node->values)));Lock.ifValidElse(stamp, node->lock, () -> result, () -> BPlusTree.getKeyAndValue(key, tree)) }////// Returns `val` if `key => val` is in `tree`. Otherwise return `d`.///pubdefgetWithDefault(key: k, stamp: Int64, d: v, node: Node[k, v, r], tree: BPlusTree[k, v, r]): v \ rwithOrder[k] = {letindex = binarySearch(key, BPlusTree.search(tree), node);letresult = if (index<0)delseArray.get(index, node->values);Lock.ifValidElse(stamp, node->lock, () -> result, () -> BPlusTree.getWithDefault(key, d, tree)) }////// Inserts the mapping `key => val` into the leaf `node`, potentially causing a/// split and returning a new root.////// Lock invariants:////// - On invocation, `stamp` is an optimistic stamp on `node->lock`.////// - On return, the thread has no lock on the tree if the returned value is None./// Otherwise it has a write-lock on `tree->rootLock` and `p` where `Some(p)` is returned///pubdefinsertIntoLeaf(key: k,val: v,node: Node[k, v, r],stamp: Int64,tree: BPlusTree[k, v, r] ): Option[(Node[k, v, r], Int64)] \ rwithOrder[k] = {letsearch = BPlusTree.search(tree);letindex = binarySearch(key, search, node);Lock.withWriteLockOrElse(stamp,node->lock,writeStamp -> insertIntoLeafPropagate(key, val, index, node, writeStamp, tree),() -> BPlusTree.putInternal(key, val, tree) ) }////// Inserts the mapping `key => val` into the leaf `node` if it is not already present/// Otherwise if the mapping `key2 => val2` is presents overwrite the mapping by `key => val`,/// if `decider(key, val, key2, val2) == true`. If a split is caused return the new root.////// Lock invariants:////// - On invocation, `stamp` is an optimistic stamp on `node->lock`.////// - On return, the thread has no lock on the tree if the returned value is None./// Otherwise it has a write-lock on `tree->rootLock` and `p` where `Some(p)` is returned///pubdefinsertIntoLeafIf(decider: k -> v -> k -> v -> Bool,key: k,val: v,node: Node[k, v, r],stamp: Int64,tree: BPlusTree[k, v, r] ): Option[(Node[k, v, r], Int64)] \ rwithOrder[k] = {letsearch = BPlusTree.search(tree);letindex = binarySearch(key, search, node);if (index<0) {// `key` is not currently in tree. Attempt to gain the write lock and add it.insertIntoLeafForceIf(decider, key, val, node, stamp, index, tree) } else {letoldKey = Array.get(index, node->keys);letoldVal = Array.get(index, node->values);Lock.ifValidElse(stamp,node->lock,() ->if (decider(key, val, oldKey, oldVal)) {insertIntoLeafForceIf(decider, key, val, node, stamp, index, tree) } else {// At some point `oldKey => oldVal` was in the tree. We observed it and// decided to not insert `key => val`, by `decider`. None },() -> BPlusTree.putIfInternal(decider, key, val, tree) ) } }////// Attempts to grab the lock of `node` and insert the `key => val` mapping. Restarts with/// `putIfInternal` if this is not successful.////// Lock invariants:////// - On invocation, `stamp` is an optimistic stamp on `node->lock`.////// - On return, the thread has no lock on the tree if the returned value is None./// Otherwise it has a write-lock on `tree->rootLock` and `p` where `Some(p)` is returned///definsertIntoLeafForceIf(decider: k -> v -> k -> v -> Bool,key: k,val: v,node: Node[k, v, r],stamp: Int64,index: Int32,tree: BPlusTree[k, v, r] ): Option[(Node[k, v, r], Int64)] \ rwithOrder[k] =Lock.withWriteLockOrElse(stamp,node->lock,writeStamp -> insertIntoLeafPropagate(key, val, index, node, writeStamp, tree),() -> BPlusTree.putIfInternal(decider, key, val, tree) )////// Inserts the mapping `key => val` into the leaf `node`, potentially causing a/// split and returning a new root.////// Lock invariants:////// - On invocation, `writeStamp` is a write stamp on `node->lock`.////// - On return, the thread has no lock on the tree if the returned value is None./// Otherwise it has a write-lock on `tree->rootLock` and `p` where `Some(p)` is returned///definsertIntoLeafPropagate(key: k,val: v,index: Int32,node: Node[k, v, r],writeStamp: Int64,tree: BPlusTree[k, v, r] ): Option[(Node[k, v, r], Int64)] \ rwithOrder[k] = {letarity = BPlusTree.arity(tree);letsize = node->size;if (index>=0) {// We now have a write lock and can insert before releasing lockArray.put(key, index, node->keys);Array.put(val, index, node->values);Lock.unlockWrite(writeStamp, node->lock); None } else {BPlusTree.incrementSize(tree);// We now have a write lock and can insert and propagate.letinsertionPoint = toInsertionPoint(index);if (size<arity) {// Just insert and after which we are done, so release lock.actualInsertLeaf(key, val, insertionPoint, node);Lock.unlockWrite(writeStamp, node->lock); None } else {// We need to insert and split, locking up through the tree.splitLeaf(node, writeStamp, key, val, insertionPoint, tree) } } }////// Returns `true` if `node` is empty.////// Not thread-safe: Reads the live node without synchronization.///pubdefisEmpty(node: Node[k, v, r]): Bool \ r = node->size ==0////// Returns true if and only if the elements of `v` are sorted./// Only used for internal testing.///defisSorted(v: Vector[k], search: Search): BoolwithOrder[k] =applyPredicate(v, x -> y -> comparison(x, y, search) == -1)////// Returns `true` if `key` is contained in the leaf `node`.///pubdefleafMemberOf(key: k, search: Search, node: Node[k, v, r]): Bool \ rwithOrder[k] = {letindex = binarySearch(key, search, node);if (index<0)falseelsetrue }////// Returns `true` if the mapping `key => val` is contained in the leaf `node`.///pubdefleafMemberOfPair(key: k, val: v, search: Search, node: Node[k, v, r]): Bool \ rwithOrder[k], Eq[v] = {letindex1 = binarySearch(key, search, node);if (index1<0)falseelseArray.get(index1, node->values) ==val }////// Returns the child of the internal `node` whose subtree should be searched for `key`.///pubdefchildForKey(key: k, search: Search, node: Node[k, v, r]): Node[k, v, r] \ rwithOrder[k] = {letchildren = node->children;letindex = binarySearch(key, search, node);if (index<0)Array.get(toInsertionPoint(index), children)elseArray.get(index+1, children) }////// Returns the leftmost child of the internal `node`.///deffirstChild(node: Node[k, v, r]): Node[k, v, r] \ r =Array.get(0, node->children)////// Returns the rightmost child of the internal `node`.///deflastChild(node: Node[k, v, r]): Node[k, v, r] \ r =Array.get(node->size -1, node->children)////// Returns the left-most leaf node rooted in `node`.////// Not thread-safe: Traverses the live tree rooted at `node`.///pubdefleftMostLeafUnconditional(node: Node[k, v, r]): Node[k, v, r] \ r = matchnode->isLeaf {casefalse =>leftMostLeafUnconditional(firstChild(node))casetrue => node }////// Returns the leftmost leaf of the tree rooted at `node` together with an optimistic stamp/// on that leaf.////// Traverses down the leftmost path of the tree, acquiring locks along the way./// If a lock is invalid, yields and retries from the root of `t`.///pubdefleftMostLeaf(n: Node[k, v, r],stamp: Int64,t: BPlusTree[k, v, r] ): (Node[k, v, r], Int64) \ rwithOrder[k] =traverseToLeaf(n, stamp, firstChild, () -> BPlusTree.leftMostLeaf(t))////// Returns the rightmost leaf of the tree rooted at `node` together with an optimistic stamp/// on that leaf.////// Traverses down the rightmost path of the tree, acquiring locks along the way./// If a lock is invalid, yields and retries from the root of `t`.///pubdefrightMostLeaf(n: Node[k, v, r],stamp: Int64,t: BPlusTree[k, v, r] ): (Node[k, v, r], Int64) \ rwithOrder[k] =traverseToLeaf(n, stamp, lastChild, () -> BPlusTree.rightMostLeaf(t))////// Attempt to lock the parent of `node`. Returns the stamp associated with the parent./// If the parent is None return a lock on the `tree->rootLock`.////// Lock invariants:////// - On invocation, the thread has a write-lock on `node`.////// - On return, the thread holds a write-lock on `node` and on `parent` given by/// `node->Some(parent)`///deflockParent(node: Node[k, v, r], tree: BPlusTree[k, v, r]): Int64 \ r = matchnode->parent {case Some(parent) =>letparentLock = parent->lock;letwriteStamp = Lock.writeLock(parentLock);// If some thread had a lock on your parent and changed your parent pointer retry.letpossiblyNewParent = matchnode->parent {case Some(v) => vcase None => bug!("Cannot happen (when there are no deletions). Parent pointer replaced by None") };letisSame = equalByReference(possiblyNewParent, parent);if (notisSame) {Lock.unlockWrite(writeStamp, parentLock);lockParent(node, tree) } elsewriteStamp// `node` was root node. We need to lock the root instead, as it protects the parent pointer of `node`.case None =>letstamp = BPlusTree.writeLockRootPointer(tree);letparentHasChanged = matchnode->parent {case Some(_) => truecase None => false };if (parentHasChanged) {BPlusTree.unlockRootPointer(stamp, tree);lockParent(node, tree) } else {stamp } }////// Create a new leaf node.///pubdefmkLeaf(rc: Region[r],keys: Array[k, r],values: Array[v, r],size: Int32,parent: Option[Node[k, v, r]],next: Option[Node[k, v, r]] ): Node[k, v, r] \ r =new Node @ rc { keys = keys, children = Array.empty(rc, 0), values = values, lock = Lock.mkLock(rc), size = size, parent = parent, next = next, isLeaf = true }////// Create a new internal node.///defmkInternal(rc: Region[r],keys: Array[k, r],children: Array[Node[k, v, r], r],size: Int32,parent: Option[Node[k, v, r]] ): Node[k, v, r] \ r =new Node @ rc { keys = keys, children = children, values = Array.empty(rc, 0), lock = Lock.mkLock(rc), size = size, parent = parent, next = None, isLeaf = false }////// Provides access to the `parent` field of `node`.///pubdefparent(node: Node[k, v, r]): Option[Node[k, v, r]] \ r = node->parent////// Push the key `newKey` to `parent`. If `parent` is `Some(node)`, insert/// `newKey` into `node` if `size + 1 <= arity`. Otherwise split `node` and return/// a new root.////// If `parent` is `None`, create a new root node, insert `newKey` into it and/// return it.////// Lock invariants:////// - On invocation, the thread has a write-lock on `parent`, represented by `parentStamp`.////// - On return, the thread only holds a lock if the return value is Some. In/// that case it holds a write-lock on `tree->rootLock`.defpushKeyToParent(parent: Option[Node[k, v, r]],leftChild: Node[k, v, r],rightChild: Node[k, v, r],parentStamp: Int64,newKey: k,tree: BPlusTree[k, v, r] ): Option[(Node[k, v, r], Int64)] \ rwithOrder[k] = matchparent {case Some(node) => {letarity = BPlusTree.arity(tree);letsearch = BPlusTree.search(tree);letchildren = node->children;letinsertionPoint = toInsertionPoint(binarySearch(newKey, search, node));letsize = node->size;if (size<arity) {arrayInsert(newKey, insertionPoint, size-1, node->keys);arrayInsert(rightChild, insertionPoint+1, size, children);node->size = size+1;Lock.unlockWrite(parentStamp, node->lock); None } else {splitInternal(node, parentStamp, rightChild, insertionPoint, newKey, tree) } }case None => {// In this case, leftChild is the root of the tree// and we must construct a new root nodeletarity = BPlusTree.arity(tree);letrc = BPlusTree.rc(tree);letchildren = Array.empty(rc, arity);letkeys = Array.empty(rc, arity-1);Array.put(leftChild, 0, children);Array.put(rightChild, 1, children);Array.put(newKey, 0, keys);letnewRoot = mkInternal(rc, keys, children, 2, None);leftChild->parent = Some(newRoot);rightChild->parent = Some(newRoot); Some((newRoot, parentStamp)) } }////// Inserts `key => val` into `node` or updates the existing mapping selected by `onExisting`.///defputOrUpdateLeaf(key: k,val: v,tree: BPlusTree[k, v, r],node: Node[k, v, r],stamp: Int64,onExisting: Int32 -> Int64 -> Option[(Node[k, v, r], Int64)] \ ef + r,restart: Unit -> Option[(Node[k, v, r], Int64)] \ ef + r ): Option[(Node[k, v, r], Int64)] \ ef + rwithOrder[k] =Lock.withWriteLockOrElse(stamp,node->lock,writeStamp -> {letindex = binarySearch(key, BPlusTree.search(tree), node);if (index<0) {insertIntoLeafPropagate(key, val, index, node, writeStamp, tree) } else {onExisting(index, writeStamp) } },restart )////// Inserts the mapping `key => f(val, val1)` if `key => val1` is in `node`./// Otherwise, inserts `key => val` into `node`.////// Returns `Some(newRoot, stamp)` if the root was split.////// Thread-safe.///pubdefputWith(f: v -> v -> v \ ef,key: k,val: v,tree: BPlusTree[k, v, r],node: Node[k, v, r],stamp: Int64 ): Option[(Node[k, v, r], Int64)] \ r + efwithOrder[k] =putOrUpdateLeaf(key,val,tree,node,stamp,index -> writeStamp -> {letv1 = Array.get(index, node->values);Array.put(f(val, v1), index, node->values);Lock.unlockWrite(writeStamp, node->lock); None },() -> BPlusTree.putWithInternal(f, key, val, tree) )////// Inserts the mapping `key => f(key, val, val1)` if `key => val1` is in `node`./// Otherwise, inserts `key => val` into `node`.////// Returns `Some(newRoot, stamp)` if the root was split.////// Thread-safe.///pubdefputWithKey(f: k -> v -> v -> v \ ef,key: k,val: v,tree: BPlusTree[k, v, r],stamp: Int64,node: Node[k, v, r] ): Option[(Node[k, v, r], Int64)] \ r + efwithOrder[k] =putOrUpdateLeaf(key,val,tree,node,stamp,index -> writeStamp -> {letv1 = Array.get(index, node->values);Array.put(f(key, val, v1), index, node->values);Lock.unlockWrite(writeStamp, node->lock); None },() -> BPlusTree.putWithKeyInternal(f, key, val, tree) )////// Applies `f` in ascending order to all mappings `k => v` in the tree/// rooted in `node` where `min <= k <= max`.////// Not thread-safe: Traverses the live leaf chain in range order.///pubdefrangeQueryWith(f: k -> v -> Unit \ r0,min: k,max: k,stamp: Int64,node: Node[k, v, r],tree: BPlusTree[k, v, r] ): Unit \ r0 + rwithOrder[k] = {letsearch = BPlusTree.search(tree);let (minLeaf, _) = traverseToLeaf(node,stamp,n -> childForKey(min, search, n),() -> BPlusTree.findLeaf(min, tree) );letindex = binarySearch(min, search, minLeaf);if (index<0) {letinsertionPoint = toInsertionPoint(index);if (insertionPoint<=minLeaf->size) {traverseRight(f, insertionPoint, max, search, minLeaf) } } elsetraverseRight(f, index, max, search, minLeaf) }////// Returns the size of `node`.///pubdefsize(node: Node[k, v, r]): Int32 \ r = node->size////// Split `leftLeaf` into two leaves and push the middle key into the parent,/// potentially causing the parent to split and return a new root. Should only/// be called when `leftLeaf->size == arity` and/// `0 <= insertionPoint <= arity + 1`.////// Lock invariants:////// - On invocation, the thread has a write-lock on `leftNode`.////// - On return, the thread only holds a lock if the return value is Some. In/// that case it holds a write-lock on `tree->rootLock`.defsplitLeaf(leftLeaf: Node[k, v, r],leftStamp: Int64,key: k,val: v,insertionPoint: Int32,tree: BPlusTree[k, v, r] ): Option[(Node[k, v, r], Int64)] \ rwithOrder[k] = {letarity = BPlusTree.arity(tree);letrc = BPlusTree.rc(tree);letprev_mid = arity/2;let (mid, insertInLeft) = if (insertionPoint<=prev_mid) (prev_mid, true) else (prev_mid+1, false);let (rightKeys, rightValues) = splitToRightLeaf(rc, mid, arity, leftLeaf);// Create a new leaf to store the right halfletrightLeaf = mkLeaf(rc, rightKeys, rightValues, leftLeaf->size -mid, leftLeaf->parent, leftLeaf->next);// Update the old leaf's fieldsleftLeaf->next = Some(rightLeaf);leftLeaf->size = mid;// Finally insert the new key-value pair into the correct leafif (insertInLeft)actualInsertLeaf(key, val, insertionPoint, leftLeaf)elseactualInsertLeaf(key, val, insertionPoint-mid, rightLeaf);// Push rightLeaf's smallest key up to parent// We do not need the lock on the left leaf any longer.letparentStamp = lockParent(leftLeaf, tree);rightLeaf->parent = leftLeaf->parent;Lock.unlockWrite(leftStamp, leftLeaf->lock);pushKeyToParent(leftLeaf->parent, leftLeaf, rightLeaf, parentStamp, Array.get(0, rightKeys), tree) }////// Split `leftNode` into two nodes with `newKey` inserted into the appropriate/// node depending on `insertionPoint`.////// Lock invariants:////// - On invocation, the thread has a lock on `leftNode`.////// - On return, the thread only holds a lock if the return value is Some. In/// that case it holds a write-lock on `tree->rootLock`.defsplitInternal(leftNode: Node[k, v, r],leftNodeStamp: Int64,insertedChild: Node[k, v, r],insertionPoint: Int32,newKey: k,tree: BPlusTree[k, v, r] ): Option[(Node[k, v, r], Int64)] \ rwithOrder[k] = {letarity = BPlusTree.arity(tree);letrc = BPlusTree.rc(tree);letprev_mid = arity/2;// Note: The current implementation requires that we do not insert `insertedChild` as// the last element in the left child. If this is done the inserted key will be incorrectlet (mid, insertInLeft, rightSize) = if (insertionPoint>=prev_mid) (prev_mid+1, false, arity-prev_mid) else (prev_mid, true, arity-prev_mid);leftNode->size = mid;let (keyOfRight, rightKeys, rightChildren) = if (insertInLeft) {splitInternalToLeftChild(rc, insertedChild, insertionPoint, newKey, mid, arity, rightSize, leftNode) } else {splitInternalToRightChild(rc, insertedChild, insertionPoint, newKey, mid, arity, rightSize, leftNode) };letrightNode = mkInternal(rc, rightKeys, rightChildren, rightSize, leftNode->parent);// Lock `rightNode` before updating child-pointers.// This prevents threads working on children from grabbing the lock of our// parent pointer before we lock it.letrightNodeStamp = Lock.writeLock(rightNode->lock);letparentStamp = lockParent(leftNode, tree);rightNode->parent = leftNode->parent;Array.forEachWithIndex(i -> child ->if (i<rightSize) {child->parent = Some(rightNode) },rightChildren );Lock.unlockWrite(leftNodeStamp, leftNode->lock);Lock.unlockWrite(rightNodeStamp, rightNode->lock);pushKeyToParent(leftNode->parent, leftNode, rightNode, parentStamp, keyOfRight, tree) }////// Moves elements of `leftLeaf->keys` and `leftLeaf->values` from `mid` (inclusive) to `arity` (exclusive)/// to the returned arrays, respectively.///defsplitToRightLeaf(rc: Region[r], mid: Int32, arity: Int32, leftLeaf: Node[k, v, r]): (Array[k, r], Array[v, r]) \ r = {letrightKeys = Array.empty(rc, arity);letrightValues = Array.empty(rc, arity);Array.copyInto(srcPos = mid, dstPos = 0, len = arity-mid, src = leftLeaf->keys, rightKeys);Array.copyInto(srcPos = mid, dstPos = 0, len = arity-mid, src = leftLeaf->values, rightValues); (rightKeys, rightValues) }////// Moves elements of `leftNode->keys` and `leftNode->children`/// from `mid` (inclusive) to `arity` (exclusive) to the returned/// arrays, respectively. Inserts `insertedChild` at `insertionPoint`/// in `leftNode`.////// Note: This function assumes that `insertionPoint < mid - 1`./// In other words `insertedChild` is not the rightMost child.////// Returns `(key, rightKeys, rightChildren)` where `key` is the largest value in the left subtree.///defsplitInternalToLeftChild(rc: Region[r],insertedChild: Node[k, v, r],insertionPoint: Int32,newKey: k,mid: Int32,arity: Int32,rightSize: Int32,leftNode: Node[k, v, r] ): (k, Array[k, r], Array[Node[k, v, r], r]) \ rwithOrder[k] = {letkeyToPass = Array.get(mid-1, leftNode->keys);letleftChildren = leftNode->children;letleftKeys = leftNode->keys;letrightKeys = Array.empty(rc, arity-1);letrightChildren = Array.empty(rc, arity);Array.copyInto(srcPos = mid, dstPos = 0, len = rightSize-1, src = leftKeys, rightKeys);Array.copyInto(srcPos = mid, dstPos = 0, len = rightSize, src = leftChildren, rightChildren);arrayInsert(insertedChild, insertionPoint+1, mid, leftChildren);arrayInsert(newKey, insertionPoint, mid, leftKeys);leftNode->size = mid+1; (keyToPass, rightKeys, rightChildren) }////// Moves elements of `leftNode->keys` and `leftNode->children` from/// `mid` (inclusive) to `arity` (exclusive) to the returned arrays, respectively./// Inserts `insertedChild` at `insertionPoint` in the right node.////// Returns `(key, rightKeys, rightChildren)` where `key` is the largest value in the left subtree.///defsplitInternalToRightChild(rc: Region[r],insertedChild: Node[k, v, r],insertionPoint: Int32,newKey: k,mid: Int32,arity: Int32,rightSize: Int32,leftNode: Node[k, v, r] ): (k, Array[k, r], Array[Node[k, v, r], r]) \ r = {// Insert the new node in the new right node and copy the relevant elements of the leftNode (index>=mid).letrightKeys = Array.empty(rc, arity-1);letrightChildren = Array.empty(rc, arity);letrightSizeWithoutNew = rightSize-1;if (insertionPoint+1==mid) {// If insertionPoint is the first element copy all old keys and old elements and insert new child at 0.Array.copyInto(srcPos = mid-1, dstPos = 0, len = rightSizeWithoutNew, src = leftNode->keys, rightKeys);Array.copyInto(srcPos = mid, dstPos = 1, len = rightSizeWithoutNew, src = leftNode->children, rightChildren);Array.put(insertedChild, 0, rightChildren); (newKey, rightKeys, rightChildren) } else {letchildIndexInRight = insertionPoint+1-mid;Array.copyInto(srcPos = mid, dstPos = 0, len = childIndexInRight-1, src = leftNode->keys, rightKeys);Array.copyInto(srcPos = mid, dstPos = 0, len = childIndexInRight, src = leftNode->children, rightChildren);Array.put(insertedChild, childIndexInRight, rightChildren);Array.put(newKey, childIndexInRight-1, rightKeys);// If insertionPoint is the last index we are done, otherwise insert rest of list.if (childIndexInRight!=rightSizeWithoutNew) {Array.copyInto(srcPos = mid+childIndexInRight-1, dstPos = childIndexInRight, len = rightSizeWithoutNew-childIndexInRight, src = leftNode->keys, rightKeys);Array.copyInto(srcPos = mid+childIndexInRight, dstPos = childIndexInRight+1, len = rightSizeWithoutNew-childIndexInRight, src = leftNode->children, rightChildren) }; (Array.get(mid-1, leftNode->keys), rightKeys, rightChildren) } }////// Returns the real insertion point of the result of binarySearch, assuming that/// the result of binarySearch was negative.///deftoInsertionPoint(i: Int32): Int32 = -i-1////// Slices `v` to the elements from 0 (inclusive) to `length` (exclusive)./// Only used for internal testing.///deftoLength(v: Vector[v], length: Int32): Vector[v] =Vector.slice(start = 0, end = length, v)////// Returns a string representation of the given Node `node` starting with/// `indent` spaces.////// Not thread-safe: Traverses the live tree rooted at `node`.///pubdeftoString(indent: Int32, rc: Region[r], node: Node[k, v, r]): String \ rwithToString[k], ToString[v] = {letindentString = String.repeat(indent, " ");matchnode->isLeaf {casefalse => {letchildren = node->children;letkeysString = Array.copyOfRange(rc, 0, node->size -1, node->keys) |> // Array.map(rc, tupleToString) |>Array.join(",");letchildrenArr = Array.copyOfRange(rc, 0, node->size, children);letchildrenString = Array.foldLeft(a -> x -> "${a}${toString(indent + 2, rc, x)}", "", childrenArr);"${indentString}Internal(${keysString})\n${childrenString}" }casetrue => {letvalues = Array.copyOfRange(rc, 0, node->size, node->values);letkeys = Array.copyOfRange(rc, 0, node->size, node->keys);letkeysStrings = Array.mapWithIndex(rc, i -> key -> "${key}: ${Array.get(i, values)}", keys);letkeysString = Array.join(",\n${indentString} ", keysStrings);"${indentString}Leaf(\n${indentString} ${keysString}\n${indentString})\n" } } }////// Traverses down the tree according to `pickChild`, acquiring locks along the way./// If a lock is invalid, yields and retries from the root using `restart`.////// `pickChild` should pick a child of `cur` if `cur` is an internal node and is not/// called if `cur` is a leaf.////// Caution: Because invalid locks trigger `restart`, `pickChild` and `restart` may/// be called an unknown number of times before traversal succeeds.////// `stamp` should be an optimistic stamp on `cur`.////// The returned stamp is an optimistic stamp on the returned leaf. Callers must still/// validate or upgrade it before relying on data read from that leaf.///pubdeftraverseToLeaf(cur: Node[k, v, r],stamp: Int64,pickChild: Node[k, v, r] -> Node[k, v, r] \ r,restart: Unit -> (Node[k, v, r], Int64) \ r ): (Node[k, v, r], Int64) \ rwithOrder[k] = matchcur->isLeaf {casefalse => {letchild = pickChild(cur);Lock.ifValidElse(stamp,cur->lock,() -> {letchildStamp = Lock.tryReadLock(child->lock);Lock.ifValidElse(stamp, cur->lock, () -> traverseToLeaf(child, childStamp, pickChild, restart), restart) },restart ) }casetrue => (cur, stamp) }////// Applies `f` to mappings `k => v` starting at `node->keys[index]` until/// `node->keys[index] > max`. Uses the `next` pointer to traverse to the next node.////// Not thread-safe: Traverses the live leaf chain.///deftraverseRight(f: k -> v -> Unit \ ef,index: Int32,max: k,search: Search,node: Node[k, v, r] ): Unit \ ef + rwithOrder[k] = {if (index<node->size) {letk = Array.get(index, node->keys);if (comparison(k, max, search) !=1) {f(k, Array.get(index, node->values));traverseRight(f, index+1, max, search, node) } } elsematchnode->next {case Some(next) => traverseRight(f, 0, max, search, next)case None => () } }////// Apply `f` to all mappings in `node` and all nodes to the right of `node`.////// Not thread-safe: Traverses the live leaf chain.///pubdeftraverseRightUnconditional(f: k -> v -> Unit \ ef,index: Int32,node: Node[k, v, r] ): Unit \ ef + r = {if (index<node->size) {letk = Array.get(index, node->keys);f(k, Array.get(index, node->values));traverseRightUnconditional(f, index+1, node) } elsematchnode->next {case Some(next) => traverseRightUnconditional(f, 0, next)case None => () } }////// Apply `f` to every `stepSize` mapping in `node` and continues in right of `node`.////// Not thread-safe: Traverses the live leaf chain.///pubdeftraverseRightUnconditionalInc(f: k -> v -> Unit \ ef,index: Int32,stepSize: Int32,node: Node[k, v, r] ): Unit \ ef + r = {if (index<node->size) {letk = Array.get(index, node->keys);f(k, Array.get(index, node->values));traverseRightUnconditionalInc(f, index+stepSize, stepSize, node) } elsematchnode->next {case Some(next) => traverseRightUnconditionalInc(f, index-node->size, stepSize, next)case None => () } }////// Apply `f` to all mappings in `node` and all nodes to the right of `node`,/// passing an index `i` as the first argument to `f`.////// Not thread-safe: Traverses the live leaf chain.///pubdeftraverseRightUnconditionalWithIndex(f: Int32 -> k -> v -> Unit \ ef,i: Int32,index0: Int32,node0: Node[k, v, r] ): Unit \ ef + r = matchseek(index0, node0) {case Some((index, node)) => {getKeyAndValueAt(index, node) ||> f(i);traverseRightUnconditionalWithIndex(f, i+1, index+1, node) }case None => () }}