** file simultaneously, or one process from reading the database while
** another is writing.
**
-** @(#) $Id: pager.c,v 1.106 2004/05/10 10:34:49 danielk1977 Exp $
+** @(#) $Id: pager.c,v 1.107 2004/05/12 13:30:08 drh Exp $
*/
#include "os.h" /* Must be first to enable large file support */
#include "sqliteInt.h"
Pager *pPager; /* The pager to which this page belongs */
Pgno pgno; /* The page number for this page */
PgHdr *pNextHash, *pPrevHash; /* Hash collision chain for PgHdr.pgno */
- int nRef; /* Number of users of this page */
PgHdr *pNextFree, *pPrevFree; /* Freelist of pages where nRef==0 */
- PgHdr *pNextAll, *pPrevAll; /* A list of all pages */
- PgHdr *pNextCkpt, *pPrevCkpt; /* List of pages in the checkpoint journal */
+ PgHdr *pNextAll; /* A list of all pages */
+ PgHdr *pNextStmt, *pPrevStmt; /* List of pages in the statement journal */
u8 inJournal; /* TRUE if has been written to journal */
- u8 inCkpt; /* TRUE if written to the checkpoint journal */
+ u8 inStmt; /* TRUE if in the statement subjournal */
u8 dirty; /* TRUE if we need to write back changes */
u8 needSync; /* Sync journal before writing this page */
u8 alwaysRollback; /* Disable dont_rollback() for this page */
+ short int nRef; /* Number of users of this page */
PgHdr *pDirty; /* Dirty pages sorted by PgHdr.pgno */
/* SQLITE_PAGE_SIZE bytes of page data follow this header */
/* Pager.nExtra bytes of local data follow the page data */
};
+/*
+** For an in-memory only database, some extra information is recorded about
+** each page so that changes can be rolled back. (Journal files are not
+** used for in-memory databases.) The following information is added to
+** the end of every EXTRA block for in-memory databases.
+**
+** This information could have been added directly to the PgHdr structure.
+** But then it would take up an extra 8 bytes of storage on every PgHdr
+** even for disk-based databases. Splitting it out saves 8 bytes. This
+** is only a savings of 0.8% but those percentages add up.
+*/
+typedef struct PgHistory PgHistory;
+struct PgHistory {
+ u8 *pOrig; /* Original page text. Restore to this on a full rollback */
+ u8 *pStmt; /* Text as it was at the beginning of the current statement */
+};
/*
** A macro used for invoking the codec if there is one
#define PGHDR_TO_DATA(P) ((void*)(&(P)[1]))
#define DATA_TO_PGHDR(D) (&((PgHdr*)(D))[-1])
#define PGHDR_TO_EXTRA(P) ((void*)&((char*)(&(P)[1]))[SQLITE_PAGE_SIZE])
+#define PGHDR_TO_HIST(P,PGR) \
+ ((PgHistory*)&((char*)(&(P)[1]))[(PGR)->pageSize+(PGR)->nExtra])
/*
** How big to make the hash table used for locating in-memory pages
char *zJournal; /* Name of the journal file */
char *zDirectory; /* Directory hold database and journal files */
OsFile fd, jfd; /* File descriptors for database and journal */
- OsFile cpfd; /* File descriptor for the checkpoint journal */
+ OsFile stfd; /* File descriptor for the statement subjournal*/
int dbSize; /* Number of pages in the file */
int origDbSize; /* dbSize before the current change */
- int ckptSize; /* Size of database (in pages) at ckpt_begin() */
- off_t ckptJSize; /* Size of journal at ckpt_begin() */
+ int stmtSize; /* Size of database (in pages) at stmt_begin() */
+ off_t stmtJSize; /* Size of journal at stmt_begin() */
int nRec; /* Number of pages written to the journal */
u32 cksumInit; /* Quasi-random value added to every checksum */
- int ckptNRec; /* Number of records in the checkpoint journal */
+ int stmtNRec; /* Number of records in stmt subjournal */
int nExtra; /* Add this many bytes to each in-memory page */
void (*xDestructor)(void*); /* Call this routine when freeing pages */
int nPage; /* Total number of in-memory pages */
int nHit, nMiss, nOvfl; /* Cache hits, missing, and LRU overflows */
void (*xCodec)(void*,void*,Pgno,int); /* Routine for en/decoding data */
void *pCodecArg; /* First argument to xCodec() */
+ int pageSize; /* Page size in bytes */
u8 journalOpen; /* True if journal file descriptors is valid */
u8 journalStarted; /* True if header of journal is synced */
u8 useJournal; /* Use a rollback journal on this file */
- u8 ckptOpen; /* True if the checkpoint journal is open */
- u8 ckptInUse; /* True we are in a checkpoint */
- u8 ckptAutoopen; /* Open ckpt journal when main journal is opened*/
+ u8 stmtOpen; /* True if the statement subjournal is open */
+ u8 stmtInUse; /* True we are in a statement subtransaction */
+ u8 stmtAutoopen; /* Open stmt journal when main journal is opened*/
u8 noSync; /* Do not sync the journal if true */
u8 fullSync; /* Do extra syncs of the journal for robustness */
u8 state; /* SQLITE_UNLOCK, _READLOCK or _WRITELOCK */
u8 needSync; /* True if an fsync() is needed on the journal */
u8 dirtyFile; /* True if database file has changed in any way */
u8 alwaysRollback; /* Disable dont_rollback() for all pages */
+ u8 memDb; /* True to inhibit all file I/O */
u8 *aInJournal; /* One bit for each page in the database file */
- u8 *aInCkpt; /* One bit for each page in the database */
+ u8 *aInStmt; /* One bit for each page in the database */
PgHdr *pFirst, *pLast; /* List of free pages */
PgHdr *pFirstSynced; /* First free page with PgHdr.needSync==0 */
PgHdr *pAll; /* List of all pages */
- PgHdr *pCkpt; /* List of pages in the checkpoint journal */
+ PgHdr *pStmt; /* List of pages in the statement subjournal */
PgHdr *aHash[N_PG_HASH]; /* Hash table to map page number of PgHdr */
};
** make sure that newer versions of the library are able to rollback older
** journal files.
**
-** Note that checkpoint journals always use format 2 and omit the header.
+** Note that statement journals always use format 2 and omit the header.
*/
#ifdef SQLITE_TEST
int journal_format = 3;
/*
** Add or remove a page from the list of all pages that are in the
-** checkpoint journal.
+** statement journal.
**
** The Pager keeps a separate list of pages that are currently in
-** the checkpoint journal. This helps the sqlite3pager_stmt_commit()
+** the statement journal. This helps the sqlite3pager_stmt_commit()
** routine run MUCH faster for the common case where there are many
-** pages in memory but only a few are in the checkpoint journal.
+** pages in memory but only a few are in the statement journal.
*/
static void page_add_to_stmt_list(PgHdr *pPg){
Pager *pPager = pPg->pPager;
- if( pPg->inCkpt ) return;
- assert( pPg->pPrevCkpt==0 && pPg->pNextCkpt==0 );
- pPg->pPrevCkpt = 0;
- if( pPager->pCkpt ){
- pPager->pCkpt->pPrevCkpt = pPg;
- }
- pPg->pNextCkpt = pPager->pCkpt;
- pPager->pCkpt = pPg;
- pPg->inCkpt = 1;
+ if( pPg->inStmt ) return;
+ assert( pPg->pPrevStmt==0 && pPg->pNextStmt==0 );
+ pPg->pPrevStmt = 0;
+ if( pPager->pStmt ){
+ pPager->pStmt->pPrevStmt = pPg;
+ }
+ pPg->pNextStmt = pPager->pStmt;
+ pPager->pStmt = pPg;
+ pPg->inStmt = 1;
}
static void page_remove_from_stmt_list(PgHdr *pPg){
- if( !pPg->inCkpt ) return;
- if( pPg->pPrevCkpt ){
- assert( pPg->pPrevCkpt->pNextCkpt==pPg );
- pPg->pPrevCkpt->pNextCkpt = pPg->pNextCkpt;
+ if( !pPg->inStmt ) return;
+ if( pPg->pPrevStmt ){
+ assert( pPg->pPrevStmt->pNextStmt==pPg );
+ pPg->pPrevStmt->pNextStmt = pPg->pNextStmt;
}else{
- assert( pPg->pPager->pCkpt==pPg );
- pPg->pPager->pCkpt = pPg->pNextCkpt;
+ assert( pPg->pPager->pStmt==pPg );
+ pPg->pPager->pStmt = pPg->pNextStmt;
}
- if( pPg->pNextCkpt ){
- assert( pPg->pNextCkpt->pPrevCkpt==pPg );
- pPg->pNextCkpt->pPrevCkpt = pPg->pPrevCkpt;
+ if( pPg->pNextStmt ){
+ assert( pPg->pNextStmt->pPrevStmt==pPg );
+ pPg->pNextStmt->pPrevStmt = pPg->pPrevStmt;
}
- pPg->pNextCkpt = 0;
- pPg->pPrevCkpt = 0;
- pPg->inCkpt = 0;
+ pPg->pNextStmt = 0;
+ pPg->pPrevStmt = 0;
+ pPg->inStmt = 0;
}
/*
PgHdr *pPg;
if( pPager->state<SQLITE_WRITELOCK ) return SQLITE_OK;
sqlite3pager_stmt_commit(pPager);
- if( pPager->ckptOpen ){
- sqlite3OsClose(&pPager->cpfd);
- pPager->ckptOpen = 0;
+ if( pPager->stmtOpen ){
+ sqlite3OsClose(&pPager->stfd);
+ pPager->stmtOpen = 0;
}
if( pPager->journalOpen ){
sqlite3OsClose(&pPager->jfd);
}
/*
-** Playback the checkpoint journal.
+** Playback the statement journal.
**
** This is similar to playing back the transaction journal but with
** a few extra twists.
**
** (1) The number of pages in the database file at the start of
-** the checkpoint is stored in pPager->ckptSize, not in the
+** the statement is stored in pPager->stmtSize, not in the
** journal file itself.
**
-** (2) In addition to playing back the checkpoint journal, also
+** (2) In addition to playing back the statement journal, also
** playback all pages of the transaction journal beginning
-** at offset pPager->ckptJSize.
+** at offset pPager->stmtJSize.
*/
static int pager_stmt_playback(Pager *pPager){
off_t szJ; /* Size of the full journal */
/* Truncate the database back to its original size.
*/
- rc = sqlite3OsTruncate(&pPager->fd, SQLITE_PAGE_SIZE*(off_t)pPager->ckptSize);
- pPager->dbSize = pPager->ckptSize;
+ rc = sqlite3OsTruncate(&pPager->fd, SQLITE_PAGE_SIZE*(off_t)pPager->stmtSize);
+ pPager->dbSize = pPager->stmtSize;
- /* Figure out how many records are in the checkpoint journal.
+ /* Figure out how many records are in the statement journal.
*/
- assert( pPager->ckptInUse && pPager->journalOpen );
- sqlite3OsSeek(&pPager->cpfd, 0);
- nRec = pPager->ckptNRec;
+ assert( pPager->stmtInUse && pPager->journalOpen );
+ sqlite3OsSeek(&pPager->stfd, 0);
+ nRec = pPager->stmtNRec;
- /* Copy original pages out of the checkpoint journal and back into the
- ** database file. Note that the checkpoint journal always uses format
+ /* Copy original pages out of the statement journal and back into the
+ ** database file. Note that the statement journal always uses format
** 2 instead of format 3 since it does not need to be concerned with
** power failures corrupting the journal and can thus omit the checksums.
*/
for(i=nRec-1; i>=0; i--){
- rc = pager_playback_one_page(pPager, &pPager->cpfd, 2);
+ rc = pager_playback_one_page(pPager, &pPager->stfd, 2);
assert( rc!=SQLITE_DONE );
if( rc!=SQLITE_OK ) goto end_stmt_playback;
}
/* Figure out how many pages need to be copied out of the transaction
** journal.
*/
- rc = sqlite3OsSeek(&pPager->jfd, pPager->ckptJSize);
+ rc = sqlite3OsSeek(&pPager->jfd, pPager->stmtJSize);
if( rc!=SQLITE_OK ){
goto end_stmt_playback;
}
if( rc!=SQLITE_OK ){
goto end_stmt_playback;
}
- nRec = (szJ - pPager->ckptJSize)/JOURNAL_PG_SZ(journal_format);
+ nRec = (szJ - pPager->stmtJSize)/JOURNAL_PG_SZ(journal_format);
for(i=nRec-1; i>=0; i--){
rc = pager_playback_one_page(pPager, &pPager->jfd, journal_format);
if( rc!=SQLITE_OK ){
OsFile fd;
int rc, i;
int tempFile;
+ int memDb = 0;
int readOnly = 0;
char zTemp[SQLITE_TEMPNAME_SIZE];
return SQLITE_NOMEM;
}
if( zFilename && zFilename[0] ){
- zFullPathname = sqlite3OsFullPathname(zFilename);
- rc = sqlite3OsOpenReadWrite(zFullPathname, &fd, &readOnly);
- tempFile = 0;
+ if( strcmp(zFilename,":memory:")==0 ){
+ memDb = 1;
+ zFullPathname = sqliteMalloc(4);
+ if( zFullPathname ) strcpy(zFullPathname, "nil");
+ rc = SQLITE_OK;
+ }else{
+ zFullPathname = sqlite3OsFullPathname(zFilename);
+ rc = sqlite3OsOpenReadWrite(zFullPathname, &fd, &readOnly);
+ tempFile = 0;
+ }
}else{
rc = sqlite3pager_opentemp(zTemp, &fd);
zFilename = zTemp;
strcpy(&pPager->zJournal[nameLen], "-journal");
pPager->fd = fd;
pPager->journalOpen = 0;
- pPager->useJournal = useJournal;
- pPager->ckptOpen = 0;
- pPager->ckptInUse = 0;
+ pPager->useJournal = useJournal && !memDb;
+ pPager->stmtOpen = 0;
+ pPager->stmtInUse = 0;
pPager->nRef = 0;
- pPager->dbSize = -1;
- pPager->ckptSize = 0;
- pPager->ckptJSize = 0;
+ pPager->dbSize = memDb-1;
+ pPager->pageSize = SQLITE_PAGE_SIZE;
+ pPager->stmtSize = 0;
+ pPager->stmtJSize = 0;
pPager->nPage = 0;
pPager->mxPage = mxPage>5 ? mxPage : 10;
pPager->state = SQLITE_UNLOCK;
pPager->errMask = 0;
pPager->tempFile = tempFile;
+ pPager->memDb = memDb;
pPager->readOnly = readOnly;
pPager->needSync = 0;
pPager->noSync = pPager->tempFile || !useJournal;
*/
static int syncJournal(Pager*);
+
+/*
+** Unlink a page from the free list (the list of all pages where nRef==0)
+** and from its hash collision chain.
+*/
+static void unlinkPage(PgHdr *pPg){
+ Pager *pPager = pPg->pPager;
+
+ /* Keep the pFirstSynced pointer pointing at the first synchronized page */
+ if( pPg==pPager->pFirstSynced ){
+ PgHdr *p = pPg->pNextFree;
+ while( p && p->needSync ){ p = p->pNextFree; }
+ pPager->pFirstSynced = p;
+ }
+
+ /* Unlink from the freelist */
+ if( pPg->pPrevFree ){
+ pPg->pPrevFree->pNextFree = pPg->pNextFree;
+ }else{
+ assert( pPager->pFirst==pPg );
+ pPager->pFirst = pPg->pNextFree;
+ }
+ if( pPg->pNextFree ){
+ pPg->pNextFree->pPrevFree = pPg->pPrevFree;
+ }else{
+ assert( pPager->pLast==pPg );
+ pPager->pLast = pPg->pPrevFree;
+ }
+ pPg->pNextFree = pPg->pPrevFree = 0;
+
+ /* Unlink from the pgno hash table */
+ if( pPg->pNextHash ){
+ pPg->pNextHash->pPrevHash = pPg->pPrevHash;
+ }
+ if( pPg->pPrevHash ){
+ pPg->pPrevHash->pNextHash = pPg->pNextHash;
+ }else{
+ int h = pager_hash(pPg->pgno);
+ assert( pPager->aHash[h]==pPg );
+ pPager->aHash[h] = pPg->pNextHash;
+ }
+ pPg->pNextHash = pPg->pPrevHash = 0;
+}
+
+/*
+** This routine is used to truncate an in-memory database. Delete
+** every pages whose pgno is larger than pPager->dbSize and is unreferenced.
+** Referenced pages larger than pPager->dbSize are zeroed.
+*/
+static void memoryTruncate(Pager *pPager){
+ PgHdr *pPg;
+ PgHdr **ppPg;
+ int dbSize = pPager->dbSize;
+
+ ppPg = &pPager->pAll;
+ while( (pPg = *ppPg)!=0 ){
+ if( pPg->pgno<=dbSize ){
+ ppPg = &pPg->pNextAll;
+ }else if( pPg->nRef>0 ){
+ memset(PGHDR_TO_DATA(pPg), 0, pPager->pageSize);
+ ppPg = &pPg->pNextAll;
+ }else{
+ *ppPg = pPg->pNextAll;
+ unlinkPage(pPg);
+ sqliteFree(pPg);
+ pPager->nPage--;
+ }
+ }
+}
+
/*
** Truncate the file to the number of pages specified.
*/
if( nPage>=(unsigned)pPager->dbSize ){
return SQLITE_OK;
}
+ if( pPager->memDb ){
+ pPager->dbSize = nPage;
+ memoryTruncate(pPager);
+ return SQLITE_OK;
+ }
syncJournal(pPager);
rc = sqlite3OsTruncate(&pPager->fd, SQLITE_PAGE_SIZE*(off_t)nPage);
if( rc==SQLITE_OK ){
switch( pPager->state ){
case SQLITE_WRITELOCK: {
sqlite3pager_rollback(pPager);
- sqlite3OsUnlock(&pPager->fd);
+ if( !pPager->memDb ){
+ sqlite3OsUnlock(&pPager->fd);
+ }
assert( pPager->journalOpen==0 );
break;
}
case SQLITE_READLOCK: {
- sqlite3OsUnlock(&pPager->fd);
+ if( !pPager->memDb ){
+ sqlite3OsUnlock(&pPager->fd);
+ }
break;
}
default: {
pNext = pPg->pNextAll;
sqliteFree(pPg);
}
- sqlite3OsClose(&pPager->fd);
+ if( !pPager->memDb ){
+ sqlite3OsClose(&pPager->fd);
+ }
assert( pPager->journalOpen==0 );
/* Temp files are automatically deleted by the OS
** if( pPager->tempFile ){
/* If this is the first page accessed, then get a read lock
** on the database file.
*/
- if( pPager->nRef==0 ){
+ if( pPager->nRef==0 && !pPager->memDb ){
rc = sqlite3OsReadLock(&pPager->fd);
if( rc!=SQLITE_OK ){
return rc;
}else{
/* Search for page in cache */
pPg = pager_lookup(pPager, pgno);
+ if( pPager->memDb && pPager->state==SQLITE_UNLOCK ){
+ pPager->state = SQLITE_READLOCK;
+ }
}
if( pPg==0 ){
/* The requested page is not in the page cache. */
int h;
pPager->nMiss++;
- if( pPager->nPage<pPager->mxPage || pPager->pFirst==0 ){
+ if( pPager->nPage<pPager->mxPage || pPager->pFirst==0 || pPager->memDb ){
/* Create a new page */
pPg = sqliteMallocRaw( sizeof(*pPg) + SQLITE_PAGE_SIZE
- + sizeof(u32) + pPager->nExtra );
+ + sizeof(u32) + pPager->nExtra
+ + pPager->memDb*sizeof(PgHistory) );
if( pPg==0 ){
pager_unwritelock(pPager);
pPager->errMask |= PAGER_ERR_MEM;
return SQLITE_NOMEM;
}
memset(pPg, 0, sizeof(*pPg));
+ if( pPager->memDb ){
+ memset(PGHDR_TO_HIST(pPg, pPager), 0, sizeof(PgHistory));
+ }
pPg->pPager = pPager;
pPg->pNextAll = pPager->pAll;
- if( pPager->pAll ){
- pPager->pAll->pPrevAll = pPg;
- }
- pPg->pPrevAll = 0;
pPager->pAll = pPg;
pPager->nPage++;
}else{
/* Unlink the old page from the free list and the hash table
*/
- if( pPg==pPager->pFirstSynced ){
- PgHdr *p = pPg->pNextFree;
- while( p && p->needSync ){ p = p->pNextFree; }
- pPager->pFirstSynced = p;
- }
- if( pPg->pPrevFree ){
- pPg->pPrevFree->pNextFree = pPg->pNextFree;
- }else{
- assert( pPager->pFirst==pPg );
- pPager->pFirst = pPg->pNextFree;
- }
- if( pPg->pNextFree ){
- pPg->pNextFree->pPrevFree = pPg->pPrevFree;
- }else{
- assert( pPager->pLast==pPg );
- pPager->pLast = pPg->pPrevFree;
- }
- pPg->pNextFree = pPg->pPrevFree = 0;
- if( pPg->pNextHash ){
- pPg->pNextHash->pPrevHash = pPg->pPrevHash;
- }
- if( pPg->pPrevHash ){
- pPg->pPrevHash->pNextHash = pPg->pNextHash;
- }else{
- h = pager_hash(pPg->pgno);
- assert( pPager->aHash[h]==pPg );
- pPager->aHash[h] = pPg->pNextHash;
- }
- pPg->pNextHash = pPg->pPrevHash = 0;
+ unlinkPage(pPg);
pPager->nOvfl++;
}
pPg->pgno = pgno;
pPg->inJournal = 0;
pPg->needSync = 0;
}
- if( pPager->aInCkpt && (int)pgno<=pPager->ckptSize
- && (pPager->aInCkpt[pgno/8] & (1<<(pgno&7)))!=0 ){
+ if( pPager->aInStmt && (int)pgno<=pPager->stmtSize
+ && (pPager->aInStmt[pgno/8] & (1<<(pgno&7)))!=0 ){
page_add_to_stmt_list(pPg);
}else{
page_remove_from_stmt_list(pPg);
memset(PGHDR_TO_DATA(pPg), 0, SQLITE_PAGE_SIZE);
}else{
int rc;
+ assert( pPager->memDb==0 );
sqlite3OsSeek(&pPager->fd, (pgno-1)*(off_t)SQLITE_PAGE_SIZE);
rc = sqlite3OsRead(&pPager->fd, PGHDR_TO_DATA(pPg), SQLITE_PAGE_SIZE);
TRACE2("FETCH %d\n", pPg->pgno);
if( pPager->errMask & ~(PAGER_ERR_FULL) ){
return 0;
}
- /* if( pPager->nRef==0 ){
- ** return 0;
- ** }
- */
pPg = pager_lookup(pPager, pgno);
if( pPg==0 ) return 0;
page_ref(pPg);
*/
pPager->nRef--;
assert( pPager->nRef>=0 );
- if( pPager->nRef==0 ){
+ if( pPager->nRef==0 && !pPager->memDb ){
pager_reset(pPager);
}
}
if( rc==SQLITE_OK ){
rc = write32bits(&pPager->jfd, pPager->dbSize);
}
- if( pPager->ckptAutoopen && rc==SQLITE_OK ){
+ if( pPager->stmtAutoopen && rc==SQLITE_OK ){
rc = sqlite3pager_stmt_begin(pPager);
}
if( rc!=SQLITE_OK ){
assert( pPager->state!=SQLITE_UNLOCK );
if( pPager->state==SQLITE_READLOCK ){
assert( pPager->aInJournal==0 );
- rc = sqlite3OsWriteLock(&pPager->fd);
- if( rc!=SQLITE_OK ){
- return rc;
- }
- pPager->state = SQLITE_WRITELOCK;
- pPager->dirtyFile = 0;
- TRACE1("TRANSACTION\n");
- if( pPager->useJournal && !pPager->tempFile ){
- rc = pager_open_journal(pPager);
+ if( pPager->memDb ){
+ pPager->state = SQLITE_WRITELOCK;
+ pPager->origDbSize = pPager->dbSize;
+ }else{
+ rc = sqlite3OsWriteLock(&pPager->fd);
+ if( rc!=SQLITE_OK ){
+ return rc;
+ }
+ pPager->state = SQLITE_WRITELOCK;
+ pPager->dirtyFile = 0;
+ TRACE1("TRANSACTION\n");
+ if( pPager->useJournal && !pPager->tempFile ){
+ rc = pager_open_journal(pPager);
+ }
}
}
return rc;
** to the journal then we can return right away.
*/
pPg->dirty = 1;
- if( pPg->inJournal && (pPg->inCkpt || pPager->ckptInUse==0) ){
+ if( pPg->inJournal && (pPg->inStmt || pPager->stmtInUse==0) ){
pPager->dirtyFile = 1;
return SQLITE_OK;
}
** main database file. Write the current page to the transaction
** journal if it is not there already.
*/
- if( !pPg->inJournal && pPager->useJournal ){
+ if( !pPg->inJournal && (pPager->useJournal || pPager->memDb) ){
if( (int)pPg->pgno <= pPager->origDbSize ){
int szPg;
u32 saved;
- if( journal_format>=JOURNAL_FORMAT_3 ){
- u32 cksum = pager_cksum(pPager, pPg->pgno, pData);
- saved = *(u32*)PGHDR_TO_EXTRA(pPg);
- store32bits(cksum, pPg, SQLITE_PAGE_SIZE);
- szPg = SQLITE_PAGE_SIZE+8;
- }else{
- szPg = SQLITE_PAGE_SIZE+4;
- }
- store32bits(pPg->pgno, pPg, -4);
- CODEC(pPager, pData, pPg->pgno, 7);
- rc = sqlite3OsWrite(&pPager->jfd, &((char*)pData)[-4], szPg);
- TRACE3("JOURNAL %d %d\n", pPg->pgno, pPg->needSync);
- CODEC(pPager, pData, pPg->pgno, 0);
- if( journal_format>=JOURNAL_FORMAT_3 ){
- *(u32*)PGHDR_TO_EXTRA(pPg) = saved;
- }
- if( rc!=SQLITE_OK ){
- sqlite3pager_rollback(pPager);
- pPager->errMask |= PAGER_ERR_FULL;
- return rc;
- }
- pPager->nRec++;
- assert( pPager->aInJournal!=0 );
- pPager->aInJournal[pPg->pgno/8] |= 1<<(pPg->pgno&7);
- pPg->needSync = !pPager->noSync;
- pPg->inJournal = 1;
- if( pPager->ckptInUse ){
- pPager->aInCkpt[pPg->pgno/8] |= 1<<(pPg->pgno&7);
- page_add_to_stmt_list(pPg);
+ if( pPager->memDb ){
+ PgHistory *pHist = PGHDR_TO_HIST(pPg, pPager);
+ TRACE2("JOURNAL %d\n", pPg->pgno);
+ assert( pHist->pOrig==0 );
+ pHist->pOrig = sqliteMallocRaw( pPager->pageSize );
+ if( pHist->pOrig ){
+ memcpy(pHist->pOrig, PGHDR_TO_DATA(pPg), pPager->pageSize);
+ }
+ pPg->inJournal = 1;
+ }else {
+ if( journal_format>=JOURNAL_FORMAT_3 ){
+ u32 cksum = pager_cksum(pPager, pPg->pgno, pData);
+ saved = *(u32*)PGHDR_TO_EXTRA(pPg);
+ store32bits(cksum, pPg, SQLITE_PAGE_SIZE);
+ szPg = SQLITE_PAGE_SIZE+8;
+ }else{
+ szPg = SQLITE_PAGE_SIZE+4;
+ }
+ store32bits(pPg->pgno, pPg, -4);
+ CODEC(pPager, pData, pPg->pgno, 7);
+ rc = sqlite3OsWrite(&pPager->jfd, &((char*)pData)[-4], szPg);
+ TRACE3("JOURNAL %d %d\n", pPg->pgno, pPg->needSync);
+ CODEC(pPager, pData, pPg->pgno, 0);
+ if( journal_format>=JOURNAL_FORMAT_3 ){
+ *(u32*)PGHDR_TO_EXTRA(pPg) = saved;
+ }
+ if( rc!=SQLITE_OK ){
+ sqlite3pager_rollback(pPager);
+ pPager->errMask |= PAGER_ERR_FULL;
+ return rc;
+ }
+ pPager->nRec++;
+ assert( pPager->aInJournal!=0 );
+ pPager->aInJournal[pPg->pgno/8] |= 1<<(pPg->pgno&7);
+ pPg->needSync = !pPager->noSync;
+ pPg->inJournal = 1;
+ if( pPager->stmtInUse ){
+ pPager->aInStmt[pPg->pgno/8] |= 1<<(pPg->pgno&7);
+ page_add_to_stmt_list(pPg);
+ }
}
}else{
pPg->needSync = !pPager->journalStarted && !pPager->noSync;
}
}
- /* If the checkpoint journal is open and the page is not in it,
- ** then write the current page to the checkpoint journal. Note that
- ** the checkpoint journal always uses the simplier format 2 that lacks
- ** checksums. The header is also omitted from the checkpoint journal.
+ /* If the statement journal is open and the page is not in it,
+ ** then write the current page to the statement journal. Note that
+ ** the statement journal always uses the simplier format 2 that lacks
+ ** checksums. The header is also omitted from the statement journal.
*/
- if( pPager->ckptInUse && !pPg->inCkpt && (int)pPg->pgno<=pPager->ckptSize ){
+ if( pPager->stmtInUse && !pPg->inStmt && (int)pPg->pgno<=pPager->stmtSize ){
assert( pPg->inJournal || (int)pPg->pgno>pPager->origDbSize );
- store32bits(pPg->pgno, pPg, -4);
- CODEC(pPager, pData, pPg->pgno, 7);
- rc = sqlite3OsWrite(&pPager->cpfd, &((char*)pData)[-4], SQLITE_PAGE_SIZE+4);
- TRACE2("CKPT-JOURNAL %d\n", pPg->pgno);
- CODEC(pPager, pData, pPg->pgno, 0);
- if( rc!=SQLITE_OK ){
- sqlite3pager_rollback(pPager);
- pPager->errMask |= PAGER_ERR_FULL;
- return rc;
+ if( pPager->memDb ){
+ PgHistory *pHist = PGHDR_TO_HIST(pPg, pPager);
+ assert( pHist->pStmt==0 );
+ pHist->pStmt = sqliteMallocRaw( pPager->pageSize );
+ if( pHist->pStmt ){
+ memcpy(pHist->pStmt, PGHDR_TO_DATA(pPg), pPager->pageSize);
+ }
+ TRACE2("STMT-JOURNAL %d\n", pPg->pgno);
+ }else{
+ store32bits(pPg->pgno, pPg, -4);
+ CODEC(pPager, pData, pPg->pgno, 7);
+ rc = sqlite3OsWrite(&pPager->stfd, ((char*)pData)-4, SQLITE_PAGE_SIZE+4);
+ TRACE2("STMT-JOURNAL %d\n", pPg->pgno);
+ CODEC(pPager, pData, pPg->pgno, 0);
+ if( rc!=SQLITE_OK ){
+ sqlite3pager_rollback(pPager);
+ pPager->errMask |= PAGER_ERR_FULL;
+ return rc;
+ }
+ pPager->stmtNRec++;
+ assert( pPager->aInStmt!=0 );
+ pPager->aInStmt[pPg->pgno/8] |= 1<<(pPg->pgno&7);
}
- pPager->ckptNRec++;
- assert( pPager->aInCkpt!=0 );
- pPager->aInCkpt[pPg->pgno/8] |= 1<<(pPg->pgno&7);
page_add_to_stmt_list(pPg);
}
Pager *pPager = pPg->pPager;
if( pPager->state!=SQLITE_WRITELOCK || pPager->journalOpen==0 ) return;
- if( pPg->alwaysRollback || pPager->alwaysRollback ) return;
+ if( pPg->alwaysRollback || pPager->alwaysRollback || pPager->memDb ) return;
if( !pPg->inJournal && (int)pPg->pgno <= pPager->origDbSize ){
assert( pPager->aInJournal!=0 );
pPager->aInJournal[pPg->pgno/8] |= 1<<(pPg->pgno&7);
pPg->inJournal = 1;
- if( pPager->ckptInUse ){
- pPager->aInCkpt[pPg->pgno/8] |= 1<<(pPg->pgno&7);
+ if( pPager->stmtInUse ){
+ pPager->aInStmt[pPg->pgno/8] |= 1<<(pPg->pgno&7);
page_add_to_stmt_list(pPg);
}
TRACE2("DONT_ROLLBACK %d\n", pPg->pgno);
}
- if( pPager->ckptInUse && !pPg->inCkpt && (int)pPg->pgno<=pPager->ckptSize ){
+ if( pPager->stmtInUse && !pPg->inStmt && (int)pPg->pgno<=pPager->stmtSize ){
assert( pPg->inJournal || (int)pPg->pgno>pPager->origDbSize );
- assert( pPager->aInCkpt!=0 );
- pPager->aInCkpt[pPg->pgno/8] |= 1<<(pPg->pgno&7);
+ assert( pPager->aInStmt!=0 );
+ pPager->aInStmt[pPg->pgno/8] |= 1<<(pPg->pgno&7);
page_add_to_stmt_list(pPg);
}
}
+
+/*
+** Clear a PgHistory block
+*/
+static void clearHistory(PgHistory *pHist){
+ sqliteFree(pHist->pOrig);
+ sqliteFree(pHist->pStmt);
+ pHist->pOrig = 0;
+ pHist->pStmt = 0;
+}
+
/*
** Commit all changes to the database and release the write lock.
**
return SQLITE_ERROR;
}
TRACE1("COMMIT\n");
+ if( pPager->memDb ){
+ pPg = pager_get_all_dirty_pages(pPager);
+ while( pPg ){
+ clearHistory(PGHDR_TO_HIST(pPg, pPager));
+ pPg->dirty = 0;
+ pPg->inJournal = 0;
+ pPg->inStmt = 0;
+ pPg->pPrevStmt = pPg->pNextStmt = 0;
+ pPg = pPg->pDirty;
+ }
+ pPager->pStmt = 0;
+ pPager->state = SQLITE_READLOCK;
+ return SQLITE_OK;
+ }
if( pPager->dirtyFile==0 ){
/* Exit early (without doing the time-consuming sqlite3OsSync() calls)
** if there have been no changes to the database file. */
int sqlite3pager_rollback(Pager *pPager){
int rc;
TRACE1("ROLLBACK\n");
+ if( pPager->memDb ){
+ PgHdr *p;
+ for(p=pPager->pAll; p; p=p->pNextAll){
+ PgHistory *pHist;
+ if( !p->dirty ) continue;
+ pHist = PGHDR_TO_HIST(p, pPager);
+ if( pHist->pOrig ){
+ memcpy(PGHDR_TO_DATA(p), pHist->pOrig, pPager->pageSize);
+ TRACE2("ROLLBACK-PAGE %d\n", p->pgno);
+ }else{
+ TRACE2("PAGE %d is clean\n", p->pgno);
+ }
+ clearHistory(pHist);
+ p->dirty = 0;
+ p->inJournal = 0;
+ p->inStmt = 0;
+ p->pPrevStmt = p->pNextStmt = 0;
+ }
+ pPager->pStmt = 0;
+ pPager->dbSize = pPager->origDbSize;
+ memoryTruncate(pPager);
+ pPager->stmtInUse = 0;
+ pPager->state = SQLITE_READLOCK;
+ return SQLITE_OK;
+ }
+
if( !pPager->dirtyFile || !pPager->journalOpen ){
rc = pager_unwritelock(pPager);
pPager->dbSize = -1;
}
/*
-** Set the checkpoint.
+** Set the statement rollback point.
**
** This routine should be called with the transaction journal already
-** open. A new checkpoint journal is created that can be used to rollback
+** open. A new statement journal is created that can be used to rollback
** changes of a single SQL command within a larger transaction.
*/
int sqlite3pager_stmt_begin(Pager *pPager){
int rc;
char zTemp[SQLITE_TEMPNAME_SIZE];
+ assert( !pPager->stmtInUse );
+ TRACE1("STMT-BEGIN\n");
+ if( pPager->memDb ){
+ pPager->stmtInUse = 1;
+ pPager->stmtSize = pPager->dbSize;
+ return SQLITE_OK;
+ }
if( !pPager->journalOpen ){
- pPager->ckptAutoopen = 1;
+ pPager->stmtAutoopen = 1;
return SQLITE_OK;
}
assert( pPager->journalOpen );
- assert( !pPager->ckptInUse );
- pPager->aInCkpt = sqliteMalloc( pPager->dbSize/8 + 1 );
- if( pPager->aInCkpt==0 ){
+ pPager->aInStmt = sqliteMalloc( pPager->dbSize/8 + 1 );
+ if( pPager->aInStmt==0 ){
sqlite3OsReadLock(&pPager->fd);
return SQLITE_NOMEM;
}
#ifndef NDEBUG
- rc = sqlite3OsFileSize(&pPager->jfd, &pPager->ckptJSize);
- if( rc ) goto ckpt_begin_failed;
- assert( pPager->ckptJSize ==
+ rc = sqlite3OsFileSize(&pPager->jfd, &pPager->stmtJSize);
+ if( rc ) goto stmt_begin_failed;
+ assert( pPager->stmtJSize ==
pPager->nRec*JOURNAL_PG_SZ(journal_format)+JOURNAL_HDR_SZ(journal_format) );
#endif
- pPager->ckptJSize = pPager->nRec*JOURNAL_PG_SZ(journal_format)
+ pPager->stmtJSize = pPager->nRec*JOURNAL_PG_SZ(journal_format)
+ JOURNAL_HDR_SZ(journal_format);
- pPager->ckptSize = pPager->dbSize;
- if( !pPager->ckptOpen ){
- rc = sqlite3pager_opentemp(zTemp, &pPager->cpfd);
- if( rc ) goto ckpt_begin_failed;
- pPager->ckptOpen = 1;
- pPager->ckptNRec = 0;
- }
- pPager->ckptInUse = 1;
+ pPager->stmtSize = pPager->dbSize;
+ if( !pPager->stmtOpen ){
+ rc = sqlite3pager_opentemp(zTemp, &pPager->stfd);
+ if( rc ) goto stmt_begin_failed;
+ pPager->stmtOpen = 1;
+ pPager->stmtNRec = 0;
+ }
+ pPager->stmtInUse = 1;
return SQLITE_OK;
-ckpt_begin_failed:
- if( pPager->aInCkpt ){
- sqliteFree(pPager->aInCkpt);
- pPager->aInCkpt = 0;
+stmt_begin_failed:
+ if( pPager->aInStmt ){
+ sqliteFree(pPager->aInStmt);
+ pPager->aInStmt = 0;
}
return rc;
}
/*
-** Commit a checkpoint.
+** Commit a statement.
*/
int sqlite3pager_stmt_commit(Pager *pPager){
- if( pPager->ckptInUse ){
+ if( pPager->stmtInUse ){
PgHdr *pPg, *pNext;
- sqlite3OsSeek(&pPager->cpfd, 0);
- /* sqlite3OsTruncate(&pPager->cpfd, 0); */
- pPager->ckptNRec = 0;
- pPager->ckptInUse = 0;
- sqliteFree( pPager->aInCkpt );
- pPager->aInCkpt = 0;
- for(pPg=pPager->pCkpt; pPg; pPg=pNext){
- pNext = pPg->pNextCkpt;
- assert( pPg->inCkpt );
- pPg->inCkpt = 0;
- pPg->pPrevCkpt = pPg->pNextCkpt = 0;
+ TRACE1("STMT-COMMIT\n");
+ if( !pPager->memDb ){
+ sqlite3OsSeek(&pPager->stfd, 0);
+ /* sqlite3OsTruncate(&pPager->stfd, 0); */
+ sqliteFree( pPager->aInStmt );
+ pPager->aInStmt = 0;
+ }
+ for(pPg=pPager->pStmt; pPg; pPg=pNext){
+ pNext = pPg->pNextStmt;
+ assert( pPg->inStmt );
+ pPg->inStmt = 0;
+ pPg->pPrevStmt = pPg->pNextStmt = 0;
+ if( pPager->memDb ){
+ PgHistory *pHist = PGHDR_TO_HIST(pPg, pPager);
+ sqliteFree(pHist->pStmt);
+ pHist->pStmt = 0;
+ }
}
- pPager->pCkpt = 0;
+ pPager->stmtNRec = 0;
+ pPager->stmtInUse = 0;
+ pPager->pStmt = 0;
}
- pPager->ckptAutoopen = 0;
+ pPager->stmtAutoopen = 0;
return SQLITE_OK;
}
/*
-** Rollback a checkpoint.
+** Rollback a statement.
*/
int sqlite3pager_stmt_rollback(Pager *pPager){
int rc;
- if( pPager->ckptInUse ){
- rc = pager_stmt_playback(pPager);
+ if( pPager->stmtInUse ){
+ TRACE1("STMT-ROLLBACK\n");
+ if( pPager->memDb ){
+ PgHdr *pPg;
+ for(pPg=pPager->pStmt; pPg; pPg=pPg->pNextStmt){
+ PgHistory *pHist = PGHDR_TO_HIST(pPg, pPager);
+ if( pHist->pStmt ){
+ memcpy(PGHDR_TO_DATA(pPg), pHist->pStmt, pPager->pageSize);
+ sqliteFree(pHist->pStmt);
+ pHist->pStmt = 0;
+ }
+ }
+ pPager->dbSize = pPager->stmtSize;
+ memoryTruncate(pPager);
+ rc = SQLITE_OK;
+ }else{
+ rc = pager_stmt_playback(pPager);
+ }
sqlite3pager_stmt_commit(pPager);
}else{
rc = SQLITE_OK;
}
- pPager->ckptAutoopen = 0;
+ pPager->stmtAutoopen = 0;
return rc;
}
--- /dev/null
+# 2001 September 15
+#
+# The author disclaims copyright to this source code. In place of
+# a legal notice, here is a blessing:
+#
+# May you do good and not evil.
+# May you find forgiveness for yourself and forgive others.
+# May you share freely, never taking more than you give.
+#
+#***********************************************************************
+# This file implements regression tests for SQLite library. The
+# focus of this script is page cache subsystem.
+#
+# $Id: pager2.test,v 1.1 2004/05/12 13:30:09 drh Exp $
+
+
+set testdir [file dirname $argv0]
+source $testdir/tester.tcl
+
+if {[info commands pager_open]!=""} {
+db close
+
+# Basic sanity check. Open and close a pager.
+#
+do_test pager2-1.0 {
+ set v [catch {
+ set ::p1 [pager_open :memory: 10]
+ } msg]
+} {0}
+do_test pager2-1.1 {
+ pager_stats $::p1
+} {ref 0 page 0 max 10 size 0 state 0 err 0 hit 0 miss 0 ovfl 0}
+do_test pager2-1.2 {
+ pager_pagecount $::p1
+} {0}
+do_test pager2-1.3 {
+ pager_stats $::p1
+} {ref 0 page 0 max 10 size 0 state 0 err 0 hit 0 miss 0 ovfl 0}
+do_test pager2-1.4 {
+ pager_close $::p1
+} {}
+
+# Try to write a few pages.
+#
+do_test pager2-2.1 {
+ set v [catch {
+ set ::p1 [pager_open :memory: 10]
+ } msg]
+} {0}
+#do_test pager2-2.2 {
+# set v [catch {
+# set ::g1 [page_get $::p1 0]
+# } msg]
+# lappend v $msg
+#} {1 SQLITE_ERROR}
+do_test pager2-2.3.1 {
+ set ::gx [page_lookup $::p1 1]
+} {}
+do_test pager2-2.3.2 {
+ pager_stats $::p1
+} {ref 0 page 0 max 10 size 0 state 0 err 0 hit 0 miss 0 ovfl 0}
+do_test pager2-2.3.3 {
+ set v [catch {
+ set ::g1 [page_get $::p1 1]
+ } msg]
+ if {$v} {lappend v $msg}
+ set v
+} {0}
+do_test pager2-2.3.3 {
+ pager_stats $::p1
+} {ref 1 page 1 max 10 size 0 state 1 err 0 hit 0 miss 1 ovfl 0}
+do_test pager2-2.3.4 {
+ set ::gx [page_lookup $::p1 1]
+ expr {$::gx!=""}
+} {1}
+do_test pager2-2.3.5 {
+ pager_stats $::p1
+} {ref 1 page 1 max 10 size 0 state 1 err 0 hit 0 miss 1 ovfl 0}
+do_test pager2-2.3.6 {
+ expr $::g1==$::gx
+} {1}
+do_test pager2-2.3.7 {
+ page_unref $::gx
+ pager_stats $::p1
+} {ref 1 page 1 max 10 size 0 state 1 err 0 hit 0 miss 1 ovfl 0}
+do_test pager2-2.4 {
+ pager_stats $::p1
+} {ref 1 page 1 max 10 size 0 state 1 err 0 hit 0 miss 1 ovfl 0}
+do_test pager2-2.5 {
+ pager_pagecount $::p1
+} {0}
+do_test pager2-2.6 {
+ pager_stats $::p1
+} {ref 1 page 1 max 10 size 0 state 1 err 0 hit 0 miss 1 ovfl 0}
+do_test pager2-2.7 {
+ page_number $::g1
+} {1}
+do_test pager2-2.8 {
+ page_read $::g1
+} {}
+do_test pager2-2.9 {
+ page_unref $::g1
+} {}
+do_test pager2-2.10 {
+ pager_stats $::p1
+} {ref 0 page 1 max 10 size 0 state 1 err 0 hit 0 miss 1 ovfl 0}
+do_test pager2-2.11 {
+ set ::g1 [page_get $::p1 1]
+ expr {$::g1!=0}
+} {1}
+do_test pager2-2.12 {
+ page_number $::g1
+} {1}
+do_test pager2-2.13 {
+ pager_stats $::p1
+} {ref 1 page 1 max 10 size 0 state 1 err 0 hit 1 miss 1 ovfl 0}
+do_test pager2-2.14 {
+ set v [catch {
+ page_write $::g1 "Page-One"
+ } msg]
+ lappend v $msg
+} {0 {}}
+do_test pager2-2.15 {
+ pager_stats $::p1
+} {ref 1 page 1 max 10 size 1 state 2 err 0 hit 1 miss 1 ovfl 0}
+do_test pager2-2.16 {
+ page_read $::g1
+} {Page-One}
+do_test pager2-2.17 {
+ set v [catch {
+ pager_commit $::p1
+ } msg]
+ lappend v $msg
+} {0 {}}
+do_test pager2-2.20 {
+ pager_stats $::p1
+} {ref 1 page 1 max 10 size 1 state 1 err 0 hit 1 miss 1 ovfl 0}
+do_test pager2-2.19 {
+ pager_pagecount $::p1
+} {1}
+do_test pager2-2.21 {
+ pager_stats $::p1
+} {ref 1 page 1 max 10 size 1 state 1 err 0 hit 1 miss 1 ovfl 0}
+do_test pager2-2.22 {
+ page_unref $::g1
+} {}
+do_test pager2-2.23 {
+ pager_stats $::p1
+} {ref 0 page 1 max 10 size 1 state 1 err 0 hit 1 miss 1 ovfl 0}
+do_test pager2-2.24 {
+ set v [catch {
+ page_get $::p1 1
+ } ::g1]
+ if {$v} {lappend v $::g1}
+ set v
+} {0}
+do_test pager2-2.25 {
+ page_read $::g1
+} {Page-One}
+do_test pager2-2.26 {
+ set v [catch {
+ page_write $::g1 {page-one}
+ } msg]
+ lappend v $msg
+} {0 {}}
+do_test pager2-2.27 {
+ page_read $::g1
+} {page-one}
+do_test pager2-2.28 {
+ set v [catch {
+ pager_rollback $::p1
+ } msg]
+ lappend v $msg
+} {0 {}}
+do_test pager2-2.29 {
+ page_unref $::g1
+ set ::g1 [page_get $::p1 1]
+ page_read $::g1
+} {Page-One}
+#do_test pager2-2.99 {
+# pager_close $::p1
+#} {}
+
+#do_test pager2-3.1 {
+# set v [catch {
+# set ::p1 [pager_open :memory: 15]
+# } msg]
+# if {$v} {lappend v $msg}
+# set v
+#} {0}
+do_test pager2-3.2 {
+ pager_pagecount $::p1
+} {1}
+do_test pager2-3.3 {
+ set v [catch {
+ set ::g(1) [page_get $::p1 1]
+ } msg]
+ if {$v} {lappend v $msg}
+ set v
+} {0}
+do_test pager2-3.4 {
+ page_read $::g(1)
+} {Page-One}
+do_test pager2-3.5 {
+ for {set i 2} {$i<=20} {incr i} {
+ set gx [page_get $::p1 $i]
+ page_write $gx "Page-$i"
+ page_unref $gx
+ }
+ pager_commit $::p1
+} {}
+for {set i 2} {$i<=20} {incr i} {
+ do_test pager2-3.6.[expr {$i-1}] [subst {
+ set gx \[page_get $::p1 $i\]
+ set v \[page_read \$gx\]
+ page_unref \$gx
+ set v
+ }] "Page-$i"
+}
+for {set i 1} {$i<=20} {incr i} {
+ regsub -all CNT {
+ set ::g1 [page_get $::p1 CNT]
+ set ::g2 [page_get $::p1 CNT]
+ set ::vx [page_read $::g2]
+ expr {$::g1==$::g2}
+ } $i body;
+ do_test pager2-3.7.$i.1 $body {1}
+ regsub -all CNT {
+ page_unref $::g2
+ set vy [page_read $::g1]
+ expr {$vy==$::vx}
+ } $i body;
+ do_test pager2-3.7.$i.2 $body {1}
+ regsub -all CNT {
+ page_unref $::g1
+ set gx [page_get $::p1 CNT]
+ set vy [page_read $gx]
+ page_unref $gx
+ expr {$vy==$::vx}
+ } $i body;
+ do_test pager2-3.7.$i.3 $body {1}
+}
+do_test pager2-3.99 {
+ pager_close $::p1
+} {}
+
+# tests of the checkpoint mechanism and api
+#
+do_test pager2-4.0 {
+ set v [catch {
+ set ::p1 [pager_open :memory: 15]
+ } msg]
+ if {$v} {lappend v $msg}
+ set v
+} {0}
+do_test pager2-4.1 {
+ set g1 [page_get $::p1 1]
+ page_write $g1 "Page-1 v0"
+ for {set i 2} {$i<=20} {incr i} {
+ set gx [page_get $::p1 $i]
+ page_write $gx "Page-$i v0"
+ page_unref $gx
+ }
+ pager_commit $::p1
+} {}
+for {set i 1} {$i<=20} {incr i} {
+ do_test pager2-4.2.$i {
+ set gx [page_get $p1 $i]
+ set v [page_read $gx]
+ page_unref $gx
+ set v
+ } "Page-$i v0"
+}
+do_test pager2-4.3 {
+ lrange [pager_stats $::p1] 0 1
+} {ref 1}
+do_test pager2-4.4 {
+ lrange [pager_stats $::p1] 8 9
+} {state 1}
+
+for {set i 1} {$i<20} {incr i} {
+ do_test pager2-4.5.$i.0 {
+ set res {}
+ for {set j 2} {$j<=20} {incr j} {
+ set gx [page_get $p1 $j]
+ set value [page_read $gx]
+ page_unref $gx
+ set shouldbe "Page-$j v[expr {$i-1}]"
+ if {$value!=$shouldbe} {
+ lappend res $value $shouldbe
+ }
+ }
+ set res
+ } {}
+ do_test pager2-4.5.$i.1 {
+ page_write $g1 "Page-1 v$i"
+ lrange [pager_stats $p1] 8 9
+ } {state 2}
+ do_test pager2-4.5.$i.2 {
+ for {set j 2} {$j<=20} {incr j} {
+ set gx [page_get $p1 $j]
+ page_write $gx "Page-$j v$i"
+ page_unref $gx
+ if {$j==$i} {
+ pager_stmt_begin $p1
+ }
+ }
+ } {}
+ do_test pager2-4.5.$i.3 {
+ set res {}
+ for {set j 2} {$j<=20} {incr j} {
+ set gx [page_get $p1 $j]
+ set value [page_read $gx]
+ page_unref $gx
+ set shouldbe "Page-$j v$i"
+ if {$value!=$shouldbe} {
+ lappend res $value $shouldbe
+ }
+ }
+ set res
+ } {}
+ do_test pager2-4.5.$i.4 {
+ pager_rollback $p1
+ set res {}
+ for {set j 2} {$j<=20} {incr j} {
+ set gx [page_get $p1 $j]
+ set value [page_read $gx]
+ page_unref $gx
+ set shouldbe "Page-$j v[expr {$i-1}]"
+ if {$value!=$shouldbe} {
+ lappend res $value $shouldbe
+ }
+ }
+ set res
+ } {}
+ do_test pager2-4.5.$i.5 {
+ page_write $g1 "Page-1 v$i"
+ lrange [pager_stats $p1] 8 9
+ } {state 2}
+ do_test pager2-4.5.$i.6 {
+ for {set j 2} {$j<=20} {incr j} {
+ set gx [page_get $p1 $j]
+ page_write $gx "Page-$j v$i"
+ page_unref $gx
+ if {$j==$i} {
+ pager_stmt_begin $p1
+ }
+ }
+ } {}
+ do_test pager2-4.5.$i.7 {
+ pager_stmt_rollback $p1
+ for {set j 2} {$j<=20} {incr j} {
+ set gx [page_get $p1 $j]
+ set value [page_read $gx]
+ page_unref $gx
+ if {$j<=$i || $i==1} {
+ set shouldbe "Page-$j v$i"
+ } else {
+ set shouldbe "Page-$j v[expr {$i-1}]"
+ }
+ if {$value!=$shouldbe} {
+ lappend res $value $shouldbe
+ }
+ }
+ set res
+ } {}
+ do_test pager2-4.5.$i.8 {
+ for {set j 2} {$j<=20} {incr j} {
+ set gx [page_get $p1 $j]
+ page_write $gx "Page-$j v$i"
+ page_unref $gx
+ if {$j==$i} {
+ pager_stmt_begin $p1
+ }
+ }
+ } {}
+ do_test pager2-4.5.$i.9 {
+ pager_stmt_commit $p1
+ for {set j 2} {$j<=20} {incr j} {
+ set gx [page_get $p1 $j]
+ set value [page_read $gx]
+ page_unref $gx
+ set shouldbe "Page-$j v$i"
+ if {$value!=$shouldbe} {
+ lappend res $value $shouldbe
+ }
+ }
+ set res
+ } {}
+ do_test pager2-4.5.$i.10 {
+ pager_commit $p1
+ lrange [pager_stats $p1] 8 9
+ } {state 1}
+}
+
+do_test pager2-4.99 {
+ pager_close $::p1
+} {}
+
+} ;# end if( not mem: and has pager_open command );
+
+
+finish_test