internal RC getOverflowPage(Pgno ovfl, out MemPage ppPage, out Pgno pPgnoNext) { Pgno next = 0; MemPage pPage = null; ppPage = null; var rc = RC.OK; Debug.Assert(MutexEx.Held(this.Mutex)); // Debug.Assert( pPgnoNext != 0); #if !SQLITE_OMIT_AUTOVACUUM // Try to find the next page in the overflow list using the autovacuum pointer-map pages. Guess that the next page in // the overflow list is page number (ovfl+1). If that guess turns out to be wrong, fall back to loading the data of page // number ovfl to determine the next page number. if (this.AutoVacuum) { Pgno pgno = 0; Pgno iGuess = ovfl + 1; PTRMAP eType = 0; while (MemPage.PTRMAP_ISPAGE(this, iGuess) || iGuess == MemPage.PENDING_BYTE_PAGE(this)) { iGuess++; } if (iGuess <= btreePagecount()) { rc = ptrmapGet(iGuess, ref eType, ref pgno); if (rc == RC.OK && eType == PTRMAP.OVERFLOW2 && pgno == ovfl) { next = iGuess; rc = RC.DONE; } } } #endif Debug.Assert(next == 0 || rc == RC.DONE); if (rc == RC.OK) { rc = btreeGetPage(ovfl, ref pPage, 0); Debug.Assert(rc == RC.OK || pPage == null); if (rc == RC.OK) { next = ConvertEx.Get4(pPage.Data); } } pPgnoNext = next; if (ppPage != null) { ppPage = pPage; } else { pPage.releasePage(); } return(rc == RC.DONE ? RC.OK : rc); }
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 Pgno ptrmapPageno(Pgno pgno) { Debug.Assert(MutexEx.Held(this.Mutex)); if (pgno < 2) { return(0); } var nPagesPerMapPage = (int)(this.UsableSize / 5 + 1); var iPtrMap = (Pgno)((pgno - 2) / nPagesPerMapPage); var ret = (Pgno)(iPtrMap * nPagesPerMapPage) + 2; if (ret == MemPage.PENDING_BYTE_PAGE(this)) { ret++; } return(ret); }
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 btreeDropTable(Pgno iTable, ref int piMoved) { MemPage pPage = null; var pBt = this.Shared; Debug.Assert(sqlite3BtreeHoldsMutex()); Debug.Assert(this.InTransaction == TRANS.WRITE); // It is illegal to drop a table if any cursors are open on the database. This is because in auto-vacuum mode the backend may // need to move another root-page to fill a gap left by the deleted root page. If an open cursor was using this page a problem would occur. // This error is caught long before control reaches this point. if (Check.NEVER(pBt.Cursors) != null) { sqlite3b.sqlite3ConnectionBlocked(this.DB, pBt.Cursors.Tree.DB); return(RC.LOCKED_SHAREDCACHE); } var rc = pBt.btreeGetPage((Pgno)iTable, ref pPage, 0); if (rc != RC.OK) { return(rc); } var dummy0 = 0; rc = ClearTable((int)iTable, ref dummy0); if (rc != RC.OK) { pPage.releasePage(); return(rc); } piMoved = 0; if (iTable > 1) { #if SQLITE_OMIT_AUTOVACUUM freePage(pPage, ref rc); releasePage(pPage); #else if (pBt.AutoVacuum) { Pgno maxRootPgno = 0; GetMeta((int)META.LARGEST_ROOT_PAGE, ref maxRootPgno); if (iTable == maxRootPgno) { // If the table being dropped is the table with the largest root-page number in the database, put the root page on the free list. pPage.freePage(ref rc); pPage.releasePage(); if (rc != RC.OK) { return(rc); } } else { // The table being dropped does not have the largest root-page number in the database. So move the page that does into the // gap left by the deleted root-page. var pMove = new MemPage(); pPage.releasePage(); rc = pBt.btreeGetPage(maxRootPgno, ref pMove, 0); if (rc != RC.OK) { return(rc); } rc = MemPage.relocatePage(pBt, pMove, PTRMAP.ROOTPAGE, 0, iTable, 0); pMove.releasePage(); if (rc != RC.OK) { return(rc); } pMove = null; rc = pBt.btreeGetPage(maxRootPgno, ref pMove, 0); pMove.freePage(ref rc); pMove.releasePage(); if (rc != RC.OK) { return(rc); } piMoved = (int)maxRootPgno; } // Set the new 'max-root-page' value in the database header. This is the old value less one, less one more if that happens to // be a root-page number, less one again if that is the PENDING_BYTE_PAGE. maxRootPgno--; while (maxRootPgno == MemPage.PENDING_BYTE_PAGE(pBt) || MemPage.PTRMAP_ISPAGE(pBt, maxRootPgno)) { maxRootPgno--; } Debug.Assert(maxRootPgno != MemPage.PENDING_BYTE_PAGE(pBt)); rc = SetMeta(4, maxRootPgno); } else { pPage.freePage(ref rc); pPage.releasePage(); } #endif } else { // If sqlite3BtreeDropTable was called on page 1. This really never should happen except in a corrupt database. pPage.zeroPage(PTF_INTKEY | PTF_LEAF); pPage.releasePage(); } return(rc); }
internal RC btreeCreateTable(ref int piTable, CREATETABLE createTabFlags) { var pBt = this.Shared; var pRoot = new MemPage(); Pgno pgnoRoot = 0; int ptfFlags; // Page-type flage for the root page of new table RC rc; Debug.Assert(sqlite3BtreeHoldsMutex()); Debug.Assert(pBt.InTransaction == TRANS.WRITE); Debug.Assert(!pBt.ReadOnly); #if SQLITE_OMIT_AUTOVACUUM rc = allocateBtreePage(pBt, ref pRoot, ref pgnoRoot, 1, 0); if (rc != SQLITE.OK) { return(rc); } #else if (pBt.AutoVacuum) { Pgno pgnoMove = 0; // Move a page here to make room for the root-page var pPageMove = new MemPage(); // The page to move to. // Creating a new table may probably require moving an existing database to make room for the new tables root page. In case this page turns // out to be an overflow page, delete all overflow page-map caches held by open cursors. invalidateAllOverflowCache(pBt); // Read the value of meta[3] from the database to determine where the root page of the new table should go. meta[3] is the largest root-page // created so far, so the new root-page is (meta[3]+1). GetMeta((int)META.LARGEST_ROOT_PAGE, ref pgnoRoot); pgnoRoot++; // The new root-page may not be allocated on a pointer-map page, or the PENDING_BYTE page. while (pgnoRoot == MemPage.PTRMAP_PAGENO(pBt, pgnoRoot) || pgnoRoot == MemPage.PENDING_BYTE_PAGE(pBt)) { pgnoRoot++; } Debug.Assert(pgnoRoot >= 3); // Allocate a page. The page that currently resides at pgnoRoot will be moved to the allocated page (unless the allocated page happens to reside at pgnoRoot). rc = pBt.allocateBtreePage(ref pPageMove, ref pgnoMove, pgnoRoot, 1); if (rc != RC.OK) { return(rc); } if (pgnoMove != pgnoRoot) { // pgnoRoot is the page that will be used for the root-page of the new table (assuming an error did not occur). But we were // allocated pgnoMove. If required (i.e. if it was not allocated by extending the file), the current page at position pgnoMove // is already journaled. PTRMAP eType = 0; Pgno iPtrPage = 0; pPageMove.releasePage(); // Move the page currently at pgnoRoot to pgnoMove. rc = pBt.btreeGetPage(pgnoRoot, ref pRoot, 0); if (rc != RC.OK) { return(rc); } rc = pBt.ptrmapGet(pgnoRoot, ref eType, ref iPtrPage); if (eType == PTRMAP.ROOTPAGE || eType == PTRMAP.FREEPAGE) { rc = SysEx.SQLITE_CORRUPT_BKPT(); } if (rc != RC.OK) { pRoot.releasePage(); return(rc); } Debug.Assert(eType != PTRMAP.ROOTPAGE); Debug.Assert(eType != PTRMAP.FREEPAGE); rc = MemPage.relocatePage(pBt, pRoot, eType, iPtrPage, pgnoMove, 0); pRoot.releasePage(); // Obtain the page at pgnoRoot if (rc != RC.OK) { return(rc); } rc = pBt.btreeGetPage(pgnoRoot, ref pRoot, 0); if (rc != RC.OK) { return(rc); } rc = Pager.Write(pRoot.DbPage); if (rc != RC.OK) { pRoot.releasePage(); return(rc); } } else { pRoot = pPageMove; } // Update the pointer-map and meta-data with the new root-page number. pBt.ptrmapPut(pgnoRoot, PTRMAP.ROOTPAGE, 0, ref rc); if (rc != RC.OK) { pRoot.releasePage(); return(rc); } // When the new root page was allocated, page 1 was made writable in order either to increase the database filesize, or to decrement the // freelist count. Hence, the sqlite3BtreeUpdateMeta() call cannot fail. Debug.Assert(Pager.IsPageWriteable(pBt.Page1.DbPage)); rc = SetMeta(4, pgnoRoot); if (Check.NEVER(rc != RC.OK)) { pRoot.releasePage(); return(rc); } } else { rc = pBt.allocateBtreePage(ref pRoot, ref pgnoRoot, 1, 0); if (rc != RC.OK) { return(rc); } } #endif Debug.Assert(Pager.IsPageWriteable(pRoot.DbPage)); ptfFlags = ((createTabFlags & CREATETABLE.INTKEY) != 0 ? PTF_INTKEY | PTF_LEAFDATA | PTF_LEAF : PTF_ZERODATA | PTF_LEAF); pRoot.zeroPage(ptfFlags); Pager.Unref(pRoot.DbPage); Debug.Assert((pBt.OpenFlags & OPEN.SINGLE) == 0 || pgnoRoot == 2); piTable = (int)pgnoRoot; return(RC.OK); }