From: dan Date: Mon, 27 Jul 2015 19:31:45 +0000 (+0000) Subject: Add an experimental "BEGIN UNLOCKED" command. X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=3d39434c36bcc7cd1da4e4885829a85077616aa3;p=thirdparty%2Fsqlite.git Add an experimental "BEGIN UNLOCKED" command. FossilOrigin-Name: 8079421604dbd40d03471dad6d12115119b554c2 --- diff --git a/manifest b/manifest index 9db7a69587..62addb80e6 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Version\s3.8.11 -D 2015-07-27T13:49:41.754 +C Add\san\sexperimental\s"BEGIN\sUNLOCKED"\scommand. +D 2015-07-27T19:31:45.102 F Makefile.arm-wince-mingw32ce-gcc d6df77f1f48d690bd73162294bbba7f59507c72f F Makefile.in 4de3ef40c8b3b75c0c55ff4242a43c8ce1ad90ee F Makefile.linux-gcc 91d710bdc4998cb015f39edf3cb314ec4f4d7e23 @@ -270,10 +270,10 @@ F src/auth.c b56c78ebe40a2110fd361379f7e8162d23f92240 F src/backup.c 4d9134dc988a87838c06056c89c0e8c4700a0452 F src/bitvec.c d1f21d7d91690747881f03940584f4cc548c9d3d F src/btmutex.c 45a968cc85afed9b5e6cf55bf1f42f8d18107f79 -F src/btree.c f48b3ef91676c06a90a8832987ecef6b94c931ee -F src/btree.h 969adc948e89e449220ff0ff724c94bb2a52e9f1 +F src/btree.c b184ce6d483dfadc53126a4b7994633ec0d5ef5f +F src/btree.h 550e8447684013a69196ec664142f67538da8ef1 F src/btreeInt.h 2ad754dd4528baa8d0946a593cc373b890bf859e -F src/build.c b3f15255d5b16e42dafeaa638fd4f8a47c94ed70 +F src/build.c 28c15c43eefc0066ff64040526ff649c32fe5523 F src/callback.c 7b44ce59674338ad48b0e84e7b72f935ea4f68b0 F src/complete.c addcd8160b081131005d5bc2d34adf20c1c5c92f F src/ctime.c 5a0b735dc95604766f5dac73973658eef782ee8b @@ -293,7 +293,7 @@ F src/journal.c b4124532212b6952f42eb2c12fa3c25701d8ba8d F src/legacy.c ba1863ea58c4c840335a84ec276fc2b25e22bc4e F src/lempar.c 92bafa308607dd985ca389a788cd9e0a2b608712 F src/loadext.c dfcee8c7c032cd0fd55af3e0fc1fcfb01e426df2 -F src/main.c 0a60b7ca8252c3a6f95438fa4ce8fe5b275c69f2 +F src/main.c ebbd5a45caf66ce0ebdf1b6c0bc4784e3cfcd768 F src/malloc.c 19461e159bccf0e2cf06a50e867963d0a7b124a8 F src/mem0.c 6a55ebe57c46ca1a7d98da93aaa07f99f1059645 F src/mem1.c abe6ee469b6c5a35c7f22bfeb9c9bac664a1c987 @@ -315,9 +315,9 @@ F src/os_setup.h c9d4553b5aaa6f73391448b265b89bed0b890faa F src/os_unix.c 388c023582b17890f10c980b30ec1922b471753b F src/os_win.c 40b3af7a47eb1107d0d69e592bec345a3b7b798a F src/os_win.h eb7a47aa17b26b77eb97e4823f20a00b8bda12ca -F src/pager.c aa916ca28606ccf4b6877dfc2b643ccbca86589f -F src/pager.h 6d435f563b3f7fcae4b84433b76a6ac2730036e2 -F src/parse.y 6d60dda8f8d418b6dc034f1fbccd816c459983a8 +F src/pager.c c9f22eb831851220c1095863c5797a88f71bea8a +F src/pager.h b6cf6e9552c5b1e7d6d0d8cd7ca51be7802bec72 +F src/parse.y 199145cd982587d70fa3db2f9b11f7ab118f0acd F src/pcache.c cde06aa50962595e412d497e22fd2e07878ba1f0 F src/pcache.h 9968603796240cdf83da7e7bef76edf90619cea9 F src/pcache1.c d08939800abf3031bd0affd5a13fbc4d7ba3fb68 @@ -333,7 +333,7 @@ F src/shell.c 8af3cced094aebb5f57a8ad739b9dafc7867eed7 F src/sqlite.h.in f623dd30a4fb7df2fb44a2a85e27813d25e486c2 F src/sqlite3.rc 992c9f5fb8285ae285d6be28240a7e8d3a7f2bad F src/sqlite3ext.h a0b948ebc89bac13941254641326a6aa248c2cc4 -F src/sqliteInt.h 81f458941cfb30c5536c37930fb6f41e66171284 +F src/sqliteInt.h f14033fa6da3ca70f7dde5ba19dc1ed58055a2d7 F src/sqliteLimit.h 216557999cb45f2e3578ed53ebefe228d779cb46 F src/status.c f266ad8a2892d659b74f0f50cb6a88b6e7c12179 F src/table.c 51b46b2a62d1b3a959633d593b89bab5e2c9155e @@ -390,20 +390,20 @@ F src/trigger.c 322f23aad694e8f31d384dcfa386d52a48d3c52f F src/update.c 487747b328b7216bb7f6af0695d6937d5c9e605f F src/utf.c fc6b889ba0779b7722634cdeaa25f1930d93820c F src/util.c 46358a204b35971a839341cf64599d65b151ba88 -F src/vacuum.c 2ddd5cad2a7b9cef7f9e431b8c7771634c6b1701 -F src/vdbe.c 86be40a4568f5385048ea6623adfd11ddb7bf710 +F src/vacuum.c 4e7e18d889662eda281855a7125e56269069f215 +F src/vdbe.c fd6832408d9621ad347a65b01a05380fe766232f F src/vdbe.h 7a75045d879118b9d3af7e8b3c108f2f27c51473 F src/vdbeInt.h 8b54e01ad0463590e7cffabce0bc36da9ee4f816 F src/vdbeapi.c adabbd66eb2e3a10f3998485ee0be7e326d06ee4 -F src/vdbeaux.c 787f5f9d58f4c6f39294ed06909ba602d1a402e6 +F src/vdbeaux.c 27feae106f3f4db298e65d37a9da4962aaff724b F src/vdbeblob.c 4f2e8e075d238392df98c5e03a64342465b03f90 F src/vdbemem.c ae38a0d35ae71cf604381a887c170466ba518090 F src/vdbesort.c f5009e7a35e3065635d8918b9a31f498a499976b F src/vdbetrace.c 8befe829faff6d9e6f6e4dee5a7d3f85cc85f1a0 F src/vtab.c 082b35a25a26e3d36f365ca8cd73c1922532f05e F src/vxworks.h c18586c8edc1bddbc15c004fa16aeb1e1342b4fb -F src/wal.c 6fb6b68969e4692593c2552c4e7bff5882de2cb8 -F src/wal.h df01efe09c5cb8c8e391ff1715cca294f89668a4 +F src/wal.c d588e26ed0d912584551dfe65d644d8c27cc2d1f +F src/wal.h 5188cd85398dc612ae5ed2ed02d193377218be56 F src/walker.c c253b95b4ee44b21c406e2a1052636c31ea27804 F src/where.c 909eba3b6db984eb2adfbca9de2c237ee7056adb F src/whereInt.h 5f87e3c4b0551747d119730dfebddd3c54f04047 @@ -1215,6 +1215,7 @@ F test/types3.test 99e009491a54f4dc02c06bdbc0c5eea56ae3e25a F test/unique.test 93f8b2ef5ea51b9495f8d6493429b1fd0f465264 F test/unique2.test 41e7f83c6827605991160a31380148a9fc5f1339 F test/unixexcl.test cd6c765f75e50e8e2c2ba763149e5d340ea19825 +F test/unlocked.test edd4ed073ab16c0f8c9f5124e039100657b422c9 F test/unordered.test ca7adce0419e4ca0c50f039885e76ed2c531eda8 F test/update.test 6c68446b8a0a33d522a7c72b320934596a2d7d32 F test/uri.test 23662b7b61958b0f0e47082de7d06341ccf85d5b @@ -1326,7 +1327,7 @@ F tool/lempar.c 01ca97f87610d1dac6d8cd96ab109ab1130e76dc F tool/loadfts.c c3c64e4d5e90e8ba41159232c2189dba4be7b862 F tool/logest.c eef612f8adf4d0993dafed0416064cf50d5d33c6 F tool/mkautoconfamal.sh d1a2da0e15b2ed33d60af35c7e9d483f13a8eb9f -F tool/mkkeywordhash.c dfff09dbbfaf950e89af294f48f902181b144670 +F tool/mkkeywordhash.c de4a823fe66f9e8c39c86e465ac7285fd6935bb8 F tool/mkopts.tcl 66ac10d240cc6e86abd37dc908d50382f84ff46e F tool/mkpragmatab.tcl 84af2b180484323a2ea22a2279e8bd9e3e1e492e F tool/mkspeedsql.tcl a1a334d288f7adfe6e996f2e712becf076745c97 @@ -1366,10 +1367,10 @@ F tool/vdbe_profile.tcl 67746953071a9f8f2f668b73fe899074e2c6d8c1 F tool/warnings-clang.sh f6aa929dc20ef1f856af04a730772f59283631d4 F tool/warnings.sh 48bd54594752d5be3337f12c72f28d2080cb630b F tool/win/sqlite.vsix deb315d026cc8400325c5863eef847784a219a2f -P 9f1dcdc3e1644c21762dacf619fac70afe6318c5 -R 8f58f88db8a7564cc507b9707cc8c853 -T +bgcolor * #d0c0ff -T +sym-release * -T +sym-version-3.8.11 * -U drh -Z ff777afb71751a2633799d5aca45576d +P b8e92227a469de677a66da62e4361f099c0b79d0 +R 889966cd4fab23d4f4355c880be4edd8 +T *branch * experimental-begin-unlocked +T *sym-experimental-begin-unlocked * +T -sym-trunk * +U dan +Z 55f5e7a6a42bdc95bbb980b319cd8658 diff --git a/manifest.uuid b/manifest.uuid index 8247151f16..25c9006198 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -b8e92227a469de677a66da62e4361f099c0b79d0 \ No newline at end of file +8079421604dbd40d03471dad6d12115119b554c2 \ No newline at end of file diff --git a/src/btree.c b/src/btree.c index 2692ade8e4..c83a01ad4c 100644 --- a/src/btree.c +++ b/src/btree.c @@ -3142,7 +3142,10 @@ int sqlite3BtreeBeginTrans(Btree *p, int wrflag){ if( (pBt->btsFlags & BTS_READ_ONLY)!=0 ){ rc = SQLITE_READONLY; }else{ - rc = sqlite3PagerBegin(pBt->pPager,wrflag>1,sqlite3TempInMemory(p->db)); + int bSubjInMem = sqlite3TempInMemory(p->db); + int exFlag = p->db->bUnlocked ? -1 : (wrflag>1); + assert( p->db->bUnlocked==0 || wrflag==1 ); + rc = sqlite3PagerBegin(pBt->pPager, exFlag, bSubjInMem); if( rc==SQLITE_OK ){ rc = newDatabase(pBt); } @@ -3670,8 +3673,15 @@ int sqlite3BtreeCommitPhaseOne(Btree *p, const char *zMaster){ if( p->inTrans==TRANS_WRITE ){ BtShared *pBt = p->pBt; sqlite3BtreeEnter(p); + #ifndef SQLITE_OMIT_AUTOVACUUM - if( pBt->autoVacuum ){ + /* Figure out if this is a commit of an UNLOCKED transaction that + ** requires a snapshot upgrade. If so, skip any auto-vacuum + ** processing. */ + if( pBt->autoVacuum && ( + 0==pBt->db->bUnlocked + || 0==sqlite3PagerCommitRequiresUpgrade(pBt->pPager) + )){ rc = autoVacuumCommit(pBt); if( rc!=SQLITE_OK ){ sqlite3BtreeLeave(p); @@ -3682,7 +3692,9 @@ int sqlite3BtreeCommitPhaseOne(Btree *p, const char *zMaster){ sqlite3PagerTruncateImage(pBt->pPager, pBt->nPage); } #endif - rc = sqlite3PagerCommitPhaseOne(pBt->pPager, zMaster, 0); + if( rc==SQLITE_OK ){ + rc = sqlite3PagerCommitPhaseOne(pBt->pPager, zMaster, 0); + } sqlite3BtreeLeave(p); } return rc; @@ -9578,3 +9590,15 @@ int sqlite3BtreeIsReadonly(Btree *p){ ** Return the size of the header added to each page by this module. */ int sqlite3HeaderSizeBtree(void){ return ROUND8(sizeof(MemPage)); } + +int sqlite3BtreeExclusiveLock(Btree *p){ + int rc; + BtShared *pBt = p->pBt; + sqlite3BtreeEnter(p); + rc = sqlite3PagerExclusiveLock(pBt->pPager, pBt->pPage1->pDbPage); + sqlite3BtreeLeave(p); + return rc; +} + + + diff --git a/src/btree.h b/src/btree.h index 3edc2b3b57..ae9b2c4e59 100644 --- a/src/btree.h +++ b/src/btree.h @@ -270,5 +270,6 @@ void sqlite3BtreeCursorList(Btree*); # define sqlite3SchemaMutexHeld(X,Y,Z) 1 #endif +int sqlite3BtreeExclusiveLock(Btree*); #endif /* _BTREE_H_ */ diff --git a/src/build.c b/src/build.c index 2936805365..0300e2fc15 100644 --- a/src/build.c +++ b/src/build.c @@ -3828,13 +3828,13 @@ void sqlite3BeginTransaction(Parse *pParse, int type){ } v = sqlite3GetVdbe(pParse); if( !v ) return; - if( type!=TK_DEFERRED ){ + if( type==TK_IMMEDIATE || type==TK_EXCLUSIVE ){ for(i=0; inDb; i++){ sqlite3VdbeAddOp2(v, OP_Transaction, i, (type==TK_EXCLUSIVE)+1); sqlite3VdbeUsesBtree(v, i); } } - sqlite3VdbeAddOp2(v, OP_AutoCommit, 0, 0); + sqlite3VdbeAddOp3(v, OP_AutoCommit, 0, 0, (type==TK_UNLOCKED)); } /* diff --git a/src/main.c b/src/main.c index 36206eec8c..27bb2f0e83 100644 --- a/src/main.c +++ b/src/main.c @@ -2737,6 +2737,7 @@ static int openDatabase( memcpy(db->aLimit, aHardLimit, sizeof(db->aLimit)); db->aLimit[SQLITE_LIMIT_WORKER_THREADS] = SQLITE_DEFAULT_WORKER_THREADS; db->autoCommit = 1; + db->bUnlocked = 0; db->nextAutovac = -1; db->szMmap = sqlite3GlobalConfig.szMmap; db->nextPagesize = 0; diff --git a/src/pager.c b/src/pager.c index 060edb8d1d..2ea3b90a96 100644 --- a/src/pager.c +++ b/src/pager.c @@ -3066,6 +3066,7 @@ static int pagerWalFrames( ** list here. */ PgHdr **ppNext = &pList; nList = 0; + for(p=pList; (*ppNext = p)!=0; p=p->pDirty){ if( p->pgno<=nTruncate ){ ppNext = &p->pDirty; @@ -4081,7 +4082,7 @@ static int syncJournal(Pager *pPager, int newHdr){ assert( assert_pager_state(pPager) ); assert( !pagerUseWal(pPager) ); - rc = sqlite3PagerExclusiveLock(pPager); + rc = sqlite3PagerExclusiveLock(pPager, 0); if( rc!=SQLITE_OK ) return rc; if( !pPager->noSync ){ @@ -4429,6 +4430,10 @@ static int pagerStress(void *p, PgHdr *pPg){ pPg->pDirty = 0; if( pagerUseWal(pPager) ){ + /* If the transaction is a "BEGIN UNLOCKED" transaction, the page + ** cannot be flushed to disk. Return early in this case. */ + if( sqlite3WalIsInTrans(pPager->pWal)==0 ) return SQLITE_OK; + /* Write a single frame for this page to the log. */ rc = subjournalPageIfRequired(pPg); if( rc==SQLITE_OK ){ @@ -5548,11 +5553,14 @@ static int pager_open_journal(Pager *pPager){ ** Begin a write-transaction on the specified pager object. If a ** write-transaction has already been opened, this function is a no-op. ** -** If the exFlag argument is false, then acquire at least a RESERVED -** lock on the database file. If exFlag is true, then acquire at least +** If the exFlag argument is 0, then acquire at least a RESERVED +** lock on the database file. If exFlag is >0, then acquire at least ** an EXCLUSIVE lock. If such a lock is already held, no locking ** functions need be called. ** +** If (exFlag<0) and the database is in WAL mode, do not take any locks. +** The transaction will run in UNLOCKED mode instead. +** ** If the subjInMemory argument is non-zero, then any sub-journal opened ** within this transaction will be opened as an in-memory file. This ** has no effect if the sub-journal is already opened (as it may be when @@ -5588,7 +5596,9 @@ int sqlite3PagerBegin(Pager *pPager, int exFlag, int subjInMemory){ ** The busy-handler is not invoked if another connection already ** holds the write-lock. If possible, the upper layer will call it. */ - rc = sqlite3WalBeginWriteTransaction(pPager->pWal); + if( exFlag>=0 ){ + rc = sqlite3WalBeginWriteTransaction(pPager->pWal); + } }else{ /* Obtain a RESERVED lock on the database file. If the exFlag parameter ** is true, then immediately upgrade this to an EXCLUSIVE lock. The @@ -5596,7 +5606,7 @@ int sqlite3PagerBegin(Pager *pPager, int exFlag, int subjInMemory){ ** lock, but not when obtaining the RESERVED lock. */ rc = pagerLockDb(pPager, RESERVED_LOCK); - if( rc==SQLITE_OK && exFlag ){ + if( rc==SQLITE_OK && exFlag>0 ){ rc = pager_wait_on_lock(pPager, EXCLUSIVE_LOCK); } } @@ -6056,7 +6066,7 @@ int sqlite3PagerSync(Pager *pPager, const char *zMaster){ ** Otherwise, either SQLITE_BUSY or an SQLITE_IOERR_XXX error code is ** returned. */ -int sqlite3PagerExclusiveLock(Pager *pPager){ +int sqlite3PagerExclusiveLock(Pager *pPager, PgHdr *pPage1){ int rc = SQLITE_OK; assert( pPager->eState==PAGER_WRITER_CACHEMOD || pPager->eState==PAGER_WRITER_DBMOD @@ -6065,10 +6075,52 @@ int sqlite3PagerExclusiveLock(Pager *pPager){ assert( assert_pager_state(pPager) ); if( 0==pagerUseWal(pPager) ){ rc = pager_wait_on_lock(pPager, EXCLUSIVE_LOCK); + }else{ + Wal *pWal = pPager->pWal; + if( 0==sqlite3WalIsInTrans(pWal) ){ + /* TODO: There must be an optimization opportunity here, as this call + ** to PcacheDirtyList() sorts the list of dirty pages, even though it + ** is not really required - and will be sorted again in CommitPhaseOne() + ** in any case. */ + PgHdr *pList = sqlite3PcacheDirtyList(pPager->pPCache); + + /* This is an UNLOCKED transaction. Attempt to lock the wal database + ** here. If SQLITE_BUSY (but not SQLITE_BUSY_SNAPSHOT) is returned, + ** invoke the busy-handler and try again for as long as it returns + ** non-zero. */ + do { + /* rc = sqlite3WalBeginWriteTransaction(pWal); */ + rc = sqlite3WalLockForCommit(pWal, pList, pPage1); + }while( rc==SQLITE_BUSY + && pPager->xBusyHandler(pPager->pBusyHandlerArg) + ); + } } return rc; } +/* +** If this is a WAL mode connection and the WRITER lock is currently held, +** relinquish it. +*/ +void sqlite3PagerDropExclusiveLock(Pager *pPager){ + if( pagerUseWal(pPager) ){ + sqlite3WalEndWriteTransaction(pPager->pWal); + } +} + +/* +** Return true if this is a WAL database and snapshot upgrade is required +** before the current transaction can be committed. +*/ +int sqlite3PagerCommitRequiresUpgrade(Pager *pPager){ + int res = 0; + if( pagerUseWal(pPager) ){ + res = sqlite3WalCommitRequiresUpgrade(pPager->pWal); + } + return res; +} + /* ** Sync the database file for the pager pPager. zMaster points to the name ** of a master journal file that should be written into the individual diff --git a/src/pager.h b/src/pager.h index e3b57f435e..57d2812701 100644 --- a/src/pager.h +++ b/src/pager.h @@ -150,7 +150,7 @@ void *sqlite3PagerGetExtra(DbPage *); void sqlite3PagerPagecount(Pager*, int*); int sqlite3PagerBegin(Pager*, int exFlag, int); int sqlite3PagerCommitPhaseOne(Pager*,const char *zMaster, int); -int sqlite3PagerExclusiveLock(Pager*); +int sqlite3PagerExclusiveLock(Pager*, DbPage *pPage1); int sqlite3PagerSync(Pager *pPager, const char *zMaster); int sqlite3PagerCommitPhaseTwo(Pager*); int sqlite3PagerRollback(Pager*); @@ -158,6 +158,9 @@ int sqlite3PagerOpenSavepoint(Pager *pPager, int n); int sqlite3PagerSavepoint(Pager *pPager, int op, int iSavepoint); int sqlite3PagerSharedLock(Pager *pPager); +void sqlite3PagerDropExclusiveLock(Pager*); +int sqlite3PagerCommitRequiresUpgrade(Pager*); + #ifndef SQLITE_OMIT_WAL int sqlite3PagerCheckpoint(Pager *pPager, int, int*, int*); int sqlite3PagerWalSupported(Pager *pPager); diff --git a/src/parse.y b/src/parse.y index d7aa763683..db40b70b9c 100644 --- a/src/parse.y +++ b/src/parse.y @@ -121,6 +121,7 @@ transtype(A) ::= . {A = TK_DEFERRED;} transtype(A) ::= DEFERRED(X). {A = @X;} transtype(A) ::= IMMEDIATE(X). {A = @X;} transtype(A) ::= EXCLUSIVE(X). {A = @X;} +transtype(A) ::= UNLOCKED(X). {A = @X;} cmd ::= COMMIT trans_opt. {sqlite3CommitTransaction(pParse);} cmd ::= END trans_opt. {sqlite3CommitTransaction(pParse);} cmd ::= ROLLBACK trans_opt. {sqlite3RollbackTransaction(pParse);} diff --git a/src/sqliteInt.h b/src/sqliteInt.h index d26cd19eb7..2480bb0cfe 100644 --- a/src/sqliteInt.h +++ b/src/sqliteInt.h @@ -1147,6 +1147,7 @@ struct sqlite3 { u16 dbOptFlags; /* Flags to enable/disable optimizations */ u8 enc; /* Text encoding */ u8 autoCommit; /* The auto-commit flag. */ + u8 bUnlocked; /* Current transaction is "UNLOCKED" */ u8 temp_store; /* 1: file 2: memory 0: default */ u8 mallocFailed; /* True if we have seen a malloc failure */ u8 dfltLockMode; /* Default locking-mode for attached dbs */ diff --git a/src/vacuum.c b/src/vacuum.c index adc802e60b..10f14cbf47 100644 --- a/src/vacuum.c +++ b/src/vacuum.c @@ -356,6 +356,7 @@ end_of_vacuum: ** is closed by the DETACH. */ db->autoCommit = 1; + db->bUnlocked = 0; if( pDb ){ sqlite3BtreeClose(pDb->pBt); diff --git a/src/vdbe.c b/src/vdbe.c index fe97087c09..b400c425b8 100644 --- a/src/vdbe.c +++ b/src/vdbe.c @@ -2899,6 +2899,7 @@ case OP_Savepoint: { if( (rc = sqlite3VdbeCheckFk(p, 1))!=SQLITE_OK ){ goto vdbe_return; } + assert( db->bUnlocked==0 ); db->autoCommit = 1; if( sqlite3VdbeHalt(p)==SQLITE_BUSY ){ p->pc = (int)(pOp - aOp); @@ -2970,29 +2971,39 @@ case OP_Savepoint: { break; } -/* Opcode: AutoCommit P1 P2 * * * +/* Opcode: AutoCommit P1 P2 P3 * * ** ** Set the database auto-commit flag to P1 (1 or 0). If P2 is true, roll ** back any currently active btree transactions. If there are any active ** VMs (apart from this one), then a ROLLBACK fails. A COMMIT fails if ** there are active writing VMs or active VMs that use shared cache. ** +** If P3 is non-zero, then this instruction is being executed as part of +** a "BEGIN UNLOCKED" command. +** ** This instruction causes the VM to halt. */ case OP_AutoCommit: { int desiredAutoCommit; int iRollback; int turnOnAC; + int bUnlocked; + int hrc; desiredAutoCommit = pOp->p1; iRollback = pOp->p2; + bUnlocked = pOp->p3; turnOnAC = desiredAutoCommit && !db->autoCommit; assert( desiredAutoCommit==1 || desiredAutoCommit==0 ); assert( desiredAutoCommit==1 || iRollback==0 ); + assert( desiredAutoCommit==0 || bUnlocked==0 ); + assert( db->autoCommit==0 || db->bUnlocked==0 ); assert( db->nVdbeActive>0 ); /* At least this one VM is active */ assert( p->bIsReader ); - if( turnOnAC && !iRollback && db->nVdbeWrite>0 ){ + if( turnOnAC && !iRollback && + (db->nVdbeWrite>0 || (db->bUnlocked && db->nVdbeActive>1)) + ){ /* If this instruction implements a COMMIT and other VMs are writing ** return an error indicating that the other VMs must complete first. */ @@ -3004,16 +3015,20 @@ case OP_AutoCommit: { assert( desiredAutoCommit==1 ); sqlite3RollbackAll(db, SQLITE_ABORT_ROLLBACK); db->autoCommit = 1; + db->bUnlocked = 0; }else if( (rc = sqlite3VdbeCheckFk(p, 1))!=SQLITE_OK ){ goto vdbe_return; }else{ db->autoCommit = (u8)desiredAutoCommit; - if( sqlite3VdbeHalt(p)==SQLITE_BUSY ){ + hrc = sqlite3VdbeHalt(p); + if( (hrc & 0xFF)==SQLITE_BUSY ){ p->pc = (int)(pOp - aOp); db->autoCommit = (u8)(1-desiredAutoCommit); - p->rc = rc = SQLITE_BUSY; + p->rc = hrc; + rc = SQLITE_BUSY; goto vdbe_return; } + db->bUnlocked = (u8)bUnlocked; } assert( db->nStatement==0 ); sqlite3CloseSavepoints(db); @@ -3208,9 +3223,16 @@ case OP_SetCookie: { /* in3 */ /* See note about index shifting on OP_ReadCookie */ rc = sqlite3BtreeUpdateMeta(pDb->pBt, pOp->p2, (int)pIn3->u.i); if( pOp->p2==BTREE_SCHEMA_VERSION ){ - /* When the schema cookie changes, record the new cookie internally */ - pDb->pSchema->schema_cookie = (int)pIn3->u.i; - db->flags |= SQLITE_InternChanges; + if( db->bUnlocked ){ + sqlite3VdbeError(p, "cannot modify database schema - " + "UNLOCKED transaction" + ); + rc = SQLITE_ERROR; + }else{ + /* When the schema cookie changes, record the new cookie internally */ + pDb->pSchema->schema_cookie = (int)pIn3->u.i; + db->flags |= SQLITE_InternChanges; + } }else if( pOp->p2==BTREE_FILE_FORMAT ){ /* Record changes in the file format */ pDb->pSchema->file_format = (u8)pIn3->u.i; diff --git a/src/vdbeaux.c b/src/vdbeaux.c index 08dc885df6..f5f9d0fbdd 100644 --- a/src/vdbeaux.c +++ b/src/vdbeaux.c @@ -2020,11 +2020,24 @@ static int vdbeCommit(sqlite3 *db, Vdbe *p){ if( sqlite3BtreeIsInTrans(pBt) ){ needXcommit = 1; if( i!=1 ) nTrans++; - sqlite3BtreeEnter(pBt); - rc = sqlite3PagerExclusiveLock(sqlite3BtreePager(pBt)); - sqlite3BtreeLeave(pBt); + rc = sqlite3BtreeExclusiveLock(pBt); } } + + if( db->bUnlocked && (rc & 0xFF)==SQLITE_BUSY ){ + /* An SQLITE_BUSY or SQLITE_BUSY_SNAPSHOT was encountered while + ** attempting to take the WRITER lock on a wal file. Release the + ** WRITER locks on all wal files and return early. */ + for(i=0; inDb; i++){ + Btree *pBt = db->aDb[i].pBt; + if( sqlite3BtreeIsInTrans(pBt) ){ + sqlite3BtreeEnter(pBt); + sqlite3PagerDropExclusiveLock(sqlite3BtreePager(pBt)); + sqlite3BtreeLeave(pBt); + } + } + } + if( rc!=SQLITE_OK ){ return rc; } @@ -2427,6 +2440,7 @@ int sqlite3VdbeHalt(Vdbe *p){ sqlite3RollbackAll(db, SQLITE_ABORT_ROLLBACK); sqlite3CloseSavepoints(db); db->autoCommit = 1; + db->bUnlocked = 0; p->nChange = 0; } } @@ -2462,9 +2476,9 @@ int sqlite3VdbeHalt(Vdbe *p){ ** is required. */ rc = vdbeCommit(db, p); } - if( rc==SQLITE_BUSY && p->readOnly ){ + if( (rc & 0xFF)==SQLITE_BUSY && p->readOnly ){ sqlite3VdbeLeave(p); - return SQLITE_BUSY; + return rc; }else if( rc!=SQLITE_OK ){ p->rc = rc; sqlite3RollbackAll(db, SQLITE_OK); @@ -2489,6 +2503,7 @@ int sqlite3VdbeHalt(Vdbe *p){ sqlite3RollbackAll(db, SQLITE_ABORT_ROLLBACK); sqlite3CloseSavepoints(db); db->autoCommit = 1; + db->bUnlocked = 0; p->nChange = 0; } } @@ -2510,6 +2525,7 @@ int sqlite3VdbeHalt(Vdbe *p){ sqlite3RollbackAll(db, SQLITE_ABORT_ROLLBACK); sqlite3CloseSavepoints(db); db->autoCommit = 1; + db->bUnlocked = 0; p->nChange = 0; } } @@ -2554,7 +2570,7 @@ int sqlite3VdbeHalt(Vdbe *p){ } assert( db->nVdbeActive>0 || db->autoCommit==0 || db->nStatement==0 ); - return (p->rc==SQLITE_BUSY ? SQLITE_BUSY : SQLITE_OK); + return ((p->rc & 0xFF)==SQLITE_BUSY ? SQLITE_BUSY : SQLITE_OK); } diff --git a/src/wal.c b/src/wal.c index f7e2594001..a2200e7005 100644 --- a/src/wal.c +++ b/src/wal.c @@ -2357,38 +2357,19 @@ void sqlite3WalEndReadTransaction(Wal *pWal){ } /* -** Search the wal file for page pgno. If found, set *piRead to the frame that -** contains the page. Otherwise, if pgno is not in the wal file, set *piRead -** to zero. -** -** Return SQLITE_OK if successful, or an error code if an error occurs. If an -** error does occur, the final value of *piRead is undefined. -*/ -int sqlite3WalFindFrame( - Wal *pWal, /* WAL handle */ - Pgno pgno, /* Database page number to read data for */ - u32 *piRead /* OUT: Frame number (or zero) */ +** Search the hash tables for an entry matching page number pgno. Ignore +** any entries that lie after frame iLast within the wal file. +*/ +static int walFindFrame( + Wal *pWal, + Pgno pgno, + u32 iLast, + u32 *piRead ){ - u32 iRead = 0; /* If !=0, WAL frame to return data from */ - u32 iLast = pWal->hdr.mxFrame; /* Last page in WAL for this reader */ int iHash; /* Used to loop through N hash tables */ + u32 iRead = 0; - /* This routine is only be called from within a read transaction. */ - assert( pWal->readLock>=0 || pWal->lockError ); - - /* If the "last page" field of the wal-index header snapshot is 0, then - ** no data will be read from the wal under any circumstances. Return early - ** in this case as an optimization. Likewise, if pWal->readLock==0, - ** then the WAL is ignored by the reader so return early, as if the - ** WAL were empty. - */ - if( iLast==0 || pWal->readLock==0 ){ - *piRead = 0; - return SQLITE_OK; - } - - /* Search the hash table or tables for an entry matching page number - ** pgno. Each iteration of the following for() loop searches one + /* Each iteration of the following for() loop searches one ** hash table (each hash table indexes up to HASHTABLE_NPAGE frames). ** ** This code might run concurrently to the code in walIndexAppend() @@ -2437,11 +2418,48 @@ int sqlite3WalFindFrame( } } + *piRead = iRead; + return SQLITE_OK; +} + +/* +** Search the wal file for page pgno. If found, set *piRead to the frame that +** contains the page. Otherwise, if pgno is not in the wal file, set *piRead +** to zero. +** +** Return SQLITE_OK if successful, or an error code if an error occurs. If an +** error does occur, the final value of *piRead is undefined. +*/ +int sqlite3WalFindFrame( + Wal *pWal, /* WAL handle */ + Pgno pgno, /* Database page number to read data for */ + u32 *piRead /* OUT: Frame number (or zero) */ +){ + u32 iRead = 0; /* If !=0, WAL frame to return data from */ + u32 iLast = pWal->hdr.mxFrame; /* Last page in WAL for this reader */ + int rc; + + /* This routine is only be called from within a read transaction. */ + assert( pWal->readLock>=0 || pWal->lockError ); + + /* If the "last page" field of the wal-index header snapshot is 0, then + ** no data will be read from the wal under any circumstances. Return early + ** in this case as an optimization. Likewise, if pWal->readLock==0, + ** then the WAL is ignored by the reader so return early, as if the + ** WAL were empty. + */ + if( iLast==0 || pWal->readLock==0 ){ + *piRead = 0; + return SQLITE_OK; + } + + rc = walFindFrame(pWal, pgno, iLast, &iRead); + #ifdef SQLITE_ENABLE_EXPENSIVE_ASSERT /* If expensive assert() statements are available, do a linear search ** of the wal-index file content. Make sure the results agree with the ** result obtained using the hash indexes above. */ - { + if( rc==SQLITE_OK ){ u32 iRead2 = 0; u32 iTest; for(iTest=iLast; iTest>0; iTest--){ @@ -2537,6 +2555,96 @@ int sqlite3WalBeginWriteTransaction(Wal *pWal){ return rc; } +/* +** TODO: Combine some code with BeginWriteTransaction() +** +** This function is only ever called when committing a "BEGIN UNLOCKED" +** transaction. It may be assumed that no frames have been written to +** the wal file. +*/ +int sqlite3WalLockForCommit(Wal *pWal, PgHdr *pList, PgHdr *pPage1){ + volatile WalIndexHdr *pHead; /* Head of the wal file */ + int rc; + + /* Cannot start a write transaction without first holding a read + ** transaction. */ + assert( pWal->readLock>=0 ); + + if( pWal->readOnly ){ + return SQLITE_READONLY; + } + + /* Only one writer allowed at a time. Get the write lock. Return + ** SQLITE_BUSY if unable. + */ + rc = walLockExclusive(pWal, WAL_WRITE_LOCK, 1, 0); + if( rc ){ + return rc; + } + pWal->writeLock = 1; + + /* If the database has been modified since this transaction was started, + ** check if it is still possible to commit. The transaction can be + ** committed if: + ** + ** a) None of the pages in pList have been modified since the + ** transaction opened, and + ** + ** b) The database schema cookie has not been modified since the + ** transaction was started. + */ + pHead = walIndexHdr(pWal); + if( memcmp(&pWal->hdr, (void*)pHead, sizeof(WalIndexHdr))!=0 ){ + /* TODO: Is this safe? Because it holds the WRITER lock this thread + ** has exclusive access to the live header, but might it be corrupt? */ + PgHdr *pPg; + u32 iLast = pHead->mxFrame; + for(pPg=pList; rc==SQLITE_OK && pPg; pPg=pPg->pDirty){ + u32 iSlot = 0; + rc = walFindFrame(pWal, pPg->pgno, iLast, &iSlot); + if( iSlot>pWal->hdr.mxFrame ){ + sqlite3_log(SQLITE_OK, + "cannot commit UNLOCKED transaction (conflict at page %d)", + (int)pPg->pgno + ); + rc = SQLITE_BUSY_SNAPSHOT; + } + } + + if( rc==SQLITE_OK ){ + /* Read the newest schema cookie from the wal file. */ + u32 iSlot = 0; + rc = walFindFrame(pWal, 1, iLast, &iSlot); + if( rc==SQLITE_OK && iSlot>pWal->hdr.mxFrame ){ + u8 aNew[4]; + u8 *aOld = &((u8*)pPage1->pData)[40]; + int sz; + i64 iOffset; + sz = pWal->hdr.szPage; + sz = (sz&0xfe00) + ((sz&0x0001)<<16); + iOffset = walFrameOffset(iSlot, sz) + WAL_FRAME_HDRSIZE + 40; + rc = sqlite3OsRead(pWal->pWalFd, aNew, sizeof(aNew), iOffset); + if( rc==SQLITE_OK && memcmp(aOld, aNew, sizeof(aNew)) ){ + /* TODO: New error code? SQLITE_BUSY_SCHEMA. */ + rc = SQLITE_BUSY_SNAPSHOT; + } + } + } + } + + return rc; +} + +/* +** The caller holds the WRITER lock. This function returns true if a snapshot +** upgrade is required before the transaction can be committed, or false +** otherwise. +*/ +int sqlite3WalCommitRequiresUpgrade(Wal *pWal){ + assert( pWal->writeLock ); + return memcmp(&pWal->hdr, (void*)walIndexHdr(pWal), sizeof(WalIndexHdr))!=0; +} + /* ** End a write transaction. The commit has already been done. This ** routine merely releases the lock. @@ -2564,7 +2672,7 @@ int sqlite3WalEndWriteTransaction(Wal *pWal){ */ int sqlite3WalUndo(Wal *pWal, int (*xUndo)(void *, Pgno), void *pUndoCtx){ int rc = SQLITE_OK; - if( ALWAYS(pWal->writeLock) ){ + if( pWal->writeLock ){ Pgno iMax = pWal->hdr.mxFrame; Pgno iFrame; @@ -2603,7 +2711,7 @@ int sqlite3WalUndo(Wal *pWal, int (*xUndo)(void *, Pgno), void *pUndoCtx){ ** point in the event of a savepoint rollback (via WalSavepointUndo()). */ void sqlite3WalSavepoint(Wal *pWal, u32 *aWalData){ - assert( pWal->writeLock ); + /* assert( pWal->writeLock ); */ aWalData[0] = pWal->hdr.mxFrame; aWalData[1] = pWal->hdr.aFrameCksum[0]; aWalData[2] = pWal->hdr.aFrameCksum[1]; @@ -2619,7 +2727,7 @@ void sqlite3WalSavepoint(Wal *pWal, u32 *aWalData){ int sqlite3WalSavepointUndo(Wal *pWal, u32 *aWalData){ int rc = SQLITE_OK; - assert( pWal->writeLock ); + assert( pWal->writeLock || aWalData[0]==pWal->hdr.mxFrame ); assert( aWalData[3]!=pWal->nCkpt || aWalData[0]<=pWal->hdr.mxFrame ); if( aWalData[3]!=pWal->nCkpt ){ @@ -2783,6 +2891,7 @@ int sqlite3WalFrames( int szFrame; /* The size of a single frame */ i64 iOffset; /* Next byte to write in WAL file */ WalWriter w; /* The writer */ + int bUpgrade = 0; /* True if commit requires snapshot upgrade */ assert( pList ); assert( pWal->writeLock ); @@ -2798,6 +2907,22 @@ int sqlite3WalFrames( } #endif + if( isCommit ){ + volatile WalIndexHdr *pHead = walIndexHdr(pWal); + if( pHead->mxFrame>pWal->hdr.mxFrame ){ + if( memcmp((void*)&pHead[0], (void*)&pHead[1], sizeof(WalIndexHdr))!=0 ){ + /* TODO: Deal with this case. It's quite possible, but fiddly. */ + return SQLITE_CORRUPT_BKPT; + } + memcpy(&pWal->hdr, (void*)pHead, sizeof(WalIndexHdr)); + if( nTruncatehdr.nPage ){ + /* Do not truncate the database file in this case */ + nTruncate = pWal->hdr.nPage; + } + bUpgrade = 1; + } + } + /* See if it is possible to write these frames into the start of the ** log file, instead of appending to it at pWal->hdr.mxFrame. */ @@ -2946,6 +3071,14 @@ int sqlite3WalFrames( } } + if( rc==SQLITE_OK && bUpgrade ){ + /* If this commit required a snapshot upgrade, the pager cache is + ** not currently consistent with the head of the wal file. Zeroing + ** Wal.hdr here forces the next transaction to reset the cache + ** before beginning to read the db. */ + memset(&pWal->hdr, 0, sizeof(WalIndexHdr)); + } + WALTRACE(("WAL%p: frame write %s\n", pWal, rc ? "failed" : "ok")); return rc; } @@ -3147,6 +3280,13 @@ int sqlite3WalHeapMemory(Wal *pWal){ return (pWal && pWal->exclusiveMode==WAL_HEAPMEMORY_MODE ); } +/* +** Return true if in a write transaction, false otherwise. +*/ +int sqlite3WalIsInTrans(Wal *pWal){ + return (int)pWal->writeLock; +} + #ifdef SQLITE_ENABLE_ZIPVFS /* ** If the argument is not NULL, it points to a Wal object that holds a diff --git a/src/wal.h b/src/wal.h index 092546354b..db341caa04 100644 --- a/src/wal.h +++ b/src/wal.h @@ -126,6 +126,11 @@ int sqlite3WalExclusiveMode(Wal *pWal, int op); */ int sqlite3WalHeapMemory(Wal *pWal); +/* Return true if the WRITER lock is held. False otherwise. */ +int sqlite3WalIsInTrans(Wal *pWal); +int sqlite3WalLockForCommit(Wal *pWal, PgHdr *pDirtyList, PgHdr *pPage1); +int sqlite3WalCommitRequiresUpgrade(Wal *pWal); + #ifdef SQLITE_ENABLE_ZIPVFS /* If the WAL file is not empty, return the number of bytes of content ** stored in each frame (i.e. the db page-size when the WAL was created). diff --git a/test/unlocked.test b/test/unlocked.test new file mode 100644 index 0000000000..e5f7868a20 --- /dev/null +++ b/test/unlocked.test @@ -0,0 +1,307 @@ +# 2015 July 26 +# +# 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. +# +#*********************************************************************** +# + +set testdir [file dirname $argv0] +source $testdir/tester.tcl +source $testdir/lock_common.tcl +set ::testprefix unlocked + + +do_execsql_test 1.0 { + PRAGMA journal_mode = wal; +} {wal} + +do_execsql_test 1.1 { + CREATE TABLE t1(k INTEGER PRIMARY KEY, v); + BEGIN UNLOCKED; + INSERT INTO t1 VALUES(1, 'abcd'); + COMMIT; +} + +do_execsql_test 1.2 { + SELECT * FROM t1; +} {1 abcd} + +do_execsql_test 1.3 { + BEGIN UNLOCKED; + INSERT INTO t1 VALUES(2, 'efgh'); + ROLLBACK; +} + +do_execsql_test 1.4 { + SELECT * FROM t1; +} {1 abcd} + + +#------------------------------------------------------------------------- +# UNLOCKED transactions cannot do cache spills. +# +foreach {tn trans spill} { + 1 {BEGIN UNLOCKED} 0 + 2 {BEGIN} 1 +} { + do_test 1.5.$tn { + sqlite3 db2 test.db + set walsz [file size test.db-wal] + + execsql { PRAGMA cache_size = 10 } db2 + execsql $trans db2 + execsql { + WITH cnt(i) AS (SELECT 1 UNION ALL SELECT i+1 FROM cnt WHERE i<50) + INSERT INTO t1(v) SELECT randomblob(900) FROM cnt; + } db2 + + expr {[file size test.db-wal]==$walsz} + } [expr !$spill] + + execsql ROLLBACK db2 + db2 close +} + +#------------------------------------------------------------------------- +# UNLOCKED transactions man not be committed while there are active +# readers. +do_execsql_test 1.6.setup { + DROP TABLE t1; + CREATE TABLE t1(a, b); + INSERT INTO t1 VALUES(1, 2); + INSERT INTO t1 VALUES(3, 4); + INSERT INTO t1 VALUES(5, 6); +} +foreach {tn trans commit_ok} { + 1 {BEGIN UNLOCKED} 0 + 2 {BEGIN} 1 +} { + do_test 1.6.$tn.1 { + set stmt [sqlite3_prepare db "SELECT * FROM t1" -1 dummy] + sqlite3_step $stmt + } SQLITE_ROW + do_test 1.6.$tn.2 { + execsql $trans + execsql { INSERT INTO t1 VALUES(7, 8) } + } {} + + if { $commit_ok } { + do_test 1.6.$tn.3 { catchsql COMMIT } {0 {}} + } else { + do_test 1.6.$tn.4 { catchsql COMMIT } {/1 {cannot commit transaction .*}/} + } + + sqlite3_finalize $stmt + catchsql ROLLBACK +} + +#------------------------------------------------------------------------- +# UNLOCKED transactions may not modify the db schema. +# +foreach {tn sql} { + 1 { CREATE TABLE xx(a, b) } + 2 { DROP TABLE t1 } +} { + do_catchsql_test 1.7.$tn.1 " + BEGIN UNLOCKED; + $sql + " {1 {cannot modify database schema - UNLOCKED transaction}} + + do_execsql_test 1.7.$tn.2 ROLLBACK +} + + +do_multiclient_test tn { + + #----------------------------------------------------------------------- + # 1. Start an UNLOCKED transaction using [db1]. + # + # 2. Start and then rollback a regular transaction using [db2]. This + # can be done as the ongoing [db1] transaction is UNLOCKED. + # + # 3. The [db1] transaction can now be committed, as [db2] has relinquished + # the write lock. + # + do_test 2.$tn.1.1 { + sql1 { + PRAGMA journal_mode = wal; + CREATE TABLE t1(k INTEGER PRIMARY KEY, v); + INSERT INTO t1 VALUES(1, 'one'); + } + sql1 { + BEGIN UNLOCKED; + INSERT INTO t1 VALUES(2, 'two'); + } + code1 { sqlite3_get_autocommit db } + } 0 + + do_test 2.$tn.1.2 { + sql2 { + BEGIN; + INSERT INTO t1 VALUES(3, 'three'); + ROLLBACK; + } + } {} + + do_test 2.$tn.1.3 { + sql1 COMMIT + sql2 { SELECT * FROM t1 } + } {1 one 2 two} + + #----------------------------------------------------------------------- + # 1. Start an UNLOCKED transaction using [db1]. + # + # 2. Commit a transaction using [db2]. + # + # 3. Try to commit with [db1]. Check that SQLITE_BUSY_SNAPSHOT is returned, + # and the transaction is not rolled back. + # + do_test 2.$tn.2.1 { + sql1 { + BEGIN UNLOCKED; + INSERT INTO t1 VALUES(-1, 'hello world'); + } + } {} + + do_test 2.$tn.2.2 { + sql2 { + INSERT INTO t1 VALUES(3, 'three'); + } + } {} + + do_test 2.$tn.2.3.1 { + set rc [catch { sql1 COMMIT } msg] + list $rc $msg + } {1 {database is locked}} + + do_test 2.$tn.2.3.2 { + code1 { list [sqlite3_extended_errcode db] [sqlite3_get_autocommit db] } + } {SQLITE_BUSY_SNAPSHOT 0} + + do_test 2.$tn.2.3.3 { + sql1 { + SELECT * FROM t1; + ROLLBACK; + } + } {-1 {hello world} 1 one 2 two} + + #----------------------------------------------------------------------- + # 1. Start an UNLOCKED transaction using [db1]. + # + # 2. Open a transaction using [db2]. + # + # 3. Try to commit with [db1]. Check that SQLITE_BUSY is returned, + # and the transaction is not rolled back. + # + # 4. Have [db2] roll its transaction back. Then check that [db1] can + # commit. + # + do_test 2.$tn.3.1 { + sql1 { + BEGIN UNLOCKED; + INSERT INTO t1 VALUES(4, 'four'); + } + } {} + + do_test 2.$tn.3.2 { + sql2 { + BEGIN; + INSERT INTO t1 VALUES(-1, 'xyz'); + } + } {} + + do_test 2.$tn.3.3.1 { + set rc [catch { sql1 COMMIT } msg] + list $rc $msg + } {1 {database is locked}} + + do_test 2.$tn.3.3.2 { + code1 { list [sqlite3_extended_errcode db] [sqlite3_get_autocommit db] } + } {SQLITE_BUSY 0} + + do_test 2.$tn.3.3.3 { + sql1 { SELECT * FROM t1; } + } {1 one 2 two 3 three 4 four} + + do_test 2.$tn.3.4 { + sql2 ROLLBACK + sql1 COMMIT + sql1 { SELECT * FROM t1; } + } {1 one 2 two 3 three 4 four} + + #----------------------------------------------------------------------- + # 1. Create a second table - t2. + # + # 2. Write to t1 with [db] and t2 with [db2]. + # + # 3. See if it worked. + # + do_test 2.$tn.4.1 { + sql1 { CREATE TABLE t2(a, b) } + } {} + do_test 2.$tn.4.2 { + sql2 { + BEGIN UNLOCKED; + INSERT INTO t2 VALUES('i', 'n'); + } + + sql1 { + BEGIN UNLOCKED; + INSERT INTO t1 VALUES(5, 'five'); + COMMIT; + } + + sql2 COMMIT + } {} + + do_test 2.$tn.4.3.1 { + sql2 {SELECT * FROM t1} + } {1 one 2 two 3 three 4 four 5 five} + do_test 2.$tn.4.3.2 { + sql1 {SELECT * FROM t1} + } {1 one 2 two 3 three 4 four 5 five} + + do_test 2.$tn.4.3.3 { sql2 {SELECT * FROM t2} } {i n} + do_test 2.$tn.4.3.4 { sql1 {SELECT * FROM t2} } {i n} + + #----------------------------------------------------------------------- + # The "schema cookie" issue. + # + # 1. Begin and UNLOCKED write to "t1" using [db] + # + # 2. Create an index on t1 using [db2]. + # + # 3. Attempt to commit the UNLOCKED write. This is an SQLITE_BUSY_SNAPSHOT, + # even though there is no page collision. + # + + do_test 2.$tn.5.1 { + sql1 { + BEGIN UNLOCKED; + INSERT INTO t1 VALUES(6, 'six'); + } + } {} + + do_test 2.$tn.5.2 { + sql2 { CREATE INDEX i1 ON t1(v); } + } {} + + do_test 2.$tn.5.3 { + list [catch { sql1 { COMMIT } } msg] $msg [sqlite3_errcode db] + } {1 {database is locked} SQLITE_BUSY_SNAPSHOT} + + do_test 2.$tn.5.4 { + sql2 { PRAGMA integrity_check } + } {ok} + catch { sql1 ROLLBACK } + +} + + + +finish_test diff --git a/tool/mkkeywordhash.c b/tool/mkkeywordhash.c index 721611f5a3..d1810cbeb9 100644 --- a/tool/mkkeywordhash.c +++ b/tool/mkkeywordhash.c @@ -262,6 +262,7 @@ static Keyword aKeywordTable[] = { { "TRIGGER", "TK_TRIGGER", TRIGGER }, { "UNION", "TK_UNION", COMPOUND }, { "UNIQUE", "TK_UNIQUE", ALWAYS }, + { "UNLOCKED", "TK_UNLOCKED", ALWAYS }, { "UPDATE", "TK_UPDATE", ALWAYS }, { "USING", "TK_USING", ALWAYS }, { "VACUUM", "TK_VACUUM", VACUUM },