/* ** 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; }
/* ** 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; }
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; }
/* ** 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; }
/* ** 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); }
int sqlite3PagerWalCallback(Pager *pPager) { return sqlite3WalCallback(pPager.pWal); }
/* ** 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; }
/* ** 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; }
/* ** 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; }
// 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); }
private static int PAGERID(Pager p) { return p.GetHashCode(); }
// 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; }
/* ** 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; }
private static uint JOURNAL_PG_SZ(Pager pPager) { return (uint)pPager.pageSize + 8; }
private static uint JOURNAL_HDR_SZ(Pager pPager) { return pPager.sectorSize; }
// 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); }
// 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; }
/* ** 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; }
// 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; }
// 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); }