mirror of
https://github.com/sqlite/sqlite.git
synced 2025-08-08 14:02:16 +03:00
:-) (CVS 208)
FossilOrigin-Name: 555351dd1918f96167e2cb46cc1db6496e8d10a3
This commit is contained in:
12
manifest
12
manifest
@@ -1,5 +1,5 @@
|
|||||||
C better\shandling\sof\sout-of-memory\serrors\s(CVS\s207)
|
C :-)\s(CVS\s208)
|
||||||
D 2001-04-11T14:28:42
|
D 2001-04-11T14:29:22
|
||||||
F COPYRIGHT 74a8a6531a42e124df07ab5599aad63870fa0bd4
|
F COPYRIGHT 74a8a6531a42e124df07ab5599aad63870fa0bd4
|
||||||
F Makefile.in 4775f7fd1ed543606bb24fa3fada1bc90a23a6b9
|
F Makefile.in 4775f7fd1ed543606bb24fa3fada1bc90a23a6b9
|
||||||
F README 51f6a4e7408b34afa5bc1c0485f61b6a4efb6958
|
F README 51f6a4e7408b34afa5bc1c0485f61b6a4efb6958
|
||||||
@@ -26,6 +26,7 @@ F src/ex/sizes.tcl f54bad4a2ac567624be59131a6ee42d71b41a3d7
|
|||||||
F src/expr.c c4c24c3af1eba094a816522eb0e085bed518ee16
|
F src/expr.c c4c24c3af1eba094a816522eb0e085bed518ee16
|
||||||
F src/insert.c aa528e20a787af85432a61daaea6df394bd251d7
|
F src/insert.c aa528e20a787af85432a61daaea6df394bd251d7
|
||||||
F src/main.c 92ce30a89f622ba36cc8b7d912829e14a480722c
|
F src/main.c 92ce30a89f622ba36cc8b7d912829e14a480722c
|
||||||
|
F src/pager.c 0aa36ee4038369fb7537871950d192e5e93d5530
|
||||||
F src/pager.h 889c5cf517ad30704e295540793c893ac843fd5f
|
F src/pager.h 889c5cf517ad30704e295540793c893ac843fd5f
|
||||||
F src/parse.y 8fc096948994a7ffbf61ba13129cc589f794a9cb
|
F src/parse.y 8fc096948994a7ffbf61ba13129cc589f794a9cb
|
||||||
F src/printf.c b1e22a47be8cdf707815647239991e08e8cb69f9
|
F src/printf.c b1e22a47be8cdf707815647239991e08e8cb69f9
|
||||||
@@ -56,6 +57,7 @@ F test/insert.test dbd3bd189edb61fddbe66c236694ef23352429f1
|
|||||||
F test/insert2.test 732405e30331635af8d159fccabe835eea5cd0c6
|
F test/insert2.test 732405e30331635af8d159fccabe835eea5cd0c6
|
||||||
F test/lock.test bca7d53de73138b1f670a2fbdb1f481ff7eaa45a
|
F test/lock.test bca7d53de73138b1f670a2fbdb1f481ff7eaa45a
|
||||||
F test/main.test da635f9e078cd21ddf074e727381a715064489ff
|
F test/main.test da635f9e078cd21ddf074e727381a715064489ff
|
||||||
|
F test/malloc.test 47777dd5697c43f575d6a898b6b3f332f10a71d3
|
||||||
F test/printf.test 4c71871e1a75a2dacb673945fc13ddb30168798f
|
F test/printf.test 4c71871e1a75a2dacb673945fc13ddb30168798f
|
||||||
F test/rowid.test 128453599def7435e988216f7fe89c7450b8a9a3
|
F test/rowid.test 128453599def7435e988216f7fe89c7450b8a9a3
|
||||||
F test/select1.test 824d9d5007dffd6a45edde79e89c0a04c36e3ebe
|
F test/select1.test 824d9d5007dffd6a45edde79e89c0a04c36e3ebe
|
||||||
@@ -97,7 +99,7 @@ F www/opcode.tcl cb3a1abf8b7b9be9f3a228d097d6bf8b742c2b6f
|
|||||||
F www/sqlite.tcl cb0d23d8f061a80543928755ec7775da6e4f362f
|
F www/sqlite.tcl cb0d23d8f061a80543928755ec7775da6e4f362f
|
||||||
F www/tclsqlite.tcl 06f81c401f79a04f2c5ebfb97e7c176225c0aef2
|
F www/tclsqlite.tcl 06f81c401f79a04f2c5ebfb97e7c176225c0aef2
|
||||||
F www/vdbe.tcl 0c8aaa529dd216ccbf7daaabd80985e413d5f9ad
|
F www/vdbe.tcl 0c8aaa529dd216ccbf7daaabd80985e413d5f9ad
|
||||||
P 2507ec40610d8034ccf9dcb58a16934065e6f120
|
P 86b30cd0975dfea9424b9f9f0d4194aa71ce508b
|
||||||
R 0329ee9c40e806471f7c325d51a23bb6
|
R 446c442a007e555210ed8cb182f64479
|
||||||
U drh
|
U drh
|
||||||
Z 598daf9af8c7873df8fb5f69d495d703
|
Z 9bc3b137c004e3bfdf49c47f3fac4875
|
||||||
|
@@ -1 +1 @@
|
|||||||
86b30cd0975dfea9424b9f9f0d4194aa71ce508b
|
555351dd1918f96167e2cb46cc1db6496e8d10a3
|
677
src/pager.c
Normal file
677
src/pager.c
Normal file
@@ -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 <fcntl.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <assert.h>
|
||||||
|
|
||||||
|
/*
|
||||||
|
** 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_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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 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; i<N_PG_HASH; i++){
|
||||||
|
PgHdr *pNext;
|
||||||
|
for(pPg=pPager->aHash[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->nPage<pPager->mxPage || 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; 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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;
|
||||||
|
};
|
69
test/malloc.test
Normal file
69
test/malloc.test
Normal file
@@ -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
|
Reference in New Issue
Block a user