]> git.ipfire.org Git - thirdparty/sqlite.git/commitdiff
Pager is working, mostly. (CVS 211)
authordrh <drh@noemail.net>
Sun, 15 Apr 2001 00:37:09 +0000 (00:37 +0000)
committerdrh <drh@noemail.net>
Sun, 15 Apr 2001 00:37:09 +0000 (00:37 +0000)
FossilOrigin-Name: f82fa7070ae281804c019e6b05cd767dadaf0827

Makefile.in
manifest
manifest.uuid
src/build.c
src/pager.c
src/pager.h
src/tclsqlite.c
src/test2.c [new file with mode: 0644]
test/pager.test [new file with mode: 0644]
www/changes.tcl

index 3428df3f6c01d1afad5cc82570b6368a713bf54f..c317a99c4ca27f7856e7654d2b71be5a8284179c 100644 (file)
@@ -48,8 +48,8 @@ LIBREADLINE = @TARGET_READLINE_LIBS@
 # Object files for the SQLite library.
 #
 LIBOBJ = build.o dbbe.o dbbegdbm.o dbbemem.o delete.o expr.o insert.o \
-         main.o parse.o printf.o random.o select.o table.o tokenize.o \
-         update.o util.o vdbe.o where.o tclsqlite.o
+         main.o pager.o parse.o printf.o random.o select.o table.o \
+         tokenize.o update.o util.o vdbe.o where.o tclsqlite.o
 
 # All of the source code files.
 #
@@ -63,6 +63,7 @@ SRC = \
   $(TOP)/src/expr.c \
   $(TOP)/src/insert.c \
   $(TOP)/src/main.c \
+  $(TOP)/src/pager.c \
   $(TOP)/src/parse.y \
   $(TOP)/src/printf.c \
   $(TOP)/src/random.c \
@@ -79,6 +80,12 @@ SRC = \
   $(TOP)/src/vdbe.h \
   $(TOP)/src/where.c
 
+# Source code to the test files.
+#
+TESTSRC = \
+  $(TOP)/src/test1.c \
+  $(TOP)/src/test2.c
+
 # This is the default Makefile target.  The objects listed here
 # are what get build when you type just "make" with no arguments.
 #
@@ -182,6 +189,9 @@ tclsqlite.o:        $(TOP)/src/tclsqlite.c $(HDR)
 printf.o:      $(TOP)/src/printf.c $(HDR)
        $(TCC) $(GDBM_FLAGS) $(TCL_FLAGS) -c $(TOP)/src/printf.c
 
+pager.o:       $(TOP)/src/pager.c $(HDR)
+       $(TCC) $(GDBM_FLAGS) $(TCL_FLAGS) -c $(TOP)/src/pager.c
+
 gdbmdump:      $(TOP)/tool/gdbmdump.c
        $(TCC) $(GDBM_FLAGS) -o gdbmdump $(TOP)/tool/gdbmdump.c $(LIBGDBM)
 
@@ -189,9 +199,9 @@ tclsqlite:  $(TOP)/src/tclsqlite.c libsqlite.a
        $(TCC) $(TCL_FLAGS) -DTCLSH=1 -o sqlite_tester \
                $(TOP)/src/tclsqlite.c libsqlite.a $(LIBGDBM) $(LIBTCL)
 
-sqlite_tester: $(TOP)/src/tclsqlite.c libsqlite.a $(TOP)/src/test1.c
-       $(TCC) $(TCL_FLAGS) -DTCLSH=1 -DSQLITE_TEST1=1 -o sqlite_tester \
-               $(TOP)/src/test1.c $(TOP)/src/tclsqlite.c \
+sqlite_tester: $(TOP)/src/tclsqlite.c libsqlite.a $(TESTSRC)
+       $(TCC) $(TCL_FLAGS) -DTCLSH=1 -DSQLITE_TEST=1 -o sqlite_tester \
+               $(TESTSRC) $(TOP)/src/tclsqlite.c \
                libsqlite.a $(LIBGDBM) $(LIBTCL)
 
 test:  sqlite_tester sqlite
index 829ede70ae8c34fad353591fc3230791b7f5c08d..4192a2b17ef5e97f6877c4266c7d3eb31afc2aac 100644 (file)
--- a/manifest
+++ b/manifest
@@ -1,7 +1,7 @@
-C Getting\sready\sto\sredo\sthe\sjournal\sfile\sformat.\s(CVS\s210)
-D 2001-04-14T16:38:23
+C Pager\sis\sworking,\smostly.\s(CVS\s211)
+D 2001-04-15T00:37:09
 F COPYRIGHT 74a8a6531a42e124df07ab5599aad63870fa0bd4
-F Makefile.in 4775f7fd1ed543606bb24fa3fada1bc90a23a6b9
+F Makefile.in ac01d6145714b0d1c9e99382caf03cf30d6f4c8d
 F README 51f6a4e7408b34afa5bc1c0485f61b6a4efb6958
 F VERSION 71874cb7e2a53c2bd22bb6affa7d223dd94a7a13
 F configure 260d3be664b6d9b4d2d985e66b6dae1ef723c86e x
@@ -9,7 +9,7 @@ F configure.in 6940e3f88bf3d28a10c73b06ab99fd3a7e039a61
 F doc/lemon.html e233a3e97a779c7a87e1bc4528c664a58e49dd47
 F doc/report1.txt 734cbae63b1310cc643fe5e9e3da1ab55a79b99e
 F src/TODO 38a68a489e56e9fd4a96263e0ff9404a47368ad4
-F src/build.c 6afbb6106c1e8c771ffcb81107b755e200310574
+F src/build.c 4f6a2d551c56342cd4a0420654835be3ad179651
 F src/dbbe.c ec82c602c598748204a61a35ab0c31e34ca58223
 F src/dbbe.h 7235b15c6c5d8be0c4da469cef9620cee70b1cc8
 F src/dbbegdbm.c 9d3a3c18b27f9f2533a3aaa3741e8668bdda7e98
@@ -26,8 +26,8 @@ F src/ex/sizes.tcl f54bad4a2ac567624be59131a6ee42d71b41a3d7
 F src/expr.c c4c24c3af1eba094a816522eb0e085bed518ee16
 F src/insert.c aa528e20a787af85432a61daaea6df394bd251d7
 F src/main.c 92ce30a89f622ba36cc8b7d912829e14a480722c
-F src/pager.c 67227909b9a16a2463bbfddf19347fcab7a251ae
-F src/pager.h 889c5cf517ad30704e295540793c893ac843fd5f
+F src/pager.c 52dc932c937cfd5ccdd3c40527ea57878994a99c
+F src/pager.h 8678d9a97fdf1c111b619a13a351e5c8ab97cb81
 F src/parse.y 8fc096948994a7ffbf61ba13129cc589f794a9cb
 F src/printf.c b1e22a47be8cdf707815647239991e08e8cb69f9
 F src/random.c b36c3f57dc80c8f354e6bfbf39cf1e1de021d54a
@@ -37,8 +37,9 @@ F src/shell.tcl 27ecbd63dd88396ad16d81ab44f73e6c0ea9d20e
 F src/sqlite.h.in 3e5906f72608f0fd4394dfbb1d7e8d35b8353677
 F src/sqliteInt.h fc1000f023b41882bbdb8db4f80172f77b44307b
 F src/table.c adcaf074f6c1075e86359174e68701fa2acfc4d6
-F src/tclsqlite.c 83dcbf015ea0319c2a97514b8b812a12d621e40a
+F src/tclsqlite.c 1f2bf4691a6bd81fbff1856ae4a12db24d1265f7
 F src/test1.c abb3cb427e735ae87e6533f5b3b7164b7da91bc4
+F src/test2.c 49e27f03d1866635c2c4539e0983e5590a322872
 F src/tokenize.c 0118b57702cb6550769316e8443b06760b067acf
 F src/update.c 0cf789656a936d4356668393267692fa4b03ffc6
 F src/util.c 1b396ac34e30dd6222d82e996c17b161bbc906bc
@@ -58,6 +59,7 @@ F test/insert2.test 732405e30331635af8d159fccabe835eea5cd0c6
 F test/lock.test bca7d53de73138b1f670a2fbdb1f481ff7eaa45a
 F test/main.test da635f9e078cd21ddf074e727381a715064489ff
 F test/malloc.test 3daa97f6a9577d8f4c6e468b274333af19ce5861
+F test/pager.test 2317ccf5821f15a637b84fa5f4d36b4c85a52a12
 F test/printf.test 4c71871e1a75a2dacb673945fc13ddb30168798f
 F test/rowid.test 128453599def7435e988216f7fe89c7450b8a9a3
 F test/select1.test 223507655cdb4f9901d83fa7f5c5328e022c211f
@@ -88,7 +90,7 @@ F www/arch.fig 4f246003b7da23bd63b8b0af0618afb4ee3055c8
 F www/arch.png 8dae0766d42ed3de9ed013c1341a5792bcf633e6
 F www/arch.tcl a40380c1fe0080c43e6cc5c20ed70731511b06be
 F www/c_interface.tcl ddca19005c47dd5a15882addc02fff5de83d8ed9
-F www/changes.tcl e89e31b688833f6393dc1add45e3b4bd1da966b8
+F www/changes.tcl 822b425cc50cb8e21563dd1aa0e4b79cf780f3dc
 F www/crosscompile.tcl c99efacb3aefaa550c6e80d91b240f55eb9fd33e
 F www/dynload.tcl 02eb8273aa78cfa9070dd4501dca937fb22b466c
 F www/fileformat.tcl cfb7fba80b7275555281ba2f256c00734bcdd1c9
@@ -99,7 +101,7 @@ F www/opcode.tcl cb3a1abf8b7b9be9f3a228d097d6bf8b742c2b6f
 F www/sqlite.tcl cb0d23d8f061a80543928755ec7775da6e4f362f
 F www/tclsqlite.tcl 06f81c401f79a04f2c5ebfb97e7c176225c0aef2
 F www/vdbe.tcl 0c8aaa529dd216ccbf7daaabd80985e413d5f9ad
-P 3bde128418fe70a2fd62bf9e013999827a16053c
-R e5aeeb4c78022c778bcfebd97e61f590
+P 42c2f3fe68c8a6bab96e035371ecd64296c5491f
+R 558a8ad440acd3c96149b89f77d45a0d
 U drh
-Z 3ef685e141624dcddb66699cb8550c69
+Z 6080de1bb9d6e86286ceffe08ee69ef0
index f175fd505fd6255929ba923596ca59420f2b9aa2..7ab935bd251ba9e6ed43e8737b10bc3d091d9c43 100644 (file)
@@ -1 +1 @@
-42c2f3fe68c8a6bab96e035371ecd64296c5491f
\ No newline at end of file
+f82fa7070ae281804c019e6b05cd767dadaf0827
\ No newline at end of file
index df2526960b793a81407e8fbab26c9e444ff319d1..b3a5eff57ffbbb36b24d9379f7210335c16e2f0f 100644 (file)
@@ -33,7 +33,7 @@
 **     COPY
 **     VACUUM
 **
-** $Id: build.c,v 1.27 2001/04/11 14:28:42 drh Exp $
+** $Id: build.c,v 1.28 2001/04/15 00:37:09 drh Exp $
 */
 #include "sqliteInt.h"
 
@@ -196,7 +196,7 @@ static void sqliteDeleteIndex(sqlite *db, Index *pIndex){
 ** Table.  No changes are made to disk by this routine.
 **
 ** This routine just deletes the data structure.  It does not unlink
-** the table data structure from the hash table.  But does it destroy
+** the table data structure from the hash table.  But it does destroy
 ** memory structures of the indices associated with the table.
 **
 ** Indices associated with the table are unlinked from the "db"
@@ -236,7 +236,13 @@ char *sqliteTableNameFromToken(Token *pName){
 /*
 ** Begin constructing a new table representation in memory.  This is
 ** the first of several action routines that get called in response
-** to a CREATE TABLE statement.
+** to a CREATE TABLE statement.  In particular, this routine is called
+** after seeing tokens "CREATE" and "TABLE" and the table name.  The
+** pStart token is the CREATE and pName is the table name.
+**
+** The new table is constructed in files of the pParse structure.  As
+** more of the CREATE TABLE statement is parsed, additional action
+** routines are called to build up more of the table.
 */
 void sqliteStartTable(Parse *pParse, Token *pStart, Token *pName){
   Table *pTable;
@@ -273,6 +279,11 @@ void sqliteStartTable(Parse *pParse, Token *pStart, Token *pName){
 
 /*
 ** Add a new column to the table currently being constructed.
+**
+** The parser calls this routine once for each column declaration
+** in a CREATE TABLE statement.  sqliteStartTable() gets called
+** first to get things going.  Then this routine is called for each
+** column.
 */
 void sqliteAddColumn(Parse *pParse, Token *pName){
   Table *p;
@@ -295,6 +306,9 @@ void sqliteAddColumn(Parse *pParse, Token *pName){
 ** The given token is the default value for the last column added to
 ** the table currently under construction.  If "minusFlag" is true, it
 ** means the value token was preceded by a minus sign.
+**
+** This routine is called by the parser while in the middle of
+** parsing a CREATE TABLE statement.
 */
 void sqliteAddDefaultValue(Parse *pParse, Token *pVal, int minusFlag){
   Table *p;
@@ -403,6 +417,7 @@ Table *sqliteTableFromToken(Parse *pParse, Token *pTok){
 
 /*
 ** This routine is called to do the work of a DROP TABLE statement.
+** pName is the name of the table to be dropped.
 */
 void sqliteDropTable(Parse *pParse, Token *pName){
   Table *pTable;
index 41650d98fa5d6b60f0ae93868f0c29d4be95f1d3..9402b10d3cb20130e6fc955c1253f23f5adad817 100644 (file)
 ** all writes in order to support rollback.  Locking is used to limit
 ** access to one or more reader or on writer.
 **
-** @(#) $Id: pager.c,v 1.2 2001/04/14 16:38:23 drh Exp $
+** @(#) $Id: pager.c,v 1.3 2001/04/15 00:37:09 drh Exp $
 */
+#include "sqliteInt.h"
 #include "pager.h"
 #include <fcntl.h>
 #include <sys/stat.h>
 #include <unistd.h>
 #include <assert.h>
+#include <string.h>
 
 /*
 ** The page cache as a whole is always in one of the following
 #define SQLITE_READLOCK    1
 #define SQLITE_WRITELOCK   2
 
+
 /*
 ** Each in-memory image of a page begins with the following header.
 */
+typedef struct PgHdr PgHdr;
 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 *pNext, *pPrev;          /* Freelist of pages where nRef==0 */
+  PgHdr *pNextFree, *pPrevFree;  /* Freelist of pages where nRef==0 */
+  PgHdr *pNextAll, *pPrevAll;    /* A list of all pages */
   char inJournal;                /* TRUE if has been written to journal */
   char dirty;                    /* TRUE if we need to write back changes */
   /* SQLITE_PAGE_SIZE bytes of page data follow this header */
@@ -87,16 +92,11 @@ struct PgHdr {
 #define PGHDR_TO_DATA(P)  ((void*)(&(P)[1]))
 #define DATA_TO_PGHDR(D)  (&((PgHdr*)(D))[-1])
 
-/*
-** The number of page numbers that will fit on one page.
-*/
-#define SQLITE_INDEX_SIZE   (SQLITE_PAGE_SIZE/sizeof(Pgno))
-
 /*
 ** How big to make the hash table used for locating in-memory pages
 ** by page number.
 */
-#define N_PG_HASH 353
+#define N_PG_HASH 101
 
 /*
 ** A open page cache is an instance of the following structure.
@@ -105,50 +105,152 @@ struct Pager {
   char *zFilename;            /* Name of the database file */
   char *zJournal;             /* Name of the journal file */
   int fd, jfd;                /* File descriptors for database and journal */
-  int nRef;                   /* Sum of PgHdr.nRef */
   int dbSize;                 /* Number of pages in the file */
   int origDbSize;             /* dbSize before the current change */
-  int jSize;                  /* Number of pages in the journal */
-  int nIdx;                   /* Number of entries in aIdx[] */
   int nPage;                  /* Total number of in-memory pages */
+  int nRef;                   /* Number of in-memory pages with PgHdr.nRef>0 */
   int mxPage;                 /* Maximum number of pages to hold in cache */
-  char state;                 /* SQLITE_UNLOCK, _READLOCK or _WRITELOCK */
-  char ioErr;                 /* True if an I/O error has occurred */
+  int nHit, nMiss, nOvfl;     /* Cache hits, missing, and LRU overflows */
+  unsigned char state;        /* SQLITE_UNLOCK, _READLOCK or _WRITELOCK */
+  unsigned char errMask;      /* One of several kinds of errors */
   PgHdr *pFirst, *pLast;      /* List of free pages */
+  PgHdr *pAll;                /* List of all pages */
   PgHdr *aHash[N_PG_HASH];    /* Hash table to map page number of PgHdr */
-  Pgno aIdx[SQLITE_INDEX_SIZE];  /* Current journal index page */
+};
+
+/*
+** These are bits that can be set in Pager.errMask.
+*/
+#define PAGER_ERR_FULL     0x01  /* a write() failed */
+#define PAGER_ERR_MEM      0x02  /* malloc() failed */
+#define PAGER_ERR_LOCK     0x04  /* error in the locking protocol */
+#define PAGER_ERR_CORRUPT  0x08  /* database or journal corruption */
+
+/*
+** The journal file contains page records in the following
+** format.
+*/
+typedef struct PageRecord PageRecord;
+struct PageRecord {
+  Pgno pgno;                     /* The page number */
+  char aData[SQLITE_PAGE_SIZE];  /* Original data for page pgno */
+};
+
+/*
+** Journal files begin with the following magic string.  This data
+** is completely random.  It is used only as a sanity check.
+*/
+static const unsigned char aJournalMagic[] = {
+  0xd9, 0xd5, 0x05, 0xf9, 0x20, 0xa1, 0x63, 0xd4,
 };
 
 /*
 ** Hash a page number
 */
-#define sqlite_pager_hash(PN)  ((PN)%N_PG_HASH)
+#define pager_hash(PN)  ((PN)%N_PG_HASH)
 
 /*
 ** Attempt to acquire a read lock (if wrlock==0) or a write lock (if wrlock==1)
 ** on the database file.  Return 0 on success and non-zero if the lock 
 ** could not be acquired.
 */
-static int sqlite_pager_lock(int fd, int wrlock){
+static int pager_lock(int fd, int wrlock){
+  int rc;
   struct flock lock;
-  lock.l_type = write_lock ? F_WRLCK : F_RDLCK;
-  return fcntl(fd, F_SETLK, &lock)!=0;
+  lock.l_type = wrlock ? F_WRLCK : F_RDLCK;
+  lock.l_whence = SEEK_SET;
+  lock.l_start = lock.l_len = 0L;
+  rc = fcntl(fd, F_SETLK, &lock);
+  return rc!=0;
 }
 
 /*
 ** Unlock the database file.
 */
-static int sqlite_pager_unlock(fd){
+static int pager_unlock(fd){
+  int rc;
   struct flock lock;
   lock.l_type = F_UNLCK;
-  return fcntl(fd, F_SETLK, &lock)!=0;
+  lock.l_whence = SEEK_SET;
+  lock.l_start = lock.l_len = 0L;
+  rc = fcntl(fd, F_SETLK, &lock);
+  return rc!=0;
+}
+
+/*
+** Move the cursor for file descriptor fd to the point whereto from
+** the beginning of the file.
+*/
+static int pager_seek(int fd, off_t whereto){
+  lseek(fd, whereto, SEEK_SET);
+  return SQLITE_OK;
+}
+
+/*
+** Truncate the given file so that it contains exactly mxPg pages
+** of data.
+*/
+static int pager_truncate(int fd, Pgno mxPg){
+  int rc;
+  rc = ftruncate(fd, mxPg*SQLITE_PAGE_SIZE);
+  return rc!=0 ? SQLITE_IOERR : SQLITE_OK;
+}
+
+/*
+** Read nBytes of data from fd into pBuf.  If the data cannot be
+** read or only a partial read occurs, then the unread parts of
+** pBuf are filled with zeros and this routine returns SQLITE_IOERR.
+** If the read is completely successful, return SQLITE_OK.
+*/
+static int pager_read(int fd, void *pBuf, int nByte){
+  int rc;
+  rc = read(fd, pBuf, nByte);
+  if( rc<0 ){
+    memset(pBuf, 0, nByte);
+    return SQLITE_IOERR;
+  }
+  if( rc<nByte ){
+    memset(&((char*)pBuf)[rc], 0, nByte - rc);
+    rc = SQLITE_IOERR;
+  }else{
+    rc = SQLITE_OK;
+  }
+  return rc;
+}
+
+/*
+** Write nBytes of data into fd.  If any problem occurs or if the
+** write is incomplete, SQLITE_IOERR is returned.  SQLITE_OK is
+** returned upon complete success.
+*/
+static int pager_write(int fd, const void *pBuf, int nByte){
+  int rc;
+  rc = write(fd, pBuf, nByte);
+  if( rc<nByte ){
+    return SQLITE_FULL;
+  }else{
+    return SQLITE_OK;
+  }
+}
+
+/*
+** Convert the bits in the pPager->errMask into an approprate
+** return code.
+*/
+static int pager_errcode(Pager *pPager){
+  int rc = SQLITE_OK;
+  if( pPager->errMask & PAGER_ERR_LOCK )    rc = SQLITE_PROTOCOL;
+  if( pPager->errMask & PAGER_ERR_FULL )    rc = SQLITE_FULL;
+  if( pPager->errMask & PAGER_ERR_MEM )     rc = SQLITE_NOMEM;
+  if( pPager->errMask & PAGER_ERR_CORRUPT ) rc = SQLITE_CORRUPT;
+  return rc;
 }
 
 /*
 ** Find a page in the hash table given its page number.  Return
 ** a pointer to the page or NULL if not found.
 */
-static PgHdr *sqlite_pager_lookup(Pager *pPager, Pgno pgno){
+static PgHdr *pager_lookup(Pager *pPager, Pgno pgno){
   PgHdr *p = pPager->aHash[pgno % N_PG_HASH];
   while( p && p->pgno!=pgno ){
     p = p->pNextHash;
@@ -162,21 +264,23 @@ static PgHdr *sqlite_pager_lookup(Pager *pPager, Pgno pgno){
 ** opened.  Any outstanding pages are invalidated and subsequent attempts
 ** to access those pages will likely result in a coredump.
 */
-static void sqlite_pager_reset(Pager *pPager){
+static void pager_reset(Pager *pPager){
   PgHdr *pPg, *pNext;
-  for(pPg=pPager->pFirst; pPg; pPg=pNext){
-    pNext = pPg->pNext;
-    sqlite_free(pPg);
+  for(pPg=pPager->pAll; pPg; pPg=pNext){
+    pNext = pPg->pNextAll;
+    sqliteFree(pPg);
   }
   pPager->pFirst = 0;
-  pPager->pNext = 0;
+  pPager->pLast = 0;
+  pPager->pAll = 0;
   memset(pPager->aHash, 0, sizeof(pPager->aHash));
   pPager->nPage = 0;
   if( pPager->state==SQLITE_WRITELOCK ){
-    sqlite_pager_rollback(pPager);
+    sqlitepager_rollback(pPager);
   }
-  sqlite_pager_unlock(pPager->fd);
+  pager_unlock(pPager->fd);
   pPager->state = SQLITE_UNLOCK;
+  pPager->dbSize = -1;
   pPager->nRef = 0;
 }
 
@@ -193,178 +297,155 @@ static void sqlite_pager_reset(Pager *pPager){
 ** should get a write lock on the database without first getting a lock
 ** on the journal.  So this routine should never fail.  But it can fail
 ** if another process is not playing by the rules.  If it does fail,
-** all in-memory cache pages are invalidated and this routine returns
-** SQLITE_PROTOCOL.  SQLITE_OK is returned on success.
+** all in-memory cache pages are invalidated, the PAGER_ERR_LOCK bit
+** is set in pPager->errMask, and this routine returns SQLITE_PROTOCOL.
+** SQLITE_OK is returned on success.
 */
-static int sqlite_pager_unwritelock(Pager *pPager){
+static int pager_unwritelock(Pager *pPager){
   int rc;
-  assert( pPager->state==SQLITE_WRITELOCK );
-  sqlite_pager_unlock(pPager->fd);
-  rc = sqlite_pager_lock(pPager->fd, 0);
+  PgHdr *pPg;
+  if( pPager->state!=SQLITE_WRITELOCK ) return SQLITE_OK;
+  pager_unlock(pPager->fd);
+  rc = pager_lock(pPager->fd, 0);
   unlink(pPager->zJournal);
   close(pPager->jfd);
   pPager->jfd = -1;
+  for(pPg=pPager->pAll; pPg; pPg=pPg->pNextAll){
+    pPg->inJournal = 0;
+    pPg->dirty = 0;
+  }
   if( rc!=SQLITE_OK ){
     pPager->state = SQLITE_UNLOCK;
-    sqlite_pager_reset(pPager);
     rc = SQLITE_PROTOCOL;
+    pPager->errMask |= PAGER_ERR_LOCK;
   }else{
+    rc = SQLITE_OK;
     pPager->state = SQLITE_READLOCK;
   }
   return rc;
 }
 
-
 /*
 ** Playback the journal and thus restore the database file to
 ** the state it was in before we started making changes.  
 **
-** A journal consists of multiple segments.  Every segment begins
-** with a single page containing SQLITE_INDEX_SIZE page numbers.  This
-** first page is called the index.  Most segments have SQLITE_INDEX_SIZE
-** additional pages after the index.  The N-th page after the index
-** contains the contents of a page in the database file before that
-** page was changed.  The N-th entry in the index tells which page
-** of the index file the data is for.
+** The journal file format is as follows:  There is an initial
+** file-type string for sanity checking.  Then there is a single
+** Pgno number which is the number of pages in the database before
+** changes were made.  The database is truncated to this size.
+** Next come zero or more page records which each page record
+** consists of a Pgno, SQLITE_PAGE_SIZE bytes of data.  
 **
-** The first segment of a journal is formatted slightly differently.
-** The first segment contains an index but only SQLITE_INDEX_SIZE-1
-** data pages.  The first page number in the index is actually the
-** total number of pages in the original file.  This number is used
-** to truncate the original database file back to its original size.
-** The second number in the index page is the page number for the
-** first data page.  And so forth.
+** For playback, the pages have to be read from the journal in
+** reverse order and put back into the original database file.
 **
-** We really need to playback the journal beginning at the end
-** and working backwards toward the beginning.  That way changes
-** to the database are undone in the reverse order from the way they
-** were applied.  This is important if the same page is changed
-** more than once.  But many operating systems work more efficiently
-** if data is read forward instead of backwards.  So for efficiency
-** we want to read the data in the forward direction.
-**
-** This routine starts with the last segment and works backwards
-** toward the first.  Within each segment, however, data is read
-** in the forward direction for efficiency.  Care is taken that
-** only the first appearance of each page is copied over to the
-** database file.  If a page appears in the index more than once,
-** only the first occurrance is written.  A hash table is used to
-** keep track of  which pages have been written and which have not.
+** If the file opened as the journal file is not a well-formed
+** journal file (as determined by looking at the magic number
+** at the beginning) then this routine returns SQLITE_PROTOCOL.
+** If any other errors occur during playback, the database will
+** likely be corrupted, so the PAGER_ERR_CORRUPT bit is set in
+** pPager->errMask and SQLITE_CORRUPT is returned.  If it all
+** works, then this routine returns SQLITE_OK.
 */
-static int sqlite_pager_playback(Pager *pPager){
-  int nSeg;                           /* Number of segments */
-  int i, j;                           /* Loop counters */
-  Pgno mxPg = 0;                      /* Size of the original file in pages */
-  struct stat statbuf;                /* Used to size the journal */
-  Pgno aIndex[SQLITE_INDEX_SIZE];     /* The index page */
-  char aBuf[SQLITE_PAGE_SIZE];        /* Page transfer buffer */
-  Pgno aHash[SQLITE_INDEX_SIZE*2-1];  /* Hash table for pages read so far */
+static int pager_playback(Pager *pPager){
+  int nRec;                /* Number of Records */
+  int i;                   /* Loop counter */
+  Pgno mxPg = 0;           /* Size of the original file in pages */
+  struct stat statbuf;     /* Used to size the journal */
+  PgHdr *pPg;              /* An existing page in the cache */
+  PageRecord pgRec;
+  unsigned char aMagic[sizeof(aJournalMagic)];
   int rc;
 
-  /* Figure out how many segments are in the journal.  Remember that
-  ** the first segment is one page shorter than the others and that
-  ** the last segment may be incomplete.
+  /* Read the beginning of the journal and truncate the
+  ** database file back to its original size.
   */
-  if( fstat(pPager->jfd; &statbuf)!=0 ){
-    return SQLITE_OK;
+  assert( pPager->jfd>=0 );
+  pager_seek(pPager->jfd, 0);
+  rc = pager_read(pPager->jfd, aMagic, sizeof(aMagic));
+  if( rc!=SQLITE_OK || memcmp(aMagic,aJournalMagic,sizeof(aMagic))!=0 ){
+    return SQLITE_PROTOCOL;
   }
-  if( statbuf.st_size <= SQLITE_INDEX_SIZE*SQLITE_PAGE_SIZE ){
-    nSeg = 1;
-  }else{
-    int nPage = statbuf.st_size/SQLITE_PAGE_SIZE;
-    nPage -= SQLITE_INDEX_SIZE;
-    nSeg = 1 + nPage/(SQLITE_INDEX_SIZE+1);
+  rc = pager_read(pPager->jfd, &mxPg, sizeof(mxPg));
+  if( rc!=SQLITE_OK ){
+    return SQLITE_PROTOCOL;
+  }
+  pager_truncate(pPager->fd, mxPg);
+  pPager->dbSize = mxPg;
+  
+  /* Begin reading the journal beginning at the end and moving
+  ** toward the beginning.
+  */
+  if( fstat(pPager->jfd, &statbuf)!=0 ){
+    return SQLITE_OK;
   }
+  nRec = (statbuf.st_size - (sizeof(aMagic)+sizeof(Pgno))) / sizeof(PageRecord);
 
   /* Process segments beginning with the last and working backwards
   ** to the first.
   */
-  for(i=nSeg-1; i>=0; i--){
+  for(i=nRec-1; i>=0; i--){
     /* Seek to the beginning of the segment */
-    sqlite_pager_seekpage(pPager->jfd, 
-        i>0 ? i*(SQLITE_INDEX_SIZE + 1) - 1 : 0,
-        SEEK_SET
-    );
-
-    /* Initialize the hash table used to avoid copying duplicate pages */
-    memset(aHash, 0, sizeof(aHash));
-
-    /* Read the index page */
-    sqlite_pager_readpage(pPager->jfd, aIndex);
-
-    /* Extract the original file size from the first index entry if this
-    ** is the first segment */   
-    if( i==0 ){
-      mxPg = aIndex[0];
-      aIndex[0] = 0;
+    off_t ofst;
+    ofst = i*sizeof(PageRecord) + sizeof(aMagic) + sizeof(Pgno);
+    rc = pager_seek(pPager->jfd, ofst);
+    if( rc!=SQLITE_OK ) break;
+    rc = pager_read(pPager->jfd, &pgRec, sizeof(pgRec));
+    if( rc!=SQLITE_OK ) break;
+
+    /* Sanity checking on the page */
+    if( pgRec.pgno>mxPg || pgRec.pgno==0 ){
+      rc = SQLITE_CORRUPT;
+      break;
     }
 
-    /* Process pages of this segment in forward order
+    /* Playback the page.  Update the in-memory copy of the page
+    ** at the same time, if there is one.
     */
-    for(j=0; j<SQLITE_INDEX_SIZE; j++){
-      Pgno pgno = aIndex[i];
-      void *pBuf;
-      PgHdr *pPg;
-
-      /* 0 means "no such page".  Skip zero entries */
-      if( pgno==0 ) continue;
-
-      /* Check to see if pgno is in the hash table.  Skip this
-      ** entry if it is.
-      */
-      h = pgno % (SQLITE_PAGE_SIZE-1);
-      while( aHash[h]!=0 && aHash[h]!=pgno ){
-        h++;
-        if( h>=SQLITE_PAGE_SIZE-1 ) h = 0;
-      }
-      if( aHash[h]==pgno ){
-        lseek(pPager->jfd, SQLITE_PAGE_SIZE, SEEK_CUR);
-        continue;
-      }
-      aHash[h] = pgno;
-
-      /* Playback the page.  Update the in-memory copy of the page
-      ** at the same time, if there is one.
-      */
-      pPg = sqlite_pager_lookup(pPager, pgno);
-      if( pPg ){
-        pBuf = PGHDR_TO_DATA(pPg);
-      }else{
-        pBuf = aBuf;
-      }
-      sqlite_pager_readpage(pPager->jfd, pBuf);
-      sqlite_pager_seekpage(pPager->fd, pgno, SEEK_SET);
-      rc = sqlite_pager_writepage(pPager->fd, pBuf);
-      if( rc!=SQLITE_OK ) return rc;
+    pPg = pager_lookup(pPager, pgRec.pgno);
+    if( pPg ){
+      memcpy(PGHDR_TO_DATA(pPg), pgRec.aData, SQLITE_PAGE_SIZE);
     }
+    rc = pager_seek(pPager->fd, (pgRec.pgno-1)*SQLITE_PAGE_SIZE);
+    if( rc!=SQLITE_OK ) break;
+    rc = pager_write(pPager->fd, pgRec.aData, SQLITE_PAGE_SIZE);
+    if( rc!=SQLITE_OK ) break;
   }
-
-  /* Truncate the database back to its original size
-  */
-  if( mxPg>0 ){
-    ftrucate(pPager->fd, mxPg * SQLITE_PAGE_SIZE);
+  if( rc!=SQLITE_OK ){
+    pager_unwritelock(pPager);
+    pPager->errMask |= PAGER_ERR_CORRUPT;
+    rc = SQLITE_CORRUPT;
+  }else{
+    rc = pager_unwritelock(pPager);
   }
-  return SQLITE_OK;
+  return rc;
 }
 
 /*
 ** Create a new page cache and put a pointer to the page cache in *ppPager.
 ** The file to be cached need not exist.  The file is not opened until
-** the first call to sqlite_pager_get() and is only held open until the
-** last page is released using sqlite_pager_unref().
+** the first call to sqlitepager_get() and is only held open until the
+** last page is released using sqlitepager_unref().
 */
-int sqlite_pager_open(Pager **ppPager, const char *zFilename, int mxPage){
+int sqlitepager_open(Pager **ppPager, const char *zFilename, int mxPage){
   Pager *pPager;
   int nameLen;
   int fd;
 
-  fd = open(zFilename, O_RDWR, 0644);
+  *ppPager = 0;
+  if( sqlite_malloc_failed ){
+    return SQLITE_NOMEM;
+  }
+  fd = open(zFilename, O_RDWR|O_CREAT, 0644);
   if( fd<0 ){
     return SQLITE_CANTOPEN;
   }
   nameLen = strlen(zFilename);
   pPager = sqliteMalloc( sizeof(*pPager) + nameLen*2 + 30 );
-  if( pPager==0 ) return SQLITE_NOMEM;
+  if( pPager==0 ){
+    close(fd);
+    return SQLITE_NOMEM;
+  }
   pPager->zFilename = (char*)&pPager[1];
   pPager->zJournal = &pPager->zFilename[nameLen+1];
   strcpy(pPager->zFilename, zFilename);
@@ -377,6 +458,7 @@ int sqlite_pager_open(Pager **ppPager, const char *zFilename, int mxPage){
   pPager->nPage = 0;
   pPager->mxPage = mxPage>10 ? mxPage : 10;
   pPager->state = SQLITE_UNLOCK;
+  pPager->errMask = 0;
   pPager->pFirst = 0;
   pPager->pLast = 0;
   memset(pPager->aHash, 0, sizeof(pPager->aHash));
@@ -387,9 +469,10 @@ int sqlite_pager_open(Pager **ppPager, const char *zFilename, int mxPage){
 /*
 ** Return the total number of pages in the file opened by pPager.
 */
-int sqlite_pager_pagecount(Pager *pPager){
+int sqlitepager_pagecount(Pager *pPager){
   int n;
   struct stat statbuf;
+  assert( pPager!=0 );
   if( pPager->dbSize>=0 ){
     return pPager->dbSize;
   }
@@ -398,7 +481,7 @@ int sqlite_pager_pagecount(Pager *pPager){
   }else{
     n = statbuf.st_size/SQLITE_PAGE_SIZE;
   }
-  if( pPager->state!=SQLITE_NOLOCK ){
+  if( pPager->state!=SQLITE_UNLOCK ){
     pPager->dbSize = n;
   }
   return n;
@@ -413,17 +496,16 @@ int sqlite_pager_pagecount(Pager *pPager){
 ** with this page cache after this function returns will likely
 ** result in a coredump.
 */
-int sqlite_pager_close(Pager *pPager){
-  int i;
-  PgHdr *pPg;
+int sqlitepager_close(Pager *pPager){
+  PgHdr *pPg, *pNext;
   switch( pPager->state ){
     case SQLITE_WRITELOCK: {
-      sqlite_pager_rollback(pPager);
-      sqlite_pager_unlock(pPager->fd);
+      sqlitepager_rollback(pPager);
+      pager_unlock(pPager->fd);
       break;
     }
     case SQLITE_READLOCK: {
-      sqlite_pager_unlock(pPager->fd);
+      pager_unlock(pPager->fd);
       break;
     }
     default: {
@@ -431,12 +513,9 @@ int sqlite_pager_close(Pager *pPager){
       break;
     }
   }
-  for(i=0; i<N_PG_HASH; i++){
-    PgHdr *pNext;
-    for(pPg=pPager->aHash[i]; pPg; pPg=pNext){
-      pNext = pPg->pNextHash;
-      sqliteFree(pPg);
-    }
+  for(pPg=pPager->pAll; pPg; pPg=pNext){
+    pNext = pPg->pNextAll;
+    sqliteFree(pPg);
   }
   if( pPager->fd>=0 ) close(pPager->fd);
   assert( pPager->jfd<0 );
@@ -447,25 +526,41 @@ int sqlite_pager_close(Pager *pPager){
 /*
 ** Return the page number for the given page data
 */
-int sqlite_pager_pagenumber(void *pData){
+Pgno sqlitepager_pagenumber(void *pData){
   PgHdr *p = DATA_TO_PGHDR(pData);
   return p->pgno;
 }
 
 /*
-** Acquire a page
+** Acquire a page.
+**
+** A read lock is obtained for the first page acquired.  The lock
+** is dropped when the last page is released.  
+**
+** The acquisition might fail for several reasons.  In all cases,
+** an appropriate error code is returned and *ppPage is set to NULL.
 */
-int sqlite_pager_get(Pager *pPager, int pgno, void **ppPage){
+int sqlitepager_get(Pager *pPager, Pgno pgno, void **ppPage){
   PgHdr *pPg;
 
+  /* Make sure we have not hit any critical errors.
+  */ 
+  if( pPager==0 || pgno==0 ){
+    return SQLITE_ERROR;
+  }
+  if( pPager->errMask & ~(PAGER_ERR_FULL) ){
+    return pager_errcode(pPager);
+  }
+
   /* If this is the first page accessed, then get a read lock
   ** on the database file.
   */
   if( pPager->nRef==0 ){
-    if( sqlite_pager_lock(pPager->fd, 0)!=0 ){
+    if( pager_lock(pPager->fd, 0)!=0 ){
       *ppPage = 0;
       return SQLITE_BUSY;
     }
+    pPager->state = SQLITE_READLOCK;
 
     /* If a journal file exists, try to play it back.
     */
@@ -476,16 +571,18 @@ int sqlite_pager_get(Pager *pPager, int pgno, void **ppPage){
        ** we cannot get exclusive access to the journal file
        */
        pPager->jfd = open(pPager->zJournal, O_RDONLY, 0);
-       if( pPager->jfd<0 || sqlite_pager_lock(pPager->jfd, 1)!=0 ){
+       if( pPager->jfd<0 || pager_lock(pPager->jfd, 1)!=0 ){
          if( pPager->jfd>=0 ){ close(pPager->jfd); pPager->jfd = -1; }
-         sqlite_pager_unlock(pPager->fd);
+         pager_unlock(pPager->fd);
          *ppPage = 0;
          return SQLITE_BUSY;
        }
 
        /* Get a write lock on the database */
-       sqlite_pager_unlock(pPager->fd);
-       if( sqlite_pager_lock(pPager->fd, 1)!=0 ){
+       pager_unlock(pPager->fd);
+       if( pager_lock(pPager->fd, 1)!=0 ){
+         close(pPager->jfd);
+         pPager->jfd = -1;
          *ppPage = 0;
          return SQLITE_PROTOCOL;
        }
@@ -493,36 +590,77 @@ int sqlite_pager_get(Pager *pPager, int pgno, void **ppPage){
        /* Playback and delete the journal.  Drop the database write
        ** lock and reacquire the read lock.
        */
-       sqlite_pager_playback(pPager);
-       rc = sqlite_pager_unwritelock(pPager);
-       if( rc!=SQLITE_OK ){ return SQLITE_PROTOCOL; }
+       rc = pager_playback(pPager);
+       if( rc!=SQLITE_OK ){
+         return rc;
+       }
     }
     pPg = 0;
+    pPager->nMiss++;
   }else{
     /* Search for page in cache */
-    pPg = sqlite_pager_lookup(pPager, pgno);
+    pPg = pager_lookup(pPager, pgno);
+    pPager->nHit++;
   }
   if( pPg==0 ){
+    /* The requested page is not in the page cache. */
     int h;
     if( pPager->nPage<pPager->mxPage || pPager->pFirst==0 ){
       /* Create a new page */
-      pPg = sqlite_malloc( sizeof(*pPg) + SQLITE_PAGE_SIZE );
+      pPg = sqliteMalloc( sizeof(*pPg) + SQLITE_PAGE_SIZE );
+      if( pPg==0 ){
+        *ppPage = 0;
+        pager_unwritelock(pPager);
+        pPager->errMask |= PAGER_ERR_MEM;
+        return SQLITE_NOMEM;
+      }
       pPg->pPager = pPager;
+      pPg->pNextAll = pPager->pAll;
+      if( pPager->pAll ){
+        pPager->pAll->pPrevAll = pPg;
+      }
+      pPg->pPrevAll = 0;
+      pPager->nPage++;
     }else{
-      /* Recycle an older page */
+      /* Recycle an older page.  First locate the page to be recycled.
+      ** Try to find one that is not dirty and is near the head of
+      ** of the free list */
+      int cnt = 4;
       pPg = pPager->pFirst;
+      while( pPg->dirty && 0<cnt-- ){
+        pPg = pPg->pNextFree;
+      }
+      if( pPg==0 || pPg->dirty ) pPg = pPager->pFirst;
+      assert( pPg->nRef==0 );
+
+      /* If the page to be recycled is dirty, sync the journal and write 
+      ** the old page into the database. */
       if( pPg->dirty ){
         int rc;
-        sqlite_pager_seekpage(pPager->fd, pPg->pgno, SEEK_SET);
-        rc = sqlite_pager_writepage(pPager->fd, PGHDR_TO_DATA(pPg));
+        assert( pPg->inJournal==1 );
+        assert( pPager->state==SQLITE_WRITELOCK );
+        rc = fsync(pPager->jfd);
+        if( rc!=0 ){
+          rc = sqlitepager_rollback(pPager);
+          *ppPage = 0;
+          if( rc==SQLITE_OK ) rc = SQLITE_IOERR;
+          return rc;
+        }
+        pager_seek(pPager->fd, (pPg->pgno-1)*SQLITE_PAGE_SIZE);
+        rc = pager_write(pPager->fd, PGHDR_TO_DATA(pPg), SQLITE_PAGE_SIZE);
         if( rc!=SQLITE_OK ){
+          rc = sqlitepager_rollback(pPager);
           *ppPage = 0;
+          if( rc==SQLITE_OK ) rc = SQLITE_FULL;
           return rc;
         }
-      } 
-      pPager->pFirst = pPg->pNext;
+      }
+
+      /* Unlink the old page from the free list and the hash table
+      */
+      pPager->pFirst = pPg->pNextFree;
       if( pPager->pFirst ){
-        pPager->pFirst->pPrev = 0;
+        pPager->pFirst->pPrevFree = 0;
       }else{
         pPager->pLast = 0;
       }
@@ -532,36 +670,41 @@ int sqlite_pager_get(Pager *pPager, int pgno, void **ppPage){
       if( pPg->pPrevHash ){
         pPg->pPrevHash->pNextHash = pPg->pNextHash;
       }else{
-        h = sqlite_pager_hash(pPg->pgno);
+        h = pager_hash(pPg->pgno);
         assert( pPager->aHash[h]==pPg );
         pPager->aHash[h] = pPg->pNextHash;
       }
+      pPager->nOvfl++;
     }
     pPg->pgno = pgno;
     pPg->inJournal = 0;
     pPg->dirty = 0;
     pPg->nRef = 1;
-    h = sqlite_pager_hash(pgno);
+    pPager->nRef++;
+    h = pager_hash(pgno);
     pPg->pNextHash = pPager->aHash[h];
     pPager->aHash[h] = pPg;
     if( pPg->pNextHash ){
       assert( pPg->pNextHash->pPrevHash==0 );
       pPg->pNextHash->pPrevHash = pPg;
     }
-    sqlite_pager_seekpage(pPager->fd, pgno, SEEK_SET);
-    sqlite_pager_readpage(pPager->fd, PGHDR_TO_DATA(pPg));
+    pager_seek(pPager->fd, (pgno-1)*SQLITE_PAGE_SIZE);
+    pager_read(pPager->fd, PGHDR_TO_DATA(pPg), SQLITE_PAGE_SIZE);
   }else{
+    /* The requested page is in the page cache. */
     if( pPg->nRef==0 ){
-      if( pPg->pPrev ){
-        pPg->pPrev->pNext = pPg->pNext;
+      /* The page is currently on the freelist.  Remove it. */
+      if( pPg->pPrevFree ){
+        pPg->pPrevFree->pNextFree = pPg->pNextFree;
       }else{
-        pPager->pFirst = pPg->pNext;
+        pPager->pFirst = pPg->pNextFree;
       }
-      if( pPg->pNext ){
-        pPg->pNext->pPrev = pPg->pPrev;
+      if( pPg->pNextFree ){
+        pPg->pNextFree->pPrevFree = pPg->pPrevFree;
       }else{
-        pPager->pLast = pPg->pPrev;
+        pPager->pLast = pPg->pPrevFree;
       }
+      pPager->nRef++;
     }
     pPg->nRef++;
   }
@@ -574,31 +717,43 @@ int sqlite_pager_get(Pager *pPager, int pgno, void **ppPage){
 **
 ** If the number of references to the page drop to zero, then the
 ** page is added to the LRU list.  When all references to all pages
-** are released, a rollback occurs, and the lock on the database is
+** are released, a rollback occurs and the lock on the database is
 ** removed.
 */
-int sqlite_pager_unref(void *pData){
+int sqlitepager_unref(void *pData){
   Pager *pPager;
   PgHdr *pPg;
+
+  /* Decrement the reference count for this page
+  */
   pPg = DATA_TO_PGHDR(pData);
   assert( pPg->nRef>0 );
   pPager = pPg->pPager;
   pPg->nRef--;
+
+  /* When the number of references to a page reach 0, add the
+  ** page to the freelist.
+  */
   if( pPg->nRef==0 ){
-    pPg->pNext = 0;
-    pPg->pPrev = pPager->pLast;
+    pPg->pNextFree = 0;
+    pPg->pPrevFree = pPager->pLast;
     pPager->pLast = pPg;
-    if( pPg->pPrev ){
-      pPg->pPrev->pNext = pPg;
+    if( pPg->pPrevFree ){
+      pPg->pPrevFree->pNextFree = pPg;
     }else{
       pPager->pFirst = pPg;
     }
+  
+    /* When all pages reach the freelist, drop the read lock from
+    ** the database file.
+    */
+    pPager->nRef--;
+    assert( pPager->nRef>=0 );
+    if( pPager->nRef==0 ){
+      pager_reset(pPager);
+    }
   }
-  pPager->nRef--;
-  assert( pPager->nRef>=0 );
-  if( pPager->nRef==0 ){
-    sqlite_pager_reset(pPager);
-  }
+  return SQLITE_OK;
 }
 
 /*
@@ -611,87 +766,120 @@ int sqlite_pager_unref(void *pData){
 ** lock could not be acquired, this routine returns SQLITE_BUSY.  The
 ** calling routine must check for that routine and be careful not to
 ** change any page data until this routine returns SQLITE_OK.
+**
+** If the journal file could not be written because the disk is full,
+** then this routine returns SQLITE_FULL and does an immediate rollback.
+** All subsequent write attempts also return SQLITE_FULL until there
+** is a call to sqlitepager_commit() or sqlitepager_rollback() to
+** reset.
 */
-int sqlite_pager_write(void *pData){
+int sqlitepager_write(void *pData){
   PgHdr *pPg = DATA_TO_PGHDR(pData);
   Pager *pPager = pPg->pPager;
   int rc;
 
+  if( pPager->errMask ){ 
+    return pager_errcode(pPager);
+  }
+  pPg->dirty = 1;
   if( pPg->inJournal ){ return SQLITE_OK; }
-  if( pPager->state==SQLITE_UNLOCK ){ return SQLITE_PROTOCOL; }
+  assert( pPager->state!=SQLITE_UNLOCK );
   if( pPager->state==SQLITE_READLOCK ){
     pPager->jfd = open(pPager->zJournal, O_RDWR|O_CREAT, 0644);
     if( pPager->jfd<0 ){
       return SQLITE_CANTOPEN;
     }
-    if( sqlite_pager_lock(pPager->jfd, 1) ){
+    if( pager_lock(pPager->jfd, 1) ){
       close(pPager->jfd);
       pPager->jfd = -1;
       return SQLITE_BUSY;
     }
-    sqlite_pager_unlock(pPager->fd);
-    if( sqlite_pager_lock(pPager->fd, 1) ){
+    pager_unlock(pPager->fd);
+    if( pager_lock(pPager->fd, 1) ){
       close(pPager->jfd);
       pPager->jfd = -1;
       pPager->state = SQLITE_UNLOCK;
-      sqlite_pager_reset(pPager);
+      pPager->errMask |= PAGER_ERR_LOCK;
       return SQLITE_PROTOCOL;
     }
     pPager->state = SQLITE_WRITELOCK;
-    pPager->jSize = 1;
-    pPager->aIdx[0] = pPager->dbSize;
+    sqlitepager_pagecount(pPager);
     pPager->origDbSize = pPager->dbSize;
-    pPager->nIdx = 1;
+    rc = pager_write(pPager->jfd, aJournalMagic, sizeof(aJournalMagic));
+    if( rc==SQLITE_OK ){
+      rc = pager_write(pPager->jfd, &pPager->dbSize, sizeof(Pgno));
+    }
+    if( rc!=SQLITE_OK ){
+      rc = pager_unwritelock(pPager);
+      if( rc==SQLITE_OK ) rc = SQLITE_FULL;
+      return rc;
+    }
   }
-  /* Write this page to the journal */
+  assert( pPager->state==SQLITE_WRITELOCK );
   assert( pPager->jfd>=0 );
-  if( pPg->pgno >= pPager->origDbSize ){
-    sqlite_pager_seekpage(pPager->fd, pPg->pgno, SEEK_SET);
-    rc = sqlite_pager_writepage(pPager->fd, pData);
-    pPg->inJournal = 1;
-    return rc;
+  if( pPg->pgno <= pPager->origDbSize ){
+    rc = pager_write(pPager->jfd, &pPg->pgno, sizeof(Pgno));
+    if( rc==SQLITE_OK ){
+      rc = pager_write(pPager->jfd, pData, SQLITE_PAGE_SIZE);
+    }
+    if( rc!=SQLITE_OK ){
+      sqlitepager_rollback(pPager);
+      pPager->errMask |= PAGER_ERR_FULL;
+      return rc;
+    }
   }
-  pPager->aIdx[pPager->nIdx++] = pPg->pgno;
-  sqlite_pager_seekpage(pPager->jfd, pPager->jSize++, SEEK_SET);
-  rc = sqlite_pager_write(pPager->jfd, pData);
   pPg->inJournal = 1;
-  if( pPager->nIdx==SQLITE_INDEX_SIZE ){
-    sqlite_pager_seekpage(pPager->jfd, pPager->idxPgno, SEEK_SET);
-    rc = sqlite_pager_writepage(pPager->jfd, &pPager->aIdx);
-    pPager->nIdx = 0;
-    pPager->jSize++;
-  }
   return rc;
 }
 
 /*
 ** Commit all changes to the database and release the write lock.
+**
+** If the commit fails for any reason, a rollback attempt is made
+** and an error code is returned.  If the commit worked, SQLITE_OK
+** is returned.
 */
-int sqlite_pager_commit(Pager*){
+int sqlitepager_commit(Pager *pPager){
   int i, rc;
   PgHdr *pPg;
-  assert( pPager->state==SQLITE_WRITELOCK );
+
+  if( pPager->errMask==PAGER_ERR_FULL ){
+    rc = sqlitepager_rollback(pPager);
+    if( rc==SQLITE_OK ) rc = SQLITE_FULL;
+    return rc;
+  }
+  if( pPager->errMask!=0 ){
+    rc = pager_errcode(pPager);
+    return rc;
+  }
+  if( pPager->state!=SQLITE_WRITELOCK ){
+    return SQLITE_ERROR;
+  }
   assert( pPager->jfd>=0 );
-  memset(&pPager->aIdx[&pPager->nIdx], 0, 
-          (SQLITE_INDEX_SIZE - pPager->nIdx)*sizeof(Pgno));
-  sqlite_pager_seekpage(pPager->jfd, pPager->idxPgno, SEEK_SET);
-  rc = sqlite_pager_writepage(pPager->jfd, &pPager->aIdx);
   if( fsync(pPager->jfd) ){
-    return SQLITE_IOERR;
+    goto commit_abort;
   }
   for(i=0; i<N_PG_HASH; i++){
     for(pPg=pPager->aHash[i]; pPg; pPg=pPg->pNextHash){
       if( pPg->dirty==0 ) continue;
-      rc = sqlite_pager_seekpage(pPager->fd, pPg->pgno, SEEK_SET);
-      if( rc!=SQLITE_OK ) return rc;
-      rc = sqlite_pager_writePage(pPager->fd, PGHDR_TO_DATA(pPg));
-      if( rc!=SQLITE_OK ) return rc;
+      rc = pager_seek(pPager->fd, (pPg->pgno-1)*SQLITE_PAGE_SIZE);
+      if( rc!=SQLITE_OK ) goto commit_abort;
+      rc = pager_write(pPager->fd, PGHDR_TO_DATA(pPg), SQLITE_PAGE_SIZE);
+      if( rc!=SQLITE_OK ) goto commit_abort;
     }
   }
-  if( fsync(pPager->fd) ){
-    return SQLITE_IOERR;
+  if( fsync(pPager->fd) ) goto commit_abort;
+  rc = pager_unwritelock(pPager);
+  pPager->dbSize = -1;
+  return rc;
+
+  /* Jump here if anything goes wrong during the commit process.
+  */
+commit_abort:
+  rc = sqlitepager_rollback(pPager);
+  if( rc==SQLITE_OK ){
+    rc = SQLITE_FULL;
   }
-  rc = sqlite_pager_unwritelock(pPager);
   return rc;
 }
 
@@ -699,17 +887,44 @@ int sqlite_pager_commit(Pager*){
 ** Rollback all changes.  The database falls back to read-only mode.
 ** All in-memory cache pages revert to their original data contents.
 ** The journal is deleted.
+**
+** This routine cannot fail unless some other process is not following
+** the correct locking protocol (SQLITE_PROTOCOL) or unless some other
+** process is writing trash into the journal file (SQLITE_CORRUPT) or
+** unless a prior malloc() failed (SQLITE_NOMEM).  Appropriate error
+** codes are returned for all these occasions.  Otherwise,
+** SQLITE_OK is returned.
 */
-int sqlite_pager_rollback(Pager *pPager){
+int sqlitepager_rollback(Pager *pPager){
   int rc;
-  if( pPager->state!=SQLITE_WRITELOCK ) return SQLITE_OK;
-  memset(&pPager->aIdx[&pPager->nIdx], 0, 
-          (SQLITE_INDEX_SIZE - pPager->nIdx)*sizeof(Pgno));
-  sqlite_pager_seekpage(pPager->jfd, pPager->idxPgno, SEEK_SET);
-  rc = sqlite_pager_writepage(pPager->jfd, &pPager->aIdx);
-  rc = sqlite_pager_playback(pPager);
+  if( pPager->errMask!=0 && pPager->errMask!=PAGER_ERR_FULL ){
+    return pager_errcode(pPager);
+  }
+  if( pPager->state!=SQLITE_WRITELOCK ){
+    return SQLITE_OK;
+  }
+  rc = pager_playback(pPager);
   if( rc!=SQLITE_OK ){
-    rc = sqlite_pager_unwritelock(pPager);
+    rc = SQLITE_CORRUPT;
+    pPager->errMask |= PAGER_ERR_CORRUPT;
   }
+  pPager->dbSize = -1;
   return rc;
 };
+
+/*
+** This routine is used for testing and analysis only.
+*/
+int *sqlitepager_stats(Pager *pPager){
+  static int a[9];
+  a[0] = pPager->nRef;
+  a[1] = pPager->nPage;
+  a[2] = pPager->mxPage;
+  a[3] = pPager->dbSize;
+  a[4] = pPager->state;
+  a[5] = pPager->errMask;
+  a[6] = pPager->nHit;
+  a[7] = pPager->nMiss;
+  a[8] = pPager->nOvfl;
+  return a;
+}
index cec8f4ea705ae569a82592b7edb30df8f19332a5..8935a80e2053494fd440cba83b3bf9cba5ca1721 100644 (file)
@@ -25,9 +25,8 @@
 ** subsystem.  The page cache subsystem reads and writes a file a page
 ** at a time and provides a journal for rollback.
 **
-** @(#) $Id: pager.h,v 1.1 2001/04/03 16:53:22 drh Exp $
+** @(#) $Id: pager.h,v 1.2 2001/04/15 00:37:09 drh Exp $
 */
-#include "sqliteInt.h"
 
 /*
 ** The size of one page
@@ -45,12 +44,14 @@ typedef unsigned int Pgno;
 */
 typedef struct Pager Pager;
 
-int sqlite_pager_open(Pager **ppPager, const char *zFilename);
-int sqlite_pager_close(Pager *pPager);
-int sqlite_pager_get(Pager *pPager, Pgno pgno, void **ppPage);
-int sqlite_pager_unref(void*);
-Pgno sqlite_pager_pagenumber(void*);
-int sqlite_pager_write(void*);
-int sqlite_pager_pagecount(Pager*);
-int sqlite_pager_commit(Pager*);
-int sqlite_pager_rollback(Pager*);
+int sqlitepager_open(Pager **ppPager, const char *zFilename, int nPage);
+int sqlitepager_close(Pager *pPager);
+int sqlitepager_get(Pager *pPager, Pgno pgno, void **ppPage);
+int sqlitepager_unref(void*);
+Pgno sqlitepager_pagenumber(void*);
+int sqlitepager_write(void*);
+int sqlitepager_pagecount(Pager*);
+int sqlitepager_commit(Pager*);
+int sqlitepager_rollback(Pager*);
+
+int *sqlitepager_stats(Pager*);
index 42c8eada555895d5211843e6eb95912ad0552b23..6d5c2c6b0be3334f4a239dfe0fc52b4e83808dbd 100644 (file)
@@ -23,7 +23,7 @@
 *************************************************************************
 ** A TCL Interface to SQLite
 **
-** $Id: tclsqlite.c,v 1.17 2001/04/07 15:24:33 drh Exp $
+** $Id: tclsqlite.c,v 1.18 2001/04/15 00:37:09 drh Exp $
 */
 #ifndef NO_TCL     /* Omit this whole file if TCL is unavailable */
 
@@ -507,10 +507,12 @@ int TCLSH_MAIN(int argc, char **argv){
   Tcl_FindExecutable(argv[0]);
   interp = Tcl_CreateInterp();
   Sqlite_Init(interp);
-#ifdef SQLITE_TEST1
+#ifdef SQLITE_TEST
   {
     extern int Sqlitetest1_Init(Tcl_Interp*);
+    extern int Sqlitetest2_Init(Tcl_Interp*);
     Sqlitetest1_Init(interp);
+    Sqlitetest2_Init(interp);
   }
 #endif
   if( argc>=2 ){
diff --git a/src/test2.c b/src/test2.c
new file mode 100644 (file)
index 0000000..036f454
--- /dev/null
@@ -0,0 +1,384 @@
+/*
+** Copyright (c) 2001 D. Richard Hipp
+**
+** This program is free software; you can redistribute it and/or
+** modify it under the terms of the GNU General Public
+** License as published by the Free Software Foundation; either
+** version 2 of the License, or (at your option) any later version.
+**
+** This program is distributed in the hope that it will be useful,
+** but WITHOUT ANY WARRANTY; without even the implied warranty of
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+** General Public License for more details.
+** 
+** You should have received a copy of the GNU General Public
+** License along with this library; if not, write to the
+** Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+** Boston, MA  02111-1307, USA.
+**
+** Author contact information:
+**   drh@hwaci.com
+**   http://www.hwaci.com/drh/
+**
+*************************************************************************
+** Code for testing the pager.c module in SQLite.  This code
+** is not included in the SQLite library.  It is used for automated
+** testing of the SQLite library.
+**
+** $Id: test2.c,v 1.1 2001/04/15 00:37:09 drh Exp $
+*/
+#include "sqliteInt.h"
+#include "pager.h"
+#include "tcl.h"
+#include <stdlib.h>
+#include <string.h>
+
+/*
+** Interpret an SQLite error number
+*/
+static char *errorName(int rc){
+  char *zName;
+  switch( rc ){
+    case SQLITE_OK:         zName = "SQLITE_OK";          break;
+    case SQLITE_ERROR:      zName = "SQLITE_ERROR";       break;
+    case SQLITE_INTERNAL:   zName = "SQLITE_INTERNAL";    break;
+    case SQLITE_PERM:       zName = "SQLITE_PERM";        break;
+    case SQLITE_ABORT:      zName = "SQLITE_ABORT";       break;
+    case SQLITE_BUSY:       zName = "SQLITE_BUSY";        break;
+    case SQLITE_NOMEM:      zName = "SQLITE_NOMEM";       break;
+    case SQLITE_READONLY:   zName = "SQLITE_READONLY";    break;
+    case SQLITE_INTERRUPT:  zName = "SQLITE_INTERRUPT";   break;
+    case SQLITE_IOERR:      zName = "SQLITE_IOERR";       break;
+    case SQLITE_CORRUPT:    zName = "SQLITE_CORRUPT";     break;
+    case SQLITE_NOTFOUND:   zName = "SQLITE_NOTFOUND";    break;
+    case SQLITE_FULL:       zName = "SQLITE_FULL";        break;
+    case SQLITE_CANTOPEN:   zName = "SQLITE_CANTOPEN";    break;
+    case SQLITE_PROTOCOL:   zName = "SQLITE_PROTOCOL";    break;
+    default:                zName = "SQLITE_Unknown";     break;
+  }
+  return zName;
+}
+
+/*
+** Usage:   pager_open FILENAME N-PAGE
+**
+** Open a new pager
+*/
+static int pager_open(
+  void *NotUsed,
+  Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
+  int argc,              /* Number of arguments */
+  char **argv            /* Text of each argument */
+){
+  Pager *pPager;
+  int nPage;
+  int rc;
+  char zBuf[100];
+  if( argc!=3 ){
+    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
+       " FILENAME N-PAGE\"", 0);
+    return TCL_ERROR;
+  }
+  if( Tcl_GetInt(interp, argv[2], &nPage) ) return TCL_ERROR;
+  rc = sqlitepager_open(&pPager, argv[1], nPage);
+  if( rc!=SQLITE_OK ){
+    Tcl_AppendResult(interp, errorName(rc), 0);
+    return TCL_ERROR;
+  }
+  sprintf(zBuf,"0x%x",(int)pPager);
+  Tcl_AppendResult(interp, zBuf, 0);
+  return TCL_OK;
+}
+
+/*
+** Usage:   pager_close ID
+**
+** Close the given pager.
+*/
+static int pager_close(
+  void *NotUsed,
+  Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
+  int argc,              /* Number of arguments */
+  char **argv            /* Text of each argument */
+){
+  Pager *pPager;
+  int rc;
+  if( argc!=2 ){
+    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
+       " ID\"", 0);
+    return TCL_ERROR;
+  }
+  if( Tcl_GetInt(interp, argv[1], (int*)&pPager) ) return TCL_ERROR;
+  rc = sqlitepager_close(pPager);
+  if( rc!=SQLITE_OK ){
+    Tcl_AppendResult(interp, errorName(rc), 0);
+    return TCL_ERROR;
+  }
+  return TCL_OK;
+}
+
+/*
+** Usage:   pager_rollback ID
+**
+** Rollback changes
+*/
+static int pager_rollback(
+  void *NotUsed,
+  Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
+  int argc,              /* Number of arguments */
+  char **argv            /* Text of each argument */
+){
+  Pager *pPager;
+  int rc;
+  if( argc!=2 ){
+    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
+       " ID\"", 0);
+    return TCL_ERROR;
+  }
+  if( Tcl_GetInt(interp, argv[1], (int*)&pPager) ) return TCL_ERROR;
+  rc = sqlitepager_rollback(pPager);
+  if( rc!=SQLITE_OK ){
+    Tcl_AppendResult(interp, errorName(rc), 0);
+    return TCL_ERROR;
+  }
+  return TCL_OK;
+}
+
+/*
+** Usage:   pager_commit ID
+**
+** Commit all changes
+*/
+static int pager_commit(
+  void *NotUsed,
+  Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
+  int argc,              /* Number of arguments */
+  char **argv            /* Text of each argument */
+){
+  Pager *pPager;
+  int rc;
+  if( argc!=2 ){
+    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
+       " ID\"", 0);
+    return TCL_ERROR;
+  }
+  if( Tcl_GetInt(interp, argv[1], (int*)&pPager) ) return TCL_ERROR;
+  rc = sqlitepager_commit(pPager);
+  if( rc!=SQLITE_OK ){
+    Tcl_AppendResult(interp, errorName(rc), 0);
+    return TCL_ERROR;
+  }
+  return TCL_OK;
+}
+
+/*
+** Usage:   pager_stats ID
+**
+** Return pager statistics.
+*/
+static int pager_stats(
+  void *NotUsed,
+  Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
+  int argc,              /* Number of arguments */
+  char **argv            /* Text of each argument */
+){
+  Pager *pPager;
+  int i, *a;
+  if( argc!=2 ){
+    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
+       " ID\"", 0);
+    return TCL_ERROR;
+  }
+  if( Tcl_GetInt(interp, argv[1], (int*)&pPager) ) return TCL_ERROR;
+  a = sqlitepager_stats(pPager);
+  for(i=0; i<9; i++){
+    static char *zName[] = {
+      "ref", "page", "max", "size", "state", "err",
+      "hit", "miss", "ovfl",
+    };
+    char zBuf[100];
+    Tcl_AppendElement(interp, zName[i]);
+    sprintf(zBuf,"%d",a[i]);
+    Tcl_AppendElement(interp, zBuf);
+  }
+  return TCL_OK;
+}
+
+/*
+** Usage:   pager_pagecount ID
+**
+** Return the size of the database file.
+*/
+static int pager_pagecount(
+  void *NotUsed,
+  Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
+  int argc,              /* Number of arguments */
+  char **argv            /* Text of each argument */
+){
+  Pager *pPager;
+  char zBuf[100];
+  if( argc!=2 ){
+    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
+       " ID\"", 0);
+    return TCL_ERROR;
+  }
+  if( Tcl_GetInt(interp, argv[1], (int*)&pPager) ) return TCL_ERROR;
+  sprintf(zBuf,"%d",sqlitepager_pagecount(pPager));
+  Tcl_AppendResult(interp, zBuf, 0);
+  return TCL_OK;
+}
+
+/*
+** Usage:   page_get ID PGNO
+**
+** Return a pointer to a page from the database.
+*/
+static int page_get(
+  void *NotUsed,
+  Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
+  int argc,              /* Number of arguments */
+  char **argv            /* Text of each argument */
+){
+  Pager *pPager;
+  char zBuf[100];
+  void *pPage;
+  int pgno;
+  int rc;
+  if( argc!=3 ){
+    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
+       " ID PGNO\"", 0);
+    return TCL_ERROR;
+  }
+  if( Tcl_GetInt(interp, argv[1], (int*)&pPager) ) return TCL_ERROR;
+  if( Tcl_GetInt(interp, argv[2], &pgno) ) return TCL_ERROR;
+  rc = sqlitepager_get(pPager, pgno, &pPage);
+  if( rc!=SQLITE_OK ){
+    Tcl_AppendResult(interp, errorName(rc), 0);
+    return TCL_ERROR;
+  }
+  sprintf(zBuf,"0x%x",(int)pPage);
+  Tcl_AppendResult(interp, zBuf, 0);
+  return TCL_OK;
+}
+
+/*
+** Usage:   page_unref PAGE
+**
+** Drop a pointer to a page.
+*/
+static int page_unref(
+  void *NotUsed,
+  Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
+  int argc,              /* Number of arguments */
+  char **argv            /* Text of each argument */
+){
+  void *pPage;
+  int rc;
+  if( argc!=2 ){
+    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
+       " PAGE\"", 0);
+    return TCL_ERROR;
+  }
+  if( Tcl_GetInt(interp, argv[1], (int*)&pPage) ) return TCL_ERROR;
+  rc = sqlitepager_unref(pPage);
+  if( rc!=SQLITE_OK ){
+    Tcl_AppendResult(interp, errorName(rc), 0);
+    return TCL_ERROR;
+  }
+  return TCL_OK;
+}
+
+/*
+** Usage:   page_read PAGE
+**
+** Return the content of a page
+*/
+static int page_read(
+  void *NotUsed,
+  Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
+  int argc,              /* Number of arguments */
+  char **argv            /* Text of each argument */
+){
+  char zBuf[100];
+  void *pPage;
+  if( argc!=2 ){
+    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
+       " PAGE\"", 0);
+    return TCL_ERROR;
+  }
+  if( Tcl_GetInt(interp, argv[1], (int*)&pPage) ) return TCL_ERROR;
+  memcpy(zBuf, pPage, sizeof(zBuf));
+  Tcl_AppendResult(interp, zBuf, 0);
+  return TCL_OK;
+}
+
+/*
+** Usage:   page_number PAGE
+**
+** Return the page number for a page.
+*/
+static int page_number(
+  void *NotUsed,
+  Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
+  int argc,              /* Number of arguments */
+  char **argv            /* Text of each argument */
+){
+  char zBuf[100];
+  void *pPage;
+  if( argc!=2 ){
+    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
+       " PAGE\"", 0);
+    return TCL_ERROR;
+  }
+  if( Tcl_GetInt(interp, argv[1], (int*)&pPage) ) return TCL_ERROR;
+  sprintf(zBuf, "%d", sqlitepager_pagenumber(pPage));
+  Tcl_AppendResult(interp, zBuf, 0);
+  return TCL_OK;
+}
+
+/*
+** Usage:   page_write PAGE DATA
+**
+** Write something into a page.
+*/
+static int page_write(
+  void *NotUsed,
+  Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
+  int argc,              /* Number of arguments */
+  char **argv            /* Text of each argument */
+){
+  void *pPage;
+  int rc;
+  if( argc!=3 ){
+    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
+       " PAGE DATA\"", 0);
+    return TCL_ERROR;
+  }
+  if( Tcl_GetInt(interp, argv[1], (int*)&pPage) ) return TCL_ERROR;
+  rc = sqlitepager_write(pPage);
+  if( rc!=SQLITE_OK ){
+    Tcl_AppendResult(interp, errorName(rc), 0);
+    return TCL_ERROR;
+  }
+  strncpy((char*)pPage, argv[2], SQLITE_PAGE_SIZE-1);
+  ((char*)pPage)[SQLITE_PAGE_SIZE-1] = 0;
+  return TCL_OK;
+}
+
+/*
+** Register commands with the TCL interpreter.
+*/
+int Sqlitetest2_Init(Tcl_Interp *interp){
+  Tcl_CreateCommand(interp, "pager_open", pager_open, 0, 0);
+  Tcl_CreateCommand(interp, "pager_close", pager_close, 0, 0);
+  Tcl_CreateCommand(interp, "pager_commit", pager_commit, 0, 0);
+  Tcl_CreateCommand(interp, "pager_rollback", pager_rollback, 0, 0);
+  Tcl_CreateCommand(interp, "pager_stats", pager_stats, 0, 0);
+  Tcl_CreateCommand(interp, "pager_pagecount", pager_pagecount, 0, 0);
+  Tcl_CreateCommand(interp, "page_get", page_get, 0, 0);
+  Tcl_CreateCommand(interp, "page_unref", page_unref, 0, 0);
+  Tcl_CreateCommand(interp, "page_read", page_read, 0, 0);
+  Tcl_CreateCommand(interp, "page_write", page_write, 0, 0);
+  Tcl_CreateCommand(interp, "page_number", page_number, 0, 0);
+  return TCL_OK;
+}
diff --git a/test/pager.test b/test/pager.test
new file mode 100644 (file)
index 0000000..cd83573
--- /dev/null
@@ -0,0 +1,176 @@
+# Copyright (c) 1999, 2000 D. Richard Hipp
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public
+# License as published by the Free Software Foundation; either
+# version 2 of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# General Public License for more details.
+# 
+# You should have received a copy of the GNU General Public
+# License along with this library; if not, write to the
+# Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+# Boston, MA  02111-1307, USA.
+#
+# Author contact information:
+#   drh@hwaci.com
+#   http://www.hwaci.com/drh/
+#
+#***********************************************************************
+# This file implements regression tests for SQLite library.  The
+# focus of this script is page cache subsystem.
+#
+# $Id: pager.test,v 1.1 2001/04/15 00:37:21 drh Exp $
+
+
+set testdir [file dirname $argv0]
+source $testdir/tester.tcl
+
+if {$dbprefix!="mem:" && [info commands pager_open]!=""} {
+
+# Basic sanity check.  Open and close a pager.
+#
+do_test pager-1.0 {
+  catch {file delete -force ptf1.db}
+  catch {file delete -force ptf1.db-journal}
+  set v [catch {
+    set ::p1 [pager_open ptf1.db 10]
+  } msg]
+} {0}
+do_test pager-1.1 {
+  pager_stats $::p1
+} {ref 0 page 0 max 10 size -1 state 0 err 0 hit 0 miss 0 ovfl 0}
+do_test pager-1.2 {
+  pager_pagecount $::p1
+} {0}
+do_test pager-1.3 {
+  pager_stats $::p1
+} {ref 0 page 0 max 10 size -1 state 0 err 0 hit 0 miss 0 ovfl 0}
+do_test pager-1.4 {
+  pager_close $::p1
+} {}
+
+# Try to write a few pages.
+#
+do_test pager-2.1 {
+  set v [catch {
+    set ::p1 [pager_open ptf1.db 10]
+  } msg]
+} {0}
+do_test pager-2.2 {
+  set v [catch {
+    set ::g1 [page_get $::p1 0]
+  } msg]
+  lappend v $msg
+} {1 SQLITE_ERROR}
+do_test pager-2.3 {
+  set v [catch {
+    set ::g1 [page_get $::p1 1]
+  } msg]
+  if {$v} {lappend v $msg}
+  set v
+} {0}
+do_test pager-2.4 {
+  pager_stats $::p1
+} {ref 1 page 1 max 10 size -1 state 1 err 0 hit 0 miss 1 ovfl 0}
+do_test pager-2.5 {
+  pager_pagecount $::p1
+} {0}
+do_test pager-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 pager-2.7 {
+  page_number $::g1
+} {1}
+do_test pager-2.8 {
+  page_read $::g1
+} {}
+do_test pager-2.9 {
+  page_unref $::g1
+} {}
+do_test pager-2.10 {
+  pager_stats $::p1
+} {ref 0 page 0 max 10 size -1 state 0 err 0 hit 0 miss 1 ovfl 0}
+do_test pager-2.11 {
+  set ::g1 [page_get $::p1 1]
+  expr {$::g1!=0}
+} {1}
+do_test pager-2.12 {
+  page_number $::g1
+} {1}
+do_test pager-2.13 {
+  pager_stats $::p1
+} {ref 1 page 1 max 10 size -1 state 1 err 0 hit 0 miss 2 ovfl 0}
+do_test pager-2.14 {
+  set v [catch {
+    page_write $::g1 "Page-One"
+  } msg]
+  lappend v $msg
+} {0 {}}
+do_test pager-2.15 {
+  pager_stats $::p1
+} {ref 1 page 1 max 10 size 0 state 2 err 0 hit 0 miss 2 ovfl 0}
+do_test pager-2.16 {
+  page_read $::g1
+} {Page-One}
+do_test pager-2.17 {
+  set v [catch {
+    pager_commit $::p1
+  } msg]
+  lappend v $msg
+} {0 {}}
+do_test pager-2.20 {
+  pager_stats $::p1
+} {ref 1 page 1 max 10 size -1 state 1 err 0 hit 0 miss 2 ovfl 0}
+do_test pager-2.19 {
+  pager_pagecount $::p1
+} {1}
+do_test pager-2.21 {
+  pager_stats $::p1
+} {ref 1 page 1 max 10 size 1 state 1 err 0 hit 0 miss 2 ovfl 0}
+do_test pager-2.22 {
+  page_unref $::g1
+} {}
+do_test pager-2.23 {
+  pager_stats $::p1
+} {ref 0 page 0 max 10 size -1 state 0 err 0 hit 0 miss 2 ovfl 0}
+do_test pager-2.24 {
+  set v [catch {
+    page_get $::p1 1
+  } ::g1]
+  if {$v} {lappend v $::g1}
+  set v
+} {0}
+do_test pager-2.25 {
+  page_read $::g1
+} {Page-One}
+do_test pager-2.26 {
+  set v [catch {
+    page_write $::g1 {page-one}
+  } msg]
+  lappend v $msg
+} {0 {}}
+do_test pager-2.27 {
+  page_read $::g1
+} {page-one}
+do_test pager-2.28 {
+  set v [catch {
+    pager_rollback $::p1
+  } msg]
+  lappend v $msg
+} {0 {}}
+do_test pager-2.29 {
+  page_read $::g1
+} {Page-One}
+
+do_test pager-2.99 {
+  pager_close $::p1
+} {}
+
+
+} ;# end if( not mem: and has pager_open command );
+
+finish_test
index f5de4f6236ce81cc5ff68e6f40f9bb2426eed7d6..80e34b4e97a8929304dd6ce306a766524f87e43a 100644 (file)
@@ -17,7 +17,8 @@ proc chng {date desc} {
   puts "<DD><P><UL>$desc</UL></P></DD>"
 }
 
-chng {2001 Apr 12 (1.0.31)} {
+chng {2001 Apr 14 (1.0.31)} {
+<li>Pager subsystem added but not yet used.</li>
 <li>More robust handling of out-of-memory errors.</li>
 <li>New tests added to the test suite.</li>
 }