// 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); }
// 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); }