Add experimental support for read-only connections to WAL databases.

FossilOrigin-Name: bb59f9862da45d25fb51d7821130854828c91c98
This commit is contained in:
dan 2011-05-10 17:31:29 +00:00
parent de941c3756
commit 4edc6bf3ee
8 changed files with 227 additions and 43 deletions

View File

@ -1,5 +1,5 @@
C Return\sa\ssuitable\serror\smessage\sif\sthe\smode=\sargument\sto\sa\sURI\sspecifies\na\shigher\smode\sthan\swhat\sis\sallowed\sby\scontext.\s\sOther\sminor\scleanups\sfor\nthe\sURI\sparsing\slogic.
D 2011-05-09T19:20:17.775
C Add\sexperimental\ssupport\sfor\sread-only\sconnections\sto\sWAL\sdatabases.
D 2011-05-10T17:31:29.338
F Makefile.arm-wince-mingw32ce-gcc d6df77f1f48d690bd73162294bbba7f59507c72f
F Makefile.in 7a4d9524721d40ef9ee26f93f9bd6a51dba106f2
F Makefile.linux-gcc 91d710bdc4998cb015f39edf3cb314ec4f4d7e23
@ -163,7 +163,7 @@ F src/os.c 22ac61d06e72a0dac900400147333b07b13d8e1d
F src/os.h 9dbed8c2b9c1f2f2ebabc09e49829d4777c26bf9
F src/os_common.h a8f95b81eca8a1ab8593d23e94f8a35f35d4078f
F src/os_os2.c 4a75888ba3dfc820ad5e8177025972d74d7f2440
F src/os_unix.c 2c67d126874b78eb427371db4793f0e8fbc7448b
F src/os_unix.c 03630dd062c3d1fb9f25e2a227048b709c5babff
F src/os_win.c ff0e14615a5086fa5ba6926e4ec0dc9cfb5a1a84
F src/pager.c 24b689bc3639d534f5fb292d2c68038b1e720527
F src/pager.h 3f8c783de1d4706b40b1ac15b64f5f896bcc78d1
@ -179,14 +179,14 @@ F src/resolve.c 1c0f32b64f8e3f555fe1f732f9d6f501a7f05706
F src/rowset.c 69afa95a97c524ba6faf3805e717b5b7ae85a697
F src/select.c d9d440809025a58547e39f4f268c2a296bfb56ff
F src/shell.c 72e7e176bf46d5c6518d15ac4ad6847c4bb5df79
F src/sqlite.h.in 41a0e4bc842917226e170273f64b95717a63270a
F src/sqlite.h.in e7bbcb330ced6b5e25c9db8089c2c77aaefadf7d
F src/sqlite3ext.h c90bd5507099f62043832d73f6425d8d5c5da754
F src/sqliteInt.h b34bd64a7ade4808fcc301e0bb67ef5051ea49c6
F src/sqliteLimit.h 164b0e6749d31e0daa1a4589a169d31c0dec7b3d
F src/status.c 7ac64842c86cec2fc1a1d0e5c16d3beb8ad332bf
F src/table.c 2cd62736f845d82200acfa1287e33feb3c15d62e
F src/tclsqlite.c 501c9a200fd998a268be475be5858febc90b725b
F src/test1.c f506164085bc4cbbb4b5c14b9c6de153f1aeafc5
F src/test1.c 6ae026cd9d2b1b1e95a372a7460d091705db645d
F src/test2.c 80d323d11e909cf0eb1b6fbb4ac22276483bcf31
F src/test3.c 124ff9735fb6bb7d41de180d6bac90e7b1509432
F src/test4.c d1e5a5e904d4b444cf572391fdcb017638e36ff7
@ -245,7 +245,7 @@ F src/vdbeblob.c c3ccb7c8732858c680f442932e66ad06bb036562
F src/vdbemem.c 0498796b6ffbe45e32960d6a1f5adfb6e419883b
F src/vdbetrace.c 5d0dc3d5fd54878cc8d6d28eb41deb8d5885b114
F src/vtab.c 48dcef8bc757c2e7b488f68b5ddebb1650da2450
F src/wal.c 7334009b396285b658a95a3b6bc6d2b016a1f794
F src/wal.c 2574b16ee88b1934e97f63ae38025dc48ef45327
F src/wal.h 7a5fbb00114b7f2cd40c7e1003d4c41ce9d26840
F src/walker.c 3112bb3afe1d85dc52317cb1d752055e9a781f8f
F src/where.c 55403ce19c506be6a321c7f129aff693d6103db5
@ -547,7 +547,7 @@ F test/lock4.test c82268c031d39345d05efa672f80b025481b3ae5
F test/lock5.test b2abb5e711bc59b0eae00f6c97a36ec9f458fada
F test/lock6.test ad5b387a3a8096afd3c68a55b9535056431b0cf5
F test/lock7.test 64006c84c1c616657e237c7ad6532b765611cf64
F test/lock_common.tcl d279887a0ab16cdb6d935c1203e64113c5a000e9
F test/lock_common.tcl 0c270b121d40959fa2f3add382200c27045b3d95
F test/lookaside.test 93f07bac140c5bb1d49f3892d2684decafdc7af2
F test/main.test 9d7bbfcc1b52c88ba7b2ba6554068ecf9939f252
F test/make-where7.tcl 05c16b5d4f5d6512881dfec560cb793915932ef9
@ -883,6 +883,7 @@ F test/walfault.test 58fce626359c9376fe35101b5c0f2df8040aa839
F test/walhook.test ed00a40ba7255da22d6b66433ab61fab16a63483
F test/walmode.test 22ddccd073c817ac9ead62b88ac446e8dedc7d2c
F test/walnoshm.test a074428046408f4eb5c6a00e09df8cc97ff93317
F test/walro.test 0fb4c79a9dfa1d14f46859e469d3b4844480cd9d
F test/walshared.test 6dda2293880c300baf5d791c307f653094585761
F test/walslow.test d21625e2e99e11c032ce949e8a94661576548933
F test/walthread.test a25a393c068a2b42b44333fa3fdaae9072f1617c
@ -935,7 +936,10 @@ F tool/speedtest8.c 2902c46588c40b55661e471d7a86e4dd71a18224
F tool/speedtest8inst1.c 293327bc76823f473684d589a8160bde1f52c14e
F tool/split-sqlite3c.tcl d9be87f1c340285a3e081eb19b4a247981ed290c
F tool/vdbe-compress.tcl d70ea6d8a19e3571d7ab8c9b75cba86d1173ff0f
P ca3797d4967361e31a8a5ce1ce8190b095f3ed4c
R fbfaa47316ae48b91e92f26cbe2f2742
U drh
Z 82e3dc394cc5052bfb9a5a6a21e4f298
P d9bc1c7fe0ca5f6973a85827330958f4d09f8171
R 864e9c4ee7a6cf5f8240415fb38d1af8
T *branch * wal-readonly
T *sym-wal-readonly *
T -sym-trunk *
U dan
Z 321776a9f8d852e48652ad186c3a479b

View File

@ -1 +1 @@
d9bc1c7fe0ca5f6973a85827330958f4d09f8171
bb59f9862da45d25fb51d7821130854828c91c98

View File

@ -212,6 +212,7 @@ struct unixFile {
UnixUnusedFd *pUnused; /* Pre-allocated UnixUnusedFd */
const char *zPath; /* Name of the file */
unixShm *pShm; /* Shared memory segment information */
int readOnlyShm; /* True to open shared-memory read-only */
int szChunk; /* Configured by FCNTL_CHUNK_SIZE */
#if SQLITE_ENABLE_LOCKING_STYLE
int openFlags; /* The flags specified at open() */
@ -3452,6 +3453,10 @@ static int unixFileControl(sqlite3_file *id, int op, void *pArg){
case SQLITE_FCNTL_SIZE_HINT: {
return fcntlSizeHint((unixFile *)id, *(i64 *)pArg);
}
case SQLITE_FCNTL_READONLY_SHM: {
((unixFile*)id)->readOnlyShm = (pArg!=0);
return SQLITE_OK;
}
#ifndef NDEBUG
/* The pager calls this method to signal that it has done
** a rollback and that the database is therefore unchanged and
@ -3541,6 +3546,7 @@ struct unixShmNode {
char **apRegion; /* Array of mapped shared-memory regions */
int nRef; /* Number of unixShm objects pointing to this */
unixShm *pFirst; /* All unixShm objects pointing to this */
u8 readOnly; /* True if this is a read-only mapping */
#ifdef SQLITE_DEBUG
u8 exclMask; /* Mask of exclusive locks held */
u8 sharedMask; /* Mask of shared locks held */
@ -3780,29 +3786,48 @@ static int unixOpenSharedMemory(unixFile *pDbFd){
}
if( pInode->bProcessLock==0 ){
pShmNode->h = robust_open(zShmFilename, O_RDWR|O_CREAT,
(sStat.st_mode & 0777));
int flags = (pDbFd->readOnlyShm ? O_RDONLY : O_RDWR|O_CREAT);
pShmNode->h = robust_open(zShmFilename, flags, (sStat.st_mode & 0777));
if( pShmNode->h<0 ){
rc = unixLogError(SQLITE_CANTOPEN_BKPT, "open", zShmFilename);
goto shm_open_err;
}
pShmNode->readOnly = pDbFd->readOnlyShm;
/* Check to see if another process is holding the dead-man switch.
** If not, truncate the file to zero length.
*/
rc = SQLITE_OK;
if( unixShmSystemLock(pShmNode, F_WRLCK, UNIX_SHM_DMS, 1)==SQLITE_OK ){
if( robust_ftruncate(pShmNode->h, 0) ){
rc = unixLogError(SQLITE_IOERR_SHMOPEN, "ftruncate", zShmFilename);
** If not, zero the first few bytes of the shared-memory file to make
** sure it is not mistaken for valid by code in wal.c. Except, if this
** is a read-only connection to the shared-memory then it is not possible
** to check check if another process is holding a read-lock on the DMS
** byte, as we cannot attempt a write-lock via a read-only file
** descriptor. So in this case, we just assume the shared-memory
** contents are Ok and proceed. */
if( pShmNode->readOnly==0 ){
rc = SQLITE_OK;
if( unixShmSystemLock(pShmNode, F_WRLCK, UNIX_SHM_DMS, 1)==SQLITE_OK ){
if( pDbFd->readOnlyShm ){
rc = SQLITE_IOERR_SHMOPEN;
}else if( 4!=osWrite(pShmNode->h, "\00\00\00\00", 4) ){
rc = unixLogError(SQLITE_IOERR_SHMOPEN, "ftruncate", zShmFilename);
}
}
if( rc==SQLITE_OK ){
rc = unixShmSystemLock(pShmNode, F_RDLCK, UNIX_SHM_DMS, 1);
}
if( rc ) goto shm_open_err;
}
if( rc==SQLITE_OK ){
rc = unixShmSystemLock(pShmNode, F_RDLCK, UNIX_SHM_DMS, 1);
}
if( rc ) goto shm_open_err;
}
}
/* If the unixShmNode is read-only, but SQLITE_FCNTL_READONLY_SHM has not
** been set for file-descriptor pDbFd, return an error. The wal.c module
** will then call this function again with SQLITE_FCNTL_READONLY_SHM set.
*/
else if( pShmNode->readOnly && !pDbFd->readOnlyShm ){
rc = SQLITE_IOERR_SHMOPEN;
goto shm_open_err;
}
/* Make the new connection a child of the unixShmNode */
p->pShmNode = pShmNode;
#ifdef SQLITE_DEBUG
@ -3923,7 +3948,7 @@ static int unixShmMap(
while(pShmNode->nRegion<=iRegion){
void *pMem;
if( pShmNode->h>=0 ){
pMem = mmap(0, szRegion, PROT_READ|PROT_WRITE,
pMem = mmap(0, szRegion, PROT_READ|(!pShmNode->readOnly?PROT_WRITE:0),
MAP_SHARED, pShmNode->h, pShmNode->nRegion*szRegion
);
if( pMem==MAP_FAILED ){

View File

@ -454,6 +454,9 @@ int sqlite3_exec(
#define SQLITE_BUSY_RECOVERY (SQLITE_BUSY | (1<<8))
#define SQLITE_CANTOPEN_NOTEMPDIR (SQLITE_CANTOPEN | (1<<8))
#define SQLITE_READONLY_RECOVERY (SQLITE_READONLY | (1<<8))
#define SQLITE_READONLY_CANTLOCK (SQLITE_READONLY | (2<<8))
/*
** CAPI3REF: Flags For File Open Operations
**
@ -742,6 +745,7 @@ struct sqlite3_io_methods {
#define SQLITE_FCNTL_CHUNK_SIZE 6
#define SQLITE_FCNTL_FILE_POINTER 7
#define SQLITE_FCNTL_SYNC_OMITTED 8
#define SQLITE_FCNTL_READONLY_SHM 9
/*

View File

@ -163,6 +163,9 @@ const char *sqlite3TestErrorName(int rc){
case SQLITE_IOERR_CHECKRESERVEDLOCK:
zName = "SQLITE_IOERR_CHECKRESERVEDLOCK"; break;
case SQLITE_IOERR_LOCK: zName = "SQLITE_IOERR_LOCK"; break;
case SQLITE_READONLY_RECOVERY: zName = "SQLITE_READONLY_RECOVERY"; break;
case SQLITE_READONLY_CANTLOCK: zName = "SQLITE_READONLY_CANTLOCK"; break;
default: zName = "SQLITE_Unknown"; break;
}
return zName;

View File

@ -420,6 +420,7 @@ struct Wal {
u8 writeLock; /* True if in a write transaction */
u8 ckptLock; /* True if holding a checkpoint lock */
u8 readOnly; /* True if the WAL file is open read-only */
u8 readOnlyShm; /* True if the SHM file is open read-only */
WalIndexHdr hdr; /* Wal-index header for current transaction */
const char *zWalName; /* Name of WAL file */
u32 nCkpt; /* Checkpoint sequence counter in the wal-header */
@ -528,6 +529,16 @@ static int walIndexPage(Wal *pWal, int iPage, volatile u32 **ppPage){
rc = sqlite3OsShmMap(pWal->pDbFd, iPage, WALINDEX_PGSZ,
pWal->writeLock, (void volatile **)&pWal->apWiData[iPage]
);
if( rc==SQLITE_CANTOPEN && iPage==0 ){
sqlite3OsFileControl(pWal->pDbFd, SQLITE_FCNTL_READONLY_SHM, (void*)1);
rc = sqlite3OsShmMap(pWal->pDbFd, iPage, WALINDEX_PGSZ,
pWal->writeLock, (void volatile **)&pWal->apWiData[iPage]
);
if( rc==SQLITE_OK ){
pWal->readOnly = pWal->readOnlyShm = 1;
}
sqlite3OsFileControl(pWal->pDbFd, SQLITE_FCNTL_READONLY_SHM, (void*)0);
}
}
}
@ -772,6 +783,7 @@ static void walUnlockShared(Wal *pWal, int lockIdx){
}
static int walLockExclusive(Wal *pWal, int lockIdx, int n){
int rc;
assert( pWal->readOnlyShm==0 );
if( pWal->exclusiveMode ) return SQLITE_OK;
rc = sqlite3OsShmLock(pWal->pDbFd, lockIdx, n,
SQLITE_SHM_LOCK | SQLITE_SHM_EXCLUSIVE);
@ -781,6 +793,7 @@ static int walLockExclusive(Wal *pWal, int lockIdx, int n){
return rc;
}
static void walUnlockExclusive(Wal *pWal, int lockIdx, int n){
assert( pWal->readOnlyShm==0 );
if( pWal->exclusiveMode ) return;
(void)sqlite3OsShmLock(pWal->pDbFd, lockIdx, n,
SQLITE_SHM_UNLOCK | SQLITE_SHM_EXCLUSIVE);
@ -1056,6 +1069,7 @@ static int walIndexRecover(Wal *pWal){
assert( WAL_ALL_BUT_WRITE==WAL_WRITE_LOCK+1 );
assert( WAL_CKPT_LOCK==WAL_ALL_BUT_WRITE );
assert( pWal->writeLock );
assert( pWal->readOnlyShm==0 );
iLock = WAL_ALL_BUT_WRITE + pWal->ckptLock;
nLock = SQLITE_SHM_NLOCK - iLock;
rc = walLockExclusive(pWal, iLock, nLock);
@ -1904,24 +1918,31 @@ static int walIndexReadHdr(Wal *pWal, int *pChanged){
badHdr = (page0 ? walIndexTryHdr(pWal, pChanged) : 1);
/* If the first attempt failed, it might have been due to a race
** with a writer. So get a WRITE lock and try again.
** with a writer. So lock the WAL_WRITE_LOCK byte and try again.
*/
assert( badHdr==0 || pWal->writeLock==0 );
if( badHdr && SQLITE_OK==(rc = walLockExclusive(pWal, WAL_WRITE_LOCK, 1)) ){
pWal->writeLock = 1;
if( SQLITE_OK==(rc = walIndexPage(pWal, 0, &page0)) ){
badHdr = walIndexTryHdr(pWal, pChanged);
if( badHdr ){
/* If the wal-index header is still malformed even while holding
** a WRITE lock, it can only mean that the header is corrupted and
** needs to be reconstructed. So run recovery to do exactly that.
*/
rc = walIndexRecover(pWal);
*pChanged = 1;
if( badHdr ){
if( pWal->readOnlyShm ){
if( SQLITE_OK==(rc = walLockShared(pWal, WAL_WRITE_LOCK)) ){
walUnlockShared(pWal, WAL_WRITE_LOCK);
rc = SQLITE_READONLY_RECOVERY;
}
}else if( SQLITE_OK==(rc = walLockExclusive(pWal, WAL_WRITE_LOCK, 1)) ){
pWal->writeLock = 1;
if( SQLITE_OK==(rc = walIndexPage(pWal, 0, &page0)) ){
badHdr = walIndexTryHdr(pWal, pChanged);
if( badHdr ){
/* If the wal-index header is still malformed even while holding
** a WRITE lock, it can only mean that the header is corrupted and
** needs to be reconstructed. So run recovery to do exactly that.
*/
rc = walIndexRecover(pWal);
*pChanged = 1;
}
}
pWal->writeLock = 0;
walUnlockExclusive(pWal, WAL_WRITE_LOCK, 1);
}
pWal->writeLock = 0;
walUnlockExclusive(pWal, WAL_WRITE_LOCK, 1);
}
/* If the header is read successfully, check the version number to make
@ -2108,7 +2129,7 @@ static int walTryBeginRead(Wal *pWal, int *pChanged, int useWal, int cnt){
}
/* There was once an "if" here. The extra "{" is to preserve indentation. */
{
if( mxReadMark < pWal->hdr.mxFrame || mxI==0 ){
if( pWal->readOnlyShm==0 && (mxReadMark < pWal->hdr.mxFrame || mxI==0) ){
for(i=1; i<WAL_NREADER; i++){
rc = walLockExclusive(pWal, WAL_READ_LOCK(i), 1);
if( rc==SQLITE_OK ){
@ -2123,7 +2144,8 @@ static int walTryBeginRead(Wal *pWal, int *pChanged, int useWal, int cnt){
}
if( mxI==0 ){
assert( rc==SQLITE_BUSY );
return WAL_RETRY;
assert( rc==SQLITE_BUSY || (pWal->readOnlyShm && rc==SQLITE_OK) );
return rc==SQLITE_BUSY ? WAL_RETRY : SQLITE_READONLY_CANTLOCK;
}
rc = walLockShared(pWal, WAL_READ_LOCK(mxI));
@ -2359,6 +2381,7 @@ int sqlite3WalBeginWriteTransaction(Wal *pWal){
if( pWal->readOnly ){
return SQLITE_READONLY;
}
assert( pWal->readOnlyShm==0 );
/* Only one writer allowed at a time. Get the write lock. Return
** SQLITE_BUSY if unable.
@ -2748,6 +2771,10 @@ int sqlite3WalCheckpoint(
assert( pWal->writeLock==0 );
WALTRACE(("WAL%p: checkpoint begins\n", pWal));
if( pWal->readOnlyShm ){
return SQLITE_READONLY;
}
rc = walLockExclusive(pWal, WAL_CKPT_LOCK, 1);
if( rc ){
/* Usually this is SQLITE_BUSY meaning that another thread or process

View File

@ -55,8 +55,8 @@ proc do_multiclient_test {varname script} {
uplevel set $varname $tn
uplevel $script
code2 { db2 close }
code3 { db3 close }
catch { code2 { db2 close } }
catch { code3 { db3 close } }
catch { close $::code2_chan }
catch { close $::code3_chan }
catch { db close }

121
test/walro.test Normal file
View File

@ -0,0 +1,121 @@
# 2011 May 09
#
# The author disclaims copyright to this source code. In place of
# a legal notice, here is a blessing:
#
# May you do good and not evil.
# May you find forgiveness for yourself and forgive others.
# May you share freely, never taking more than you give.
#
#***********************************************************************
#
# This file contains tests for using WAL databases in read-only mode.
#
set testdir [file dirname $argv0]
source $testdir/tester.tcl
source $testdir/lock_common.tcl
set ::testprefix walro
do_multiclient_test tn {
# These tests are only going to work on unix.
#
if {$tcl_platform(platform) != "unix"} continue
# Do not run tests with the connections in the same process.
#
if {$tn==2} continue
# Close all connections and delete the database.
#
code1 { db close }
code2 { db2 close }
code3 { db3 close }
forcedelete test.db
forcedelete walro
file mkdir walro
do_test 1.1.1 {
code2 { sqlite3 db2 test.db }
sql2 {
PRAGMA journal_mode = WAL;
CREATE TABLE t1(x, y);
INSERT INTO t1 VALUES('a', 'b');
}
file exists test.db-shm
} {1}
do_test 1.1.2 {
file attributes test.db-shm -permissions r--r--r--
code1 { sqlite3 db test.db }
} {}
do_test 1.1.3 { sql1 "SELECT * FROM t1" } {a b}
do_test 1.1.4 { sql2 "INSERT INTO t1 VALUES('c', 'd')" } {}
do_test 1.1.5 { sql1 "SELECT * FROM t1" } {a b c d}
# Check that the read-only connection cannot write or checkpoint the db.
#
do_test 1.1.6 {
csql1 "INSERT INTO t1 VALUES('e', 'f')"
} {1 {attempt to write a readonly database}}
do_test 1.1.7 {
csql1 "PRAGMA wal_checkpoint"
} {1 {attempt to write a readonly database}}
do_test 1.1.9 { sql2 "INSERT INTO t1 VALUES('e', 'f')" } {}
do_test 1.1.10 { sql1 "SELECT * FROM t1" } {a b c d e f}
do_test 1.1.11 {
sql2 {
INSERT INTO t1 VALUES('g', 'h');
PRAGMA wal_checkpoint;
}
set {} {}
} {}
do_test 1.1.12 { sql1 "SELECT * FROM t1" } {a b c d e f g h}
do_test 1.1.13 { sql2 "INSERT INTO t1 VALUES('i', 'j')" } {}
do_test 1.2.1 {
code2 { db2 close }
code1 { db close }
list [file exists test.db-wal] [file exists test.db-shm]
} {1 1}
do_test 1.2.2 {
code1 { sqlite3 db test.db }
sql1 { SELECT * FROM t1 }
} {a b c d e f g h i j}
do_test 1.2.3 {
code1 { db close }
file attributes test.db-shm -permissions rw-r--r--
hexio_write test.db-shm 0 01020304
file attributes test.db-shm -permissions r--r--r--
code1 { sqlite3 db test.db }
csql1 { SELECT * FROM t1 }
} {1 {attempt to write a readonly database}}
do_test 1.2.4 {
code1 { sqlite3_extended_errcode db }
} {SQLITE_READONLY_RECOVERY}
do_test 1.2.5 {
file attributes test.db-shm -permissions rw-r--r--
code2 { sqlite3 db2 test.db }
sql2 "SELECT * FROM t1"
} {a b c d e f g h i j}
file attributes test.db-shm -permissions r--r--r--
do_test 1.2.6 { sql1 "SELECT * FROM t1" } {a b c d e f g h i j}
do_test 1.2.7 {
sql2 {
PRAGMA wal_checkpoint;
INSERT INTO t1 VALUES('k', 'l');
}
set {} {}
} {}
do_test 1.2.8 { sql1 "SELECT * FROM t1" } {a b c d e f g h i j k l}
}
finish_test