aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/build.c21
-rw-r--r--src/pager.c711
-rw-r--r--src/pager.h23
-rw-r--r--src/tclsqlite.c6
-rw-r--r--src/test2.c384
5 files changed, 881 insertions, 264 deletions
diff --git a/src/build.c b/src/build.c
index df2526960..b3a5eff57 100644
--- a/src/build.c
+++ b/src/build.c
@@ -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;
diff --git a/src/pager.c b/src/pager.c
index 41650d98f..9402b10d3 100644
--- a/src/pager.c
+++ b/src/pager.c
@@ -27,13 +27,15 @@
** 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
@@ -66,15 +68,18 @@
#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 */
@@ -88,15 +93,10 @@ struct PgHdr {
#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;
+}
diff --git a/src/pager.h b/src/pager.h
index cec8f4ea7..8935a80e2 100644
--- a/src/pager.h
+++ b/src/pager.h
@@ -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*);
diff --git a/src/tclsqlite.c b/src/tclsqlite.c
index 42c8eada5..6d5c2c6b0 100644
--- a/src/tclsqlite.c
+++ b/src/tclsqlite.c
@@ -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
index 000000000..036f454e2
--- /dev/null
+++ b/src/test2.c
@@ -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;
+}