/// <summary> /// Removes a key-value pair from this <see cref="BTree{TKey, TValue}"/>. /// </summary> /// <param name="key">The key that defines the key-value pair to remove.</param> /// <param name="valueOrDefault">Upon removal, assigned to the value that was removed. Otherwise /// assigned to the default <typeparamref name="TValue"/> value.</param> /// <returns>True if the key-value pair was removed. False indicates that the <paramref name="key"/> /// was not present in this <see cref="BTree{TKey, TValue}"/>.</returns> /// <remarks> /// <para> /// Even though the <see cref="Insert(TKey, TValue, bool, out bool)"/> /// method may inflate the <see cref="PageStorage"/> if necessary, this method will never deflate it. /// </para> /// <para> /// If any <see cref="Exception"/> is thrown (except the <see cref="InvalidOperationException"/> mentioned /// in this document), then the application should assume that data may have been corrupted. /// </para> /// </remarks> /// <exception cref="InvalidOperationException">Thrown if <see cref="IsReadOnly"/> is true or if this method /// was called from within a <see cref="Traverse(bool)"/> enumeration.</exception> public bool Remove(TKey key, out TValue valueOrDefault) { if (IsReadOnly) { throw new InvalidOperationException("Cannot remove from a read-only " + nameof(BTree <TKey, TValue>) + "."); } lock (locker) { if (isTraversing) { throw new InvalidOperationException("Cannot remove a key-value pair from a " + nameof(BTree <TKey, TValue>) + " while traversing it."); } var rootNode = Root; if (rootNode != null) { bool ret = rootNode.Remove(key, out valueOrDefault); if (rootNode.KeyValuePairCount == 0) { //We completely emptied the root node if (rootNode.IsLeaf) { //And there are no sub-trees, so we emptied the whole tree this.Root = null; } else { //The new root becomes the only sub-tree this.Root = rootNode.GetSubTreeAt(0); } //Delete the old root node's page PageStorage.FreePage(rootNode.PageIndex); } if (ret) { Count--; } return(ret); } else { //There is no root node, so no key exists! valueOrDefault = default(TValue); return(false); } } }
/// <summary> /// Deallocates a page. /// </summary> /// <param name="index">The index of the page to deallocate.</param> /// <returns>True if the page was deallocated, false if it was already /// unallocated when this method was called.</returns> /// <remarks> /// <para> /// Before freeing the page, this method will ensure that any pending /// write operations are sent to the base <see cref="PageStorage"/>. This /// may be important for security cases where the application was storing /// sensitive data that it wishes to overwrite before freeing the page. After /// cache is flushed, the <see cref="IPageStorage.FreePage(long)"/> method of /// the base <see cref="PageStorage"/> will be called. /// </para> /// </remarks> /// <exception cref="InvalidOperationException">Thrown if <see cref="IsReadOnly"/> is true.</exception> /// <exception cref="ArgumentOutOfRangeException">Thrown if <paramref name="index"/> is negative or /// greater than or equal to <see cref="PageCapacity"/>. See <see cref="IsPageOnStorage(long)"/>.</exception> /// <seealso cref="TryAllocatePage(out long)"/> /// <seealso cref="IsReadOnly"/> /// <seealso cref="IsPageAllocated(long)"/> /// <seealso cref="AllocatedPageCount"/> public bool FreePage(long index) { lock (locker) { if (IsReadOnly) { throw new InvalidOperationException("Cannot free a page on a read-only " + nameof(CachedPageStorage) + "."); } if (!IsPageOnStorage(index)) { throw new ArgumentOutOfRangeException(nameof(index), "The index refers to a page which does not exist on the base " + nameof(IPageStorage) + "."); } //First, evict the page from cache (assuming it is cached). //Note that this may cause any pending writes to be sent to the base PageStorage. //This is intended! The application may have overwritten sensitive data. EvictPageFromCache(index); bool ret = PageStorage.FreePage(index); return(ret); } }
/// <summary> /// Inserts a new key-value pair to this <see cref="BTree{TKey, TValue}"/>, or optionally updates an existing /// one. /// </summary> /// <param name="key">The key.</param> /// <param name="value">The value.</param> /// <param name="updateIfExists">If this <see cref="BTree{TKey, TValue}"/> already contains a key-value pair /// with the specified <paramref name="key"/>, should it be updated?</param> /// <param name="alreadyExists">Assigned to true if the <paramref name="key"/> was already present in this /// <see cref="BTree{TKey, TValue}"/>, otherwise false.</param> /// <returns>True if any change was made, otherwise false. False indicates that the insertion was rejected /// because the <paramref name="key"/> is already stored in this <see cref="BTree{TKey, TValue}"/> while /// <paramref name="updateIfExists"/> is false, or the insertion failed due to an allocation failure /// (when <see cref="IPageStorage.TryAllocatePage(out long)"/> returns false).</returns> /// <remarks> /// <para> /// This method may split any <see cref="BTreeNode{TKey, TValue}"/>s that are discovered to be at /// full capacity (<see cref="BTreeNode{TKey, TValue}.MaxKeyValuePairCapacity"/>) [often called /// 'preemptive split' or 'proactive insertion']. Even if false is returned (due to insertion /// rejection, or allocation failure), some <see cref="BTreeNode{TKey, TValue}"/>s may have been /// split. This will <em>not</em> have any effect on the correctness of the /// <see cref="BTree{TKey, TValue}"/> or any data that is stored in it. When false is returned, the /// caller can be confident that all stored key-value pairs are unchanged, though they may have /// internally moved between <see cref="BTreeNode{TKey, TValue}"/>s (a result of splitting). /// </para> /// <para> /// Insertion may sometimes require a new <see cref="BTreeNode{TKey, TValue}"/> to be allocated on the /// <see cref="PageStorage"/>. If the <see cref="PageStorage"/> is at full capacity (<see cref="IPageStorage.PageCapacity"/>), /// then inflation may be required to insert a new key-value pair. This method will automatically try to inflate /// the <see cref="PageStorage"/> by one page when necessary (unless <see cref="IPageStorage.IsCapacityFixed"/> /// is true) via the <see cref="IPageStorage.TryInflate(long, IProgress{ProgressReport}, CancellationToken)"/> method. /// If allocation or inflation fails (or <see cref="IPageStorage.IsCapacityFixed"/> is true when inflation is /// necessary), then it may not be possible to insert certain <em>new</em> key-value pairs (depending on the /// value of the <paramref name="key"/> and the current structure of the B-Tree). However, even if inflation /// or allocation fails, it will be possible to update the value associated to an <em>existing</em> /// <paramref name="key"/>. /// </para> /// <para> /// If any <see cref="Exception"/> is thrown (except the <see cref="InvalidOperationException"/> mentioned /// in this document), then the application should assume that data may have been corrupted. /// </para> /// </remarks> /// <exception cref="InvalidOperationException">Thrown if <see cref="IsReadOnly"/> is true or if this method /// was called from within a <see cref="Traverse(bool)"/> enumeration.</exception> public bool Insert(TKey key, TValue value, bool updateIfExists, out bool alreadyExists) { if (IsReadOnly) { throw new InvalidOperationException("Cannot insert to a read-only " + nameof(BTree <TKey, TValue>) + "."); } lock (locker) { if (isTraversing) { throw new InvalidOperationException("Cannot insert key-value pairs to a " + nameof(BTreeNode <TKey, TValue>) + " while traversing it."); } var rootNode = Root; if (rootNode == null) { alreadyExists = false;//The tree is empty, so the key certainly does not already exist if (BTreeNode <TKey, TValue> .TryCreateNew(this, true, out var newRootNode)) { newRootNode.KeyValuePairCount = 1; newRootNode.SetKeyAt(0, key); newRootNode.SetValueAt(0, value); Root = newRootNode; Count++; return(true); } else { //Failed to allocate the root node return(false); } } else if (rootNode.KeyValuePairCount == rootNode.MaxKeyValuePairCapacity) { //The root node is full, so we will split it now if (BTreeNode <TKey, TValue> .TryCreateNew(this, false, out var newRootNode)) //Allocate a new root node { if (BTreeNode <TKey, TValue> .TryCreateNew(this, false, out var splitUsingNode)) //Allocate the 'right' part of the split node { newRootNode.KeyValuePairCount = 1; //Just to allow the split newRootNode.SetSubTreeAt(1, null); //Temporary, just to allow the split [otherwise sub tree at 1 will be undefined, very dangerous!] newRootNode.SetSubTreeAt(0, rootNode); newRootNode.SplitSubTreeAt(0, splitUsingNode); newRootNode.KeyValuePairCount--;//Restore to 'correct' //Now 'newRootNode' has only one KeyValuePair (and two sub-trees, one left, one right) //Determine whether we will insert the new key-value pair to the left or the right sub-tree long dstSubTreeIndex = 0; if (newRootNode.GetKeyAt(0).CompareTo(key) < 0) { dstSubTreeIndex = 1; } Root = newRootNode; if (newRootNode.GetSubTreeAt(dstSubTreeIndex).Insert(key, value, updateIfExists, out alreadyExists)) { if (!alreadyExists) { Count++; } return(true); } else { return(false); } } else { //Free 'newRootNode', since we failed to use it PageStorage.FreePage(newRootNode.PageIndex); //Since allocation failed, we cannot insert a new key-value pair. //But we can still update the value of an existing key-value pair, so don't return yet! } } else { //Failed to allocate a new root node, so we cannot insert a new key-value pair. //But we can still update the value of an existing key-value pair, so don't return yet! } //If we made it here, insertion of a new key-value pair failed. if (updateIfExists) { //Try to update the value of the existing key-value pair (if it exists) if (TryUpdateValue(key, value)) { alreadyExists = true; return(true); } else { //Does not exist, and insertion failed due to allocation failure alreadyExists = false; return(false); } } else { //We are not allowed to update any existing key-value pair, so insertion has failed. //But we still need to let the caller know whether the key already exists. alreadyExists = ContainsKey(key, new CancellationToken(false)); return(false); } } else { //Insert starting from the root if (rootNode.Insert(key, value, updateIfExists, out alreadyExists)) { if (!alreadyExists) { Count++; } return(true); } else { return(false); } } } }