From ac69b05efa67ca68d94ea8e7945ad59ff1018dce Mon Sep 17 00:00:00 2001 From: drh Date: Wed, 12 May 2004 13:30:07 +0000 Subject: [PATCH] The pager now handles file ":memory:" complete in memory with no disk I/O. (CVS 1363) FossilOrigin-Name: 97de9f7ceebab859ef984d155808575ad321afc0 --- manifest | 15 +- manifest.uuid | 2 +- src/pager.c | 620 +++++++++++++++++++++++++++++++---------------- test/pager2.test | 403 ++++++++++++++++++++++++++++++ 4 files changed, 817 insertions(+), 223 deletions(-) create mode 100644 test/pager2.test diff --git a/manifest b/manifest index c2dfab9d05..ecf58d7a07 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Add\ssome\smore\scode\sto\ssupport\smanifest\styping\sin\sindices.\sNot\sactivated\syet.\s(CVS\s1362) -D 2004-05-12T11:24:03 +C The\spager\snow\shandles\sfile\s":memory:"\scomplete\sin\smemory\swith\sno\sdisk\sI/O.\s(CVS\s1363) +D 2004-05-12T13:30:08 F Makefile.in ab7b0d5118e2da97bac66be8684a1034e3500f5a F Makefile.linux-gcc b86a99c493a5bfb402d1d9178dcdc4bd4b32f906 F README f1de682fbbd94899d50aca13d387d1b3fd3be2dd @@ -40,7 +40,7 @@ F src/main.c 4b82d7e78f4c9799343b02740a5ba9768d5e464d F src/md5.c 8e39fdae6d8776b87558e91dcc94740c9b635a9c F src/os.c ddcda92f7fd71b4513c57c1ec797917f206d504e F src/os.h fbb2f6595fc34fa351830d88fe1c6b85118f0383 -F src/pager.c 43556f37b80efdccb853dbf86b3d09470d791d0d +F src/pager.c fa60e566b370de0ed400859ba681134948b027bc F src/pager.h 0c95b18f2785b58bfbb2b6f6a221f23caf378687 F src/parse.y d0258aa3cc8b0c5742b07b699d10fa98f3caea7d F src/pragma.c 2ab2a12b62ec5370b9221f44b4743a633a90bfa8 @@ -115,6 +115,7 @@ F test/misuse.test 1095f26d1aed406c65e1d2eba651c4bb7c38cbff F test/notnull.test 7a08117a71e74b0321aaa937dbeb41a09d6eb1d0 F test/null.test c14d0f4739f21e929b8115b72bf0c765b6bb1721 F test/pager.test 548968643d91c1c43a3a3eb1a232e9ca87b4069e +F test/pager2.test 7ff175a28484fd324df9315dfe35f6fb159910ec F test/pragma.test 24a3f7a697b45cb90d664ebce5566bec7ac41571 F test/printf.test 46b3d07d59d871d0831b4a657f6dfcafe0574850 F test/progress.test 701b6115c2613128ececdfe1398a1bd0e1a4cfb3 x @@ -189,7 +190,7 @@ F www/sqlite.tcl 3c83b08cf9f18aa2d69453ff441a36c40e431604 F www/tclsqlite.tcl b9271d44dcf147a93c98f8ecf28c927307abd6da F www/vdbe.tcl 9b9095d4495f37697fd1935d10e14c6015e80aa1 F www/whentouse.tcl a8335bce47cc2fddb07f19052cb0cb4d9129a8e4 -P 0242c9e4f7c85e9c911cf30d90b0cdb1015f3d7d -R 55c977fb56a1a23227079a7a24ed9935 -U danielk1977 -Z 9a14b0592b340e466792086217d73072 +P 2f16c9ef3c101c4280991ce3cb0c3bea7b6ed439 +R 34cbf66e13c9fd717a5c35355887a263 +U drh +Z fff149a4adda820449b55f8e9ff0bdd9 diff --git a/manifest.uuid b/manifest.uuid index 0b2ab7a968..56127d654d 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -2f16c9ef3c101c4280991ce3cb0c3bea7b6ed439 \ No newline at end of file +97de9f7ceebab859ef984d155808575ad321afc0 \ No newline at end of file diff --git a/src/pager.c b/src/pager.c index e098aeafe7..29db8640d2 100644 --- a/src/pager.c +++ b/src/pager.c @@ -18,7 +18,7 @@ ** 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" @@ -103,20 +103,36 @@ struct PgHdr { 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 @@ -134,6 +150,8 @@ struct PgHdr { #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 @@ -154,14 +172,14 @@ struct Pager { 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 */ @@ -170,12 +188,13 @@ struct Pager { 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 */ @@ -185,12 +204,13 @@ struct Pager { 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 */ }; @@ -266,7 +286,7 @@ static const unsigned char aJournalMagic3[] = { ** 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; @@ -383,41 +403,41 @@ static int pager_errcode(Pager *pPager){ /* ** 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; + if( pPg->inStmt ) return; + assert( pPg->pPrevStmt==0 && pPg->pNextStmt==0 ); + pPg->pPrevStmt = 0; + if( pPager->pStmt ){ + pPager->pStmt->pPrevStmt = pPg; } - pPg->pNextCkpt = pPager->pCkpt; - pPager->pCkpt = pPg; - pPg->inCkpt = 1; + 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; } /* @@ -475,9 +495,9 @@ static int pager_unwritelock(Pager *pPager){ PgHdr *pPg; if( pPager->stateckptOpen ){ - sqlite3OsClose(&pPager->cpfd); - pPager->ckptOpen = 0; + if( pPager->stmtOpen ){ + sqlite3OsClose(&pPager->stfd); + pPager->stmtOpen = 0; } if( pPager->journalOpen ){ sqlite3OsClose(&pPager->jfd); @@ -761,18 +781,18 @@ end_playback: } /* -** 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 */ @@ -782,22 +802,22 @@ static int pager_stmt_playback(Pager *pPager){ /* 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; } @@ -805,7 +825,7 @@ static int pager_stmt_playback(Pager *pPager){ /* 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; } @@ -813,7 +833,7 @@ static int pager_stmt_playback(Pager *pPager){ 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 ){ @@ -928,6 +948,7 @@ int sqlite3pager_open( OsFile fd; int rc, i; int tempFile; + int memDb = 0; int readOnly = 0; char zTemp[SQLITE_TEMPNAME_SIZE]; @@ -936,9 +957,16 @@ int sqlite3pager_open( 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; @@ -972,18 +1000,20 @@ int sqlite3pager_open( 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; @@ -1034,6 +1064,76 @@ int sqlite3pager_pagecount(Pager *pPager){ */ 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. */ @@ -1049,6 +1149,11 @@ int sqlite3pager_truncate(Pager *pPager, Pgno nPage){ 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 ){ @@ -1071,12 +1176,16 @@ int sqlite3pager_close(Pager *pPager){ 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: { @@ -1088,7 +1197,9 @@ int sqlite3pager_close(Pager *pPager){ 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 ){ @@ -1341,7 +1452,7 @@ int sqlite3pager_get(Pager *pPager, Pgno pgno, void **ppPage){ /* 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; @@ -1394,27 +1505,30 @@ int sqlite3pager_get(Pager *pPager, Pgno pgno, void **ppPage){ }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->nPagemxPage || pPager->pFirst==0 ){ + if( pPager->nPagemxPage || 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{ @@ -1465,35 +1579,7 @@ int sqlite3pager_get(Pager *pPager, Pgno pgno, void **ppPage){ /* 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; @@ -1506,8 +1592,8 @@ int sqlite3pager_get(Pager *pPager, Pgno pgno, void **ppPage){ 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); @@ -1536,6 +1622,7 @@ int sqlite3pager_get(Pager *pPager, Pgno pgno, void **ppPage){ 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); @@ -1579,10 +1666,6 @@ void *sqlite3pager_lookup(Pager *pPager, Pgno 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); @@ -1633,7 +1716,7 @@ int sqlite3pager_unref(void *pData){ */ pPager->nRef--; assert( pPager->nRef>=0 ); - if( pPager->nRef==0 ){ + if( pPager->nRef==0 && !pPager->memDb ){ pager_reset(pPager); } } @@ -1696,7 +1779,7 @@ static int pager_open_journal(Pager *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 ){ @@ -1736,15 +1819,20 @@ int sqlite3pager_begin(void *pData){ 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; @@ -1785,7 +1873,7 @@ int sqlite3pager_write(void *pData){ ** 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; } @@ -1814,39 +1902,50 @@ int sqlite3pager_write(void *pData){ ** 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; @@ -1857,26 +1956,36 @@ int sqlite3pager_write(void *pData){ } } - /* 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); } @@ -1974,25 +2083,36 @@ void sqlite3pager_dont_rollback(void *pData){ 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. ** @@ -2019,6 +2139,20 @@ int sqlite3pager_commit(Pager *pPager){ 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. */ @@ -2068,6 +2202,32 @@ commit_abort: 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; @@ -2118,88 +2278,118 @@ int *sqlite3pager_stats(Pager *pPager){ } /* -** 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->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->ckptInUse = 1; + 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; } - pPager->pCkpt = 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->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; } diff --git a/test/pager2.test b/test/pager2.test new file mode 100644 index 0000000000..201631c6b2 --- /dev/null +++ b/test/pager2.test @@ -0,0 +1,403 @@ +# 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