// was:moveToChild private RC MoveToChild(Pgno newID) { var id = PageID; var newPage = new MemPage(); Debug.Assert(HoldsMutex()); Debug.Assert(State == CursorState.VALID); Debug.Assert(PageID < BTCURSOR_MAX_DEPTH); if (PageID >= (BTCURSOR_MAX_DEPTH - 1)) { return(SysEx.SQLITE_CORRUPT_BKPT()); } var rc = Shared.getAndInitPage(newID, ref newPage); if (rc != RC.OK) { return(rc); } Pages[id + 1] = newPage; PagesIndexs[id + 1] = 0; PageID++; Info.nSize = 0; ValidNKey = false; if (newPage.Cells < 1 || newPage.HasIntKey != Pages[id].HasIntKey) { return(SysEx.SQLITE_CORRUPT_BKPT()); } return(RC.OK); }
internal MemPage btreePageLookup(Pgno pgno) { Debug.Assert(MutexEx.Held(this.Mutex)); var pDbPage = this.Pager.Lookup(pgno); return(pDbPage ? MemPage.btreePageFromDbPage(pDbPage, pgno, this) : null); }
internal RC ptrmapGet(Pgno key, ref PTRMAP pEType, ref Pgno pPgno) { Debug.Assert(MutexEx.Held(this.Mutex)); var iPtrmap = (int)MemPage.PTRMAP_PAGENO(this, key); var pDbPage = new PgHdr(); // The pointer map page var rc = this.Pager.Get((Pgno)iPtrmap, ref pDbPage); if (rc != RC.OK) { return(rc); } var pPtrmap = Pager.sqlite3PagerGetData(pDbPage);// Pointer map page data var offset = (int)MemPage.PTRMAP_PTROFFSET((Pgno)iPtrmap, key); if (offset < 0) { Pager.Unref(pDbPage); return(SysEx.SQLITE_CORRUPT_BKPT()); } Debug.Assert(offset <= (int)this.UsableSize - 5); var v = pPtrmap[offset]; if (v < 1 || v > 5) { return(SysEx.SQLITE_CORRUPT_BKPT()); } pEType = (PTRMAP)v; pPgno = ConvertEx.Get4(pPtrmap, offset + 1); Pager.Unref(pDbPage); return(RC.OK); }
static int NB = (NN * 2 + 1); // Total pages involved in the balance #endregion Fields #if !SQLITE_OMIT_QUICKBALANCE internal static RC balance_quick(MemPage parentPage, MemPage page, byte[] space) { Debug.Assert(MutexEx.Held(page.Shared.Mutex)); Debug.Assert(Pager.IsPageWriteable(parentPage.DbPage)); Debug.Assert(page.NOverflows == 1); // This error condition is now caught prior to reaching this function if (page.Cells <= 0) return SysEx.SQLITE_CORRUPT_BKPT(); // Allocate a new page. This page will become the right-sibling of pPage. Make the parent page writable, so that the new divider cell // may be inserted. If both these operations are successful, proceed. var shared = page.Shared; // B-Tree Database var newPage = new MemPage(); // Newly allocated page Pgno pgnoNew = 0; // Page number of pNew var rc = shared.allocateBtreePage(ref newPage, ref pgnoNew, 0, 0); if (rc != RC.OK) return rc; var pOut = 4; var pCell = page.Overflows[0].Cell; var szCell = new int[1] { page.cellSizePtr(pCell) }; Debug.Assert(Pager.IsPageWriteable(newPage.DbPage)); Debug.Assert(page.Data[0] == (Btree.PTF_INTKEY | Btree.PTF_LEAFDATA | Btree.PTF_LEAF)); newPage.zeroPage(Btree.PTF_INTKEY | Btree.PTF_LEAFDATA | Btree.PTF_LEAF); newPage.assemblePage(1, pCell, szCell); // If this is an auto-vacuum database, update the pointer map with entries for the new page, and any pointer from the // cell on the page to an overflow page. If either of these operations fails, the return code is set, but the contents // of the parent page are still manipulated by thh code below. That is Ok, at this point the parent page is guaranteed to // be marked as dirty. Returning an error code will cause a rollback, undoing any changes made to the parent page. #if !SQLITE_OMIT_AUTOVACUUM if (shared.AutoVacuum) #else if (false) #endif { shared.ptrmapPut(pgnoNew, PTRMAP.BTREE, parentPage.ID, ref rc); if (szCell[0] > newPage.MinLocal) newPage.ptrmapPutOvflPtr(pCell, ref rc); } // Create a divider cell to insert into pParent. The divider cell consists of a 4-byte page number (the page number of pPage) and // a variable length key value (which must be the same value as the largest key on pPage). // To find the largest key value on pPage, first find the right-most cell on pPage. The first two fields of this cell are the // record-length (a variable length integer at most 32-bits in size) and the key value (a variable length integer, may have any value). // The first of the while(...) loops below skips over the record-length field. The second while(...) loop copies the key value from the // cell on pPage into the pSpace buffer. var iCell = page.FindCell(page.Cells - 1); pCell = page.Data; var _pCell = iCell; var pStop = _pCell + 9; while (((pCell[_pCell++]) & 0x80) != 0 && _pCell < pStop) ; pStop = _pCell + 9; while (((space[pOut++] = pCell[_pCell++]) & 0x80) != 0 && _pCell < pStop) ; // Insert the new divider cell into pParent. parentPage.insertCell(parentPage.Cells, space, pOut, null, page.ID, ref rc); // Set the right-child pointer of pParent to point to the new page. ConvertEx.Put4L(parentPage.Data, parentPage.HeaderOffset + 8, pgnoNew); // Release the reference to the new page. newPage.releasePage(); return rc; }
internal RC clearDatabasePage(Pgno pgno, int freePageFlag, ref int pnChange) { var pPage = new MemPage(); Debug.Assert(MutexEx.Held(this.Mutex)); if (pgno > btreePagecount()) { return(SysEx.SQLITE_CORRUPT_BKPT()); } var rc = getAndInitPage(pgno, ref pPage); if (rc != RC.OK) { return(rc); } for (var i = 0; i < pPage.Cells; i++) { var iCell = pPage.FindCell(i); var pCell = pPage.Data; if (pPage.Leaf == 0) { rc = clearDatabasePage(ConvertEx.Get4(pCell, iCell), 1, ref pnChange); if (rc != RC.OK) { goto cleardatabasepage_out; } } rc = pPage.clearCell(iCell); if (rc != RC.OK) { goto cleardatabasepage_out; } } if (pPage.Leaf == 0) { rc = clearDatabasePage(ConvertEx.Get4(pPage.Data, 8), 1, ref pnChange); if (rc != RC.OK) { goto cleardatabasepage_out; } } else { pnChange += pPage.Cells; } if (freePageFlag != 0) { pPage.freePage(ref rc); } else if ((rc = Pager.Write(pPage.DbPage)) == RC.OK) { pPage.zeroPage(pPage.Data[0] | Btree.PTF_LEAF); } cleardatabasepage_out: pPage.releasePage(); return(rc); }
internal RC btreeGetPage(Pgno pgno, ref MemPage ppPage, int noContent) { Debug.Assert(MutexEx.Held(this.Mutex)); DbPage pDbPage = null; var rc = this.Pager.Get(pgno, ref pDbPage, (byte)noContent); if (rc != RC.OK) return rc; ppPage = MemPage.btreePageFromDbPage(pDbPage, pgno, this); return RC.OK; }
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 static void assertParentIndex(MemPage pParent, int iIdx, Pgno iChild) { Debug.Assert(iIdx <= pParent.Cells); if (iIdx == pParent.Cells) { Debug.Assert(ConvertEx.Get4(pParent.Data, pParent.HeaderOffset + 8) == iChild); } else { Debug.Assert(ConvertEx.Get4(pParent.Data, pParent.FindCell(iIdx)) == iChild); } }
// was:moveToParent private void MoveToParent() { Debug.Assert(HoldsMutex()); Debug.Assert(State == CursorState.VALID); Debug.Assert(PageID > 0); Debug.Assert(Pages[PageID] != null); MemPage.assertParentIndex(Pages[PageID - 1], PagesIndexs[PageID - 1], Pages[PageID].ID); Pages[PageID].releasePage(); PageID--; Info.nSize = 0; ValidNKey = false; }
internal RC btreeGetPage(Pgno pgno, ref MemPage ppPage, int noContent) { Debug.Assert(MutexEx.Held(this.Mutex)); DbPage pDbPage = null; var rc = this.Pager.Get(pgno, ref pDbPage, (byte)noContent); if (rc != RC.OK) { return(rc); } ppPage = MemPage.btreePageFromDbPage(pDbPage, pgno, this); return(RC.OK); }
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 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); }
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:moveToRightmost private RC MoveToRightmost() { Debug.Assert(HoldsMutex()); Debug.Assert(State == CursorState.VALID); var rc = RC.OK; MemPage page = null; while (rc == RC.OK && (page = Pages[PageID]).Leaf == 0) { var pgno = (Pgno)ConvertEx.Get4(page.Data, page.HeaderOffset + 8); PagesIndexs[PageID] = page.Cells; rc = MoveToChild(pgno); } if (rc == RC.OK) { PagesIndexs[PageID] = (ushort)(page.Cells - 1); Info.nSize = 0; ValidNKey = false; } return(rc); }
internal static void copyNodeContent(MemPage pFrom, MemPage pTo, ref RC pRC) { if (pRC != RC.OK) { return; } var pBt = pFrom.Shared; var aFrom = pFrom.Data; var aTo = pTo.Data; var iFromHdr = pFrom.HeaderOffset; var iToHdr = (pTo.ID == 1 ? 100 : 0); Debug.Assert(pFrom.HasInit); Debug.Assert(pFrom.FreeBytes >= iToHdr); Debug.Assert(ConvertEx.Get2(aFrom, iFromHdr + 5) <= (int)pBt.UsableSize); // Copy the b-tree node content from page pFrom to page pTo. var iData = (int)ConvertEx.Get2(aFrom, iFromHdr + 5); Buffer.BlockCopy(aFrom, iData, aTo, iData, (int)pBt.UsableSize - iData); Buffer.BlockCopy(aFrom, iFromHdr, aTo, iToHdr, pFrom.CellOffset + 2 * pFrom.Cells); // Reinitialize page pTo so that the contents of the MemPage structure match the new data. The initialization of pTo can actually fail under // fairly obscure circumstances, even though it is a copy of initialized page pFrom. pTo.HasInit = false; var rc = pTo.btreeInitPage(); if (rc != RC.OK) { pRC = rc; return; } // If this is an auto-vacuum database, update the pointer-map entries for any b-tree or overflow pages that pTo now contains the pointers to. #if !SQLITE_OMIT_AUTOVACUUM if (pBt.AutoVacuum) #else if (false) #endif { pRC = pTo.setChildPtrmaps(); } }
internal RC clearDatabasePage(Pgno pgno, int freePageFlag, ref int pnChange) { var pPage = new MemPage(); Debug.Assert(MutexEx.Held(this.Mutex)); if (pgno > btreePagecount()) return SysEx.SQLITE_CORRUPT_BKPT(); var rc = getAndInitPage(pgno, ref pPage); if (rc != RC.OK) return rc; for (var i = 0; i < pPage.Cells; i++) { var iCell = pPage.FindCell(i); var pCell = pPage.Data; if (pPage.Leaf == 0) { rc = clearDatabasePage(ConvertEx.Get4(pCell, iCell), 1, ref pnChange); if (rc != RC.OK) goto cleardatabasepage_out; } rc = pPage.clearCell(iCell); if (rc != RC.OK) goto cleardatabasepage_out; } if (pPage.Leaf == 0) { rc = clearDatabasePage(ConvertEx.Get4(pPage.Data, 8), 1, ref pnChange); if (rc != RC.OK) goto cleardatabasepage_out; } else pnChange += pPage.Cells; if (freePageFlag != 0) pPage.freePage(ref rc); else if ((rc = Pager.Write(pPage.DbPage)) == RC.OK) pPage.zeroPage(pPage.Data[0] | Btree.PTF_LEAF); cleardatabasepage_out: pPage.releasePage(); return rc; }
internal RC getAndInitPage(Pgno pgno, ref MemPage ppPage) { Debug.Assert(MutexEx.Held(this.Mutex)); RC rc; if (pgno > btreePagecount()) { rc = SysEx.SQLITE_CORRUPT_BKPT(); } else { rc = btreeGetPage(pgno, ref ppPage, 0); if (rc == RC.OK) { rc = ppPage.btreeInitPage(); if (rc != RC.OK) { ppPage.releasePage(); } } } Debug.Assert(pgno != 0 || rc == RC.CORRUPT); return(rc); }
// was:fetchPayload private RC AccessPayload(uint offset, uint size, byte[] b, bool writeOperation) { var page = Pages[PageID]; // Btree page of current entry Debug.Assert(page != null); Debug.Assert(State == CursorState.VALID); Debug.Assert(PagesIndexs[PageID] < page.Cells); Debug.Assert(HoldsMutex()); GetCellInfo(); var payload = Info.Cells; var nKey = (uint)(page.HasIntKey ? 0 : (int)Info.nKey); var shared = Shared; // Btree this cursor belongs to if (Check.NEVER(offset + size > nKey + Info.nData) || Info.nLocal > shared.UsableSize) { // Trying to read or write past the end of the data is an error return(SysEx.SQLITE_CORRUPT_BKPT()); } // Check if data must be read/written to/from the btree page itself. var rc = RC.OK; uint bOffset = 0; if (offset < Info.nLocal) { var a = (int)size; if (a + offset > Info.nLocal) { a = (int)(Info.nLocal - offset); } rc = CopyPayload(page.DbPage, payload, (uint)(offset + Info.CellID + Info.nHeader), b, bOffset, (uint)a, writeOperation); offset = 0; bOffset += (uint)a; size -= (uint)a; } else { offset -= Info.nLocal; } var iIdx = 0; if (rc == RC.OK && size > 0) { var ovflSize = (uint)(shared.UsableSize - 4); // Bytes content per ovfl page var nextPage = (Pgno)ConvertEx.Get4(payload, Info.nLocal + Info.CellID + Info.nHeader); #if !SQLITE_OMIT_INCRBLOB // If the isIncrblobHandle flag is set and the BtCursor.aOverflow[] has not been allocated, allocate it now. The array is sized at // one entry for each overflow page in the overflow chain. The page number of the first overflow page is stored in aOverflow[0], // etc. A value of 0 in the aOverflow[] array means "not yet known" (the cache is lazily populated). if (IsIncrblob && OverflowIDs == null) { var nOvfl = (Info.nPayload - Info.nLocal + ovflSize - 1) / ovflSize; OverflowIDs = new Pgno[nOvfl]; } // If the overflow page-list cache has been allocated and the entry for the first required overflow page is valid, skip directly to it. if (OverflowIDs != null && OverflowIDs[offset / ovflSize] != 0) { iIdx = (int)(offset / ovflSize); nextPage = OverflowIDs[iIdx]; offset = (offset % ovflSize); } #endif for (; rc == RC.OK && size > 0 && nextPage != 0; iIdx++) { #if !SQLITE_OMIT_INCRBLOB // If required, populate the overflow page-list cache. if (OverflowIDs != null) { Debug.Assert(OverflowIDs[iIdx] == 0 || OverflowIDs[iIdx] == nextPage); OverflowIDs[iIdx] = nextPage; } #endif MemPage MemPageDummy = null; if (offset >= ovflSize) { // The only reason to read this page is to obtain the page number for the next page in the overflow chain. The page // data is not required. So first try to lookup the overflow page-list cache, if any, then fall back to the getOverflowPage() function. #if !SQLITE_OMIT_INCRBLOB if (OverflowIDs != null && OverflowIDs[iIdx + 1] != 0) { nextPage = OverflowIDs[iIdx + 1]; } else #endif rc = shared.getOverflowPage(nextPage, out MemPageDummy, out nextPage); offset -= ovflSize; } else { // Need to read this page properly. It contains some of the range of data that is being read (eOp==null) or written (eOp!=null). var pDbPage = new PgHdr(); var a = (int)size; rc = shared.Pager.Get(nextPage, ref pDbPage); if (rc == RC.OK) { payload = Pager.sqlite3PagerGetData(pDbPage); nextPage = ConvertEx.Get4(payload); if (a + offset > ovflSize) { a = (int)(ovflSize - offset); } rc = CopyPayload(pDbPage, payload, offset + 4, b, bOffset, (uint)a, writeOperation); Pager.Unref(pDbPage); offset = 0; size -= (uint)a; bOffset += (uint)a; } } } } if (rc == RC.OK && size > 0) { return(SysEx.SQLITE_CORRUPT_BKPT()); } return(rc); }
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 static void assertParentIndex(MemPage pParent, int iIdx, Pgno iChild) { Debug.Assert(iIdx <= pParent.Cells); if (iIdx == pParent.Cells) Debug.Assert(ConvertEx.Get4(pParent.Data, pParent.HeaderOffset + 8) == iChild); else Debug.Assert(ConvertEx.Get4(pParent.Data, pParent.FindCell(iIdx)) == iChild); }
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 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); }
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; }
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; }
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 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; }
internal static void assertParentIndex(MemPage pParent, int iIdx, Pgno iChild) { }
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); }
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 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 getAndInitPage(Pgno pgno, ref MemPage ppPage) { Debug.Assert(MutexEx.Held(this.Mutex)); RC rc; if (pgno > btreePagecount()) rc = SysEx.SQLITE_CORRUPT_BKPT(); else { rc = btreeGetPage(pgno, ref ppPage, 0); if (rc == RC.OK) { rc = ppPage.btreeInitPage(); if (rc != RC.OK) ppPage.releasePage(); } } Debug.Assert(pgno != 0 || rc == RC.CORRUPT); return rc; }
internal static RC incrVacuumStep(BtShared pBt, Pgno nFin, Pgno iLastPg) { Pgno nFreeList; // Number of pages still on the free-list Debug.Assert(MutexEx.Held(pBt.Mutex)); Debug.Assert(iLastPg > nFin); if (!PTRMAP_ISPAGE(pBt, iLastPg) && iLastPg != PENDING_BYTE_PAGE(pBt)) { PTRMAP eType = 0; Pgno iPtrPage = 0; nFreeList = ConvertEx.Get4(pBt.Page1.Data, 36); if (nFreeList == 0) { return(RC.DONE); } var rc = pBt.ptrmapGet(iLastPg, ref eType, ref iPtrPage); if (rc != RC.OK) { return(rc); } if (eType == PTRMAP.ROOTPAGE) { return(SysEx.SQLITE_CORRUPT_BKPT()); } if (eType == PTRMAP.FREEPAGE) { if (nFin == 0) { // Remove the page from the files free-list. This is not required if nFin is non-zero. In that case, the free-list will be // truncated to zero after this function returns, so it doesn't matter if it still contains some garbage entries. Pgno iFreePg = 0; var pFreePg = new MemPage(); rc = pBt.allocateBtreePage(ref pFreePg, ref iFreePg, iLastPg, 1); if (rc != RC.OK) { return(rc); } Debug.Assert(iFreePg == iLastPg); pFreePg.releasePage(); } } else { Pgno iFreePg = 0; // Index of free page to move pLastPg to var pLastPg = new MemPage(); rc = pBt.btreeGetPage(iLastPg, ref pLastPg, 0); if (rc != RC.OK) { return(rc); } // If nFin is zero, this loop runs exactly once and page pLastPg is swapped with the first free page pulled off the free list. // On the other hand, if nFin is greater than zero, then keep looping until a free-page located within the first nFin pages of the file is found. do { var pFreePg = new MemPage(); rc = pBt.allocateBtreePage(ref pFreePg, ref iFreePg, 0, 0); if (rc != RC.OK) { pLastPg.releasePage(); return(rc); } pFreePg.releasePage(); } while (nFin != 0 && iFreePg > nFin); Debug.Assert(iFreePg < iLastPg); rc = Pager.Write(pLastPg.DbPage); if (rc == RC.OK) { rc = relocatePage(pBt, pLastPg, eType, iPtrPage, iFreePg, (nFin != 0) ? 1 : 0); } pLastPg.releasePage(); if (rc != RC.OK) { return(rc); } } } if (nFin == 0) { iLastPg--; while (iLastPg == PENDING_BYTE_PAGE(pBt) || PTRMAP_ISPAGE(pBt, iLastPg)) { if (PTRMAP_ISPAGE(pBt, iLastPg)) { var pPg = new MemPage(); var rc = pBt.btreeGetPage(iLastPg, ref pPg, 0); if (rc != RC.OK) { return(rc); } rc = Pager.Write(pPg.DbPage); pPg.releasePage(); if (rc != RC.OK) { return(rc); } } iLastPg--; } pBt.Pager.TruncateImage(iLastPg); pBt.Pages = iLastPg; } return(RC.OK); }
internal RC fillInCell(byte[] pCell, byte[] pKey, long nKey, byte[] pData, int nData, int nZero, ref int pnSize) { Debug.Assert(MutexEx.Held(this.Shared.Mutex)); // pPage is not necessarily writeable since pCell might be auxiliary buffer space that is separate from the pPage buffer area // TODO -- Determine if the following Assert is needed under c# //Debug.Assert( pCell < pPage.aData || pCell >= &pPage.aData[pBt.pageSize] || sqlite3PagerIswriteable(pPage.pDbPage) ); // Fill in the header. var nHeader = 0; if (this.Leaf == 0) { nHeader += 4; } if (this.HasData != 0) { nHeader += (int)ConvertEx.PutVariant9(pCell, (uint)nHeader, (int)(nData + nZero)); } else { nData = nZero = 0; } nHeader += ConvertEx.PutVariant9L(pCell, (uint)nHeader, (ulong)nKey); var info = new CellInfo(); btreeParseCellPtr(pCell, ref info); Debug.Assert(info.nHeader == nHeader); Debug.Assert(info.nKey == nKey); Debug.Assert(info.nData == (uint)(nData + nZero)); // Fill in the payload var nPayload = nData + nZero; byte[] pSrc; int nSrc; if (this.HasIntKey) { pSrc = pData; nSrc = nData; nData = 0; } else { if (Check.NEVER(nKey > 0x7fffffff || pKey == null)) { return(SysEx.SQLITE_CORRUPT_BKPT()); } nPayload += (int)nKey; pSrc = pKey; nSrc = (int)nKey; } pnSize = info.nSize; var spaceLeft = (int)info.nLocal; var pPayload = pCell; var pPayloadIndex = nHeader; var pPrior = pCell; var pPriorIndex = (int)info.iOverflow; var pBt = this.Shared; Pgno pgnoOvfl = 0; MemPage pToRelease = null; while (nPayload > 0) { if (spaceLeft == 0) { #if !SQLITE_OMIT_AUTOVACUUM var pgnoPtrmap = pgnoOvfl; // Overflow page pointer-map entry page if (pBt.AutoVacuum) { do { pgnoOvfl++; }while (PTRMAP_ISPAGE(pBt, pgnoOvfl) || pgnoOvfl == PENDING_BYTE_PAGE(pBt)); } #endif MemPage pOvfl = null; var rc = pBt.allocateBtreePage(ref pOvfl, ref pgnoOvfl, pgnoOvfl, 0); #if !SQLITE_OMIT_AUTOVACUUM // If the database supports auto-vacuum, and the second or subsequent overflow page is being allocated, add an entry to the pointer-map for that page now. // If this is the first overflow page, then write a partial entry to the pointer-map. If we write nothing to this pointer-map slot, // then the optimistic overflow chain processing in clearCell() may misinterpret the uninitialised values and delete the // wrong pages from the database. if (pBt.AutoVacuum && rc == RC.OK) { var eType = (pgnoPtrmap != 0 ? PTRMAP.OVERFLOW2 : PTRMAP.OVERFLOW1); pBt.ptrmapPut(pgnoOvfl, eType, pgnoPtrmap, ref rc); if (rc != RC.OK) { pOvfl.releasePage(); } } #endif if (rc != RC.OK) { pToRelease.releasePage(); return(rc); } // If pToRelease is not zero than pPrior points into the data area of pToRelease. Make sure pToRelease is still writeable. Debug.Assert(pToRelease == null || Pager.IsPageWriteable(pToRelease.DbPage)); // If pPrior is part of the data area of pPage, then make sure pPage is still writeable // TODO -- Determine if the following Assert is needed under c# //Debug.Assert( pPrior < pPage.aData || pPrior >= &pPage.aData[pBt.pageSize] || sqlite3PagerIswriteable(pPage.pDbPage) ); ConvertEx.Put4L(pPrior, (uint)pPriorIndex, pgnoOvfl); pToRelease.releasePage(); pToRelease = pOvfl; pPrior = pOvfl.Data; pPriorIndex = 0; ConvertEx.Put4(pPrior, 0); pPayload = pOvfl.Data; pPayloadIndex = 4; spaceLeft = (int)pBt.UsableSize - 4; } var n = nPayload; if (n > spaceLeft) { n = spaceLeft; } // If pToRelease is not zero than pPayload points into the data area of pToRelease. Make sure pToRelease is still writeable. Debug.Assert(pToRelease == null || Pager.IsPageWriteable(pToRelease.DbPage)); // If pPayload is part of the data area of pPage, then make sure pPage is still writeable // TODO -- Determine if the following Assert is needed under c# //Debug.Assert( pPayload < pPage.aData || pPayload >= &pPage.aData[pBt.pageSize] || sqlite3PagerIswriteable(pPage.pDbPage) ); var pSrcIndex = 0; if (nSrc > 0) { if (n > nSrc) { n = nSrc; } Debug.Assert(pSrc != null); Buffer.BlockCopy(pSrc, pSrcIndex, pPayload, pPayloadIndex, n); } else { var pZeroBlob = MallocEx.sqlite3Malloc(n); Buffer.BlockCopy(pZeroBlob, 0, pPayload, pPayloadIndex, n); } nPayload -= n; pPayloadIndex += n; pSrcIndex += n; nSrc -= n; spaceLeft -= n; if (nSrc == 0) { nSrc = nData; pSrc = pData; } } pToRelease.releasePage(); return(RC.OK); }
static int NB = (NN * 2 + 1); // Total pages involved in the balance #if !SQLITE_OMIT_QUICKBALANCE internal static RC balance_quick(MemPage parentPage, MemPage page, byte[] space) { Debug.Assert(MutexEx.Held(page.Shared.Mutex)); Debug.Assert(Pager.IsPageWriteable(parentPage.DbPage)); Debug.Assert(page.NOverflows == 1); // This error condition is now caught prior to reaching this function if (page.Cells <= 0) { return(SysEx.SQLITE_CORRUPT_BKPT()); } // Allocate a new page. This page will become the right-sibling of pPage. Make the parent page writable, so that the new divider cell // may be inserted. If both these operations are successful, proceed. var shared = page.Shared; // B-Tree Database var newPage = new MemPage(); // Newly allocated page Pgno pgnoNew = 0; // Page number of pNew var rc = shared.allocateBtreePage(ref newPage, ref pgnoNew, 0, 0); if (rc != RC.OK) { return(rc); } var pOut = 4; var pCell = page.Overflows[0].Cell; var szCell = new int[1] { page.cellSizePtr(pCell) }; Debug.Assert(Pager.IsPageWriteable(newPage.DbPage)); Debug.Assert(page.Data[0] == (Btree.PTF_INTKEY | Btree.PTF_LEAFDATA | Btree.PTF_LEAF)); newPage.zeroPage(Btree.PTF_INTKEY | Btree.PTF_LEAFDATA | Btree.PTF_LEAF); newPage.assemblePage(1, pCell, szCell); // If this is an auto-vacuum database, update the pointer map with entries for the new page, and any pointer from the // cell on the page to an overflow page. If either of these operations fails, the return code is set, but the contents // of the parent page are still manipulated by thh code below. That is Ok, at this point the parent page is guaranteed to // be marked as dirty. Returning an error code will cause a rollback, undoing any changes made to the parent page. #if !SQLITE_OMIT_AUTOVACUUM if (shared.AutoVacuum) #else if (false) #endif { shared.ptrmapPut(pgnoNew, PTRMAP.BTREE, parentPage.ID, ref rc); if (szCell[0] > newPage.MinLocal) { newPage.ptrmapPutOvflPtr(pCell, ref rc); } } // Create a divider cell to insert into pParent. The divider cell consists of a 4-byte page number (the page number of pPage) and // a variable length key value (which must be the same value as the largest key on pPage). // To find the largest key value on pPage, first find the right-most cell on pPage. The first two fields of this cell are the // record-length (a variable length integer at most 32-bits in size) and the key value (a variable length integer, may have any value). // The first of the while(...) loops below skips over the record-length field. The second while(...) loop copies the key value from the // cell on pPage into the pSpace buffer. var iCell = page.FindCell(page.Cells - 1); pCell = page.Data; var _pCell = iCell; var pStop = _pCell + 9; while (((pCell[_pCell++]) & 0x80) != 0 && _pCell < pStop) { ; } pStop = _pCell + 9; while (((space[pOut++] = pCell[_pCell++]) & 0x80) != 0 && _pCell < pStop) { ; } // Insert the new divider cell into pParent. parentPage.insertCell(parentPage.Cells, space, pOut, null, page.ID, ref rc); // Set the right-child pointer of pParent to point to the new page. ConvertEx.Put4L(parentPage.Data, parentPage.HeaderOffset + 8, pgnoNew); // Release the reference to the new page. newPage.releasePage(); 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); }
static i64 refNULL = 0; //Dummy for C# ref NULL static int checkTreePage( IntegrityCk pCheck, /* Context for the sanity check */ int iPage, /* Page number of the page to check */ string zParentContext, /* Parent context */ ref i64 pnParentMinKey, ref i64 pnParentMaxKey, object _pnParentMinKey, /* C# Needed to determine if content passed*/ object _pnParentMaxKey /* C# Needed to determine if content passed*/ ) { MemPage pPage = new MemPage(); int i, rc, depth, d2, pgno, cnt; int hdr, cellStart; int nCell; u8[] data; BtShared pBt; int usableSize; StringBuilder zContext = new StringBuilder(100); byte[] hit = null; i64 nMinKey = 0; i64 nMaxKey = 0; sqlite3_snprintf(200, zContext, "Page %d: ", iPage); /* Check that the page exists */ pBt = pCheck.pBt; usableSize = (int)pBt.usableSize; if (iPage == 0) { return(0); } if (checkRef(pCheck, (u32)iPage, zParentContext) != 0) { return(0); } if ((rc = btreeGetPage(pBt, (Pgno)iPage, ref pPage, 0)) != 0) { checkAppendMsg(pCheck, zContext.ToString(), "unable to get the page. error code=%d", rc); return(0); } /* Clear MemPage.isInit to make sure the corruption detection code in ** btreeInitPage() is executed. */ pPage.isInit = 0; if ((rc = btreeInitPage(pPage)) != 0) { Debug.Assert(rc == SQLITE_CORRUPT); /* The only possible error from InitPage */ checkAppendMsg(pCheck, zContext.ToString(), "btreeInitPage() returns error code %d", rc); releasePage(pPage); return(0); } /* Check out all the cells. */ depth = 0; for (i = 0; i < pPage.nCell && pCheck.mxErr != 0; i++) { u8[] pCell; u32 sz; CellInfo info = new CellInfo(); /* Check payload overflow pages */ sqlite3_snprintf(200, zContext, "On tree page %d cell %d: ", iPage, i); int iCell = findCell(pPage, i); //pCell = findCell( pPage, i ); pCell = pPage.aData; btreeParseCellPtr(pPage, iCell, ref info); //btreeParseCellPtr( pPage, pCell, info ); sz = info.nData; if (0 == pPage.intKey) { sz += (u32)info.nKey; } /* For intKey pages, check that the keys are in order. */ else if (i == 0) { nMinKey = nMaxKey = info.nKey; } else { if (info.nKey <= nMaxKey) { checkAppendMsg(pCheck, zContext.ToString(), "Rowid %lld out of order (previous was %lld)", info.nKey, nMaxKey); } nMaxKey = info.nKey; } Debug.Assert(sz == info.nPayload); if ((sz > info.nLocal) //&& (pCell[info.iOverflow]<=&pPage.aData[pBt.usableSize]) ) { int nPage = (int)(sz - info.nLocal + usableSize - 5) / (usableSize - 4); Pgno pgnoOvfl = sqlite3Get4byte(pCell, iCell, info.iOverflow); #if !SQLITE_OMIT_AUTOVACUUM if (pBt.autoVacuum) { checkPtrmap(pCheck, pgnoOvfl, PTRMAP_OVERFLOW1, (u32)iPage, zContext.ToString()); } #endif checkList(pCheck, 0, (int)pgnoOvfl, nPage, zContext.ToString()); } /* Check sanity of left child page. */ if (0 == pPage.leaf) { pgno = (int)sqlite3Get4byte(pCell, iCell); //sqlite3Get4byte( pCell ); #if !SQLITE_OMIT_AUTOVACUUM if (pBt.autoVacuum) { checkPtrmap(pCheck, (u32)pgno, PTRMAP_BTREE, (u32)iPage, zContext.ToString()); } #endif if (i == 0) { d2 = checkTreePage(pCheck, pgno, zContext.ToString(), ref nMinKey, ref refNULL, pCheck, null); } else { d2 = checkTreePage(pCheck, pgno, zContext.ToString(), ref nMinKey, ref nMaxKey, pCheck, pCheck); } if (i > 0 && d2 != depth) { checkAppendMsg(pCheck, zContext, "Child page depth differs"); } depth = d2; } } if (0 == pPage.leaf) { pgno = (int)sqlite3Get4byte(pPage.aData, pPage.hdrOffset + 8); sqlite3_snprintf(200, zContext, "On page %d at right child: ", iPage); #if !SQLITE_OMIT_AUTOVACUUM if (pBt.autoVacuum) { checkPtrmap(pCheck, (u32)pgno, PTRMAP_BTREE, (u32)iPage, zContext.ToString()); } #endif // checkTreePage(pCheck, pgno, zContext, NULL, !pPage->nCell ? NULL : &nMaxKey); if (0 == pPage.nCell) { checkTreePage(pCheck, pgno, zContext.ToString(), ref refNULL, ref refNULL, null, null); } else { checkTreePage(pCheck, pgno, zContext.ToString(), ref refNULL, ref nMaxKey, null, pCheck); } } /* For intKey leaf pages, check that the min/max keys are in order ** with any left/parent/right pages. */ if (pPage.leaf != 0 && pPage.intKey != 0) { /* if we are a left child page */ if (_pnParentMinKey != null) { /* if we are the left most child page */ if (_pnParentMaxKey == null) { if (nMaxKey > pnParentMinKey) { checkAppendMsg(pCheck, zContext, "Rowid %lld out of order (max larger than parent min of %lld)", nMaxKey, pnParentMinKey); } } else { if (nMinKey <= pnParentMinKey) { checkAppendMsg(pCheck, zContext, "Rowid %lld out of order (min less than parent min of %lld)", nMinKey, pnParentMinKey); } if (nMaxKey > pnParentMaxKey) { checkAppendMsg(pCheck, zContext, "Rowid %lld out of order (max larger than parent max of %lld)", nMaxKey, pnParentMaxKey); } pnParentMinKey = nMaxKey; } /* else if we're a right child page */ } else if (_pnParentMaxKey != null) { if (nMinKey <= pnParentMaxKey) { checkAppendMsg(pCheck, zContext, "Rowid %lld out of order (min less than parent max of %lld)", nMinKey, pnParentMaxKey); } } } /* Check for complete coverage of the page */ data = pPage.aData; hdr = pPage.hdrOffset; hit = sqlite3Malloc(pBt.pageSize); //if( hit==null ){ // pCheck.mallocFailed = 1; //}else { int contentOffset = get2byteNotZero(data, hdr + 5); Debug.Assert(contentOffset <= usableSize); /* Enforced by btreeInitPage() */ Array.Clear(hit, contentOffset, usableSize - contentOffset); //memset(hit+contentOffset, 0, usableSize-contentOffset); for (int iLoop = contentOffset - 1; iLoop >= 0; iLoop--) { hit[iLoop] = 1;//memset(hit, 1, contentOffset); } nCell = get2byte(data, hdr + 3); cellStart = hdr + 12 - 4 * pPage.leaf; for (i = 0; i < nCell; i++) { int pc = get2byte(data, cellStart + i * 2); u32 size = 65536; int j; if (pc <= usableSize - 4) { size = cellSizePtr(pPage, data, pc); } if ((int)(pc + size - 1) >= usableSize) { checkAppendMsg(pCheck, "", "Corruption detected in cell %d on page %d", i, iPage); } else { for (j = (int)(pc + size - 1); j >= pc; j--) { hit[j]++; } } } i = get2byte(data, hdr + 1); while (i > 0) { int size, j; Debug.Assert(i <= usableSize - 4); /* Enforced by btreeInitPage() */ size = get2byte(data, i + 2); Debug.Assert(i + size <= usableSize); /* Enforced by btreeInitPage() */ for (j = i + size - 1; j >= i; j--) { hit[j]++; } j = get2byte(data, i); Debug.Assert(j == 0 || j > i + size); /* Enforced by btreeInitPage() */ Debug.Assert(j <= usableSize - 4); /* Enforced by btreeInitPage() */ i = j; } for (i = cnt = 0; i < usableSize; i++) { if (hit[i] == 0) { cnt++; } else if (hit[i] > 1) { checkAppendMsg(pCheck, "", "Multiple uses for byte %d of page %d", i, iPage); break; } } if (cnt != data[hdr + 7]) { checkAppendMsg(pCheck, "", "Fragmentation of %d bytes reported as %d on page %d", cnt, data[hdr + 7], iPage); } } sqlite3PageFree(ref hit); releasePage(pPage); return(depth + 1); }
internal RC clearCell(int pCell) { Debug.Assert(MutexEx.Held(this.Shared.Mutex)); var info = new CellInfo(); btreeParseCellPtr(pCell, ref info); if (info.iOverflow == 0) { return(RC.OK); // No overflow pages. Return without doing anything } var pBt = this.Shared; var ovflPgno = (Pgno)ConvertEx.Get4(this.Data, pCell + info.iOverflow); Debug.Assert(pBt.UsableSize > 4); var ovflPageSize = (uint)(pBt.UsableSize - 4); var nOvfl = (int)((info.nPayload - info.nLocal + ovflPageSize - 1) / ovflPageSize); Debug.Assert(ovflPgno == 0 || nOvfl > 0); RC rc; while (nOvfl-- != 0) { Pgno iNext = 0; MemPage pOvfl = null; if (ovflPgno < 2 || ovflPgno > pBt.btreePagecount()) { // 0 is not a legal page number and page 1 cannot be an overflow page. Therefore if ovflPgno<2 or past the end of the file the database must be corrupt. return(SysEx.SQLITE_CORRUPT_BKPT()); } if (nOvfl != 0) { rc = pBt.getOverflowPage(ovflPgno, out pOvfl, out iNext); if (rc != RC.OK) { return(rc); } } if ((pOvfl != null || ((pOvfl = pBt.btreePageLookup(ovflPgno)) != null)) && Pager.GetPageRefCount(pOvfl.DbPage) != 1) { // There is no reason any cursor should have an outstanding reference to an overflow page belonging to a cell that is being deleted/updated. // So if there exists more than one reference to this page, then it must not really be an overflow page and the database must be corrupt. // It is helpful to detect this before calling freePage2(), as freePage2() may zero the page contents if secure-delete mode is // enabled. If this 'overflow' page happens to be a page that the caller is iterating through or using in some other way, this // can be problematic. rc = SysEx.SQLITE_CORRUPT_BKPT(); } else { rc = pBt.freePage2(pOvfl, ovflPgno); } if (pOvfl != null) { Pager.Unref(pOvfl.DbPage); } if (rc != RC.OK) { return(rc); } ovflPgno = iNext; } return(RC.OK); }
internal static void copyNodeContent(MemPage pFrom, MemPage pTo, ref RC pRC) { if (pRC != RC.OK) return; var pBt = pFrom.Shared; var aFrom = pFrom.Data; var aTo = pTo.Data; var iFromHdr = pFrom.HeaderOffset; var iToHdr = (pTo.ID == 1 ? 100 : 0); Debug.Assert(pFrom.HasInit); Debug.Assert(pFrom.FreeBytes >= iToHdr); Debug.Assert(ConvertEx.Get2(aFrom, iFromHdr + 5) <= (int)pBt.UsableSize); // Copy the b-tree node content from page pFrom to page pTo. var iData = (int)ConvertEx.Get2(aFrom, iFromHdr + 5); Buffer.BlockCopy(aFrom, iData, aTo, iData, (int)pBt.UsableSize - iData); Buffer.BlockCopy(aFrom, iFromHdr, aTo, iToHdr, pFrom.CellOffset + 2 * pFrom.Cells); // Reinitialize page pTo so that the contents of the MemPage structure match the new data. The initialization of pTo can actually fail under // fairly obscure circumstances, even though it is a copy of initialized page pFrom. pTo.HasInit = false; var rc = pTo.btreeInitPage(); if (rc != RC.OK) { pRC = rc; return; } // If this is an auto-vacuum database, update the pointer-map entries for any b-tree or overflow pages that pTo now contains the pointers to. #if !SQLITE_OMIT_AUTOVACUUM if (pBt.AutoVacuum) #else if (false) #endif { pRC = pTo.setChildPtrmaps(); } }
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 static RC incrVacuumStep(BtShared pBt, Pgno nFin, Pgno iLastPg) { Pgno nFreeList; // Number of pages still on the free-list Debug.Assert(MutexEx.Held(pBt.Mutex)); Debug.Assert(iLastPg > nFin); if (!PTRMAP_ISPAGE(pBt, iLastPg) && iLastPg != PENDING_BYTE_PAGE(pBt)) { PTRMAP eType = 0; Pgno iPtrPage = 0; nFreeList = ConvertEx.Get4(pBt.Page1.Data, 36); if (nFreeList == 0) return RC.DONE; var rc = pBt.ptrmapGet( iLastPg, ref eType, ref iPtrPage); if (rc != RC.OK) return rc; if (eType == PTRMAP.ROOTPAGE) return SysEx.SQLITE_CORRUPT_BKPT(); if (eType == PTRMAP.FREEPAGE) { if (nFin == 0) { // Remove the page from the files free-list. This is not required if nFin is non-zero. In that case, the free-list will be // truncated to zero after this function returns, so it doesn't matter if it still contains some garbage entries. Pgno iFreePg = 0; var pFreePg = new MemPage(); rc = pBt.allocateBtreePage( ref pFreePg, ref iFreePg, iLastPg, 1); if (rc != RC.OK) return rc; Debug.Assert(iFreePg == iLastPg); pFreePg.releasePage(); } } else { Pgno iFreePg = 0; // Index of free page to move pLastPg to var pLastPg = new MemPage(); rc = pBt.btreeGetPage( iLastPg, ref pLastPg, 0); if (rc != RC.OK) return rc; // If nFin is zero, this loop runs exactly once and page pLastPg is swapped with the first free page pulled off the free list. // On the other hand, if nFin is greater than zero, then keep looping until a free-page located within the first nFin pages of the file is found. do { var pFreePg = new MemPage(); rc = pBt.allocateBtreePage(ref pFreePg, ref iFreePg, 0, 0); if (rc != RC.OK) { pLastPg.releasePage(); return rc; } pFreePg.releasePage(); } while (nFin != 0 && iFreePg > nFin); Debug.Assert(iFreePg < iLastPg); rc = Pager.Write(pLastPg.DbPage); if (rc == RC.OK) rc = relocatePage(pBt, pLastPg, eType, iPtrPage, iFreePg, (nFin != 0) ? 1 : 0); pLastPg.releasePage(); if (rc != RC.OK) return rc; } } if (nFin == 0) { iLastPg--; while (iLastPg == PENDING_BYTE_PAGE(pBt) || PTRMAP_ISPAGE(pBt, iLastPg)) { if (PTRMAP_ISPAGE(pBt, iLastPg)) { var pPg = new MemPage(); var rc = pBt.btreeGetPage(iLastPg, ref pPg, 0); if (rc != RC.OK) return rc; rc = Pager.Write(pPg.DbPage); pPg.releasePage(); if (rc != RC.OK) return rc; } iLastPg--; } pBt.Pager.TruncateImage(iLastPg); pBt.Pages = iLastPg; } return RC.OK; }
// was:balance private RC Balance() { var rc = RC.OK; var nMin = (int)this.Shared.UsableSize * 2 / 3; var balance_quick_called = 0; var balance_deeper_called = 0; do { var pageID = this.PageID; var page = this.Pages[pageID]; if (pageID == 0) { if (page.NOverflows != 0) { // The root page of the b-tree is overfull. In this case call the balance_deeper() function to create a new child for the root-page // and copy the current contents of the root-page to it. The next iteration of the do-loop will balance the child page. Debug.Assert((balance_deeper_called++) == 0); rc = MemPage.balance_deeper(page, ref this.Pages[1]); if (rc == RC.OK) { this.PageID = 1; this.PagesIndexs[0] = 0; this.PagesIndexs[1] = 0; Debug.Assert(this.Pages[1].NOverflows != 0); } } else { break; } } else if (page.NOverflows == 0 && page.FreeBytes <= nMin) { break; } else { var pParent = this.Pages[pageID - 1]; var iIdx = this.PagesIndexs[pageID - 1]; rc = Pager.Write(pParent.DbPage); if (rc == RC.OK) { #if !SQLITE_OMIT_QUICKBALANCE if (page.HasData != 0 && page.NOverflows == 1 && page.Overflows[0].Index == page.Cells && pParent.ID != 1 && pParent.Cells == iIdx) { // Call balance_quick() to create a new sibling of pPage on which to store the overflow cell. balance_quick() inserts a new cell // into pParent, which may cause pParent overflow. If this happens, the next interation of the do-loop will balance pParent // use either balance_nonroot() or balance_deeper(). Until this happens, the overflow cell is stored in the aBalanceQuickSpace[] // buffer. // The purpose of the following Debug.Assert() is to check that only a single call to balance_quick() is made for each call to this // function. If this were not verified, a subtle bug involving reuse of the aBalanceQuickSpace[] might sneak in. Debug.Assert((balance_quick_called++) == 0); rc = MemPage.balance_quick(pParent, page, _balanceQuickSpaces); } else #endif { // In this case, call balance_nonroot() to redistribute cells between pPage and up to 2 of its sibling pages. This involves // modifying the contents of pParent, which may cause pParent to become overfull or underfull. The next iteration of the do-loop // will balance the parent page to correct this. // If the parent page becomes overfull, the overflow cell or cells are stored in the pSpace buffer allocated immediately below. // A subsequent iteration of the do-loop will deal with this by calling balance_nonroot() (balance_deeper() may be called first, // but it doesn't deal with overflow cells - just moves them to a different page). Once this subsequent call to balance_nonroot() // has completed, it is safe to release the pSpace buffer used by the previous call, as the overflow cell data will have been // copied either into the body of a database page or into the new pSpace buffer passed to the latter call to balance_nonroot(). var pSpace = new byte[this.Shared.PageSize];// u8 pSpace = sqlite3PageMalloc( pCur.pBt.pageSize ); rc = MemPage.balance_nonroot(pParent, iIdx, null, pageID == 1 ? 1 : 0); // The pSpace buffer will be freed after the next call to balance_nonroot(), or just before this function returns, whichever comes first. } } page.NOverflows = 0; // The next iteration of the do-loop balances the parent page. page.releasePage(); this.PageID--; } } while (rc == RC.OK); return(rc); }
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); }