/ Check-in [899e6070]
Login

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

Overview
Comment:Some fixes and test cases for exclusive access mode. (CVS 3714)
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA1: 899e60707bea0fabab2ff3ac8a3fbb676a539120
User & Date: danielk1977 2007-03-26 08:05:12
Context
2007-03-26
08:41
Add some documentation for pragma locking_mode. (CVS 3715) check-in: 394b174e user: danielk1977 tags: trunk
08:05
Some fixes and test cases for exclusive access mode. (CVS 3714) check-in: 899e6070 user: danielk1977 tags: trunk
2007-03-25
19:08
Add the sqlite3_prepare_v2 and sqlite3_prepare16_v2 APIs to the loadable extension interface. (CVS 3713) check-in: f02ba56d user: drh tags: trunk
Changes
Hide Diffs Side-by-Side Diffs Ignore Whitespace Patch

Changes to src/pager.c.

    14     14   ** The pager is used to access a database disk file.  It implements
    15     15   ** atomic commit and rollback through the use of a journal file that
    16     16   ** is separate from the database file.  The pager also implements file
    17     17   ** locking to prevent two processes from writing the same database
    18     18   ** file simultaneously, or one process from reading the database while
    19     19   ** another is writing.
    20     20   **
    21         -** @(#) $Id: pager.c,v 1.294 2007/03/24 16:45:05 danielk1977 Exp $
           21  +** @(#) $Id: pager.c,v 1.295 2007/03/26 08:05:12 danielk1977 Exp $
    22     22   */
    23     23   #ifndef SQLITE_OMIT_DISKIO
    24     24   #include "sqliteInt.h"
    25     25   #include "os.h"
    26     26   #include "pager.h"
    27     27   #include <assert.h>
    28     28   #include <string.h>
................................................................................
   868    868   }
   869    869   
   870    870   /*
   871    871   ** Execute a rollback if a transaction is active and unlock the 
   872    872   ** database file. This is a no-op if the pager has already entered
   873    873   ** the error-state.
   874    874   */
   875         -static void pagerUnlockAndRollback(Pager *pPager){
   876         -  if( pPager->errCode ) return;
   877         -  if( pPager->state>=PAGER_RESERVED ){
   878         -    sqlite3PagerRollback(pPager);
          875  +static void pagerUnlockAndRollback(Pager *p){
          876  +  if( p->errCode ) return;
          877  +  if( p->state>=PAGER_RESERVED ){
          878  +    sqlite3PagerRollback(p);
   879    879     }
   880         -  pager_unlock(pPager);
          880  +  pager_unlock(p);
          881  +  assert( p->errCode || !p->journalOpen || (p->exclusiveMode&&!p->journalOff) );
          882  +  assert( p->errCode || !p->stmtOpen || p->exclusiveMode );
   881    883   }
   882    884   
   883    885   
   884    886   /*
   885    887   ** Unlock the database and clear the in-memory cache.  This routine
   886    888   ** sets the state of the pager back to what it was when it was first
   887    889   ** opened.  Any outstanding pages are invalidated and subsequent attempts
................................................................................
   920    922     PgHdr *pPg;
   921    923     int rc = SQLITE_OK;
   922    924     assert( !MEMDB );
   923    925     if( pPager->state<PAGER_RESERVED ){
   924    926       return SQLITE_OK;
   925    927     }
   926    928     sqlite3PagerStmtCommit(pPager);
   927         -  if( pPager->stmtOpen ){
   928         -    sqlite3OsClose(&pPager->stfd);
   929         -    pPager->stmtOpen = 0;
          929  +  if( pPager->stmtOpen && !pPager->exclusiveMode ){
          930  +    if( pPager->exclusiveMode ){
          931  +      sqlite3OsClose(&pPager->stfd);
          932  +      pPager->stmtOpen = 0;
          933  +    }else{
          934  +      sqlite3OsTruncate(pPager->stfd, 0);
          935  +    }
   930    936     }
   931    937     if( pPager->journalOpen ){
   932    938       if( pPager->exclusiveMode ){
   933    939         sqlite3OsTruncate(pPager->jfd, 0);
   934    940         sqlite3OsSeek(pPager->jfd, 0);
   935    941         pPager->journalOff = 0;
          942  +      pPager->journalStarted = 0;
   936    943       }else{
   937    944         sqlite3OsClose(&pPager->jfd);
   938    945         pPager->journalOpen = 0;
   939    946         sqlite3OsDelete(pPager->zJournal);
   940    947       }
   941    948       sqliteFree( pPager->aInJournal );
   942    949       pPager->aInJournal = 0;
................................................................................
   955    962     }else{
   956    963       assert( pPager->aInJournal==0 );
   957    964       assert( pPager->dirtyCache==0 || pPager->useJournal==0 );
   958    965     }
   959    966     if( !pPager->exclusiveMode ){
   960    967       rc = sqlite3OsUnlock(pPager->fd, SHARED_LOCK);
   961    968       pPager->state = PAGER_SHARED;
   962         -    pPager->origDbSize = 0;
   963         -  }else{
   964         -    sqlite3PagerPagecount(pPager);
   965         -    pPager->origDbSize = pPager->dbSize;
   966         -    pPager->aInJournal = sqliteMalloc( pPager->dbSize/8 + 1 );
   967         -    if( !pPager->aInJournal ){
   968         -      rc = SQLITE_NOMEM;
   969         -    }
          969  +  }else if( pPager->state==PAGER_SYNCED ){
          970  +    pPager->state = PAGER_EXCLUSIVE;
   970    971     }
          972  +  pPager->origDbSize = 0;
   971    973     pPager->setMaster = 0;
   972    974     pPager->needSync = 0;
   973    975     pPager->pFirstSynced = pPager->pFirst;
   974    976     pPager->dbSize = -1;
   975    977     return rc;
   976    978   }
   977    979   
................................................................................
  1319   1321     char *zMaster = 0;       /* Name of master journal file if any */
  1320   1322   
  1321   1323     /* Figure out how many records are in the journal.  Abort early if
  1322   1324     ** the journal is empty.
  1323   1325     */
  1324   1326     assert( pPager->journalOpen );
  1325   1327     rc = sqlite3OsFileSize(pPager->jfd, &szJ);
  1326         -  if( rc!=SQLITE_OK ){
         1328  +  if( rc!=SQLITE_OK || szJ==0 ){
  1327   1329       goto end_playback;
  1328   1330     }
  1329   1331   
  1330   1332     /* Read the master journal name from the journal, if it is present.
  1331   1333     ** If a master journal file name is specified, but the file is not
  1332   1334     ** present on disk, then the journal is not hot and does not need to be
  1333   1335     ** played back.
................................................................................
  2797   2799     assert( pPager!=0 );
  2798   2800     *ppPage = 0;
  2799   2801     if( pPager->errCode && pPager->errCode!=SQLITE_FULL ){
  2800   2802       return pPager->errCode;
  2801   2803     }
  2802   2804   
  2803   2805     /* If this is the first page accessed, then get a SHARED lock
  2804         -  ** on the database file.
         2806  +  ** on the database file. pagerSharedLock() is a no-op if 
         2807  +  ** a database lock is already held.
  2805   2808     */
  2806   2809     rc = pagerSharedLock(pPager);
  2807   2810     if( rc!=SQLITE_OK ){
  2808   2811       return rc;
  2809   2812     }
  2810   2813     assert( pPager->state!=PAGER_UNLOCK );
  2811   2814   
................................................................................
  2944   2947   DbPage *sqlite3PagerLookup(Pager *pPager, Pgno pgno){
  2945   2948     PgHdr *pPg;
  2946   2949   
  2947   2950     assert( pPager!=0 );
  2948   2951     assert( pgno!=0 );
  2949   2952   
  2950   2953     if( pPager->state==PAGER_UNLOCK ){
         2954  +    assert( !pPager->pAll || pPager->exclusiveMode );
  2951   2955       return 0;
  2952   2956     }
  2953         -  if( (pPager->errCode && pPager->errCode!=SQLITE_FULL) ){
         2957  +  if( pPager->errCode && pPager->errCode!=SQLITE_FULL ){
  2954   2958       return 0;
  2955   2959     }
  2956   2960     pPg = pager_lookup(pPager, pgno);
  2957   2961     if( pPg==0 ) return 0;
  2958   2962     page_ref(pPg);
  2959   2963     return pPg;
  2960   2964   }
................................................................................
  3000   3004     
  3001   3005       /* When all pages reach the freelist, drop the read lock from
  3002   3006       ** the database file.
  3003   3007       */
  3004   3008       pPager->nRef--;
  3005   3009       assert( pPager->nRef>=0 );
  3006   3010       if( pPager->nRef==0 ){
  3007         -      /* pager_reset(pPager); */
  3008   3011         pagerUnlockAndRollback(pPager);
  3009   3012       }
  3010   3013     }
  3011   3014     return SQLITE_OK;
  3012   3015   }
  3013   3016   
  3014   3017   /*
................................................................................
  3132   3135         }
  3133   3136         pPager->dirtyCache = 0;
  3134   3137         TRACE2("TRANSACTION %d\n", PAGERID(pPager));
  3135   3138         if( pPager->useJournal && !pPager->tempFile ){
  3136   3139           rc = pager_open_journal(pPager);
  3137   3140         }
  3138   3141       }
         3142  +  }else if( pPager->journalOpen && pPager->journalOff==0 ){
         3143  +    /* This happens when the pager was in exclusive-access mode last
         3144  +    ** time a (read or write) transaction was successfully concluded
         3145  +    ** by this connection. Instead of deleting the journal file it was 
         3146  +    ** kept open and truncated to 0 bytes.
         3147  +    */
         3148  +    assert( pPager->nRec==0 );
         3149  +    assert( pPager->origDbSize==0 );
         3150  +    sqlite3PagerPagecount(pPager);
         3151  +    pPager->origDbSize = pPager->dbSize;
         3152  +    pPager->aInJournal = sqliteMalloc( pPager->dbSize/8 + 1 );
         3153  +    if( !pPager->aInJournal ){
         3154  +      rc = SQLITE_NOMEM;
         3155  +    }else{
         3156  +      rc = writeJournalHdr(pPager);
         3157  +    }
  3139   3158     }
         3159  +  assert( !pPager->journalOpen || pPager->journalOff>0 || rc!=SQLITE_OK );
  3140   3160     return rc;
  3141   3161   }
  3142   3162   
  3143   3163   /*
  3144   3164   ** Make a page dirty.  Set its dirty flag and add it to the dirty
  3145   3165   ** page list.
  3146   3166   */
................................................................................
  3610   3630     TRACE2("ROLLBACK %d\n", PAGERID(pPager));
  3611   3631     if( MEMDB ){
  3612   3632       PgHdr *p;
  3613   3633       for(p=pPager->pAll; p; p=p->pNextAll){
  3614   3634         PgHistory *pHist;
  3615   3635         assert( !p->alwaysRollback );
  3616   3636         if( !p->dirty ){
  3617         -        assert( p->inJournal==0 );
  3618         -        assert( p->inStmt==0 );
  3619   3637           assert( !((PgHistory *)PGHDR_TO_HIST(p, pPager))->pOrig );
  3620   3638           assert( !((PgHistory *)PGHDR_TO_HIST(p, pPager))->pStmt );
  3621   3639           continue;
  3622   3640         }
  3623   3641   
  3624   3642         pHist = PGHDR_TO_HIST(p, pPager);
  3625   3643         if( pHist->pOrig ){
................................................................................
  3655   3673       if( pPager->state>=PAGER_EXCLUSIVE ){
  3656   3674         pager_playback(pPager, 0);
  3657   3675       }
  3658   3676       return pPager->errCode;
  3659   3677     }
  3660   3678     if( pPager->state==PAGER_RESERVED ){
  3661   3679       int rc2;
  3662         -    /* rc = pager_reload_cache(pPager); */
  3663   3680       rc = pager_playback(pPager, 0);
  3664   3681       rc2 = pager_unwritelock(pPager);
  3665   3682       if( rc==SQLITE_OK ){
  3666   3683         rc = rc2;
  3667   3684       }
  3668   3685     }else{
  3669   3686       rc = pager_playback(pPager, 0);

Changes to test/exclusive.test.

     6      6   #    May you do good and not evil.
     7      7   #    May you find forgiveness for yourself and forgive others.
     8      8   #    May you share freely, never taking more than you give.
     9      9   #
    10     10   #***********************************************************************
    11     11   # This file implements regression tests for SQLite library.
    12     12   #
    13         -# $Id: exclusive.test,v 1.1 2007/03/24 16:45:05 danielk1977 Exp $
           13  +# $Id: exclusive.test,v 1.2 2007/03/26 08:05:12 danielk1977 Exp $
    14     14   
    15     15   set testdir [file dirname $argv0]
    16     16   source $testdir/tester.tcl
    17     17   
    18     18   ifcapable {!pager_pragmas} {
    19     19     finish_test
    20     20     return
................................................................................
   270    270   do_test exclusive-3.4 {
   271    271     execsql {
   272    272       BEGIN;
   273    273       UPDATE abc SET a = 1, b = 2, c = 3;
   274    274       ROLLBACK;
   275    275       SELECT * FROM abc;
   276    276     }
   277         -} {1 2 3}
          277  +} {A B C}
   278    278   do_test exclusive-3.5 {
   279    279     filestate test.db-journal
   280    280   } {1 0}
   281    281   do_test exclusive-3.6 {
   282    282     execsql {
   283    283       PRAGMA locking_mode = normal;
   284    284       SELECT * FROM abc;
   285    285     }
   286    286     filestate test.db-journal
   287    287   } {0 0}
          288  +
          289  +#----------------------------------------------------------------------
          290  +# Tests exclusive-4.X - test that rollback works correctly when
          291  +# in exclusive-access mode.
          292  +#
   288    293   
   289    294   # The following procedure computes a "signature" for table "t3".  If
   290    295   # T3 changes in any way, the signature should change.  
   291    296   #
   292    297   # This is used to test ROLLBACK.  We gather a signature for t3, then
   293    298   # make lots of changes to t3, then rollback and take another signature.
   294    299   # The two signatures should be the same.
   295    300   #
   296    301   proc signature {} {
   297    302     return [db eval {SELECT count(*), md5sum(x) FROM t3}]
   298    303   }
   299    304   
   300         -if 0 {
   301         -
   302    305   do_test exclusive-4.0 {
   303         -  execsql { PRAGMA default_cache_size=10; }
   304         -  db close
   305         -  sqlite3 db test.db
   306    306     execsql { PRAGMA locking_mode = exclusive; }
          307  +  execsql { PRAGMA default_cache_size = 10; }
   307    308     execsql {
   308    309       BEGIN;
   309    310       CREATE TABLE t3(x TEXT);
   310    311       INSERT INTO t3 VALUES(randstr(10,400));
   311    312       INSERT INTO t3 VALUES(randstr(10,400));
   312    313       INSERT INTO t3 SELECT randstr(10,400) FROM t3;
   313    314       INSERT INTO t3 SELECT randstr(10,400) FROM t3;
   314    315       INSERT INTO t3 SELECT randstr(10,400) FROM t3;
   315    316       INSERT INTO t3 SELECT randstr(10,400) FROM t3;
   316         -    INSERT INTO t3 SELECT randstr(10,400) FROM t3;
   317         -    INSERT INTO t3 SELECT randstr(10,400) FROM t3;
   318         -    INSERT INTO t3 SELECT randstr(10,400) FROM t3;
   319         -    INSERT INTO t3 SELECT randstr(10,400) FROM t3;
   320         -    INSERT INTO t3 SELECT randstr(10,400) FROM t3;
   321    317       COMMIT;
   322         -    SELECT count(*) FROM t3;
   323    318     }
   324         -} {1024}
   325         -set sig [signature]
          319  +  execsql {SELECT count(*) FROM t3;}
          320  +} {32}
          321  +
          322  +set ::X [signature]
   326    323   do_test exclusive-4.1 {
   327    324     execsql {
   328    325       BEGIN;
   329    326       DELETE FROM t3 WHERE random()%10!=0;
   330    327       INSERT INTO t3 SELECT randstr(10,10)||x FROM t3;
   331    328       INSERT INTO t3 SELECT randstr(10,10)||x FROM t3;
          329  +    SELECT count(*) FROM t3;
          330  +    ROLLBACK;
          331  +  }
          332  +  signature
          333  +} $::X
          334  +
          335  +do_test exclusive-4.2 {
          336  +  execsql {
          337  +    BEGIN;
          338  +    DELETE FROM t3 WHERE random()%10!=0;
          339  +    INSERT INTO t3 SELECT randstr(10,10)||x FROM t3;
          340  +    DELETE FROM t3 WHERE random()%10!=0;
          341  +    INSERT INTO t3 SELECT randstr(10,10)||x FROM t3;
   332    342       ROLLBACK;
   333    343     }
   334    344     signature
   335         -} $sig
          345  +} $::X
          346  +
          347  +do_test exclusive-4.3 {
          348  +  execsql {
          349  +    INSERT INTO t3 SELECT randstr(10,400) FROM t3 WHERE random()%10==0;
          350  +  }
          351  +} {}
          352  +
          353  +do_test exclusive-4.4 {
          354  +  catch {set ::X [signature]}
          355  +} {0}
          356  +do_test exclusive-4.5 {
          357  +  execsql {
          358  +    PRAGMA locking_mode = NORMAL;
          359  +    DROP TABLE t3;
          360  +    DROP TABLE abc;
          361  +  }
          362  +} {normal}
   336    363   
   337         -}
          364  +#----------------------------------------------------------------------
          365  +# Tests exclusive-5.X - test that statement journals are truncated
          366  +# instead of deleted when in exclusive access mode.
          367  +#
          368  +#set sqlite_os_trace 1
          369  +do_test exclusive-5.0 {
          370  +  execsql {
          371  +    CREATE TABLE abc(a UNIQUE, b UNIQUE, c UNIQUE);
          372  +    BEGIN;
          373  +    INSERT INTO abc VALUES(1, 2, 3);
          374  +    INSERT INTO abc SELECT a+1, b+1, c+1 FROM abc;
          375  +  }
          376  +} {}
          377  +do_test exclusive-5.1 {
          378  +  # Three files are open: The db, journal and statement-journal.
          379  +  set sqlite_open_file_count
          380  +} {3}
          381  +do_test exclusive-5.2 {
          382  +  execsql {
          383  +    COMMIT;
          384  +  }
          385  +  # One file open: the db.
          386  +  set sqlite_open_file_count
          387  +} {1}
          388  +do_test exclusive-5.3 {
          389  +  execsql {
          390  +    PRAGMA locking_mode = exclusive;
          391  +    BEGIN;
          392  +    INSERT INTO abc VALUES(5, 6, 7);
          393  +  }
          394  +  # Two files open: the db and journal.
          395  +  set sqlite_open_file_count
          396  +} {2}
          397  +do_test exclusive-5.4 {
          398  +  execsql {
          399  +    INSERT INTO abc SELECT a+10, b+10, c+10 FROM abc;
          400  +  }
          401  +  # Three files are open: The db, journal and statement-journal.
          402  +  set sqlite_open_file_count
          403  +} {3}
          404  +do_test exclusive-5.5 {
          405  +  execsql {
          406  +    COMMIT;
          407  +  }
          408  +  # Three files are still open: The db, journal and statement-journal.
          409  +  set sqlite_open_file_count
          410  +} {3}
          411  +do_test exclusive-5.6 {
          412  +  execsql {
          413  +    PRAGMA locking_mode = normal;
          414  +    SELECT * FROM abc;
          415  +  }
          416  +} {normal 1 2 3 2 3 4 5 6 7 11 12 13 12 13 14 15 16 17}
          417  +do_test exclusive-5.7 {
          418  +  # Just the db open.
          419  +  set sqlite_open_file_count
          420  +} {1}
   338    421   
   339    422   finish_test
   340    423   

Changes to test/trans.test.

     7      7   #    May you find forgiveness for yourself and forgive others.
     8      8   #    May you share freely, never taking more than you give.
     9      9   #
    10     10   #***********************************************************************
    11     11   # This file implements regression tests for SQLite library.  The
    12     12   # focus of this script is database locks.
    13     13   #
    14         -# $Id: trans.test,v 1.33 2007/03/24 16:45:05 danielk1977 Exp $
           14  +# $Id: trans.test,v 1.34 2007/03/26 08:05:12 danielk1977 Exp $
    15     15   
    16     16   
    17     17   set testdir [file dirname $argv0]
    18     18   source $testdir/tester.tcl
    19     19   
    20     20   
    21     21   # Create several tables to work with.
................................................................................
   811    811   #
   812    812   do_test trans-9.1 {
   813    813     execsql {
   814    814       PRAGMA default_cache_size=10;
   815    815     }
   816    816     db close
   817    817     sqlite3 db test.db
   818         -  # execsql { PRAGMA locking_mode = exclusive; }
   819    818     execsql {
   820    819       BEGIN;
   821    820       CREATE TABLE t3(x TEXT);
   822    821       INSERT INTO t3 VALUES(randstr(10,400));
   823    822       INSERT INTO t3 VALUES(randstr(10,400));
   824    823       INSERT INTO t3 SELECT randstr(10,400) FROM t3;
   825    824       INSERT INTO t3 SELECT randstr(10,400) FROM t3;