protected override void movingTo(long newLocation) { // if this node has been written before, our left sibling has a pointer to this node on disk. // update that sibling to point at the new location. if (this.Location != 0) { BPlusTreeLeafNode <TKey, TValue> left = GetLeftSibling(false) as BPlusTreeLeafNode <TKey, TValue>; if (left != null) { Tree.Lock(delegate() { left.RightSiblingLocation = newLocation; left.Write(); }); } } }
private static BPlusTreeNode <TKey, TValue> processRead(BPlusTree <TKey, TValue> tree, BPlusTreeIndexNode <TKey, TValue> parent, BinaryReader rdr, object[] args) { // stream has been positioned appropriately. // just read from it. BPlusTreeNode <TKey, TValue> node = null; tree.Lock(delegate() { long startLocation = rdr.BaseStream.Position; int slotSize = rdr.ReadInt32(); bool leaf = rdr.ReadBoolean(); short keywordCount = rdr.ReadInt16(); if (leaf) { node = new BPlusTreeLeafNode <TKey, TValue>(tree, parent, rdr); } else { node = new BPlusTreeIndexNode <TKey, TValue>(tree, parent, rdr); } node.Location = startLocation; // now we must read in all the keywords (they're always at the end of the stream) for (short i = 0; i < keywordCount; i++) { TKey keyManager = new TKey(); keyManager.Read(rdr); node.Keywords.Add(keyManager); } node.SlotSize = slotSize; // orient the stream pointer to the end of the node (which may be past the current data in it) rdr.BaseStream.Position = startLocation + node.SlotSize; }); return(node); }
internal BPlusTreeNode <TKey, TValue> SplitChild(BPlusTreeNode <TKey, TValue> originalChild, out int keywordIndex) { // Split the current node into two nodes: // a. Left node contains all pointers/keywords to the left of the median // b. Right node contains all pointers/keywords to the right of the median // c. Parent node gets median keyword pushed into it // // Disk-wise: the originalChild is always written back to its original position (since we're splitting it, it's guaranteed to get smaller) // the newChild is placed into either an available abandoned node or tacked on the end of the file. // the parent node is written back to its original position IF IT FITS. Since we're coyping a keyword // up into the parent node, it may cause it to outgrow its allocated size in the file. If this occurs, // the call to this.Write() implicitly calls relocate(). relocate() will then either write the parent to // an abandoned node or tack it onto the end of the file. // first, determine the offset of the current originalchild in this (the parent) keywordIndex = 0; try { BPlusTree <TKey, TValue> .LogInfo("Begin splitting node=" + this.ToString() + ", child=" + originalChild.ToString()); if (ChildLocations.Count > 0) { // TODO: troubleshoot this -- after several thousand splits, originalChild.Location is no longer found in the ChildLocations array!!! //if (LastChildLocation == originalChild.Location) { // // rightmost child, on a full node. //} else { while (keywordIndex < ChildLocations.Count && ChildLocations[keywordIndex] != originalChild.Location) { keywordIndex++; } //} } // assign the median keyword to the parent's Keywords list (inserts just before the current one) //if (keywordIndex > this.Keywords.Count) { // // rightmost keyword, add to the end of the list // this.Keywords.Add(originalChild.MedianKeyword); //} else { // not rightmost keyword, insert it at the proper position this.Keywords.Insert(keywordIndex, originalChild.MedianKeyword); //} if (originalChild.IsLeaf) { // since this is a leaf, we will copy up the median keyword to the parent (as opposed to push up for index nodes) // create a leaf node BPlusTreeLeafNode <TKey, TValue> origNode = (BPlusTreeLeafNode <TKey, TValue>)originalChild; BPlusTreeLeafNode <TKey, TValue> newNode = new BPlusTreeLeafNode <TKey, TValue>(this.Tree, (BPlusTreeIndexNode <TKey, TValue>) this, null); // copy right half of keywords + values from originalChild to newChild newNode.Keywords.AddRange(origNode.Keywords.Skip(this.Tree.Median - 1)); newNode.Values.AddRange(origNode.Values.Skip(this.Tree.Median - 1)); // remove right half of keywords + values from originalChild origNode.Keywords = origNode.Keywords.Take(this.Tree.Median - 1).ToList(); origNode.Values = origNode.Values.Take(this.Tree.Median - 1).ToList(); var nextKeywordIndex = keywordIndex + 1; Tree.Lock(delegate() { // write out nodes so sibling locations can be set properly // note: orig node should never relocate on write since we're making it smaller (moving 1/2 of its keywords to a new node) if (origNode.Write()) { BPlusTree <TKey, TValue> .LogInfo("orig leaf node relocated during split child: " + origNode.ToString()); } if (newNode.Write()) { BPlusTree <TKey, TValue> .LogInfo("new leaf node relocated during split child: " + newNode.ToString()); } // adjust sibling locations (we always add the new child to the right of the original one) newNode.RightSiblingLocation = origNode.RightSiblingLocation; // newNode.LeftSiblingLocation = origNode.Location; origNode.RightSiblingLocation = newNode.Location; ChildLocations.Insert(nextKeywordIndex, newNode.Location); // write them out again so we save sibling locations if (origNode.Write()) { BPlusTree <TKey, TValue> .LogInfo("orig leaf node relocated on 2nd write during split child: " + origNode.ToString()); } if (newNode.Write()) { BPlusTree <TKey, TValue> .LogInfo("new leaf node relocated on 2nd write during split child: " + newNode.ToString()); } // and write out the parent since it changed as well if (Write()) { BPlusTree <TKey, TValue> .LogInfo("parent of leaf node relocated during split child: " + this.ToString()); } }); return(newNode); } else { // since this is an index node, we push up the keyword to the parent (as opposed to copy up for leaves) // this means the median keyword will be removed from the original child but not moved to the new child // create an index node BPlusTreeIndexNode <TKey, TValue> origNode = (BPlusTreeIndexNode <TKey, TValue>)originalChild; BPlusTreeIndexNode <TKey, TValue> newNode = new BPlusTreeIndexNode <TKey, TValue>(this.Tree, (BPlusTreeIndexNode <TKey, TValue>) this, null); // copy right half of keywords + childlocations from originalChild to newChild // (since this is an index node, skip the median keyword newNode.Keywords.AddRange(origNode.Keywords.Skip(this.Tree.Median)); newNode.ChildLocations.AddRange(origNode.ChildLocations.Skip(this.Tree.Median)); // remove right half of keywords + childlocations from originalChild // (note we stop one shy of the median keyword -- this is because the median keyword needs to be "pushed up" // on an index node split. However, we don't drop the corresponding ChildLocation) origNode.Keywords = origNode.Keywords.Take(this.Tree.Median - 1).ToList(); origNode.ChildLocations = origNode.ChildLocations.Take(this.Tree.Median).ToList(); var ki = keywordIndex; Tree.Lock(delegate() { if (origNode.Write()) { BPlusTree <TKey, TValue> .LogInfo("orig index node relocated during split child: " + origNode.ToString()); } if (newNode.Write()) { BPlusTree <TKey, TValue> .LogInfo("new index node relocated during split child: " + newNode.ToString()); } // original node may have relocated on write... ChildLocations[ki] = origNode.Location; ChildLocations.Insert(ki + 1, newNode.Location); // and write out the parent since it changed as well if (Write()) { BPlusTree <TKey, TValue> .LogInfo("parent of index node relocated during split child: " + this.ToString()); } }); return(newNode); } } finally { BPlusTree <TKey, TValue> .LogInfo("End splitting node=" + this.ToString() + ", child=" + originalChild.ToString()); } }
protected BPlusTreeNode <TKey, TValue> GetRightSibling(bool mustHaveSameParent) { if (this.IsLeaf) { // use the rightsibling pointer BPlusTreeLeafNode <TKey, TValue> leaf = this as BPlusTreeLeafNode <TKey, TValue>; return(BPlusTreeNode <TKey, TValue> .Read(this.Tree, this.Parent, leaf.RightSiblingLocation)); } else { // look in parent for our smallest keyword, grab childlocation to the left of it int parentChildCount; int childLocationIndex = GetIndexInParent(out parentChildCount); if (childLocationIndex == -1) { // never found the index, or no parent (aka root). No sibling to return. return(null); } else { if (childLocationIndex < parentChildCount - 1) { // we know the right sibling is attached to the same parent. BPlusTreeNode <TKey, TValue> node = BPlusTreeNode <TKey, TValue> .Read(this.Tree, this.Parent, Parent.ChildLocations[childLocationIndex + 1]); return(node); } else { // we are the rightmost child, as we're at the location that's 1 past the fanout size if (mustHaveSameParent) { // caller wants the sibling only if it's from the same parent, and it's not. return(null); } else { // we have to find the common ancestor between this and its right sibling. // spin up until we find the first node whose child index is <= Fanoutsize // then drill down the leftmost path until we're at the same depth as we started at int i = 0; int nodeChildLocationIndex = -1; BPlusTreeNode <TKey, TValue> node = this; while (node.Parent != null) { i++; // keep bubbling up until we're at a node whose parent has at least one child to our right. nodeChildLocationIndex = node.GetIndexInParent(out parentChildCount); if (nodeChildLocationIndex < parentChildCount - 1) { // this node is not the rightmost child of its parent. // we're done bubbling (node contains the ancestor node, nodeChildLocationIndex can tell us the subtree to search down) break; } else { node = node.Parent; } } if (nodeChildLocationIndex == parentChildCount - 1) { // the only common ancestor has our node being the rightmost node in the tree (at this depth). return(null); } // ok, node contains the ancestor somehwere up the tree that has the node we're looking for as its // leftmost descendent at the same depth. // So we just keep drilling down the leftmost path until we're at the same depth. node = BPlusTreeNode <TKey, TValue> .Read(this.Tree, node as BPlusTreeIndexNode <TKey, TValue>, nodeChildLocationIndex + 1); while (i > 0) { BPlusTreeIndexNode <TKey, TValue> indexNode = node as BPlusTreeIndexNode <TKey, TValue>; node = BPlusTreeNode <TKey, TValue> .Read(this.Tree, indexNode, indexNode.ChildLocations[0]); i--; } // node now contains the right sibling! return(node); } } } //// bounce up to the parent //if (Parent != null) { // TKey ourKey = FirstKeyword; // for (int i=0; i < Parent.Keywords.Count; i++) { // TKey parentKeyword = Parent.Keywords[i]; // if (parentKeyword.CompareTo(ourKey, KeywordMatchMode.ExactMatch, false) == 0) { // // follow right child // BPlusTreeNode<TKey, TValue> node = BPlusTreeNode<TKey, TValue>.Read(this.Tree, this.Parent, Parent.ChildLocations[i+1]); // return node; // } // } //} //return null; } }
protected internal override void performSearch(TKey keyword, KeywordMatchMode matchMode, bool ignoreCase, bool returnLeafIfNotFound, List <KeywordMatch <TKey, TValue> > matches) { // given a keyword, spin until the keyword is found (return null when not found) short lastMatchIndex = -2; for (short i = 0; i < Keywords.Count; i++) { int compared = keyword.CompareTo(Keywords[i], matchMode, ignoreCase); if (compared < 0) { // given keyword is smaller than this entry. // means we're done looking in this leaf. // if we didn't find anything and the caller said to return a leaf even if nothing was found, append this leaf if (returnLeafIfNotFound && (matches.Count == 0 || matches[matches.Count - 1].Node != this)) { // add this node as containing the hit even though it doesn't (so the value can be inserted) // add this keyword as matching -- so they know where at in the node to insert their new item matches.Add(new KeywordMatch <TKey, TValue> { Index = i, Keyword = Keywords[i], Value = default(TValue), Node = this }); } return; } else if (compared == 0) { // found keyword! // add this node if it's not already in the list // we may have multiple hits, so don't return yet. // just add it to the list of matches. matches.Add(new KeywordMatch <TKey, TValue> { Index = i, Keyword = Keywords[i], Value = Values[i], Node = this }); lastMatchIndex = i; } else { // compared > 0 // given keyword is larger than this entry. // keep checking. } } var inspectRightLeaf = false; switch (matchMode) { //case KeywordMatchMode.Contains: //case KeywordMatchMode.EndsWith: // // data isn't stored in this order, so we always check the node (assuming there is one) // inspectRightLeaf = true; // break; case KeywordMatchMode.ExactMatch: // nothing more to do -- if the exact match occurred in this node, it won't occur in another node. break; case KeywordMatchMode.StartsWith: case KeywordMatchMode.Contains: case KeywordMatchMode.EndsWith: if (lastMatchIndex == Keywords.Count - 1) { // last keyword matched, next leaf might have info in it. inspectRightLeaf = true; } break; } if (inspectRightLeaf) { var nextLeaf = BPlusTreeLeafNode <TKey, TValue> .Read(this.Tree, null, this.RightSiblingLocation) as BPlusTreeLeafNode <TKey, TValue>; if (nextLeaf != null) { // we're not on the rightmost node, inspect the next one nextLeaf.performSearch(keyword, matchMode, ignoreCase, returnLeafIfNotFound, matches); } else { // we get here, no more leaves exist. // just return, the matches collection is filled properly already. } } else { // we get here, it was bigger than our biggest keyword // Since we're a leaf, it doesn't exist. if (returnLeafIfNotFound) { // add this keyword as matching -- so they know where at in the node to insert their new item (note Index = Keywords.Count, so it'll be tacked on the end) matches.Add(new KeywordMatch <TKey, TValue> { Index = Keywords.Count, Keyword = default(TKey), Value = default(TValue), Node = this }); } } }