Пример #1
0
        static int NB = (NN * 2 + 1); // Total pages involved in the balance

        #endregion Fields

        #if !SQLITE_OMIT_QUICKBALANCE

        internal static RC balance_quick(MemPage parentPage, MemPage page, byte[] space)
        {
            Debug.Assert(MutexEx.Held(page.Shared.Mutex));
            Debug.Assert(Pager.IsPageWriteable(parentPage.DbPage));
            Debug.Assert(page.NOverflows == 1);
            // This error condition is now caught prior to reaching this function
            if (page.Cells <= 0)
                return SysEx.SQLITE_CORRUPT_BKPT();
            // Allocate a new page. This page will become the right-sibling of pPage. Make the parent page writable, so that the new divider cell
            // may be inserted. If both these operations are successful, proceed.
            var shared = page.Shared; // B-Tree Database
            var newPage = new MemPage(); // Newly allocated page
            Pgno pgnoNew = 0; // Page number of pNew
            var rc = shared.allocateBtreePage(ref newPage, ref pgnoNew, 0, 0);
            if (rc != RC.OK)
                return rc;
            var pOut = 4;
            var pCell = page.Overflows[0].Cell;
            var szCell = new int[1] { page.cellSizePtr(pCell) };
            Debug.Assert(Pager.IsPageWriteable(newPage.DbPage));
            Debug.Assert(page.Data[0] == (Btree.PTF_INTKEY | Btree.PTF_LEAFDATA | Btree.PTF_LEAF));
            newPage.zeroPage(Btree.PTF_INTKEY | Btree.PTF_LEAFDATA | Btree.PTF_LEAF);
            newPage.assemblePage(1, pCell, szCell);
            // If this is an auto-vacuum database, update the pointer map with entries for the new page, and any pointer from the
            // cell on the page to an overflow page. If either of these operations fails, the return code is set, but the contents
            // of the parent page are still manipulated by thh code below. That is Ok, at this point the parent page is guaranteed to
            // be marked as dirty. Returning an error code will cause a rollback, undoing any changes made to the parent page.
            #if !SQLITE_OMIT_AUTOVACUUM
            if (shared.AutoVacuum)
            #else
            if (false)
            #endif
            {
                shared.ptrmapPut(pgnoNew, PTRMAP.BTREE, parentPage.ID, ref rc);
                if (szCell[0] > newPage.MinLocal)
                    newPage.ptrmapPutOvflPtr(pCell, ref rc);
            }
            // Create a divider cell to insert into pParent. The divider cell consists of a 4-byte page number (the page number of pPage) and
            // a variable length key value (which must be the same value as the largest key on pPage).
            // To find the largest key value on pPage, first find the right-most cell on pPage. The first two fields of this cell are the
            // record-length (a variable length integer at most 32-bits in size) and the key value (a variable length integer, may have any value).
            // The first of the while(...) loops below skips over the record-length field. The second while(...) loop copies the key value from the
            // cell on pPage into the pSpace buffer.
            var iCell = page.FindCell(page.Cells - 1);
            pCell = page.Data;
            var _pCell = iCell;
            var pStop = _pCell + 9;
            while (((pCell[_pCell++]) & 0x80) != 0 && _pCell < pStop) ;
            pStop = _pCell + 9;
            while (((space[pOut++] = pCell[_pCell++]) & 0x80) != 0 && _pCell < pStop) ;
            // Insert the new divider cell into pParent.
            parentPage.insertCell(parentPage.Cells, space, pOut, null, page.ID, ref rc);
            // Set the right-child pointer of pParent to point to the new page.
            ConvertEx.Put4L(parentPage.Data, parentPage.HeaderOffset + 8, pgnoNew);
            // Release the reference to the new page.
            newPage.releasePage();
            return rc;
        }
Пример #2
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);
        }
Пример #3
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);
        }
Пример #4
0
        internal static RC balance_deeper(MemPage pRoot, ref MemPage ppChild)
        {
            MemPage pChild    = null; // Pointer to a new child page
            Pgno    pgnoChild = 0;    // Page number of the new child page
            var     pBt       = pRoot.Shared;

            Debug.Assert(pRoot.NOverflows > 0);
            Debug.Assert(MutexEx.Held(pBt.Mutex));
            // Make pRoot, the root page of the b-tree, writable. Allocate a new page that will become the new right-child of pPage. Copy the contents
            // of the node stored on pRoot into the new child page.
            var rc = Pager.Write(pRoot.DbPage);

            if (rc == RC.OK)
            {
                rc = pBt.allocateBtreePage(ref pChild, ref pgnoChild, pRoot.ID, 0);
                copyNodeContent(pRoot, pChild, ref rc);
#if !SQLITE_OMIT_AUTOVACUUM
                if (pBt.AutoVacuum)
#else
                if (false)
#endif
                {
                    pBt.ptrmapPut(pgnoChild, PTRMAP.BTREE, pRoot.ID, ref rc);
                }
            }
            if (rc != RC.OK)
            {
                ppChild = null;
                pChild.releasePage();
                return(rc);
            }
            Debug.Assert(Pager.IsPageWriteable(pChild.DbPage));
            Debug.Assert(Pager.IsPageWriteable(pRoot.DbPage));
            Debug.Assert(pChild.Cells == pRoot.Cells);
            Btree.TRACE("BALANCE: copy root %d into %d\n", pRoot.ID, pChild.ID);
            // Copy the overflow cells from pRoot to pChild
            Array.Copy(pRoot.Overflows, pChild.Overflows, pRoot.NOverflows);
            pChild.NOverflows = pRoot.NOverflows;
            // Zero the contents of pRoot. Then install pChild as the right-child.
            pRoot.zeroPage(pChild.Data[0] & ~Btree.PTF_LEAF);
            ConvertEx.Put4L(pRoot.Data, pRoot.HeaderOffset + 8, pgnoChild);
            ppChild = pChild;
            return(RC.OK);
        }
Пример #5
0
        internal RC getAndInitPage(Pgno pgno, ref MemPage ppPage)
        {
            Debug.Assert(MutexEx.Held(this.Mutex));
            RC rc;

            if (pgno > btreePagecount())
            {
                rc = SysEx.SQLITE_CORRUPT_BKPT();
            }
            else
            {
                rc = btreeGetPage(pgno, ref ppPage, 0);
                if (rc == RC.OK)
                {
                    rc = ppPage.btreeInitPage();
                    if (rc != RC.OK)
                    {
                        ppPage.releasePage();
                    }
                }
            }
            Debug.Assert(pgno != 0 || rc == RC.CORRUPT);
            return(rc);
        }
Пример #6
0
        internal RC fillInCell(byte[] pCell, byte[] pKey, long nKey, byte[] pData, int nData, int nZero, ref int pnSize)
        {
            Debug.Assert(MutexEx.Held(this.Shared.Mutex));
            // pPage is not necessarily writeable since pCell might be auxiliary buffer space that is separate from the pPage buffer area
            // TODO -- Determine if the following Assert is needed under c#
            //Debug.Assert( pCell < pPage.aData || pCell >= &pPage.aData[pBt.pageSize] || sqlite3PagerIswriteable(pPage.pDbPage) );
            // Fill in the header.
            var nHeader = 0;

            if (this.Leaf == 0)
            {
                nHeader += 4;
            }
            if (this.HasData != 0)
            {
                nHeader += (int)ConvertEx.PutVariant9(pCell, (uint)nHeader, (int)(nData + nZero));
            }
            else
            {
                nData = nZero = 0;
            }
            nHeader += ConvertEx.PutVariant9L(pCell, (uint)nHeader, (ulong)nKey);
            var info = new CellInfo();

            btreeParseCellPtr(pCell, ref info);
            Debug.Assert(info.nHeader == nHeader);
            Debug.Assert(info.nKey == nKey);
            Debug.Assert(info.nData == (uint)(nData + nZero));
            // Fill in the payload
            var nPayload = nData + nZero;

            byte[] pSrc;
            int    nSrc;

            if (this.HasIntKey)
            {
                pSrc  = pData;
                nSrc  = nData;
                nData = 0;
            }
            else
            {
                if (Check.NEVER(nKey > 0x7fffffff || pKey == null))
                {
                    return(SysEx.SQLITE_CORRUPT_BKPT());
                }
                nPayload += (int)nKey;
                pSrc      = pKey;
                nSrc      = (int)nKey;
            }
            pnSize = info.nSize;
            var     spaceLeft     = (int)info.nLocal;
            var     pPayload      = pCell;
            var     pPayloadIndex = nHeader;
            var     pPrior        = pCell;
            var     pPriorIndex   = (int)info.iOverflow;
            var     pBt           = this.Shared;
            Pgno    pgnoOvfl      = 0;
            MemPage pToRelease    = null;

            while (nPayload > 0)
            {
                if (spaceLeft == 0)
                {
#if !SQLITE_OMIT_AUTOVACUUM
                    var pgnoPtrmap = pgnoOvfl; // Overflow page pointer-map entry page
                    if (pBt.AutoVacuum)
                    {
                        do
                        {
                            pgnoOvfl++;
                        }while (PTRMAP_ISPAGE(pBt, pgnoOvfl) || pgnoOvfl == PENDING_BYTE_PAGE(pBt));
                    }
#endif
                    MemPage pOvfl = null;
                    var     rc    = pBt.allocateBtreePage(ref pOvfl, ref pgnoOvfl, pgnoOvfl, 0);
#if !SQLITE_OMIT_AUTOVACUUM
                    // If the database supports auto-vacuum, and the second or subsequent overflow page is being allocated, add an entry to the pointer-map for that page now.
                    // If this is the first overflow page, then write a partial entry to the pointer-map. If we write nothing to this pointer-map slot,
                    // then the optimistic overflow chain processing in clearCell() may misinterpret the uninitialised values and delete the
                    // wrong pages from the database.
                    if (pBt.AutoVacuum && rc == RC.OK)
                    {
                        var eType = (pgnoPtrmap != 0 ? PTRMAP.OVERFLOW2 : PTRMAP.OVERFLOW1);
                        pBt.ptrmapPut(pgnoOvfl, eType, pgnoPtrmap, ref rc);
                        if (rc != RC.OK)
                        {
                            pOvfl.releasePage();
                        }
                    }
#endif
                    if (rc != RC.OK)
                    {
                        pToRelease.releasePage();
                        return(rc);
                    }
                    // If pToRelease is not zero than pPrior points into the data area of pToRelease.  Make sure pToRelease is still writeable.
                    Debug.Assert(pToRelease == null || Pager.IsPageWriteable(pToRelease.DbPage));
                    // If pPrior is part of the data area of pPage, then make sure pPage is still writeable
                    // TODO -- Determine if the following Assert is needed under c#
                    //Debug.Assert( pPrior < pPage.aData || pPrior >= &pPage.aData[pBt.pageSize] || sqlite3PagerIswriteable(pPage.pDbPage) );
                    ConvertEx.Put4L(pPrior, (uint)pPriorIndex, pgnoOvfl);
                    pToRelease.releasePage();
                    pToRelease  = pOvfl;
                    pPrior      = pOvfl.Data;
                    pPriorIndex = 0;
                    ConvertEx.Put4(pPrior, 0);
                    pPayload      = pOvfl.Data;
                    pPayloadIndex = 4;
                    spaceLeft     = (int)pBt.UsableSize - 4;
                }
                var n = nPayload;
                if (n > spaceLeft)
                {
                    n = spaceLeft;
                }
                // If pToRelease is not zero than pPayload points into the data area of pToRelease.  Make sure pToRelease is still writeable.
                Debug.Assert(pToRelease == null || Pager.IsPageWriteable(pToRelease.DbPage));
                // If pPayload is part of the data area of pPage, then make sure pPage is still writeable
                // TODO -- Determine if the following Assert is needed under c#
                //Debug.Assert( pPayload < pPage.aData || pPayload >= &pPage.aData[pBt.pageSize] || sqlite3PagerIswriteable(pPage.pDbPage) );
                var pSrcIndex = 0;
                if (nSrc > 0)
                {
                    if (n > nSrc)
                    {
                        n = nSrc;
                    }
                    Debug.Assert(pSrc != null);
                    Buffer.BlockCopy(pSrc, pSrcIndex, pPayload, pPayloadIndex, n);
                }
                else
                {
                    var pZeroBlob = MallocEx.sqlite3Malloc(n);
                    Buffer.BlockCopy(pZeroBlob, 0, pPayload, pPayloadIndex, n);
                }
                nPayload      -= n;
                pPayloadIndex += n;
                pSrcIndex     += n;
                nSrc          -= n;
                spaceLeft     -= n;
                if (nSrc == 0)
                {
                    nSrc = nData;
                    pSrc = pData;
                }
            }
            pToRelease.releasePage();
            return(RC.OK);
        }
Пример #7
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);
        }
Пример #8
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);
        }
Пример #9
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);
        }
Пример #10
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);
        }
Пример #11
0
        internal RC btreeDropTable(Pgno iTable, ref int piMoved)
        {
            MemPage pPage = null;
            var     pBt   = this.Shared;

            Debug.Assert(sqlite3BtreeHoldsMutex());
            Debug.Assert(this.InTransaction == TRANS.WRITE);
            // It is illegal to drop a table if any cursors are open on the database. This is because in auto-vacuum mode the backend may
            // need to move another root-page to fill a gap left by the deleted root page. If an open cursor was using this page a problem would occur.
            // This error is caught long before control reaches this point.
            if (Check.NEVER(pBt.Cursors) != null)
            {
                sqlite3b.sqlite3ConnectionBlocked(this.DB, pBt.Cursors.Tree.DB);
                return(RC.LOCKED_SHAREDCACHE);
            }
            var rc = pBt.btreeGetPage((Pgno)iTable, ref pPage, 0);

            if (rc != RC.OK)
            {
                return(rc);
            }
            var dummy0 = 0;

            rc = ClearTable((int)iTable, ref dummy0);
            if (rc != RC.OK)
            {
                pPage.releasePage();
                return(rc);
            }
            piMoved = 0;
            if (iTable > 1)
            {
#if SQLITE_OMIT_AUTOVACUUM
                freePage(pPage, ref rc);
                releasePage(pPage);
#else
                if (pBt.AutoVacuum)
                {
                    Pgno maxRootPgno = 0;
                    GetMeta((int)META.LARGEST_ROOT_PAGE, ref maxRootPgno);
                    if (iTable == maxRootPgno)
                    {
                        // If the table being dropped is the table with the largest root-page number in the database, put the root page on the free list.
                        pPage.freePage(ref rc);
                        pPage.releasePage();
                        if (rc != RC.OK)
                        {
                            return(rc);
                        }
                    }
                    else
                    {
                        // The table being dropped does not have the largest root-page number in the database. So move the page that does into the
                        // gap left by the deleted root-page.
                        var pMove = new MemPage();
                        pPage.releasePage();
                        rc = pBt.btreeGetPage(maxRootPgno, ref pMove, 0);
                        if (rc != RC.OK)
                        {
                            return(rc);
                        }
                        rc = MemPage.relocatePage(pBt, pMove, PTRMAP.ROOTPAGE, 0, iTable, 0);
                        pMove.releasePage();
                        if (rc != RC.OK)
                        {
                            return(rc);
                        }
                        pMove = null;
                        rc    = pBt.btreeGetPage(maxRootPgno, ref pMove, 0);
                        pMove.freePage(ref rc);
                        pMove.releasePage();
                        if (rc != RC.OK)
                        {
                            return(rc);
                        }
                        piMoved = (int)maxRootPgno;
                    }
                    // Set the new 'max-root-page' value in the database header. This is the old value less one, less one more if that happens to
                    // be a root-page number, less one again if that is the PENDING_BYTE_PAGE.
                    maxRootPgno--;
                    while (maxRootPgno == MemPage.PENDING_BYTE_PAGE(pBt) || MemPage.PTRMAP_ISPAGE(pBt, maxRootPgno))
                    {
                        maxRootPgno--;
                    }
                    Debug.Assert(maxRootPgno != MemPage.PENDING_BYTE_PAGE(pBt));
                    rc = SetMeta(4, maxRootPgno);
                }
                else
                {
                    pPage.freePage(ref rc);
                    pPage.releasePage();
                }
#endif
            }
            else
            {
                // If sqlite3BtreeDropTable was called on page 1. This really never should happen except in a corrupt database.
                pPage.zeroPage(PTF_INTKEY | PTF_LEAF);
                pPage.releasePage();
            }
            return(rc);
        }
Пример #12
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;
 }
Пример #13
0
 internal RC btreeDropTable(Pgno iTable, ref int piMoved)
 {
     MemPage pPage = null;
     var pBt = this.Shared;
     Debug.Assert(sqlite3BtreeHoldsMutex());
     Debug.Assert(this.InTransaction == TRANS.WRITE);
     // It is illegal to drop a table if any cursors are open on the database. This is because in auto-vacuum mode the backend may
     // need to move another root-page to fill a gap left by the deleted root page. If an open cursor was using this page a problem would occur.
     // This error is caught long before control reaches this point.
     if (Check.NEVER(pBt.Cursors) != null)
     {
         sqlite3b.sqlite3ConnectionBlocked(this.DB, pBt.Cursors.Tree.DB);
         return RC.LOCKED_SHAREDCACHE;
     }
     var rc = pBt.btreeGetPage((Pgno)iTable, ref pPage, 0);
     if (rc != RC.OK)
         return rc;
     var dummy0 = 0;
     rc = ClearTable((int)iTable, ref dummy0);
     if (rc != RC.OK)
     {
         pPage.releasePage();
         return rc;
     }
     piMoved = 0;
     if (iTable > 1)
     {
     #if SQLITE_OMIT_AUTOVACUUM
     freePage(pPage, ref rc);
     releasePage(pPage);
     #else
         if (pBt.AutoVacuum)
         {
             Pgno maxRootPgno = 0;
             GetMeta((int)META.LARGEST_ROOT_PAGE, ref maxRootPgno);
             if (iTable == maxRootPgno)
             {
                 // If the table being dropped is the table with the largest root-page number in the database, put the root page on the free list.
                 pPage.freePage(ref rc);
                 pPage.releasePage();
                 if (rc != RC.OK)
                     return rc;
             }
             else
             {
                 // The table being dropped does not have the largest root-page number in the database. So move the page that does into the
                 // gap left by the deleted root-page.
                 var pMove = new MemPage();
                 pPage.releasePage();
                 rc = pBt.btreeGetPage(maxRootPgno, ref pMove, 0);
                 if (rc != RC.OK)
                     return rc;
                 rc = MemPage.relocatePage(pBt, pMove, PTRMAP.ROOTPAGE, 0, iTable, 0);
                 pMove.releasePage();
                 if (rc != RC.OK)
                     return rc;
                 pMove = null;
                 rc = pBt.btreeGetPage(maxRootPgno, ref pMove, 0);
                 pMove.freePage(ref rc);
                 pMove.releasePage();
                 if (rc != RC.OK)
                     return rc;
                 piMoved = (int)maxRootPgno;
             }
             // Set the new 'max-root-page' value in the database header. This is the old value less one, less one more if that happens to
             // be a root-page number, less one again if that is the PENDING_BYTE_PAGE.
             maxRootPgno--;
             while (maxRootPgno == MemPage.PENDING_BYTE_PAGE(pBt) || MemPage.PTRMAP_ISPAGE(pBt, maxRootPgno))
                 maxRootPgno--;
             Debug.Assert(maxRootPgno != MemPage.PENDING_BYTE_PAGE(pBt));
             rc = SetMeta(4, maxRootPgno);
         }
         else
         {
             pPage.freePage(ref rc);
             pPage.releasePage();
         }
     #endif
     }
     else
     {
         // If sqlite3BtreeDropTable was called on page 1. This really never should happen except in a corrupt database.
         pPage.zeroPage(PTF_INTKEY | PTF_LEAF);
         pPage.releasePage();
     }
     return rc;
 }
Пример #14
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;
 }
Пример #15
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);
        }
Пример #16
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;
        }
Пример #17
0
 internal RC getAndInitPage(Pgno pgno, ref MemPage ppPage)
 {
     Debug.Assert(MutexEx.Held(this.Mutex));
     RC rc;
     if (pgno > btreePagecount())
         rc = SysEx.SQLITE_CORRUPT_BKPT();
     else
     {
         rc = btreeGetPage(pgno, ref ppPage, 0);
         if (rc == RC.OK)
         {
             rc = ppPage.btreeInitPage();
             if (rc != RC.OK)
                 ppPage.releasePage();
         }
     }
     Debug.Assert(pgno != 0 || rc == RC.CORRUPT);
     return rc;
 }
Пример #18
0
        static int NB = (NN * 2 + 1);   // Total pages involved in the balance

#if !SQLITE_OMIT_QUICKBALANCE
        internal static RC balance_quick(MemPage parentPage, MemPage page, byte[] space)
        {
            Debug.Assert(MutexEx.Held(page.Shared.Mutex));
            Debug.Assert(Pager.IsPageWriteable(parentPage.DbPage));
            Debug.Assert(page.NOverflows == 1);
            // This error condition is now caught prior to reaching this function
            if (page.Cells <= 0)
            {
                return(SysEx.SQLITE_CORRUPT_BKPT());
            }
            // Allocate a new page. This page will become the right-sibling of pPage. Make the parent page writable, so that the new divider cell
            // may be inserted. If both these operations are successful, proceed.
            var  shared  = page.Shared;   // B-Tree Database
            var  newPage = new MemPage(); // Newly allocated page
            Pgno pgnoNew = 0;             // Page number of pNew
            var  rc      = shared.allocateBtreePage(ref newPage, ref pgnoNew, 0, 0);

            if (rc != RC.OK)
            {
                return(rc);
            }
            var pOut   = 4;
            var pCell  = page.Overflows[0].Cell;
            var szCell = new int[1] {
                page.cellSizePtr(pCell)
            };

            Debug.Assert(Pager.IsPageWriteable(newPage.DbPage));
            Debug.Assert(page.Data[0] == (Btree.PTF_INTKEY | Btree.PTF_LEAFDATA | Btree.PTF_LEAF));
            newPage.zeroPage(Btree.PTF_INTKEY | Btree.PTF_LEAFDATA | Btree.PTF_LEAF);
            newPage.assemblePage(1, pCell, szCell);
            // If this is an auto-vacuum database, update the pointer map with entries for the new page, and any pointer from the
            // cell on the page to an overflow page. If either of these operations fails, the return code is set, but the contents
            // of the parent page are still manipulated by thh code below. That is Ok, at this point the parent page is guaranteed to
            // be marked as dirty. Returning an error code will cause a rollback, undoing any changes made to the parent page.
#if !SQLITE_OMIT_AUTOVACUUM
            if (shared.AutoVacuum)
#else
            if (false)
#endif
            {
                shared.ptrmapPut(pgnoNew, PTRMAP.BTREE, parentPage.ID, ref rc);
                if (szCell[0] > newPage.MinLocal)
                {
                    newPage.ptrmapPutOvflPtr(pCell, ref rc);
                }
            }
            // Create a divider cell to insert into pParent. The divider cell consists of a 4-byte page number (the page number of pPage) and
            // a variable length key value (which must be the same value as the largest key on pPage).
            // To find the largest key value on pPage, first find the right-most cell on pPage. The first two fields of this cell are the
            // record-length (a variable length integer at most 32-bits in size) and the key value (a variable length integer, may have any value).
            // The first of the while(...) loops below skips over the record-length field. The second while(...) loop copies the key value from the
            // cell on pPage into the pSpace buffer.
            var iCell = page.FindCell(page.Cells - 1);
            pCell = page.Data;
            var _pCell = iCell;
            var pStop  = _pCell + 9;
            while (((pCell[_pCell++]) & 0x80) != 0 && _pCell < pStop)
            {
                ;
            }
            pStop = _pCell + 9;
            while (((space[pOut++] = pCell[_pCell++]) & 0x80) != 0 && _pCell < pStop)
            {
                ;
            }
            // Insert the new divider cell into pParent.
            parentPage.insertCell(parentPage.Cells, space, pOut, null, page.ID, ref rc);
            // Set the right-child pointer of pParent to point to the new page.
            ConvertEx.Put4L(parentPage.Data, parentPage.HeaderOffset + 8, pgnoNew);
            // Release the reference to the new page.
            newPage.releasePage();
            return(rc);
        }
Пример #19
0
        internal RC btreeCreateTable(ref int piTable, CREATETABLE createTabFlags)
        {
            var  pBt      = this.Shared;
            var  pRoot    = new MemPage();
            Pgno pgnoRoot = 0;
            int  ptfFlags;         // Page-type flage for the root page of new table
            RC   rc;

            Debug.Assert(sqlite3BtreeHoldsMutex());
            Debug.Assert(pBt.InTransaction == TRANS.WRITE);
            Debug.Assert(!pBt.ReadOnly);
#if SQLITE_OMIT_AUTOVACUUM
            rc = allocateBtreePage(pBt, ref pRoot, ref pgnoRoot, 1, 0);
            if (rc != SQLITE.OK)
            {
                return(rc);
            }
#else
            if (pBt.AutoVacuum)
            {
                Pgno pgnoMove  = 0;             // Move a page here to make room for the root-page
                var  pPageMove = new MemPage(); // The page to move to.
                // Creating a new table may probably require moving an existing database to make room for the new tables root page. In case this page turns
                // out to be an overflow page, delete all overflow page-map caches held by open cursors.
                invalidateAllOverflowCache(pBt);
                // Read the value of meta[3] from the database to determine where the root page of the new table should go. meta[3] is the largest root-page
                // created so far, so the new root-page is (meta[3]+1).
                GetMeta((int)META.LARGEST_ROOT_PAGE, ref pgnoRoot);
                pgnoRoot++;
                // The new root-page may not be allocated on a pointer-map page, or the PENDING_BYTE page.
                while (pgnoRoot == MemPage.PTRMAP_PAGENO(pBt, pgnoRoot) || pgnoRoot == MemPage.PENDING_BYTE_PAGE(pBt))
                {
                    pgnoRoot++;
                }
                Debug.Assert(pgnoRoot >= 3);
                // Allocate a page. The page that currently resides at pgnoRoot will be moved to the allocated page (unless the allocated page happens to reside at pgnoRoot).
                rc = pBt.allocateBtreePage(ref pPageMove, ref pgnoMove, pgnoRoot, 1);
                if (rc != RC.OK)
                {
                    return(rc);
                }
                if (pgnoMove != pgnoRoot)
                {
                    // pgnoRoot is the page that will be used for the root-page of the new table (assuming an error did not occur). But we were
                    // allocated pgnoMove. If required (i.e. if it was not allocated by extending the file), the current page at position pgnoMove
                    // is already journaled.
                    PTRMAP eType    = 0;
                    Pgno   iPtrPage = 0;
                    pPageMove.releasePage();
                    // Move the page currently at pgnoRoot to pgnoMove.
                    rc = pBt.btreeGetPage(pgnoRoot, ref pRoot, 0);
                    if (rc != RC.OK)
                    {
                        return(rc);
                    }
                    rc = pBt.ptrmapGet(pgnoRoot, ref eType, ref iPtrPage);
                    if (eType == PTRMAP.ROOTPAGE || eType == PTRMAP.FREEPAGE)
                    {
                        rc = SysEx.SQLITE_CORRUPT_BKPT();
                    }
                    if (rc != RC.OK)
                    {
                        pRoot.releasePage();
                        return(rc);
                    }
                    Debug.Assert(eType != PTRMAP.ROOTPAGE);
                    Debug.Assert(eType != PTRMAP.FREEPAGE);
                    rc = MemPage.relocatePage(pBt, pRoot, eType, iPtrPage, pgnoMove, 0);
                    pRoot.releasePage();
                    // Obtain the page at pgnoRoot
                    if (rc != RC.OK)
                    {
                        return(rc);
                    }
                    rc = pBt.btreeGetPage(pgnoRoot, ref pRoot, 0);
                    if (rc != RC.OK)
                    {
                        return(rc);
                    }
                    rc = Pager.Write(pRoot.DbPage);
                    if (rc != RC.OK)
                    {
                        pRoot.releasePage();
                        return(rc);
                    }
                }
                else
                {
                    pRoot = pPageMove;
                }
                // Update the pointer-map and meta-data with the new root-page number.
                pBt.ptrmapPut(pgnoRoot, PTRMAP.ROOTPAGE, 0, ref rc);
                if (rc != RC.OK)
                {
                    pRoot.releasePage();
                    return(rc);
                }
                // When the new root page was allocated, page 1 was made writable in order either to increase the database filesize, or to decrement the
                // freelist count.  Hence, the sqlite3BtreeUpdateMeta() call cannot fail.
                Debug.Assert(Pager.IsPageWriteable(pBt.Page1.DbPage));
                rc = SetMeta(4, pgnoRoot);
                if (Check.NEVER(rc != RC.OK))
                {
                    pRoot.releasePage();
                    return(rc);
                }
            }
            else
            {
                rc = pBt.allocateBtreePage(ref pRoot, ref pgnoRoot, 1, 0);
                if (rc != RC.OK)
                {
                    return(rc);
                }
            }
#endif
            Debug.Assert(Pager.IsPageWriteable(pRoot.DbPage));
            ptfFlags = ((createTabFlags & CREATETABLE.INTKEY) != 0 ? PTF_INTKEY | PTF_LEAFDATA | PTF_LEAF : PTF_ZERODATA | PTF_LEAF);
            pRoot.zeroPage(ptfFlags);
            Pager.Unref(pRoot.DbPage);
            Debug.Assert((pBt.OpenFlags & OPEN.SINGLE) == 0 || pgnoRoot == 2);
            piTable = (int)pgnoRoot;
            return(RC.OK);
        }
Пример #20
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;
 }
Пример #21
0
 internal RC btreeCreateTable(ref int piTable, CREATETABLE createTabFlags)
 {
     var pBt = this.Shared;
     var pRoot = new MemPage();
     Pgno pgnoRoot = 0;
     int ptfFlags;          // Page-type flage for the root page of new table
     RC rc;
     Debug.Assert(sqlite3BtreeHoldsMutex());
     Debug.Assert(pBt.InTransaction == TRANS.WRITE);
     Debug.Assert(!pBt.ReadOnly);
     #if SQLITE_OMIT_AUTOVACUUM
     rc = allocateBtreePage(pBt, ref pRoot, ref pgnoRoot, 1, 0);
     if (rc != SQLITE.OK)
         return rc;
     #else
     if (pBt.AutoVacuum)
     {
         Pgno pgnoMove = 0; // Move a page here to make room for the root-page
         var pPageMove = new MemPage(); // The page to move to.
         // Creating a new table may probably require moving an existing database to make room for the new tables root page. In case this page turns
         // out to be an overflow page, delete all overflow page-map caches held by open cursors.
         invalidateAllOverflowCache(pBt);
         // Read the value of meta[3] from the database to determine where the root page of the new table should go. meta[3] is the largest root-page
         // created so far, so the new root-page is (meta[3]+1).
         GetMeta((int)META.LARGEST_ROOT_PAGE, ref pgnoRoot);
         pgnoRoot++;
         // The new root-page may not be allocated on a pointer-map page, or the PENDING_BYTE page.
         while (pgnoRoot == MemPage.PTRMAP_PAGENO(pBt, pgnoRoot) || pgnoRoot == MemPage.PENDING_BYTE_PAGE(pBt))
             pgnoRoot++;
         Debug.Assert(pgnoRoot >= 3);
         // Allocate a page. The page that currently resides at pgnoRoot will be moved to the allocated page (unless the allocated page happens to reside at pgnoRoot).
         rc = pBt.allocateBtreePage(ref pPageMove, ref pgnoMove, pgnoRoot, 1);
         if (rc != RC.OK)
             return rc;
         if (pgnoMove != pgnoRoot)
         {
             // pgnoRoot is the page that will be used for the root-page of the new table (assuming an error did not occur). But we were
             // allocated pgnoMove. If required (i.e. if it was not allocated by extending the file), the current page at position pgnoMove
             // is already journaled.
             PTRMAP eType = 0;
             Pgno iPtrPage = 0;
             pPageMove.releasePage();
             // Move the page currently at pgnoRoot to pgnoMove.
             rc = pBt.btreeGetPage(pgnoRoot, ref pRoot, 0);
             if (rc != RC.OK)
                 return rc;
             rc = pBt.ptrmapGet(pgnoRoot, ref eType, ref iPtrPage);
             if (eType == PTRMAP.ROOTPAGE || eType == PTRMAP.FREEPAGE)
                 rc = SysEx.SQLITE_CORRUPT_BKPT();
             if (rc != RC.OK)
             {
                 pRoot.releasePage();
                 return rc;
             }
             Debug.Assert(eType != PTRMAP.ROOTPAGE);
             Debug.Assert(eType != PTRMAP.FREEPAGE);
             rc = MemPage.relocatePage(pBt, pRoot, eType, iPtrPage, pgnoMove, 0);
             pRoot.releasePage();
             // Obtain the page at pgnoRoot
             if (rc != RC.OK)
                 return rc;
             rc = pBt.btreeGetPage(pgnoRoot, ref pRoot, 0);
             if (rc != RC.OK)
                 return rc;
             rc = Pager.Write(pRoot.DbPage);
             if (rc != RC.OK)
             {
                 pRoot.releasePage();
                 return rc;
             }
         }
         else
             pRoot = pPageMove;
         // Update the pointer-map and meta-data with the new root-page number.
         pBt.ptrmapPut(pgnoRoot, PTRMAP.ROOTPAGE, 0, ref rc);
         if (rc != RC.OK)
         {
             pRoot.releasePage();
             return rc;
         }
         // When the new root page was allocated, page 1 was made writable in order either to increase the database filesize, or to decrement the
         // freelist count.  Hence, the sqlite3BtreeUpdateMeta() call cannot fail.
         Debug.Assert(Pager.IsPageWriteable(pBt.Page1.DbPage));
         rc = SetMeta(4, pgnoRoot);
         if (Check.NEVER(rc != RC.OK))
         {
             pRoot.releasePage();
             return rc;
         }
     }
     else
     {
         rc = pBt.allocateBtreePage(ref pRoot, ref pgnoRoot, 1, 0);
         if (rc != RC.OK)
             return rc;
     }
     #endif
     Debug.Assert(Pager.IsPageWriteable(pRoot.DbPage));
     ptfFlags = ((createTabFlags & CREATETABLE.INTKEY) != 0 ? PTF_INTKEY | PTF_LEAFDATA | PTF_LEAF : PTF_ZERODATA | PTF_LEAF);
     pRoot.zeroPage(ptfFlags);
     Pager.Unref(pRoot.DbPage);
     Debug.Assert((pBt.OpenFlags & OPEN.SINGLE) == 0 || pgnoRoot == 2);
     piTable = (int)pgnoRoot;
     return RC.OK;
 }