Ejemplo n.º 1
0
        /*
        ** Call sqlite3WalOpen() to open the WAL handle. If the pager is in
        ** exclusive-locking mode when this function is called, take an EXCLUSIVE
        ** lock on the database file and use heap-memory to store the wal-index
        ** in. Otherwise, use the normal shared-memory.
        */
        static int pagerOpenWal(Pager *pPager)
        {
            int rc = SQLITE.OK;

            assert( pPager.pWal==0 && pPager.tempFile==0 );
            assert( pPager.eLock==SHARED_LOCK || pPager.eLock==EXCLUSIVE_LOCK || pPager.noReadlock);

            /* If the pager is already in exclusive-mode, the WAL module will use
            ** heap-memory for the wal-index instead of the VFS shared-memory
            ** implementation. Take the exclusive lock now, before opening the WAL
            ** file, to make sure this is safe.
            */
            if( pPager.exclusiveMode ){
            rc = pagerExclusiveLock(pPager);
            }

            /* Open the connection to the log file. If this operation fails,
            ** (e.g. due to malloc() failure), return an error code.
            */
            if( rc==SQLITE.OK ){
            rc = sqlite3WalOpen(pPager.pVfs,
            pPager.fd, pPager.zWal, pPager.exclusiveMode, &pPager.pWal
            pPager.journalSizeLimit, &pPager.pWal
            );
            }

            return rc;
        }
Ejemplo n.º 2
0
        /*
        ** Attempt to take an exclusive lock on the database file. If a PENDING lock
        ** is obtained instead, immediately release it.
        */
        static int pagerExclusiveLock(Pager *pPager)
        {
            int rc;                         /* Return code */

            assert( pPager.eLock==SHARED_LOCK || pPager.eLock==EXCLUSIVE_LOCK );
            rc = pagerLockDb(pPager, EXCLUSIVE_LOCK);
            if( rc!=SQLITE.OK ){
            /* If the attempt to grab the exclusive lock failed, release the
            ** pending lock that may have been obtained instead.  */
            pagerUnlockDb(pPager, SHARED_LOCK);
            }

            return rc;
        }
Ejemplo n.º 3
0
 public void ClearState()
 {
     this.Data = null;
     this.Extra = null;
     this.Dirtys = null;
     this.ID = 0;
     this.Pager = null;
     #if DEBUG
     this.PageHash = 0;
     #endif
     this.Flags = 0;
     this.Refs = 0;
     this.CacheAllocated = false;
     this.Cache = null;
     this.DirtyNext = null;
     this.DirtyPrev = null;
     this.PgHdr1 = null;
 }
Ejemplo n.º 4
0
        /*
        ** Begin a read transaction on the WAL.
        **
        ** This routine used to be called "pagerOpenSnapshot()" because it essentially
        ** makes a snapshot of the database at the current point in time and preserves
        ** that snapshot for use by the reader in spite of concurrently changes by
        ** other writers or checkpointers.
        */
        static int pagerBeginReadTransaction(Pager* pPager)
        {
            int rc;                         /* Return code */
            int changed = 0;                /* True if cache must be reset */

            assert(pagerUseWal(pPager));
            assert(pPager.eState == PAGER_OPEN || pPager.eState == PAGER_READER);

            /* sqlite3WalEndReadTransaction() was not called for the previous
            ** transaction in locking_mode=EXCLUSIVE.  So call it now.  If we
            ** are in locking_mode=NORMAL and EndRead() was previously called,
            ** the duplicate call is harmless.
            */
            sqlite3WalEndReadTransaction(pPager.pWal);

            rc = sqlite3WalBeginReadTransaction(pPager.pWal, &changed);
            if (rc != SQLITE.OK || changed)
            {
                pager_reset(pPager);
            }

            return rc;
        }
Ejemplo n.º 5
0
 /*
 ** Return true if the underlying VFS for the given pager supports the
 ** primitives necessary for write-ahead logging.
 */
 int sqlite3PagerWalSupported(Pager *pPager)
 {
     const sqlite3_io_methods *pMethods = pPager.fd->pMethods;
     return pPager.exclusiveMode || (pMethods->iVersion>=2 && pMethods->xShmMap);
 }
Ejemplo n.º 6
0
 int sqlite3PagerWalCallback(Pager *pPager)
 {
     return sqlite3WalCallback(pPager.pWal);
 }
Ejemplo n.º 7
0
        /*
        ** The caller must be holding a SHARED lock on the database file to call
        ** this function.
        **
        ** If the pager passed as the first argument is open on a real database
        ** file (not a temp file or an in-memory database), and the WAL file
        ** is not already open, make an attempt to open it now. If successful,
        ** return SQLITE.OK. If an error occurs or the VFS used by the pager does
        ** not support the xShmXXX() methods, return an error code. *pbOpen is
        ** not modified in either case.
        **
        ** If the pager is open on a temp-file (or in-memory database), or if
        ** the WAL file is already open, set *pbOpen to 1 and return SQLITE.OK
        ** without doing anything.
        */
        int sqlite3PagerOpenWal(
            Pager *pPager,                  /* Pager object */
            int *pbOpen                     /* OUT: Set to true if call is a no-op */
            )
        {
            int rc = SQLITE.OK;             /* Return code */

            assert( assert_pager_state(pPager) );
            assert( pPager.eState==PAGER_OPEN   || pbOpen );
            assert( pPager.eState==PAGER_READER || !pbOpen );
            assert( pbOpen==0 || *pbOpen==0 );
            assert( pbOpen!=0 || (!pPager.tempFile && !pPager.pWal) );

            if( !pPager.tempFile && !pPager.pWal ){
            if( !sqlite3PagerWalSupported(pPager) ) return SQLITE_CANTOPEN;

            /* Close any rollback journal previously open */
            sqlite3OsClose(pPager.jfd);

            rc = pagerOpenWal(pPager);
            if( rc==SQLITE.OK ){
            pPager.journalMode = PAGER_JOURNALMODE_WAL;
            pPager.eState = PAGER_OPEN;
            }
            }else{
            *pbOpen = 1;
            }

            return rc;
        }
Ejemplo n.º 8
0
        /*
        ** This function is called to close the connection to the log file prior
        ** to switching from WAL to rollback mode.
        **
        ** Before closing the log file, this function attempts to take an
        ** EXCLUSIVE lock on the database file. If this cannot be obtained, an
        ** error (SQLITE_BUSY) is returned and the log connection is not closed.
        ** If successful, the EXCLUSIVE lock is not released before returning.
        */
        int sqlite3PagerCloseWal(Pager *pPager)
        {
            int rc = SQLITE.OK;

            assert( pPager.journalMode==PAGER_JOURNALMODE_WAL );

            /* If the log file is not already open, but does exist in the file-system,
            ** it may need to be checkpointed before the connection can switch to
            ** rollback mode. Open it now so this can happen.
            */
            if( !pPager.pWal ){
            int logexists = 0;
            rc = pagerLockDb(pPager, SHARED_LOCK);
            if( rc==SQLITE.OK ){
            rc = sqlite3OsAccess(
            pPager.pVfs, pPager.zWal, SQLITE_ACCESS_EXISTS, &logexists
            );
            }
            if( rc==SQLITE.OK && logexists ){
            rc = pagerOpenWal(pPager);
            }
            }

            /* Checkpoint and close the log. Because an EXCLUSIVE lock is held on
            ** the database file, the log and log-summary files will be deleted.
            */
            if( rc==SQLITE.OK && pPager.pWal ){
            rc = pagerExclusiveLock(pPager);
            if( rc==SQLITE.OK ){
            rc = sqlite3WalClose(pPager.pWal, pPager.ckptSyncFlags,
               pPager.pageSize, (u8*)pPager.pTmpSpace);
            pPager.pWal = 0;
            }
            }
            return rc;
        }
Ejemplo n.º 9
0
 /*
 ** This function is called when the user invokes "PRAGMA wal_checkpoint",
 ** "PRAGMA wal_blocking_checkpoint" or calls the sqlite3_wal_checkpoint()
 ** or wal_blocking_checkpoint() API functions.
 **
 ** Parameter eMode is one of SQLITE_CHECKPOINT_PASSIVE, FULL or RESTART.
 */
 int sqlite3PagerCheckpoint(Pager *pPager, int eMode, int *pnLog, int *pnCkpt)
 {
     int rc = SQLITE.OK;
       if( pPager.pWal ){
     rc = sqlite3WalCheckpoint(pPager.pWal, eMode,
     pPager.xBusyHandler, pPager.pBusyHandlerArg,
     pPager.ckptSyncFlags, pPager.pageSize, (u8 *)pPager.pTmpSpace,
     pnLog, pnCkpt
     );
       }
       return rc;
 }
Ejemplo n.º 10
0
        // was:sqlite3BtreeDelete
        public RC Delete()
        {
            MemPage pPage;                      // Page to delete cell from
            int     pCell;                      // Pointer to cell to delete
            int     iCellIdx;                   // Index of cell to delete
            int     iCellDepth;                 // Depth of node containing pCell
            var     p   = this.Tree;
            var     pBt = p.Shared;

            Debug.Assert(HoldsMutex());
            Debug.Assert(pBt.InTransaction == TRANS.WRITE);
            Debug.Assert(!pBt.ReadOnly);
            Debug.Assert(this.Writeable);
            Debug.Assert(p.hasSharedCacheTableLock(this.RootID, (this.KeyInfo != null), LOCK.WRITE));
            Debug.Assert(!p.hasReadConflicts(this.RootID));
            if (Check.NEVER(this.PagesIndexs[this.PageID] >= this.Pages[this.PageID].Cells) || Check.NEVER(this.State != CursorState.VALID))
            {
                return(RC.ERROR);
            }
            // If this is a delete operation to remove a row from a table b-tree, invalidate any incrblob cursors open on the row being deleted.
            if (this.KeyInfo == null)
            {
                Btree.invalidateIncrblobCursors(p, this.Info.nKey, false);
            }
            iCellDepth = this.PageID;
            iCellIdx   = this.PagesIndexs[iCellDepth];
            pPage      = this.Pages[iCellDepth];
            pCell      = pPage.FindCell(iCellIdx);
            // If the page containing the entry to delete is not a leaf page, move the cursor to the largest entry in the tree that is smaller than
            // the entry being deleted. This cell will replace the cell being deleted from the internal node. The 'previous' entry is used for this instead
            // of the 'next' entry, as the previous entry is always a part of the sub-tree headed by the child page of the cell being deleted. This makes
            // balancing the tree following the delete operation easier.
            RC rc;

            if (pPage.Leaf == 0)
            {
                var notUsed = 0;
                rc = MovePrevious(ref notUsed);
                if (rc != RC.OK)
                {
                    return(rc);
                }
            }
            // Save the positions of any other cursors open on this table before making any modifications. Make the page containing the entry to be
            // deleted writable. Then free any overflow pages associated with the entry and finally remove the cell itself from within the page.
            rc = pBt.saveAllCursors(this.RootID, this);
            if (rc != RC.OK)
            {
                return(rc);
            }
            rc = Pager.Write(pPage.DbPage);
            if (rc != RC.OK)
            {
                return(rc);
            }
            rc = pPage.clearCell(pCell);
            pPage.dropCell(iCellIdx, pPage.cellSizePtr(pCell), ref rc);
            if (rc != RC.OK)
            {
                return(rc);
            }
            // If the cell deleted was not located on a leaf page, then the cursor is currently pointing to the largest entry in the sub-tree headed
            // by the child-page of the cell that was just deleted from an internal node. The cell from the leaf node needs to be moved to the internal
            // node to replace the deleted cell.
            if (pPage.Leaf == 0)
            {
                var pLeaf = this.Pages[this.PageID];
                int nCell;
                var n = this.Pages[iCellDepth + 1].ID;
                pCell = pLeaf.FindCell(pLeaf.Cells - 1);
                nCell = pLeaf.cellSizePtr(pCell);
                Debug.Assert(Btree.MX_CELL_SIZE(pBt) >= nCell);
                rc = Pager.Write(pLeaf.DbPage);
                var pNext_4 = MallocEx.sqlite3Malloc(nCell + 4);
                Buffer.BlockCopy(pLeaf.Data, pCell - 4, pNext_4, 0, nCell + 4);
                pPage.insertCell(iCellIdx, pNext_4, nCell + 4, null, n, ref rc);
                pLeaf.dropCell(pLeaf.Cells - 1, nCell, ref rc);
                if (rc != RC.OK)
                {
                    return(rc);
                }
            }
            // Balance the tree. If the entry deleted was located on a leaf page, then the cursor still points to that page. In this case the first
            // call to balance() repairs the tree, and the if(...) condition is never true.
            //
            // Otherwise, if the entry deleted was on an internal node page, then pCur is pointing to the leaf page from which a cell was removed to
            // replace the cell deleted from the internal node. This is slightly tricky as the leaf node may be underfull, and the internal node may
            // be either under or overfull. In this case run the balancing algorithm on the leaf node first. If the balance proceeds far enough up the
            // tree that we can be sure that any problem in the internal node has been corrected, so be it. Otherwise, after balancing the leaf node,
            // walk the cursor up the tree to the internal node and balance it as well.
            rc = Balance();
            if (rc == RC.OK && this.PageID > iCellDepth)
            {
                while (this.PageID > iCellDepth)
                {
                    this.Pages[this.PageID--].releasePage();
                }
                rc = Balance();
            }
            if (rc == RC.OK)
            {
                MoveToRoot();
            }
            return(rc);
        }
Ejemplo n.º 11
0
 private static int PAGERID(Pager p)
 {
     return p.GetHashCode();
 }
Ejemplo n.º 12
0
        // This function is called to rollback a transaction on a WAL database.
        static int pagerRollbackWal(Pager pPager)
        {
            int rc;                         /* Return Code */
            PgHdr pList;                   /* List of dirty pages to revert */

            /* For all pages in the cache that are currently dirty or have already been written (but not committed) to the log file, do one of the
            ** following:
            **   + Discard the cached page (if refcount==0), or
            **   + Reload page content from the database (if refcount>0).
            */
            pPager.dbSize = pPager.dbOrigSize;
            rc = sqlite3WalUndo(pPager.pWal, pagerUndoCallback, pPager);
            pList = sqlite3PcacheDirtyList(pPager.pPCache);
            while (pList && rc == SQLITE.OK)
            {
                PgHdr pNext = pList.pDirty;
                rc = pagerUndoCallback(pPager, pList.pgno);
                pList = pNext;
            }

            return rc;
        }
Ejemplo n.º 13
0
        /*
        ** Check if the *-wal file that corresponds to the database opened by pPager
        ** exists if the database is not empy, or verify that the *-wal file does
        ** not exist (by deleting it) if the database file is empty.
        **
        ** If the database is not empty and the *-wal file exists, open the pager
        ** in WAL mode.  If the database is empty or if no *-wal file exists and
        ** if no error occurs, make sure Pager.journalMode is not set to
        ** PAGER_JOURNALMODE_WAL.
        **
        ** Return SQLITE.OK or an error code.
        **
        ** The caller must hold a SHARED lock on the database file to call this
        ** function. Because an EXCLUSIVE lock on the db file is required to delete
        ** a WAL on a none-empty database, this ensures there is no race condition
        ** between the xAccess() below and an xDelete() being executed by some
        ** other connection.
        */
        static int pagerOpenWalIfPresent(Pager* pPager)
        {
            int rc = SQLITE.OK;
            Debug.Assert(pPager.eState == PAGER_OPEN);
            Debug.Assert(pPager.eLock >= SHARED_LOCK || pPager.noReadlock);

            if (!pPager.tempFile)
            {
                int isWal;                    /* True if WAL file exists */
                Pgno nPage;                   /* Size of the database file */

                rc = pagerPagecount(pPager, &nPage);
                if (rc) return rc;
                if (nPage == 0)
                {
                    rc = sqlite3OsDelete(pPager.pVfs, pPager.zWal, 0);
                    isWal = 0;
                }
                else
                {
                    rc = sqlite3OsAccess(
                    pPager.pVfs, pPager.zWal, SQLITE_ACCESS_EXISTS, &isWal
                    );
                }
                if (rc == SQLITE.OK)
                {
                    if (isWal)
                    {
                        testcase(sqlite3PcachePagecount(pPager.pPCache) == 0);
                        rc = sqlite3PagerOpenWal(pPager, 0);
                    }
                    else if (pPager.journalMode == PAGER_JOURNALMODE_WAL)
                    {
                        pPager.journalMode = PAGER_JOURNALMODE_DELETE;
                    }
                }
            }
            return rc;
        }
Ejemplo n.º 14
0
 private static uint JOURNAL_PG_SZ(Pager pPager)
 {
     return (uint)pPager.pageSize + 8;
 }
Ejemplo n.º 15
0
 private static uint JOURNAL_HDR_SZ(Pager pPager)
 {
     return pPager.sectorSize;
 }
Ejemplo n.º 16
0
        // was:sqlite3BtreeInsert
        public RC Insert(byte[] key, long nKey, byte[] data, int nData, int nZero, bool appendBiasRight, int seekResult)
        {
            var loc   = seekResult; // -1: before desired location  +1: after
            var szNew = 0;
            int idx;
            var p   = this.Tree;
            var pBt = p.Shared;
            int oldCell;

            byte[] newCell = null;
            if (this.State == CursorState.FAULT)
            {
                Debug.Assert(this.SkipNext != 0);
                return((RC)this.SkipNext);
            }
            Debug.Assert(HoldsMutex());
            Debug.Assert(this.Writeable && pBt.InTransaction == TRANS.WRITE && !pBt.ReadOnly);
            Debug.Assert(p.hasSharedCacheTableLock(this.RootID, (this.KeyInfo != null), LOCK.WRITE));
            // Assert that the caller has been consistent. If this cursor was opened expecting an index b-tree, then the caller should be inserting blob
            // keys with no associated data. If the cursor was opened expecting an intkey table, the caller should be inserting integer keys with a
            // blob of associated data.
            Debug.Assert((key == null) == (this.KeyInfo == null));
            // If this is an insert into a table b-tree, invalidate any incrblob cursors open on the row being replaced (assuming this is a replace
            // operation - if it is not, the following is a no-op).  */
            if (this.KeyInfo == null)
            {
                Btree.invalidateIncrblobCursors(p, nKey, false);
            }
            // Save the positions of any other cursors open on this table.
            // In some cases, the call to btreeMoveto() below is a no-op. For example, when inserting data into a table with auto-generated integer
            // keys, the VDBE layer invokes sqlite3BtreeLast() to figure out the integer key to use. It then calls this function to actually insert the
            // data into the intkey B-Tree. In this case btreeMoveto() recognizes that the cursor is already where it needs to be and returns without
            // doing any work. To avoid thwarting these optimizations, it is important not to clear the cursor here.
            var rc = pBt.saveAllCursors(this.RootID, this);

            if (rc != RC.OK)
            {
                return(rc);
            }
            if (loc == 0)
            {
                rc = BtreeMoveTo(key, nKey, appendBiasRight, ref loc);
                if (rc != RC.OK)
                {
                    return(rc);
                }
            }
            Debug.Assert(this.State == CursorState.VALID || (this.State == CursorState.INVALID && loc != 0));
            var pPage = this.Pages[this.PageID];

            Debug.Assert(pPage.HasIntKey || nKey >= 0);
            Debug.Assert(pPage.Leaf != 0 || !pPage.HasIntKey);
            Btree.TRACE("INSERT: table=%d nkey=%lld ndata=%d page=%d %s\n", this.RootID, nKey, nData, pPage.ID, (loc == 0 ? "overwrite" : "new entry"));
            Debug.Assert(pPage.HasInit);
            pBt.allocateTempSpace();
            newCell = pBt.pTmpSpace;
            rc      = pPage.fillInCell(newCell, key, nKey, data, nData, nZero, ref szNew);
            if (rc != RC.OK)
            {
                goto end_insert;
            }
            Debug.Assert(szNew == pPage.cellSizePtr(newCell));
            Debug.Assert(szNew <= Btree.MX_CELL_SIZE(pBt));
            idx = this.PagesIndexs[this.PageID];
            if (loc == 0)
            {
                ushort szOld;
                Debug.Assert(idx < pPage.Cells);
                rc = Pager.Write(pPage.DbPage);
                if (rc != RC.OK)
                {
                    goto end_insert;
                }
                oldCell = pPage.FindCell(idx);
                if (0 == pPage.Leaf)
                {
                    newCell[0] = pPage.Data[oldCell + 0];
                    newCell[1] = pPage.Data[oldCell + 1];
                    newCell[2] = pPage.Data[oldCell + 2];
                    newCell[3] = pPage.Data[oldCell + 3];
                }
                szOld = pPage.cellSizePtr(oldCell);
                rc    = pPage.clearCell(oldCell);
                pPage.dropCell(idx, szOld, ref rc);
                if (rc != RC.OK)
                {
                    goto end_insert;
                }
            }
            else if (loc < 0 && pPage.Cells > 0)
            {
                Debug.Assert(pPage.Leaf != 0);
                idx = ++this.PagesIndexs[this.PageID];
            }
            else
            {
                Debug.Assert(pPage.Leaf != 0);
            }
            pPage.insertCell(idx, newCell, szNew, null, 0, ref rc);
            Debug.Assert(rc != RC.OK || pPage.Cells > 0 || pPage.NOverflows > 0);
            // If no error has occured and pPage has an overflow cell, call balance() to redistribute the cells within the tree. Since balance() may move
            // the cursor, zero the BtCursor.info.nSize and BtCursor.validNKey variables.
            // Previous versions of SQLite called moveToRoot() to move the cursor back to the root page as balance() used to invalidate the contents
            // of BtCursor.apPage[] and BtCursor.aiIdx[]. Instead of doing that, set the cursor state to "invalid". This makes common insert operations
            // slightly faster.
            // There is a subtle but important optimization here too. When inserting multiple records into an intkey b-tree using a single cursor (as can
            // happen while processing an "INSERT INTO ... SELECT" statement), it is advantageous to leave the cursor pointing to the last entry in
            // the b-tree if possible. If the cursor is left pointing to the last entry in the table, and the next row inserted has an integer key
            // larger than the largest existing key, it is possible to insert the row without seeking the cursor. This can be a big performance boost.
            this.Info.nSize = 0;
            this.ValidNKey  = false;
            if (rc == RC.OK && pPage.NOverflows != 0)
            {
                rc = Balance();
                // Must make sure nOverflow is reset to zero even if the balance() fails. Internal data structure corruption will result otherwise.
                // Also, set the cursor state to invalid. This stops saveCursorPosition() from trying to save the current position of the cursor.
                this.Pages[this.PageID].NOverflows = 0;
                this.State = CursorState.INVALID;
            }
            Debug.Assert(this.Pages[this.PageID].NOverflows == 0);
end_insert:
            return(rc);
        }
Ejemplo n.º 17
0
        // This function is invoked once for each page that has already been written into the log file when a WAL transaction is rolled back.
        // Parameter iPg is the page number of said page. The pCtx argument is actually a pointer to the Pager structure.
        //
        // If page iPg is present in the cache, and has no outstanding references, it is discarded. Otherwise, if there are one or more outstanding
        // references, the page content is reloaded from the database. If the attempt to reload content from the database is required and fails,
        // return an SQLite error code. Otherwise, SQLITE.OK.
        static int pagerUndoCallback(Pager pCtx, Pgno iPg)
        {
            var rc = SQLITE.OK;
            Pager pPager = (Pager)pCtx;
            PgHdr pPg;

            pPg = sqlite3PagerLookup(pPager, iPg);
            if (pPg)
            {
                if (sqlite3PcachePageRefcount(pPg) == 1)
                {
                    sqlite3PcacheDrop(pPg);
                }
                else
                {
                    rc = readDbPage(pPg);
                    if (rc == SQLITE.OK)
                    {
                        pPager.xReiniter(pPg);
                    }
                    sqlite3PagerUnref(pPg);
                }
            }
            // Normally, if a transaction is rolled back, any backup processes are updated as data is copied out of the rollback journal and into the
            // database. This is not generally possible with a WAL database, as rollback involves simply truncating the log file. Therefore, if one
            // or more frames have already been written to the log (and therefore also copied into the backup databases) as part of this transaction,
            // the backups must be restarted.
            sqlite3BackupRestart(pPager.pBackup);
            return rc;
        }
Ejemplo n.º 18
0
        /*
        ** This function is a wrapper around sqlite3WalFrames(). As well as logging
        ** the contents of the list of pages headed by pList (connected by pDirty),
        ** this function notifies any active backup processes that the pages have
        ** changed.
        **
        ** The list of pages passed into this routine is always sorted by page number.
        ** Hence, if page 1 appears anywhere on the list, it will be the first page.
        */
        static int pagerWalFrames(Pager pPager, PgHdr pList, Pgno nTruncate, int isCommit, int syncFlags)
        {
            int rc;                         /* Return code */
            #if DEBUG || (SQLITE_CHECK_PAGES)
            PgHdr p;                       /* For looping over pages */
            #endif

            Debug.Assert(pPager.pWal);
            #if SQLITE_DEBUG
            /* Verify that the page list is in accending order */
            for(p=pList; p && p->pDirty; p=p->pDirty){
            assert( p->pgno < p->pDirty->pgno );
            }
            #endif

            if (isCommit)
            {
                /* If a WAL transaction is being committed, there is no point in writing
                ** any pages with page numbers greater than nTruncate into the WAL file.
                ** They will never be read by any client. So remove them from the pDirty
                ** list here. */
                PgHdr* p;
                PgHdr** ppNext = &pList;
                for (p = pList; (*ppNext = p); p = p->pDirty)
                {
                    if (p->pgno <= nTruncate) ppNext = &p->pDirty;
                }
                assert(pList);
            }

            if (pList->pgno == 1) pager_write_changecounter(pList);
            rc = sqlite3WalFrames(pPager.pWal,
            pPager.pageSize, pList, nTruncate, isCommit, syncFlags
            );
            if (rc == SQLITE.OK && pPager.pBackup)
            {
                PgHdr* p;
                for (p = pList; p; p = p->pDirty)
                {
                    sqlite3BackupUpdate(pPager.pBackup, p->pgno, (u8*)p->pData);
                }
            }

            #if SQLITE_CHECK_PAGES
            pList = sqlite3PcacheDirtyList(pPager.pPCache);
            for(p=pList; p; p=p->pDirty){
            pager_set_pagehash(p);
            }
            #endif

            return rc;
        }
Ejemplo n.º 19
0
        // was:sqlite3PagerOpen
        public static RC Open(VirtualFileSystem pVfs, out Pager ppPager, string zFilename, int nExtra, PAGEROPEN flags, VFSOPEN vfsFlags, Action<PgHdr> xReinit, Func<object> memPageBuilder)
        {
            Pager pPager = null;     // Pager object to allocate and return
            byte memDb = 0;            // True if this is an in-memory file
            bool readOnly = false;   // True if this is a read-only file
            string zPathname = null; // Full path to database file
            //int nPathname = 0;       // Number of bytes in zPathname
            bool useJournal = (flags & PAGEROPEN.OMIT_JOURNAL) == 0; // False to omit journal
            bool noReadlock = (flags & PAGEROPEN.NO_READLOCK) != 0;  // True to omit read-lock
            int pcacheSize = PCache.sqlite3PcacheSize();       // Bytes to allocate for PCache
            uint szPageDflt = SQLITE_DEFAULT_PAGE_SIZE;  // Default page size
            string zUri = null;     // URI args to copy
            int nUri = 0;           // Number of bytes of URI args at *zUri
            // Figure out how much space is required for each journal file-handle (there are two of them, the main journal and the sub-journal). This
            // is the maximum space required for an in-memory journal file handle and a regular journal file-handle. Note that a "regular journal-handle"
            // may be a wrapper capable of caching the first portion of the journal file in memory to implement the atomic-write optimization (see
            // source file journal.c).
            int journalFileSize = SysEx.ROUND8(sqlite3JournalSize(pVfs) > MemJournalFile.sqlite3MemJournalSize() ? sqlite3JournalSize(pVfs) : MemJournalFile.sqlite3MemJournalSize()); // Bytes to allocate for each journal fd
            // Set the output variable to NULL in case an error occurs.
            ppPager = null;
            #if !SQLITE_OMIT_MEMORYDB
            if ((flags & PAGEROPEN.MEMORY) != 0)
            {
                memDb = 1;
                zFilename = null;
            }
            #endif
            // Compute and store the full pathname in an allocated buffer pointed to by zPathname, length nPathname. Or, if this is a temporary file,
            // leave both nPathname and zPathname set to 0.
            var rc = RC.OK;
            if (!string.IsNullOrEmpty(zFilename))
            {
                rc = pVfs.xFullPathname(zFilename, out zPathname);
                var z = zUri = zFilename;
                nUri = zUri.Length;
                if (rc == RC.OK && zPathname.Length + 8 > pVfs.mxPathname)
                    // This branch is taken when the journal path required by the database being opened will be more than pVfs.mxPathname
                    // bytes in length. This means the database cannot be opened, as it will not be possible to open the journal file or even
                    // check for a hot-journal before reading.
                    rc = SysEx.SQLITE_CANTOPEN_BKPT();
                if (rc != RC.OK)
                    return rc;
            }
            // Allocate memory for the Pager structure, PCache object, the three file descriptors, the database file name and the journal
            // file name. The layout in memory is as follows:
            //     Pager object                    (sizeof(Pager) bytes)
            //     PCache object                   (sqlite3PcacheSize() bytes)
            //     Database file handle            (pVfs.szOsFile bytes)
            //     Sub-journal file handle         (journalFileSize bytes)
            //     Main journal file handle        (journalFileSize bytes)
            //     Database file name              (nPathname+1 bytes)
            //     Journal file name               (nPathname+8+1 bytes)
            pPager = new Pager(memPageBuilder);
            pPager.pPCache = new PCache();
            pPager.fd = new VirtualFile();
            pPager.sjfd = new VirtualFile();
            pPager.jfd = new VirtualFile();
            // Fill in the Pager.zFilename and Pager.zJournal buffers, if required.
            if (zPathname != null)
            {
                Debug.Assert(zPathname.Length > 0);
                pPager.zFilename = zPathname.ToString();
                zUri = pPager.zFilename;
                pPager.zJournal = pPager.zFilename + "-journal";
            #if !SQLITE_OMIT_WAL
                pPager.zWal = &pPager.zJournal[nPathname + 8 + 1];
                memcpy(pPager.zWal, zPathname, nPathname);
                memcpy(&pPager.zWal[nPathname], "-wal", 4);
            #endif
            }
            else
                pPager.zFilename = string.Empty;
            pPager.pVfs = pVfs;
            pPager.vfsFlags = vfsFlags;
            // Open the pager file.
            var tempFile = LOCKINGMODE.NORMAL;         // True for temp files (incl. in-memory files)
            if (!string.IsNullOrEmpty(zFilename))
            {
                VFSOPEN fout = 0; // VFS flags returned by xOpen()
                rc = FileEx.sqlite3OsOpen(pVfs, zFilename, pPager.fd, vfsFlags, ref fout);
                Debug.Assert(0 == memDb);
                readOnly = (fout & VFSOPEN.READONLY) != 0;
                // If the file was successfully opened for read/write access, choose a default page size in case we have to create the
                // database file. The default page size is the maximum of:
                //    + SQLITE_DEFAULT_PAGE_SIZE,
                //    + The value returned by sqlite3OsSectorSize()
                //    + The largest page size that can be written atomically.
                if (rc == RC.OK && !readOnly)
                {
                    pPager.setSectorSize();
                    Debug.Assert(SQLITE_DEFAULT_PAGE_SIZE <= SQLITE_MAX_DEFAULT_PAGE_SIZE);
                    if (szPageDflt < pPager.sectorSize)
                        szPageDflt = (pPager.sectorSize > SQLITE_MAX_DEFAULT_PAGE_SIZE ? SQLITE_MAX_DEFAULT_PAGE_SIZE : (uint)pPager.sectorSize);
            #if SQLITE_ENABLE_ATOMIC_WRITE
                    int iDc = sqlite3OsDeviceCharacteristics(pPager.fd);
                    Debug.Assert(SQLITE_IOCAP_ATOMIC512 == (512 >> 8));
                    Debug.Assert(SQLITE_IOCAP_ATOMIC64K == (65536 >> 8));
                    Debug.Assert(SQLITE_MAX_DEFAULT_PAGE_SIZE <= 65536);
                    for (var ii = szPageDflt; ii <= SQLITE_MAX_DEFAULT_PAGE_SIZE; ii = ii * 2)
                        if (iDc & (SQLITE_IOCAP_ATOMIC | (ii >> 8)))
                            szPageDflt = ii;
            #endif
                }
            }
            else
            {
                // If a temporary file is requested, it is not opened immediately. In this case we accept the default page size and delay actually
                // opening the file until the first call to OsWrite().
                // This branch is also run for an in-memory database. An in-memory database is the same as a temp-file that is never written out to
                // disk and uses an in-memory rollback journal.
                tempFile = LOCKINGMODE.EXCLUSIVE;
                pPager.eState = PAGER.READER;
                pPager.eLock = VFSLOCK.EXCLUSIVE;
                readOnly = (vfsFlags & VFSOPEN.READONLY) != 0;
            }

            // The following call to PagerSetPagesize() serves to set the value of Pager.pageSize and to allocate the Pager.pTmpSpace buffer.
            if (rc == RC.OK)
            {
                Debug.Assert(pPager.memDb == 0);
                rc = pPager.SetPageSize(ref szPageDflt, -1);
            }
            // If an error occurred in either of the blocks above, free the Pager structure and close the file.
            if (rc != RC.OK)
            {
                Debug.Assert(null == pPager.pTmpSpace);
                FileEx.sqlite3OsClose(pPager.fd);
                return rc;
            }
            // Initialize the PCache object.
            Debug.Assert(nExtra < 1000);
            nExtra = SysEx.ROUND8(nExtra);
            PCache.Open((int)szPageDflt, nExtra, (memDb == 0), (memDb == 0 ? (Func<object, PgHdr, RC>)pagerStress : null), pPager, pPager.pPCache);
            PAGERTRACE("OPEN {0} {1}", FILEHANDLEID(pPager.fd), pPager.zFilename);
            SysEx.IOTRACE("OPEN {0:x} {1}", pPager.GetHashCode(), pPager.zFilename);
            pPager.useJournal = (byte)(useJournal ? 1 : 0);
            pPager.noReadlock = (byte)(noReadlock && readOnly ? 1 : 0);
            pPager.mxPgno = SQLITE_MAX_PAGE_COUNT;
            #if false
            Debug.Assert(pPager.state == (tempFile != 0 ? PAGER.EXCLUSIVE : PAGER.UNLOCK));
            #endif
            pPager.tempFile = tempFile != 0;
            Debug.Assert(tempFile == LOCKINGMODE.NORMAL || tempFile == LOCKINGMODE.EXCLUSIVE);
            pPager.exclusiveMode = tempFile != 0;
            pPager.changeCountDone = pPager.tempFile;
            pPager.memDb = memDb;
            pPager.readOnly = readOnly;
            Debug.Assert(useJournal || pPager.tempFile);
            pPager.noSync = pPager.tempFile;
            pPager.fullSync = pPager.noSync;
            pPager.syncFlags = (pPager.noSync ? 0 : VirtualFile.SYNC.NORMAL);
            pPager.ckptSyncFlags = pPager.syncFlags;
            pPager.nExtra = (ushort)nExtra;
            pPager.journalSizeLimit = SQLITE_DEFAULT_JOURNAL_SIZE_LIMIT;
            Debug.Assert(pPager.fd.IsOpen || tempFile != 0);
            pPager.setSectorSize();
            if (!useJournal)
                pPager.journalMode = JOURNALMODE.OFF;
            else if (memDb != 0)
                pPager.journalMode = JOURNALMODE.MEMORY;
            pPager.xReiniter = xReinit;
            ppPager = pPager;
            return RC.OK;
        }
Ejemplo n.º 20
0
        // was:balance
        private RC Balance()
        {
            var rc   = RC.OK;
            var nMin = (int)this.Shared.UsableSize * 2 / 3;
            var balance_quick_called  = 0;
            var balance_deeper_called = 0;

            do
            {
                var pageID = this.PageID;
                var page   = this.Pages[pageID];
                if (pageID == 0)
                {
                    if (page.NOverflows != 0)
                    {
                        // The root page of the b-tree is overfull. In this case call the balance_deeper() function to create a new child for the root-page
                        // and copy the current contents of the root-page to it. The next iteration of the do-loop will balance the child page.
                        Debug.Assert((balance_deeper_called++) == 0);
                        rc = MemPage.balance_deeper(page, ref this.Pages[1]);
                        if (rc == RC.OK)
                        {
                            this.PageID         = 1;
                            this.PagesIndexs[0] = 0;
                            this.PagesIndexs[1] = 0;
                            Debug.Assert(this.Pages[1].NOverflows != 0);
                        }
                    }
                    else
                    {
                        break;
                    }
                }
                else if (page.NOverflows == 0 && page.FreeBytes <= nMin)
                {
                    break;
                }
                else
                {
                    var pParent = this.Pages[pageID - 1];
                    var iIdx    = this.PagesIndexs[pageID - 1];
                    rc = Pager.Write(pParent.DbPage);
                    if (rc == RC.OK)
                    {
#if !SQLITE_OMIT_QUICKBALANCE
                        if (page.HasData != 0 && page.NOverflows == 1 && page.Overflows[0].Index == page.Cells && pParent.ID != 1 && pParent.Cells == iIdx)
                        {
                            // Call balance_quick() to create a new sibling of pPage on which to store the overflow cell. balance_quick() inserts a new cell
                            // into pParent, which may cause pParent overflow. If this happens, the next interation of the do-loop will balance pParent
                            // use either balance_nonroot() or balance_deeper(). Until this happens, the overflow cell is stored in the aBalanceQuickSpace[]
                            // buffer.
                            // The purpose of the following Debug.Assert() is to check that only a single call to balance_quick() is made for each call to this
                            // function. If this were not verified, a subtle bug involving reuse of the aBalanceQuickSpace[] might sneak in.
                            Debug.Assert((balance_quick_called++) == 0);
                            rc = MemPage.balance_quick(pParent, page, _balanceQuickSpaces);
                        }
                        else
#endif
                        {
                            // In this case, call balance_nonroot() to redistribute cells between pPage and up to 2 of its sibling pages. This involves
                            // modifying the contents of pParent, which may cause pParent to become overfull or underfull. The next iteration of the do-loop
                            // will balance the parent page to correct this.
                            // If the parent page becomes overfull, the overflow cell or cells are stored in the pSpace buffer allocated immediately below.
                            // A subsequent iteration of the do-loop will deal with this by calling balance_nonroot() (balance_deeper() may be called first,
                            // but it doesn't deal with overflow cells - just moves them to a different page). Once this subsequent call to balance_nonroot()
                            // has completed, it is safe to release the pSpace buffer used by the previous call, as the overflow cell data will have been
                            // copied either into the body of a database page or into the new pSpace buffer passed to the latter call to balance_nonroot().
                            var pSpace = new byte[this.Shared.PageSize];// u8 pSpace = sqlite3PageMalloc( pCur.pBt.pageSize );
                            rc = MemPage.balance_nonroot(pParent, iIdx, null, pageID == 1 ? 1 : 0);
                            // The pSpace buffer will be freed after the next call to balance_nonroot(), or just before this function returns, whichever comes first.
                        }
                    }
                    page.NOverflows = 0;
                    // The next iteration of the do-loop balances the parent page.
                    page.releasePage();
                    this.PageID--;
                }
            } while (rc == RC.OK);
            return(rc);
        }