Esempio n. 1
0
        /// <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);
                }
            }
        }
Esempio n. 2
0
        /// <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);
            }
        }
Esempio n. 3
0
        /// <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);
                    }
                }
            }
        }