Beispiel #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);
        }
        internal RC lockBtree()
        {
            Debug.Assert(MutexEx.Held(this.Mutex));
            Debug.Assert(this.Page1 == null);
            var rc = this.Pager.SharedLock();

            if (rc != RC.OK)
            {
                return(rc);
            }
            MemPage pPage1 = null; // Page 1 of the database file

            rc = btreeGetPage(1, ref pPage1, 0);
            if (rc != RC.OK)
            {
                return(rc);
            }
            // Do some checking to help insure the file we opened really is a valid database file.
            Pgno nPageHeader;                                           // Number of pages in the database according to hdr
            var  nPage = nPageHeader = ConvertEx.Get4(pPage1.Data, 28); // Number of pages in the database
            Pgno nPageFile;                                             // Number of pages in the database file

            this.Pager.GetPageCount(out nPageFile);
            if (nPage == 0 || ArrayEx.Compare(pPage1.Data, 24, pPage1.Data, 92, 4) != 0)
            {
                nPage = nPageFile;
            }
            if (nPage > 0)
            {
                var page1 = pPage1.Data;
                rc = RC.NOTADB;
                if (ArrayEx.Compare(page1, Btree.zMagicHeader, 16) != 0)
                {
                    goto page1_init_failed;
                }
#if SQLITE_OMIT_WAL
                if (page1[18] > 1)
                {
                    this.ReadOnly = true;
                }
                if (page1[19] > 1)
                {
                    this.Schema.file_format = page1[19];
                    goto page1_init_failed;
                }
#else
                if (page1[18] > 2)
                {
                    pBt.readOnly = true;
                }
                if (page1[19] > 2)
                {
                    goto page1_init_failed;
                }

/* If the write version is set to 2, this database should be accessed
** in WAL mode. If the log is not already open, open it now. Then
** return SQLITE_OK and return without populating BtShared.pPage1.
** The caller detects this and calls this function again. This is
** required as the version of page 1 currently in the page1 buffer
** may not be the latest version - there may be a newer one in the log
** file.
*/
                if (page1[19] == 2 && pBt.doNotUseWAL == false)
                {
                    int isOpen = 0;
                    rc = sqlite3PagerOpenWal(pBt.pPager, ref isOpen);
                    if (rc != SQLITE_OK)
                    {
                        goto page1_init_failed;
                    }
                    else if (isOpen == 0)
                    {
                        releasePage(pPage1);
                        return(SQLITE_OK);
                    }
                    rc = SQLITE_NOTADB;
                }
#endif
                // The maximum embedded fraction must be exactly 25%.  And the minimum embedded fraction must be 12.5% for both leaf-data and non-leaf-data.
                // The original design allowed these amounts to vary, but as of version 3.6.0, we require them to be fixed.
                if (ArrayEx.Compare(page1, 21, "\x0040\x0020\x0020", 3) != 0) // "\100\040\040"
                {
                    goto page1_init_failed;
                }
                var pageSize = (uint)((page1[16] << 8) | (page1[17] << 16));
                if (((pageSize - 1) & pageSize) != 0 || pageSize > Pager.SQLITE_MAX_PAGE_SIZE || pageSize <= 256)
                {
                    goto page1_init_failed;
                }
                Debug.Assert((pageSize & 7) == 0);
                var usableSize = pageSize - page1[20];
                if (pageSize != this.PageSize)
                {
                    // After reading the first page of the database assuming a page size of BtShared.pageSize, we have discovered that the page-size is
                    // actually pageSize. Unlock the database, leave pBt.pPage1 at zero and return SQLITE_OK. The caller will call this function
                    // again with the correct page-size.
                    pPage1.releasePage();
                    this.UsableSize = usableSize;
                    this.PageSize   = pageSize;
                    rc = this.Pager.SetPageSize(ref this.PageSize, (int)(pageSize - usableSize));
                    return(rc);
                }
                if ((this.DB.flags & sqlite3b.SQLITE.RecoveryMode) == 0 && nPage > nPageFile)
                {
                    rc = SysEx.SQLITE_CORRUPT_BKPT();
                    goto page1_init_failed;
                }
                if (usableSize < 480)
                {
                    goto page1_init_failed;
                }
                this.PageSize   = pageSize;
                this.UsableSize = usableSize;
#if !SQLITE_OMIT_AUTOVACUUM
                this.AutoVacuum = (ConvertEx.Get4(page1, 36 + 4 * 4) != 0);
                this.IncrVacuum = (ConvertEx.Get4(page1, 36 + 7 * 4) != 0);
#endif
            }
            // maxLocal is the maximum amount of payload to store locally for a cell.  Make sure it is small enough so that at least minFanout
            // cells can will fit on one page.  We assume a 10-byte page header. Besides the payload, the cell must store:
            //     2-byte pointer to the cell
            //     4-byte child pointer
            //     9-byte nKey value
            //     4-byte nData value
            //     4-byte overflow page pointer
            // So a cell consists of a 2-byte pointer, a header which is as much as 17 bytes long, 0 to N bytes of payload, and an optional 4 byte overflow page pointer.
            this.MaxLocal = (ushort)((this.UsableSize - 12) * 64 / 255 - 23);
            this.MinLocal = (ushort)((this.UsableSize - 12) * 32 / 255 - 23);
            this.MaxLeaf  = (ushort)(this.UsableSize - 35);
            this.MinLeaf  = (ushort)((this.UsableSize - 12) * 32 / 255 - 23);
            Debug.Assert(this.MaxLeaf + 23 <= Btree.MX_CELL_SIZE(this));
            this.Page1 = pPage1;
            this.Pages = nPage;
            return(RC.OK);

page1_init_failed:
            pPage1.releasePage();
            this.Page1 = null;
            return(rc);
        }
Beispiel #3
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);
        }