-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
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
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
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
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
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
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
-d9bc1c7fe0ca5f6973a85827330958f4d09f8171
\ No newline at end of file
+bb59f9862da45d25fb51d7821130854828c91c98
\ No newline at end of file
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() */
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
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 */
}
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
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 ){
#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
**
#define SQLITE_FCNTL_CHUNK_SIZE 6
#define SQLITE_FCNTL_FILE_POINTER 7
#define SQLITE_FCNTL_SYNC_OMITTED 8
+#define SQLITE_FCNTL_READONLY_SHM 9
/*
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;
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 */
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);
+ }
}
}
}
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);
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);
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);
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
}
/* 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 ){
}
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));
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.
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
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 }
--- /dev/null
+# 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