internal static RC sqlite3BtreeIncrVacuum(Btree p) { var pBt = p.Shared; p.sqlite3BtreeEnter(); Debug.Assert(pBt.InTransaction == TRANS.WRITE && p.InTransaction == TRANS.WRITE); RC rc; if (!pBt.AutoVacuum) { rc = RC.DONE; } else { Btree.invalidateAllOverflowCache(pBt); rc = incrVacuumStep(pBt, 0, pBt.btreePagecount()); if (rc == RC.OK) { rc = Pager.Write(pBt.Page1.DbPage); ConvertEx.Put4(pBt.Page1.Data, 28, pBt.Pages); } } p.sqlite3BtreeLeave(); return(rc); }
// was:sqlite3BtreeCacheOverflow public void BtreeCacheOverflow() { Debug.Assert(HoldsMutex()); Debug.Assert(MutexEx.Held(Tree.DB.Mutex)); Btree.invalidateOverflowCache(this); IsIncrblob = true; }
// was:saveCursorPosition internal RC SavePosition() { Debug.Assert(State == CursorState.VALID); Debug.Assert(Key == null); Debug.Assert(HoldsMutex()); NKey = GetKeySize(); // If this is an intKey table, then the above call to BtreeKeySize() stores the integer key in pCur.nKey. In this case this value is // all that is required. Otherwise, if pCur is not open on an intKey table, then malloc space for and store the pCur.nKey bytes of key data. var rc = RC.OK; if (!Pages[0].HasIntKey) { var pKey = MallocEx.sqlite3Malloc((int)NKey); rc = GetKey(0, (uint)NKey, pKey); if (rc == RC.OK) { Key = pKey; } } Debug.Assert(!Pages[0].HasIntKey || Key == null); if (rc == RC.OK) { for (var i = 0; i <= PageID; i++) { Pages[i].releasePage(); Pages[i] = null; } PageID = -1; State = CursorState.REQUIRESEEK; } Btree.invalidateOverflowCache(this); return(rc); }
internal void assemblePage(int nCell, byte[] apCell, int[] aSize) { Debug.Assert(this.NOverflows == 0); Debug.Assert(MutexEx.Held(this.Shared.Mutex)); Debug.Assert(nCell >= 0 && nCell <= (int)Btree.MX_CELL(this.Shared) && (int)Btree.MX_CELL(this.Shared) <= 10921); Debug.Assert(Pager.IsPageWriteable(this.DbPage)); // Check that the page has just been zeroed by zeroPage() Debug.Assert(this.Cells == 0); // var data = this.Data; // Pointer to data for pPage int hdr = this.HeaderOffset; // Offset of header on pPage var nUsable = (int)this.Shared.UsableSize; // Usable size of page Debug.Assert(ConvertEx.Get2nz(data, hdr + 5) == nUsable); var pCellptr = this.CellOffset + nCell * 2; // Address of next cell pointer var cellbody = nUsable; // Address of next cell body for (var i = nCell - 1; i >= 0; i--) { var sz = (ushort)aSize[i]; pCellptr -= 2; cellbody -= sz; ConvertEx.Put2(data, pCellptr, cellbody); Buffer.BlockCopy(apCell, 0, data, cellbody, sz); } ConvertEx.Put2(data, hdr + 3, nCell); ConvertEx.Put2(data, hdr + 5, cellbody); this.FreeBytes -= (ushort)(nCell * 2 + nUsable - cellbody); this.Cells = (ushort)nCell; }
// was:sqlite3BtreeCloseCursor public RC Close() { if (Tree != null) { Tree.sqlite3BtreeEnter(); Clear(); if (Prev != null) { Prev.Next = Next; } else { Shared.Cursors = Next; } if (Next != null) { Next.Prev = Prev; } for (var id = 0; id <= PageID; id++) { Pages[id].releasePage(); } Shared.unlockBtreeIfUnused(); Btree.invalidateOverflowCache(this); Tree.sqlite3BtreeLeave(); } return(RC.OK); }
internal static RC autoVacuumCommit(BtShared pBt) { var rc = RC.OK; var pPager = pBt.Pager; #if DEBUG var nRef = pPager.RefCount; #else var nRef = 0; #endif Debug.Assert(MutexEx.Held(pBt.Mutex)); Btree.invalidateAllOverflowCache(pBt); Debug.Assert(pBt.AutoVacuum); if (!pBt.IncrVacuum) { var nOrig = pBt.btreePagecount(); // Database size before freeing if (PTRMAP_ISPAGE(pBt, nOrig) || nOrig == PENDING_BYTE_PAGE(pBt)) { // It is not possible to create a database for which the final page is either a pointer-map page or the pending-byte page. If one // is encountered, this indicates corruption. return(SysEx.SQLITE_CORRUPT_BKPT()); } var nFree = (Pgno)ConvertEx.Get4(pBt.Page1.Data, 36); // Number of pages on the freelist initially var nEntry = (int)pBt.UsableSize / 5; // Number of entries on one ptrmap page var nPtrmap = (Pgno)((nFree - nOrig + PTRMAP_PAGENO(pBt, nOrig) + (Pgno)nEntry) / nEntry); // Number of PtrMap pages to be freed var nFin = nOrig - nFree - nPtrmap; // Number of pages in database after autovacuuming if (nOrig > PENDING_BYTE_PAGE(pBt) && nFin < PENDING_BYTE_PAGE(pBt)) { nFin--; } while (PTRMAP_ISPAGE(pBt, nFin) || nFin == PENDING_BYTE_PAGE(pBt)) { nFin--; } if (nFin > nOrig) { return(SysEx.SQLITE_CORRUPT_BKPT()); } for (var iFree = nOrig; iFree > nFin && rc == RC.OK; iFree--) { rc = incrVacuumStep(pBt, nFin, iFree); } if ((rc == RC.DONE || rc == RC.OK) && nFree > 0) { rc = Pager.Write(pBt.Page1.DbPage); ConvertEx.Put4(pBt.Page1.Data, 32, 0); ConvertEx.Put4(pBt.Page1.Data, 36, 0); ConvertEx.Put4(pBt.Page1.Data, 28, nFin); pBt.Pager.TruncateImage(nFin); pBt.Pages = nFin; } if (rc != RC.OK) { pPager.Rollback(); } } Debug.Assert(nRef == pPager.RefCount); return(rc); }
// was:invalidateIncrblobCursors internal static void invalidateIncrblobCursors(Btree tree, long row, bool clearTable) { var shared = tree.Shared; Debug.Assert(tree.sqlite3BtreeHoldsMutex()); for (var cursor = shared.Cursors; cursor != null; cursor = cursor.Next) if (cursor.IsIncrblob && (clearTable || cursor.Info.nKey == row)) cursor.State = CURSOR.INVALID; }
// was:invalidateIncrblobCursors internal static void invalidateIncrblobCursors(Btree tree, long row, bool clearTable) { var shared = tree.Shared; Debug.Assert(tree.sqlite3BtreeHoldsMutex()); for (var cursor = shared.Cursors; cursor != null; cursor = cursor.Next) { if (cursor.IsIncrblob && (clearTable || cursor.Info.nKey == row)) { cursor.State = CURSOR.INVALID; } } }
internal void ptrmapPut(Pgno key, PTRMAP eType, Pgno parent, ref RC rRC) { if (rRC != RC.OK) { return; } Debug.Assert(MutexEx.Held(this.Mutex)); // The master-journal page number must never be used as a pointer map page Debug.Assert(!MemPage.PTRMAP_ISPAGE(this, MemPage.PENDING_BYTE_PAGE(this))); Debug.Assert(this.AutoVacuum); if (key == 0) { rRC = SysEx.SQLITE_CORRUPT_BKPT(); return; } var iPtrmap = MemPage.PTRMAP_PAGENO(this, key); var pDbPage = new PgHdr(); // The pointer map page var rc = this.Pager.Get(iPtrmap, ref pDbPage); if (rc != RC.OK) { rRC = rc; return; } var offset = (int)MemPage.PTRMAP_PTROFFSET(iPtrmap, key); if (offset < 0) { rRC = SysEx.SQLITE_CORRUPT_BKPT(); goto ptrmap_exit; } Debug.Assert(offset <= (int)this.UsableSize - 5); var pPtrmap = Pager.sqlite3PagerGetData(pDbPage); // The pointer map data if (eType != (PTRMAP)pPtrmap[offset] || ConvertEx.Get4(pPtrmap, offset + 1) != parent) { Btree.TRACE("PTRMAP_UPDATE: {0}->({1},{2})", key, eType, parent); rRC = rc = Pager.Write(pDbPage); if (rc == RC.OK) { pPtrmap[offset] = (byte)eType; ConvertEx.Put4L(pPtrmap, (uint)offset + 1, parent); } } ptrmap_exit: Pager.Unref(pDbPage); }
internal static RC balance_deeper(MemPage pRoot, ref MemPage ppChild) { MemPage pChild = null; // Pointer to a new child page Pgno pgnoChild = 0; // Page number of the new child page var pBt = pRoot.Shared; Debug.Assert(pRoot.NOverflows > 0); Debug.Assert(MutexEx.Held(pBt.Mutex)); // Make pRoot, the root page of the b-tree, writable. Allocate a new page that will become the new right-child of pPage. Copy the contents // of the node stored on pRoot into the new child page. var rc = Pager.Write(pRoot.DbPage); if (rc == RC.OK) { rc = pBt.allocateBtreePage(ref pChild, ref pgnoChild, pRoot.ID, 0); copyNodeContent(pRoot, pChild, ref rc); #if !SQLITE_OMIT_AUTOVACUUM if (pBt.AutoVacuum) #else if (false) #endif { pBt.ptrmapPut(pgnoChild, PTRMAP.BTREE, pRoot.ID, ref rc); } } if (rc != RC.OK) { ppChild = null; pChild.releasePage(); return(rc); } Debug.Assert(Pager.IsPageWriteable(pChild.DbPage)); Debug.Assert(Pager.IsPageWriteable(pRoot.DbPage)); Debug.Assert(pChild.Cells == pRoot.Cells); Btree.TRACE("BALANCE: copy root %d into %d\n", pRoot.ID, pChild.ID); // Copy the overflow cells from pRoot to pChild Array.Copy(pRoot.Overflows, pChild.Overflows, pRoot.NOverflows); pChild.NOverflows = pRoot.NOverflows; // Zero the contents of pRoot. Then install pChild as the right-child. pRoot.zeroPage(pChild.Data[0] & ~Btree.PTF_LEAF); ConvertEx.Put4L(pRoot.Data, pRoot.HeaderOffset + 8, pgnoChild); ppChild = pChild; return(RC.OK); }
// was:sqlite3BtreeClose public static RC Close(ref Btree p) { // Close all cursors opened via this handle. Debug.Assert(MutexEx.Held(p.DB.Mutex)); p.sqlite3BtreeEnter(); var shared = p.Shared; var cursor = shared.Cursors; while (cursor != null) { var lastCursor = cursor; cursor = cursor.Next; if (lastCursor.Tree == p) lastCursor.Close(); } // Rollback any active transaction and free the handle structure. The call to sqlite3BtreeRollback() drops any table-locks held by this handle. p.Rollback(); p.sqlite3BtreeLeave(); // If there are still other outstanding references to the shared-btree structure, return now. The remainder of this procedure cleans up the shared-btree. Debug.Assert(p.WantToLock == 0 && !p.Locked); if (!p.Sharable || shared.removeFromSharingList()) { // The pBt is no longer on the sharing list, so we can access it without having to hold the mutex. // Clean out and delete the BtShared object. Debug.Assert(shared.Cursors == null); shared.Pager.Close(); if (shared.xFreeSchema != null && shared.Schema != null) shared.xFreeSchema(shared.Schema); shared.Schema = null;// sqlite3DbFree(0, pBt->pSchema); //freeTempSpace(pBt); shared = null; //sqlite3_free(ref pBt); } #if !SQLITE_OMIT_SHARED_CACHE Debug.Assert(p.WantToLock == 0); Debug.Assert(p.Locked == false); if (p.Prev != null) p.Prev.Next = p.Next; if (p.Next != null) p.Next.Prev = p.Prev; #endif return RC.OK; }
// was:sqlite3BtreeOpen public static RC Open(VirtualFileSystem pVfs, string zFilename, sqlite3 db, ref Btree rTree, OPEN flags, VFSOPEN vfsFlags) { Btree p; // Handle to return var rc = RC.OK; byte nReserve; // Byte of unused space on each page var zDbHeader = new byte[100]; // Database header content // True if opening an ephemeral, temporary database */ bool isTempDb = string.IsNullOrEmpty(zFilename); // Set the variable isMemdb to true for an in-memory database, or false for a file-based database. #if SQLITE_OMIT_MEMORYDB var isMemdb = false; #else var isMemdb = (zFilename == ":memory:" || isTempDb && db.sqlite3TempInMemory()); #endif Debug.Assert(db != null); Debug.Assert(pVfs != null); Debug.Assert(MutexEx.Held(db.Mutex)); Debug.Assert(((uint)flags & 0xff) == (uint)flags); // flags fit in 8 bits // Only a BTREE_SINGLE database can be BTREE_UNORDERED Debug.Assert((flags & OPEN.UNORDERED) == 0 || (flags & OPEN.SINGLE) != 0); // A BTREE_SINGLE database is always a temporary and/or ephemeral Debug.Assert((flags & OPEN.SINGLE) == 0 || isTempDb); if ((db.flags & sqlite3b.SQLITE.NoReadlock) != 0) flags |= OPEN.NO_READLOCK; if (isMemdb) flags |= OPEN.MEMORY; if ((vfsFlags & VFSOPEN.MAIN_DB) != 0 && (isMemdb || isTempDb)) vfsFlags = (vfsFlags & ~VFSOPEN.MAIN_DB) | VFSOPEN.TEMP_DB; p = new Btree(); p.InTransaction = TRANS.NONE; p.DB = db; #if !SQLITE_OMIT_SHARED_CACHE p.Locks.Tree = p; p.Locks.TableID = 1; #endif BtShared shared = null; // Shared part of btree structure MutexEx mutexOpen = null; // Prevents a race condition. #if !SQLITE_OMIT_SHARED_CACHE && !SQLITE_OMIT_DISKIO // If this Btree is a candidate for shared cache, try to find an existing BtShared object that we can share with if (!isMemdb && !isTempDb) { if ((vfsFlags & VFSOPEN.SHAREDCACHE) != 0) { p.Sharable = true; string zPathname; rc = pVfs.xFullPathname(zFilename, out zPathname); mutexOpen = MutexEx.Alloc(MUTEX.STATIC_OPEN); MutexEx.Enter(mutexOpen); var mutexShared = MutexEx.Alloc(MUTEX.STATIC_MASTER); MutexEx.Enter(mutexShared); for (shared = SysEx.getGLOBAL<BtShared>(s_sqlite3SharedCacheList); shared != null; shared = shared.Next) { Debug.Assert(shared.nRef > 0); if (string.Equals(zPathname, shared.Pager.sqlite3PagerFilename) && shared.Pager.sqlite3PagerVfs == pVfs) { for (var iDb = db.DBs - 1; iDb >= 0; iDb--) { var existingTree = db.AllocDBs[iDb].Tree; if (existingTree != null && existingTree.Shared == shared) { MutexEx.Leave(mutexShared); MutexEx.Leave(mutexOpen); p = null; return RC.CONSTRAINT; } } p.Shared = shared; shared.nRef++; break; } } MutexEx.Leave(mutexShared); } #if DEBUG else // In debug mode, we mark all persistent databases as sharable even when they are not. This exercises the locking code and // gives more opportunity for asserts(sqlite3_mutex_held()) statements to find locking problems. p.Sharable = true; #endif } #endif if (shared == null) { // The following asserts make sure that structures used by the btree are the right size. This is to guard against size changes that result // when compiling on a different architecture. Debug.Assert(sizeof(long) == 8 || sizeof(long) == 4); Debug.Assert(sizeof(ulong) == 8 || sizeof(ulong) == 4); Debug.Assert(sizeof(uint) == 4); Debug.Assert(sizeof(ushort) == 2); Debug.Assert(sizeof(Pgno) == 4); shared = new BtShared(); rc = Pager.Open(pVfs, out shared.Pager, zFilename, EXTRA_SIZE, (Pager.PAGEROPEN)flags, vfsFlags, pageReinit, () => new MemPage()); if (rc == RC.OK) rc = shared.Pager.ReadFileHeader(zDbHeader.Length, zDbHeader); if (rc != RC.OK) goto btree_open_out; shared.OpenFlags = flags; shared.DB = db; shared.Pager.SetBusyHandler(btreeInvokeBusyHandler, shared); p.Shared = shared; shared.Cursors = null; shared.Page1 = null; shared.ReadOnly = shared.Pager.IsReadonly; #if SQLITE_SECURE_DELETE pBt.secureDelete = true; #endif shared.PageSize = (uint)((zDbHeader[16] << 8) | (zDbHeader[17] << 16)); if (shared.PageSize < 512 || shared.PageSize > Pager.SQLITE_MAX_PAGE_SIZE || ((shared.PageSize - 1) & shared.PageSize) != 0) { shared.PageSize = 0; #if !SQLITE_OMIT_AUTOVACUUM // If the magic name ":memory:" will create an in-memory database, then leave the autoVacuum mode at 0 (do not auto-vacuum), even if // SQLITE_DEFAULT_AUTOVACUUM is true. On the other hand, if SQLITE_OMIT_MEMORYDB has been defined, then ":memory:" is just a // regular file-name. In this case the auto-vacuum applies as per normal. if (zFilename != string.Empty && !isMemdb) { shared.AutoVacuum = (AUTOVACUUM.DEFAULT != AUTOVACUUM.NONE); shared.IncrVacuum = (AUTOVACUUM.DEFAULT == AUTOVACUUM.INCR); } #endif nReserve = 0; } else { nReserve = zDbHeader[20]; shared.PageSizeFixed = true; #if !SQLITE_OMIT_AUTOVACUUM shared.AutoVacuum = ConvertEx.Get4(zDbHeader, 36 + 4 * 4) != 0; shared.IncrVacuum = ConvertEx.Get4(zDbHeader, 36 + 7 * 4) != 0; #endif } rc = shared.Pager.SetPageSize(ref shared.PageSize, nReserve); if (rc != RC.OK) goto btree_open_out; shared.UsableSize = (ushort)(shared.PageSize - nReserve); Debug.Assert((shared.PageSize & 7) == 0); // 8-byte alignment of pageSize #if !SQLITE_OMIT_SHARED_CACHE && !SQLITE_OMIT_DISKIO // Add the new BtShared object to the linked list sharable BtShareds. if (p.Sharable) { MutexEx mutexShared; shared.nRef = 1; mutexShared = MutexEx.Alloc(MUTEX.STATIC_MASTER); if (MutexEx.SQLITE_THREADSAFE && MutexEx.WantsCoreMutex) shared.Mutex = MutexEx.Alloc(MUTEX.FAST); MutexEx.Enter(mutexShared); shared.Next = SysEx.getGLOBAL<BtShared>(s_sqlite3SharedCacheList); SysEx.setGLOBAL<BtShared>(s_sqlite3SharedCacheList, shared); MutexEx.Leave(mutexShared); } #endif } #if !SQLITE_OMIT_SHARED_CACHE && !SQLITE_OMIT_DISKIO // If the new Btree uses a sharable pBtShared, then link the new Btree into the list of all sharable Btrees for the same connection. // The list is kept in ascending order by pBt address. Btree existingTree2; if (p.Sharable) for (var i = 0; i < db.DBs; i++) if ((existingTree2 = db.AllocDBs[i].Tree) != null && existingTree2.Sharable) { while (existingTree2.Prev != null) { existingTree2 = existingTree2.Prev; } if (p.Shared.Version < existingTree2.Shared.Version) { p.Next = existingTree2; p.Prev = null; existingTree2.Prev = p; } else { while (existingTree2.Next != null && existingTree2.Next.Shared.Version < p.Shared.Version) existingTree2 = existingTree2.Next; p.Next = existingTree2.Next; p.Prev = existingTree2; if (p.Next != null) p.Next.Prev = p; existingTree2.Next = p; } break; } #endif rTree = p; // btree_open_out: if (rc != RC.OK) { if (shared != null && shared.Pager != null) shared.Pager.Close(); shared = null; p = null; rTree = null; } else { // If the B-Tree was successfully opened, set the pager-cache size to the default value. Except, when opening on an existing shared pager-cache, // do not change the pager-cache size. if (p.GetSchema(0, null, null) == null) p.Shared.Pager.SetCacheSize(SQLITE_DEFAULT_CACHE_SIZE); } if (mutexOpen != null) { Debug.Assert(MutexEx.Held(mutexOpen)); MutexEx.Leave(mutexOpen); } 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); }
static string sqlite3BtreeIntegrityCheck( Btree p, /* The btree to be checked */ int[] aRoot, /* An array of root pages numbers for individual trees */ int nRoot, /* Number of entries in aRoot[] */ int mxErr, /* Stop reporting errors after this many */ ref int pnErr /* Write number of errors seen to this variable */ ) { Pgno i; int nRef; IntegrityCk sCheck = new IntegrityCk(); BtShared pBt = p.pBt; StringBuilder zErr = new StringBuilder(100);//char zErr[100]; sqlite3BtreeEnter(p); Debug.Assert(p.inTrans > TRANS_NONE && pBt.inTransaction > TRANS_NONE); nRef = sqlite3PagerRefcount(pBt.pPager); sCheck.pBt = pBt; sCheck.pPager = pBt.pPager; sCheck.nPage = btreePagecount(sCheck.pBt); sCheck.mxErr = mxErr; sCheck.nErr = 0; //sCheck.mallocFailed = 0; pnErr = 0; if (sCheck.nPage == 0) { sqlite3BtreeLeave(p); return(""); } sCheck.anRef = sqlite3Malloc(sCheck.anRef, (int)sCheck.nPage + 1); //if( !sCheck.anRef ){ // pnErr = 1; // sqlite3BtreeLeave(p); // return 0; //} // for (i = 0; i <= sCheck.nPage; i++) { sCheck.anRef[i] = 0; } i = PENDING_BYTE_PAGE(pBt); if (i <= sCheck.nPage) { sCheck.anRef[i] = 1; } sqlite3StrAccumInit(sCheck.errMsg, null, 1000, 20000); //sCheck.errMsg.useMalloc = 2; /* Check the integrity of the freelist */ checkList(sCheck, 1, (int)sqlite3Get4byte(pBt.pPage1.aData, 32), (int)sqlite3Get4byte(pBt.pPage1.aData, 36), "Main freelist: "); /* Check all the tables. */ for (i = 0; (int)i < nRoot && sCheck.mxErr != 0; i++) { if (aRoot[i] == 0) { continue; } #if !SQLITE_OMIT_AUTOVACUUM if (pBt.autoVacuum && aRoot[i] > 1) { checkPtrmap(sCheck, (u32)aRoot[i], PTRMAP_ROOTPAGE, 0, ""); } #endif checkTreePage(sCheck, aRoot[i], "List of tree roots: ", ref refNULL, ref refNULL, null, null); } /* Make sure every page in the file is referenced */ for (i = 1; i <= sCheck.nPage && sCheck.mxErr != 0; i++) { #if SQLITE_OMIT_AUTOVACUUM if (sCheck.anRef[i] == null) { checkAppendMsg(sCheck, 0, "Page %d is never used", i); } #else /* If the database supports auto-vacuum, make sure no tables contain ** references to pointer-map pages. */ if (sCheck.anRef[i] == 0 && (PTRMAP_PAGENO(pBt, i) != i || !pBt.autoVacuum)) { checkAppendMsg(sCheck, "", "Page %d is never used", i); } if (sCheck.anRef[i] != 0 && (PTRMAP_PAGENO(pBt, i) == i && pBt.autoVacuum)) { checkAppendMsg(sCheck, "", "Pointer map page %d is referenced", i); } #endif } /* Make sure this analysis did not leave any unref() pages. ** This is an internal consistency check; an integrity check ** of the integrity check. */ if (NEVER(nRef != sqlite3PagerRefcount(pBt.pPager))) { checkAppendMsg(sCheck, "", "Outstanding page count goes from %d to %d during this analysis", nRef, sqlite3PagerRefcount(pBt.pPager) ); } /* Clean up and report errors. */ sqlite3BtreeLeave(p); sCheck.anRef = null;// sqlite3_free( ref sCheck.anRef ); //if( sCheck.mallocFailed ){ // sqlite3StrAccumReset(sCheck.errMsg); // pnErr = sCheck.nErr+1; // return 0; //} pnErr = sCheck.nErr; if (sCheck.nErr == 0) { sqlite3StrAccumReset(sCheck.errMsg); } return(sqlite3StrAccumFinish(sCheck.errMsg)); }
internal RC btreeInitPage() { Debug.Assert(this.Shared != null); Debug.Assert(MutexEx.Held(this.Shared.Mutex)); Debug.Assert(this.ID == Pager.GetPageID(this.DbPage)); Debug.Assert(this == Pager.sqlite3PagerGetExtra <MemPage>(this.DbPage)); Debug.Assert(this.Data == Pager.sqlite3PagerGetData(this.DbPage)); if (!this.HasInit) { var pBt = this.Shared; // The main btree structure var hdr = this.HeaderOffset; // Offset to beginning of page header var data = this.Data; if (decodeFlags(data[hdr]) != 0) { return(SysEx.SQLITE_CORRUPT_BKPT()); } Debug.Assert(pBt.PageSize >= 512 && pBt.PageSize <= 65536); this.MaskPage = (ushort)(pBt.PageSize - 1); this.NOverflows = 0; var usableSize = (int)pBt.UsableSize; // Amount of usable space on each page ushort cellOffset; // Offset from start of page to first cell pointer this.CellOffset = (cellOffset = (ushort)(hdr + 12 - 4 * this.Leaf)); var top = ConvertEx.Get2nz(data, hdr + 5); // First byte of the cell content area this.Cells = (ushort)(ConvertEx.Get2(data, hdr + 3)); if (this.Cells > Btree.MX_CELL(pBt)) { // To many cells for a single page. The page must be corrupt return(SysEx.SQLITE_CORRUPT_BKPT()); } // A malformed database page might cause us to read past the end of page when parsing a cell. // The following block of code checks early to see if a cell extends past the end of a page boundary and causes SQLITE_CORRUPT to be // returned if it does. var iCellFirst = cellOffset + 2 * this.Cells; // First allowable cell or freeblock offset var iCellLast = usableSize - 4; // Last possible cell or freeblock offset #if SQLITE_ENABLE_OVERSIZE_CELL_CHECK if (pPage.leaf == 0) { iCellLast--; } for (var i = 0; i < pPage.nCell; i++) { pc = (ushort)ConvertEx.get2byte(data, cellOffset + i * 2); if (pc < iCellFirst || pc > iCellLast) { return(SysEx.SQLITE_CORRUPT_BKPT()); } var sz = cellSizePtr(pPage, data, pc); if (pc + sz > usableSize) { return(SysEx.SQLITE_CORRUPT_BKPT()); } } if (pPage.leaf == 0) { iCellLast++; } #endif // Compute the total free space on the page var pc = (ushort)ConvertEx.Get2(data, hdr + 1); // Address of a freeblock within pPage.aData[] var nFree = (ushort)(data[hdr + 7] + top); // Number of unused bytes on the page while (pc > 0) { if (pc < iCellFirst || pc > iCellLast) { // Start of free block is off the page return(SysEx.SQLITE_CORRUPT_BKPT()); } var next = (ushort)ConvertEx.Get2(data, pc); var size = (ushort)ConvertEx.Get2(data, pc + 2); if ((next > 0 && next <= pc + size + 3) || pc + size > usableSize) { // Free blocks must be in ascending order. And the last byte of the free-block must lie on the database page. return(SysEx.SQLITE_CORRUPT_BKPT()); } nFree = (ushort)(nFree + size); pc = next; } // At this point, nFree contains the sum of the offset to the start of the cell-content area plus the number of free bytes within // the cell-content area. If this is greater than the usable-size of the page, then the page must be corrupted. This check also // serves to verify that the offset to the start of the cell-content area, according to the page header, lies within the page. if (nFree > usableSize) { return(SysEx.SQLITE_CORRUPT_BKPT()); } this.FreeBytes = (ushort)(nFree - iCellFirst); this.HasInit = true; } return(RC.OK); }
internal void insertCell(int i, byte[] pCell, int sz, byte[] pTemp, Pgno iChild, ref RC pRC) { var nSkip = (iChild != 0 ? 4 : 0); if (pRC != RC.OK) { return; } Debug.Assert(i >= 0 && i <= this.Cells + this.NOverflows); Debug.Assert(this.Cells <= Btree.MX_CELL(this.Shared) && Btree.MX_CELL(this.Shared) <= 10921); Debug.Assert(this.NOverflows <= this.Overflows.Length); Debug.Assert(MutexEx.Held(this.Shared.Mutex)); // The cell should normally be sized correctly. However, when moving a malformed cell from a leaf page to an interior page, if the cell size // wanted to be less than 4 but got rounded up to 4 on the leaf, then size might be less than 8 (leaf-size + pointer) on the interior node. Hence // the term after the || in the following assert(). Debug.Assert(sz == cellSizePtr(pCell) || (sz == 8 && iChild > 0)); if (this.NOverflows != 0 || sz + 2 > this.FreeBytes) { if (pTemp != null) { Buffer.BlockCopy(pCell, nSkip, pTemp, nSkip, sz - nSkip); pCell = pTemp; } if (iChild != 0) { ConvertEx.Put4L(pCell, iChild); } var j = this.NOverflows++; Debug.Assert(j < this.Overflows.Length); this.Overflows[j].Cell = pCell; this.Overflows[j].Index = (ushort)i; } else { var rc = Pager.Write(this.DbPage); if (rc != RC.OK) { pRC = rc; return; } Debug.Assert(Pager.IsPageWriteable(this.DbPage)); var data = this.Data; // The content of the whole page var cellOffset = (int)this.CellOffset; // Address of first cell pointer in data[] var end = cellOffset + 2 * this.Cells; // First byte past the last cell pointer in data[] var ins = cellOffset + 2 * i; // Index in data[] where new cell pointer is inserted int idx = 0; // Where to write new cell content in data[] rc = allocateSpace(sz, ref idx); if (rc != RC.OK) { pRC = rc; return; } // The allocateSpace() routine guarantees the following two properties if it returns success Debug.Assert(idx >= end + 2); Debug.Assert(idx + sz <= (int)this.Shared.UsableSize); this.Cells++; this.FreeBytes -= (ushort)(2 + sz); Buffer.BlockCopy(pCell, nSkip, data, idx + nSkip, sz - nSkip); if (iChild != 0) { ConvertEx.Put4L(data, (uint)idx, iChild); } for (var j = end; j > ins; j -= 2) { data[j + 0] = data[j - 2]; data[j + 1] = data[j - 1]; } ConvertEx.Put2(data, ins, idx); ConvertEx.Put2(data, this.HeaderOffset + 3, this.Cells); #if !SQLITE_OMIT_AUTOVACUUM if (this.Shared.AutoVacuum) { // The cell may contain a pointer to an overflow page. If so, write the entry for the overflow page into the pointer map. ptrmapPutOvflPtr(pCell, ref pRC); } #endif } }
internal static RC balance_nonroot(MemPage pParent, int iParentIdx, byte[] aOvflSpace, int isRoot) { var apOld = new MemPage[NB]; // pPage and up to two siblings var apCopy = new MemPage[NB]; // Private copies of apOld[] pages var apNew = new MemPage[NB + 2]; // pPage and up to NB siblings after balancing var apDiv = new int[NB - 1]; // Divider cells in pParent var cntNew = new int[NB + 2]; // Index in aCell[] of cell after i-th page var szNew = new int[NB + 2]; // Combined size of cells place on i-th page var szCell = new ushort[1]; // Local size of all cells in apCell[] BtShared pBt; // The whole database int nCell = 0; // Number of cells in apCell[] int nMaxCells = 0; // Allocated size of apCell, szCell, aFrom. int nNew = 0; // Number of pages in apNew[] ushort leafCorrection; // 4 if pPage is a leaf. 0 if not int leafData; // True if pPage is a leaf of a LEAFDATA tree int usableSpace; // Bytes in pPage beyond the header int pageFlags; // Value of pPage.aData[0] int subtotal; // Subtotal of bytes in cells on one page int iOvflSpace = 0; // First unused byte of aOvflSpace[] //int szScratch; // Size of scratch memory requested byte[][] apCell = null; // All cells begin balanced // pBt = pParent.Shared; Debug.Assert(MutexEx.Held(pBt.Mutex)); Debug.Assert(Pager.IsPageWriteable(pParent.DbPage)); #if false Btree.TRACE("BALANCE: begin page %d child of %d\n", pPage.pgno, pParent.pgno); #endif // At this point pParent may have at most one overflow cell. And if this overflow cell is present, it must be the cell with // index iParentIdx. This scenario comes about when this function is called (indirectly) from sqlite3BtreeDelete(). Debug.Assert(pParent.NOverflows == 0 || pParent.NOverflows == 1); Debug.Assert(pParent.NOverflows == 0 || pParent.Overflows[0].Index == iParentIdx); // Find the sibling pages to balance. Also locate the cells in pParent that divide the siblings. An attempt is made to find NN siblings on // either side of pPage. More siblings are taken from one side, however, if there are fewer than NN siblings on the other side. If pParent // has NB or fewer children then all children of pParent are taken. // This loop also drops the divider cells from the parent page. This way, the remainder of the function does not have to deal with any // overflow cells in the parent page, since if any existed they will have already been removed. int nOld; // Number of pages in apOld[] int nxDiv; // Next divider slot in pParent.aCell[] var i = pParent.NOverflows + pParent.Cells; if (i < 2) { nxDiv = 0; nOld = i + 1; } else { nOld = 3; if (iParentIdx == 0) { nxDiv = 0; } else if (iParentIdx == i) { nxDiv = i - 2; } else { nxDiv = iParentIdx - 1; } i = 2; } var pRight = ((i + nxDiv - pParent.NOverflows) == pParent.Cells ? pParent.HeaderOffset + 8 : pParent.FindCell(i + nxDiv - pParent.NOverflows)); // Location in parent of right-sibling pointer var pgno = (Pgno)ConvertEx.Get4(pParent.Data, pRight); var rc = RC.OK; while (true) { rc = pBt.getAndInitPage(pgno, ref apOld[i]); if (rc != RC.OK) { goto balance_cleanup; } nMaxCells += 1 + apOld[i].Cells + apOld[i].NOverflows; if (i-- == 0) { break; } if (i + nxDiv == pParent.Overflows[0].Index && pParent.NOverflows != 0) { apDiv[i] = 0; pgno = ConvertEx.Get4(pParent.Overflows[0].Cell, apDiv[i]); szNew[i] = pParent.cellSizePtr(apDiv[i]); pParent.NOverflows = 0; } else { apDiv[i] = pParent.FindCell(i + nxDiv - pParent.NOverflows); pgno = ConvertEx.Get4(pParent.Data, apDiv[i]); szNew[i] = pParent.cellSizePtr(apDiv[i]); // Drop the cell from the parent page. apDiv[i] still points to the cell within the parent, even though it has been dropped. // This is safe because dropping a cell only overwrites the first four bytes of it, and this function does not need the first // four bytes of the divider cell. So the pointer is safe to use later on. // // Unless SQLite is compiled in secure-delete mode. In this case, the dropCell() routine will overwrite the entire cell with zeroes. // In this case, temporarily copy the cell into the aOvflSpace[] buffer. It will be copied out again as soon as the aSpace[] buffer // is allocated. //if (pBt.secureDelete) //{ // int iOff = (int)(apDiv[i]) - (int)(pParent.aData); //SQLITE_PTR_TO_INT(apDiv[i]) - SQLITE_PTR_TO_INT(pParent.aData); // if( (iOff+szNew[i])>(int)pBt->usableSize ) // { // rc = SQLITE_CORRUPT_BKPT(); // Array.Clear(apOld[0].aData,0,apOld[0].aData.Length); //memset(apOld, 0, (i + 1) * sizeof(MemPage*)); // goto balance_cleanup; // } // else // { // memcpy(&aOvflSpace[iOff], apDiv[i], szNew[i]); // apDiv[i] = &aOvflSpace[apDiv[i] - pParent.aData]; // } //} pParent.dropCell(i + nxDiv - pParent.NOverflows, szNew[i], ref rc); } } // Make nMaxCells a multiple of 4 in order to preserve 8-byte alignment nMaxCells = (nMaxCells + 3) & ~3; // Allocate space for memory structures apCell = MallocEx.sqlite3ScratchMalloc(apCell, nMaxCells); if (szCell.Length < nMaxCells) { Array.Resize(ref szCell, nMaxCells); } // Load pointers to all cells on sibling pages and the divider cells into the local apCell[] array. Make copies of the divider cells // into space obtained from aSpace1[] and remove the the divider Cells from pParent. // If the siblings are on leaf pages, then the child pointers of the divider cells are stripped from the cells before they are copied // into aSpace1[]. In this way, all cells in apCell[] are without child pointers. If siblings are not leaves, then all cell in // apCell[] include child pointers. Either way, all cells in apCell[] are alike. // leafCorrection: 4 if pPage is a leaf. 0 if pPage is not a leaf. // leafData: 1 if pPage holds key+data and pParent holds only keys. leafCorrection = (ushort)(apOld[0].Leaf * 4); leafData = apOld[0].HasData; int j; for (i = 0; i < nOld; i++) { // Before doing anything else, take a copy of the i'th original sibling The rest of this function will use data from the copies rather // that the original pages since the original pages will be in the process of being overwritten. var pOld = apCopy[i] = apOld[i].Clone(); var limit = pOld.Cells + pOld.NOverflows; if (pOld.NOverflows > 0 || true) { for (j = 0; j < limit; j++) { Debug.Assert(nCell < nMaxCells); var iFOFC = pOld.FindOverflowCell(j); szCell[nCell] = pOld.cellSizePtr(iFOFC); // Copy the Data Locally if (apCell[nCell] == null) { apCell[nCell] = new byte[szCell[nCell]]; } else if (apCell[nCell].Length < szCell[nCell]) { Array.Resize(ref apCell[nCell], szCell[nCell]); } if (iFOFC < 0) // Overflow Cell { Buffer.BlockCopy(pOld.Overflows[-(iFOFC + 1)].Cell, 0, apCell[nCell], 0, szCell[nCell]); } else { Buffer.BlockCopy(pOld.Data, iFOFC, apCell[nCell], 0, szCell[nCell]); } nCell++; } } else { var aData = pOld.Data; var maskPage = pOld.MaskPage; var cellOffset = pOld.CellOffset; for (j = 0; j < limit; j++) { Debugger.Break(); Debug.Assert(nCell < nMaxCells); apCell[nCell] = FindCellv2(aData, maskPage, cellOffset, j); szCell[nCell] = pOld.cellSizePtr(apCell[nCell]); nCell++; } } if (i < nOld - 1 && 0 == leafData) { var sz = (ushort)szNew[i]; var pTemp = MallocEx.sqlite3Malloc(sz + leafCorrection); Debug.Assert(nCell < nMaxCells); szCell[nCell] = sz; Debug.Assert(sz <= pBt.MaxLocal + 23); Buffer.BlockCopy(pParent.Data, apDiv[i], pTemp, 0, sz); if (apCell[nCell] == null || apCell[nCell].Length < sz) { Array.Resize(ref apCell[nCell], sz); } Buffer.BlockCopy(pTemp, leafCorrection, apCell[nCell], 0, sz); Debug.Assert(leafCorrection == 0 || leafCorrection == 4); szCell[nCell] = (ushort)(szCell[nCell] - leafCorrection); if (0 == pOld.Leaf) { Debug.Assert(leafCorrection == 0); Debug.Assert(pOld.HeaderOffset == 0); // The right pointer of the child page pOld becomes the left pointer of the divider cell Buffer.BlockCopy(pOld.Data, 8, apCell[nCell], 0, 4);//memcpy( apCell[nCell], ref pOld.aData[8], 4 ); } else { Debug.Assert(leafCorrection == 4); if (szCell[nCell] < 4) { // Do not allow any cells smaller than 4 bytes. szCell[nCell] = 4; } } nCell++; } } // Figure out the number of pages needed to hold all nCell cells. Store this number in "k". Also compute szNew[] which is the total // size of all cells on the i-th page and cntNew[] which is the index in apCell[] of the cell that divides page i from page i+1. // cntNew[k] should equal nCell. // Values computed by this block: // k: The total number of sibling pages // szNew[i]: Spaced used on the i-th sibling page. // cntNew[i]: Index in apCell[] and szCell[] for the first cell to // the right of the i-th sibling page. // usableSpace: Number of bytes of space available on each sibling. usableSpace = (int)pBt.UsableSize - 12 + leafCorrection; int k; for (subtotal = k = i = 0; i < nCell; i++) { Debug.Assert(i < nMaxCells); subtotal += szCell[i] + 2; if (subtotal > usableSpace) { szNew[k] = subtotal - szCell[i]; cntNew[k] = i; if (leafData != 0) { i--; } subtotal = 0; k++; if (k > NB + 1) { rc = SysEx.SQLITE_CORRUPT_BKPT(); goto balance_cleanup; } } } szNew[k] = subtotal; cntNew[k] = nCell; k++; // The packing computed by the previous block is biased toward the siblings on the left side. The left siblings are always nearly full, while the // right-most sibling might be nearly empty. This block of code attempts to adjust the packing of siblings to get a better balance. // // This adjustment is more than an optimization. The packing above might be so out of balance as to be illegal. For example, the right-most // sibling might be completely empty. This adjustment is not optional. for (i = k - 1; i > 0; i--) { var szRight = szNew[i]; // Size of sibling on the right var szLeft = szNew[i - 1]; // Size of sibling on the left var r = cntNew[i - 1] - 1; // Index of right-most cell in left sibling var d = r + 1 - leafData; // Index of first cell to the left of right sibling Debug.Assert(d < nMaxCells); Debug.Assert(r < nMaxCells); while (szRight == 0 || szRight + szCell[d] + 2 <= szLeft - (szCell[r] + 2)) { szRight += szCell[d] + 2; szLeft -= szCell[r] + 2; cntNew[i - 1]--; r = cntNew[i - 1] - 1; d = r + 1 - leafData; } szNew[i] = szRight; szNew[i - 1] = szLeft; } // Either we found one or more cells (cntnew[0])>0) or pPage is a virtual root page. A virtual root page is when the real root // page is page 1 and we are the only child of that page. Debug.Assert(cntNew[0] > 0 || (pParent.ID == 1 && pParent.Cells == 0)); Btree.TRACE("BALANCE: old: %d %d %d ", apOld[0].ID, (nOld >= 2 ? apOld[1].ID : 0), (nOld >= 3 ? apOld[2].ID : 0)); // Allocate k new pages. Reuse old pages where possible. if (apOld[0].ID <= 1) { rc = SysEx.SQLITE_CORRUPT_BKPT(); goto balance_cleanup; } pageFlags = apOld[0].Data[0]; for (i = 0; i < k; i++) { var pNew = new MemPage(); if (i < nOld) { pNew = apNew[i] = apOld[i]; apOld[i] = null; rc = Pager.Write(pNew.DbPage); nNew++; if (rc != RC.OK) { goto balance_cleanup; } } else { Debug.Assert(i > 0); rc = pBt.allocateBtreePage(ref pNew, ref pgno, pgno, 0); if (rc != 0) { goto balance_cleanup; } apNew[i] = pNew; nNew++; // Set the pointer-map entry for the new sibling page. #if !SQLITE_OMIT_AUTOVACUUM if (pBt.AutoVacuum) #else if (false) #endif { pBt.ptrmapPut(pNew.ID, PTRMAP.BTREE, pParent.ID, ref rc); if (rc != RC.OK) { goto balance_cleanup; } } } } // Free any old pages that were not reused as new pages. while (i < nOld) { apOld[i].freePage(ref rc); if (rc != RC.OK) { goto balance_cleanup; } apOld[i].releasePage(); apOld[i] = null; i++; } // Put the new pages in accending order. This helps to keep entries in the disk file in order so that a scan // of the table is a linear scan through the file. That in turn helps the operating system to deliver pages // from the disk more rapidly. // An O(n^2) insertion sort algorithm is used, but since n is never more than NB (a small constant), that should // not be a problem. // When NB==3, this one optimization makes the database about 25% faster for large insertions and deletions. for (i = 0; i < k - 1; i++) { var minV = (int)apNew[i].ID; var minI = i; for (j = i + 1; j < k; j++) { if (apNew[j].ID < (uint)minV) { minI = j; minV = (int)apNew[j].ID; } } if (minI > i) { var pT = apNew[i]; apNew[i] = apNew[minI]; apNew[minI] = pT; } } Btree.TRACE("new: %d(%d) %d(%d) %d(%d) %d(%d) %d(%d)\n", apNew[0].ID, szNew[0], (nNew >= 2 ? apNew[1].ID : 0), (nNew >= 2 ? szNew[1] : 0), (nNew >= 3 ? apNew[2].ID : 0), (nNew >= 3 ? szNew[2] : 0), (nNew >= 4 ? apNew[3].ID : 0), (nNew >= 4 ? szNew[3] : 0), (nNew >= 5 ? apNew[4].ID : 0), (nNew >= 5 ? szNew[4] : 0)); Debug.Assert(Pager.IsPageWriteable(pParent.DbPage)); ConvertEx.Put4L(pParent.Data, pRight, apNew[nNew - 1].ID); // Evenly distribute the data in apCell[] across the new pages. Insert divider cells into pParent as necessary. j = 0; for (i = 0; i < nNew; i++) { // Assemble the new sibling page. MemPage pNew = apNew[i]; Debug.Assert(j < nMaxCells); pNew.zeroPage(pageFlags); pNew.assemblePage(cntNew[i] - j, apCell, szCell, j); Debug.Assert(pNew.Cells > 0 || (nNew == 1 && cntNew[0] == 0)); Debug.Assert(pNew.NOverflows == 0); j = cntNew[i]; // If the sibling page assembled above was not the right-most sibling, insert a divider cell into the parent page. Debug.Assert(i < nNew - 1 || j == nCell); if (j < nCell) { Debug.Assert(j < nMaxCells); var pCell = apCell[j]; var sz = szCell[j] + leafCorrection; var pTemp = MallocEx.sqlite3Malloc(sz); if (pNew.Leaf == 0) { Buffer.BlockCopy(pCell, 0, pNew.Data, 8, 4); } else if (leafData != 0) { // If the tree is a leaf-data tree, and the siblings are leaves, then there is no divider cell in apCell[]. Instead, the divider // cell consists of the integer key for the right-most cell of the sibling-page assembled above only. var info = new CellInfo(); j--; pNew.btreeParseCellPtr(apCell[j], ref info); pCell = pTemp; sz = 4 + ConvertEx.PutVarint9L(pCell, 4, (ulong)info.nKey); pTemp = null; } else { //------------ pCell -= 4; var _pCell_4 = MallocEx.sqlite3Malloc(pCell.Length + 4); Buffer.BlockCopy(pCell, 0, _pCell_4, 4, pCell.Length); pCell = _pCell_4; // Obscure case for non-leaf-data trees: If the cell at pCell was previously stored on a leaf node, and its reported size was 4 // bytes, then it may actually be smaller than this (see btreeParseCellPtr(), 4 bytes is the minimum size of // any cell). But it is important to pass the correct size to insertCell(), so reparse the cell now. // Note that this can never happen in an SQLite data file, as all cells are at least 4 bytes. It only happens in b-trees used // to evaluate "IN (SELECT ...)" and similar clauses. if (szCell[j] == 4) { Debug.Assert(leafCorrection == 4); sz = pParent.cellSizePtr(pCell); } } iOvflSpace += sz; Debug.Assert(sz <= pBt.MaxLocal + 23); Debug.Assert(iOvflSpace <= (int)pBt.PageSize); pParent.insertCell(nxDiv, pCell, sz, pTemp, pNew.ID, ref rc); if (rc != RC.OK) { goto balance_cleanup; } Debug.Assert(Pager.IsPageWriteable(pParent.DbPage)); j++; nxDiv++; } } Debug.Assert(j == nCell); Debug.Assert(nOld > 0); Debug.Assert(nNew > 0); if ((pageFlags & Btree.PTF_LEAF) == 0) { Buffer.BlockCopy(apCopy[nOld - 1].Data, 8, apNew[nNew - 1].Data, 8, 4); } if (isRoot != 0 && pParent.Cells == 0 && pParent.HeaderOffset <= apNew[0].FreeBytes) { // The root page of the b-tree now contains no cells. The only sibling page is the right-child of the parent. Copy the contents of the // child page into the parent, decreasing the overall height of the b-tree structure by one. This is described as the "balance-shallower" // sub-algorithm in some documentation. // If this is an auto-vacuum database, the call to copyNodeContent() sets all pointer-map entries corresponding to database image pages // for which the pointer is stored within the content being copied. // The second Debug.Assert below verifies that the child page is defragmented (it must be, as it was just reconstructed using assemblePage()). This // is important if the parent page happens to be page 1 of the database image. */ Debug.Assert(nNew == 1); Debug.Assert(apNew[0].FreeBytes == (ConvertEx.Get2(apNew[0].Data, 5) - apNew[0].CellOffset - apNew[0].Cells * 2)); copyNodeContent(apNew[0], pParent, ref rc); apNew[0].freePage(ref rc); } else #if !SQLITE_OMIT_AUTOVACUUM if (pBt.AutoVacuum) #else if (false) #endif { // Fix the pointer-map entries for all the cells that were shifted around. There are several different types of pointer-map entries that need to // be dealt with by this routine. Some of these have been set already, but many have not. The following is a summary: // 1) The entries associated with new sibling pages that were not siblings when this function was called. These have already // been set. We don't need to worry about old siblings that were moved to the free-list - the freePage() code has taken care // of those. // 2) The pointer-map entries associated with the first overflow page in any overflow chains used by new divider cells. These // have also already been taken care of by the insertCell() code. // 3) If the sibling pages are not leaves, then the child pages of cells stored on the sibling pages may need to be updated. // 4) If the sibling pages are not internal intkey nodes, then any overflow pages used by these cells may need to be updated // (internal intkey nodes never contain pointers to overflow pages). // 5) If the sibling pages are not leaves, then the pointer-map entries for the right-child pages of each sibling may need // to be updated. // Cases 1 and 2 are dealt with above by other code. The next block deals with cases 3 and 4 and the one after that, case 5. Since // setting a pointer map entry is a relatively expensive operation, this code only sets pointer map entries for child or overflow pages that have // actually moved between pages. var pNew = apNew[0]; var pOld = apCopy[0]; var nOverflow = pOld.NOverflows; var iNextOld = pOld.Cells + nOverflow; var iOverflow = (nOverflow != 0 ? pOld.Overflows[0].Index : -1); j = 0; // Current 'old' sibling page k = 0; // Current 'new' sibling page for (i = 0; i < nCell; i++) { var isDivider = 0; while (i == iNextOld) { // Cell i is the cell immediately following the last cell on old sibling page j. If the siblings are not leaf pages of an // intkey b-tree, then cell i was a divider cell. pOld = apCopy[++j]; iNextOld = i + (0 == leafData ? 1 : 0) + pOld.Cells + pOld.NOverflows; if (pOld.NOverflows != 0) { nOverflow = pOld.NOverflows; iOverflow = i + (0 == leafData ? 1 : 0) + pOld.Overflows[0].Index; } isDivider = 0 == leafData ? 1 : 0; } Debug.Assert(nOverflow > 0 || iOverflow < i); Debug.Assert(nOverflow < 2 || pOld.Overflows[0].Index == pOld.Overflows[1].Index - 1); Debug.Assert(nOverflow < 3 || pOld.Overflows[1].Index == pOld.Overflows[2].Index - 1); if (i == iOverflow) { isDivider = 1; if (--nOverflow > 0) { iOverflow++; } } if (i == cntNew[k]) { // Cell i is the cell immediately following the last cell on new sibling page k. If the siblings are not leaf pages of an // intkey b-tree, then cell i is a divider cell. pNew = apNew[++k]; if (leafData == 0) { continue; } } Debug.Assert(j < nOld); Debug.Assert(k < nNew); // If the cell was originally divider cell (and is not now) or an overflow cell, or if the cell was located on a different sibling // page before the balancing, then the pointer map entries associated with any child or overflow pages need to be updated. if (isDivider != 0 || pOld.ID != pNew.ID) { if (leafCorrection == 0) { pBt.ptrmapPut(ConvertEx.Get4(apCell[i]), PTRMAP.BTREE, pNew.ID, ref rc); } if (szCell[i] > pNew.MinLocal) { pNew.ptrmapPutOvflPtr(apCell[i], ref rc); } } } if (leafCorrection == 0) { for (i = 0; i < nNew; i++) { var key = ConvertEx.Get4(apNew[i].Data, 8); pBt.ptrmapPut(key, PTRMAP.BTREE, apNew[i].ID, ref rc); } } #if false // The ptrmapCheckPages() contains Debug.Assert() statements that verify that all pointer map pages are set correctly. This is helpful while // debugging. This is usually disabled because a corrupt database may cause an Debug.Assert() statement to fail. ptrmapCheckPages(apNew, nNew); ptrmapCheckPages(pParent, 1); #endif } Debug.Assert(pParent.HasInit); Btree.TRACE("BALANCE: finished: old=%d new=%d cells=%d\n", nOld, nNew, nCell); // Cleanup before returning. balance_cleanup: MallocEx.sqlite3ScratchFree(apCell); for (i = 0; i < nOld; i++) { apOld[i].releasePage(); } for (i = 0; i < nNew; i++) { apNew[i].releasePage(); } return(rc); }
internal RC allocateBtreePage(ref MemPage ppPage, ref Pgno pPgno, Pgno nearby, byte exact) { MemPage pTrunk = null; MemPage pPrevTrunk = null; Debug.Assert(MutexEx.Held(this.Mutex)); var pPage1 = this.Page1; var mxPage = btreePagecount(); // Total size of the database file var n = ConvertEx.Get4(pPage1.Data, 36); // Number of pages on the freelist if (n >= mxPage) { return(SysEx.SQLITE_CORRUPT_BKPT()); } RC rc; if (n > 0) { // There are pages on the freelist. Reuse one of those pages. Pgno iTrunk; byte searchList = 0; // If the free-list must be searched for 'nearby' // If the 'exact' parameter was true and a query of the pointer-map shows that the page 'nearby' is somewhere on the free-list, then the entire-list will be searched for that page. #if !SQLITE_OMIT_AUTOVACUUM if (exact != 0 && nearby <= mxPage) { Debug.Assert(nearby > 0); Debug.Assert(this.AutoVacuum); PTRMAP eType = 0; uint dummy0 = 0; rc = ptrmapGet(nearby, ref eType, ref dummy0); if (rc != RC.OK) { return(rc); } if (eType == PTRMAP.FREEPAGE) { searchList = 1; } pPgno = nearby; } #endif // Decrement the free-list count by 1. Set iTrunk to the index of the first free-list trunk page. iPrevTrunk is initially 1. rc = Pager.Write(pPage1.DbPage); if (rc != RC.OK) { return(rc); } ConvertEx.Put4(pPage1.Data, 36, n - 1); // The code within this loop is run only once if the 'searchList' variable is not true. Otherwise, it runs once for each trunk-page on the // free-list until the page 'nearby' is located. do { pPrevTrunk = pTrunk; iTrunk = (pPrevTrunk != null ? ConvertEx.Get4(pPrevTrunk.Data, 0) : ConvertEx.Get4(pPage1.Data, 32)); rc = (iTrunk > mxPage ? SysEx.SQLITE_CORRUPT_BKPT() : btreeGetPage(iTrunk, ref pTrunk, 0)); if (rc != RC.OK) { pTrunk = null; goto end_allocate_page; } var k = ConvertEx.Get4(pTrunk.Data, 4); // # of leaves on this trunk page if (k == 0 && searchList == 0) { // The trunk has no leaves and the list is not being searched. So extract the trunk page itself and use it as the newly allocated page Debug.Assert(pPrevTrunk == null); rc = Pager.Write(pTrunk.DbPage); if (rc != RC.OK) { goto end_allocate_page; } pPgno = iTrunk; Buffer.BlockCopy(pTrunk.Data, 0, pPage1.Data, 32, 4); ppPage = pTrunk; pTrunk = null; Btree.TRACE("ALLOCATE: %d trunk - %d free pages left\n", pPgno, n - 1); } else if (k > (uint)(this.UsableSize / 4 - 2)) { // Value of k is out of range. Database corruption rc = SysEx.SQLITE_CORRUPT_BKPT(); goto end_allocate_page; #if !SQLITE_OMIT_AUTOVACUUM } else if (searchList != 0 && nearby == iTrunk) { // The list is being searched and this trunk page is the page to allocate, regardless of whether it has leaves. Debug.Assert(pPgno == iTrunk); ppPage = pTrunk; searchList = 0; rc = Pager.Write(pTrunk.DbPage); if (rc != RC.OK) { goto end_allocate_page; } if (k == 0) { if (pPrevTrunk == null) { pPage1.Data[32 + 0] = pTrunk.Data[0 + 0]; pPage1.Data[32 + 1] = pTrunk.Data[0 + 1]; pPage1.Data[32 + 2] = pTrunk.Data[0 + 2]; pPage1.Data[32 + 3] = pTrunk.Data[0 + 3]; } else { rc = Pager.Write(pPrevTrunk.DbPage); if (rc != RC.OK) { goto end_allocate_page; } pPrevTrunk.Data[0 + 0] = pTrunk.Data[0 + 0]; pPrevTrunk.Data[0 + 1] = pTrunk.Data[0 + 1]; pPrevTrunk.Data[0 + 2] = pTrunk.Data[0 + 2]; pPrevTrunk.Data[0 + 3] = pTrunk.Data[0 + 3]; } } else { // The trunk page is required by the caller but it contains pointers to free-list leaves. The first leaf becomes a trunk page in this case. var pNewTrunk = new MemPage(); var iNewTrunk = (Pgno)ConvertEx.Get4(pTrunk.Data, 8); if (iNewTrunk > mxPage) { rc = SysEx.SQLITE_CORRUPT_BKPT(); goto end_allocate_page; } rc = btreeGetPage(iNewTrunk, ref pNewTrunk, 0); if (rc != RC.OK) { goto end_allocate_page; } rc = Pager.Write(pNewTrunk.DbPage); if (rc != RC.OK) { pNewTrunk.releasePage(); goto end_allocate_page; } pNewTrunk.Data[0 + 0] = pTrunk.Data[0 + 0]; pNewTrunk.Data[0 + 1] = pTrunk.Data[0 + 1]; pNewTrunk.Data[0 + 2] = pTrunk.Data[0 + 2]; pNewTrunk.Data[0 + 3] = pTrunk.Data[0 + 3]; ConvertEx.Put4(pNewTrunk.Data, 4, (uint)(k - 1)); Buffer.BlockCopy(pTrunk.Data, 12, pNewTrunk.Data, 8, (int)(k - 1) * 4); pNewTrunk.releasePage(); if (pPrevTrunk == null) { Debug.Assert(Pager.IsPageWriteable(pPage1.DbPage)); ConvertEx.Put4(pPage1.Data, 32, iNewTrunk); } else { rc = Pager.Write(pPrevTrunk.DbPage); if (rc != RC.OK) { goto end_allocate_page; } ConvertEx.Put4(pPrevTrunk.Data, 0, iNewTrunk); } } pTrunk = null; Btree.TRACE("ALLOCATE: %d trunk - %d free pages left\n", pPgno, n - 1); #endif } else if (k > 0) { // Extract a leaf from the trunk uint closest; var aData = pTrunk.Data; if (nearby > 0) { closest = 0; var dist = Math.Abs((int)(ConvertEx.Get4(aData, 8) - nearby)); for (uint i = 1; i < k; i++) { int dist2 = Math.Abs((int)(ConvertEx.Get4(aData, 8 + i * 4) - nearby)); if (dist2 < dist) { closest = i; dist = dist2; } } } else { closest = 0; } // var iPage = (Pgno)ConvertEx.Get4(aData, 8 + closest * 4); if (iPage > mxPage) { rc = SysEx.SQLITE_CORRUPT_BKPT(); goto end_allocate_page; } if (searchList == 0 || iPage == nearby) { pPgno = iPage; Btree.TRACE("ALLOCATE: %d was leaf %d of %d on trunk %d" + ": %d more free pages\n", pPgno, closest + 1, k, pTrunk.ID, n - 1); rc = Pager.Write(pTrunk.DbPage); if (rc != RC.OK) { goto end_allocate_page; } if (closest < k - 1) { Buffer.BlockCopy(aData, (int)(4 + k * 4), aData, 8 + (int)closest * 4, 4); } ConvertEx.Put4(aData, 4, (k - 1)); var noContent = (!btreeGetHasContent(pPgno) ? 1 : 0); rc = btreeGetPage(pPgno, ref ppPage, noContent); if (rc == RC.OK) { rc = Pager.Write((ppPage).DbPage); if (rc != RC.OK) { ppPage.releasePage(); } } searchList = 0; } } pPrevTrunk.releasePage(); pPrevTrunk = null; } while (searchList != 0); } else { // There are no pages on the freelist, so create a new page at the end of the file rc = Pager.Write(this.Page1.DbPage); if (rc != RC.OK) { return(rc); } this.Pages++; if (this.Pages == MemPage.PENDING_BYTE_PAGE(this)) { this.Pages++; } #if !SQLITE_OMIT_AUTOVACUUM if (this.AutoVacuum && MemPage.PTRMAP_ISPAGE(this, this.Pages)) { // If pPgno refers to a pointer-map page, allocate two new pages at the end of the file instead of one. The first allocated page // becomes a new pointer-map page, the second is used by the caller. MemPage pPg = null; Btree.TRACE("ALLOCATE: %d from end of file (pointer-map page)\n", pPgno); Debug.Assert(this.Pages != MemPage.PENDING_BYTE_PAGE(this)); rc = btreeGetPage(this.Pages, ref pPg, 1); if (rc == RC.OK) { rc = Pager.Write(pPg.DbPage); pPg.releasePage(); } if (rc != RC.OK) { return(rc); } this.Pages++; if (this.Pages == MemPage.PENDING_BYTE_PAGE(this)) { this.Pages++; } } #endif ConvertEx.Put4(this.Page1.Data, 28, this.Pages); pPgno = this.Pages; Debug.Assert(pPgno != MemPage.PENDING_BYTE_PAGE(this)); rc = btreeGetPage(pPgno, ref ppPage, 1); if (rc != RC.OK) { return(rc); } rc = Pager.Write((ppPage).DbPage); if (rc != RC.OK) { ppPage.releasePage(); } Btree.TRACE("ALLOCATE: %d from end of file\n", pPgno); } Debug.Assert(pPgno != MemPage.PENDING_BYTE_PAGE(this)); end_allocate_page: pTrunk.releasePage(); pPrevTrunk.releasePage(); if (rc == RC.OK) { if (Pager.GetPageRefCount((ppPage).DbPage) > 1) { ppPage.releasePage(); return(SysEx.SQLITE_CORRUPT_BKPT()); } (ppPage).HasInit = false; } else { ppPage = null; } Debug.Assert(rc != RC.OK || Pager.IsPageWriteable((ppPage).DbPage)); return(rc); }
internal RC freePage2(MemPage pMemPage, Pgno iPage) { MemPage pTrunk = null; // Free-list trunk page var pPage1 = this.Page1; // Local reference to page 1 Debug.Assert(MutexEx.Held(this.Mutex)); Debug.Assert(iPage > 1); Debug.Assert(pMemPage == null || pMemPage.ID == iPage); MemPage pPage; // Page being freed. May be NULL. if (pMemPage != null) { pPage = pMemPage; Pager.AddPageRef(pPage.DbPage); } else { pPage = btreePageLookup(iPage); } // Increment the free page count on pPage1 var rc = Pager.Write(pPage1.DbPage); if (rc != RC.OK) { goto freepage_out; } var nFree = (int)ConvertEx.Get4(pPage1.Data, 36); // Initial number of pages on free-list ConvertEx.Put4(pPage1.Data, 36, nFree + 1); if (this.SecureDelete) { // If the secure_delete option is enabled, then always fully overwrite deleted information with zeros. if ((pPage == null && ((rc = btreeGetPage(iPage, ref pPage, 0)) != RC.OK)) || ((rc = Pager.Write(pPage.DbPage)) != RC.OK)) { goto freepage_out; } Array.Clear(pPage.Data, 0, (int)pPage.Shared.PageSize); } // If the database supports auto-vacuum, write an entry in the pointer-map to indicate that the page is free. #if !SQLITE_OMIT_AUTOVACUUM if (this.AutoVacuum) #else if (false) #endif { ptrmapPut(iPage, PTRMAP.FREEPAGE, 0, ref rc); if (rc != RC.OK) { goto freepage_out; } } // Now manipulate the actual database free-list structure. There are two possibilities. If the free-list is currently empty, or if the first // trunk page in the free-list is full, then this page will become a new free-list trunk page. Otherwise, it will become a leaf of the // first trunk page in the current free-list. This block tests if it is possible to add the page as a new free-list leaf. Pgno iTrunk = 0; // Page number of free-list trunk page if (nFree != 0) { uint nLeaf; // Initial number of leaf cells on trunk page iTrunk = (Pgno)ConvertEx.Get4(pPage1.Data, 32); // Page number of free-list trunk page rc = btreeGetPage(iTrunk, ref pTrunk, 0); if (rc != RC.OK) { goto freepage_out; } nLeaf = ConvertEx.Get4(pTrunk.Data, 4); Debug.Assert(this.UsableSize > 32); if (nLeaf > (uint)this.UsableSize / 4 - 2) { rc = SysEx.SQLITE_CORRUPT_BKPT(); goto freepage_out; } if (nLeaf < (uint)this.UsableSize / 4 - 8) { // In this case there is room on the trunk page to insert the page being freed as a new leaf. // Note: that the trunk page is not really full until it contains usableSize/4 - 2 entries, not usableSize/4 - 8 entries as we have // coded. But due to a coding error in versions of SQLite prior to 3.6.0, databases with freelist trunk pages holding more than // usableSize/4 - 8 entries will be reported as corrupt. In order to maintain backwards compatibility with older versions of SQLite, // we will continue to restrict the number of entries to usableSize/4 - 8 for now. At some point in the future (once everyone has upgraded // to 3.6.0 or later) we should consider fixing the conditional above to read "usableSize/4-2" instead of "usableSize/4-8". rc = Pager.Write(pTrunk.DbPage); if (rc == RC.OK) { ConvertEx.Put4(pTrunk.Data, 4, nLeaf + 1); ConvertEx.Put4(pTrunk.Data, (uint)(8 + nLeaf * 4), iPage); if (pPage != null && !this.SecureDelete) { Pager.DontWrite(pPage.DbPage); } rc = btreeSetHasContent(iPage); } Btree.TRACE("FREE-PAGE: %d leaf on trunk page %d\n", iPage, pTrunk.ID); goto freepage_out; } } // If control flows to this point, then it was not possible to add the the page being freed as a leaf page of the first trunk in the free-list. // Possibly because the free-list is empty, or possibly because the first trunk in the free-list is full. Either way, the page being freed // will become the new first trunk page in the free-list. if (pPage == null && (rc = btreeGetPage(iPage, ref pPage, 0)) != RC.OK) { goto freepage_out; } rc = Pager.Write(pPage.DbPage); if (rc != RC.OK) { goto freepage_out; } ConvertEx.Put4L(pPage.Data, iTrunk); ConvertEx.Put4(pPage.Data, 4, 0); ConvertEx.Put4(pPage1.Data, 32, iPage); Btree.TRACE("FREE-PAGE: %d new trunk page replacing %d\n", pPage.ID, iTrunk); freepage_out: if (pPage != null) { pPage.HasInit = false; } pPage.releasePage(); pTrunk.releasePage(); return(rc); }
// was:sqlite3BtreeMovetoUnpacked public RC MoveToUnpacked(Btree.UnpackedRecord idxKey, long intKey, bool biasRight, ref int pRes) { Debug.Assert(HoldsMutex()); Debug.Assert(MutexEx.Held(Tree.DB.Mutex)); Debug.Assert((idxKey == null) == (KeyInfo == null)); // If the cursor is already positioned at the point we are trying to move to, then just return without doing any work if (State == CursorState.VALID && ValidNKey && Pages[0].HasIntKey) { if (Info.nKey == intKey) { pRes = 0; return RC.OK; } if (AtLast && Info.nKey < intKey) { pRes = -1; return RC.OK; } } var rc = MoveToRoot(); if (rc != RC.OK) return rc; Debug.Assert(Pages[PageID] != null); Debug.Assert(Pages[PageID].HasInit); Debug.Assert(Pages[PageID].Cells > 0 || State == CursorState.INVALID); if (State == CursorState.INVALID) { pRes = -1; Debug.Assert(Pages[PageID].Cells == 0); return RC.OK; } Debug.Assert(Pages[0].HasIntKey || idxKey != null); for (; ; ) { var page = Pages[PageID]; // pPage.nCell must be greater than zero. If this is the root-page the cursor would have been INVALID above and this for(;;) loop // not run. If this is not the root-page, then the moveToChild() routine would have already detected db corruption. Similarly, pPage must // be the right kind (index or table) of b-tree page. Otherwise a moveToChild() or moveToRoot() call would have detected corruption. Debug.Assert(page.Cells > 0); Debug.Assert(page.HasIntKey == (idxKey == null)); var lwr = 0; var upr = page.Cells - 1; int idx; PagesIndexs[PageID] = (ushort)(biasRight ? (idx = upr) : (idx = (upr + lwr) / 2)); int c; for (; ; ) { Debug.Assert(idx == PagesIndexs[PageID]); Info.nSize = 0; var cell = page.FindCell(idx) + page.ChildPtrSize; // Pointer to current cell in pPage if (page.HasIntKey) { var nCellKey = 0L; if (page.HasData != 0) { uint dummy0; cell += ConvertEx.GetVarint4(page.Data, (uint)cell, out dummy0); } ConvertEx.GetVarint9L(page.Data, (uint)cell, out nCellKey); if (nCellKey == intKey) c = 0; else if (nCellKey < intKey) c = -1; else { Debug.Assert(nCellKey > intKey); c = 1; } ValidNKey = true; Info.nKey = nCellKey; } else { // The maximum supported page-size is 65536 bytes. This means that the maximum number of record bytes stored on an index B-Tree // page is less than 16384 bytes and may be stored as a 2-byte varint. This information is used to attempt to avoid parsing // the entire cell by checking for the cases where the record is stored entirely within the b-tree page by inspecting the first // 2 bytes of the cell. var nCell = (int)page.Data[cell + 0]; if (0 == (nCell & 0x80) && nCell <= page.MaxLocal) // This branch runs if the record-size field of the cell is a single byte varint and the record fits entirely on the main b-tree page. c = Btree._vdbe.sqlite3VdbeRecordCompare(nCell, page.Data, cell + 1, idxKey); else if (0 == (page.Data[cell + 1] & 0x80) && (nCell = ((nCell & 0x7f) << 7) + page.Data[cell + 1]) <= page.MaxLocal) // The record-size field is a 2 byte varint and the record fits entirely on the main b-tree page. c = Btree._vdbe.sqlite3VdbeRecordCompare(nCell, page.Data, cell + 2, idxKey); else { // The record flows over onto one or more overflow pages. In this case the whole cell needs to be parsed, a buffer allocated // and accessPayload() used to retrieve the record into the buffer before VdbeRecordCompare() can be called. var pCellBody = new byte[page.Data.Length - cell + page.ChildPtrSize]; Buffer.BlockCopy(page.Data, cell - page.ChildPtrSize, pCellBody, 0, pCellBody.Length); page.btreeParseCellPtr(pCellBody, ref Info); nCell = (int)Info.nKey; var pCellKey = MallocEx.sqlite3Malloc(nCell); rc = AccessPayload(0, (uint)nCell, pCellKey, false); if (rc != RC.OK) { pCellKey = null; goto moveto_finish; } c = Btree._vdbe.sqlite3VdbeRecordCompare(nCell, pCellKey, idxKey); pCellKey = null; } } if (c == 0) { if (page.HasIntKey && 0 == page.Leaf) { lwr = idx; upr = lwr - 1; break; } else { pRes = 0; rc = RC.OK; goto moveto_finish; } } if (c < 0) lwr = idx + 1; else upr = idx - 1; if (lwr > upr) break; PagesIndexs[PageID] = (ushort)(idx = (lwr + upr) / 2); } Debug.Assert(lwr == upr + 1); Debug.Assert(page.HasInit); Pgno chldPg; if (page.Leaf != 0) chldPg = 0; else if (lwr >= page.Cells) chldPg = ConvertEx.Get4(page.Data, page.HeaderOffset + 8); else chldPg = ConvertEx.Get4(page.Data, page.FindCell(lwr)); if (chldPg == 0) { Debug.Assert(PagesIndexs[PageID] < Pages[PageID].Cells); pRes = c; rc = RC.OK; goto moveto_finish; } PagesIndexs[PageID] = (ushort)lwr; Info.nSize = 0; ValidNKey = false; rc = MoveToChild(chldPg); if (rc != RC.OK) goto moveto_finish; } moveto_finish: return rc; }
// was:invalidateIncrblobCursors internal static void invalidateIncrblobCursors(Btree tree, long row, int clearTable) { }
internal static RC sqlite3BtreeIncrVacuum(Btree p) { var pBt = p.Shared; p.sqlite3BtreeEnter(); Debug.Assert(pBt.InTransaction == TRANS.WRITE && p.InTransaction == TRANS.WRITE); RC rc; if (!pBt.AutoVacuum) rc = RC.DONE; else { Btree.invalidateAllOverflowCache(pBt); rc = incrVacuumStep(pBt, 0, pBt.btreePagecount()); if (rc == RC.OK) { rc = Pager.Write(pBt.Page1.DbPage); ConvertEx.Put4(pBt.Page1.Data, 28, pBt.Pages); } } p.sqlite3BtreeLeave(); 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); }
internal static RC relocatePage(BtShared pBt, MemPage pDbPage, PTRMAP eType, Pgno iPtrPage, Pgno iFreePage, int isCommit) { var pPtrPage = new MemPage(); // The page that contains a pointer to pDbPage var iDbPage = pDbPage.ID; var pPager = pBt.Pager; Debug.Assert(eType == PTRMAP.OVERFLOW2 || eType == PTRMAP.OVERFLOW1 || eType == PTRMAP.BTREE || eType == PTRMAP.ROOTPAGE); Debug.Assert(MutexEx.Held(pBt.Mutex)); Debug.Assert(pDbPage.Shared == pBt); // Move page iDbPage from its current location to page number iFreePage Btree.TRACE("AUTOVACUUM: Moving %d to free page %d (ptr page %d type %d)\n", iDbPage, iFreePage, iPtrPage, eType); var rc = pPager.sqlite3PagerMovepage(pDbPage.DbPage, iFreePage, isCommit); if (rc != RC.OK) { return(rc); } pDbPage.ID = iFreePage; // If pDbPage was a btree-page, then it may have child pages and/or cells that point to overflow pages. The pointer map entries for all these // pages need to be changed. // If pDbPage is an overflow page, then the first 4 bytes may store a pointer to a subsequent overflow page. If this is the case, then // the pointer map needs to be updated for the subsequent overflow page. if (eType == PTRMAP.BTREE || eType == PTRMAP.ROOTPAGE) { rc = pDbPage.setChildPtrmaps(); if (rc != RC.OK) { return(rc); } } else { var nextOvfl = (Pgno)ConvertEx.Get4(pDbPage.Data); if (nextOvfl != 0) { pBt.ptrmapPut(nextOvfl, PTRMAP.OVERFLOW2, iFreePage, ref rc); if (rc != RC.OK) { return(rc); } } } // Fix the database pointer on page iPtrPage that pointed at iDbPage so that it points at iFreePage. Also fix the pointer map entry for iPtrPage. if (eType != PTRMAP.ROOTPAGE) { rc = pBt.btreeGetPage(iPtrPage, ref pPtrPage, 0); if (rc != RC.OK) { return(rc); } rc = Pager.Write(pPtrPage.DbPage); if (rc != RC.OK) { pPtrPage.releasePage(); return(rc); } rc = pPtrPage.modifyPagePointer(iDbPage, iFreePage, eType); pPtrPage.releasePage(); if (rc == RC.OK) { pBt.ptrmapPut(iFreePage, eType, iPtrPage, ref rc); } } return(rc); }
// was:sqlite3BtreeOpen public static RC Open(VirtualFileSystem pVfs, string zFilename, sqlite3 db, ref Btree rTree, OPEN flags, VFSOPEN vfsFlags) { Btree p; // Handle to return var rc = RC.OK; byte nReserve; // Byte of unused space on each page var zDbHeader = new byte[100]; // Database header content // True if opening an ephemeral, temporary database */ bool isTempDb = string.IsNullOrEmpty(zFilename); // Set the variable isMemdb to true for an in-memory database, or false for a file-based database. #if SQLITE_OMIT_MEMORYDB var isMemdb = false; #else var isMemdb = (zFilename == ":memory:" || isTempDb && db.sqlite3TempInMemory()); #endif Debug.Assert(db != null); Debug.Assert(pVfs != null); Debug.Assert(MutexEx.Held(db.Mutex)); Debug.Assert(((uint)flags & 0xff) == (uint)flags); // flags fit in 8 bits // Only a BTREE_SINGLE database can be BTREE_UNORDERED Debug.Assert((flags & OPEN.UNORDERED) == 0 || (flags & OPEN.SINGLE) != 0); // A BTREE_SINGLE database is always a temporary and/or ephemeral Debug.Assert((flags & OPEN.SINGLE) == 0 || isTempDb); if ((db.flags & sqlite3b.SQLITE.NoReadlock) != 0) flags |= OPEN.NO_READLOCK; if (isMemdb) flags |= OPEN.MEMORY; if ((vfsFlags & VFSOPEN.MAIN_DB) != 0 && (isMemdb || isTempDb)) vfsFlags = (vfsFlags & ~VFSOPEN.MAIN_DB) | VFSOPEN.TEMP_DB; p = new Btree(); p.InTransaction = TRANS.NONE; p.DB = db; #if !SQLITE_OMIT_SHARED_CACHE p.Locks.Tree = p; p.Locks.TableID = 1; #endif BtShared shared = null; // Shared part of btree structure sqlite3_mutex mutexOpen = null; // Prevents a race condition. #if !SQLITE_OMIT_SHARED_CACHE && !SQLITE_OMIT_DISKIO // If this Btree is a candidate for shared cache, try to find an existing BtShared object that we can share with if (!isMemdb && !isTempDb) { if ((vfsFlags & VFSOPEN.SHAREDCACHE) != 0) { p.Sharable = true; string zPathname; rc = pVfs.xFullPathname(zFilename, out zPathname); mutexOpen = MutexEx.sqlite3MutexAlloc(MUTEX.STATIC_OPEN); MutexEx.sqlite3_mutex_enter(mutexOpen); var mutexShared = MutexEx.sqlite3MutexAlloc(MUTEX.STATIC_MASTER); MutexEx.sqlite3_mutex_enter(mutexShared); for (shared = SysEx.getGLOBAL<BtShared>(s_sqlite3SharedCacheList); shared != null; shared = shared.Next) { Debug.Assert(shared.nRef > 0); if (string.Equals(zPathname, shared.Pager.sqlite3PagerFilename) && shared.Pager.sqlite3PagerVfs == pVfs) { for (var iDb = db.DBs - 1; iDb >= 0; iDb--) { var existingTree = db.AllocDBs[iDb].Tree; if (existingTree != null && existingTree.Shared == shared) { MutexEx.sqlite3_mutex_leave(mutexShared); MutexEx.sqlite3_mutex_leave(mutexOpen); p = null; return RC.CONSTRAINT; } } p.Shared = shared; shared.nRef++; break; } } MutexEx.sqlite3_mutex_leave(mutexShared); } #if DEBUG else // In debug mode, we mark all persistent databases as sharable even when they are not. This exercises the locking code and // gives more opportunity for asserts(sqlite3_mutex_held()) statements to find locking problems. p.Sharable = true; #endif } #endif if (shared == null) { // The following asserts make sure that structures used by the btree are the right size. This is to guard against size changes that result // when compiling on a different architecture. Debug.Assert(sizeof(long) == 8 || sizeof(long) == 4); Debug.Assert(sizeof(ulong) == 8 || sizeof(ulong) == 4); Debug.Assert(sizeof(uint) == 4); Debug.Assert(sizeof(ushort) == 2); Debug.Assert(sizeof(Pgno) == 4); shared = new BtShared(); rc = Pager.Open(pVfs, out shared.Pager, zFilename, EXTRA_SIZE, (Pager.PAGEROPEN)flags, vfsFlags, pageReinit, () => new MemPage()); if (rc == RC.OK) rc = shared.Pager.ReadFileHeader(zDbHeader.Length, zDbHeader); if (rc != RC.OK) goto btree_open_out; shared.OpenFlags = flags; shared.DB = db; shared.Pager.SetBusyHandler(btreeInvokeBusyHandler, shared); p.Shared = shared; shared.Cursors = null; shared.Page1 = null; shared.ReadOnly = shared.Pager.IsReadonly; #if SQLITE_SECURE_DELETE pBt.secureDelete = true; #endif shared.PageSize = (uint)((zDbHeader[16] << 8) | (zDbHeader[17] << 16)); if (shared.PageSize < 512 || shared.PageSize > Pager.SQLITE_MAX_PAGE_SIZE || ((shared.PageSize - 1) & shared.PageSize) != 0) { shared.PageSize = 0; #if !SQLITE_OMIT_AUTOVACUUM // If the magic name ":memory:" will create an in-memory database, then leave the autoVacuum mode at 0 (do not auto-vacuum), even if // SQLITE_DEFAULT_AUTOVACUUM is true. On the other hand, if SQLITE_OMIT_MEMORYDB has been defined, then ":memory:" is just a // regular file-name. In this case the auto-vacuum applies as per normal. if (zFilename != string.Empty && !isMemdb) { shared.AutoVacuum = (AUTOVACUUM.DEFAULT != AUTOVACUUM.NONE); shared.IncrVacuum = (AUTOVACUUM.DEFAULT == AUTOVACUUM.INCR); } #endif nReserve = 0; } else { nReserve = zDbHeader[20]; shared.PageSizeFixed = true; #if !SQLITE_OMIT_AUTOVACUUM shared.AutoVacuum = ConvertEx.Get4(zDbHeader, 36 + 4 * 4) != 0; shared.IncrVacuum = ConvertEx.Get4(zDbHeader, 36 + 7 * 4) != 0; #endif } rc = shared.Pager.SetPageSize(ref shared.PageSize, nReserve); if (rc != RC.OK) goto btree_open_out; shared.UsableSize = (ushort)(shared.PageSize - nReserve); Debug.Assert((shared.PageSize & 7) == 0); // 8-byte alignment of pageSize #if !SQLITE_OMIT_SHARED_CACHE && !SQLITE_OMIT_DISKIO // Add the new BtShared object to the linked list sharable BtShareds. if (p.Sharable) { sqlite3_mutex mutexShared; shared.nRef = 1; mutexShared = MutexEx.sqlite3MutexAlloc(MUTEX.STATIC_MASTER); if (MutexEx.SQLITE_THREADSAFE && MutexEx.WantsCoreMutex) shared.Mutex = MutexEx.sqlite3MutexAlloc(MUTEX.FAST); MutexEx.sqlite3_mutex_enter(mutexShared); shared.Next = SysEx.getGLOBAL<BtShared>(s_sqlite3SharedCacheList); SysEx.setGLOBAL<BtShared>(s_sqlite3SharedCacheList, shared); MutexEx.sqlite3_mutex_leave(mutexShared); } #endif } #if !SQLITE_OMIT_SHARED_CACHE && !SQLITE_OMIT_DISKIO // If the new Btree uses a sharable pBtShared, then link the new Btree into the list of all sharable Btrees for the same connection. // The list is kept in ascending order by pBt address. Btree existingTree2; if (p.Sharable) for (var i = 0; i < db.DBs; i++) if ((existingTree2 = db.AllocDBs[i].Tree) != null && existingTree2.Sharable) { while (existingTree2.Prev != null) { existingTree2 = existingTree2.Prev; } if (p.Shared.Version < existingTree2.Shared.Version) { p.Next = existingTree2; p.Prev = null; existingTree2.Prev = p; } else { while (existingTree2.Next != null && existingTree2.Next.Shared.Version < p.Shared.Version) existingTree2 = existingTree2.Next; p.Next = existingTree2.Next; p.Prev = existingTree2; if (p.Next != null) p.Next.Prev = p; existingTree2.Next = p; } break; } #endif rTree = p; // btree_open_out: if (rc != RC.OK) { if (shared != null && shared.Pager != null) shared.Pager.Close(); shared = null; p = null; rTree = null; } else { // If the B-Tree was successfully opened, set the pager-cache size to the default value. Except, when opening on an existing shared pager-cache, // do not change the pager-cache size. if (p.GetSchema(0, null, null) == null) p.Shared.Pager.SetCacheSize(SQLITE_DEFAULT_CACHE_SIZE); } if (mutexOpen != null) { Debug.Assert(MutexEx.Held(mutexOpen)); MutexEx.sqlite3_mutex_leave(mutexOpen); } return rc; }
// 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); }