Esempio n. 1
0
        // was:sqlite3BtreeInsert
        public RC Insert(byte[] key, long nKey, byte[] data, int nData, int nZero, bool appendBiasRight, int seekResult)
        {
            var loc   = seekResult; // -1: before desired location  +1: after
            var szNew = 0;
            int idx;
            var p   = this.Tree;
            var pBt = p.Shared;
            int oldCell;

            byte[] newCell = null;
            if (this.State == CursorState.FAULT)
            {
                Debug.Assert(this.SkipNext != 0);
                return((RC)this.SkipNext);
            }
            Debug.Assert(HoldsMutex());
            Debug.Assert(this.Writeable && pBt.InTransaction == TRANS.WRITE && !pBt.ReadOnly);
            Debug.Assert(p.hasSharedCacheTableLock(this.RootID, (this.KeyInfo != null), LOCK.WRITE));
            // Assert that the caller has been consistent. If this cursor was opened expecting an index b-tree, then the caller should be inserting blob
            // keys with no associated data. If the cursor was opened expecting an intkey table, the caller should be inserting integer keys with a
            // blob of associated data.
            Debug.Assert((key == null) == (this.KeyInfo == null));
            // If this is an insert into a table b-tree, invalidate any incrblob cursors open on the row being replaced (assuming this is a replace
            // operation - if it is not, the following is a no-op).  */
            if (this.KeyInfo == null)
            {
                Btree.invalidateIncrblobCursors(p, nKey, false);
            }
            // Save the positions of any other cursors open on this table.
            // In some cases, the call to btreeMoveto() below is a no-op. For example, when inserting data into a table with auto-generated integer
            // keys, the VDBE layer invokes sqlite3BtreeLast() to figure out the integer key to use. It then calls this function to actually insert the
            // data into the intkey B-Tree. In this case btreeMoveto() recognizes that the cursor is already where it needs to be and returns without
            // doing any work. To avoid thwarting these optimizations, it is important not to clear the cursor here.
            var rc = pBt.saveAllCursors(this.RootID, this);

            if (rc != RC.OK)
            {
                return(rc);
            }
            if (loc == 0)
            {
                rc = BtreeMoveTo(key, nKey, appendBiasRight, ref loc);
                if (rc != RC.OK)
                {
                    return(rc);
                }
            }
            Debug.Assert(this.State == CursorState.VALID || (this.State == CursorState.INVALID && loc != 0));
            var pPage = this.Pages[this.PageID];

            Debug.Assert(pPage.HasIntKey || nKey >= 0);
            Debug.Assert(pPage.Leaf != 0 || !pPage.HasIntKey);
            Btree.TRACE("INSERT: table=%d nkey=%lld ndata=%d page=%d %s\n", this.RootID, nKey, nData, pPage.ID, (loc == 0 ? "overwrite" : "new entry"));
            Debug.Assert(pPage.HasInit);
            pBt.allocateTempSpace();
            newCell = pBt.pTmpSpace;
            rc      = pPage.fillInCell(newCell, key, nKey, data, nData, nZero, ref szNew);
            if (rc != RC.OK)
            {
                goto end_insert;
            }
            Debug.Assert(szNew == pPage.cellSizePtr(newCell));
            Debug.Assert(szNew <= Btree.MX_CELL_SIZE(pBt));
            idx = this.PagesIndexs[this.PageID];
            if (loc == 0)
            {
                ushort szOld;
                Debug.Assert(idx < pPage.Cells);
                rc = Pager.Write(pPage.DbPage);
                if (rc != RC.OK)
                {
                    goto end_insert;
                }
                oldCell = pPage.FindCell(idx);
                if (0 == pPage.Leaf)
                {
                    newCell[0] = pPage.Data[oldCell + 0];
                    newCell[1] = pPage.Data[oldCell + 1];
                    newCell[2] = pPage.Data[oldCell + 2];
                    newCell[3] = pPage.Data[oldCell + 3];
                }
                szOld = pPage.cellSizePtr(oldCell);
                rc    = pPage.clearCell(oldCell);
                pPage.dropCell(idx, szOld, ref rc);
                if (rc != RC.OK)
                {
                    goto end_insert;
                }
            }
            else if (loc < 0 && pPage.Cells > 0)
            {
                Debug.Assert(pPage.Leaf != 0);
                idx = ++this.PagesIndexs[this.PageID];
            }
            else
            {
                Debug.Assert(pPage.Leaf != 0);
            }
            pPage.insertCell(idx, newCell, szNew, null, 0, ref rc);
            Debug.Assert(rc != RC.OK || pPage.Cells > 0 || pPage.NOverflows > 0);
            // If no error has occured and pPage has an overflow cell, call balance() to redistribute the cells within the tree. Since balance() may move
            // the cursor, zero the BtCursor.info.nSize and BtCursor.validNKey variables.
            // Previous versions of SQLite called moveToRoot() to move the cursor back to the root page as balance() used to invalidate the contents
            // of BtCursor.apPage[] and BtCursor.aiIdx[]. Instead of doing that, set the cursor state to "invalid". This makes common insert operations
            // slightly faster.
            // There is a subtle but important optimization here too. When inserting multiple records into an intkey b-tree using a single cursor (as can
            // happen while processing an "INSERT INTO ... SELECT" statement), it is advantageous to leave the cursor pointing to the last entry in
            // the b-tree if possible. If the cursor is left pointing to the last entry in the table, and the next row inserted has an integer key
            // larger than the largest existing key, it is possible to insert the row without seeking the cursor. This can be a big performance boost.
            this.Info.nSize = 0;
            this.ValidNKey  = false;
            if (rc == RC.OK && pPage.NOverflows != 0)
            {
                rc = Balance();
                // Must make sure nOverflow is reset to zero even if the balance() fails. Internal data structure corruption will result otherwise.
                // Also, set the cursor state to invalid. This stops saveCursorPosition() from trying to save the current position of the cursor.
                this.Pages[this.PageID].NOverflows = 0;
                this.State = CursorState.INVALID;
            }
            Debug.Assert(this.Pages[this.PageID].NOverflows == 0);
end_insert:
            return(rc);
        }
Esempio n. 2
0
        // was:sqlite3BtreeDelete
        public RC Delete()
        {
            MemPage pPage;                      // Page to delete cell from
            int     pCell;                      // Pointer to cell to delete
            int     iCellIdx;                   // Index of cell to delete
            int     iCellDepth;                 // Depth of node containing pCell
            var     p   = this.Tree;
            var     pBt = p.Shared;

            Debug.Assert(HoldsMutex());
            Debug.Assert(pBt.InTransaction == TRANS.WRITE);
            Debug.Assert(!pBt.ReadOnly);
            Debug.Assert(this.Writeable);
            Debug.Assert(p.hasSharedCacheTableLock(this.RootID, (this.KeyInfo != null), LOCK.WRITE));
            Debug.Assert(!p.hasReadConflicts(this.RootID));
            if (Check.NEVER(this.PagesIndexs[this.PageID] >= this.Pages[this.PageID].Cells) || Check.NEVER(this.State != CursorState.VALID))
            {
                return(RC.ERROR);
            }
            // If this is a delete operation to remove a row from a table b-tree, invalidate any incrblob cursors open on the row being deleted.
            if (this.KeyInfo == null)
            {
                Btree.invalidateIncrblobCursors(p, this.Info.nKey, false);
            }
            iCellDepth = this.PageID;
            iCellIdx   = this.PagesIndexs[iCellDepth];
            pPage      = this.Pages[iCellDepth];
            pCell      = pPage.FindCell(iCellIdx);
            // If the page containing the entry to delete is not a leaf page, move the cursor to the largest entry in the tree that is smaller than
            // the entry being deleted. This cell will replace the cell being deleted from the internal node. The 'previous' entry is used for this instead
            // of the 'next' entry, as the previous entry is always a part of the sub-tree headed by the child page of the cell being deleted. This makes
            // balancing the tree following the delete operation easier.
            RC rc;

            if (pPage.Leaf == 0)
            {
                var notUsed = 0;
                rc = MovePrevious(ref notUsed);
                if (rc != RC.OK)
                {
                    return(rc);
                }
            }
            // Save the positions of any other cursors open on this table before making any modifications. Make the page containing the entry to be
            // deleted writable. Then free any overflow pages associated with the entry and finally remove the cell itself from within the page.
            rc = pBt.saveAllCursors(this.RootID, this);
            if (rc != RC.OK)
            {
                return(rc);
            }
            rc = Pager.Write(pPage.DbPage);
            if (rc != RC.OK)
            {
                return(rc);
            }
            rc = pPage.clearCell(pCell);
            pPage.dropCell(iCellIdx, pPage.cellSizePtr(pCell), ref rc);
            if (rc != RC.OK)
            {
                return(rc);
            }
            // If the cell deleted was not located on a leaf page, then the cursor is currently pointing to the largest entry in the sub-tree headed
            // by the child-page of the cell that was just deleted from an internal node. The cell from the leaf node needs to be moved to the internal
            // node to replace the deleted cell.
            if (pPage.Leaf == 0)
            {
                var pLeaf = this.Pages[this.PageID];
                int nCell;
                var n = this.Pages[iCellDepth + 1].ID;
                pCell = pLeaf.FindCell(pLeaf.Cells - 1);
                nCell = pLeaf.cellSizePtr(pCell);
                Debug.Assert(Btree.MX_CELL_SIZE(pBt) >= nCell);
                rc = Pager.Write(pLeaf.DbPage);
                var pNext_4 = MallocEx.sqlite3Malloc(nCell + 4);
                Buffer.BlockCopy(pLeaf.Data, pCell - 4, pNext_4, 0, nCell + 4);
                pPage.insertCell(iCellIdx, pNext_4, nCell + 4, null, n, ref rc);
                pLeaf.dropCell(pLeaf.Cells - 1, nCell, ref rc);
                if (rc != RC.OK)
                {
                    return(rc);
                }
            }
            // Balance the tree. If the entry deleted was located on a leaf page, then the cursor still points to that page. In this case the first
            // call to balance() repairs the tree, and the if(...) condition is never true.
            //
            // Otherwise, if the entry deleted was on an internal node page, then pCur is pointing to the leaf page from which a cell was removed to
            // replace the cell deleted from the internal node. This is slightly tricky as the leaf node may be underfull, and the internal node may
            // be either under or overfull. In this case run the balancing algorithm on the leaf node first. If the balance proceeds far enough up the
            // tree that we can be sure that any problem in the internal node has been corrected, so be it. Otherwise, after balancing the leaf node,
            // walk the cursor up the tree to the internal node and balance it as well.
            rc = Balance();
            if (rc == RC.OK && this.PageID > iCellDepth)
            {
                while (this.PageID > iCellDepth)
                {
                    this.Pages[this.PageID--].releasePage();
                }
                rc = Balance();
            }
            if (rc == RC.OK)
            {
                MoveToRoot();
            }
            return(rc);
        }