internal byte *DirectRead(Slice key) { Lazy <Cursor> lazy; var p = FindPageFor(key, out lazy); var node = p.Search(key); if (node == null) { return(null); } var item1 = new Slice(node); if (item1.Compare(key) != 0) { return(null); } if (node->Flags == (NodeFlags.PageRef)) { var overFlowPage = _tx.GetReadOnlyPage(node->PageNumber); return(overFlowPage.Base + Constants.PageHeaderSize); } return((byte *)node + node->KeySize + Constants.NodeHeaderSize); }
public IIterator MultiRead(Slice key) { Lazy <Cursor> lazy; NodeHeader * node; var page = FindPageFor(key, out node, out lazy); if (page == null || page.LastMatch != 0) { return(new EmptyIterator()); } Debug.Assert(node != null); var fetchedNodeKey = new Slice(node); if (fetchedNodeKey.Compare(key) != 0) { throw new InvalidDataException("Was unable to retrieve the correct node. Data corruption possible"); } if (node->Flags == NodeFlags.MultiValuePageRef) { var tree = OpenOrCreateMultiValueTree(_tx, key, node); return(tree.Iterate()); } var nestedPage = new Page(NodeHeader.DirectAccess(_tx, node), "multi tree", (ushort)NodeHeader.GetDataSize(_tx, node)); return(new PageIterator(nestedPage)); }
public IIterator MultiRead(Transaction tx, Slice key) { using (var cursor = tx.NewCursor(this)) { var page = FindPageFor(tx, key, cursor); if (page == null || page.LastMatch != 0) { return(new EmptyIterator()); } var item = page.Search(key, _cmp); var fetchedNodeKey = new Slice(item); if (fetchedNodeKey.Compare(key, _cmp) != 0) { throw new InvalidDataException("Was unable to retrieve the correct node. Data corruption possible"); } if (item->Flags == NodeFlags.MultiValuePageRef) { var tree = OpenOrCreateMultiValueTree(tx, key, item); return(tree.Iterate(tx)); } return(new SingleEntryIterator(_cmp, item, tx)); } }
public FoundPage Find(Slice key) { for (int i = 0; i < _cache.Length; i++) { var page = _cache[i]; if (page == null) continue; var first = page.FirstKey; var last = page.LastKey; switch (key.Options) { case SliceOptions.BeforeAllKeys: if (first.Options == SliceOptions.BeforeAllKeys) return page; break; case SliceOptions.AfterAllKeys: if (last.Options == SliceOptions.AfterAllKeys) return page; break; case SliceOptions.Key: if ((first.Options != SliceOptions.BeforeAllKeys && key.Compare(first) < 0)) continue; if (last.Options != SliceOptions.AfterAllKeys && key.Compare(last) > 0) continue; return page; default: throw new ArgumentException(key.Options.ToString()); } } return null; }
internal byte *DirectRead(Transaction tx, Slice key) { using (var cursor = tx.NewCursor(this)) { var p = FindPageFor(tx, key, cursor); var node = p.Search(key, _cmp); if (node == null) { return(null); } var item1 = new Slice(node); if (item1.Compare(key, _cmp) != 0) { return(null); } if (node->Flags == (NodeFlags.PageRef)) { var overFlowPage = tx.GetReadOnlyPage(node->PageNumber); return(overFlowPage.Base + Constants.PageHeaderSize); } return((byte *)node + node->KeySize + Constants.NodeHeaderSize); } }
public void DebugValidate(Transaction tx, long root) { if (NumberOfEntries == 0) { return; } var prev = new Slice(GetNode(0)); var pages = new HashSet <long>(); for (int i = 1; i < NumberOfEntries; i++) { var node = GetNode(i); var current = new Slice(node); if (prev.Compare(current) >= 0) { DebugStuff.RenderAndShow(tx, root, 1); throw new InvalidOperationException("The page " + PageNumber + " is not sorted"); } if (node->Flags == (NodeFlags.PageRef)) { if (pages.Add(node->PageNumber) == false) { DebugStuff.RenderAndShow(tx, root, 1); throw new InvalidOperationException("The page " + PageNumber + " references same page multiple times"); } } prev = current; } }
public FoundPage Find(Slice key) { for (int i = 0; i < _cache.Length; i++) { var page = _cache[i]; if (page == null) { continue; } var first = page.FirstKey; var last = page.LastKey; switch (key.Options) { case SliceOptions.BeforeAllKeys: if (first.Options == SliceOptions.BeforeAllKeys) { return(page); } break; case SliceOptions.AfterAllKeys: if (last.Options == SliceOptions.AfterAllKeys) { return(page); } break; case SliceOptions.Key: if ((first.Options != SliceOptions.BeforeAllKeys && key.Compare(first) < 0)) { continue; } if (last.Options != SliceOptions.AfterAllKeys && key.Compare(last) > 0) { continue; } return(page); default: throw new ArgumentException(key.Options.ToString()); } } return(null); }
private byte *SplitPageInHalf(Page rightPage) { int currentIndex = _page.LastSearchPosition; bool newPosition = true; int splitIndex = _page.NumberOfEntries / 2; if (currentIndex < splitIndex) { newPosition = false; } if (_page.IsLeaf) { splitIndex = AdjustSplitPosition(_newKey, _len, _page, currentIndex, splitIndex, ref newPosition); } NodeHeader *currentNode = _page.GetNode(splitIndex); var currentKey = new Slice(currentNode); // here we the current key is the separator key and can go either way, so // use newPosition to decide if it stays on the left node or moves to the right Slice seperatorKey; if (currentIndex == splitIndex && newPosition) { seperatorKey = currentKey.Compare(_newKey) < 0 ? currentKey : _newKey; } else { seperatorKey = currentKey; } AddSeparatorToParentPage(rightPage, seperatorKey); // move the actual entries from page to right page ushort nKeys = _page.NumberOfEntries; for (int i = splitIndex; i < nKeys; i++) { NodeHeader *node = _page.GetNode(i); if (_page.IsBranch && rightPage.NumberOfEntries == 0) { rightPage.CopyNodeDataToEndOfPage(node, Slice.Empty); } else { rightPage.CopyNodeDataToEndOfPage(node); } } _page.Truncate(_tx, splitIndex); // actually insert the new key return((currentIndex > splitIndex || newPosition && currentIndex == splitIndex) ? InsertNewKey(rightPage) : InsertNewKey(_page)); }
public void MultiAdd(Transaction tx, Slice key, Slice value, ushort?version = null) { if (value == null) { throw new ArgumentNullException("value"); } if (value.Size > tx.DataPager.MaxNodeSize) { throw new ArgumentException( "Cannot add a value to child tree that is over " + tx.DataPager.MaxNodeSize + " bytes in size", "value"); } if (value.Size == 0) { throw new ArgumentException("Cannot add empty value to child tree"); } State.IsModified = true; Lazy <Cursor> lazy; var page = FindPageFor(tx, key, out lazy); if (page == null || page.LastMatch != 0) { var ptr = DirectAdd(tx, key, value.Size, version: version); value.CopyTo(ptr); return; } page = tx.ModifyPage(page.PageNumber, page); var item = page.GetNode(page.LastSearchPosition); CheckConcurrency(key, version, item->Version, TreeActionType.Add); var existingValue = new Slice(DirectRead(tx, key), (ushort)item->DataSize); if (existingValue.Compare(value, _cmp) == 0) { return; //nothing to do, the exact value is already there } if (item->Flags == NodeFlags.MultiValuePageRef) { var tree = OpenOrCreateMultiValueTree(tx, key, item); tree.DirectAdd(tx, value, 0); } else // need to turn to tree { var tree = Create(tx, _cmp, TreeFlags.MultiValue); var current = NodeHeader.GetData(tx, item); tree.DirectAdd(tx, current, 0); tree.DirectAdd(tx, value, 0); tx.AddMultiValueTree(this, key, tree); // we need to record that we switched to tree mode here, so the next call wouldn't also try to create the tree again DirectAdd(tx, key, sizeof(TreeRootHeader), NodeFlags.MultiValuePageRef); } }
public unsafe static bool ValidateCurrentKey(this IIterator self, NodeHeader *node, SliceComparer cmp) { if (self.RequiredPrefix != null) { var currentKey = new Slice(node); if (currentKey.StartsWith(self.RequiredPrefix, cmp) == false) { return(false); } } if (self.MaxKey != null) { var currentKey = new Slice(node); if (currentKey.Compare(self.MaxKey, cmp) >= 0) { return(false); } } return(true); }
protected unsafe Tuple <Slice, Slice> ReadKey(Transaction tx, Slice key) { Lazy <Cursor> lazy; var p = tx.State.Root.FindPageFor(key, out lazy); var node = p.Search(key); if (node == null) { return(null); } var item1 = new Slice(node); if (item1.Compare(key) != 0) { return(null); } return(Tuple.Create(item1, new Slice((byte *)node + node->KeySize + Constants.NodeHeaderSize, (ushort)node->DataSize))); }
public Stream Read(Transaction tx, Slice key) { using (var cursor = tx.NewCursor(this)) { var p = FindPageFor(tx, key, cursor); var node = p.Search(key, _cmp); if (node == null) { return(null); } var item = new Slice(node); if (item.Compare(key, _cmp) != 0) { return(null); } return(NodeHeader.Stream(tx, node)); } }
public unsafe int CompareTo(BatchOperation other) { var r = SliceEqualityComparer.Instance.Compare(Key, other.Key); if (r != 0) { return(r); } if (valSlice != null) { if (other.valSlice == null) { return(-1); } return(valSlice.Compare(other.valSlice)); } else if (other.valSlice != null) { return(1); } return(0); }
protected unsafe Tuple <Slice, Slice> ReadKey(Transaction tx, Slice key) { using (var c = tx.NewCursor(Env.Root)) { var p = Env.Root.FindPageFor(tx, key, c); var node = p.Search(key, Env.SliceComparer); if (node == null) { return(null); } var item1 = new Slice(node); if (item1.Compare(key, Env.SliceComparer) != 0) { return(null); } return(Tuple.Create(item1, new Slice((byte *)node + node->KeySize + Constants.NodeHeaderSize, (ushort)node->DataSize))); } }
public IIterator MultiRead(Transaction tx, Slice key) { Lazy<Cursor> lazy; var page = FindPageFor(tx, key, out lazy); if (page == null || page.LastMatch != 0) { return new EmptyIterator(); } var item = page.Search(key, _cmp); var fetchedNodeKey = new Slice(item); if (fetchedNodeKey.Compare(key, _cmp) != 0) { throw new InvalidDataException("Was unable to retrieve the correct node. Data corruption possible"); } if (item->Flags == NodeFlags.MultiValuePageRef) { var tree = OpenOrCreateMultiValueTree(tx, key, item); return tree.Iterate(tx); } return new SingleEntryIterator(_cmp, item, tx); }
public unsafe static bool ValidateCurrentKey(this IIterator self, NodeHeader* node) { if (self.RequiredPrefix != null) { var currentKey = new Slice(node); if (currentKey.StartsWith(self.RequiredPrefix) == false) return false; } if (self.MaxKey != null) { var currentKey = new Slice(node); if (currentKey.Compare(self.MaxKey) >= 0) return false; } return true; }
public void DebugValidate(Transaction tx, long root) { if (NumberOfEntries == 0) return; var prev = new Slice(GetNode(0)); var pages = new HashSet<long>(); for (int i = 1; i < NumberOfEntries; i++) { var node = GetNode(i); var current = new Slice(node); if (prev.Compare(current) >= 0) { DebugStuff.RenderAndShow(tx, root, 1); throw new InvalidOperationException("The page " + PageNumber + " is not sorted"); } if (node->Flags==(NodeFlags.PageRef)) { if (pages.Add(node->PageNumber) == false) { DebugStuff.RenderAndShow(tx, root, 1); throw new InvalidOperationException("The page " + PageNumber + " references same page multiple times"); } } prev = current; } }
public NodeHeader* Search(Slice key) { if (NumberOfEntries == 0) { LastSearchPosition = 0; LastMatch = 1; return null; } if (key.Options == SliceOptions.BeforeAllKeys) { LastSearchPosition = 0; LastMatch = 1; return GetNode(0); } if (key.Options == SliceOptions.AfterAllKeys) { LastMatch = -1; LastSearchPosition = NumberOfEntries - 1; return GetNode(LastSearchPosition); } var pageKey = new Slice(SliceOptions.Key); if (NumberOfEntries == 1) { pageKey.Set(GetNode(0)); LastMatch = key.Compare(pageKey); LastSearchPosition = LastMatch > 0 ? 1 : 0; return LastSearchPosition == 0 ? GetNode(0) : null; } int low = IsLeaf ? 0 : 1; int high = NumberOfEntries - 1; int position = 0; while (low <= high) { position = (low + high) >> 1; var node = GetNode(position); pageKey.Set(node); LastMatch = key.Compare(pageKey); if (LastMatch == 0) break; if (LastMatch > 0) low = position + 1; else high = position - 1; } if (LastMatch > 0) // found entry less than key { position++; // move to the smallest entry larger than the key } Debug.Assert(position < ushort.MaxValue); LastSearchPosition = position; if (position >= NumberOfEntries) return null; return GetNode(position); }
public IIterator MultiRead(Slice key) { Lazy<Cursor> lazy; var page = FindPageFor(key, out lazy); if (page == null || page.LastMatch != 0) { return new EmptyIterator(); } var item = page.Search(key); var fetchedNodeKey = new Slice(item); if (fetchedNodeKey.Compare(key) != 0) { throw new InvalidDataException("Was unable to retrieve the correct node. Data corruption possible"); } if (item->Flags == NodeFlags.MultiValuePageRef) { var tree = OpenOrCreateMultiValueTree(_tx, key, item); return tree.Iterate(); } var nestedPage = new Page(NodeHeader.DirectAccess(_tx, item), "multi tree", (ushort)NodeHeader.GetDataSize(_tx, item)); return new PageIterator(nestedPage); }
public void MultiAdd(Slice key, Slice value, ushort? version = null) { if (value == null) throw new ArgumentNullException("value"); int maxNodeSize = _tx.DataPager.MaxNodeSize; if (value.Size > maxNodeSize) throw new ArgumentException( "Cannot add a value to child tree that is over " + maxNodeSize + " bytes in size", "value"); if (value.Size == 0) throw new ArgumentException("Cannot add empty value to child tree"); State.IsModified = true; Lazy<Cursor> lazy; var page = FindPageFor(key, out lazy); if ((page == null || page.LastMatch != 0)) { MultiAddOnNewValue(_tx, key, value, version, maxNodeSize); return; } page = _tx.ModifyPage(page.PageNumber, page); var item = page.GetNode(page.LastSearchPosition); // already was turned into a multi tree, not much to do here if (item->Flags == NodeFlags.MultiValuePageRef) { var existingTree = OpenOrCreateMultiValueTree(_tx, key, item); existingTree.DirectAdd(value, 0, version: version); return; } byte* nestedPagePtr; if (item->Flags == NodeFlags.PageRef) { var overFlowPage = _tx.ModifyPage(item->PageNumber, null); nestedPagePtr = overFlowPage.Base + Constants.PageHeaderSize; } else { nestedPagePtr = NodeHeader.DirectAccess(_tx, item); } var nestedPage = new Page(nestedPagePtr, "multi tree", (ushort)NodeHeader.GetDataSize(_tx, item)); var existingItem = nestedPage.Search(value); if (nestedPage.LastMatch != 0) existingItem = null;// not an actual match, just greater than ushort previousNodeRevision = existingItem != null ? existingItem->Version : (ushort)0; CheckConcurrency(key, value, version, previousNodeRevision, TreeActionType.Add); if (existingItem != null) { // maybe same value added twice? var tmpKey = new Slice(item); if (tmpKey.Compare(value) == 0) return; // already there, turning into a no-op nestedPage.RemoveNode(nestedPage.LastSearchPosition); } if (nestedPage.HasSpaceFor(_tx, value, 0)) { // we are now working on top of the modified root page, we can just modify the memory directly nestedPage.AddDataNode(nestedPage.LastSearchPosition, value, 0, previousNodeRevision); return; } int pageSize = nestedPage.CalcSizeUsed() + Constants.PageHeaderSize; var newRequiredSize = pageSize + nestedPage.GetRequiredSpace(value, 0); if (newRequiredSize <= maxNodeSize) { // we can just expand the current value... no need to create a nested tree yet var actualPageSize = (ushort)Math.Min(Utils.NearestPowerOfTwo(newRequiredSize), maxNodeSize); ExpandMultiTreeNestedPageSize(_tx, key, value, nestedPagePtr, actualPageSize, item->DataSize); return; } // we now have to convert this into a tree instance, instead of just a nested page var tree = Create(_tx, TreeFlags.MultiValue); for (int i = 0; i < nestedPage.NumberOfEntries; i++) { var existingValue = nestedPage.GetNodeKey(i); tree.DirectAdd(existingValue, 0); } tree.DirectAdd(value, 0, version: version); _tx.AddMultiValueTree(this, key, tree); // we need to record that we switched to tree mode here, so the next call wouldn't also try to create the tree again DirectAdd(key, sizeof (TreeRootHeader), NodeFlags.MultiValuePageRef); }
private bool TryFindSection(Transaction tx, int minSeq, Slice currentKey, Slice start, Slice end, out NodeHeader *current) { int minFreeSpace = _minimumFreePagesInSectionSet ? _minimumFreePagesInSection : Math.Min(256, _lastTransactionPageUsage); current = null; int currentMax = 0; using (var it = _env.FreeSpaceRoot.Iterate(tx)) { it.RequiredPrefix = _sectionsPrefix; it.MaxKey = end; if (it.Seek(start) == false) { return(false); } int triesAfterFindingSuitable = 256; do { if (_recordsToSkip.Exists(x => x.Compare(it.CurrentKey, _env.SliceComparer) == 0)) { continue; // if it is marked in memory for either update / delete, we don't want it } if (current != null) { triesAfterFindingSuitable--; } if (currentKey != null) { if (_currentKey.Compare(it.CurrentKey, _env.SliceComparer) == 0) { continue; // skip current one } } using (var stream = it.CreateStreamForCurrent()) using (var reader = new BinaryReader(stream)) { stream.Position = sizeof(long); var largestSeq = reader.ReadInt32(); if (largestSeq < minSeq) { continue; } var pageCount = reader.ReadInt32(); if (pageCount < minFreeSpace || pageCount < currentMax) { continue; } current = it.Current; currentMax = pageCount; } } while (it.MoveNext() && triesAfterFindingSuitable >= 0); } return(current != null); }
public NodeHeader* Search(Slice key, SliceComparer cmp) { if (NumberOfEntries == 0) { LastSearchPosition = 0; LastMatch = 1; return null; } if (key.Options == SliceOptions.BeforeAllKeys) { LastSearchPosition = 0; LastMatch = 1; return GetNode(0); } if (key.Options == SliceOptions.AfterAllKeys) { LastMatch = -1; LastSearchPosition = NumberOfEntries - 1; return GetNode(LastSearchPosition); } int low = IsLeaf ? 0 : 1; int high = NumberOfEntries - 1; int position = 0; var pageKey = new Slice(SliceOptions.Key); bool matched = false; NodeHeader* node = null; while (low <= high) { position = (low + high) >> 1; node = GetNode(position); pageKey.Set(node); LastMatch = key.Compare(pageKey, cmp); matched = true; if (LastMatch == 0) break; if (LastMatch > 0) low = position + 1; else high = position - 1; } if (matched == false) { node = GetNode(position); LastMatch = key.Compare(pageKey, cmp); } if (LastMatch > 0) // found entry less than key position++; // move to the smallest entry larger than the key Debug.Assert(position < ushort.MaxValue); LastSearchPosition = position; if (position >= NumberOfEntries) return null; return node; }
protected unsafe Tuple<Slice, Slice> ReadKey(Transaction tx, Slice key) { Lazy<Cursor> lazy; var p = tx.State.Root.FindPageFor(tx, key, out lazy); var node = p.Search(key, Env.SliceComparer); if (node == null) return null; var item1 = new Slice(node); if (item1.Compare(key, Env.SliceComparer) != 0) return null; return Tuple.Create(item1, new Slice((byte*) node + node->KeySize + Constants.NodeHeaderSize, (ushort) node->DataSize)); }
internal byte* DirectRead(Transaction tx, Slice key) { Lazy<Cursor> lazy; var p = FindPageFor(tx, key, out lazy); var node = p.Search(key, _cmp); if (node == null) return null; var item1 = new Slice(node); if (item1.Compare(key, _cmp) != 0) return null; if (node->Flags == (NodeFlags.PageRef)) { var overFlowPage = tx.GetReadOnlyPage(node->PageNumber); return overFlowPage.Base + Constants.PageHeaderSize; } return (byte*) node + node->KeySize + Constants.NodeHeaderSize; }
private byte* SplitPageInHalf(Page rightPage) { int currentIndex = _page.LastSearchPosition; bool newPosition = true; int splitIndex = _page.NumberOfEntries/2; if (currentIndex < splitIndex) newPosition = false; if (_page.IsLeaf) { splitIndex = AdjustSplitPosition(_newKey, _len, _page, currentIndex, splitIndex, ref newPosition); } NodeHeader* currentNode = _page.GetNode(splitIndex); var currentKey = new Slice(currentNode); // here we the current key is the separator key and can go either way, so // use newPosition to decide if it stays on the left node or moves to the right Slice seperatorKey; if (currentIndex == splitIndex && newPosition) { seperatorKey = currentKey.Compare(_newKey, NativeMethods.memcmp) < 0 ? currentKey : _newKey; } else { seperatorKey = currentKey; } AddSeparatorToParentPage(rightPage, seperatorKey); // move the actual entries from page to right page ushort nKeys = _page.NumberOfEntries; for (int i = splitIndex; i < nKeys; i++) { NodeHeader* node = _page.GetNode(i); if (_page.IsBranch && rightPage.NumberOfEntries == 0) { rightPage.CopyNodeDataToEndOfPage(node, Slice.Empty); } else { rightPage.CopyNodeDataToEndOfPage(node); } } _page.Truncate(_tx, splitIndex); // actually insert the new key return (currentIndex > splitIndex || newPosition && currentIndex == splitIndex) ? InsertNewKey(rightPage) : InsertNewKey(_page); }
public override int Compare(Slice a, Slice b) { return a.Compare(b); }
public NodeHeader *Search(Slice key) { if (NumberOfEntries == 0) { LastSearchPosition = 0; LastMatch = 1; return(null); } if (key.Options == SliceOptions.BeforeAllKeys) { LastSearchPosition = 0; LastMatch = 1; return(GetNode(0)); } if (key.Options == SliceOptions.AfterAllKeys) { LastMatch = -1; LastSearchPosition = NumberOfEntries - 1; return(GetNode(LastSearchPosition)); } var pageKey = new Slice(SliceOptions.Key); if (NumberOfEntries == 1) { pageKey.Set(GetNode(0)); LastMatch = key.Compare(pageKey); LastSearchPosition = LastMatch > 0 ? 1 : 0; return(LastSearchPosition == 0 ? GetNode(0) : null); } int low = IsLeaf ? 0 : 1; int high = NumberOfEntries - 1; int position = 0; while (low <= high) { position = (low + high) >> 1; var node = GetNode(position); pageKey.Set(node); LastMatch = key.Compare(pageKey); if (LastMatch == 0) { break; } if (LastMatch > 0) { low = position + 1; } else { high = position - 1; } } if (LastMatch > 0) // found entry less than key { position++; // move to the smallest entry larger than the key } Debug.Assert(position < ushort.MaxValue); LastSearchPosition = position; if (position >= NumberOfEntries) { return(null); } return(GetNode(position)); }
public void MultiAdd(Transaction tx, Slice key, Slice value, ushort? version = null) { if (value == null) throw new ArgumentNullException("value"); if (value.Size > tx.DataPager.MaxNodeSize) throw new ArgumentException( "Cannot add a value to child tree that is over " + tx.DataPager.MaxNodeSize + " bytes in size", "value"); if (value.Size == 0) throw new ArgumentException("Cannot add empty value to child tree"); State.IsModified = true; Lazy<Cursor> lazy; var page = FindPageFor(tx, key, out lazy); if (page == null || page.LastMatch != 0) { var ptr = DirectAdd(tx, key, value.Size, version: version); value.CopyTo(ptr); return; } page = tx.ModifyPage(page.PageNumber, page); var item = page.GetNode(page.LastSearchPosition); CheckConcurrency(key, version, item->Version, TreeActionType.Add); var existingValue = new Slice(DirectRead(tx, key), (ushort) item->DataSize); if (existingValue.Compare(value, _cmp) == 0) return; //nothing to do, the exact value is already there if (item->Flags == NodeFlags.MultiValuePageRef) { var tree = OpenOrCreateMultiValueTree(tx, key, item); tree.DirectAdd(tx, value, 0); } else // need to turn to tree { var tree = Create(tx, _cmp, TreeFlags.MultiValue); var current = NodeHeader.GetData(tx, item); tree.DirectAdd(tx, current, 0); tree.DirectAdd(tx, value, 0); tx.AddMultiValueTree(this, key, tree); // we need to record that we switched to tree mode here, so the next call wouldn't also try to create the tree again DirectAdd(tx, key, sizeof (TreeRootHeader), NodeFlags.MultiValuePageRef); } }
public void MultiAdd(Slice key, Slice value, ushort?version = null) { if (value == null) { throw new ArgumentNullException("value"); } int maxNodeSize = _tx.DataPager.MaxNodeSize; if (value.Size > maxNodeSize) { throw new ArgumentException( "Cannot add a value to child tree that is over " + maxNodeSize + " bytes in size", "value"); } if (value.Size == 0) { throw new ArgumentException("Cannot add empty value to child tree"); } State.IsModified = true; Lazy <Cursor> lazy; NodeHeader * node; var page = FindPageFor(key, out node, out lazy); if ((page == null || page.LastMatch != 0)) { MultiAddOnNewValue(_tx, key, value, version, maxNodeSize); return; } page = _tx.ModifyPage(page.PageNumber, page); var item = page.GetNode(page.LastSearchPosition); // already was turned into a multi tree, not much to do here if (item->Flags == NodeFlags.MultiValuePageRef) { var existingTree = OpenOrCreateMultiValueTree(_tx, key, item); existingTree.DirectAdd(value, 0, version: version); return; } byte *nestedPagePtr; if (item->Flags == NodeFlags.PageRef) { var overFlowPage = _tx.ModifyPage(item->PageNumber, null); nestedPagePtr = overFlowPage.Base + Constants.PageHeaderSize; } else { nestedPagePtr = NodeHeader.DirectAccess(_tx, item); } var nestedPage = new Page(nestedPagePtr, "multi tree", (ushort)NodeHeader.GetDataSize(_tx, item)); var existingItem = nestedPage.Search(value); if (nestedPage.LastMatch != 0) { existingItem = null; // not an actual match, just greater than } ushort previousNodeRevision = existingItem != null ? existingItem->Version : (ushort)0; CheckConcurrency(key, value, version, previousNodeRevision, TreeActionType.Add); if (existingItem != null) { // maybe same value added twice? var tmpKey = new Slice(item); if (tmpKey.Compare(value) == 0) { return; // already there, turning into a no-op } nestedPage.RemoveNode(nestedPage.LastSearchPosition); } if (nestedPage.HasSpaceFor(_tx, value, 0)) { // we are now working on top of the modified root page, we can just modify the memory directly nestedPage.AddDataNode(nestedPage.LastSearchPosition, value, 0, previousNodeRevision); return; } int pageSize = nestedPage.CalcSizeUsed() + Constants.PageHeaderSize; var newRequiredSize = pageSize + nestedPage.GetRequiredSpace(value, 0); if (newRequiredSize <= maxNodeSize) { // we can just expand the current value... no need to create a nested tree yet var actualPageSize = (ushort)Math.Min(Utils.NearestPowerOfTwo(newRequiredSize), maxNodeSize); ExpandMultiTreeNestedPageSize(_tx, key, value, nestedPagePtr, actualPageSize, item->DataSize); return; } // we now have to convert this into a tree instance, instead of just a nested page var tree = Create(_tx, TreeFlags.MultiValue); for (int i = 0; i < nestedPage.NumberOfEntries; i++) { var existingValue = nestedPage.GetNodeKey(i); tree.DirectAdd(existingValue, 0); } tree.DirectAdd(value, 0, version: version); _tx.AddMultiValueTree(this, key, tree); // we need to record that we switched to tree mode here, so the next call wouldn't also try to create the tree again DirectAdd(key, sizeof(TreeRootHeader), NodeFlags.MultiValuePageRef); }