diff --git a/manifest b/manifest index f7b6fd060c..15b32bc7e9 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C better\shandling\sof\sout-of-memory\serrors\s(CVS\s207) -D 2001-04-11T14:28:42 +C :-)\s(CVS\s208) +D 2001-04-11T14:29:22 F COPYRIGHT 74a8a6531a42e124df07ab5599aad63870fa0bd4 F Makefile.in 4775f7fd1ed543606bb24fa3fada1bc90a23a6b9 F README 51f6a4e7408b34afa5bc1c0485f61b6a4efb6958 @@ -26,6 +26,7 @@ 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 0aa36ee4038369fb7537871950d192e5e93d5530 F src/pager.h 889c5cf517ad30704e295540793c893ac843fd5f F src/parse.y 8fc096948994a7ffbf61ba13129cc589f794a9cb F src/printf.c b1e22a47be8cdf707815647239991e08e8cb69f9 @@ -56,6 +57,7 @@ F test/insert.test dbd3bd189edb61fddbe66c236694ef23352429f1 F test/insert2.test 732405e30331635af8d159fccabe835eea5cd0c6 F test/lock.test bca7d53de73138b1f670a2fbdb1f481ff7eaa45a F test/main.test da635f9e078cd21ddf074e727381a715064489ff +F test/malloc.test 47777dd5697c43f575d6a898b6b3f332f10a71d3 F test/printf.test 4c71871e1a75a2dacb673945fc13ddb30168798f F test/rowid.test 128453599def7435e988216f7fe89c7450b8a9a3 F test/select1.test 824d9d5007dffd6a45edde79e89c0a04c36e3ebe @@ -97,7 +99,7 @@ F www/opcode.tcl cb3a1abf8b7b9be9f3a228d097d6bf8b742c2b6f F www/sqlite.tcl cb0d23d8f061a80543928755ec7775da6e4f362f F www/tclsqlite.tcl 06f81c401f79a04f2c5ebfb97e7c176225c0aef2 F www/vdbe.tcl 0c8aaa529dd216ccbf7daaabd80985e413d5f9ad -P 2507ec40610d8034ccf9dcb58a16934065e6f120 -R 0329ee9c40e806471f7c325d51a23bb6 +P 86b30cd0975dfea9424b9f9f0d4194aa71ce508b +R 446c442a007e555210ed8cb182f64479 U drh -Z 598daf9af8c7873df8fb5f69d495d703 +Z 9bc3b137c004e3bfdf49c47f3fac4875 diff --git a/manifest.uuid b/manifest.uuid index 67d0873d5d..cd48105137 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -86b30cd0975dfea9424b9f9f0d4194aa71ce508b \ No newline at end of file +555351dd1918f96167e2cb46cc1db6496e8d10a3 \ No newline at end of file diff --git a/src/pager.c b/src/pager.c new file mode 100644 index 0000000000..a763d7d89a --- /dev/null +++ b/src/pager.c @@ -0,0 +1,677 @@ +/* +** 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/ +** +************************************************************************* +** This is the implementation of the page cache subsystem. +** +** The page cache is used to access a database file. The pager journals +** 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.1 2001/04/11 14:29:22 drh Exp $ +*/ +#include "pager.h" +#include +#include +#include +#include + +/* +** The page cache as a whole is always in one of the following +** states: +** +** SQLITE_UNLOCK The page cache is not currently reading or +** writing the database file. There is no +** data held in memory. This is the initial +** state. +** +** SQLITE_READLOCK The page cache is reading the database. +** Writing is not permitted. There can be +** multiple readers accessing the same database +** at the same time. +** +** SQLITE_WRITELOCK The page cache is writing the database. +** Access is exclusive. No other processes or +** threads can be reading or writing while one +** process is writing. +** +** The page cache comes up in PCS_UNLOCK. The first time a +** sqlite_page_get() occurs, the state transitions to PCS_READLOCK. +** After all pages have been released using sqlite_page_unref(), +** the state transitions back to PCS_UNLOCK. The first time +** that sqlite_page_write() is called, the state transitions to +** PCS_WRITELOCK. The sqlite_page_rollback() and sqlite_page_commit() +** functions transition the state back to PCS_READLOCK. +*/ +#define SQLITE_UNLOCK 0 +#define SQLITE_READLOCK 1 +#define SQLITE_WRITELOCK 2 + +/* +** Each in-memory image of a page begins with the following header. +*/ +struct PgHdr { + Pager *pPager; /* The pager to which this page belongs */ + Pgno pgno; /* The page number for this page */ + PgHdr *pNextHash, *pPrevHash; /* Hash collision change for PgHdr.pgno */ + int nRef; /* Number of users of this page */ + PgHdr *pNext, *pPrev; /* Freelist of pages where nRef==0 */ + char inJournal; /* TRUE if has been written to journal */ + char dirty; /* TRUE if we need to write back changes */ + /* The page data follows this header */ +}; + +/* +** Convert a pointer to a PgHdr into a pointer to its data, +** and vice verse. +*/ +#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 + +/* +** A open page cache is an instance of the following structure. +*/ +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 jSize; /* Number of pages in the journal */ + int nPage; /* Total number of in-memory pages */ + int mxPage; /* Maximum number of pages to hold in cache */ + Pgno *aIdx; /* Current journal index page */ + char state; /* SQLITE_UNLOCK, _READLOCK or _WRITELOCK */ + PgHdr *pFirst, *pLast; /* List of free pages */ + PgHdr *aHash[N_PG_HASH]; /* Hash table to map page number of PgHdr */ +}; + +/* +** Hash a page number +*/ +#define sqlite_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){ + struct flock lock; + lock.l_type = write_lock ? F_WRLCK : F_RDLCK; + return fcntl(fd, F_SETLK, &lock)!=0; +} + +/* +** Unlock the database file. +*/ +static int sqlite_pager_unlock(fd){ + struct flock lock; + lock.l_type = F_UNLCK; + return fcntl(fd, F_SETLK, &lock)!=0; +} + +/* +** 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){ + PgHdr *p = pPager->aHash[pgno % N_PG_HASH]; + while( p && p->pgno!=pgno ){ + p = p->pNextHash; + } + return p; +} + +/* +** Unlock the database and clear the in-memory cache. This routine +** sets the state of the pager back to what it was when it was first +** 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){ + PgHdr *pPg, *pNext; + for(pPg=pPager->pFirst; pPg; pPg=pNext){ + pNext = pPg->pNext; + sqlite_free(pPg); + } + pPager->pFirst = 0; + pPager->pNext = 0; + memset(pPager->aHash, 0, sizeof(pPager->aHash)); + pPager->nPage = 0; + if( pPager->state==SQLITE_WRITELOCK ){ + sqlite_pager_rollback(pPager); + } + sqlite_pager_unlock(pPager->fd); + pPager->state = SQLITE_UNLOCK; + pPager->nRef = 0; +} + +/* +** When this routine is called, the pager has the journal file open and +** a write lock on the database. This routine releases the database +** write lock and acquires a read lock in its place. The journal file +** is deleted and closed. +** +** We have to release the write lock before acquiring the read lock, +** so there is a race condition where another process can get the lock +** while we are not holding it. But, no other process should do this +** because we are also holding a lock on the journal, and no process +** 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. +*/ +static int sqlite_pager_unwritelock(Pager *pPager){ + int rc; + assert( pPager->state==SQLITE_WRITELOCK ); + sqlite_pager_unlock(pPager->fd); + rc = sqlite_pager_lock(pPager->fd, 0); + unlink(pPager->zJournal); + close(pPager->jfd); + pPager->jfd = -1; + if( rc!=SQLITE_OK ){ + pPager->state = SQLITE_UNLOCK; + sqlite_pager_reset(pPager); + rc = SQLITE_PROTOCOL; + }else{ + 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 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. +** +** 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. +*/ +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 */ + 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. + */ + if( fstat(pPager->jfd; &statbuf)!=0 ){ + return SQLITE_OK; + } + 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); + } + + /* Process segments beginning with the last and working backwards + ** to the first. + */ + for(i=nSeg-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; + } + + /* Process pages of this segment in forward order + */ + for(j=0; j=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; + } + } + + /* Truncate the database back to its original size + */ + if( mxPg>0 ){ + ftrucate(pPager->fd, mxPg * SQLITE_PAGE_SIZE); + } + return SQLITE_OK; +} + +/* +** 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(). +*/ +int sqlite_pager_open(Pager **ppPager, const char *zFilename, int mxPage){ + Pager *pPager; + int nameLen; + int fd; + + fd = open(zFilename, O_RDWR, 0644); + if( fd<0 ){ + return SQLITE_CANTOPEN; + } + nameLen = strlen(zFilename); + pPager = sqliteMalloc( sizeof(*pPager) + nameLen*2 + 30 ); + if( pPager==0 ) return SQLITE_NOMEM; + pPager->zFilename = (char*)&pPager[1]; + pPager->zJournal = &pPager->zFilename[nameLen+1]; + strcpy(pPager->zFilename, zFilename); + strcpy(pPager->zJournal, zFilename); + strcpy(&pPager->zJournal[nameLen], "-journal"); + pPager->fd = fd; + pPager->jfd = -1; + pPager->nRef = 0; + pPager->dbSize = -1; + pPager->nPage = 0; + pPager->mxPage = mxPage>10 ? mxPage : 10; + pPager->state = SQLITE_UNLOCK; + pPager->pFirst = 0; + pPager->pLast = 0; + memset(pPager->aHash, 0, sizeof(pPager->aHash)); + *ppPager = pPager; + return SQLITE_OK; +} + +/* +** Return the total number of pages in the file opened by pPager. +*/ +int sqlite_pager_pagecount(Pager *pPager){ + int n; + struct stat statbuf; + if( pPager->dbSize>=0 ){ + return pPager->dbSize; + } + if( fstat(pPager->fd, &statbuf)!=0 ){ + n = 0; + }else{ + n = statbuf.st_size/SQLITE_PAGE_SIZE; + } + if( pPager->state!=SQLITE_NOLOCK ){ + pPager->dbSize = n; + } + return n; +} + +/* +** Shutdown the page cache. Free all memory and close all files. +** +** If a transaction was in progress when this routine is called, that +** transaction is rolled back. All outstanding pages are invalidated +** and their memory is freed. Any attempt to use a page associated +** with this page cache after this function returns will likely +** result in a coredump. +*/ +int sqlite_pager_close(Pager *pPager){ + int i; + PgHdr *pPg; + switch( pPager->state ){ + case SQLITE_WRITELOCK: { + sqlite_pager_rollback(pPager); + sqlite_pager_unlock(pPager->fd); + break; + } + case SQLITE_READLOCK: { + sqlite_pager_unlock(pPager->fd); + break; + } + default: { + /* Do nothing */ + break; + } + } + for(i=0; iaHash[i]; pPg; pPg=pNext){ + pNext = pPg->pNextHash; + sqliteFree(pPg); + } + } + if( pPager->fd>=0 ) close(pPager->fd); + assert( pPager->jfd<0 ); + sqliteFree(pPager); + return SQLITE_OK; +} + +/* +** Return the page number for the given page data +*/ +int sqlite_pager_pagenumber(void *pData){ + PgHdr *p = DATA_TO_PGHDR(pData); + return p->pgno; +} + +/* +** Acquire a page +*/ +int sqlite_pager_get(Pager *pPager, int pgno, void **ppPage){ + PgHdr *pPg; + + /* 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 ){ + *ppPage = 0; + return SQLITE_BUSY; + } + + /* If a journal file exists, try to play it back. + */ + if( access(pPager->zJournal,0)==0 ){ + int rc; + + /* Open the journal for exclusive access. Return SQLITE_BUSY if + ** 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 ){ close(pPager->jfd); pPager->jfd = -1; } + sqlite_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 ){ + *ppPage = 0; + return SQLITE_PROTOCOL; + } + + /* 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; } + } + pPg = 0; + }else{ + /* Search for page in cache */ + pPg = sqlite_pager_lookup(pPager, pgno); + } + if( pPg==0 ){ + int h; + if( pPager->nPagemxPage || pPager->pFirst==0 ){ + /* Create a new page */ + pPg = sqlite_malloc( sizeof(*pPg) + SQLITE_PAGE_SIZE ); + pPg->pPager = pPager; + }else{ + /* Recycle an older page */ + pPg = pPager->pFirst; + if( pPg->dirty ){ + int rc; + sqlite_pager_seekpage(pPager->fd, pPg->pgno, SEEK_SET); + rc = sqlite_pager_writepage(pPager->fd, PGHDR_TO_DATA(pPg)); + if( rc!=SQLITE_OK ){ + *ppPage = 0; + return rc; + } + } + pPager->pFirst = pPg->pNext; + if( pPager->pFirst ){ + pPager->pFirst->pPrev = 0; + }else{ + pPager->pLast = 0; + } + if( pPg->pNextHash ){ + pPg->pNextHash->pPrevHash = pPg->pPrevHash; + } + if( pPg->pPrevHash ){ + pPg->pPrevHash->pNextHash = pPg->pNextHash; + }else{ + h = sqlite_pager_hash(pPg->pgno); + assert( pPager->aHash[h]==pPg ); + pPager->aHash[h] = pPg->pNextHash; + } + } + pPg->pgno = pgno; + pPg->inJournal = 0; + pPg->dirty = 0; + pPg->nRef = 1; + h = sqlite_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)); + }else{ + if( pPg->nRef==0 ){ + if( pPg->pPrev ){ + pPg->pPrev->pNext = pPg->pNext; + }else{ + pPager->pFirst = pPg->pNext; + } + if( pPg->pNext ){ + pPg->pNext->pPrev = pPg->pPrev; + }else{ + pPager->pLast = pPg->pPrev; + } + } + pPg->nRef++; + } + *ppPage = PGHDR_TO_DATA(pPg); + return SQLITE_OK; +} + +/* +** Release a page. +** +** 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 +** removed. +*/ +int sqlite_pager_unref(void *pData){ + Pager *pPager; + PgHdr *pPg; + pPg = DATA_TO_PGHDR(pData); + assert( pPg->nRef>0 ); + pPager = pPg->pPager; + pPg->nRef--; + if( pPg->nRef==0 ){ + pPg->pNext = 0; + pPg->pPrev = pPager->pLast; + pPager->pLast = pPg; + if( pPg->pPrev ){ + pPg->pPrev->pNext = pPg; + }else{ + pPager->pFirst = pPg; + } + } + pPager->nRef--; + assert( pPager->nRef>=0 ); + if( pPager->nRef==0 ){ + sqlite_pager_reset(pPager); + } +} + +/* +** Mark a data page as writeable. The page is written into the journal +** if it is not there already. This routine must be called before making +** changes to a page. +** +** The first time this routine is called, the pager creates a new +** journal and acquires a write lock on the database. If the write +** 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. +*/ +int sqlite_pager_write(void *pData){ + 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) ){ + close(pPager->jfd); + pPager->jfd = -1; + return SQLITE_BUSY; + } + sqlite_pager_unlock(pPager->fd); + if( sqlite_pager_lock(pPager->fd, 1) ){ + close(pPager->jfd); + pPager->jfd = -1; + pPager->state = SQLITE_UNLOCK; + sqlite_pager_reset(pPager); + return SQLITE_PROTOCOL; + } + pPager->state = SQLITE_WRITELOCK; + pPager->jSize = 0; + } + /* Write this page to the journal */ +} + +/* +** Commit all changes to the database and release the write lock. +*/ +int sqlite_pager_commit(Pager*){ + int i, rc; + PgHdr *pPg; + assert( pPager->state==SQLITE_WRITELOCK ); + assert( pPager->jfd>=0 ); + if( fsync(pPager->jfd) ){ + return SQLITE_IOERR; + } + for(i=0; iaHash[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; + } + } + if( fsync(pPager->fd) ){ + return SQLITE_IOERR; + } + rc = sqlite_pager_unwritelock(pPager); + return rc; +} + +/* +** 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. +*/ +int sqlite_pager_rollback(Pager *pPager){ + int rc; + if( pPager->state!=SQLITE_WRITELOCK ) return SQLITE_OK; + rc = sqlite_pager_playback(pPager); + if( rc!=SQLITE_OK ){ + rc = sqlite_pager_unwritelock(pPager); + } + return rc; +}; diff --git a/test/malloc.test b/test/malloc.test new file mode 100644 index 0000000000..d8a24f199d --- /dev/null +++ b/test/malloc.test @@ -0,0 +1,69 @@ +# 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/ +# +#*********************************************************************** +# This file attempts to check the library in an out-of-memory situation. +# When compiled with -DMEMORY_DEBUG=1, the SQLite library accepts a special +# command (--malloc-fail=N) which causes the N-th malloc to fail. This +# special feature is used to see what happens in the library if a malloc +# were to really fail due to an out-of-memory situation. +# +# $Id: malloc.test,v 1.1 2001/04/11 14:29:22 drh Exp $ + +set testdir [file dirname $argv0] +source $testdir/tester.tcl + +# Only run these tests if memory debugging is turned on. +# +if {[info command sqlite_malloc_fail]==""} { + puts "Skipping malloc tests: not compiled with -DMEMORY_DEBUG..." + finish_test + return +} + +for {set go 1; set i 1} {$go} {incr i} { + do_test malloc-1.$i { + sqlite_malloc_fail 0 + catch {execsql {DROP TABLE t1}} + sqlite_malloc_fail $i + set v [catch {execsql { + CREATE TABLE t1( + a int, b float, c double, d text, e varchar(20), + primary key(a,b,c) + ); + CREATE INDEX i1 ON t1(a,b); + INSERT INTO t1 VALUES(1,2.3,4.5,'hi','there'); + INSERT INTO t1 VALUES(6,7.0,0.8,'hello','out yonder'); + SELECT * FROM t1; + SELECT avg(b) FROM t1 GROUP BY a; + DELETE FROM t1 WHERE a==6; + SELECT count(*) FROM t1; + }} msg] + if {[lindex [sqlite_malloc_stat] 2]>0} { + set ::go 0 + set v {1 1} + } else { + lappend v [expr {$msg=="" || $msg=="out of memory"}] + } + } {1 1} +} +sqlite_malloc_fail 0 +finish_test