]> git.ipfire.org Git - thirdparty/sqlite.git/commitdiff
Add experimental support for read-only connections to WAL databases.
authordan <dan@noemail.net>
Tue, 10 May 2011 17:31:29 +0000 (17:31 +0000)
committerdan <dan@noemail.net>
Tue, 10 May 2011 17:31:29 +0000 (17:31 +0000)
FossilOrigin-Name: bb59f9862da45d25fb51d7821130854828c91c98

manifest
manifest.uuid
src/os_unix.c
src/sqlite.h.in
src/test1.c
src/wal.c
test/lock_common.tcl
test/walro.test [new file with mode: 0644]

index daee9754e7ca84bf66336f646b976d8988f62c36..50889411e104d996a9844b7ed2227d35f2707858 100644 (file)
--- a/manifest
+++ b/manifest
@@ -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
index 2b8787f1e24fc482e0434e47db04d1c225b3d0c5..e39ce5418fb24cf74a3f1f24fbb885e946156e42 100644 (file)
@@ -1 +1 @@
-d9bc1c7fe0ca5f6973a85827330958f4d09f8171
\ No newline at end of file
+bb59f9862da45d25fb51d7821130854828c91c98
\ No newline at end of file
index a760e2c1473033e8e8ae6bd25efa2c8130cf9542..d463e5917f01170c94f646819fcf046ebcb5f05a 100644 (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 ){
index d78daa76728393d66503c304cd0c8324fe4f9a4b..af88e4e726485b58c375e893a7895f1ffee3dc97 100644 (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
 
 
 /*
index 8e3012343341b6521eb54aa47e487d44602a364e..c25fd2289db4ad954d0014215ed13ccbb9c42249 100644 (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;
index 51ea18fb21677476c9f4ac626c41c1b1a8ef6267..707fe12976c3fbdab4695d4cf1e0b6044beb6acf 100644 (file)
--- a/src/wal.c
+++ b/src/wal.c
@@ -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
index 21f584264a8a0cefd6074ca8558c2912e4efd4b3..bc1eb86bdc2b8e4757431a51fe4b3b19fab1687f 100644 (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 }
diff --git a/test/walro.test b/test/walro.test
new file mode 100644 (file)
index 0000000..b558622
--- /dev/null
@@ -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