/ Check-in [5b9f2721]
Login

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

Overview
Comment:Fix handling of attempts to modify the database schema, application_id or user_version within an UNLOCKED transaction.
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | begin-concurrent
Files: files | file ages | folders
SHA1: 5b9f272113d21fd606903509d6f830fe60fac039
User & Date: dan 2015-08-24 19:08:10
Wiki:begin-concurrent
Context
2015-08-24
19:56
Change "BEGIN UNLOCKED" to "BEGIN CONCURRENT". check-in: ba1ab858 user: dan tags: begin-concurrent
19:08
Fix handling of attempts to modify the database schema, application_id or user_version within an UNLOCKED transaction. check-in: 5b9f2721 user: dan tags: begin-concurrent
16:00
Fix compilation without SQLITE_ENABLE_UNLOCKED. Also other code organization issues. check-in: 04113557 user: dan tags: begin-concurrent
Changes
Hide Diffs Side-by-Side Diffs Ignore Whitespace Patch

Changes to src/vdbe.c.

  3223   3223     assert( DbMaskTest(p->btreeMask, pOp->p1) );
  3224   3224     assert( p->readOnly==0 );
  3225   3225     pDb = &db->aDb[pOp->p1];
  3226   3226     assert( pDb->pBt!=0 );
  3227   3227     assert( sqlite3SchemaMutexHeld(db, pOp->p1, 0) );
  3228   3228     pIn3 = &aMem[pOp->p3];
  3229   3229     sqlite3VdbeMemIntegerify(pIn3);
         3230  +#ifdef SQLITE_ENABLE_UNLOCKED
         3231  +  if( db->bUnlocked 
         3232  +   && (pOp->p2==BTREE_USER_VERSION || pOp->p2==BTREE_APPLICATION_ID)
         3233  +  ){
         3234  +    rc = SQLITE_ERROR;
         3235  +    sqlite3VdbeError(p, "cannot modify %s within UNLOCKED transaction",
         3236  +        pOp->p2==BTREE_USER_VERSION ? "user_version" : "application_id"
         3237  +    );
         3238  +    break; 
         3239  +  }
         3240  +#endif
  3230   3241     /* See note about index shifting on OP_ReadCookie */
  3231   3242     rc = sqlite3BtreeUpdateMeta(pDb->pBt, pOp->p2, (int)pIn3->u.i);
  3232   3243     if( pOp->p2==BTREE_SCHEMA_VERSION ){
  3233         -    if( db->bUnlocked ){
  3234         -      sqlite3VdbeError(p, "cannot modify database schema - "
  3235         -          "UNLOCKED transaction"
  3236         -      );
  3237         -      rc = SQLITE_ERROR;
  3238         -    }else{
  3239         -      /* When the schema cookie changes, record the new cookie internally */
  3240         -      pDb->pSchema->schema_cookie = (int)pIn3->u.i;
  3241         -      db->flags |= SQLITE_InternChanges;
  3242         -    }
         3244  +    /* When the schema cookie changes, record the new cookie internally */
         3245  +    assert( db->bUnlocked==0 );
         3246  +    pDb->pSchema->schema_cookie = (int)pIn3->u.i;
         3247  +    db->flags |= SQLITE_InternChanges;
  3243   3248     }else if( pOp->p2==BTREE_FILE_FORMAT ){
  3244   3249       /* Record changes in the file format */
  3245   3250       pDb->pSchema->file_format = (u8)pIn3->u.i;
  3246   3251     }
  3247   3252     if( pOp->p1==1 ){
  3248   3253       /* Invalidate all prepared statements whenever the TEMP database
  3249   3254       ** schema is changed.  Ticket #1644 */
................................................................................
  6106   6111   ** P2 contains the root-page of the table to lock.
  6107   6112   **
  6108   6113   ** P4 contains a pointer to the name of the table being locked. This is only
  6109   6114   ** used to generate an error message if the lock cannot be obtained.
  6110   6115   */
  6111   6116   case OP_TableLock: {
  6112   6117     u8 isWriteLock = (u8)pOp->p3;
         6118  +#ifdef SQLITE_ENABLE_UNLOCKED
         6119  +  if( isWriteLock && db->bUnlocked && pOp->p2==1 ){
         6120  +    rc = SQLITE_ERROR;
         6121  +    sqlite3VdbeError(p, 
         6122  +        "cannot modify database schema within UNLOCKED transaction");
         6123  +    rc = SQLITE_ERROR;
         6124  +    break;
         6125  +  }
         6126  +#endif
  6113   6127     if( isWriteLock || 0==(db->flags&SQLITE_ReadUncommitted) ){
  6114   6128       int p1 = pOp->p1; 
  6115   6129       assert( p1>=0 && p1<db->nDb );
  6116   6130       assert( DbMaskTest(p->btreeMask, p1) );
  6117   6131       assert( isWriteLock==0 || isWriteLock==1 );
  6118   6132       rc = sqlite3BtreeLockTable(db->aDb[p1].pBt, pOp->p2, isWriteLock);
  6119   6133       if( (rc&0xFF)==SQLITE_LOCKED ){

Changes to src/wal.c.

  1944   1944       }
  1945   1945       WALTRACE(("WAL%p: closed\n", pWal));
  1946   1946       sqlite3_free((void *)pWal->apWiData);
  1947   1947       sqlite3_free(pWal);
  1948   1948     }
  1949   1949     return rc;
  1950   1950   }
         1951  +
         1952  +/*
         1953  +** Try to copy the wal-index header from shared-memory into (*pHdr). Return
         1954  +** zero if successful or non-zero otherwise. If the header is corrupted
         1955  +** (either because the two copies are inconsistent or because the checksum 
         1956  +** values are incorrect), the read fails and non-zero is returned.
         1957  +*/
         1958  +static int walIndexLoadHdr(Wal *pWal, WalIndexHdr *pHdr){
         1959  +  u32 aCksum[2];                  /* Checksum on the header content */
         1960  +  WalIndexHdr h2;                 /* Second copy of the header content */
         1961  +  WalIndexHdr volatile *aHdr;     /* Header in shared memory */
         1962  +
         1963  +  /* The first page of the wal-index must be mapped at this point. */
         1964  +  assert( pWal->nWiData>0 && pWal->apWiData[0] );
         1965  +
         1966  +  /* Read the header. This might happen concurrently with a write to the
         1967  +  ** same area of shared memory on a different CPU in a SMP,
         1968  +  ** meaning it is possible that an inconsistent snapshot is read
         1969  +  ** from the file. If this happens, return non-zero.
         1970  +  **
         1971  +  ** There are two copies of the header at the beginning of the wal-index.
         1972  +  ** When reading, read [0] first then [1].  Writes are in the reverse order.
         1973  +  ** Memory barriers are used to prevent the compiler or the hardware from
         1974  +  ** reordering the reads and writes.
         1975  +  */
         1976  +  aHdr = walIndexHdr(pWal);
         1977  +  memcpy(pHdr, (void *)&aHdr[0], sizeof(h2));
         1978  +  walShmBarrier(pWal);
         1979  +  memcpy(&h2, (void *)&aHdr[1], sizeof(h2));
         1980  +
         1981  +  if( memcmp(&h2, pHdr, sizeof(h2))!=0 ){
         1982  +    return 1;   /* Dirty read */
         1983  +  }  
         1984  +  if( h2.isInit==0 ){
         1985  +    return 1;   /* Malformed header - probably all zeros */
         1986  +  }
         1987  +  walChecksumBytes(1, (u8*)&h2, sizeof(h2)-sizeof(h2.aCksum), 0, aCksum);
         1988  +  if( aCksum[0]!=h2.aCksum[0] || aCksum[1]!=h2.aCksum[1] ){
         1989  +    return 1;   /* Checksum does not match */
         1990  +  }
         1991  +
         1992  +  return 0;
         1993  +}
  1951   1994   
  1952   1995   /*
  1953   1996   ** Try to read the wal-index header.  Return 0 on success and 1 if
  1954   1997   ** there is a problem.
  1955   1998   **
  1956   1999   ** The wal-index is in shared memory.  Another thread or process might
  1957   2000   ** be writing the header at the same time this procedure is trying to
................................................................................
  1963   2006   ** pWal->hdr, then pWal->hdr is updated to the content of the new header
  1964   2007   ** and *pChanged is set to 1.
  1965   2008   **
  1966   2009   ** If the checksum cannot be verified return non-zero. If the header
  1967   2010   ** is read successfully and the checksum verified, return zero.
  1968   2011   */
  1969   2012   static int walIndexTryHdr(Wal *pWal, int *pChanged){
  1970         -  u32 aCksum[2];                  /* Checksum on the header content */
  1971         -  WalIndexHdr h1, h2;             /* Two copies of the header content */
  1972         -  WalIndexHdr volatile *aHdr;     /* Header in shared memory */
         2013  +  WalIndexHdr h1;                 /* Copy of the header content */
  1973   2014   
  1974         -  /* The first page of the wal-index must be mapped at this point. */
  1975         -  assert( pWal->nWiData>0 && pWal->apWiData[0] );
  1976         -
  1977         -  /* Read the header. This might happen concurrently with a write to the
  1978         -  ** same area of shared memory on a different CPU in a SMP,
  1979         -  ** meaning it is possible that an inconsistent snapshot is read
  1980         -  ** from the file. If this happens, return non-zero.
  1981         -  **
  1982         -  ** There are two copies of the header at the beginning of the wal-index.
  1983         -  ** When reading, read [0] first then [1].  Writes are in the reverse order.
  1984         -  ** Memory barriers are used to prevent the compiler or the hardware from
  1985         -  ** reordering the reads and writes.
  1986         -  */
  1987         -  aHdr = walIndexHdr(pWal);
  1988         -  memcpy(&h1, (void *)&aHdr[0], sizeof(h1));
  1989         -  walShmBarrier(pWal);
  1990         -  memcpy(&h2, (void *)&aHdr[1], sizeof(h2));
  1991         -
  1992         -  if( memcmp(&h1, &h2, sizeof(h1))!=0 ){
  1993         -    return 1;   /* Dirty read */
  1994         -  }  
  1995         -  if( h1.isInit==0 ){
  1996         -    return 1;   /* Malformed header - probably all zeros */
  1997         -  }
  1998         -  walChecksumBytes(1, (u8*)&h1, sizeof(h1)-sizeof(h1.aCksum), 0, aCksum);
  1999         -  if( aCksum[0]!=h1.aCksum[0] || aCksum[1]!=h1.aCksum[1] ){
  2000         -    return 1;   /* Checksum does not match */
         2015  +  if( walIndexLoadHdr(pWal, &h1) ){
         2016  +    return 1;
  2001   2017     }
  2002   2018   
  2003   2019     if( memcmp(&pWal->hdr, &h1, sizeof(WalIndexHdr)) ){
  2004   2020       *pChanged = 1;
  2005   2021       memcpy(&pWal->hdr, &h1, sizeof(WalIndexHdr));
  2006   2022       pWal->szPage = (pWal->hdr.szPage&0xfe00) + ((pWal->hdr.szPage&0x0001)<<16);
  2007   2023       testcase( pWal->szPage<=32768 );
................................................................................
  2632   2648     **   a) None of the pages in pList have been modified since the 
  2633   2649     **      transaction opened, and
  2634   2650     **
  2635   2651     **   b) The database schema cookie has not been modified since the
  2636   2652     **      transaction was started.
  2637   2653     */
  2638   2654     if( rc==SQLITE_OK ){
  2639         -    volatile WalIndexHdr *pHead;    /* Head of the wal file */
  2640         -    pHead = walIndexHdr(pWal);
         2655  +    WalIndexHdr head;
  2641   2656   
  2642         -    /* TODO: Check header checksum is good here. */
  2643         -
  2644         -    if( memcmp(&pWal->hdr, (void*)pHead, sizeof(WalIndexHdr))!=0 ){
         2657  +    if( walIndexLoadHdr(pWal, &head) ){
         2658  +      /* This branch is taken if the wal-index header is corrupted. This 
         2659  +      ** occurs if some other writer has crashed while committing a 
         2660  +      ** transaction to this database since the current unlocked transaction
         2661  +      ** was opened.  */
         2662  +      rc = SQLITE_BUSY_SNAPSHOT;
         2663  +    }else if( memcmp(&pWal->hdr, (void*)&head, sizeof(WalIndexHdr))!=0 ){
  2645   2664         int iHash;
  2646         -      int iLastHash = walFramePage(pHead->mxFrame);
         2665  +      int iLastHash = walFramePage(head.mxFrame);
  2647   2666         u32 iFirst = pWal->hdr.mxFrame+1;     /* First wal frame to check */
  2648         -      if( memcmp(pWal->hdr.aSalt, (u32*)pHead->aSalt, sizeof(u32)*2) ){
         2667  +      if( memcmp(pWal->hdr.aSalt, (u32*)head.aSalt, sizeof(u32)*2) ){
  2649   2668           assert( pWal->readLock==0 );
  2650   2669           iFirst = 1;
  2651   2670         }
  2652   2671         for(iHash=walFramePage(iFirst); iHash<=iLastHash; iHash++){
  2653   2672           volatile ht_slot *aHash;
  2654   2673           volatile u32 *aPgno;
  2655   2674           u32 iZero;
................................................................................
  2656   2675   
  2657   2676           rc = walHashGet(pWal, iHash, &aHash, &aPgno, &iZero);
  2658   2677           if( rc==SQLITE_OK ){
  2659   2678             int i;
  2660   2679             int iMin = (iFirst - iZero);
  2661   2680             int iMax = (iHash==0) ? HASHTABLE_NPAGE_ONE : HASHTABLE_NPAGE;
  2662   2681             if( iMin<1 ) iMin = 1;
  2663         -          if( iMax>pHead->mxFrame ) iMax = pHead->mxFrame;
         2682  +          if( iMax>head.mxFrame ) iMax = head.mxFrame;
  2664   2683             for(i=iMin; i<=iMax; i++){
  2665   2684               PgHdr *pPg;
  2666   2685               if( aPgno[i]==1 ){
  2667   2686                 /* Check that the schema cookie has not been modified. If
  2668   2687                 ** it has not, the commit can proceed. */
  2669   2688                 u8 aNew[4];
  2670   2689                 u8 *aOld = &((u8*)pPage1->pData)[40];

Changes to test/unlocked.test.

   106    106   
   107    107   #-------------------------------------------------------------------------
   108    108   # UNLOCKED transactions may not modify the db schema.
   109    109   #
   110    110   foreach {tn sql} {
   111    111     1 { CREATE TABLE xx(a, b) }
   112    112     2 { DROP TABLE t1 }
          113  +  3 { CREATE INDEX i1 ON t1(a) }
          114  +  4 { CREATE VIEW v1 AS SELECT * FROM t1 }
          115  +  5 { CREATE TEMP TABLE xx(a, b) }
   113    116   } {
   114    117     do_catchsql_test 1.7.$tn.1 "
   115    118       BEGIN UNLOCKED;
   116    119       $sql
   117         -  " {1 {cannot modify database schema - UNLOCKED transaction}}
          120  +  " {1 {cannot modify database schema within UNLOCKED transaction}}
   118    121   
   119         -  do_execsql_test 1.7.$tn.2 ROLLBACK
          122  +  do_execsql_test 1.7.$tn.2 {
          123  +    SELECT sql FROM sqlite_master;
          124  +    SELECT sql FROM sqlite_temp_master;
          125  +  } {{CREATE TABLE t1(a, b)}}
          126  +
          127  +  do_execsql_test 1.7.$tn.3 COMMIT
   120    128   }
   121    129   
   122    130   #-------------------------------------------------------------------------
   123    131   # If an auto-vacuum database is written within an UNLOCKED transaction, it
   124    132   # is handled in the same way as for a non-UNLOCKED transaction.
   125    133   #
   126    134   reset_db
................................................................................
   404    412     do_test 2.$tn.7.3 {
   405    413       list [catch { sql1 { COMMIT } } msg] $msg [sqlite3_errcode db]
   406    414     } {0 {} SQLITE_OK}
   407    415   
   408    416     do_test 2.$tn.7.4 { sql3 { PRAGMA integrity_check } } ok
   409    417   }
   410    418   
   411         -
          419  +#-------------------------------------------------------------------------
          420  +# Unlocked transactions may not modify the user_version or application_id.
          421  +#
          422  +reset_db
          423  +do_execsql_test 3.0 {
          424  +  PRAGMA journal_mode = wal;
          425  +  CREATE TABLE t1(x, y);
          426  +  INSERT INTO t1 VALUES('a', 'b');
          427  +  PRAGMA user_version = 10;
          428  +} {wal}
          429  +do_execsql_test 3.1 {
          430  +  BEGIN UNLOCKED;
          431  +    INSERT INTO t1 VALUES('c', 'd');
          432  +    SELECT * FROM t1;
          433  +} {a b c d}
          434  +do_catchsql_test 3.2 {
          435  +  PRAGMA user_version = 11;
          436  +} {1 {cannot modify user_version within UNLOCKED transaction}}
          437  +do_execsql_test 3.3 {
          438  +  PRAGMA user_version;
          439  +  SELECT * FROM t1;
          440  +} {10 a b c d}
          441  +do_catchsql_test 3.4 {
          442  +  PRAGMA application_id = 11;
          443  +} {1 {cannot modify application_id within UNLOCKED transaction}}
          444  +do_execsql_test 3.5 {
          445  +  COMMIT;
          446  +  PRAGMA user_version;
          447  +  PRAGMA application_id;
          448  +  SELECT * FROM t1;
          449  +} {10 0 a b c d}
   412    450   
   413    451   finish_test
          452  +