/ Check-in [bb59f986]
Login
SQLite training in Houston TX on 2019-11-05 (details)
Part of the 2019 Tcl Conference

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Overview
Comment:Add experimental support for read-only connections to WAL databases.
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | wal-readonly
Files: files | file ages | folders
SHA1: bb59f9862da45d25fb51d7821130854828c91c98
User & Date: dan 2011-05-10 17:31:29
Context
2011-05-11
14:57
Only open a read-only connection to shared-memory if the "readonly_shm=1" option is specified as part of the database file URI (and if a read-write connection fails). check-in: 671ba5fc user: dan tags: wal-readonly
2011-05-10
17:31
Add experimental support for read-only connections to WAL databases. check-in: bb59f986 user: dan tags: wal-readonly
2011-05-09
19:20
Return a suitable error message if the mode= argument to a URI specifies a higher mode than what is allowed by context. Other minor cleanups for the URI parsing logic. check-in: d9bc1c7f user: drh tags: trunk
Changes
Hide Diffs Side-by-Side Diffs Ignore Whitespace Patch

Changes to src/os_unix.c.

   208    208     unsigned char eFileLock;            /* The type of lock held on this fd */
   209    209     unsigned char ctrlFlags;            /* Behavioral bits.  UNIXFILE_* flags */
   210    210     int lastErrno;                      /* The unix errno from last I/O error */
   211    211     void *lockingContext;               /* Locking style specific state */
   212    212     UnixUnusedFd *pUnused;              /* Pre-allocated UnixUnusedFd */
   213    213     const char *zPath;                  /* Name of the file */
   214    214     unixShm *pShm;                      /* Shared memory segment information */
          215  +  int readOnlyShm;                    /* True to open shared-memory read-only */
   215    216     int szChunk;                        /* Configured by FCNTL_CHUNK_SIZE */
   216    217   #if SQLITE_ENABLE_LOCKING_STYLE
   217    218     int openFlags;                      /* The flags specified at open() */
   218    219   #endif
   219    220   #if SQLITE_ENABLE_LOCKING_STYLE || defined(__APPLE__)
   220    221     unsigned fsFlags;                   /* cached details from statfs() */
   221    222   #endif
................................................................................
  3448   3449       case SQLITE_FCNTL_CHUNK_SIZE: {
  3449   3450         ((unixFile*)id)->szChunk = *(int *)pArg;
  3450   3451         return SQLITE_OK;
  3451   3452       }
  3452   3453       case SQLITE_FCNTL_SIZE_HINT: {
  3453   3454         return fcntlSizeHint((unixFile *)id, *(i64 *)pArg);
  3454   3455       }
         3456  +    case SQLITE_FCNTL_READONLY_SHM: {
         3457  +      ((unixFile*)id)->readOnlyShm = (pArg!=0);
         3458  +      return SQLITE_OK;
         3459  +    }
  3455   3460   #ifndef NDEBUG
  3456   3461       /* The pager calls this method to signal that it has done
  3457   3462       ** a rollback and that the database is therefore unchanged and
  3458   3463       ** it hence it is OK for the transaction change counter to be
  3459   3464       ** unchanged.
  3460   3465       */
  3461   3466       case SQLITE_FCNTL_DB_UNCHANGED: {
................................................................................
  3537   3542     char *zFilename;           /* Name of the mmapped file */
  3538   3543     int h;                     /* Open file descriptor */
  3539   3544     int szRegion;              /* Size of shared-memory regions */
  3540   3545     int nRegion;               /* Size of array apRegion */
  3541   3546     char **apRegion;           /* Array of mapped shared-memory regions */
  3542   3547     int nRef;                  /* Number of unixShm objects pointing to this */
  3543   3548     unixShm *pFirst;           /* All unixShm objects pointing to this */
         3549  +  u8 readOnly;               /* True if this is a read-only mapping */
  3544   3550   #ifdef SQLITE_DEBUG
  3545   3551     u8 exclMask;               /* Mask of exclusive locks held */
  3546   3552     u8 sharedMask;             /* Mask of shared locks held */
  3547   3553     u8 nextShmId;              /* Next available unixShm.id value */
  3548   3554   #endif
  3549   3555   };
  3550   3556   
................................................................................
  3776   3782       pShmNode->mutex = sqlite3_mutex_alloc(SQLITE_MUTEX_FAST);
  3777   3783       if( pShmNode->mutex==0 ){
  3778   3784         rc = SQLITE_NOMEM;
  3779   3785         goto shm_open_err;
  3780   3786       }
  3781   3787   
  3782   3788       if( pInode->bProcessLock==0 ){
  3783         -      pShmNode->h = robust_open(zShmFilename, O_RDWR|O_CREAT,
  3784         -                               (sStat.st_mode & 0777));
         3789  +      int flags = (pDbFd->readOnlyShm ? O_RDONLY : O_RDWR|O_CREAT);
         3790  +      pShmNode->h = robust_open(zShmFilename, flags, (sStat.st_mode & 0777));
  3785   3791         if( pShmNode->h<0 ){
  3786   3792           rc = unixLogError(SQLITE_CANTOPEN_BKPT, "open", zShmFilename);
  3787   3793           goto shm_open_err;
  3788   3794         }
         3795  +      pShmNode->readOnly = pDbFd->readOnlyShm;
  3789   3796     
  3790   3797         /* Check to see if another process is holding the dead-man switch.
  3791         -      ** If not, truncate the file to zero length. 
  3792         -      */
  3793         -      rc = SQLITE_OK;
  3794         -      if( unixShmSystemLock(pShmNode, F_WRLCK, UNIX_SHM_DMS, 1)==SQLITE_OK ){
  3795         -        if( robust_ftruncate(pShmNode->h, 0) ){
  3796         -          rc = unixLogError(SQLITE_IOERR_SHMOPEN, "ftruncate", zShmFilename);
  3797         -        }
  3798         -      }
  3799         -      if( rc==SQLITE_OK ){
  3800         -        rc = unixShmSystemLock(pShmNode, F_RDLCK, UNIX_SHM_DMS, 1);
  3801         -      }
  3802         -      if( rc ) goto shm_open_err;
  3803         -    }
         3798  +      ** If not, zero the first few bytes of the shared-memory file to make
         3799  +      ** sure it is not mistaken for valid by code in wal.c. Except, if this 
         3800  +      ** is a read-only connection to the shared-memory then it is not possible
         3801  +      ** to check check if another process is holding a read-lock on the DMS
         3802  +      ** byte, as we cannot attempt a write-lock via a read-only file 
         3803  +      ** descriptor. So in this case, we just assume the shared-memory 
         3804  +      ** contents are Ok and proceed.  */
         3805  +      if( pShmNode->readOnly==0 ){
         3806  +        rc = SQLITE_OK;
         3807  +        if( unixShmSystemLock(pShmNode, F_WRLCK, UNIX_SHM_DMS, 1)==SQLITE_OK ){
         3808  +          if( pDbFd->readOnlyShm ){
         3809  +            rc = SQLITE_IOERR_SHMOPEN;
         3810  +          }else if( 4!=osWrite(pShmNode->h, "\00\00\00\00", 4) ){
         3811  +            rc = unixLogError(SQLITE_IOERR_SHMOPEN, "ftruncate", zShmFilename);
         3812  +          }
         3813  +        }
         3814  +        if( rc==SQLITE_OK ){
         3815  +          rc = unixShmSystemLock(pShmNode, F_RDLCK, UNIX_SHM_DMS, 1);
         3816  +        }
         3817  +        if( rc ) goto shm_open_err;
         3818  +      }
         3819  +    }
         3820  +  }
         3821  +
         3822  +  /* If the unixShmNode is read-only, but SQLITE_FCNTL_READONLY_SHM has not
         3823  +  ** been set for file-descriptor pDbFd, return an error. The wal.c module
         3824  +  ** will then call this function again with SQLITE_FCNTL_READONLY_SHM set.
         3825  +  */
         3826  +  else if( pShmNode->readOnly && !pDbFd->readOnlyShm ){
         3827  +    rc = SQLITE_IOERR_SHMOPEN;
         3828  +    goto shm_open_err;
  3804   3829     }
  3805   3830   
  3806   3831     /* Make the new connection a child of the unixShmNode */
  3807   3832     p->pShmNode = pShmNode;
  3808   3833   #ifdef SQLITE_DEBUG
  3809   3834     p->id = pShmNode->nextShmId++;
  3810   3835   #endif
................................................................................
  3919   3944         rc = SQLITE_IOERR_NOMEM;
  3920   3945         goto shmpage_out;
  3921   3946       }
  3922   3947       pShmNode->apRegion = apNew;
  3923   3948       while(pShmNode->nRegion<=iRegion){
  3924   3949         void *pMem;
  3925   3950         if( pShmNode->h>=0 ){
  3926         -        pMem = mmap(0, szRegion, PROT_READ|PROT_WRITE, 
         3951  +        pMem = mmap(0, szRegion, PROT_READ|(!pShmNode->readOnly?PROT_WRITE:0), 
  3927   3952               MAP_SHARED, pShmNode->h, pShmNode->nRegion*szRegion
  3928   3953           );
  3929   3954           if( pMem==MAP_FAILED ){
  3930   3955             rc = unixLogError(SQLITE_IOERR_SHMMAP, "mmap", pShmNode->zFilename);
  3931   3956             goto shmpage_out;
  3932   3957           }
  3933   3958         }else{

Changes to src/sqlite.h.in.

   450    450   #define SQLITE_IOERR_SHMLOCK           (SQLITE_IOERR | (20<<8))
   451    451   #define SQLITE_IOERR_SHMMAP            (SQLITE_IOERR | (21<<8))
   452    452   #define SQLITE_IOERR_SEEK              (SQLITE_IOERR | (22<<8))
   453    453   #define SQLITE_LOCKED_SHAREDCACHE      (SQLITE_LOCKED |  (1<<8))
   454    454   #define SQLITE_BUSY_RECOVERY           (SQLITE_BUSY   |  (1<<8))
   455    455   #define SQLITE_CANTOPEN_NOTEMPDIR      (SQLITE_CANTOPEN | (1<<8))
   456    456   
          457  +#define SQLITE_READONLY_RECOVERY       (SQLITE_READONLY | (1<<8))
          458  +#define SQLITE_READONLY_CANTLOCK       (SQLITE_READONLY | (2<<8))
          459  +
   457    460   /*
   458    461   ** CAPI3REF: Flags For File Open Operations
   459    462   **
   460    463   ** These bit values are intended for use in the
   461    464   ** 3rd parameter to the [sqlite3_open_v2()] interface and
   462    465   ** in the 4th parameter to the xOpen method of the
   463    466   ** [sqlite3_vfs] object.
................................................................................
   738    741   #define SQLITE_GET_LOCKPROXYFILE      2
   739    742   #define SQLITE_SET_LOCKPROXYFILE      3
   740    743   #define SQLITE_LAST_ERRNO             4
   741    744   #define SQLITE_FCNTL_SIZE_HINT        5
   742    745   #define SQLITE_FCNTL_CHUNK_SIZE       6
   743    746   #define SQLITE_FCNTL_FILE_POINTER     7
   744    747   #define SQLITE_FCNTL_SYNC_OMITTED     8
          748  +#define SQLITE_FCNTL_READONLY_SHM     9
   745    749   
   746    750   
   747    751   /*
   748    752   ** CAPI3REF: Mutex Handle
   749    753   **
   750    754   ** The mutex module within SQLite defines [sqlite3_mutex] to be an
   751    755   ** abstract type for a mutex object.  The SQLite core never looks

Changes to src/test1.c.

   159    159       case SQLITE_IOERR_DELETE:        zName = "SQLITE_IOERR_DELETE";      break;
   160    160       case SQLITE_IOERR_BLOCKED:       zName = "SQLITE_IOERR_BLOCKED";     break;
   161    161       case SQLITE_IOERR_NOMEM:         zName = "SQLITE_IOERR_NOMEM";       break;
   162    162       case SQLITE_IOERR_ACCESS:        zName = "SQLITE_IOERR_ACCESS";      break;
   163    163       case SQLITE_IOERR_CHECKRESERVEDLOCK:
   164    164                                  zName = "SQLITE_IOERR_CHECKRESERVEDLOCK"; break;
   165    165       case SQLITE_IOERR_LOCK:          zName = "SQLITE_IOERR_LOCK";        break;
          166  +
          167  +    case SQLITE_READONLY_RECOVERY:   zName = "SQLITE_READONLY_RECOVERY"; break;
          168  +    case SQLITE_READONLY_CANTLOCK:   zName = "SQLITE_READONLY_CANTLOCK"; break;
   166    169       default:                         zName = "SQLITE_Unknown";           break;
   167    170     }
   168    171     return zName;
   169    172   }
   170    173   #define t1ErrorName sqlite3TestErrorName
   171    174   
   172    175   /*

Changes to src/wal.c.

   416    416     volatile u32 **apWiData;   /* Pointer to wal-index content in memory */
   417    417     u32 szPage;                /* Database page size */
   418    418     i16 readLock;              /* Which read lock is being held.  -1 for none */
   419    419     u8 exclusiveMode;          /* Non-zero if connection is in exclusive mode */
   420    420     u8 writeLock;              /* True if in a write transaction */
   421    421     u8 ckptLock;               /* True if holding a checkpoint lock */
   422    422     u8 readOnly;               /* True if the WAL file is open read-only */
          423  +  u8 readOnlyShm;            /* True if the SHM file is open read-only */
   423    424     WalIndexHdr hdr;           /* Wal-index header for current transaction */
   424    425     const char *zWalName;      /* Name of WAL file */
   425    426     u32 nCkpt;                 /* Checkpoint sequence counter in the wal-header */
   426    427   #ifdef SQLITE_DEBUG
   427    428     u8 lockError;              /* True if a locking error has occurred */
   428    429   #endif
   429    430   };
................................................................................
   524    525       if( pWal->exclusiveMode==WAL_HEAPMEMORY_MODE ){
   525    526         pWal->apWiData[iPage] = (u32 volatile *)sqlite3MallocZero(WALINDEX_PGSZ);
   526    527         if( !pWal->apWiData[iPage] ) rc = SQLITE_NOMEM;
   527    528       }else{
   528    529         rc = sqlite3OsShmMap(pWal->pDbFd, iPage, WALINDEX_PGSZ, 
   529    530             pWal->writeLock, (void volatile **)&pWal->apWiData[iPage]
   530    531         );
          532  +      if( rc==SQLITE_CANTOPEN && iPage==0 ){
          533  +        sqlite3OsFileControl(pWal->pDbFd, SQLITE_FCNTL_READONLY_SHM, (void*)1);
          534  +        rc = sqlite3OsShmMap(pWal->pDbFd, iPage, WALINDEX_PGSZ, 
          535  +            pWal->writeLock, (void volatile **)&pWal->apWiData[iPage]
          536  +        );
          537  +        if( rc==SQLITE_OK ){
          538  +          pWal->readOnly = pWal->readOnlyShm = 1;
          539  +        }
          540  +        sqlite3OsFileControl(pWal->pDbFd, SQLITE_FCNTL_READONLY_SHM, (void*)0);
          541  +      }
   531    542       }
   532    543     }
   533    544   
   534    545     *ppPage = pWal->apWiData[iPage];
   535    546     assert( iPage==0 || *ppPage || rc!=SQLITE_OK );
   536    547     return rc;
   537    548   }
................................................................................
   768    779     if( pWal->exclusiveMode ) return;
   769    780     (void)sqlite3OsShmLock(pWal->pDbFd, lockIdx, 1,
   770    781                            SQLITE_SHM_UNLOCK | SQLITE_SHM_SHARED);
   771    782     WALTRACE(("WAL%p: release SHARED-%s\n", pWal, walLockName(lockIdx)));
   772    783   }
   773    784   static int walLockExclusive(Wal *pWal, int lockIdx, int n){
   774    785     int rc;
          786  +  assert( pWal->readOnlyShm==0 );
   775    787     if( pWal->exclusiveMode ) return SQLITE_OK;
   776    788     rc = sqlite3OsShmLock(pWal->pDbFd, lockIdx, n,
   777    789                           SQLITE_SHM_LOCK | SQLITE_SHM_EXCLUSIVE);
   778    790     WALTRACE(("WAL%p: acquire EXCLUSIVE-%s cnt=%d %s\n", pWal,
   779    791               walLockName(lockIdx), n, rc ? "failed" : "ok"));
   780    792     VVA_ONLY( pWal->lockError = (u8)(rc!=SQLITE_OK && rc!=SQLITE_BUSY); )
   781    793     return rc;
   782    794   }
   783    795   static void walUnlockExclusive(Wal *pWal, int lockIdx, int n){
          796  +  assert( pWal->readOnlyShm==0 );
   784    797     if( pWal->exclusiveMode ) return;
   785    798     (void)sqlite3OsShmLock(pWal->pDbFd, lockIdx, n,
   786    799                            SQLITE_SHM_UNLOCK | SQLITE_SHM_EXCLUSIVE);
   787    800     WALTRACE(("WAL%p: release EXCLUSIVE-%s cnt=%d\n", pWal,
   788    801                walLockName(lockIdx), n));
   789    802   }
   790    803   
................................................................................
  1052   1065     ** If successful, the same bytes that are locked here are unlocked before
  1053   1066     ** this function returns.
  1054   1067     */
  1055   1068     assert( pWal->ckptLock==1 || pWal->ckptLock==0 );
  1056   1069     assert( WAL_ALL_BUT_WRITE==WAL_WRITE_LOCK+1 );
  1057   1070     assert( WAL_CKPT_LOCK==WAL_ALL_BUT_WRITE );
  1058   1071     assert( pWal->writeLock );
         1072  +  assert( pWal->readOnlyShm==0 );
  1059   1073     iLock = WAL_ALL_BUT_WRITE + pWal->ckptLock;
  1060   1074     nLock = SQLITE_SHM_NLOCK - iLock;
  1061   1075     rc = walLockExclusive(pWal, iLock, nLock);
  1062   1076     if( rc ){
  1063   1077       return rc;
  1064   1078     }
  1065   1079     WALTRACE(("WAL%p: recovery begin...\n", pWal));
................................................................................
  1900   1914     ** wal-index header immediately, without holding any lock. This usually
  1901   1915     ** works, but may fail if the wal-index header is corrupt or currently 
  1902   1916     ** being modified by another thread or process.
  1903   1917     */
  1904   1918     badHdr = (page0 ? walIndexTryHdr(pWal, pChanged) : 1);
  1905   1919   
  1906   1920     /* If the first attempt failed, it might have been due to a race
  1907         -  ** with a writer.  So get a WRITE lock and try again.
         1921  +  ** with a writer. So lock the WAL_WRITE_LOCK byte and try again.
  1908   1922     */
  1909   1923     assert( badHdr==0 || pWal->writeLock==0 );
  1910         -  if( badHdr && SQLITE_OK==(rc = walLockExclusive(pWal, WAL_WRITE_LOCK, 1)) ){
  1911         -    pWal->writeLock = 1;
  1912         -    if( SQLITE_OK==(rc = walIndexPage(pWal, 0, &page0)) ){
  1913         -      badHdr = walIndexTryHdr(pWal, pChanged);
  1914         -      if( badHdr ){
  1915         -        /* If the wal-index header is still malformed even while holding
  1916         -        ** a WRITE lock, it can only mean that the header is corrupted and
  1917         -        ** needs to be reconstructed.  So run recovery to do exactly that.
  1918         -        */
  1919         -        rc = walIndexRecover(pWal);
  1920         -        *pChanged = 1;
         1924  +  if( badHdr ){
         1925  +    if( pWal->readOnlyShm ){
         1926  +      if( SQLITE_OK==(rc = walLockShared(pWal, WAL_WRITE_LOCK)) ){
         1927  +        walUnlockShared(pWal, WAL_WRITE_LOCK);
         1928  +        rc = SQLITE_READONLY_RECOVERY;
         1929  +      }
         1930  +    }else if( SQLITE_OK==(rc = walLockExclusive(pWal, WAL_WRITE_LOCK, 1)) ){
         1931  +      pWal->writeLock = 1;
         1932  +      if( SQLITE_OK==(rc = walIndexPage(pWal, 0, &page0)) ){
         1933  +        badHdr = walIndexTryHdr(pWal, pChanged);
         1934  +        if( badHdr ){
         1935  +          /* If the wal-index header is still malformed even while holding
         1936  +          ** a WRITE lock, it can only mean that the header is corrupted and
         1937  +          ** needs to be reconstructed.  So run recovery to do exactly that.
         1938  +          */
         1939  +          rc = walIndexRecover(pWal);
         1940  +          *pChanged = 1;
         1941  +        }
  1921   1942         }
         1943  +      pWal->writeLock = 0;
         1944  +      walUnlockExclusive(pWal, WAL_WRITE_LOCK, 1);
  1922   1945       }
  1923         -    pWal->writeLock = 0;
  1924         -    walUnlockExclusive(pWal, WAL_WRITE_LOCK, 1);
  1925   1946     }
  1926   1947   
  1927   1948     /* If the header is read successfully, check the version number to make
  1928   1949     ** sure the wal-index was not constructed with some future format that
  1929   1950     ** this version of SQLite cannot understand.
  1930   1951     */
  1931   1952     if( badHdr==0 && pWal->hdr.iVersion!=WALINDEX_MAX_VERSION ){
................................................................................
  2104   2125         assert( thisMark!=READMARK_NOT_USED );
  2105   2126         mxReadMark = thisMark;
  2106   2127         mxI = i;
  2107   2128       }
  2108   2129     }
  2109   2130     /* There was once an "if" here. The extra "{" is to preserve indentation. */
  2110   2131     {
  2111         -    if( mxReadMark < pWal->hdr.mxFrame || mxI==0 ){
         2132  +    if( pWal->readOnlyShm==0 && (mxReadMark < pWal->hdr.mxFrame || mxI==0) ){
  2112   2133         for(i=1; i<WAL_NREADER; i++){
  2113   2134           rc = walLockExclusive(pWal, WAL_READ_LOCK(i), 1);
  2114   2135           if( rc==SQLITE_OK ){
  2115   2136             mxReadMark = pInfo->aReadMark[i] = pWal->hdr.mxFrame;
  2116   2137             mxI = i;
  2117   2138             walUnlockExclusive(pWal, WAL_READ_LOCK(i), 1);
  2118   2139             break;
................................................................................
  2119   2140           }else if( rc!=SQLITE_BUSY ){
  2120   2141             return rc;
  2121   2142           }
  2122   2143         }
  2123   2144       }
  2124   2145       if( mxI==0 ){
  2125   2146         assert( rc==SQLITE_BUSY );
  2126         -      return WAL_RETRY;
         2147  +      assert( rc==SQLITE_BUSY || (pWal->readOnlyShm && rc==SQLITE_OK) );
         2148  +      return rc==SQLITE_BUSY ? WAL_RETRY : SQLITE_READONLY_CANTLOCK;
  2127   2149       }
  2128   2150   
  2129   2151       rc = walLockShared(pWal, WAL_READ_LOCK(mxI));
  2130   2152       if( rc ){
  2131   2153         return rc==SQLITE_BUSY ? WAL_RETRY : rc;
  2132   2154       }
  2133   2155       /* Now that the read-lock has been obtained, check that neither the
................................................................................
  2355   2377     /* Cannot start a write transaction without first holding a read
  2356   2378     ** transaction. */
  2357   2379     assert( pWal->readLock>=0 );
  2358   2380   
  2359   2381     if( pWal->readOnly ){
  2360   2382       return SQLITE_READONLY;
  2361   2383     }
         2384  +  assert( pWal->readOnlyShm==0 );
  2362   2385   
  2363   2386     /* Only one writer allowed at a time.  Get the write lock.  Return
  2364   2387     ** SQLITE_BUSY if unable.
  2365   2388     */
  2366   2389     rc = walLockExclusive(pWal, WAL_WRITE_LOCK, 1);
  2367   2390     if( rc ){
  2368   2391       return rc;
................................................................................
  2744   2767     int isChanged = 0;              /* True if a new wal-index header is loaded */
  2745   2768     int eMode2 = eMode;             /* Mode to pass to walCheckpoint() */
  2746   2769   
  2747   2770     assert( pWal->ckptLock==0 );
  2748   2771     assert( pWal->writeLock==0 );
  2749   2772   
  2750   2773     WALTRACE(("WAL%p: checkpoint begins\n", pWal));
         2774  +  if( pWal->readOnlyShm ){
         2775  +    return SQLITE_READONLY;
         2776  +  }
         2777  +
  2751   2778     rc = walLockExclusive(pWal, WAL_CKPT_LOCK, 1);
  2752   2779     if( rc ){
  2753   2780       /* Usually this is SQLITE_BUSY meaning that another thread or process
  2754   2781       ** is already running a checkpoint, or maybe a recovery.  But it might
  2755   2782       ** also be SQLITE_IOERR. */
  2756   2783       return rc;
  2757   2784     }

Changes to test/lock_common.tcl.

    51     51       proc csql1 {sql} { list [catch { sql1 $sql } msg] $msg }
    52     52       proc csql2 {sql} { list [catch { sql2 $sql } msg] $msg }
    53     53       proc csql3 {sql} { list [catch { sql3 $sql } msg] $msg }
    54     54   
    55     55       uplevel set $varname $tn
    56     56       uplevel $script
    57     57   
    58         -    code2 { db2 close }
    59         -    code3 { db3 close }
           58  +    catch { code2 { db2 close } }
           59  +    catch { code3 { db3 close } }
    60     60       catch { close $::code2_chan }
    61     61       catch { close $::code3_chan }
    62     62       catch { db close }
    63     63     }
    64     64   }
    65     65   
    66     66   # Launch another testfixture process to be controlled by this one. A

Added test/walro.test.

            1  +# 2011 May 09
            2  +#
            3  +# The author disclaims copyright to this source code.  In place of
            4  +# a legal notice, here is a blessing:
            5  +#
            6  +#    May you do good and not evil.
            7  +#    May you find forgiveness for yourself and forgive others.
            8  +#    May you share freely, never taking more than you give.
            9  +#
           10  +#***********************************************************************
           11  +#
           12  +# This file contains tests for using WAL databases in read-only mode.
           13  +#
           14  +
           15  +set testdir [file dirname $argv0]
           16  +source $testdir/tester.tcl
           17  +source $testdir/lock_common.tcl
           18  +set ::testprefix walro
           19  +
           20  +
           21  +do_multiclient_test tn {
           22  +  # These tests are only going to work on unix.
           23  +  #
           24  +  if {$tcl_platform(platform) != "unix"} continue
           25  +
           26  +  # Do not run tests with the connections in the same process.
           27  +  #
           28  +  if {$tn==2} continue
           29  +  
           30  +  # Close all connections and delete the database.
           31  +  #
           32  +  code1 { db close  }
           33  +  code2 { db2 close }
           34  +  code3 { db3 close }
           35  +  forcedelete test.db
           36  +  forcedelete walro
           37  +
           38  +  file mkdir walro
           39  +
           40  +  do_test 1.1.1 {
           41  +    code2 { sqlite3 db2 test.db }
           42  +    sql2 { 
           43  +      PRAGMA journal_mode = WAL;
           44  +      CREATE TABLE t1(x, y);
           45  +      INSERT INTO t1 VALUES('a', 'b');
           46  +    }
           47  +    file exists test.db-shm
           48  +  } {1}
           49  +
           50  +  do_test 1.1.2 {
           51  +    file attributes test.db-shm -permissions r--r--r--
           52  +    code1 { sqlite3 db test.db }
           53  +  } {}
           54  +
           55  +  do_test 1.1.3 { sql1 "SELECT * FROM t1" }                {a b}
           56  +  do_test 1.1.4 { sql2 "INSERT INTO t1 VALUES('c', 'd')" } {}
           57  +  do_test 1.1.5 { sql1 "SELECT * FROM t1" }                {a b c d}
           58  +
           59  +  # Check that the read-only connection cannot write or checkpoint the db.
           60  +  #
           61  +  do_test 1.1.6 { 
           62  +    csql1 "INSERT INTO t1 VALUES('e', 'f')" 
           63  +  } {1 {attempt to write a readonly database}}
           64  +  do_test 1.1.7 { 
           65  +    csql1 "PRAGMA wal_checkpoint"
           66  +  } {1 {attempt to write a readonly database}}
           67  +
           68  +  do_test 1.1.9  { sql2 "INSERT INTO t1 VALUES('e', 'f')" } {}
           69  +  do_test 1.1.10 { sql1 "SELECT * FROM t1" }                {a b c d e f}
           70  +
           71  +  do_test 1.1.11 { 
           72  +    sql2 {
           73  +      INSERT INTO t1 VALUES('g', 'h');
           74  +      PRAGMA wal_checkpoint;
           75  +    }
           76  +    set {} {}
           77  +  } {}
           78  +  do_test 1.1.12 { sql1 "SELECT * FROM t1" }                {a b c d e f g h}
           79  +  do_test 1.1.13  { sql2 "INSERT INTO t1 VALUES('i', 'j')" } {}
           80  +
           81  +  do_test 1.2.1 {
           82  +    code2 { db2 close }
           83  +    code1 { db close }
           84  +    list [file exists test.db-wal] [file exists test.db-shm]
           85  +  } {1 1}
           86  +  do_test 1.2.2 {
           87  +    code1 { sqlite3 db test.db }
           88  +    sql1 { SELECT * FROM t1 }
           89  +  } {a b c d e f g h i j}
           90  +
           91  +  do_test 1.2.3 {
           92  +    code1 { db close }
           93  +    file attributes test.db-shm -permissions rw-r--r--
           94  +    hexio_write test.db-shm 0 01020304 
           95  +    file attributes test.db-shm -permissions r--r--r--
           96  +    code1 { sqlite3 db test.db }
           97  +    csql1 { SELECT * FROM t1 }
           98  +  } {1 {attempt to write a readonly database}}
           99  +  do_test 1.2.4 {
          100  +    code1 { sqlite3_extended_errcode db } 
          101  +  } {SQLITE_READONLY_RECOVERY}
          102  +
          103  +  do_test 1.2.5 {
          104  +    file attributes test.db-shm -permissions rw-r--r--
          105  +    code2 { sqlite3 db2 test.db }
          106  +    sql2 "SELECT * FROM t1" 
          107  +  } {a b c d e f g h i j}
          108  +  file attributes test.db-shm -permissions r--r--r--
          109  +  do_test 1.2.6 { sql1 "SELECT * FROM t1" } {a b c d e f g h i j}
          110  +
          111  +  do_test 1.2.7 { 
          112  +    sql2 {
          113  +      PRAGMA wal_checkpoint;
          114  +      INSERT INTO t1 VALUES('k', 'l');
          115  +    }
          116  +    set {} {}
          117  +  } {}
          118  +  do_test 1.2.8 { sql1 "SELECT * FROM t1" } {a b c d e f g h i j k l}
          119  +}
          120  +
          121  +finish_test