-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
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
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
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
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
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
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
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
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
-b8e92227a469de677a66da62e4361f099c0b79d0
\ No newline at end of file
+8079421604dbd40d03471dad6d12115119b554c2
\ No newline at end of file
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);
}
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);
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;
** 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;
+}
+
+
+
# define sqlite3SchemaMutexHeld(X,Y,Z) 1
#endif
+int sqlite3BtreeExclusiveLock(Btree*);
#endif /* _BTREE_H_ */
}
v = sqlite3GetVdbe(pParse);
if( !v ) return;
- if( type!=TK_DEFERRED ){
+ if( type==TK_IMMEDIATE || type==TK_EXCLUSIVE ){
for(i=0; i<db->nDb; 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));
}
/*
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;
** list here. */
PgHdr **ppNext = &pList;
nList = 0;
+
for(p=pList; (*ppNext = p)!=0; p=p->pDirty){
if( p->pgno<=nTruncate ){
ppNext = &p->pDirty;
assert( assert_pager_state(pPager) );
assert( !pagerUseWal(pPager) );
- rc = sqlite3PagerExclusiveLock(pPager);
+ rc = sqlite3PagerExclusiveLock(pPager, 0);
if( rc!=SQLITE_OK ) return rc;
if( !pPager->noSync ){
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 ){
** 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
** 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
** 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);
}
}
** 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
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
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*);
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);
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);}
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 */
** is closed by the DETACH.
*/
db->autoCommit = 1;
+ db->bUnlocked = 0;
if( pDb ){
sqlite3BtreeClose(pDb->pBt);
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);
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.
*/
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);
/* 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;
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; i<db->nDb; i++){
+ Btree *pBt = db->aDb[i].pBt;
+ if( sqlite3BtreeIsInTrans(pBt) ){
+ sqlite3BtreeEnter(pBt);
+ sqlite3PagerDropExclusiveLock(sqlite3BtreePager(pBt));
+ sqlite3BtreeLeave(pBt);
+ }
+ }
+ }
+
if( rc!=SQLITE_OK ){
return rc;
}
sqlite3RollbackAll(db, SQLITE_ABORT_ROLLBACK);
sqlite3CloseSavepoints(db);
db->autoCommit = 1;
+ db->bUnlocked = 0;
p->nChange = 0;
}
}
** 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);
sqlite3RollbackAll(db, SQLITE_ABORT_ROLLBACK);
sqlite3CloseSavepoints(db);
db->autoCommit = 1;
+ db->bUnlocked = 0;
p->nChange = 0;
}
}
sqlite3RollbackAll(db, SQLITE_ABORT_ROLLBACK);
sqlite3CloseSavepoints(db);
db->autoCommit = 1;
+ db->bUnlocked = 0;
p->nChange = 0;
}
}
}
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);
}
}
/*
-** 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()
}
}
+ *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--){
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.
*/
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;
** 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];
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 ){
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 );
}
#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( nTruncate<pWal->hdr.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.
*/
}
}
+ 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;
}
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
*/
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).
--- /dev/null
+# 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
{ "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 },