aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/os_unix.c83
-rw-r--r--src/os_win.c160
-rw-r--r--src/pager.c1415
-rw-r--r--src/sqlite.h.in9
-rw-r--r--src/test1.c39
-rw-r--r--src/test_stat.c14
-rw-r--r--src/wal.c25
-rw-r--r--src/wal.h7
8 files changed, 1100 insertions, 652 deletions
diff --git a/src/os_unix.c b/src/os_unix.c
index 9457516ca..5c648e3f9 100644
--- a/src/os_unix.c
+++ b/src/os_unix.c
@@ -210,6 +210,7 @@ struct unixFile {
int fileFlags; /* Miscellanous flags */
const char *zPath; /* Name of the file */
unixShm *pShm; /* Shared memory segment information */
+ int szChunk; /* Configured by FCNTL_CHUNK_SIZE */
#if SQLITE_ENABLE_LOCKING_STYLE
int openFlags; /* The flags specified at open() */
#endif
@@ -2763,6 +2764,7 @@ static int unixWrite(
}
SimulateIOError(( wrote=(-1), amt=1 ));
SimulateDiskfullError(( wrote=0, amt=1 ));
+
if( amt>0 ){
if( wrote<0 ){
/* lastErrno set by seekAndWrite */
@@ -2772,6 +2774,7 @@ static int unixWrite(
return SQLITE_FULL;
}
}
+
return SQLITE_OK;
}
@@ -2973,12 +2976,23 @@ static int unixSync(sqlite3_file *id, int flags){
** Truncate an open file to a specified size
*/
static int unixTruncate(sqlite3_file *id, i64 nByte){
+ unixFile *pFile = (unixFile *)id;
int rc;
- assert( id );
+ assert( pFile );
SimulateIOError( return SQLITE_IOERR_TRUNCATE );
- rc = ftruncate(((unixFile*)id)->h, (off_t)nByte);
+
+ /* If the user has configured a chunk-size for this file, truncate the
+ ** file so that it consists of an integer number of chunks (i.e. the
+ ** actual file size after the operation may be larger than the requested
+ ** size).
+ */
+ if( pFile->szChunk ){
+ nByte = ((nByte + pFile->szChunk - 1)/pFile->szChunk) * pFile->szChunk;
+ }
+
+ rc = ftruncate(pFile->h, (off_t)nByte);
if( rc ){
- ((unixFile*)id)->lastErrno = errno;
+ pFile->lastErrno = errno;
return SQLITE_IOERR_TRUNCATE;
}else{
#ifndef NDEBUG
@@ -2989,8 +3003,8 @@ static int unixTruncate(sqlite3_file *id, i64 nByte){
** when restoring a database using the backup API from a zero-length
** source.
*/
- if( ((unixFile*)id)->inNormalWrite && nByte==0 ){
- ((unixFile*)id)->transCntrChng = 1;
+ if( pFile->inNormalWrite && nByte==0 ){
+ pFile->transCntrChng = 1;
}
#endif
@@ -3033,6 +3047,54 @@ static int unixFileSize(sqlite3_file *id, i64 *pSize){
static int proxyFileControl(sqlite3_file*,int,void*);
#endif
+/*
+** This function is called to handle the SQLITE_FCNTL_SIZE_HINT
+** file-control operation.
+**
+** If the user has configured a chunk-size for this file, it could be
+** that the file needs to be extended at this point. Otherwise, the
+** SQLITE_FCNTL_SIZE_HINT operation is a no-op for Unix.
+*/
+static int fcntlSizeHint(unixFile *pFile, i64 nByte){
+ if( pFile->szChunk ){
+ i64 nSize; /* Required file size */
+ struct stat buf; /* Used to hold return values of fstat() */
+
+ if( fstat(pFile->h, &buf) ) return SQLITE_IOERR_FSTAT;
+
+ nSize = ((nByte+pFile->szChunk-1) / pFile->szChunk) * pFile->szChunk;
+ if( nSize>(i64)buf.st_size ){
+#if defined(HAVE_POSIX_FALLOCATE) && HAVE_POSIX_FALLOCATE
+ if( posix_fallocate(pFile->h, buf.st_size, nSize-buf.st_size) ){
+ return SQLITE_IOERR_WRITE;
+ }
+#else
+ /* If the OS does not have posix_fallocate(), fake it. First use
+ ** ftruncate() to set the file size, then write a single byte to
+ ** the last byte in each block within the extended region. This
+ ** is the same technique used by glibc to implement posix_fallocate()
+ ** on systems that do not have a real fallocate() system call.
+ */
+ int nBlk = buf.st_blksize; /* File-system block size */
+ i64 iWrite; /* Next offset to write to */
+ int nWrite; /* Return value from seekAndWrite() */
+
+ if( ftruncate(pFile->h, nSize) ){
+ pFile->lastErrno = errno;
+ return SQLITE_IOERR_TRUNCATE;
+ }
+ iWrite = ((buf.st_size + 2*nBlk - 1)/nBlk)*nBlk-1;
+ do {
+ nWrite = seekAndWrite(pFile, iWrite, "", 1);
+ iWrite += nBlk;
+ } while( nWrite==1 && iWrite<nSize );
+ if( nWrite!=1 ) return SQLITE_IOERR_WRITE;
+#endif
+ }
+ }
+
+ return SQLITE_OK;
+}
/*
** Information and control of an open file handle.
@@ -3047,14 +3109,13 @@ static int unixFileControl(sqlite3_file *id, int op, void *pArg){
*(int*)pArg = ((unixFile*)id)->lastErrno;
return SQLITE_OK;
}
- case SQLITE_FCNTL_SIZE_HINT: {
-#if 0 /* No performance advantage seen on Linux */
- sqlite3_int64 szFile = *(sqlite3_int64*)pArg;
- unixFile *pFile = (unixFile*)id;
- ftruncate(pFile->h, szFile);
-#endif
+ case SQLITE_FCNTL_CHUNK_SIZE: {
+ ((unixFile*)id)->szChunk = *(int *)pArg;
return SQLITE_OK;
}
+ case SQLITE_FCNTL_SIZE_HINT: {
+ return fcntlSizeHint((unixFile *)id, *(i64 *)pArg);
+ }
#ifndef NDEBUG
/* The pager calls this method to signal that it has done
** a rollback and that the database is therefore unchanged and
diff --git a/src/os_win.c b/src/os_win.c
index 095131232..562282f20 100644
--- a/src/os_win.c
+++ b/src/os_win.c
@@ -108,6 +108,7 @@ struct winFile {
DWORD sectorSize; /* Sector size of the device file is on */
winShm *pShm; /* Instance of shared memory on this file */
const char *zPath; /* Full pathname of this file */
+ int szChunk; /* Chunk size configured by FCNTL_CHUNK_SIZE */
#if SQLITE_OS_WINCE
WCHAR *zDeleteOnClose; /* Name of file to delete when closing */
HANDLE hMutex; /* Mutex used to control access to shared lock */
@@ -621,6 +622,42 @@ static BOOL winceLockFileEx(
******************************************************************************/
/*
+** Some microsoft compilers lack this definition.
+*/
+#ifndef INVALID_SET_FILE_POINTER
+# define INVALID_SET_FILE_POINTER ((DWORD)-1)
+#endif
+
+/*
+** Move the current position of the file handle passed as the first
+** argument to offset iOffset within the file. If successful, return 0.
+** Otherwise, set pFile->lastErrno and return non-zero.
+*/
+static int seekWinFile(winFile *pFile, sqlite3_int64 iOffset){
+ LONG upperBits; /* Most sig. 32 bits of new offset */
+ LONG lowerBits; /* Least sig. 32 bits of new offset */
+ DWORD dwRet; /* Value returned by SetFilePointer() */
+
+ upperBits = (LONG)((iOffset>>32) & 0x7fffffff);
+ lowerBits = (LONG)(iOffset & 0xffffffff);
+
+ /* API oddity: If successful, SetFilePointer() returns a dword
+ ** containing the lower 32-bits of the new file-offset. Or, if it fails,
+ ** it returns INVALID_SET_FILE_POINTER. However according to MSDN,
+ ** INVALID_SET_FILE_POINTER may also be a valid new offset. So to determine
+ ** whether an error has actually occured, it is also necessary to call
+ ** GetLastError().
+ */
+ dwRet = SetFilePointer(pFile->h, lowerBits, &upperBits, FILE_BEGIN);
+ if( (dwRet==INVALID_SET_FILE_POINTER && GetLastError()!=NO_ERROR) ){
+ pFile->lastErrno = GetLastError();
+ return 1;
+ }
+
+ return 0;
+}
+
+/*
** Close a file.
**
** It is reported that an attempt to close a handle might sometimes
@@ -663,13 +700,6 @@ static int winClose(sqlite3_file *id){
}
/*
-** Some microsoft compilers lack this definition.
-*/
-#ifndef INVALID_SET_FILE_POINTER
-# define INVALID_SET_FILE_POINTER ((DWORD)-1)
-#endif
-
-/*
** Read data from a file into a buffer. Return SQLITE_OK if all
** bytes were read successfully and SQLITE_IOERR if anything goes
** wrong.
@@ -680,32 +710,27 @@ static int winRead(
int amt, /* Number of bytes to read */
sqlite3_int64 offset /* Begin reading at this offset */
){
- LONG upperBits = (LONG)((offset>>32) & 0x7fffffff);
- LONG lowerBits = (LONG)(offset & 0xffffffff);
- DWORD rc;
- winFile *pFile = (winFile*)id;
- DWORD error;
- DWORD got;
+ winFile *pFile = (winFile*)id; /* file handle */
+ DWORD nRead; /* Number of bytes actually read from file */
assert( id!=0 );
SimulateIOError(return SQLITE_IOERR_READ);
OSTRACE(("READ %d lock=%d\n", pFile->h, pFile->locktype));
- rc = SetFilePointer(pFile->h, lowerBits, &upperBits, FILE_BEGIN);
- if( rc==INVALID_SET_FILE_POINTER && (error=GetLastError())!=NO_ERROR ){
- pFile->lastErrno = error;
+
+ if( seekWinFile(pFile, offset) ){
return SQLITE_FULL;
}
- if( !ReadFile(pFile->h, pBuf, amt, &got, 0) ){
+ if( !ReadFile(pFile->h, pBuf, amt, &nRead, 0) ){
pFile->lastErrno = GetLastError();
return SQLITE_IOERR_READ;
}
- if( got==(DWORD)amt ){
- return SQLITE_OK;
- }else{
+ if( nRead<(DWORD)amt ){
/* Unread parts of the buffer must be zero-filled */
- memset(&((char*)pBuf)[got], 0, amt-got);
+ memset(&((char*)pBuf)[nRead], 0, amt-nRead);
return SQLITE_IOERR_SHORT_READ;
}
+
+ return SQLITE_OK;
}
/*
@@ -713,47 +738,42 @@ static int winRead(
** or some other error code on failure.
*/
static int winWrite(
- sqlite3_file *id, /* File to write into */
- const void *pBuf, /* The bytes to be written */
- int amt, /* Number of bytes to write */
- sqlite3_int64 offset /* Offset into the file to begin writing at */
+ sqlite3_file *id, /* File to write into */
+ const void *pBuf, /* The bytes to be written */
+ int amt, /* Number of bytes to write */
+ sqlite3_int64 offset /* Offset into the file to begin writing at */
){
- LONG upperBits = (LONG)((offset>>32) & 0x7fffffff);
- LONG lowerBits = (LONG)(offset & 0xffffffff);
- DWORD rc;
- winFile *pFile = (winFile*)id;
- DWORD error;
- DWORD wrote = 0;
+ int rc; /* True if error has occured, else false */
+ winFile *pFile = (winFile*)id; /* File handle */
- assert( id!=0 );
+ assert( amt>0 );
+ assert( pFile );
SimulateIOError(return SQLITE_IOERR_WRITE);
SimulateDiskfullError(return SQLITE_FULL);
+
OSTRACE(("WRITE %d lock=%d\n", pFile->h, pFile->locktype));
- rc = SetFilePointer(pFile->h, lowerBits, &upperBits, FILE_BEGIN);
- if( rc==INVALID_SET_FILE_POINTER && (error=GetLastError())!=NO_ERROR ){
- pFile->lastErrno = error;
- if( pFile->lastErrno==ERROR_HANDLE_DISK_FULL ){
- return SQLITE_FULL;
- }else{
- return SQLITE_IOERR_WRITE;
+
+ rc = seekWinFile(pFile, offset);
+ if( rc==0 ){
+ u8 *aRem = (u8 *)pBuf; /* Data yet to be written */
+ int nRem = amt; /* Number of bytes yet to be written */
+ DWORD nWrite; /* Bytes written by each WriteFile() call */
+
+ while( nRem>0 && WriteFile(pFile->h, aRem, nRem, &nWrite, 0) && nWrite>0 ){
+ aRem += nWrite;
+ nRem -= nWrite;
+ }
+ if( nRem>0 ){
+ pFile->lastErrno = GetLastError();
+ rc = 1;
}
}
- assert( amt>0 );
- while(
- amt>0
- && (rc = WriteFile(pFile->h, pBuf, amt, &wrote, 0))!=0
- && wrote>0
- ){
- amt -= wrote;
- pBuf = &((char*)pBuf)[wrote];
- }
- if( !rc || amt>(int)wrote ){
- pFile->lastErrno = GetLastError();
+
+ if( rc ){
if( pFile->lastErrno==ERROR_HANDLE_DISK_FULL ){
return SQLITE_FULL;
- }else{
- return SQLITE_IOERR_WRITE;
}
+ return SQLITE_IOERR_WRITE;
}
return SQLITE_OK;
}
@@ -762,26 +782,32 @@ static int winWrite(
** Truncate an open file to a specified size
*/
static int winTruncate(sqlite3_file *id, sqlite3_int64 nByte){
- LONG upperBits = (LONG)((nByte>>32) & 0x7fffffff);
- LONG lowerBits = (LONG)(nByte & 0xffffffff);
- DWORD dwRet;
- winFile *pFile = (winFile*)id;
- DWORD error;
- int rc = SQLITE_OK;
+ winFile *pFile = (winFile*)id; /* File handle object */
+ int rc = SQLITE_OK; /* Return code for this function */
+
+ assert( pFile );
- assert( id!=0 );
OSTRACE(("TRUNCATE %d %lld\n", pFile->h, nByte));
SimulateIOError(return SQLITE_IOERR_TRUNCATE);
- dwRet = SetFilePointer(pFile->h, lowerBits, &upperBits, FILE_BEGIN);
- if( dwRet==INVALID_SET_FILE_POINTER && (error=GetLastError())!=NO_ERROR ){
- pFile->lastErrno = error;
+
+ /* If the user has configured a chunk-size for this file, truncate the
+ ** file so that it consists of an integer number of chunks (i.e. the
+ ** actual file size after the operation may be larger than the requested
+ ** size).
+ */
+ if( pFile->szChunk ){
+ nByte = ((nByte + pFile->szChunk - 1)/pFile->szChunk) * pFile->szChunk;
+ }
+
+ /* SetEndOfFile() returns non-zero when successful, or zero when it fails. */
+ if( seekWinFile(pFile, nByte) ){
rc = SQLITE_IOERR_TRUNCATE;
- /* SetEndOfFile will fail if nByte is negative */
- }else if( !SetEndOfFile(pFile->h) ){
+ }else if( 0==SetEndOfFile(pFile->h) ){
pFile->lastErrno = GetLastError();
rc = SQLITE_IOERR_TRUNCATE;
}
- OSTRACE(("TRUNCATE %d %lld %s\n", pFile->h, nByte, rc==SQLITE_OK ? "ok" : "failed"));
+
+ OSTRACE(("TRUNCATE %d %lld %s\n", pFile->h, nByte, rc ? "failed" : "ok"));
return rc;
}
@@ -1146,6 +1172,10 @@ static int winFileControl(sqlite3_file *id, int op, void *pArg){
*(int*)pArg = (int)((winFile*)id)->lastErrno;
return SQLITE_OK;
}
+ case SQLITE_FCNTL_CHUNK_SIZE: {
+ ((winFile*)id)->szChunk = *(int *)pArg;
+ return SQLITE_OK;
+ }
case SQLITE_FCNTL_SIZE_HINT: {
sqlite3_int64 sz = *(sqlite3_int64*)pArg;
SimulateIOErrorBenign(1);
diff --git a/src/pager.c b/src/pager.c
index 5f8a90c58..52065f1f3 100644
--- a/src/pager.c
+++ b/src/pager.c
@@ -126,6 +126,175 @@ int sqlite3PagerTrace=1; /* True to enable tracing */
#define FILEHANDLEID(fd) ((int)fd)
/*
+** The Pager.eState variable stores the current 'state' of a pager. A
+** pager may be in any one of the following six states:
+**
+** NONE:
+**
+** The pager starts up in this state. Nothing is guaranteed in this
+** state - the file may or may not be locked and the database size is
+** unknown. The database may not be read or written.
+**
+** * No read or write transaction is active.
+** * Any lock, or no lock at all, may be held on the database file.
+** * The dbSize, dbOrigSize and dbFileSize variables may not be trusted.
+**
+** READER:
+**
+** In this state all the requirements for reading the database in
+** rollback (non-WAL) mode are met. Unless the pager is (or recently
+** was) in exclusive-locking mode, a user-level read transaction is
+** open. The database size is known in this state.
+**
+** * A read transaction may be active.
+** * A SHARED or greater lock is held on the database file.
+** * The dbSize variable may be trusted (even if a user-level read
+** transaction is not active). The dbOrigSize and dbFileSize variables
+** may not be trusted at this point.
+**
+** WRITER_INITIAL:
+**
+** * A write transaction is active.
+** * A RESERVED or greater lock is held on the database file.
+** * The dbSize, dbOrigSize and dbFileSize variables are all valid.
+** * The contents of the pager cache have not been modified.
+** * The journal file may or may not be open.
+** * Nothing (not even the first header) has been written to the journal.
+**
+** WRITER_CACHEMOD:
+**
+** * A write transaction is active.
+** * A RESERVED or greater lock is held on the database file.
+** * The journal file is open and the first header has been written
+** to it, but the header has not been synced to disk.
+** * The contents of the page cache have been modified.
+**
+** WRITER_DBMOD:
+**
+** * A write transaction is active.
+** * An EXCLUSIVE or greater lock is held on the database file.
+** * The journal file is open and the first header has been written
+** and synced to disk.
+** * The contents of the page cache have been modified (and possibly
+** written to disk).
+**
+** WRITER_FINISHED:
+**
+** * A write transaction is active.
+** * An EXCLUSIVE or greater lock is held on the database file.
+** * All writing and syncing of journal and database data has finished.
+** If no error occured, all that remains is to finalize the journal to
+** commit the transaction. If an error did occur, the caller will need
+** to rollback the transaction.
+**
+** ERROR:
+**
+** The ERROR state is entered when an IO, OOM or disk-full error
+** occurs at a point in the code that makes it difficult to be sure
+** that the in-memory pager state (cache contents, db size etc.) are
+** consistent with the contents of the file-system.
+**
+** For example, if an IO error occurs while performing a rollback,
+** the contents of the page-cache may be left in an inconsistent state.
+** At this point it would be dangerous to change back to READER state
+** (as usually happens after a rollback). Any subsequent readers might
+** report database corruption (due to the inconsistent cache), and if
+** they upgrade to writers, they may inadvertently corrupt the database
+** file. To avoid this hazard, the pager switches into the ERROR state
+** instead of READER following such an error.
+**
+** Once it has entered the ERROR state, any attempt to use the pager
+** to read or write data returns an error. Eventually, once all
+** outstanding transactions have been abandoned, the pager is able to
+** transition back to NONE state, discarding the contents of the
+** page-cache and any other in-memory state at the same time. Everything
+** is reloaded from disk (and, if necessary, hot-journal rollback peformed)
+** when a read-transaction is next opened on the pager (transitioning
+** the pager into READER state). At that point the system has recovered
+** from the error.
+**
+** Specifically, the pager jumps into the ERROR state if:
+**
+** 1. An error occurs while attempting a rollback. This happens in
+** function sqlite3PagerRollback().
+**
+** 2. An error occurs while attempting to finalize a journal file
+** following a commit in function sqlite3PagerCommitPhaseTwo().
+**
+** 3. An error occurs while attempting to write to the journal or
+** database file in function pagerStress() in order to free up
+** memory.
+**
+** In other cases, the error is returned to the b-tree layer. The b-tree
+** layer then attempts a rollback operation. If the error condition
+** persists, the pager enters the ERROR state via condition (1) above.
+**
+** Condition (3) is necessary because it can be triggered by a read-only
+** statement executed within a transaction. In this case, if the error
+** code were simply returned to the user, the b-tree layer would not
+** automatically attempt a rollback, as it assumes that an error in a
+** read-only statement cannot leave the pager in an internally inconsistent
+** state.
+**
+**
+** State diagram:
+**
+** NONE <------+------+
+** | | |
+** V | |
+** +---------> READER-------+ |
+** | | |
+** | V |
+** |<-------WRITER_INITIAL-----> ERROR
+** | | ^
+** | V |
+** |<------WRITER_CACHEMOD-------->|
+** | | |
+** | V |
+** |<-------WRITER_DBMOD---------->|
+** | | |
+** | V |
+** +<------WRITER_FINISHED-------->+
+**
+**
+** State transitions and the [function] that performs each:
+**
+** NONE -> READER [PagerSharedLock]
+** READER -> WRITER_INITIAL [PagerBegin]
+** WRITER_INITIAL -> WRITER_CACHEMOD [pager_open_journal]
+** WRITER_CACHEMOD -> WRITER_DBMOD [syncJournal]
+** WRITER_DBMOD -> WRITER_FINISHED [PagerCommitPhaseOne]
+**
+** WRITER_INITIAL -> READER [pager_end_transaction]
+** WRITER_CACHEMOD -> READER [pager_end_transaction]
+** WRITER_DBMOD -> READER [pager_end_transaction]
+** WRITER_FINISHED -> READER [pager_end_transaction]
+**
+** READER -> NONE [pager_unlock]
+**
+** Notes:
+**
+** * A pager is never in WRITER_DBMOD or WRITER_FINISHED state if the
+** connection is open in WAL mode. A WAL connection is always in one
+** of the first four states.
+**
+** * Normally, a connection open in exclusive mode is never in PAGER_NONE
+** state. There are two exceptions: immediately after exclusive-mode has
+** been turned on (and before any read or write transactions are
+** executed), and when the pager is leaving the "error state".
+**
+** * See also: assert_pager_state().
+*/
+#define PAGER_NONE 0
+#define PAGER_READER 1
+#define PAGER_WRITER_INITIAL 2
+#define PAGER_WRITER_CACHEMOD 3
+#define PAGER_WRITER_DBMOD 4
+#define PAGER_WRITER_FINISHED 5
+#define PAGER_ERROR 6
+
+
+/*
** The page cache as a whole is always in one of the following
** states:
**
@@ -239,7 +408,7 @@ struct PagerSavepoint {
** SQLITE_FULL does not affect the sqlite3PagerGet() and sqlite3PagerLookup()
** APIs, they may still be used successfully.
**
-** dbSizeValid, dbSize, dbOrigSize, dbFileSize
+** dbSize, dbOrigSize, dbFileSize
**
** Managing the size of the database file in pages is a little complicated.
** The variable Pager.dbSize contains the number of pages that the database
@@ -325,12 +494,6 @@ struct PagerSavepoint {
** doNotSpill value set to prevent pagerStress() from trying to use
** the journal during a rollback.
**
-** needSync
-**
-** TODO: It might be easier to set this variable in writeJournalHdr()
-** and writeMasterJournal() only. Change its meaning to "unsynced data
-** has been written to the journal".
-**
** subjInMemory
**
** This is a boolean variable. If true, then any required sub-journal
@@ -340,7 +503,7 @@ struct PagerSavepoint {
struct Pager {
sqlite3_vfs *pVfs; /* OS functions to use for IO */
u8 exclusiveMode; /* Boolean. True if locking_mode==EXCLUSIVE */
- u8 journalMode; /* On of the PAGER_JOURNALMODE_* values */
+ u8 journalMode; /* One of the PAGER_JOURNALMODE_* values */
u8 useJournal; /* Use a rollback journal on this file */
u8 noReadlock; /* Do not bother to obtain readlocks */
u8 noSync; /* Do not sync the journal if true */
@@ -361,15 +524,12 @@ struct Pager {
** other variables in this block are described in the comment directly
** above this class definition.
*/
- u8 state; /* PAGER_UNLOCK, _SHARED, _RESERVED, etc. */
- u8 dbModified; /* True if there are any changes to the Db */
- u8 needSync; /* True if an fsync() is needed on the journal */
- u8 journalStarted; /* True if header of journal is synced */
+ u8 eState; /* Pager state (NONE, READER, WRITER_INITIAL..) */
+ u8 eLock; /* Current lock held on database file */
u8 changeCountDone; /* Set after incrementing the change-counter */
u8 setMaster; /* True if a m-j name has been written to jrnl */
u8 doNotSpill; /* Do not spill the cache when non-zero */
u8 doNotSyncSpill; /* Do not do a spill that requires jrnl sync */
- u8 dbSizeValid; /* Set when dbSize is correct */
u8 subjInMemory; /* True to use in-memory sub-journals */
Pgno dbSize; /* Number of pages in the database */
Pgno dbOrigSize; /* dbSize before the current transaction */
@@ -491,22 +651,179 @@ static const unsigned char aJournalMagic[] = {
*/
#define PAGER_MAX_PGNO 2147483647
+/*
+** The argument to this macro is a file descriptor (type sqlite3_file*).
+** Return 0 if it is not open, or non-zero (but not 1) if it is.
+**
+** This is so that expressions can be written as:
+**
+** if( isOpen(pPager->jfd) ){ ...
+**
+** instead of
+**
+** if( pPager->jfd->pMethods ){ ...
+*/
+#define isOpen(pFd) ((pFd)->pMethods)
+
+/*
+** Return true if this pager uses a write-ahead log instead of the usual
+** rollback journal. Otherwise false.
+*/
+#ifndef SQLITE_OMIT_WAL
+static int pagerUseWal(Pager *pPager){
+ return (pPager->pWal!=0);
+}
+#else
+# define pagerUseWal(x) 0
+# define pagerRollbackWal(x) 0
+# define pagerWalFrames(v,w,x,y,z) 0
+# define pagerOpenWalIfPresent(z) SQLITE_OK
+# define pagerBeginReadTransaction(z) SQLITE_OK
+#endif
+
#ifndef NDEBUG
/*
** Usage:
**
** assert( assert_pager_state(pPager) );
*/
-static int assert_pager_state(Pager *pPager){
+static int assert_pager_state(Pager *p){
+ Pager *pPager = p;
+
+ /* State must be valid. */
+ assert( p->eState==PAGER_NONE
+ || p->eState==PAGER_READER
+ || p->eState==PAGER_WRITER_INITIAL
+ || p->eState==PAGER_WRITER_CACHEMOD
+ || p->eState==PAGER_WRITER_DBMOD
+ || p->eState==PAGER_WRITER_FINISHED
+ || p->eState==PAGER_ERROR
+ );
- /* A temp-file is always in PAGER_EXCLUSIVE or PAGER_SYNCED state. */
- assert( pPager->tempFile==0 || pPager->state>=PAGER_EXCLUSIVE );
+ /* Regardless of the current state, a temp-file connection always behaves
+ ** as if it has an exclusive lock on the database file. It never updates
+ ** the change-counter field, so the changeCountDone flag is always set.
+ */
+ assert( p->tempFile==0 || p->eLock==EXCLUSIVE_LOCK );
+ assert( p->tempFile==0 || pPager->changeCountDone );
- /* The changeCountDone flag is always set for temp-files */
- assert( pPager->tempFile==0 || pPager->changeCountDone );
+ /* If the useJournal flag is clear, the journal-mode must be "OFF".
+ ** And if the journal-mode is "OFF", the journal file must not be open.
+ */
+ assert( p->journalMode==PAGER_JOURNALMODE_OFF || p->useJournal );
+ assert( p->journalMode!=PAGER_JOURNALMODE_OFF || !isOpen(p->jfd) );
+
+ /* Check that MEMDB implies noSync. */
+ assert( !MEMDB || p->noSync );
+
+ switch( p->eState ){
+ case PAGER_NONE:
+ assert( !MEMDB );
+ assert( !p->tempFile );
+ assert( pPager->errCode==SQLITE_OK );
+ assert( sqlite3PcacheRefCount(pPager->pPCache)==0 );
+ break;
+
+ case PAGER_READER:
+ assert( pPager->errCode==SQLITE_OK );
+ assert( p->eLock>=SHARED_LOCK || p->noReadlock );
+ break;
+
+ case PAGER_WRITER_INITIAL:
+ assert( pPager->errCode==SQLITE_OK );
+ if( !pagerUseWal(pPager) ){
+ assert( p->eLock>=RESERVED_LOCK );
+ }
+ assert( pPager->dbSize==pPager->dbOrigSize );
+ assert( pPager->dbOrigSize==pPager->dbFileSize );
+ assert( pPager->setMaster==0 );
+ break;
+
+ case PAGER_WRITER_CACHEMOD:
+ assert( pPager->errCode==SQLITE_OK );
+ if( !pagerUseWal(pPager) ){
+ /* It is possible that if journal_mode=wal here that neither the
+ ** journal file nor the WAL file are open. This happens during
+ ** a rollback transaction that switches from journal_mode=off
+ ** to journal_mode=wal.
+ */
+ assert( p->eLock>=RESERVED_LOCK );
+ assert( isOpen(p->jfd)
+ || p->journalMode==PAGER_JOURNALMODE_OFF
+ || p->journalMode==PAGER_JOURNALMODE_WAL
+ );
+ }
+ assert( pPager->dbOrigSize==pPager->dbFileSize );
+ break;
+
+ case PAGER_WRITER_DBMOD:
+ assert( pPager->errCode==SQLITE_OK );
+ assert( !pagerUseWal(pPager) );
+ assert( p->eLock>=EXCLUSIVE_LOCK || pagerUseWal(pPager) );
+ assert( isOpen(p->jfd)
+ || p->journalMode==PAGER_JOURNALMODE_OFF
+ || p->journalMode==PAGER_JOURNALMODE_WAL
+ );
+ break;
+
+ case PAGER_WRITER_FINISHED:
+ assert( pPager->errCode==SQLITE_OK );
+ assert( !pagerUseWal(pPager) );
+ assert( p->eLock>=EXCLUSIVE_LOCK || pagerUseWal(pPager) );
+ assert( isOpen(p->jfd)
+ || p->journalMode==PAGER_JOURNALMODE_OFF
+ || p->journalMode==PAGER_JOURNALMODE_WAL
+ );
+ break;
+
+ case PAGER_ERROR:
+ /* There must be at least one outstanding reference to the pager if
+ ** in ERROR state. Otherwise the pager should have already dropped
+ ** back to NONE state.
+ */
+ assert( pPager->errCode!=SQLITE_OK );
+ assert( sqlite3PcacheRefCount(pPager->pPCache)>0 );
+ break;
+ }
return 1;
}
+
+/*
+** (gdb) printf "%s", print_pager_state(pPager)
+*/
+static char *print_pager_state(Pager *p){
+ static char zRet[1024];
+
+ sqlite3_snprintf(1024, zRet,
+ "State: %s\n"
+ "Lock: %s\n"
+ "Locking mode: locking_mode=%s\n"
+ "Journal mode: journal_mode=%s\n"
+ "Backing store: tempFile=%d memDb=%d useJournal=%d\n"
+ , p->eState==PAGER_NONE ? "NONE" :
+ p->eState==PAGER_READER ? "READER" :
+ p->eState==PAGER_WRITER_INITIAL ? "WRITER_INITIAL" :
+ p->eState==PAGER_WRITER_CACHEMOD ? "WRITER_CACHEMOD" :
+ p->eState==PAGER_WRITER_DBMOD ? "WRITER_DBMOD" :
+ p->eState==PAGER_WRITER_FINISHED ? "WRITER_FINISHED" :
+ p->eState==PAGER_ERROR ? "ERROR" : "?error?"
+ , p->eLock==NO_LOCK ? "NONE" :
+ p->eLock==RESERVED_LOCK ? "RESERVED" :
+ p->eLock==EXCLUSIVE_LOCK ? "EXCLUSIVE" :
+ p->eLock==SHARED_LOCK ? "SHARED" : "?error?"
+ , p->exclusiveMode ? "exclusive" : "normal"
+ , p->journalMode==PAGER_JOURNALMODE_MEMORY ? "memory" :
+ p->journalMode==PAGER_JOURNALMODE_OFF ? "off" :
+ p->journalMode==PAGER_JOURNALMODE_DELETE ? "delete" :
+ p->journalMode==PAGER_JOURNALMODE_PERSIST ? "persist" :
+ p->journalMode==PAGER_JOURNALMODE_TRUNCATE ? "truncate" :
+ p->journalMode==PAGER_JOURNALMODE_WAL ? "wal" : "?error?"
+ , (int)p->tempFile, (int)p->memDb, (int)p->useJournal
+ );
+
+ return zRet;
+}
#endif
/*
@@ -559,6 +876,7 @@ static int read32bits(sqlite3_file *fd, i64 offset, u32 *pRes){
*/
#define put32bits(A,B) sqlite3Put4byte((u8*)A,B)
+
/*
** Write a 32-bit integer into the given file descriptor. Return SQLITE_OK
** on success or an error code is something goes wrong.
@@ -570,27 +888,16 @@ static int write32bits(sqlite3_file *fd, i64 offset, u32 val){
}
/*
-** The argument to this macro is a file descriptor (type sqlite3_file*).
-** Return 0 if it is not open, or non-zero (but not 1) if it is.
-**
-** This is so that expressions can be written as:
-**
-** if( isOpen(pPager->jfd) ){ ...
-**
-** instead of
-**
-** if( pPager->jfd->pMethods ){ ...
-*/
-#define isOpen(pFd) ((pFd)->pMethods)
-
-/*
** If file pFd is open, call sqlite3OsUnlock() on it.
*/
-static int osUnlock(sqlite3_file *pFd, int eLock){
- if( !isOpen(pFd) ){
+static int osUnlock(Pager *pPager, int eLock){
+ if( !isOpen(pPager->fd) ){
return SQLITE_OK;
}
- return sqlite3OsUnlock(pFd, eLock);
+ assert( pPager->eLock>=eLock );
+ assert( eLock!=NO_LOCK || pagerUseWal(pPager)==0 );
+ pPager->eLock = eLock;
+ return sqlite3OsUnlock(pPager->fd, eLock);
}
/*
@@ -882,7 +1189,7 @@ static int writeJournalHdr(Pager *pPager){
** that garbage data is never appended to the journal file.
*/
assert( isOpen(pPager->fd) || pPager->noSync );
- if( (pPager->noSync) || (pPager->journalMode==PAGER_JOURNALMODE_MEMORY)
+ if( pPager->noSync || (pPager->journalMode==PAGER_JOURNALMODE_MEMORY)
|| (sqlite3OsDeviceCharacteristics(pPager->fd)&SQLITE_IOCAP_SAFE_APPEND)
){
memcpy(zHeader, aJournalMagic, sizeof(aJournalMagic));
@@ -1082,6 +1389,9 @@ static int writeMasterJournal(Pager *pPager, const char *zMaster){
u32 cksum = 0; /* Checksum of string zMaster */
assert( pPager->setMaster==0 );
+ assert( !zMaster || pPager->journalMode!=PAGER_JOURNALMODE_WAL );
+ assert( !pagerUseWal(pPager) );
+
if( !zMaster
|| pPager->journalMode==PAGER_JOURNALMODE_MEMORY
|| pPager->journalMode==PAGER_JOURNALMODE_OFF
@@ -1118,7 +1428,6 @@ static int writeMasterJournal(Pager *pPager, const char *zMaster){
return rc;
}
pPager->journalOff += (nMaster+20);
- pPager->needSync = !pPager->noSync;
/* If the pager is in peristent-journal mode, then the physical
** journal-file may extend past the end of the master-journal name
@@ -1154,17 +1463,11 @@ static PgHdr *pager_lookup(Pager *pPager, Pgno pgno){
}
/*
-** Unless the pager is in error-state, discard all in-memory pages. If
-** the pager is in error-state, then this call is a no-op.
-**
-** TODO: Why can we not reset the pager while in error state?
+** Discard the entire contents of the in-memory page-cache.
*/
static void pager_reset(Pager *pPager){
- if( SQLITE_OK==pPager->errCode ){
- sqlite3BackupRestart(pPager->pBackup);
- sqlite3PcacheClear(pPager->pPCache);
- pPager->dbSizeValid = 0;
- }
+ sqlite3BackupRestart(pPager->pBackup);
+ sqlite3PcacheClear(pPager->pPCache);
}
/*
@@ -1207,22 +1510,6 @@ static int addToSavepointBitvecs(Pager *pPager, Pgno pgno){
}
/*
-** Return true if this pager uses a write-ahead log instead of the usual
-** rollback journal. Otherwise false.
-*/
-#ifndef SQLITE_OMIT_WAL
-static int pagerUseWal(Pager *pPager){
- return (pPager->pWal!=0);
-}
-#else
-# define pagerUseWal(x) 0
-# define pagerRollbackWal(x) 0
-# define pagerWalFrames(v,w,x,y,z) 0
-# define pagerOpenWalIfPresent(z) SQLITE_OK
-# define pagerBeginReadTransaction(z) SQLITE_OK
-#endif
-
-/*
** Unlock the database file. This function is a no-op if the pager
** is in exclusive mode.
**
@@ -1233,8 +1520,16 @@ static int pagerUseWal(Pager *pPager){
** treated as a hot-journal and rolled back.
*/
static void pager_unlock(Pager *pPager){
- if( !pPager->exclusiveMode ){
- int rc = SQLITE_OK; /* Return code */
+
+ sqlite3BitvecDestroy(pPager->pInJournal);
+ pPager->pInJournal = 0;
+ releaseAllSavepoints(pPager);
+
+ if( pagerUseWal(pPager) ){
+ assert( !isOpen(pPager->jfd) );
+ sqlite3WalEndReadTransaction(pPager->pWal);
+ pPager->eState = PAGER_NONE;
+ }else if( !pPager->exclusiveMode ){
int iDc = isOpen(pPager->fd)?sqlite3OsDeviceCharacteristics(pPager->fd):0;
/* If the operating system support deletion of open files, then
@@ -1253,43 +1548,28 @@ static void pager_unlock(Pager *pPager){
){
sqlite3OsClose(pPager->jfd);
}
+ osUnlock(pPager, NO_LOCK);
- sqlite3BitvecDestroy(pPager->pInJournal);
- pPager->pInJournal = 0;
- releaseAllSavepoints(pPager);
-
- /* If the file is unlocked, somebody else might change it. The
- ** values stored in Pager.dbSize etc. might become invalid if
- ** this happens. One can argue that this doesn't need to be cleared
- ** until the change-counter check fails in PagerSharedLock().
- ** Clearing the page size cache here is being conservative.
+ /* The pager state may be changed from PAGER_ERROR to PAGER_NONE here
+ ** without clearing the error code. This is intentional - the error
+ ** code is cleared and the cache reset in the block below.
*/
- pPager->dbSizeValid = 0;
-
- if( pagerUseWal(pPager) ){
- sqlite3WalEndReadTransaction(pPager->pWal);
- }else{
- rc = osUnlock(pPager->fd, NO_LOCK);
- }
- if( rc ){
- pPager->errCode = rc;
- }
+ assert( pPager->errCode || pPager->eState!=PAGER_ERROR );
+ pPager->changeCountDone = 0;
+ pPager->eState = PAGER_NONE;
IOTRACE(("UNLOCK %p\n", pPager))
+ }
- /* If Pager.errCode is set, the contents of the pager cache cannot be
- ** trusted. Now that the pager file is unlocked, the contents of the
- ** cache can be discarded and the error code safely cleared.
- */
- if( pPager->errCode ){
- if( rc==SQLITE_OK ){
- pPager->errCode = SQLITE_OK;
- }
- pager_reset(pPager);
- }
-
+ /* If Pager.errCode is set, the contents of the pager cache cannot be
+ ** trusted. Now that there are no outstanding references to the pager,
+ ** it can safely move back to PAGER_NONE state. This happens in both
+ ** normal and exclusive-locking mode.
+ */
+ if( pPager->errCode ){
+ pager_reset(pPager);
pPager->changeCountDone = 0;
- pPager->state = PAGER_UNLOCK;
- pPager->dbModified = 0;
+ pPager->eState = PAGER_NONE;
+ pPager->errCode = SQLITE_OK;
}
}
@@ -1322,37 +1602,12 @@ static int pager_error(Pager *pPager, int rc){
);
if( rc2==SQLITE_FULL || rc2==SQLITE_IOERR ){
pPager->errCode = rc;
+ pPager->eState = PAGER_ERROR;
}
return rc;
}
/*
-** Execute a rollback if a transaction is active and unlock the
-** database file.
-**
-** If the pager has already entered the error state, do not attempt
-** the rollback at this time. Instead, pager_unlock() is called. The
-** call to pager_unlock() will discard all in-memory pages, unlock
-** the database file and clear the error state. If this means that
-** there is a hot-journal left in the file-system, the next connection
-** to obtain a shared lock on the pager (which may be this one) will
-** roll it back.
-**
-** If the pager has not already entered the error state, but an IO or
-** malloc error occurs during a rollback, then this will itself cause
-** the pager to enter the error state. Which will be cleared by the
-** call to pager_unlock(), as described above.
-*/
-static void pagerUnlockAndRollback(Pager *pPager){
- if( pPager->errCode==SQLITE_OK && pPager->state>=PAGER_RESERVED ){
- sqlite3BeginBenignMalloc();
- sqlite3PagerRollback(pPager);
- sqlite3EndBenignMalloc();
- }
- pager_unlock(pPager);
-}
-
-/*
** This routine ends a transaction. A transaction is usually ended by
** either a COMMIT or a ROLLBACK operation. This routine may be called
** after rollback of a hot-journal, or if an error occurs while opening
@@ -1412,11 +1667,14 @@ static int pager_end_transaction(Pager *pPager, int hasMaster){
int rc = SQLITE_OK; /* Error code from journal finalization operation */
int rc2 = SQLITE_OK; /* Error code from db file unlock operation */
- if( pPager->state<PAGER_RESERVED ){
+ /* Do nothing if the pager does not have an open write transaction. */
+ assert( assert_pager_state(pPager) );
+ assert( pPager->eState!=PAGER_ERROR );
+ if( pPager->eState<PAGER_WRITER_INITIAL && pPager->eLock<RESERVED_LOCK ){
return SQLITE_OK;
}
- releaseAllSavepoints(pPager);
+ releaseAllSavepoints(pPager);
assert( isOpen(pPager->jfd) || pPager->pInJournal==0 );
if( isOpen(pPager->jfd) ){
assert( !pagerUseWal(pPager) );
@@ -1432,14 +1690,11 @@ static int pager_end_transaction(Pager *pPager, int hasMaster){
rc = sqlite3OsTruncate(pPager->jfd, 0);
}
pPager->journalOff = 0;
- pPager->journalStarted = 0;
}else if( pPager->journalMode==PAGER_JOURNALMODE_PERSIST
|| (pPager->exclusiveMode && pPager->journalMode!=PAGER_JOURNALMODE_WAL)
){
rc = zeroJournalHdr(pPager, hasMaster);
- pager_error(pPager, rc);
pPager->journalOff = 0;
- pPager->journalStarted = 0;
}else{
/* This branch may be executed with Pager.journalMode==MEMORY if
** a hot-journal was just rolled back. In this case the journal
@@ -1464,41 +1719,60 @@ static int pager_end_transaction(Pager *pPager, int hasMaster){
pPager->pInJournal = 0;
pPager->nRec = 0;
sqlite3PcacheCleanAll(pPager->pPCache);
+ sqlite3PcacheTruncate(pPager->pPCache, pPager->dbSize);
if( pagerUseWal(pPager) ){
+ /* Drop the WAL write-lock, if any. Also, if the connection was in
+ ** locking_mode=exclusive mode but is no longer, drop the EXCLUSIVE
+ ** lock held on the database file.
+ */
rc2 = sqlite3WalEndWriteTransaction(pPager->pWal);
assert( rc2==SQLITE_OK );
- pPager->state = PAGER_SHARED;
-
- /* If the connection was in locking_mode=exclusive mode but is no longer,
- ** drop the EXCLUSIVE lock held on the database file.
- */
if( !pPager->exclusiveMode && sqlite3WalExclusiveMode(pPager->pWal, 0) ){
- rc2 = osUnlock(pPager->fd, SHARED_LOCK);
+ rc2 = osUnlock(pPager, SHARED_LOCK);
}
}else if( !pPager->exclusiveMode ){
- rc2 = osUnlock(pPager->fd, SHARED_LOCK);
- pPager->state = PAGER_SHARED;
+ rc2 = osUnlock(pPager, SHARED_LOCK);
pPager->changeCountDone = 0;
- }else if( pPager->state==PAGER_SYNCED ){
- pPager->state = PAGER_EXCLUSIVE;
}
+ pPager->eState = PAGER_READER;
pPager->setMaster = 0;
- pPager->needSync = 0;
- pPager->dbModified = 0;
-
- /* TODO: Is this optimal? Why is the db size invalidated here
- ** when the database file is not unlocked? */
- pPager->dbOrigSize = 0;
- sqlite3PcacheTruncate(pPager->pPCache, pPager->dbSize);
- if( !MEMDB ){
- pPager->dbSizeValid = 0;
- }
return (rc==SQLITE_OK?rc2:rc);
}
/*
+** Execute a rollback if a transaction is active and unlock the
+** database file.
+**
+** If the pager has already entered the error state, do not attempt
+** the rollback at this time. Instead, pager_unlock() is called. The
+** call to pager_unlock() will discard all in-memory pages, unlock
+** the database file and clear the error state. If this means that
+** there is a hot-journal left in the file-system, the next connection
+** to obtain a shared lock on the pager (which may be this one) will
+** roll it back.
+**
+** If the pager has not already entered the error state, but an IO or
+** malloc error occurs during a rollback, then this will itself cause
+** the pager to enter the error state. Which will be cleared by the
+** call to pager_unlock(), as described above.
+*/
+static void pagerUnlockAndRollback(Pager *pPager){
+ if( pPager->eState!=PAGER_ERROR ){
+ assert( assert_pager_state(pPager) );
+ if( pPager->eState>=PAGER_WRITER_INITIAL ){
+ sqlite3BeginBenignMalloc();
+ sqlite3PagerRollback(pPager);
+ sqlite3EndBenignMalloc();
+ }else if( pPager->eLock>=RESERVED_LOCK && !pPager->exclusiveMode ){
+ pager_end_transaction(pPager, 0);
+ }
+ }
+ pager_unlock(pPager);
+}
+
+/*
** Parameter aData must point to a buffer of pPager->pageSize bytes
** of data. Compute and return a checksum based ont the contents of the
** page of data and the current value of pPager->cksumInit.
@@ -1640,7 +1914,7 @@ static int pager_playback_one_page(
if( pDone && (rc = sqlite3BitvecSet(pDone, pgno))!=SQLITE_OK ){
return rc;
}
- assert( pPager->state==PAGER_RESERVED || pPager->state>=PAGER_EXCLUSIVE );
+ assert( pPager->eState>=PAGER_WRITER_CACHEMOD );
/* When playing back page 1, restore the nReserve setting
*/
@@ -1696,7 +1970,7 @@ static int pager_playback_one_page(
}else{
isSynced = (pPg==0 || 0==(pPg->flags & PGHDR_NEED_SYNC));
}
- if( (pPager->state>=PAGER_EXCLUSIVE)
+ if( (pPager->eState>=PAGER_WRITER_DBMOD)
&& isOpen(pPager->fd)
&& isSynced
){
@@ -1944,7 +2218,8 @@ delmaster_out:
*/
static int pager_truncate(Pager *pPager, Pgno nPage){
int rc = SQLITE_OK;
- if( pPager->state>=PAGER_EXCLUSIVE && isOpen(pPager->fd) ){
+ assert( pPager->eState!=PAGER_ERROR );
+ if( pPager->eState>=PAGER_WRITER_DBMOD && isOpen(pPager->fd) ){
i64 currentSize, newSize;
/* TODO: Is it safe to use Pager.dbFileSize here? */
rc = sqlite3OsFileSize(pPager->fd, &currentSize);
@@ -2064,10 +2339,13 @@ static int pager_playback(Pager *pPager, int isHot){
char *zMaster = 0; /* Name of master journal file if any */
int needPagerReset; /* True to reset page prior to first page rollback */
+ if( !isOpen(pPager->jfd) ){
+ return SQLITE_OK;
+ }
+
/* Figure out how many records are in the journal. Abort early if
** the journal is empty.
*/
- assert( isOpen(pPager->jfd) );
rc = sqlite3OsFileSize(pPager->jfd, &szJ);
if( rc!=SQLITE_OK || szJ==0 ){
goto end_playback;
@@ -2217,10 +2495,10 @@ end_playback:
rc = readMasterJournal(pPager->jfd, zMaster, pPager->pVfs->mxPathname+1);
testcase( rc!=SQLITE_OK );
}
- if( rc==SQLITE_OK && pPager->noSync==0 && pPager->state>=PAGER_EXCLUSIVE ){
+ if( rc==SQLITE_OK && !pPager->noSync && pPager->eState>=PAGER_WRITER_DBMOD ){
rc = sqlite3OsSync(pPager->fd, pPager->sync_flags);
}
- if( rc==SQLITE_OK && pPager->noSync==0 && pPager->state>=PAGER_EXCLUSIVE ){
+ if( rc==SQLITE_OK && !pPager->noSync && pPager->eState>=PAGER_WRITER_DBMOD ){
rc = sqlite3OsSync(pPager->fd, pPager->sync_flags);
}
if( rc==SQLITE_OK ){
@@ -2262,7 +2540,7 @@ static int readDbPage(PgHdr *pPg){
int isInWal = 0; /* True if page is in log file */
int pgsz = pPager->pageSize; /* Number of bytes to read */
- assert( pPager->state>=PAGER_SHARED && !MEMDB );
+ assert( pPager->eState>=PAGER_READER && !MEMDB );
assert( isOpen(pPager->fd) );
if( NEVER(!isOpen(pPager->fd)) ){
@@ -2425,6 +2703,7 @@ static int pagerBeginReadTransaction(Pager *pPager){
int changed = 0; /* True if cache must be reset */
assert( pagerUseWal(pPager) );
+ assert( pPager->eState==PAGER_NONE || pPager->eState==PAGER_SHARED );
/* sqlite3WalEndReadTransaction() was not called for the previous
** transaction in locking_mode=EXCLUSIVE. So call it now. If we
@@ -2434,19 +2713,50 @@ static int pagerBeginReadTransaction(Pager *pPager){
sqlite3WalEndReadTransaction(pPager->pWal);
rc = sqlite3WalBeginReadTransaction(pPager->pWal, &changed);
- if( rc==SQLITE_OK ){
- int dummy;
- if( changed ){
- pager_reset(pPager);
- assert( pPager->errCode || pPager->dbSizeValid==0 );
- }
- rc = sqlite3PagerPagecount(pPager, &dummy);
+ if( rc==SQLITE_OK && changed ){
+ pager_reset(pPager);
}
- pPager->state = PAGER_SHARED;
return rc;
}
+
+/*
+** TODO: Description here.
+*/
+static int pagerPagecount(Pager *pPager, Pgno *pnPage){
+ Pgno nPage; /* Value to return via *pnPage */
+
+ assert( pPager->eState==PAGER_NONE );
+ nPage = sqlite3WalDbsize(pPager->pWal);
+ if( nPage==0 ){
+ i64 n = 0; /* Size of db file in bytes */
+ assert( isOpen(pPager->fd) || pPager->tempFile );
+ if( isOpen(pPager->fd) ){
+ int rc = sqlite3OsFileSize(pPager->fd, &n);
+ if( rc!=SQLITE_OK ){
+ return rc;
+ }
+ }
+ nPage = (Pgno)(n / pPager->pageSize);
+ if( nPage==0 && n>0 ){
+ nPage = 1;
+ }
+ }
+
+ /* If the current number of pages in the file is greater than the
+ ** configured maximum pager number, increase the allowed limit so
+ ** that the file can be read.
+ */
+ if( nPage>pPager->mxPgno ){
+ pPager->mxPgno = (Pgno)nPage;
+ }
+
+ *pnPage = nPage;
+ return SQLITE_OK;
+}
+
+
/*
** 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
@@ -2471,9 +2781,11 @@ static int pagerOpenWalIfPresent(Pager *pPager){
int rc = SQLITE_OK;
if( !pPager->tempFile ){
int isWal; /* True if WAL file exists */
- int nPage; /* Size of the database file */
- assert( pPager->state>=SHARED_LOCK );
- rc = sqlite3PagerPagecount(pPager, &nPage);
+ Pgno nPage; /* Size of the database file */
+
+ assert( pPager->eState==PAGER_NONE );
+ assert( pPager->eLock>=SHARED_LOCK || pPager->noReadlock );
+ rc = pagerPagecount(pPager, &nPage);
if( rc ) return rc;
if( nPage==0 ){
rc = sqlite3OsDelete(pPager->pVfs, pPager->zWal, 0);
@@ -2485,7 +2797,7 @@ static int pagerOpenWalIfPresent(Pager *pPager){
}
if( rc==SQLITE_OK ){
if( isWal ){
- pager_reset(pPager);
+ assert( sqlite3PcachePagecount(pPager->pPCache)==0 );
rc = sqlite3PagerOpenWal(pPager, 0);
if( rc==SQLITE_OK ){
rc = pagerBeginReadTransaction(pPager);
@@ -2541,7 +2853,8 @@ static int pagerPlaybackSavepoint(Pager *pPager, PagerSavepoint *pSavepoint){
int rc = SQLITE_OK; /* Return code */
Bitvec *pDone = 0; /* Bitvec to ensure pages played back only once */
- assert( pPager->state>=PAGER_SHARED );
+ assert( pPager->eState!=PAGER_ERROR );
+ assert( pPager->eState>=PAGER_WRITER_INITIAL );
/* Allocate a bitvec to use to store the set of pages rolled back */
if( pSavepoint ){
@@ -2680,7 +2993,6 @@ void sqlite3PagerSetSafetyLevel(Pager *pPager, int level, int bFullFsync){
pPager->noSync = (level==1 || pPager->tempFile) ?1:0;
pPager->fullSync = (level==3 && !pPager->tempFile) ?1:0;
pPager->sync_flags = (bFullFsync?SQLITE_SYNC_FULL:SQLITE_SYNC_NORMAL);
- if( pPager->noSync ) pPager->needSync = 0;
}
#endif
@@ -2789,6 +3101,12 @@ void sqlite3PagerSetBusyhandler(
int sqlite3PagerSetPagesize(Pager *pPager, u16 *pPageSize, int nReserve){
int rc = pPager->errCode;
+ /* It is not possible to do a full assert_pager_state() here, as this
+ ** function may be called from within PagerOpen(), before the state
+ ** of the Pager object is internally consistent.
+ */
+ assert( rc==SQLITE_OK || pPager->eState==PAGER_ERROR );
+
if( rc==SQLITE_OK ){
u16 pageSize = *pPageSize;
assert( pageSize==0 || (pageSize>=512 && pageSize<=SQLITE_MAX_PAGE_SIZE) );
@@ -2796,11 +3114,19 @@ int sqlite3PagerSetPagesize(Pager *pPager, u16 *pPageSize, int nReserve){
&& sqlite3PcacheRefCount(pPager->pPCache)==0
&& pageSize && pageSize!=pPager->pageSize
){
- char *pNew = (char *)sqlite3PageMalloc(pageSize);
+ char *pNew; /* New temp space */
+ i64 nByte = 0;
+ if( pPager->eState>PAGER_NONE && isOpen(pPager->fd) ){
+ rc = sqlite3OsFileSize(pPager->fd, &nByte);
+ if( rc!=SQLITE_OK ) return rc;
+ }
+ pNew = (char *)sqlite3PageMalloc(pageSize);
if( !pNew ){
rc = SQLITE_NOMEM;
}else{
+ assert( pPager->eState==PAGER_NONE || pPager->eState==PAGER_READER );
pager_reset(pPager);
+ pPager->dbSize = nByte/pageSize;
pPager->pageSize = pageSize;
sqlite3PageFree(pPager->pTmpSpace);
pPager->pTmpSpace = pNew;
@@ -2836,13 +3162,11 @@ void *sqlite3PagerTempSpace(Pager *pPager){
** Regardless of mxPage, return the current maximum page count.
*/
int sqlite3PagerMaxPageCount(Pager *pPager, int mxPage){
- int nPage;
if( mxPage>0 ){
pPager->mxPgno = mxPage;
}
- if( pPager->state!=PAGER_UNLOCK ){
- sqlite3PagerPagecount(pPager, &nPage);
- assert( (int)pPager->mxPgno>=nPage );
+ if( pPager->eState!=PAGER_NONE && pPager->mxPgno<pPager->dbSize ){
+ pPager->mxPgno = pPager->dbSize;
}
return pPager->mxPgno;
}
@@ -2907,69 +3231,34 @@ int sqlite3PagerReadFileheader(Pager *pPager, int N, unsigned char *pDest){
}
/*
-** Return the total number of pages in the database file associated
-** with pPager. Normally, this is calculated as (<db file size>/<page-size>).
+** This function may only be called when a read-transaction is open on
+** the pager. It returns the total number of pages in the database.
+**
** However, if the file is between 1 and <page-size> bytes in size, then
** this is considered a 1 page file.
-**
-** If the pager is in error state when this function is called, then the
-** error state error code is returned and *pnPage left unchanged. Or,
-** if the file system has to be queried for the size of the file and
-** the query attempt returns an IO error, the IO error code is returned
-** and *pnPage is left unchanged.
-**
-** Otherwise, if everything is successful, then SQLITE_OK is returned
-** and *pnPage is set to the number of pages in the database.
*/
int sqlite3PagerPagecount(Pager *pPager, int *pnPage){
- Pgno nPage = 0; /* Value to return via *pnPage */
-
- /* Determine the number of pages in the file. Store this in nPage. */
- if( pPager->dbSizeValid ){
- nPage = pPager->dbSize;
- }else{
- int rc; /* Error returned by OsFileSize() */
- i64 n = 0; /* File size in bytes returned by OsFileSize() */
+ assert( pPager->eState>=PAGER_SHARED );
+ assert( pPager->eState!=PAGER_WRITER_FINISHED );
+ *pnPage = (int)pPager->dbSize;
+ return SQLITE_OK;
+}
- if( pagerUseWal(pPager) && pPager->state!=PAGER_UNLOCK ){
- sqlite3WalDbsize(pPager->pWal, &nPage);
- }
- if( nPage==0 ){
- assert( isOpen(pPager->fd) || pPager->tempFile );
- if( isOpen(pPager->fd) ){
- if( SQLITE_OK!=(rc = sqlite3OsFileSize(pPager->fd, &n)) ){
- pager_error(pPager, rc);
- return rc;
- }
- }
- if( n>0 && n<pPager->pageSize ){
- nPage = 1;
- }else{
- nPage = (Pgno)(n / pPager->pageSize);
- }
- }
- if( pPager->state!=PAGER_UNLOCK ){
- pPager->dbSize = nPage;
- pPager->dbFileSize = nPage;
- pPager->dbSizeValid = 1;
+static int pagerLock(Pager *pPager, int eLock){
+ int rc;
+ assert( eLock==SHARED_LOCK || eLock==RESERVED_LOCK || eLock==EXCLUSIVE_LOCK );
+ if( pPager->eLock>=eLock ){
+ rc = SQLITE_OK;
+ }else{
+ rc = sqlite3OsLock(pPager->fd, eLock);
+ if( rc==SQLITE_OK ){
+ pPager->eLock = eLock;
}
}
-
- /* If the current number of pages in the file is greater than the
- ** configured maximum pager number, increase the allowed limit so
- ** that the file can be read.
- */
- if( nPage>pPager->mxPgno ){
- pPager->mxPgno = (Pgno)nPage;
- }
-
- /* Set the output variable and return SQLITE_OK */
- *pnPage = nPage;
- return SQLITE_OK;
+ return rc;
}
-
/*
** Try to obtain a lock of type locktype on the database file. If
** a similar or greater lock is already held, this function is a no-op
@@ -2992,30 +3281,24 @@ static int pager_wait_on_lock(Pager *pPager, int locktype){
assert( PAGER_RESERVED==RESERVED_LOCK );
assert( PAGER_EXCLUSIVE==EXCLUSIVE_LOCK );
- /* If the file is currently unlocked then the size must be unknown. It
- ** must not have been modified at this point.
- */
- assert( pPager->state>=PAGER_SHARED || pPager->dbSizeValid==0 );
- assert( pPager->state>=PAGER_SHARED || pPager->dbModified==0 );
-
/* Check that this is either a no-op (because the requested lock is
** already held, or one of the transistions that the busy-handler
** may be invoked during, according to the comment above
** sqlite3PagerSetBusyhandler().
*/
- assert( (pPager->state>=locktype)
- || (pPager->state==PAGER_UNLOCK && locktype==PAGER_SHARED)
- || (pPager->state==PAGER_RESERVED && locktype==PAGER_EXCLUSIVE)
+ assert( (pPager->eLock>=locktype)
+ || (pPager->eLock==NO_LOCK && locktype==SHARED_LOCK)
+ || (pPager->eLock==RESERVED_LOCK && locktype==EXCLUSIVE_LOCK)
);
- if( pPager->state>=locktype ){
+ if( pPager->eLock>=locktype ){
rc = SQLITE_OK;
}else{
do {
rc = sqlite3OsLock(pPager->fd, locktype);
}while( rc==SQLITE_BUSY && pPager->xBusyHandler(pPager->pBusyHandlerArg) );
if( rc==SQLITE_OK ){
- pPager->state = (u8)locktype;
+ pPager->eLock = (u8)locktype;
IOTRACE(("LOCK %p %d\n", pPager, locktype))
}
}
@@ -3063,9 +3346,8 @@ static void assertTruncateConstraint(Pager *pPager){
** truncation will be done when the current transaction is committed.
*/
void sqlite3PagerTruncateImage(Pager *pPager, Pgno nPage){
- assert( pPager->dbSizeValid );
assert( pPager->dbSize>=nPage );
- assert( pPager->state>=PAGER_RESERVED );
+ assert( pPager->eState>=PAGER_WRITER_CACHEMOD );
pPager->dbSize = nPage;
assertTruncateConstraint(pPager);
}
@@ -3115,7 +3397,7 @@ int sqlite3PagerClose(Pager *pPager){
disable_simulated_io_errors();
sqlite3BeginBenignMalloc();
- pPager->errCode = 0;
+ /* pPager->errCode = 0; */
pPager->exclusiveMode = 0;
#ifndef SQLITE_OMIT_WAL
sqlite3WalClose(pPager->pWal,
@@ -3128,14 +3410,19 @@ int sqlite3PagerClose(Pager *pPager){
if( MEMDB ){
pager_unlock(pPager);
}else{
- /* Set Pager.journalHdr to -1 for the benefit of the pager_playback()
- ** call which may be made from within pagerUnlockAndRollback(). If it
- ** is not -1, then the unsynced portion of an open journal file may
- ** be played back into the database. If a power failure occurs while
- ** this is happening, the database may become corrupt.
+ /* If it is open, sync the journal file before calling UnlockAndRollback.
+ ** If this is not done, then an unsynced portion of the open journal
+ ** file may be played back into the database. If a power failure occurs
+ ** while this is happening, the database could become corrupt.
+ **
+ ** If an error occurs while trying to sync the journal, shift the pager
+ ** into the ERROR state. This causes UnlockAndRollback to unlock the
+ ** database and close the journal file without attempting to roll it
+ ** back or finalize it. The next database user will have to do hot-journal
+ ** rollback before accessing the database file.
*/
if( isOpen(pPager->jfd) ){
- pPager->errCode = pagerSyncHotJournal(pPager);
+ pager_error(pPager, pagerSyncHotJournal(pPager));
}
pagerUnlockAndRollback(pPager);
}
@@ -3180,9 +3467,9 @@ void sqlite3PagerRef(DbPage *pPg){
** been written to the journal have actually reached the surface of the
** disk and can be restored in the event of a hot-journal rollback.
**
-** If the Pager.needSync flag is not set, then this function is a
-** no-op. Otherwise, the actions required depend on the journal-mode
-** and the device characteristics of the the file-system, as follows:
+** If the Pager.noSync flag is set, then this function is a no-op.
+** Otherwise, the actions required depend on the journal-mode and the
+** device characteristics of the the file-system, as follows:
**
** * If the journal file is an in-memory journal file, no action need
** be taken.
@@ -3206,18 +3493,25 @@ void sqlite3PagerRef(DbPage *pPg){
** if( NOT SEQUENTIAL ) xSync(<journal file>);
** }
**
-** The Pager.needSync flag is never be set for temporary files, or any
-** file operating in no-sync mode (Pager.noSync set to non-zero).
-**
** If successful, this routine clears the PGHDR_NEED_SYNC flag of every
** page currently held in memory before returning SQLITE_OK. If an IO
** error is encountered, then the IO error code is returned to the caller.
*/
-static int syncJournal(Pager *pPager){
- if( pPager->needSync ){
+static int syncJournal(Pager *pPager, int newHdr){
+ int rc; /* Return code */
+
+ assert( pPager->eState==PAGER_WRITER_CACHEMOD
+ || pPager->eState==PAGER_WRITER_DBMOD
+ );
+ assert( assert_pager_state(pPager) );
+ assert( !pagerUseWal(pPager) );
+
+ rc = sqlite3PagerExclusiveLock(pPager);
+ if( rc!=SQLITE_OK ) return rc;
+
+ if( !pPager->noSync ){
assert( !pPager->tempFile );
- if( pPager->journalMode!=PAGER_JOURNALMODE_MEMORY ){
- int rc; /* Return code */
+ if( isOpen(pPager->jfd) && pPager->journalMode!=PAGER_JOURNALMODE_MEMORY ){
const int iDc = sqlite3OsDeviceCharacteristics(pPager->fd);
assert( isOpen(pPager->jfd) );
@@ -3292,17 +3586,24 @@ static int syncJournal(Pager *pPager){
);
if( rc!=SQLITE_OK ) return rc;
}
- }
- /* The journal file was just successfully synced. Set Pager.needSync
- ** to zero and clear the PGHDR_NEED_SYNC flag on all pagess.
- */
- pPager->needSync = 0;
- pPager->journalStarted = 1;
- pPager->journalHdr = pPager->journalOff;
- sqlite3PcacheClearSyncFlags(pPager->pPCache);
+ pPager->journalHdr = pPager->journalOff;
+ if( newHdr && 0==(iDc&SQLITE_IOCAP_SAFE_APPEND) ){
+ pPager->nRec = 0;
+ rc = writeJournalHdr(pPager);
+ }
+ }else{
+ pPager->journalHdr = pPager->journalOff;
+ }
}
+ /* Unless the pager is in noSync mode, the journal file was just
+ ** successfully synced. Either way, clear the PGHDR_NEED_SYNC flag on
+ ** all pages.
+ */
+ sqlite3PcacheClearSyncFlags(pPager->pPCache);
+ pPager->eState = PAGER_WRITER_DBMOD;
+ assert( assert_pager_state(pPager) );
return SQLITE_OK;
}
@@ -3357,8 +3658,23 @@ static int pager_write_pagelist(Pager *pPager, PgHdr *pList){
** EXCLUSIVE, it means the database file has been changed and any rollback
** will require a journal playback.
*/
+
+ /* Normally, this function is called in WRITER_DBMOD state.
+ **
+ ** However it may be called in WRITER_CACHEMOD state if the page being
+ ** written (and all other pages that reside on the same disk sector) was
+ ** a free-list leaf page at the start of the transaction. In that case
+ ** the database file is not really being modified, so it is Ok to write
+ ** to it in CACHEMOD state.
+ */
assert( !pagerUseWal(pPager) );
- assert( pPager->state>=PAGER_RESERVED );
+ assert( pPager->eState==PAGER_WRITER_DBMOD
+ || pPager->eState==PAGER_WRITER_CACHEMOD
+ );
+ assert( pPager->eState==PAGER_WRITER_DBMOD
+ || (pList->pDirty==0 && pList->pgno<=pPager->dbFileSize)
+ );
+
rc = pager_wait_on_lock(pPager, EXCLUSIVE_LOCK);
/* If the file is a temp-file has not yet been opened, open it now. It
@@ -3374,7 +3690,7 @@ static int pager_write_pagelist(Pager *pPager, PgHdr *pList){
** file size will be.
*/
assert( rc!=SQLITE_OK || isOpen(pPager->fd) );
- if( rc==SQLITE_OK && pPager->dbSize>(pPager->dbOrigSize+1) ){
+ if( rc==SQLITE_OK && pPager->dbSize>pPager->dbOrigSize ){
sqlite3_int64 szFile = pPager->pageSize * (sqlite3_int64)pPager->dbSize;
sqlite3OsFileControl(pPager->fd, SQLITE_FCNTL_SIZE_HINT, &szFile);
}
@@ -3394,6 +3710,8 @@ static int pager_write_pagelist(Pager *pPager, PgHdr *pList){
i64 offset = (pgno-1)*(i64)pPager->pageSize; /* Offset to write */
char *pData; /* Data to write */
+ assert( (pList->flags&PGHDR_NEED_SYNC)==0 );
+
/* Encode the database */
CODEC2(pPager, pList->pData, pgno, 6, return SQLITE_NOMEM, pData);
@@ -3557,15 +3875,7 @@ static int pagerStress(void *p, PgHdr *pPg){
/* Sync the journal file if required. */
if( pPg->flags&PGHDR_NEED_SYNC ){
- assert( !pPager->noSync );
- rc = syncJournal(pPager);
- if( rc==SQLITE_OK &&
- !(pPager->journalMode==PAGER_JOURNALMODE_MEMORY) &&
- !(sqlite3OsDeviceCharacteristics(pPager->fd)&SQLITE_IOCAP_SAFE_APPEND)
- ){
- pPager->nRec = 0;
- rc = writeJournalHdr(pPager);
- }
+ rc = syncJournal(pPager, 1);
}
/* If the page number of this page is larger than the current size of
@@ -3603,6 +3913,7 @@ static int pagerStress(void *p, PgHdr *pPg){
/* Write the contents of the page out to the database file. */
if( rc==SQLITE_OK ){
+ assert( (pPg->flags&PGHDR_NEED_SYNC)==0 );
rc = pager_write_pagelist(pPager, pPg);
}
}
@@ -3831,7 +4142,8 @@ int sqlite3PagerOpen(
** disk and uses an in-memory rollback journal.
*/
tempFile = 1;
- pPager->state = PAGER_EXCLUSIVE;
+ pPager->eState = PAGER_READER;
+ pPager->eLock = EXCLUSIVE_LOCK;
readOnly = (vfsFlags&SQLITE_OPEN_READONLY);
}
@@ -3868,13 +4180,14 @@ int sqlite3PagerOpen(
/* pPager->stmtOpen = 0; */
/* pPager->stmtInUse = 0; */
/* pPager->nRef = 0; */
- pPager->dbSizeValid = (u8)memDb;
/* pPager->stmtSize = 0; */
/* pPager->stmtJSize = 0; */
/* pPager->nPage = 0; */
pPager->mxPgno = SQLITE_MAX_PAGE_COUNT;
/* pPager->state = PAGER_UNLOCK; */
+#if 0
assert( pPager->state == (tempFile ? PAGER_EXCLUSIVE : PAGER_UNLOCK) );
+#endif
/* pPager->errMask = 0; */
pPager->tempFile = (u8)tempFile;
assert( tempFile==PAGER_LOCKINGMODE_NORMAL
@@ -3884,7 +4197,6 @@ int sqlite3PagerOpen(
pPager->changeCountDone = pPager->tempFile;
pPager->memDb = (u8)memDb;
pPager->readOnly = (u8)readOnly;
- /* pPager->needSync = 0; */
assert( useJournal || pPager->tempFile );
pPager->noSync = pPager->tempFile;
pPager->fullSync = pPager->noSync ?0:1;
@@ -3949,10 +4261,10 @@ static int hasHotJournal(Pager *pPager, int *pExists){
int exists = 1; /* True if a journal file is present */
int jrnlOpen = !!isOpen(pPager->jfd);
- assert( pPager!=0 );
assert( pPager->useJournal );
assert( isOpen(pPager->fd) );
- assert( pPager->state <= PAGER_SHARED );
+ assert( pPager->eState==PAGER_NONE );
+
assert( jrnlOpen==0 || ( sqlite3OsDeviceCharacteristics(pPager->jfd) &
SQLITE_IOCAP_UNDELETABLE_WHEN_OPEN
));
@@ -3974,7 +4286,7 @@ static int hasHotJournal(Pager *pPager, int *pExists){
*/
rc = sqlite3OsCheckReservedLock(pPager->fd, &locked);
if( rc==SQLITE_OK && !locked ){
- int nPage;
+ Pgno nPage; /* Number of pages in database file */
/* Check the size of the database file. If it consists of 0 pages,
** then delete the journal file. See the header comment above for
@@ -3982,7 +4294,7 @@ static int hasHotJournal(Pager *pPager, int *pExists){
** a RESERVED lock to avoid race conditions and to avoid violating
** [H33020].
*/
- rc = sqlite3PagerPagecount(pPager, &nPage);
+ rc = pagerPagecount(pPager, &nPage);
if( rc==SQLITE_OK ){
if( nPage==0 ){
sqlite3BeginBenignMalloc();
@@ -4070,52 +4382,39 @@ int sqlite3PagerSharedLock(Pager *pPager){
int isErrorReset = 0; /* True if recovering from error state */
/* This routine is only called from b-tree and only when there are no
- ** outstanding pages */
+ ** outstanding pages. This implies that the pager state should either
+ ** be NONE or READER. READER is only possible if the pager is or was in
+ ** exclusive access mode.
+ */
assert( sqlite3PcacheRefCount(pPager->pPCache)==0 );
+ assert( pPager->eState==PAGER_NONE || pPager->eState==PAGER_READER );
+ assert( assert_pager_state(pPager) );
if( NEVER(MEMDB && pPager->errCode) ){ return pPager->errCode; }
- /* If this database is in an error-state, now is a chance to clear
- ** the error. Discard the contents of the pager-cache and rollback
- ** any hot journal in the file-system.
- */
- if( pPager->errCode ){
- if( isOpen(pPager->jfd) || pPager->zJournal ){
- isErrorReset = 1;
- }
- pPager->errCode = SQLITE_OK;
- pager_reset(pPager);
- }
-
- if( pagerUseWal(pPager) ){
- rc = pagerBeginReadTransaction(pPager);
- }else if( pPager->state==PAGER_UNLOCK || isErrorReset ){
+ if( !pagerUseWal(pPager) && pPager->eState==PAGER_NONE ){
sqlite3_vfs * const pVfs = pPager->pVfs;
- int isHotJournal = 0;
- assert( !MEMDB );
- assert( sqlite3PcacheRefCount(pPager->pPCache)==0 );
- if( pPager->noReadlock ){
- assert( pPager->readOnly );
- pPager->state = PAGER_SHARED;
- }else{
+
+ assert( !MEMDB && !pPager->tempFile );
+ assert( pPager->noReadlock==0 || pPager->readOnly );
+
+ if( pPager->noReadlock==0 ){
rc = pager_wait_on_lock(pPager, SHARED_LOCK);
if( rc!=SQLITE_OK ){
- assert( pPager->state==PAGER_UNLOCK );
- return pager_error(pPager, rc);
+ assert( pPager->eLock==PAGER_UNLOCK );
+ goto failed;
}
}
- assert( pPager->state>=SHARED_LOCK );
/* If a journal file exists, and there is no RESERVED lock on the
** database file, then it either needs to be played back or deleted.
*/
if( !isErrorReset ){
- assert( pPager->state <= PAGER_SHARED );
- rc = hasHotJournal(pPager, &isHotJournal);
+ rc = hasHotJournal(pPager, &isErrorReset);
if( rc!=SQLITE_OK ){
goto failed;
}
}
- if( isErrorReset || isHotJournal ){
+ if( isErrorReset ){
/* Get an EXCLUSIVE lock on the database file. At this point it is
** important that a RESERVED lock is not obtained on the way to the
** EXCLUSIVE lock. If it were, another process might open the
@@ -4127,21 +4426,27 @@ int sqlite3PagerSharedLock(Pager *pPager){
** other process attempting to access the database file will get to
** this point in the code and fail to obtain its own EXCLUSIVE lock
** on the database file.
+ **
+ ** Unless the pager is in locking_mode=exclusive mode, the lock is
+ ** downgraded to SHARED_LOCK before this function returns.
*/
- if( pPager->state<EXCLUSIVE_LOCK ){
- rc = sqlite3OsLock(pPager->fd, EXCLUSIVE_LOCK);
- if( rc!=SQLITE_OK ){
- rc = pager_error(pPager, rc);
- goto failed;
- }
- pPager->state = PAGER_EXCLUSIVE;
+ rc = pagerLock(pPager, EXCLUSIVE_LOCK);
+ if( rc!=SQLITE_OK ){
+ goto failed;
}
- /* Open the journal for read/write access. This is because in
- ** exclusive-access mode the file descriptor will be kept open and
- ** possibly used for a transaction later on. On some systems, the
- ** OsTruncate() call used in exclusive-access mode also requires
- ** a read/write file handle.
+ /* If it is not already open and the file exists on disk, open the
+ ** journal for read/write access. Write access is required because
+ ** in exclusive-access mode the file descriptor will be kept open
+ ** and possibly used for a transaction later on. Also, write-access
+ ** is usually required to finalize the journal in journal_mode=persist
+ ** mode (and also for journal_mode=truncate on some systems).
+ **
+ ** If the journal does not exist, it usually means that some
+ ** other connection managed to get in and roll it back before
+ ** this connection obtained the exclusive lock above. Or, it
+ ** may mean that the pager was in the error-state when this
+ ** function was called and the journal file does not exist.
*/
if( !isOpen(pPager->jfd) ){
int res;
@@ -4157,13 +4462,6 @@ int sqlite3PagerSharedLock(Pager *pPager){
rc = SQLITE_CANTOPEN_BKPT;
sqlite3OsClose(pPager->jfd);
}
- }else{
- /* If the journal does not exist, it usually means that some
- ** other connection managed to get in and roll it back before
- ** this connection obtained the exclusive lock above. Or, it
- ** may mean that the pager was in the error-state when this
- ** function was called and the journal file does not exist. */
- rc = pager_end_transaction(pPager, 0);
}
}
}
@@ -4173,7 +4471,6 @@ int sqlite3PagerSharedLock(Pager *pPager){
/* Reset the journal status fields to indicates that we have no
** rollback journal at this time. */
- pPager->journalStarted = 0;
pPager->journalOff = 0;
pPager->setMaster = 0;
pPager->journalHdr = 0;
@@ -4187,19 +4484,29 @@ int sqlite3PagerSharedLock(Pager *pPager){
** it back since the process that crashed and left the hot journal
** probably did not sync it and we are required to always sync
** the journal before playing it back.
+ **
+ ** Even if an error occurs while syncing or rolling back the
+ ** hot-journal, there is no need to enter the ERROR state here, as
+ ** the pager never left state NONE anyhow.
*/
if( isOpen(pPager->jfd) ){
rc = pagerSyncHotJournal(pPager);
if( rc==SQLITE_OK ){
+ assert( pPager->eState==PAGER_NONE );
+ pPager->eState = PAGER_WRITER_FINISHED;
rc = pager_playback(pPager, 1);
+ pPager->eState = PAGER_NONE;
}
if( rc!=SQLITE_OK ){
- rc = pager_error(pPager, rc);
goto failed;
}
+ }else{
+ osUnlock(pPager, SHARED_LOCK);
}
- assert( (pPager->state==PAGER_SHARED)
- || (pPager->exclusiveMode && pPager->state>PAGER_SHARED)
+
+ assert( pPager->eState==PAGER_NONE );
+ assert( (pPager->eLock==SHARED_LOCK)
+ || (pPager->exclusiveMode && pPager->eLock>SHARED_LOCK)
);
}
@@ -4220,14 +4527,11 @@ int sqlite3PagerSharedLock(Pager *pPager){
** detected. The chance of an undetected change is so small that
** it can be neglected.
*/
- int nPage = 0;
+ Pgno nPage = 0;
char dbFileVers[sizeof(pPager->dbFileVers)];
- sqlite3PagerPagecount(pPager, &nPage);
- if( pPager->errCode ){
- rc = pPager->errCode;
- goto failed;
- }
+ rc = pagerPagecount(pPager, &nPage);
+ if( rc ) goto failed;
if( nPage>0 ){
IOTRACE(("CKVERS %p %d\n", pPager, sizeof(dbFileVers)));
@@ -4243,7 +4547,6 @@ int sqlite3PagerSharedLock(Pager *pPager){
pager_reset(pPager);
}
}
- assert( pPager->exclusiveMode || pPager->state==PAGER_SHARED );
/* If there is a WAL file in the file-system, open this database in WAL
** mode. Otherwise, the following function call is a no-op.
@@ -4251,10 +4554,20 @@ int sqlite3PagerSharedLock(Pager *pPager){
rc = pagerOpenWalIfPresent(pPager);
}
+ if( pagerUseWal(pPager) && rc==SQLITE_OK ){
+ rc = pagerBeginReadTransaction(pPager);
+ }
+
+ if( pPager->eState==PAGER_NONE && rc==SQLITE_OK ){
+ rc = pagerPagecount(pPager, &pPager->dbSize);
+ }
+
failed:
if( rc!=SQLITE_OK ){
- /* pager_unlock() is a no-op for exclusive mode and in-memory databases. */
pager_unlock(pPager);
+ assert( pPager->eState==PAGER_NONE );
+ }else{
+ pPager->eState = PAGER_READER;
}
return rc;
}
@@ -4334,8 +4647,8 @@ int sqlite3PagerAcquire(
int rc;
PgHdr *pPg;
+ assert( pPager->eState>=PAGER_READER );
assert( assert_pager_state(pPager) );
- assert( pPager->state>PAGER_UNLOCK );
if( pgno==0 ){
return SQLITE_CORRUPT_BKPT;
@@ -4369,7 +4682,6 @@ int sqlite3PagerAcquire(
}else{
/* The pager cache has created a new page. Its content needs to
** be initialized. */
- int nMax;
PAGER_INCR(pPager->nMiss);
pPg = *ppPage;
@@ -4382,12 +4694,7 @@ int sqlite3PagerAcquire(
goto pager_acquire_err;
}
- rc = sqlite3PagerPagecount(pPager, &nMax);
- if( rc!=SQLITE_OK ){
- goto pager_acquire_err;
- }
-
- if( MEMDB || nMax<(int)pgno || noContent || !isOpen(pPager->fd) ){
+ if( MEMDB || pPager->dbSize<(int)pgno || noContent || !isOpen(pPager->fd) ){
if( pgno>pPager->mxPgno ){
rc = SQLITE_FULL;
goto pager_acquire_err;
@@ -4453,7 +4760,7 @@ DbPage *sqlite3PagerLookup(Pager *pPager, Pgno pgno){
assert( pPager!=0 );
assert( pgno!=0 );
assert( pPager->pPCache!=0 );
- assert( pPager->state > PAGER_UNLOCK );
+ assert( pPager->eState>=PAGER_READER );
sqlite3PcacheFetch(pPager->pPCache, pgno, 0, &pPg);
return pPg;
}
@@ -4498,12 +4805,10 @@ void sqlite3PagerUnref(DbPage *pPg){
*/
static int pager_open_journal(Pager *pPager){
int rc = SQLITE_OK; /* Return code */
- int nPage; /* Size of database file */
sqlite3_vfs * const pVfs = pPager->pVfs; /* Local cache of vfs pointer */
- assert( pPager->state>=PAGER_RESERVED );
- assert( pPager->useJournal );
- assert( pPager->journalMode!=PAGER_JOURNALMODE_OFF );
+ assert( pPager->eState==PAGER_WRITER_INITIAL );
+ assert( assert_pager_state(pPager) );
assert( pPager->pInJournal==0 );
/* If already in the error state, this function is a no-op. But on
@@ -4511,56 +4816,56 @@ static int pager_open_journal(Pager *pPager){
** an error state. */
if( NEVER(pPager->errCode) ) return pPager->errCode;
- testcase( pPager->dbSizeValid==0 );
- rc = sqlite3PagerPagecount(pPager, &nPage);
- if( rc ) return rc;
- pPager->pInJournal = sqlite3BitvecCreate(nPage);
- if( pPager->pInJournal==0 ){
- return SQLITE_NOMEM;
- }
-
- /* Open the journal file if it is not already open. */
- if( !isOpen(pPager->jfd) ){
- if( pPager->journalMode==PAGER_JOURNALMODE_MEMORY ){
- sqlite3MemJournalOpen(pPager->jfd);
- }else{
- const int flags = /* VFS flags to open journal file */
- SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE|
- (pPager->tempFile ?
- (SQLITE_OPEN_DELETEONCLOSE|SQLITE_OPEN_TEMP_JOURNAL):
- (SQLITE_OPEN_MAIN_JOURNAL)
+ if( !pagerUseWal(pPager) && pPager->journalMode!=PAGER_JOURNALMODE_OFF ){
+ pPager->pInJournal = sqlite3BitvecCreate(pPager->dbSize);
+ if( pPager->pInJournal==0 ){
+ return SQLITE_NOMEM;
+ }
+
+ /* Open the journal file if it is not already open. */
+ if( !isOpen(pPager->jfd) ){
+ if( pPager->journalMode==PAGER_JOURNALMODE_MEMORY ){
+ sqlite3MemJournalOpen(pPager->jfd);
+ }else{
+ const int flags = /* VFS flags to open journal file */
+ SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE|
+ (pPager->tempFile ?
+ (SQLITE_OPEN_DELETEONCLOSE|SQLITE_OPEN_TEMP_JOURNAL):
+ (SQLITE_OPEN_MAIN_JOURNAL)
+ );
+ #ifdef SQLITE_ENABLE_ATOMIC_WRITE
+ rc = sqlite3JournalOpen(
+ pVfs, pPager->zJournal, pPager->jfd, flags, jrnlBufferSize(pPager)
);
-#ifdef SQLITE_ENABLE_ATOMIC_WRITE
- rc = sqlite3JournalOpen(
- pVfs, pPager->zJournal, pPager->jfd, flags, jrnlBufferSize(pPager)
- );
-#else
- rc = sqlite3OsOpen(pVfs, pPager->zJournal, pPager->jfd, flags, 0);
-#endif
+ #else
+ rc = sqlite3OsOpen(pVfs, pPager->zJournal, pPager->jfd, flags, 0);
+ #endif
+ }
+ assert( rc!=SQLITE_OK || isOpen(pPager->jfd) );
+ }
+
+
+ /* Write the first journal header to the journal file and open
+ ** the sub-journal if necessary.
+ */
+ if( rc==SQLITE_OK ){
+ /* TODO: Check if all of these are really required. */
+ pPager->nRec = 0;
+ pPager->journalOff = 0;
+ pPager->setMaster = 0;
+ pPager->journalHdr = 0;
+ rc = writeJournalHdr(pPager);
}
- assert( rc!=SQLITE_OK || isOpen(pPager->jfd) );
- }
-
-
- /* Write the first journal header to the journal file and open
- ** the sub-journal if necessary.
- */
- if( rc==SQLITE_OK ){
- /* TODO: Check if all of these are really required. */
- pPager->dbOrigSize = pPager->dbSize;
- pPager->journalStarted = 0;
- pPager->needSync = 0;
- pPager->nRec = 0;
- pPager->journalOff = 0;
- pPager->setMaster = 0;
- pPager->journalHdr = 0;
- rc = writeJournalHdr(pPager);
}
if( rc!=SQLITE_OK ){
sqlite3BitvecDestroy(pPager->pInJournal);
pPager->pInJournal = 0;
+ }else{
+ assert( pPager->eState==PAGER_WRITER_INITIAL );
+ pPager->eState = PAGER_WRITER_CACHEMOD;
}
+
return rc;
}
@@ -4591,20 +4896,18 @@ static int pager_open_journal(Pager *pPager){
*/
int sqlite3PagerBegin(Pager *pPager, int exFlag, int subjInMemory){
int rc = SQLITE_OK;
- assert( pPager->state!=PAGER_UNLOCK );
+ assert( pPager->eState>=PAGER_READER );
pPager->subjInMemory = (u8)subjInMemory;
- if( pPager->state==PAGER_SHARED ){
+ if( pPager->eState==PAGER_READER ){
assert( pPager->pInJournal==0 );
- assert( !MEMDB && !pPager->tempFile );
if( pagerUseWal(pPager) ){
/* If the pager is configured to use locking_mode=exclusive, and an
** exclusive lock on the database is not already held, obtain it now.
*/
if( pPager->exclusiveMode && sqlite3WalExclusiveMode(pPager->pWal, -1) ){
- rc = sqlite3OsLock(pPager->fd, EXCLUSIVE_LOCK);
- pPager->state = PAGER_SHARED;
+ rc = pagerLock(pPager, EXCLUSIVE_LOCK);
if( rc!=SQLITE_OK ){
return rc;
}
@@ -4615,45 +4918,35 @@ int sqlite3PagerBegin(Pager *pPager, int exFlag, int subjInMemory){
** PAGER_RESERVED state. Otherwise, return an error code to the caller.
** The busy-handler is not invoked if another connection already
** holds the write-lock. If possible, the upper layer will call it.
- **
- ** WAL mode sets Pager.state to PAGER_RESERVED when it has an open
- ** transaction, but never to PAGER_EXCLUSIVE. This is because in
- ** PAGER_EXCLUSIVE state the code to roll back savepoint transactions
- ** may copy data from the sub-journal into the database file as well
- ** as into the page cache. Which would be incorrect in WAL mode.
*/
rc = sqlite3WalBeginWriteTransaction(pPager->pWal);
- if( rc==SQLITE_OK ){
- pPager->dbOrigSize = pPager->dbSize;
- pPager->state = PAGER_RESERVED;
- pPager->journalOff = 0;
- }
-
- assert( rc!=SQLITE_OK || pPager->state==PAGER_RESERVED );
- assert( rc==SQLITE_OK || pPager->state==PAGER_SHARED );
}else{
/* Obtain a RESERVED lock on the database file. If the exFlag parameter
** is true, then immediately upgrade this to an EXCLUSIVE lock. The
** busy-handler callback can be used when upgrading to the EXCLUSIVE
** lock, but not when obtaining the RESERVED lock.
*/
- rc = sqlite3OsLock(pPager->fd, RESERVED_LOCK);
- if( rc==SQLITE_OK ){
- pPager->state = PAGER_RESERVED;
- if( exFlag ){
- rc = pager_wait_on_lock(pPager, EXCLUSIVE_LOCK);
- }
+ rc = pagerLock(pPager, RESERVED_LOCK);
+ if( rc==SQLITE_OK && exFlag ){
+ rc = pager_wait_on_lock(pPager, EXCLUSIVE_LOCK);
}
}
- /* No need to open the journal file at this time. It will be
- ** opened before it is written to. If we defer opening the journal,
- ** we might save the work of creating a file if the transaction
- ** ends up being a no-op.
- */
- if( rc!=SQLITE_OK ){
- assert( !pPager->dbModified );
+ if( rc==SQLITE_OK ){
+ /* Change to WRITER_INITIAL state.
+ **
+ ** WAL mode sets Pager.eState to PAGER_WRITER_INITIAL or CACHEMOD
+ ** when it has an open transaction, but never to DBMOD or FINISHED.
+ ** This is because in those states the code to roll back savepoint
+ ** transactions may copy data from the sub-journal into the database
+ ** file as well as into the page cache. Which would be incorrect in
+ ** WAL mode.
+ */
+ pPager->eState = PAGER_WRITER_INITIAL;
+ pPager->dbFileSize = pPager->dbOrigSize = pPager->dbSize;
+ pPager->journalOff = 0;
+ }else{
/* Ignore any IO error that occurs within pager_end_transaction(). The
** purpose of this call is to reset the internal state of the pager
** sub-system. It doesn't matter if the journal-file is not properly
@@ -4661,6 +4954,10 @@ int sqlite3PagerBegin(Pager *pPager, int exFlag, int subjInMemory){
*/
pager_end_transaction(pPager, 0);
}
+
+ assert( rc==SQLITE_OK || pPager->eState==PAGER_READER );
+ assert( rc!=SQLITE_OK || pPager->eState==PAGER_WRITER_INITIAL );
+ assert( assert_pager_state(pPager) );
}
PAGERTRACE(("TRANSACTION %d\n", PAGERID(pPager)));
@@ -4679,22 +4976,24 @@ static int pager_write(PgHdr *pPg){
Pager *pPager = pPg->pPager;
int rc = SQLITE_OK;
- /* This routine is not called unless a transaction has already been
- ** started.
+ /* This routine is not called unless a write-transaction has already
+ ** been started. The journal file may or may not be open at this point.
+ ** It is never called in the ERROR state.
*/
- assert( pPager->state>=PAGER_RESERVED );
+ assert( pPager->eState==PAGER_WRITER_INITIAL
+ || pPager->eState==PAGER_WRITER_CACHEMOD
+ || pPager->eState==PAGER_WRITER_DBMOD
+ );
+ assert( assert_pager_state(pPager) );
/* If an error has been previously detected, report the same error
- ** again.
- */
+ ** again. This should not happen, but the check provides robustness. */
if( NEVER(pPager->errCode) ) return pPager->errCode;
/* Higher-level routines never call this function if database is not
** writable. But check anyway, just for robustness. */
if( NEVER(pPager->readOnly) ) return SQLITE_PERM;
- assert( !pPager->setMaster );
-
CHECK_PAGE(pPg);
/* Mark the page as dirty. If the page has already been written
@@ -4703,35 +5002,31 @@ static int pager_write(PgHdr *pPg){
sqlite3PcacheMakeDirty(pPg);
if( pageInJournal(pPg) && !subjRequiresPage(pPg) ){
assert( !pagerUseWal(pPager) );
- pPager->dbModified = 1;
+ assert( pPager->eState>=PAGER_WRITER_CACHEMOD );
}else{
/* If we get this far, it means that the page needs to be
- ** written to the transaction journal or the ckeckpoint journal
+ ** written to the transaction journal or the checkpoint journal
** or both.
**
- ** Higher level routines should have already started a transaction,
- ** which means they have acquired the necessary locks but the rollback
- ** journal might not yet be open.
+ ** Higher level routines have already obtained the necessary locks
+ ** to begin the write-transaction, but the rollback journal might not
+ ** yet be open. Open it now if this is the case.
*/
- assert( pPager->state>=RESERVED_LOCK );
- if( pPager->pInJournal==0
- && pPager->journalMode!=PAGER_JOURNALMODE_OFF
- && !pagerUseWal(pPager)
- ){
- assert( pPager->useJournal );
+ if( pPager->eState==PAGER_WRITER_INITIAL ){
rc = pager_open_journal(pPager);
if( rc!=SQLITE_OK ) return rc;
}
- pPager->dbModified = 1;
+ assert( pPager->eState>=PAGER_WRITER_CACHEMOD );
+ assert( assert_pager_state(pPager) );
/* The transaction journal now exists and we have a RESERVED or an
** EXCLUSIVE lock on the main database file. Write the current page to
** the transaction journal if it is not there already.
*/
- if( !pageInJournal(pPg) && isOpen(pPager->jfd) ){
- assert( !pagerUseWal(pPager) );
- if( pPg->pgno<=pPager->dbOrigSize ){
+ if( !pageInJournal(pPg) && !pagerUseWal(pPager) ){
+ assert( pagerUseWal(pPager)==0 );
+ if( pPg->pgno<=pPager->dbOrigSize && isOpen(pPager->jfd) ){
u32 cksum;
char *pData2;
@@ -4767,10 +5062,7 @@ static int pager_write(PgHdr *pPg){
** in the database file. And if an IO error occurs while doing so,
** then corruption may follow.
*/
- if( !pPager->noSync ){
- pPg->flags |= PGHDR_NEED_SYNC;
- pPager->needSync = 1;
- }
+ pPg->flags |= PGHDR_NEED_SYNC;
/* An error has occurred writing to the journal file. The
** transaction will be rolled back by the layer above.
@@ -4790,9 +5082,8 @@ static int pager_write(PgHdr *pPg){
return rc;
}
}else{
- if( !pPager->journalStarted && !pPager->noSync ){
+ if( pPager->eState!=PAGER_WRITER_DBMOD ){
pPg->flags |= PGHDR_NEED_SYNC;
- pPager->needSync = 1;
}
PAGERTRACE(("APPEND %d page %d needSync=%d\n",
PAGERID(pPager), pPg->pgno,
@@ -4812,7 +5103,6 @@ static int pager_write(PgHdr *pPg){
/* Update the database size and return.
*/
- assert( pPager->state>=PAGER_SHARED );
if( pPager->dbSize<pPg->pgno ){
pPager->dbSize = pPg->pgno;
}
@@ -4840,6 +5130,10 @@ int sqlite3PagerWrite(DbPage *pDbPage){
Pager *pPager = pPg->pPager;
Pgno nPagePerSector = (pPager->sectorSize/pPager->pageSize);
+ assert( pPager->eState>=PAGER_WRITER_INITIAL );
+ assert( pPager->eState!=PAGER_ERROR );
+ assert( assert_pager_state(pPager) );
+
if( nPagePerSector>1 ){
Pgno nPageCount; /* Total number of pages in database file */
Pgno pg1; /* First page of the sector pPg is located on. */
@@ -4861,19 +5155,17 @@ int sqlite3PagerWrite(DbPage *pDbPage){
*/
pg1 = ((pPg->pgno-1) & ~(nPagePerSector-1)) + 1;
- rc = sqlite3PagerPagecount(pPager, (int *)&nPageCount);
- if( rc==SQLITE_OK ){
- if( pPg->pgno>nPageCount ){
- nPage = (pPg->pgno - pg1)+1;
- }else if( (pg1+nPagePerSector-1)>nPageCount ){
- nPage = nPageCount+1-pg1;
- }else{
- nPage = nPagePerSector;
- }
- assert(nPage>0);
- assert(pg1<=pPg->pgno);
- assert((pg1+nPage)>pPg->pgno);
+ nPageCount = pPager->dbSize;
+ if( pPg->pgno>nPageCount ){
+ nPage = (pPg->pgno - pg1)+1;
+ }else if( (pg1+nPagePerSector-1)>nPageCount ){
+ nPage = nPageCount+1-pg1;
+ }else{
+ nPage = nPagePerSector;
}
+ assert(nPage>0);
+ assert(pg1<=pPg->pgno);
+ assert((pg1+nPage)>pPg->pgno);
for(ii=0; ii<nPage && rc==SQLITE_OK; ii++){
Pgno pg = pg1+ii;
@@ -4885,7 +5177,6 @@ int sqlite3PagerWrite(DbPage *pDbPage){
rc = pager_write(pPage);
if( pPage->flags&PGHDR_NEED_SYNC ){
needSync = 1;
- assert(pPager->needSync);
}
sqlite3PagerUnref(pPage);
}
@@ -4913,7 +5204,6 @@ int sqlite3PagerWrite(DbPage *pDbPage){
sqlite3PagerUnref(pPage);
}
}
- assert(pPager->needSync);
}
assert( pPager->doNotSyncSpill==1 );
@@ -4980,6 +5270,11 @@ void sqlite3PagerDontWrite(PgHdr *pPg){
static int pager_incr_changecounter(Pager *pPager, int isDirectMode){
int rc = SQLITE_OK;
+ assert( pPager->eState==PAGER_WRITER_CACHEMOD
+ || pPager->eState==PAGER_WRITER_DBMOD
+ );
+ assert( assert_pager_state(pPager) );
+
/* Declare and initialize constant integer 'isDirect'. If the
** atomic-write optimization is enabled in this build, then isDirect
** is initialized to the value passed as the isDirectMode parameter
@@ -4998,7 +5293,6 @@ static int pager_incr_changecounter(Pager *pPager, int isDirectMode){
# define DIRECT_MODE isDirectMode
#endif
- assert( pPager->state>=PAGER_RESERVED );
if( !pPager->changeCountDone && pPager->dbSize>0 ){
PgHdr *pPgHdr; /* Reference to page 1 */
u32 change_counter; /* Initial value of change-counter field */
@@ -5083,7 +5377,11 @@ int sqlite3PagerSync(Pager *pPager){
*/
int sqlite3PagerExclusiveLock(Pager *pPager){
int rc = SQLITE_OK;
- assert( pPager->state>=PAGER_RESERVED );
+ assert( pPager->eState==PAGER_WRITER_CACHEMOD
+ || pPager->eState==PAGER_WRITER_DBMOD
+ || pPager->eState==PAGER_WRITER_INITIAL
+ );
+ assert( assert_pager_state(pPager) );
if( 0==pagerUseWal(pPager) ){
rc = pager_wait_on_lock(pPager, PAGER_EXCLUSIVE);
}
@@ -5123,8 +5421,11 @@ int sqlite3PagerCommitPhaseOne(
){
int rc = SQLITE_OK; /* Return code */
- /* The dbOrigSize is never set if journal_mode=OFF */
- assert( pPager->journalMode!=PAGER_JOURNALMODE_OFF || pPager->dbOrigSize==0 );
+ assert( pPager->eState==PAGER_WRITER_INITIAL
+ || pPager->eState==PAGER_WRITER_CACHEMOD
+ || pPager->eState==PAGER_WRITER_DBMOD
+ );
+ assert( assert_pager_state(pPager) );
/* If a prior error occurred, report that error again. */
if( pPager->errCode ) return pPager->errCode;
@@ -5132,13 +5433,16 @@ int sqlite3PagerCommitPhaseOne(
PAGERTRACE(("DATABASE SYNC: File=%s zMaster=%s nSize=%d\n",
pPager->zFilename, zMaster, pPager->dbSize));
- if( MEMDB && pPager->dbModified ){
+ /* If no database changes have been made, return early. */
+ if( pPager->eState<PAGER_WRITER_CACHEMOD ) return SQLITE_OK;
+
+ if( MEMDB ){
/* If this is an in-memory db, or no pages have been written to, or this
** function has already been called, it is mostly a no-op. However, any
** backup in progress needs to be restarted.
*/
sqlite3BackupRestart(pPager->pBackup);
- }else if( pPager->dbModified ){
+ }else{
if( pagerUseWal(pPager) ){
PgHdr *pList = sqlite3PcacheDirtyList(pPager->pPCache);
if( pList ){
@@ -5181,7 +5485,7 @@ int sqlite3PagerCommitPhaseOne(
);
if( !zMaster && isOpen(pPager->jfd)
&& pPager->journalOff==jrnlBufferSize(pPager)
- && pPager->dbSize>=pPager->dbFileSize
+ && pPager->dbSize>=pPager->dbOrigSize
&& (0==(pPg = sqlite3PcacheDirtyList(pPager->pPCache)) || 0==pPg->pDirty)
){
/* Update the db file change counter via the direct-write method. The
@@ -5217,7 +5521,7 @@ int sqlite3PagerCommitPhaseOne(
*/
#ifndef SQLITE_OMIT_AUTOVACUUM
if( pPager->dbSize<pPager->dbOrigSize
- && ALWAYS(pPager->journalMode!=PAGER_JOURNALMODE_OFF)
+ && pPager->journalMode!=PAGER_JOURNALMODE_OFF
){
Pgno i; /* Iterator variable */
const Pgno iSkip = PAGER_MJ_PGNO(pPager); /* Pending lock page */
@@ -5244,14 +5548,20 @@ int sqlite3PagerCommitPhaseOne(
rc = writeMasterJournal(pPager, zMaster);
if( rc!=SQLITE_OK ) goto commit_phase_one_exit;
- /* Sync the journal file. If the atomic-update optimization is being
- ** used, this call will not create the journal file or perform any
- ** real IO.
+ /* Sync the journal file and write all dirty pages to the database.
+ ** If the atomic-update optimization is being used, this sync will not
+ ** create the journal file or perform any real IO.
+ **
+ ** Because the change-counter page was just modified, unless the
+ ** atomic-update optimization is used it is almost certain that the
+ ** journal requires a sync here. However, in locking_mode=exclusive
+ ** on a system under memory pressure it is just possible that this is
+ ** not the case. In this case it is likely enough that the redundant
+ ** xSync() call will be changed to a no-op by the OS anyhow.
*/
- rc = syncJournal(pPager);
+ rc = syncJournal(pPager, 0);
if( rc!=SQLITE_OK ) goto commit_phase_one_exit;
- /* Write all dirty pages to the database file. */
rc = pager_write_pagelist(pPager,sqlite3PcacheDirtyList(pPager->pPCache));
if( rc!=SQLITE_OK ){
assert( rc!=SQLITE_IOERR_BLOCKED );
@@ -5264,7 +5574,7 @@ int sqlite3PagerCommitPhaseOne(
*/
if( pPager->dbSize!=pPager->dbFileSize ){
Pgno nNew = pPager->dbSize - (pPager->dbSize==PAGER_MJ_PGNO(pPager));
- assert( pPager->state>=PAGER_EXCLUSIVE );
+ assert( pPager->eState==PAGER_WRITER_DBMOD );
rc = pager_truncate(pPager, nNew);
if( rc!=SQLITE_OK ) goto commit_phase_one_exit;
}
@@ -5275,12 +5585,12 @@ int sqlite3PagerCommitPhaseOne(
}
IOTRACE(("DBSYNC %p\n", pPager))
}
-
- assert( pPager->state!=PAGER_SYNCED );
- pPager->state = PAGER_SYNCED;
}
commit_phase_one_exit:
+ if( rc==SQLITE_OK && !pagerUseWal(pPager) ){
+ pPager->eState = PAGER_WRITER_FINISHED;
+ }
return rc;
}
@@ -5308,12 +5618,11 @@ int sqlite3PagerCommitPhaseTwo(Pager *pPager){
** called, just return the same error code without doing anything. */
if( NEVER(pPager->errCode) ) return pPager->errCode;
- /* This function should not be called if the pager is not in at least
- ** PAGER_RESERVED state. **FIXME**: Make it so that this test always
- ** fails - make it so that we never reach this point if we do not hold
- ** all necessary locks.
- */
- if( NEVER(pPager->state<PAGER_RESERVED) ) return SQLITE_ERROR;
+ assert( pPager->eState==PAGER_WRITER_INITIAL
+ || pPager->eState==PAGER_WRITER_FINISHED
+ || (pagerUseWal(pPager) && pPager->eState==PAGER_WRITER_CACHEMOD)
+ );
+ assert( assert_pager_state(pPager) );
/* An optimization. If the database was not actually modified during
** this transaction, the pager is running in exclusive-mode and is
@@ -5326,15 +5635,16 @@ int sqlite3PagerCommitPhaseTwo(Pager *pPager){
** header. Since the pager is in exclusive mode, there is no need
** to drop any locks either.
*/
- if( pPager->dbModified==0 && pPager->exclusiveMode
+ if( pPager->eState==PAGER_WRITER_INITIAL
+ && pPager->exclusiveMode
&& pPager->journalMode==PAGER_JOURNALMODE_PERSIST
){
assert( pPager->journalOff==JOURNAL_HDR_SZ(pPager) || !pPager->journalOff );
+ pPager->eState = PAGER_READER;
return SQLITE_OK;
}
PAGERTRACE(("COMMIT %d\n", PAGERID(pPager)));
- assert( pPager->state==PAGER_SYNCED || MEMDB || !pPager->dbModified );
rc = pager_end_transaction(pPager, pPager->setMaster);
return pager_error(pPager, rc);
}
@@ -5385,43 +5695,32 @@ int sqlite3PagerCommitPhaseTwo(Pager *pPager){
int sqlite3PagerRollback(Pager *pPager){
int rc = SQLITE_OK; /* Return code */
PAGERTRACE(("ROLLBACK %d\n", PAGERID(pPager)));
+
+ /* PagerRollback() is a no-op if called in READER or NONE state. If
+ ** the pager is already in the ERROR state, the rollback is not
+ ** attempted here. Instead, the error code is returned to the caller.
+ */
+ assert( assert_pager_state(pPager) );
+ if( pPager->eState==PAGER_ERROR ) return pPager->errCode;
+ if( pPager->eState<=PAGER_READER ) return SQLITE_OK;
+
if( pagerUseWal(pPager) ){
int rc2;
-
rc = sqlite3PagerSavepoint(pPager, SAVEPOINT_ROLLBACK, -1);
rc2 = pager_end_transaction(pPager, pPager->setMaster);
if( rc==SQLITE_OK ) rc = rc2;
- rc = pager_error(pPager, rc);
- }else if( !pPager->dbModified || !isOpen(pPager->jfd) ){
- rc = pager_end_transaction(pPager, pPager->setMaster);
- }else if( pPager->errCode && pPager->errCode!=SQLITE_FULL ){
- if( pPager->state>=PAGER_EXCLUSIVE ){
- pager_playback(pPager, 0);
- }
- rc = pPager->errCode;
+ }else if( !isOpen(pPager->jfd) ){
+ rc = pager_end_transaction(pPager, 0);
}else{
- if( pPager->state==PAGER_RESERVED ){
- int rc2;
- rc = pager_playback(pPager, 0);
- rc2 = pager_end_transaction(pPager, pPager->setMaster);
- if( rc==SQLITE_OK ){
- rc = rc2;
- }
- }else{
- rc = pager_playback(pPager, 0);
- }
-
- if( !MEMDB ){
- pPager->dbSizeValid = 0;
- }
-
- /* If an error occurs during a ROLLBACK, we can no longer trust the pager
- ** cache. So call pager_error() on the way out to make any error
- ** persistent.
- */
- rc = pager_error(pPager, rc);
+ rc = pager_playback(pPager, 0);
}
- return rc;
+ assert( pPager->eState==PAGER_READER || rc!=SQLITE_OK );
+ assert( rc==SQLITE_OK || rc==SQLITE_FULL || (rc&0xFF)==SQLITE_IOERR );
+
+ /* If an error occurs during a ROLLBACK, we can no longer trust the pager
+ ** cache. So call pager_error() on the way out to make any error persistent.
+ */
+ return pager_error(pPager, rc);
}
/*
@@ -5467,8 +5766,8 @@ int *sqlite3PagerStats(Pager *pPager){
a[0] = sqlite3PcacheRefCount(pPager->pPCache);
a[1] = sqlite3PcachePagecount(pPager->pPCache);
a[2] = sqlite3PcacheGetCachesize(pPager->pPCache);
- a[3] = pPager->dbSizeValid ? (int) pPager->dbSize : -1;
- a[4] = pPager->state;
+ a[3] = pPager->eState==PAGER_NONE ? -1 : (int) pPager->dbSize;
+ a[4] = pPager->eState;
a[5] = pPager->errCode;
a[6] = pPager->nHit;
a[7] = pPager->nMiss;
@@ -5500,13 +5799,12 @@ int sqlite3PagerOpenSavepoint(Pager *pPager, int nSavepoint){
int rc = SQLITE_OK; /* Return code */
int nCurrent = pPager->nSavepoint; /* Current number of savepoints */
+ assert( pPager->eState>=PAGER_WRITER_INITIAL );
+ assert( assert_pager_state(pPager) );
+
if( nSavepoint>nCurrent && pPager->useJournal ){
int ii; /* Iterator variable */
PagerSavepoint *aNew; /* New Pager.aSavepoint array */
- int nPage; /* Size of database file */
-
- rc = sqlite3PagerPagecount(pPager, &nPage);
- if( rc ) return rc;
/* Grow the Pager.aSavepoint array using realloc(). Return SQLITE_NOMEM
** if the allocation fails. Otherwise, zero the new portion in case a
@@ -5523,14 +5821,14 @@ int sqlite3PagerOpenSavepoint(Pager *pPager, int nSavepoint){
/* Populate the PagerSavepoint structures just allocated. */
for(ii=nCurrent; ii<nSavepoint; ii++){
- aNew[ii].nOrig = nPage;
+ aNew[ii].nOrig = pPager->dbSize;
if( isOpen(pPager->jfd) && pPager->journalOff>0 ){
aNew[ii].iOffset = pPager->journalOff;
}else{
aNew[ii].iOffset = JOURNAL_HDR_SZ(pPager);
}
aNew[ii].iSubRec = pPager->nSubRec;
- aNew[ii].pInSavepoint = sqlite3BitvecCreate(nPage);
+ aNew[ii].pInSavepoint = sqlite3BitvecCreate(pPager->dbSize);
if( !aNew[ii].pInSavepoint ){
return SQLITE_NOMEM;
}
@@ -5717,6 +6015,10 @@ int sqlite3PagerMovepage(Pager *pPager, DbPage *pPg, Pgno pgno, int isCommit){
Pgno origPgno; /* The original page number */
assert( pPg->nRef>0 );
+ assert( pPager->eState==PAGER_WRITER_CACHEMOD
+ || pPager->eState==PAGER_WRITER_DBMOD
+ );
+ assert( assert_pager_state(pPager) );
/* In order to be able to rollback, an in-memory database must journal
** the page we are moving from.
@@ -5766,11 +6068,10 @@ int sqlite3PagerMovepage(Pager *pPager, DbPage *pPg, Pgno pgno, int isCommit){
needSyncPgno = pPg->pgno;
assert( pageInJournal(pPg) || pPg->pgno>pPager->dbOrigSize );
assert( pPg->flags&PGHDR_DIRTY );
- assert( pPager->needSync );
}
/* If the cache contains a page with page-number pgno, remove it
- ** from its hash chain. Also, if the PgHdr.needSync was set for
+ ** from its hash chain. Also, if the PGHDR_NEED_SYNC flag was set for
** page pgno before the 'move' operation, it needs to be retained
** for the page moved there.
*/
@@ -5782,7 +6083,6 @@ int sqlite3PagerMovepage(Pager *pPager, DbPage *pPg, Pgno pgno, int isCommit){
if( MEMDB ){
/* Do not discard pages from an in-memory database since we might
** need to rollback later. Just move the page out of the way. */
- assert( pPager->dbSizeValid );
sqlite3PcacheMove(pPgOld, pPager->dbSize+1);
}else{
sqlite3PcacheDrop(pPgOld);
@@ -5792,14 +6092,13 @@ int sqlite3PagerMovepage(Pager *pPager, DbPage *pPg, Pgno pgno, int isCommit){
origPgno = pPg->pgno;
sqlite3PcacheMove(pPg, pgno);
sqlite3PcacheMakeDirty(pPg);
- pPager->dbModified = 1;
if( needSyncPgno ){
/* If needSyncPgno is non-zero, then the journal file needs to be
** sync()ed before any data is written to database file page needSyncPgno.
** Currently, no such page exists in the page-cache and the
** "is journaled" bitvec flag has been set. This needs to be remedied by
- ** loading the page into the pager-cache and setting the PgHdr.needSync
+ ** loading the page into the pager-cache and setting the PGHDR_NEED_SYNC
** flag.
**
** If the attempt to load the page into the page-cache fails, (due
@@ -5808,12 +6107,8 @@ int sqlite3PagerMovepage(Pager *pPager, DbPage *pPg, Pgno pgno, int isCommit){
** this transaction, it may be written to the database file before
** it is synced into the journal file. This way, it may end up in
** the journal file twice, but that is not a problem.
- **
- ** The sqlite3PagerGet() call may cause the journal to sync. So make
- ** sure the Pager.needSync flag is set too.
*/
PgHdr *pPgHdr;
- assert( pPager->needSync );
rc = sqlite3PagerGet(pPager, needSyncPgno, &pPgHdr);
if( rc!=SQLITE_OK ){
if( needSyncPgno<=pPager->dbOrigSize ){
@@ -5822,8 +6117,6 @@ int sqlite3PagerMovepage(Pager *pPager, DbPage *pPg, Pgno pgno, int isCommit){
}
return rc;
}
- pPager->needSync = 1;
- assert( pPager->noSync==0 && !MEMDB );
pPgHdr->flags |= PGHDR_NEED_SYNC;
sqlite3PcacheMakeDirty(pPgHdr);
sqlite3PagerUnref(pPgHdr);
@@ -5964,23 +6257,23 @@ int sqlite3PagerSetJournalMode(Pager *pPager, int eMode){
** while it is in use by some other client.
*/
int rc = SQLITE_OK;
- int state = pPager->state;
- if( state<PAGER_SHARED ){
+ int state = pPager->eState;
+ if( state==PAGER_NONE ){
rc = sqlite3PagerSharedLock(pPager);
}
- if( pPager->state==PAGER_SHARED ){
+ if( pPager->eState==PAGER_READER ){
assert( rc==SQLITE_OK );
rc = sqlite3OsLock(pPager->fd, RESERVED_LOCK);
}
if( rc==SQLITE_OK ){
sqlite3OsDelete(pPager->pVfs, pPager->zJournal, 0);
}
- if( rc==SQLITE_OK && state==PAGER_SHARED ){
+ if( rc==SQLITE_OK && state==PAGER_READER ){
sqlite3OsUnlock(pPager->fd, SHARED_LOCK);
- }else if( state==PAGER_UNLOCK ){
+ }else if( state==PAGER_NONE ){
pager_unlock(pPager);
}
- assert( state==pPager->state );
+ assert( state==pPager->eState );
}
}
@@ -6001,7 +6294,7 @@ int sqlite3PagerGetJournalMode(Pager *pPager){
** is unmodified.
*/
int sqlite3PagerOkToChangeJournalMode(Pager *pPager){
- if( pPager->dbModified ) return 0;
+ if( pPager->eState>=PAGER_WRITER_CACHEMOD ) return 0;
if( NEVER(isOpen(pPager->jfd) && pPager->journalOff>0) ) return 0;
return 1;
}
@@ -6066,21 +6359,24 @@ int sqlite3PagerWalSupported(Pager *pPager){
** 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. *pisOpen is
+** 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 *pisOpen to 1 and return SQLITE_OK
+** the WAL file is already open, set *pbOpen to 1 and return SQLITE_OK
** without doing anything.
*/
int sqlite3PagerOpenWal(
Pager *pPager, /* Pager object */
- int *pisOpen /* OUT: Set to true if call is a no-op */
+ int *pbOpen /* OUT: Set to true if call is a no-op */
){
int rc = SQLITE_OK; /* Return code */
- assert( pPager->state>=PAGER_SHARED );
- assert( (pisOpen==0 && !pPager->tempFile && !pPager->pWal) || *pisOpen==0 );
+ assert( assert_pager_state(pPager) );
+ assert( pPager->eState==PAGER_NONE || 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;
@@ -6092,9 +6388,10 @@ int sqlite3PagerOpenWal(
rc = sqlite3WalOpen(pPager->pVfs, pPager->fd, pPager->zWal, &pPager->pWal);
if( rc==SQLITE_OK ){
pPager->journalMode = PAGER_JOURNALMODE_WAL;
+ pPager->eState = PAGER_NONE;
}
}else{
- *pisOpen = 1;
+ *pbOpen = 1;
}
return rc;
diff --git a/src/sqlite.h.in b/src/sqlite.h.in
index 57910ee32..7764d2a86 100644
--- a/src/sqlite.h.in
+++ b/src/sqlite.h.in
@@ -690,12 +690,21 @@ struct sqlite3_io_methods {
** is often close. The underlying VFS might choose to preallocate database
** file space based on this hint in order to help writes to the database
** file run faster.
+**
+** The [SQLITE_FCNTL_CHUNK_SIZE] opcode is used to request that the VFS
+** extends and truncates the database file in chunks of a size specified
+** by the user. The fourth argument to [sqlite3_file_control()] should
+** point to an integer (type int) containing the new chunk-size to use
+** for the nominated database. Allocating database file space in large
+** chunks (say 1MB at a time), may reduce file-system fragmentation and
+** improve performance on some systems.
*/
#define SQLITE_FCNTL_LOCKSTATE 1
#define SQLITE_GET_LOCKPROXYFILE 2
#define SQLITE_SET_LOCKPROXYFILE 3
#define SQLITE_LAST_ERRNO 4
#define SQLITE_FCNTL_SIZE_HINT 5
+#define SQLITE_FCNTL_CHUNK_SIZE 6
/*
** CAPI3REF: Mutex Handle
diff --git a/src/test1.c b/src/test1.c
index 33ac7a1bd..acb812b37 100644
--- a/src/test1.c
+++ b/src/test1.c
@@ -4614,6 +4614,44 @@ static int file_control_lasterrno_test(
}
/*
+** tclcmd: file_control_chunksize_test DB DBNAME SIZE
+**
+** This TCL command runs the sqlite3_file_control interface and
+** verifies correct operation of the SQLITE_GET_LOCKPROXYFILE and
+** SQLITE_SET_LOCKPROXYFILE verbs.
+*/
+static int file_control_chunksize_test(
+ ClientData clientData, /* Pointer to sqlite3_enable_XXX function */
+ Tcl_Interp *interp, /* The TCL interpreter that invoked this command */
+ int objc, /* Number of arguments */
+ Tcl_Obj *CONST objv[] /* Command arguments */
+){
+ int nSize; /* New chunk size */
+ char *zDb; /* Db name ("main", "temp" etc.) */
+ sqlite3 *db; /* Database handle */
+ int rc; /* file_control() return code */
+
+ if( objc!=4 ){
+ Tcl_WrongNumArgs(interp, 1, objv, "DB DBNAME SIZE");
+ return TCL_ERROR;
+ }
+ if( getDbPointer(interp, Tcl_GetString(objv[1]), &db)
+ || Tcl_GetIntFromObj(interp, objv[3], &nSize)
+ ){
+ return TCL_ERROR;
+ }
+ zDb = Tcl_GetString(objv[2]);
+ if( zDb[0]=='\0' ) zDb = NULL;
+
+ rc = sqlite3_file_control(db, zDb, SQLITE_FCNTL_CHUNK_SIZE, (void *)&nSize);
+ if( rc ){
+ Tcl_SetResult(interp, (char *)sqlite3TestErrorName(rc), TCL_STATIC);
+ return TCL_ERROR;
+ }
+ return TCL_OK;
+}
+
+/*
** tclcmd: file_control_lockproxy_test DB PWD
**
** This TCL command runs the sqlite3_file_control interface and
@@ -5106,6 +5144,7 @@ int Sqlitetest1_Init(Tcl_Interp *interp){
{ "file_control_test", file_control_test, 0 },
{ "file_control_lasterrno_test", file_control_lasterrno_test, 0 },
{ "file_control_lockproxy_test", file_control_lockproxy_test, 0 },
+ { "file_control_chunksize_test", file_control_chunksize_test, 0 },
{ "sqlite3_vfs_list", vfs_list, 0 },
/* Functions from os.h */
diff --git a/src/test_stat.c b/src/test_stat.c
index 894be80ec..153462111 100644
--- a/src/test_stat.c
+++ b/src/test_stat.c
@@ -376,7 +376,13 @@ static int statNext(sqlite3_vtab_cursor *pCursor){
if( pCsr->aPage[0].pPg==0 ){
rc = sqlite3_step(pCsr->pStmt);
if( rc==SQLITE_ROW ){
+ int nPage;
u32 iRoot = sqlite3_column_int64(pCsr->pStmt, 1);
+ sqlite3PagerPagecount(pPager, &nPage);
+ if( nPage==0 ){
+ pCsr->isEof = 1;
+ return sqlite3_reset(pCsr->pStmt);
+ }
rc = sqlite3PagerGet(pPager, iRoot, &pCsr->aPage[0].pPg);
pCsr->aPage[0].iPgno = iRoot;
pCsr->aPage[0].iCell = 0;
@@ -486,17 +492,9 @@ static int statFilter(
int idxNum, const char *idxStr,
int argc, sqlite3_value **argv
){
- sqlite3 *db = ((StatTable *)(pCursor->pVtab))->db;
StatCursor *pCsr = (StatCursor *)pCursor;
- int nPage = 0;
statResetCsr((StatCursor *)pCursor);
- sqlite3PagerPagecount(sqlite3BtreePager(db->aDb[0].pBt), &nPage);
- if( nPage==0 ){
- pCsr->isEof = 1;
- return SQLITE_OK;
- }
-
return statNext(pCursor);
}
diff --git a/src/wal.c b/src/wal.c
index 54274640e..be8c46642 100644
--- a/src/wal.c
+++ b/src/wal.c
@@ -1521,6 +1521,7 @@ static int walCheckpoint(
u32 iDbpage = 0; /* Next database page to write */
u32 iFrame = 0; /* Wal frame containing data for iDbpage */
u32 mxSafeFrame; /* Max frame that can be backfilled */
+ u32 mxPage; /* Max database page to write */
int i; /* Loop counter */
volatile WalCkptInfo *pInfo; /* The checkpoint status information */
@@ -1545,6 +1546,7 @@ static int walCheckpoint(
** cannot be backfilled from the WAL.
*/
mxSafeFrame = pWal->hdr.mxFrame;
+ mxPage = pWal->hdr.nPage;
pInfo = walCkptInfo(pWal);
for(i=1; i<WAL_NREADER; i++){
u32 y = pInfo->aReadMark[i];
@@ -1565,6 +1567,8 @@ static int walCheckpoint(
if( pInfo->nBackfill<mxSafeFrame
&& (rc = walLockExclusive(pWal, WAL_READ_LOCK(0), 1))==SQLITE_OK
){
+ i64 nReq; /* File size hint passed to VFS */
+ i64 nSize; /* Current size of database file */
u32 nBackfill = pInfo->nBackfill;
/* Sync the WAL to disk */
@@ -1572,11 +1576,20 @@ static int walCheckpoint(
rc = sqlite3OsSync(pWal->pWalFd, sync_flags);
}
+ /* If the database file may grow as a result of this checkpoint, hint
+ ** about the eventual size of the db file to the VFS layer.
+ */
+ nReq = ((i64)mxPage * szPage);
+ rc = sqlite3OsFileSize(pWal->pDbFd, &nSize);
+ if( rc==SQLITE_OK && nSize<nReq ){
+ rc = sqlite3OsFileControl(pWal->pDbFd, SQLITE_FCNTL_SIZE_HINT, &nReq);
+ }
+
/* Iterate through the contents of the WAL, copying data to the db file. */
while( rc==SQLITE_OK && 0==walIteratorNext(pIter, &iDbpage, &iFrame) ){
i64 iOffset;
assert( walFramePgno(pWal, iFrame)==iDbpage );
- if( iFrame<=nBackfill || iFrame>mxSafeFrame ) continue;
+ if( iFrame<=nBackfill || iFrame>mxSafeFrame || iDbpage>mxPage ) continue;
iOffset = walFrameOffset(iFrame, szPage) + WAL_FRAME_HDRSIZE;
/* testcase( IS_BIG_INT(iOffset) ); // requires a 4GiB WAL file */
rc = sqlite3OsRead(pWal->pWalFd, zBuf, szPage, iOffset);
@@ -2153,11 +2166,13 @@ int sqlite3WalRead(
/*
-** Set *pPgno to the size of the database file (or zero, if unknown).
+** Return the size of the database in pages (or zero, if unknown).
*/
-void sqlite3WalDbsize(Wal *pWal, Pgno *pPgno){
- assert( pWal->readLock>=0 || pWal->lockError );
- *pPgno = pWal->hdr.nPage;
+Pgno sqlite3WalDbsize(Wal *pWal){
+ if( pWal && pWal->readLock>=0 ){
+ return pWal->hdr.nPage;
+ }
+ return 0;
}
diff --git a/src/wal.h b/src/wal.h
index f29d5838f..f20dfa8e1 100644
--- a/src/wal.h
+++ b/src/wal.h
@@ -25,7 +25,7 @@
# define sqlite3WalBeginReadTransaction(y,z) 0
# define sqlite3WalEndReadTransaction(z)
# define sqlite3WalRead(v,w,x,y,z) 0
-# define sqlite3WalDbsize(y,z)
+# define sqlite3WalDbsize(y) 0
# define sqlite3WalBeginWriteTransaction(y) 0
# define sqlite3WalEndWriteTransaction(x) 0
# define sqlite3WalUndo(x,y,z) 0
@@ -61,9 +61,8 @@ void sqlite3WalEndReadTransaction(Wal *pWal);
/* Read a page from the write-ahead log, if it is present. */
int sqlite3WalRead(Wal *pWal, Pgno pgno, int *pInWal, int nOut, u8 *pOut);
-/* Return the size of the database as it existed at the beginning
-** of the snapshot */
-void sqlite3WalDbsize(Wal *pWal, Pgno *pPgno);
+/* If the WAL is not empty, return the size of the database. */
+Pgno sqlite3WalDbsize(Wal *pWal);
/* Obtain or release the WRITER lock. */
int sqlite3WalBeginWriteTransaction(Wal *pWal);