Ejemplo n.º 1
0
        internal RC setChildPtrmaps()
        {
            var hasInitLast = HasInit;

            Debug.Assert(MutexEx.Held(Shared.Mutex));
            var rc = btreeInitPage();

            if (rc != RC.OK)
            {
                goto set_child_ptrmaps_out;
            }
            var cells  = Cells; // Number of cells in page pPage
            var shared = Shared;
            var id     = ID;

            for (var i = 0; i < cells; i++)
            {
                var cell = FindCell(i);
                ptrmapPutOvflPtr(cell, ref rc);
                if (Leaf == 0)
                {
                    var childPgno = (Pgno)ConvertEx.Get4(Data, cell);
                    shared.ptrmapPut(childPgno, PTRMAP.BTREE, id, ref rc);
                }
            }
            if (Leaf == 0)
            {
                var childPgno = (Pgno)ConvertEx.Get4(Data, HeaderOffset + 8);
                shared.ptrmapPut(childPgno, PTRMAP.BTREE, id, ref rc);
            }
set_child_ptrmaps_out:
            HasInit = hasInitLast;
            return(rc);
        }
Ejemplo n.º 2
0
        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);
        }
Ejemplo n.º 3
0
        internal static RC autoVacuumCommit(BtShared pBt)
        {
            var rc     = RC.OK;
            var pPager = pBt.Pager;

#if DEBUG
            var nRef = pPager.RefCount;
#else
            var nRef = 0;
#endif
            Debug.Assert(MutexEx.Held(pBt.Mutex));
            Btree.invalidateAllOverflowCache(pBt);
            Debug.Assert(pBt.AutoVacuum);
            if (!pBt.IncrVacuum)
            {
                var nOrig = pBt.btreePagecount(); // Database size before freeing
                if (PTRMAP_ISPAGE(pBt, nOrig) || nOrig == PENDING_BYTE_PAGE(pBt))
                {
                    // It is not possible to create a database for which the final page is either a pointer-map page or the pending-byte page. If one
                    // is encountered, this indicates corruption.
                    return(SysEx.SQLITE_CORRUPT_BKPT());
                }
                var nFree   = (Pgno)ConvertEx.Get4(pBt.Page1.Data, 36);                                    // Number of pages on the freelist initially
                var nEntry  = (int)pBt.UsableSize / 5;                                                     // Number of entries on one ptrmap page
                var nPtrmap = (Pgno)((nFree - nOrig + PTRMAP_PAGENO(pBt, nOrig) + (Pgno)nEntry) / nEntry); // Number of PtrMap pages to be freed
                var nFin    = nOrig - nFree - nPtrmap;                                                     // Number of pages in database after autovacuuming
                if (nOrig > PENDING_BYTE_PAGE(pBt) && nFin < PENDING_BYTE_PAGE(pBt))
                {
                    nFin--;
                }
                while (PTRMAP_ISPAGE(pBt, nFin) || nFin == PENDING_BYTE_PAGE(pBt))
                {
                    nFin--;
                }
                if (nFin > nOrig)
                {
                    return(SysEx.SQLITE_CORRUPT_BKPT());
                }
                for (var iFree = nOrig; iFree > nFin && rc == RC.OK; iFree--)
                {
                    rc = incrVacuumStep(pBt, nFin, iFree);
                }
                if ((rc == RC.DONE || rc == RC.OK) && nFree > 0)
                {
                    rc = Pager.Write(pBt.Page1.DbPage);
                    ConvertEx.Put4(pBt.Page1.Data, 32, 0);
                    ConvertEx.Put4(pBt.Page1.Data, 36, 0);
                    ConvertEx.Put4(pBt.Page1.Data, 28, nFin);
                    pBt.Pager.TruncateImage(nFin);
                    pBt.Pages = nFin;
                }
                if (rc != RC.OK)
                {
                    pPager.Rollback();
                }
            }
            Debug.Assert(nRef == pPager.RefCount);
            return(rc);
        }
Ejemplo n.º 4
0
        public RC ReadByte(int offset, ref uint pRes)
        {
            var ac = new byte[4];
            var rc = this.Read(ac, ac.Length, offset);

            pRes = (rc == RC.OK ? ConvertEx.Get4(ac) : 0);
            return(rc);
        }
Ejemplo n.º 5
0
        public RC Read4(int offset, out uint valueOut)
        {
            var b  = new byte[4];
            var rc = Read(b, b.Length, offset);

            valueOut = (rc == RC.OK ? ConvertEx.Get4(b) : 0);
            return(rc);
        }
Ejemplo n.º 6
0
        // was:sqlite3BtreePrevious
        public RC MovePrevious(ref int pRes)
        {
            Debug.Assert(HoldsMutex());
            var rc = RestorePosition();

            if (rc != RC.OK)
            {
                return(rc);
            }
            AtLast = false;
            if (State == CursorState.INVALID)
            {
                pRes = 1;
                return(RC.OK);
            }
            if (SkipNext < 0)
            {
                SkipNext = 0;
                pRes     = 0;
                return(RC.OK);
            }
            SkipNext = 0;
            //
            var page = Pages[PageID];

            Debug.Assert(page.HasInit);
            if (page.Leaf == 0)
            {
                var index = PagesIndexs[PageID];
                rc = MoveToChild(ConvertEx.Get4(page.Data, page.FindCell(index)));
                if (rc != RC.OK)
                {
                    return(rc);
                }
                rc = MoveToRightmost();
            }
            else
            {
                while (PagesIndexs[PageID] == 0)
                {
                    if (PageID == 0)
                    {
                        State = CursorState.INVALID;
                        pRes  = 1;
                        return(RC.OK);
                    }
                    MoveToParent();
                }
                Info.nSize = 0;
                ValidNKey  = false;
                PagesIndexs[PageID]--;
                page = Pages[PageID];
                rc   = (page.HasIntKey && page.Leaf == 0 ? MovePrevious(ref pRes) : RC.OK);
            }
            pRes = 0;
            return(rc);
        }
Ejemplo n.º 7
0
        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);
        }
Ejemplo n.º 8
0
 internal RC modifyPagePointer(Pgno iFrom, Pgno iTo, PTRMAP eType)
 {
     Debug.Assert(MutexEx.Held(this.Shared.Mutex));
     Debug.Assert(Pager.IsPageWriteable(this.DbPage));
     if (eType == PTRMAP.OVERFLOW2)
     {
         // The pointer is always the first 4 bytes of the page in this case.
         if (ConvertEx.Get4(this.Data) != iFrom)
         {
             return(SysEx.SQLITE_CORRUPT_BKPT());
         }
         ConvertEx.Put4L(this.Data, iTo);
     }
     else
     {
         var isInitOrig = this.HasInit;
         btreeInitPage();
         var nCell = this.Cells;
         int i;
         for (i = 0; i < nCell; i++)
         {
             var pCell = FindCell(i);
             if (eType == PTRMAP.OVERFLOW1)
             {
                 var info = new CellInfo();
                 btreeParseCellPtr(pCell, ref info);
                 if (info.iOverflow != 0)
                 {
                     if (iFrom == ConvertEx.Get4(this.Data, pCell + info.iOverflow))
                     {
                         ConvertEx.Put4(this.Data, pCell + info.iOverflow, (int)iTo);
                         break;
                     }
                 }
             }
             else
             {
                 if (ConvertEx.Get4(this.Data, pCell) == iFrom)
                 {
                     ConvertEx.Put4(this.Data, pCell, (int)iTo);
                     break;
                 }
             }
         }
         if (i == nCell)
         {
             if (eType != PTRMAP.BTREE || ConvertEx.Get4(this.Data, this.HeaderOffset + 8) != iFrom)
             {
                 return(SysEx.SQLITE_CORRUPT_BKPT());
             }
             ConvertEx.Put4L(this.Data, (uint)this.HeaderOffset + 8, iTo);
         }
         this.HasInit = isInitOrig;
     }
     return(RC.OK);
 }
Ejemplo n.º 9
0
        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);
        }
Ejemplo n.º 10
0
        private static void pager_write_changecounter(PgHdr pPg)
        {
            // Increment the value just read and write it back to byte 24.
            uint change_counter = ConvertEx.Get4(pPg.Pager.dbFileVers, 0) + 1;

            ConvertEx.Put4(pPg.Data, 24, change_counter);
            // Also store the SQLite version number in bytes 96..99 and in bytes 92..95 store the change counter for which the version number
            // is valid.
            ConvertEx.Put4(pPg.Data, 92, change_counter);
            ConvertEx.Put4(pPg.Data, 96, SysEx.SQLITE_VERSION_NUMBER);
        }
Ejemplo n.º 11
0
 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);
     }
 }
Ejemplo n.º 12
0
        // was:sqlite3BtreeGetMeta
        public void GetMeta(int idx, ref uint pMeta)
        {
            var pBt = this.Shared;
            sqlite3BtreeEnter();
            Debug.Assert(this.InTransaction > TRANS.NONE);
            Debug.Assert(querySharedCacheTableLock(MASTER_ROOT, BtreeLock.LOCK.READ) == RC.OK);
            Debug.Assert(pBt.Page1 != null);
            Debug.Assert(idx >= 0 && idx <= 15);
            pMeta = ConvertEx.Get4(pBt.Page1.Data, 36 + idx * 4);
            // If auto-vacuum is disabled in this build and this is an auto-vacuum database, mark the database as read-only.
#if SQLITE_OMIT_AUTOVACUUM
            if (idx == BTREE_LARGEST_ROOT_PAGE && pMeta > 0) pBt.readOnly = 1;
#endif
            sqlite3BtreeLeave();
        }
Ejemplo n.º 13
0
        // was:moveToLeftmost
        private RC MoveToLeftmost()
        {
            Debug.Assert(HoldsMutex());
            Debug.Assert(State == CursorState.VALID);
            var     rc = RC.OK;
            MemPage page;

            while (rc == RC.OK && (page = Pages[PageID]).Leaf == 0)
            {
                Debug.Assert(PagesIndexs[PageID] < page.Cells);
                var id = (Pgno)ConvertEx.Get4(page.Data, page.FindCell(PagesIndexs[PageID]));
                rc = MoveToChild(id);
            }
            return(rc);
        }
Ejemplo n.º 14
0
        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);
        }
Ejemplo n.º 15
0
        internal void ptrmapPutOvflPtr(byte[] pCell, ref RC pRC)
        {
            if (pRC != 0)
            {
                return;
            }
            var info = new CellInfo();

            Debug.Assert(pCell != null);
            btreeParseCellPtr(pCell, ref info);
            Debug.Assert((info.nData + (this.HasIntKey ? 0 : info.nKey)) == info.nPayload);
            if (info.iOverflow != 0)
            {
                Pgno ovfl = ConvertEx.Get4(pCell, info.iOverflow);
                this.Shared.ptrmapPut(ovfl, PTRMAP.OVERFLOW1, this.ID, ref pRC);
            }
        }
Ejemplo n.º 16
0
        // 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);
        }
Ejemplo n.º 17
0
        // was:sqlite3BtreeCount
        public RC GetCount(ref long count)
        {
            long count2 = 0; // Value to return in pnEntry
            var  rc     = MoveToRoot();

            // Unless an error occurs, the following loop runs one iteration for each page in the B-Tree structure (not including overflow pages).
            while (rc == RC.OK)
            {
                // If this is a leaf page or the tree is not an int-key tree, then this page contains countable entries. Increment the entry counter accordingly.
                var page = Pages[PageID]; // Current page of the b-tree
                if (page.Leaf != 0 || !page.HasIntKey)
                {
                    count2 += page.Cells;
                }
                // pPage is a leaf node. This loop navigates the cursor so that it points to the first interior cell that it points to the parent of
                // the next page in the tree that has not yet been visited. The pCur.aiIdx[pCur.iPage] value is set to the index of the parent cell
                // of the page, or to the number of cells in the page if the next page to visit is the right-child of its parent.
                // If all pages in the tree have been visited, return SQLITE_OK to the caller.
                if (page.Leaf != 0)
                {
                    do
                    {
                        if (PageID == 0)
                        {
                            // All pages of the b-tree have been visited. Return successfully.
                            count = count2;
                            return(RC.OK);
                        }
                        MoveToParent();
                    } while (PagesIndexs[PageID] >= Pages[PageID].Cells);
                    PagesIndexs[PageID]++;
                    page = Pages[PageID];
                }
                // Descend to the child node of the cell that the cursor currently points at. This is the right-child if (iIdx==pPage.nCell).
                var indexID = PagesIndexs[PageID]; // Index of child node in parent
                rc = (indexID == page.Cells ? MoveToChild(ConvertEx.Get4(page.Data, page.HeaderOffset + 8)) : MoveToChild(ConvertEx.Get4(page.Data, page.FindCell(indexID))));
            }
            // An error has occurred. Return an error code.
            return(rc);
        }
Ejemplo n.º 18
0
 // was:sqlite3BtreeSavepoint
 public RC Savepoint(SAVEPOINT op, int iSavepoint)
 {
     var rc = RC.OK;
     if (this != null && this.InTransaction == TRANS.WRITE)
     {
         Debug.Assert(op == SAVEPOINT.RELEASE || op == SAVEPOINT.ROLLBACK);
         Debug.Assert(iSavepoint >= 0 || (iSavepoint == -1 && op == SAVEPOINT.ROLLBACK));
         sqlite3BtreeEnter();
         var pBt = this.Shared;
         rc = pBt.Pager.Savepoint(op, iSavepoint);
         if (rc == RC.OK)
         {
             if (iSavepoint < 0 && pBt.InitiallyEmpty)
                 pBt.Pages = 0;
             rc = pBt.newDatabase();
             pBt.Pages = ConvertEx.Get4(pBt.Page1.Data, 28);
             //The database size was written into the offset 28 of the header when the transaction started, so we know that the value at offset 28 is nonzero.
             Debug.Assert(pBt.Pages > 0);
         }
         sqlite3BtreeLeave();
     }
     return rc;
 }
Ejemplo n.º 19
0
        // was:sqlite3BtreeBeginTrans
        public RC BeginTrans(byte wrflag)
        {
            var shared = this.Shared;
            var rc = RC.OK;
            sqlite3BtreeEnter();
            btreeIntegrity();
            // If the btree is already in a write-transaction, or it is already in a read-transaction and a read-transaction is requested, this is a no-op.
            if (this.InTransaction == TRANS.WRITE || (this.InTransaction == TRANS.READ && wrflag == 0))
                goto trans_begun;
            // Write transactions are not possible on a read-only database
            if (shared.ReadOnly && wrflag != 0)
            {
                rc = RC.READONLY;
                goto trans_begun;
            }
#if !SQLITE_OMIT_SHARED_CACHE
            // If another database handle has already opened a write transaction on this shared-btree structure and a second write transaction is
            // requested, return SQLITE_LOCKED.
            sqlite3b sharedDB = null;
            if ((wrflag != 0 && shared.InTransaction == TRANS.WRITE) || shared.IsPending)
                sharedDB = shared.Writer.DB;
            else if (wrflag > 1)
                for (var @lock = shared.Locks; @lock != null; @lock = @lock.Next)
                    if (@lock.Tree != this)
                    {
                        sharedDB = @lock.Tree.DB;
                        break;
                    }
            if (sharedDB != null)
            {
                sqlite3b.sqlite3ConnectionBlocked(this.DB, sharedDB);
                rc = RC.LOCKED_SHAREDCACHE;
                goto trans_begun;
            }
#endif
            // Any read-only or read-write transaction implies a read-lock on page 1. So if some other shared-cache client already has a write-lock
            // on page 1, the transaction cannot be opened. */
            rc = querySharedCacheTableLock(MASTER_ROOT, LOCK.READ);
            if (rc != RC.OK)
                goto trans_begun;
            shared.InitiallyEmpty = shared.Pages == 0;
            do
            {
                // Call lockBtree() until either pBt.pPage1 is populated or lockBtree() returns something other than SQLITE_OK. lockBtree()
                // may return SQLITE_OK but leave pBt.pPage1 set to 0 if after reading page 1 it discovers that the page-size of the database
                // file is not pBt.pageSize. In this case lockBtree() will update pBt.pageSize to the page-size of the file on disk.
                while (shared.Page1 == null && (rc = shared.lockBtree()) == RC.OK) ;
                if (rc == RC.OK && wrflag != 0)
                {
                    if (shared.ReadOnly)
                        rc = RC.READONLY;
                    else
                    {
                        rc = shared.Pager.Begin(wrflag > 1, this.DB.sqlite3TempInMemory());
                        if (rc == RC.OK)
                            rc = shared.newDatabase();
                    }
                }
                if (rc != RC.OK)
                    shared.unlockBtreeIfUnused();
            } while (((int)rc & 0xFF) == (int)RC.BUSY && shared.InTransaction == TRANS.NONE && btreeInvokeBusyHandler(shared) != 0);
            if (rc == RC.OK)
            {
                if (this.InTransaction == TRANS.NONE)
                {
                    shared.Transactions++;
#if !SQLITE_OMIT_SHARED_CACHE
                    if (Sharable)
                    {
                        Debug.Assert(Locks.Tree == this && Locks.TableID == 1);
                        Locks.Lock = LOCK.READ;
                        Locks.Next = shared.Locks;
                        shared.Locks = Locks;
                    }
#endif
                }
                this.InTransaction = (wrflag != 0 ? TRANS.WRITE : TRANS.READ);
                if (this.InTransaction > shared.InTransaction)
                    shared.InTransaction = this.InTransaction;
                if (wrflag != 0)
                {
                    var pPage1 = shared.Page1;
#if !SQLITE_OMIT_SHARED_CACHE
                    Debug.Assert(shared.Writer == null);
                    shared.Writer = this;
                    shared.IsExclusive = (wrflag > 1);
#endif
                    // If the db-size header field is incorrect (as it may be if an old client has been writing the database file), update it now. Doing
                    // this sooner rather than later means the database size can safely  re-read the database size from page 1 if a savepoint or transaction
                    // rollback occurs within the transaction.
                    if (shared.Pages != ConvertEx.Get4(pPage1.Data, 28))
                    {
                        rc = Pager.Write(pPage1.DbPage);
                        if (rc == RC.OK)
                            ConvertEx.Put4(pPage1.Data, 28, shared.Pages);
                    }
                }
            }
        trans_begun:
            if (rc == RC.OK && wrflag != 0)
                // This call makes sure that the pager has the correct number of open savepoints. If the second parameter is greater than 0 and
                // the sub-journal is not already open, then it will be opened here.
                rc = shared.Pager.OpenSavepoint(this.DB.nSavepoint);
            btreeIntegrity();
            sqlite3BtreeLeave();
            return rc;
        }
Ejemplo n.º 20
0
        // was:sqlite3BtreeOpen
        public static RC Open(VirtualFileSystem pVfs, string zFilename, sqlite3 db, ref Btree rTree, OPEN flags, VFSOPEN vfsFlags)
        {
            Btree p;                      // Handle to return   
            var rc = RC.OK;
            byte nReserve;                   // Byte of unused space on each page
            var zDbHeader = new byte[100]; // Database header content
            // True if opening an ephemeral, temporary database */
            bool isTempDb = string.IsNullOrEmpty(zFilename);
            // Set the variable isMemdb to true for an in-memory database, or  false for a file-based database.
#if SQLITE_OMIT_MEMORYDB
            var isMemdb = false;
#else
            var isMemdb = (zFilename == ":memory:" || isTempDb && db.sqlite3TempInMemory());
#endif
            Debug.Assert(db != null);
            Debug.Assert(pVfs != null);
            Debug.Assert(MutexEx.Held(db.Mutex));
            Debug.Assert(((uint)flags & 0xff) == (uint)flags);   // flags fit in 8 bits
            // Only a BTREE_SINGLE database can be BTREE_UNORDERED
            Debug.Assert((flags & OPEN.UNORDERED) == 0 || (flags & OPEN.SINGLE) != 0);
            // A BTREE_SINGLE database is always a temporary and/or ephemeral
            Debug.Assert((flags & OPEN.SINGLE) == 0 || isTempDb);
            if ((db.flags & sqlite3b.SQLITE.NoReadlock) != 0)
                flags |= OPEN.NO_READLOCK;
            if (isMemdb)
                flags |= OPEN.MEMORY;
            if ((vfsFlags & VFSOPEN.MAIN_DB) != 0 && (isMemdb || isTempDb))
                vfsFlags = (vfsFlags & ~VFSOPEN.MAIN_DB) | VFSOPEN.TEMP_DB;
            p = new Btree();
            p.InTransaction = TRANS.NONE;
            p.DB = db;
#if !SQLITE_OMIT_SHARED_CACHE
            p.Locks.Tree = p;
            p.Locks.TableID = 1;
#endif
            BtShared shared = null;          // Shared part of btree structure
            MutexEx mutexOpen = null;  // Prevents a race condition.
#if !SQLITE_OMIT_SHARED_CACHE && !SQLITE_OMIT_DISKIO
            // If this Btree is a candidate for shared cache, try to find an existing BtShared object that we can share with
            if (!isMemdb && !isTempDb)
            {
                if ((vfsFlags & VFSOPEN.SHAREDCACHE) != 0)
                {
                    p.Sharable = true;
                    string zPathname;
                    rc = pVfs.xFullPathname(zFilename, out zPathname);
                    mutexOpen = MutexEx.Alloc(MUTEX.STATIC_OPEN);
                    MutexEx.Enter(mutexOpen);
                    var mutexShared = MutexEx.Alloc(MUTEX.STATIC_MASTER);
                    MutexEx.Enter(mutexShared);
                    for (shared = SysEx.getGLOBAL<BtShared>(s_sqlite3SharedCacheList); shared != null; shared = shared.Next)
                    {
                        Debug.Assert(shared.nRef > 0);
                        if (string.Equals(zPathname, shared.Pager.sqlite3PagerFilename) && shared.Pager.sqlite3PagerVfs == pVfs)
                        {
                            for (var iDb = db.DBs - 1; iDb >= 0; iDb--)
                            {
                                var existingTree = db.AllocDBs[iDb].Tree;
                                if (existingTree != null && existingTree.Shared == shared)
                                {
                                    MutexEx.Leave(mutexShared);
                                    MutexEx.Leave(mutexOpen);
                                    p = null;
                                    return RC.CONSTRAINT;
                                }
                            }
                            p.Shared = shared;
                            shared.nRef++;
                            break;
                        }
                    }
                    MutexEx.Leave(mutexShared);
                }
#if DEBUG
                else
                    // In debug mode, we mark all persistent databases as sharable even when they are not.  This exercises the locking code and
                    // gives more opportunity for asserts(sqlite3_mutex_held()) statements to find locking problems.
                    p.Sharable = true;
#endif
            }
#endif
            if (shared == null)
            {
                // The following asserts make sure that structures used by the btree are the right size.  This is to guard against size changes that result
                // when compiling on a different architecture.
                Debug.Assert(sizeof(long) == 8 || sizeof(long) == 4);
                Debug.Assert(sizeof(ulong) == 8 || sizeof(ulong) == 4);
                Debug.Assert(sizeof(uint) == 4);
                Debug.Assert(sizeof(ushort) == 2);
                Debug.Assert(sizeof(Pgno) == 4);
                shared = new BtShared();
                rc = Pager.Open(pVfs, out shared.Pager, zFilename, EXTRA_SIZE, (Pager.PAGEROPEN)flags, vfsFlags, pageReinit, () => new MemPage());
                if (rc == RC.OK)
                    rc = shared.Pager.ReadFileHeader(zDbHeader.Length, zDbHeader);
                if (rc != RC.OK)
                    goto btree_open_out;
                shared.OpenFlags = flags;
                shared.DB = db;
                shared.Pager.SetBusyHandler(btreeInvokeBusyHandler, shared);
                p.Shared = shared;
                shared.Cursors = null;
                shared.Page1 = null;
                shared.ReadOnly = shared.Pager.IsReadonly;
#if SQLITE_SECURE_DELETE
pBt.secureDelete = true;
#endif
                shared.PageSize = (uint)((zDbHeader[16] << 8) | (zDbHeader[17] << 16));
                if (shared.PageSize < 512 || shared.PageSize > Pager.SQLITE_MAX_PAGE_SIZE || ((shared.PageSize - 1) & shared.PageSize) != 0)
                {
                    shared.PageSize = 0;
#if !SQLITE_OMIT_AUTOVACUUM
                    // If the magic name ":memory:" will create an in-memory database, then leave the autoVacuum mode at 0 (do not auto-vacuum), even if
                    // SQLITE_DEFAULT_AUTOVACUUM is true. On the other hand, if SQLITE_OMIT_MEMORYDB has been defined, then ":memory:" is just a
                    // regular file-name. In this case the auto-vacuum applies as per normal.
                    if (zFilename != string.Empty && !isMemdb)
                    {
                        shared.AutoVacuum = (AUTOVACUUM.DEFAULT != AUTOVACUUM.NONE);
                        shared.IncrVacuum = (AUTOVACUUM.DEFAULT == AUTOVACUUM.INCR);
                    }
#endif
                    nReserve = 0;
                }
                else
                {
                    nReserve = zDbHeader[20];
                    shared.PageSizeFixed = true;
#if !SQLITE_OMIT_AUTOVACUUM
                    shared.AutoVacuum = ConvertEx.Get4(zDbHeader, 36 + 4 * 4) != 0;
                    shared.IncrVacuum = ConvertEx.Get4(zDbHeader, 36 + 7 * 4) != 0;
#endif
                }
                rc = shared.Pager.SetPageSize(ref shared.PageSize, nReserve);
                if (rc != RC.OK)
                    goto btree_open_out;
                shared.UsableSize = (ushort)(shared.PageSize - nReserve);
                Debug.Assert((shared.PageSize & 7) == 0);  // 8-byte alignment of pageSize
#if !SQLITE_OMIT_SHARED_CACHE && !SQLITE_OMIT_DISKIO
                // Add the new BtShared object to the linked list sharable BtShareds.
                if (p.Sharable)
                {
                    MutexEx mutexShared;
                    shared.nRef = 1;
                    mutexShared = MutexEx.Alloc(MUTEX.STATIC_MASTER);
                    if (MutexEx.SQLITE_THREADSAFE && MutexEx.WantsCoreMutex)
                        shared.Mutex = MutexEx.Alloc(MUTEX.FAST);
                    MutexEx.Enter(mutexShared);
                    shared.Next = SysEx.getGLOBAL<BtShared>(s_sqlite3SharedCacheList);
                    SysEx.setGLOBAL<BtShared>(s_sqlite3SharedCacheList, shared);
                    MutexEx.Leave(mutexShared);
                }
#endif
            }
#if !SQLITE_OMIT_SHARED_CACHE && !SQLITE_OMIT_DISKIO
            // If the new Btree uses a sharable pBtShared, then link the new Btree into the list of all sharable Btrees for the same connection.
            // The list is kept in ascending order by pBt address.
            Btree existingTree2;
            if (p.Sharable)
                for (var i = 0; i < db.DBs; i++)
                    if ((existingTree2 = db.AllocDBs[i].Tree) != null && existingTree2.Sharable)
                    {
                        while (existingTree2.Prev != null) { existingTree2 = existingTree2.Prev; }
                        if (p.Shared.Version < existingTree2.Shared.Version)
                        {
                            p.Next = existingTree2;
                            p.Prev = null;
                            existingTree2.Prev = p;
                        }
                        else
                        {
                            while (existingTree2.Next != null && existingTree2.Next.Shared.Version < p.Shared.Version)
                                existingTree2 = existingTree2.Next;
                            p.Next = existingTree2.Next;
                            p.Prev = existingTree2;
                            if (p.Next != null)
                                p.Next.Prev = p;
                            existingTree2.Next = p;
                        }
                        break;
                    }
#endif
            rTree = p;
        //
        btree_open_out:
            if (rc != RC.OK)
            {
                if (shared != null && shared.Pager != null)
                    shared.Pager.Close();
                shared = null;
                p = null;
                rTree = null;
            }
            else
            {
                // If the B-Tree was successfully opened, set the pager-cache size to the default value. Except, when opening on an existing shared pager-cache,
                // do not change the pager-cache size.
                if (p.GetSchema(0, null, null) == null)
                    p.Shared.Pager.SetCacheSize(SQLITE_DEFAULT_CACHE_SIZE);
            }
            if (mutexOpen != null)
            {
                Debug.Assert(MutexEx.Held(mutexOpen));
                MutexEx.Leave(mutexOpen);
            }
            return rc;
        }
Ejemplo n.º 21
0
        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);
        }
Ejemplo n.º 22
0
        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);
        }
Ejemplo n.º 23
0
        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);
        }
Ejemplo n.º 24
0
        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);
        }
Ejemplo n.º 25
0
        // was:moveToRoot
        private RC MoveToRoot()
        {
            Debug.Assert(HoldsMutex());
            Debug.Assert(CursorState.INVALID < CursorState.REQUIRESEEK);
            Debug.Assert(CursorState.VALID < CursorState.REQUIRESEEK);
            Debug.Assert(CursorState.FAULT > CursorState.REQUIRESEEK);
            if (State >= CursorState.REQUIRESEEK)
            {
                if (State == CursorState.FAULT)
                {
                    Debug.Assert(SkipNext != 0);
                    return((RC)SkipNext);
                }
                Clear();
            }
            var rc = RC.OK;

            if (PageID >= 0)
            {
                for (var id = 1; id <= PageID; id++)
                {
                    Pages[id].releasePage();
                }
                PageID = 0;
            }
            else
            {
                rc = Tree.Shared.getAndInitPage(RootID, ref Pages[0]);
                if (rc != RC.OK)
                {
                    State = CursorState.INVALID;
                    return(rc);
                }
                PageID = 0;
                // If pCur.pKeyInfo is not NULL, then the caller that opened this cursor expected to open it on an index b-tree. Otherwise, if pKeyInfo is
                // NULL, the caller expects a table b-tree. If this is not the case, return an SQLITE_CORRUPT error.
                if ((KeyInfo == null) != (Pages[0].HasIntKey))
                {
                    return(SysEx.SQLITE_CORRUPT_BKPT());
                }
            }
            // Assert that the root page is of the correct type. This must be the case as the call to this function that loaded the root-page (either
            // this call or a previous invocation) would have detected corruption if the assumption were not true, and it is not possible for the flags
            // byte to have been modified while this cursor is holding a reference to the page.
            var root = Pages[0];

            Debug.Assert(root.ID == RootID);
            Debug.Assert(root.HasInit && (KeyInfo == null) == root.HasIntKey);
            PagesIndexs[0] = 0;
            Info.nSize     = 0;
            AtLast         = false;
            ValidNKey      = false;
            if (root.Cells == 0 && 0 == root.Leaf)
            {
                if (root.ID != 1)
                {
                    return(SysEx.SQLITE_CORRUPT_BKPT());
                }
                var subpage = (Pgno)ConvertEx.Get4(root.Data, root.HeaderOffset + 8);
                State = CursorState.VALID;
                rc    = MoveToChild(subpage);
            }
            else
            {
                State = (root.Cells > 0 ? CursorState.VALID : CursorState.INVALID);
            }
            return(rc);
        }
Ejemplo n.º 26
0
        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);
        }
Ejemplo n.º 27
0
        // 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);
        }
Ejemplo n.º 28
0
        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);
        }
Ejemplo n.º 29
0
        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);
        }
Ejemplo n.º 30
0
        // was:sqlite3BtreeNext
        public RC MoveNext(ref int pRes)
        {
            Debug.Assert(HoldsMutex());
            var rc = RestorePosition();

            if (rc != RC.OK)
            {
                return(rc);
            }
            if (State == CursorState.INVALID)
            {
                pRes = 1;
                return(RC.OK);
            }
            if (SkipNext > 0)
            {
                SkipNext = 0;
                pRes     = 0;
                return(RC.OK);
            }
            SkipNext = 0;
            //
            var page  = Pages[PageID];
            var index = ++PagesIndexs[PageID];

            Debug.Assert(page.HasInit);
            Debug.Assert(index <= page.Cells);
            Info.nSize = 0;
            ValidNKey  = false;
            if (index >= page.Cells)
            {
                if (page.Leaf == 0)
                {
                    rc = MoveToChild(ConvertEx.Get4(page.Data, page.HeaderOffset + 8));
                    if (rc != RC.OK)
                    {
                        return(rc);
                    }
                    rc   = MoveToLeftmost();
                    pRes = 0;
                    return(rc);
                }
                do
                {
                    if (PageID == 0)
                    {
                        pRes  = 1;
                        State = CursorState.INVALID;
                        return(RC.OK);
                    }
                    MoveToParent();
                    page = Pages[PageID];
                } while (PagesIndexs[PageID] >= page.Cells);
                pRes = 0;
                rc   = (page.HasIntKey ? MoveNext(ref pRes) : RC.OK);
                return(rc);
            }
            pRes = 0;
            if (page.Leaf != 0)
            {
                return(RC.OK);
            }
            rc = MoveToLeftmost();
            return(rc);
        }