From 980b1a74051196303d4b10df9d0528490d94ec1d Mon Sep 17 00:00:00 2001 From: drh Date: Wed, 16 Aug 2006 16:42:48 +0000 Subject: [PATCH] Allows UPDATE, INSERT, and DELETEs to occur while a SELECT is pending on the same table. (CVS 3355) FossilOrigin-Name: 8c52d2ad468615e50a727adab2977a0bef1bc068 --- manifest | 24 ++--- manifest.uuid | 2 +- src/btree.c | 259 ++++++++++++++++++++++------------------------ test/btree.test | 16 ++- test/capi2.test | 10 +- test/capi3.test | 12 ++- test/delete2.test | 10 +- test/lock.test | 20 ++-- test/misc2.test | 114 ++++++++++++++------ 9 files changed, 261 insertions(+), 206 deletions(-) diff --git a/manifest b/manifest index ed49ee372b..53a64f0824 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Tighten\san\sassert\s(ticket\s#1920).\s\sChange\sto\s"sqlite3.h"\sfrom\s\non\sthe\ssqlite3ext.h\sheader\s(ticket\s#1916).\s\sFix\sa\sbug\sin\sthe\stest\sscripts.\s(CVS\s3354) -D 2006-08-15T14:21:16 +C Allows\sUPDATE,\sINSERT,\sand\sDELETEs\sto\soccur\swhile\sa\sSELECT\sis\spending\son\nthe\ssame\stable.\s(CVS\s3355) +D 2006-08-16T16:42:48 F Makefile.in 986db66b0239b460fc118e7d2fa88b45b26c444e F Makefile.linux-gcc 2d8574d1ba75f129aba2019f0b959db380a90935 F README 9c4e2d6706bdcc3efdd773ce752a8cdab4f90028 @@ -34,7 +34,7 @@ F src/alter.c eba661e77bfd00282fbfa316cdb6aef04856fedc F src/analyze.c 7d2b7ab9a9c2fd6e55700f69064dfdd3e36d7a8a F src/attach.c b11eb4d5d3fb99a10a626956bccc7215f6b68b16 F src/auth.c 902f4722661c796b97f007d9606bd7529c02597f -F src/btree.c b39b7147d400b4906a48850b83d22b0c2a641007 +F src/btree.c 8f18bb08f84d6a12104fd0f01255febe6289a186 F src/btree.h 061c50e37de7f50b58528e352d400cf33ead7418 F src/build.c 4359b34a36938716ed10ac037eec9dc5173b8f4b F src/callback.c fd9bb39f7ff6b52bad8365617abc61c720640429 @@ -133,7 +133,7 @@ F test/bigrow.test f0aeb7573dcb8caaafea76454be3ade29b7fc747 F test/bind.test 941a424e7722dd8994c2d503b28d00e6a8f87f23 F test/bindxfer.test b76bfb7df68bb0b238039f4543a84e9612291b54 F test/blob.test 28c3b25150684ee3d108bb78cfb67a472deef2f0 -F test/btree.test b1957e39f4858b0722dc0f70f926a2143d3b25f9 +F test/btree.test 099978c3b9f0a203f4805d2bb8fdb042d5cb8ffc F test/btree2.test 4b56a2a4a4f84d68c77aef271223a713bf5ebafc F test/btree4.test 3797b4305694c7af6828675b0f4b1424b8ca30e4 F test/btree5.test 8e5ff32c02e685d36516c6499add9375fe1377f2 @@ -141,8 +141,8 @@ F test/btree6.test a5ede6bfbbb2ec8b27e62813612c0f28e8f3e027 F test/btree7.test a6d3b842db22af97dd14b989e90a2fd96066b72f F test/btree8.test fadc112bcbd6a0c622d34c813fc8a648eacf8804 F test/busy.test 0271c854738e23ad76e10d4096a698e5af29d211 -F test/capi2.test cddd151c7b687e9e00fde408b9547ec93c2146a4 -F test/capi3.test 0d26e0ef558e3d409258f69dc74ca72f6a7aa76e +F test/capi2.test cb478885b8b1a6a9f703a9da1c8d7d101c0970d6 +F test/capi3.test 5f54824e8356ad25ee40a101b36452e74d68a945 F test/capi3b.test 5f0bc94b104e11086b1103b20277e1910f59c7f4 F test/cast.test f88e7b6946e9a467cf4bb142d92bb65a83747fc2 F test/check.test e5ea0c1a06c10e81e3434ca029e2c4a562f2b673 @@ -161,7 +161,7 @@ F test/crashtest1.c 09c1c7d728ccf4feb9e481671e29dda5669bbcc2 F test/date.test 288b41dbcc7aa114a976c53b45b78b3aa7736940 F test/default.test 252298e42a680146b1dd64f563b95bdf088d94fb F test/delete.test 525a6953bc3978780cae35f3eaf1027cf4ce887d -F test/delete2.test d20b08733243f1890079f3b48f2356fbb62212b2 +F test/delete2.test c06be3806ba804bc8c6f134476816080280b40e3 F test/delete3.test 555e84a00a99230b7d049d477a324a631126a6ab F test/descidx1.test 2177c4ad55edcf56ad5f4c6490f307d7774e8a10 F test/descidx2.test eb3a2882ec58aa6e1e8131d9bb54436e5b4a3ce2 @@ -197,7 +197,7 @@ F test/laststmtchanges.test 19a6d0c11f7a31dc45465b495f7b845a62cbec17 F test/like.test 5f7d76574752a9101cac13372c8a85999d0d91e6 F test/limit.test 2a87b9cb2165abb49ca0ddcf5cb43cf24074581f F test/loadext.test 6e4ecf99ec26334768c63b4322177b5e147f006a -F test/lock.test 9b7afcb24f53d24da502abb33daaad2cd6d44107 +F test/lock.test 6825aea0b5885578b1b63a3b178803842c4ee9f1 F test/lock2.test d83ba79d3c4fffdb5b926c7d8ca7a36c34288a55 F test/lock3.test 615111293cf32aa2ed16d01c6611737651c96fb9 F test/main.test e7212ce1023957c7209778cc87fa932bd79ba89a @@ -213,7 +213,7 @@ F test/memdb.test a67bda4ff90a38f2b19f6c7f95aa7289e051d893 F test/memleak.test df2b2b96e77f8ba159a332299535b1e5f18e49ac F test/minmax.test 66434d8ee04869fe4c220b665b73748accbb9163 F test/misc1.test 27a6ad11ba6e4b73aeee650ab68053ad7dfd0433 -F test/misc2.test 09388e5a2c5c1017ad3ff1c4bf469375def2a0c2 +F test/misc2.test 9740c2fb7e4a69b2bebd4c5fd9ba45ae27b27e98 F test/misc3.test 7bd937e2c62bcc6be71939faf068d506467b1e03 F test/misc4.test b043a05dea037cca5989f3ae09552fa16119bc80 F test/misc5.test 83bceca3d38ed10ced00271e02b26b24795def83 @@ -377,7 +377,7 @@ F www/tclsqlite.tcl bb0d1357328a42b1993d78573e587c6dcbc964b9 F www/vdbe.tcl 87a31ace769f20d3627a64fa1fade7fed47b90d0 F www/version3.tcl 890248cf7b70e60c383b0e84d77d5132b3ead42b F www/whentouse.tcl 97e2b5cd296f7d8057e11f44427dea8a4c2db513 -P b4d53974c30d195c061cc7605a707d7d30c52740 -R 89af5dea7bfa8fd23f7d7d4bce8f2b2b +P 3ebedbb6f90ec0f9d3bed181f8fb5366f91fc48c +R 80ebffa6703b819b2ae14611b1bcf70c U drh -Z 69cf4aeab2e20bee08dd4d43f781d8b4 +Z 257c04f6c61217c6002648ad82ead94a diff --git a/manifest.uuid b/manifest.uuid index ec522e92e6..af83ba59cc 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -3ebedbb6f90ec0f9d3bed181f8fb5366f91fc48c \ No newline at end of file +8c52d2ad468615e50a727adab2977a0bef1bc068 \ No newline at end of file diff --git a/src/btree.c b/src/btree.c index 25306cf37a..abd47124d3 100644 --- a/src/btree.c +++ b/src/btree.c @@ -9,7 +9,7 @@ ** May you share freely, never taking more than you give. ** ************************************************************************* -** $Id: btree.c,v 1.327 2006/08/13 18:39:26 drh Exp $ +** $Id: btree.c,v 1.328 2006/08/16 16:42:48 drh Exp $ ** ** This file implements a external (disk-based) database using BTrees. ** For a detailed discussion of BTrees, refer to @@ -387,17 +387,13 @@ struct BtCursor { CellInfo info; /* A parse of the cell we are pointing at */ u8 wrFlag; /* True if writable */ u8 eState; /* One of the CURSOR_XXX constants (see below) */ -#ifndef SQLITE_OMIT_SHARED_CACHE void *pKey; /* Saved key that was cursor's last known position */ i64 nKey; /* Size of pKey, or last integer key */ int skip; /* (skip<0) -> Prev() is a no-op. (skip>0) -> Next() is */ -#endif }; /* -** Potential values for BtCursor.eState. The first two values (VALID and -** INVALID) may occur in any build. The third (REQUIRESEEK) may only occur -** if sqlite was compiled without the OMIT_SHARED_CACHE symbol defined. +** Potential values for BtCursor.eState. ** ** CURSOR_VALID: ** Cursor points to a valid entry. getPayload() etc. may be called. @@ -434,7 +430,7 @@ int sqlite3_btree_trace=0; /* True to enable tracing */ /* ** Forward declaration */ -static int checkReadLocks(BtShared*,Pgno,BtCursor*); +static int checkReadLocks(Btree*,Pgno,BtCursor*); /* ** Read or write a two- and four-byte big-endian integer values. @@ -509,105 +505,8 @@ struct BtLock { #define queryTableLock(a,b,c) SQLITE_OK #define lockTable(a,b,c) SQLITE_OK #define unlockAllTables(a) - #define restoreOrClearCursorPosition(a,b) SQLITE_OK - #define saveAllCursors(a,b,c) SQLITE_OK - #else -static void releasePage(MemPage *pPage); - -/* -** Save the current cursor position in the variables BtCursor.nKey -** and BtCursor.pKey. The cursor's state is set to CURSOR_REQUIRESEEK. -*/ -static int saveCursorPosition(BtCursor *pCur){ - int rc; - - assert( CURSOR_VALID==pCur->eState ); - assert( 0==pCur->pKey ); - - rc = sqlite3BtreeKeySize(pCur, &pCur->nKey); - - /* If this is an intKey table, then the above call to BtreeKeySize() - ** stores the integer key in pCur->nKey. In this case this value is - ** all that is required. Otherwise, if pCur is not open on an intKey - ** table, then malloc space for and store the pCur->nKey bytes of key - ** data. - */ - if( rc==SQLITE_OK && 0==pCur->pPage->intKey){ - void *pKey = sqliteMalloc(pCur->nKey); - if( pKey ){ - rc = sqlite3BtreeKey(pCur, 0, pCur->nKey, pKey); - if( rc==SQLITE_OK ){ - pCur->pKey = pKey; - }else{ - sqliteFree(pKey); - } - }else{ - rc = SQLITE_NOMEM; - } - } - assert( !pCur->pPage->intKey || !pCur->pKey ); - - if( rc==SQLITE_OK ){ - releasePage(pCur->pPage); - pCur->pPage = 0; - pCur->eState = CURSOR_REQUIRESEEK; - } - - return rc; -} - -/* -** Save the positions of all cursors except pExcept open on the table -** with root-page iRoot. Usually, this is called just before cursor -** pExcept is used to modify the table (BtreeDelete() or BtreeInsert()). -*/ -static int saveAllCursors(BtShared *pBt, Pgno iRoot, BtCursor *pExcept){ - BtCursor *p; - if( sqlite3ThreadDataReadOnly()->useSharedData ){ - for(p=pBt->pCursor; p; p=p->pNext){ - if( p!=pExcept && (0==iRoot || p->pgnoRoot==iRoot) && - p->eState==CURSOR_VALID ){ - int rc = saveCursorPosition(p); - if( SQLITE_OK!=rc ){ - return rc; - } - } - } - } - return SQLITE_OK; -} - -/* -** Restore the cursor to the position it was in (or as close to as possible) -** when saveCursorPosition() was called. Note that this call deletes the -** saved position info stored by saveCursorPosition(), so there can be -** at most one effective restoreOrClearCursorPosition() call after each -** saveCursorPosition(). -** -** If the second argument argument - doSeek - is false, then instead of -** returning the cursor to it's saved position, any saved position is deleted -** and the cursor state set to CURSOR_INVALID. -*/ -static int restoreOrClearCursorPositionX(BtCursor *pCur, int doSeek){ - int rc = SQLITE_OK; - assert( sqlite3ThreadDataReadOnly()->useSharedData ); - assert( pCur->eState==CURSOR_REQUIRESEEK ); - pCur->eState = CURSOR_INVALID; - if( doSeek ){ - rc = sqlite3BtreeMoveto(pCur, pCur->pKey, pCur->nKey, &pCur->skip); - } - if( rc==SQLITE_OK ){ - sqliteFree(pCur->pKey); - pCur->pKey = 0; - assert( CURSOR_VALID==pCur->eState || CURSOR_INVALID==pCur->eState ); - } - return rc; -} - -#define restoreOrClearCursorPosition(p,x) \ - (p->eState==CURSOR_REQUIRESEEK?restoreOrClearCursorPositionX(p,x):SQLITE_OK) /* ** Query to see if btree handle p may obtain a lock of type eLock @@ -747,6 +646,98 @@ static void unlockAllTables(Btree *p){ } #endif /* SQLITE_OMIT_SHARED_CACHE */ +static void releasePage(MemPage *pPage); /* Forward reference */ + +/* +** Save the current cursor position in the variables BtCursor.nKey +** and BtCursor.pKey. The cursor's state is set to CURSOR_REQUIRESEEK. +*/ +static int saveCursorPosition(BtCursor *pCur){ + int rc; + + assert( CURSOR_VALID==pCur->eState ); + assert( 0==pCur->pKey ); + + rc = sqlite3BtreeKeySize(pCur, &pCur->nKey); + + /* If this is an intKey table, then the above call to BtreeKeySize() + ** stores the integer key in pCur->nKey. In this case this value is + ** all that is required. Otherwise, if pCur is not open on an intKey + ** table, then malloc space for and store the pCur->nKey bytes of key + ** data. + */ + if( rc==SQLITE_OK && 0==pCur->pPage->intKey){ + void *pKey = sqliteMalloc(pCur->nKey); + if( pKey ){ + rc = sqlite3BtreeKey(pCur, 0, pCur->nKey, pKey); + if( rc==SQLITE_OK ){ + pCur->pKey = pKey; + }else{ + sqliteFree(pKey); + } + }else{ + rc = SQLITE_NOMEM; + } + } + assert( !pCur->pPage->intKey || !pCur->pKey ); + + if( rc==SQLITE_OK ){ + releasePage(pCur->pPage); + pCur->pPage = 0; + pCur->eState = CURSOR_REQUIRESEEK; + } + + return rc; +} + +/* +** Save the positions of all cursors except pExcept open on the table +** with root-page iRoot. Usually, this is called just before cursor +** pExcept is used to modify the table (BtreeDelete() or BtreeInsert()). +*/ +static int saveAllCursors(BtShared *pBt, Pgno iRoot, BtCursor *pExcept){ + BtCursor *p; + for(p=pBt->pCursor; p; p=p->pNext){ + if( p!=pExcept && (0==iRoot || p->pgnoRoot==iRoot) && + p->eState==CURSOR_VALID ){ + int rc = saveCursorPosition(p); + if( SQLITE_OK!=rc ){ + return rc; + } + } + } + return SQLITE_OK; +} + +/* +** Restore the cursor to the position it was in (or as close to as possible) +** when saveCursorPosition() was called. Note that this call deletes the +** saved position info stored by saveCursorPosition(), so there can be +** at most one effective restoreOrClearCursorPosition() call after each +** saveCursorPosition(). +** +** If the second argument argument - doSeek - is false, then instead of +** returning the cursor to it's saved position, any saved position is deleted +** and the cursor state set to CURSOR_INVALID. +*/ +static int restoreOrClearCursorPositionX(BtCursor *pCur, int doSeek){ + int rc = SQLITE_OK; + assert( pCur->eState==CURSOR_REQUIRESEEK ); + pCur->eState = CURSOR_INVALID; + if( doSeek ){ + rc = sqlite3BtreeMoveto(pCur, pCur->pKey, pCur->nKey, &pCur->skip); + } + if( rc==SQLITE_OK ){ + sqliteFree(pCur->pKey); + pCur->pKey = 0; + assert( CURSOR_VALID==pCur->eState || CURSOR_INVALID==pCur->eState ); + } + return rc; +} + +#define restoreOrClearCursorPosition(p,x) \ + (p->eState==CURSOR_REQUIRESEEK?restoreOrClearCursorPositionX(p,x):SQLITE_OK) + #ifndef SQLITE_OMIT_AUTOVACUUM /* ** These macros define the location of the pointer-map entry for a @@ -1591,9 +1582,9 @@ int sqlite3BtreeOpen( */ #if !defined(SQLITE_OMIT_SHARED_CACHE) || !defined(SQLITE_OMIT_AUTOVACUUM) #ifdef SQLITE_OMIT_MEMORYDB - const int isMemdb = !zFilename; + const int isMemdb = 0; #else - const int isMemdb = !zFilename || (strcmp(zFilename, ":memory:")?0:1); + const int isMemdb = zFilename && !strcmp(zFilename, ":memory:"); #endif #endif @@ -2778,7 +2769,7 @@ int sqlite3BtreeCursor( if( pBt->readOnly ){ return SQLITE_READONLY; } - if( checkReadLocks(pBt, iTable, 0) ){ + if( checkReadLocks(p, iTable, 0) ){ return SQLITE_LOCKED; } } @@ -5215,27 +5206,35 @@ static int balance(MemPage *pPage, int insert){ /* ** This routine checks all cursors that point to table pgnoRoot. -** If any of those cursors other than pExclude were opened with -** wrFlag==0 then this routine returns SQLITE_LOCKED. If all -** cursors that point to pgnoRoot were opened with wrFlag==1 -** then this routine returns SQLITE_OK. +** If any of those cursors were opened with wrFlag==0 in a different +** database connection (a database connection that shares the pager +** cache with the current connection) and that other connection +** is not in the ReadUncommmitted state, then this routine returns +** SQLITE_LOCKED. ** ** In addition to checking for read-locks (where a read-lock ** means a cursor opened with wrFlag==0) this routine also moves -** all cursors other than pExclude so that they are pointing to the -** first Cell on root page. This is necessary because an insert +** all cursors write cursors so that they are pointing to the +** first Cell on the root page. This is necessary because an insert ** or delete might change the number of cells on a page or delete ** a page entirely and we do not want to leave any cursors ** pointing to non-existant pages or cells. */ -static int checkReadLocks(BtShared *pBt, Pgno pgnoRoot, BtCursor *pExclude){ +static int checkReadLocks(Btree *pBtree, Pgno pgnoRoot, BtCursor *pExclude){ BtCursor *p; + BtShared *pBt = pBtree->pBt; + sqlite3 *db = pBtree->pSqlite; for(p=pBt->pCursor; p; p=p->pNext){ - u32 flags = (p->pBtree->pSqlite ? p->pBtree->pSqlite->flags : 0); - if( p->pgnoRoot!=pgnoRoot || p==pExclude ) continue; - if( p->wrFlag==0 && flags&SQLITE_ReadUncommitted ) continue; - if( p->wrFlag==0 ) return SQLITE_LOCKED; - if( p->pPage->pgno!=p->pgnoRoot ){ + if( p==pExclude ) continue; + if( p->eState!=CURSOR_VALID ) continue; + if( p->pgnoRoot!=pgnoRoot ) continue; + if( p->wrFlag==0 ){ + sqlite3 *dbOther = p->pBtree->pSqlite; + if( dbOther==0 || + (dbOther!=db && (dbOther->flags & SQLITE_ReadUncommitted)==0) ){ + return SQLITE_LOCKED; + } + }else if( p->pPage->pgno!=p->pgnoRoot ){ moveToRoot(p); } } @@ -5272,7 +5271,7 @@ int sqlite3BtreeInsert( if( !pCur->wrFlag ){ return SQLITE_PERM; /* Cursor not open for writing */ } - if( checkReadLocks(pBt, pCur->pgnoRoot, pCur) ){ + if( checkReadLocks(pCur->pBtree, pCur->pgnoRoot, pCur) ){ return SQLITE_LOCKED; /* The table pCur points to has a read lock */ } @@ -5354,7 +5353,7 @@ int sqlite3BtreeDelete(BtCursor *pCur){ if( !pCur->wrFlag ){ return SQLITE_PERM; /* Did not open this cursor for writing */ } - if( checkReadLocks(pBt, pCur->pgnoRoot, pCur) ){ + if( checkReadLocks(pCur->pBtree, pCur->pgnoRoot, pCur) ){ return SQLITE_LOCKED; /* The table pCur points to has a read lock */ } @@ -5631,25 +5630,13 @@ cleardatabasepage_out: */ int sqlite3BtreeClearTable(Btree *p, int iTable){ int rc; - BtCursor *pCur; BtShared *pBt = p->pBt; - sqlite3 *db = p->pSqlite; if( p->inTrans!=TRANS_WRITE ){ return pBt->readOnly ? SQLITE_READONLY : SQLITE_ERROR; } - - /* If this connection is not in read-uncommitted mode and currently has - ** a read-cursor open on the table being cleared, return SQLITE_LOCKED. - */ - if( 0==db || 0==(db->flags&SQLITE_ReadUncommitted) ){ - for(pCur=pBt->pCursor; pCur; pCur=pCur->pNext){ - if( pCur->pBtree==p && pCur->pgnoRoot==(Pgno)iTable ){ - if( 0==pCur->wrFlag ){ - return SQLITE_LOCKED; - } - moveToRoot(pCur); - } - } + rc = checkReadLocks(p, iTable, 0); + if( rc ){ + return rc; } /* Save the position of all cursors open on this table */ diff --git a/test/btree.test b/test/btree.test index ae1b78ffbe..8d440aebde 100644 --- a/test/btree.test +++ b/test/btree.test @@ -11,7 +11,7 @@ # This file implements regression tests for SQLite library. The # focus of this script is btree database backend # -# $Id: btree.test,v 1.36 2006/03/19 13:00:25 drh Exp $ +# $Id: btree.test,v 1.37 2006/08/16 16:42:48 drh Exp $ set testdir [file dirname $argv0] @@ -1018,22 +1018,30 @@ do_test btree-16.10 { catch {btree_delete $::c1} msg set msg } {SQLITE_PERM} + +# As of 2006-08-16 (version 3.3.7+) a read cursor will no +# longer block a write cursor from the same database +# connectiin. The following three tests uses to return +# the SQLITE_LOCK error, but no more. +# do_test btree-16.11 { btree_close_cursor $::c1 set ::c2 [btree_cursor $::b1 2 1] set ::c1 [btree_cursor $::b1 2 0] catch {btree_insert $::c2 101 helloworld} msg set msg -} {SQLITE_LOCKED} +} {} do_test btree-16.12 { btree_first $::c2 catch {btree_delete $::c2} msg set msg -} {SQLITE_LOCKED} +} {} do_test btree-16.13 { catch {btree_clear_table $::b1 2} msg set msg -} {SQLITE_LOCKED} +} {} + + do_test btree-16.14 { btree_close_cursor $::c1 btree_close_cursor $::c2 diff --git a/test/capi2.test b/test/capi2.test index 79283ac2cf..dc7182cf3b 100644 --- a/test/capi2.test +++ b/test/capi2.test @@ -11,7 +11,7 @@ # This file implements regression tests for SQLite library. The # focus of this script testing the callback-free C/C++ API. # -# $Id: capi2.test,v 1.31 2006/02/10 13:33:31 danielk1977 Exp $ +# $Id: capi2.test,v 1.32 2006/08/16 16:42:48 drh Exp $ # set testdir [file dirname $argv0] @@ -470,9 +470,11 @@ do_test capi2-6.12 { [get_column_names $VM1] } {SQLITE_ROW 1 5 {x counter}} -do_test capi2-6.13 { - catchsql {UPDATE t3 SET x=x+1} -} {1 {database table is locked}} +# A read no longer blocks a write in the same connection. +#do_test capi2-6.13 { +# catchsql {UPDATE t3 SET x=x+1} +#} {1 {database table is locked}} + do_test capi2-6.14 { list [sqlite3_step $VM1] \ [sqlite3_column_count $VM1] \ diff --git a/test/capi3.test b/test/capi3.test index 7d7a9bda69..35b7468042 100644 --- a/test/capi3.test +++ b/test/capi3.test @@ -11,7 +11,7 @@ # This file implements regression tests for SQLite library. The # focus of this script testing the callback-free C/C++ API. # -# $Id: capi3.test,v 1.45 2006/04/07 13:50:38 drh Exp $ +# $Id: capi3.test,v 1.46 2006/08/16 16:42:48 drh Exp $ # set testdir [file dirname $argv0] @@ -912,8 +912,8 @@ do_test capi3-11.21 { # The following tests - capi3-12.* - check that it's Ok to start a # transaction while other VMs are active, and that it's Ok to execute -# atomic updates in the same situation (so long as they are on a different -# table). +# atomic updates in the same situation +# do_test capi3-12.1 { set STMT [sqlite3_prepare $DB "SELECT a FROM t2" -1 TAIL] sqlite3_step $STMT @@ -923,12 +923,11 @@ do_test capi3-12.2 { INSERT INTO t1 VALUES(3, NULL); } } {0 {}} - do_test capi3-12.3 { catchsql { INSERT INTO t2 VALUES(4); } -} {1 {database table is locked}} +} {0 {}} do_test capi3-12.4 { catchsql { BEGIN; @@ -938,6 +937,9 @@ do_test capi3-12.4 { do_test capi3-12.5 { sqlite3_step $STMT } {SQLITE_ROW} +do_test capi3-12.5.1 { + sqlite3_step $STMT +} {SQLITE_ROW} do_test capi3-12.6 { sqlite3_step $STMT } {SQLITE_DONE} diff --git a/test/delete2.test b/test/delete2.test index f0e5d86a97..659cc56735 100644 --- a/test/delete2.test +++ b/test/delete2.test @@ -29,7 +29,7 @@ # The solution to the problem was to detect that the table is locked # before the index entry is deleted. # -# $Id: delete2.test,v 1.6 2006/01/05 11:34:34 danielk1977 Exp $ +# $Id: delete2.test,v 1.7 2006/08/16 16:42:48 drh Exp $ # set testdir [file dirname $argv0] @@ -65,20 +65,20 @@ do_test delete2-1.4 { } SQLITE_ROW integrity_check delete2-1.5 -# Try to delete a row from the table. The delete should fail. +# Try to delete a row from the table while a read is in process. +# As of 2006-08-16, this is allowed. (It used to fail with SQLITE_LOCKED.) # -breakpoint do_test delete2-1.6 { catchsql { DELETE FROM q WHERE rowid=1 } -} {1 {database table is locked}} +} {0 {}} integrity_check delete2-1.7 do_test delete2-1.8 { execsql { SELECT * FROM q; } -} {hello id.1 goodbye id.2 again id.3} +} {goodbye id.2 again id.3} # Finalize the query, thus clearing the lock on the table. Then # retry the delete. The delete should work this time. diff --git a/test/lock.test b/test/lock.test index c2f09eb196..e453ffd28b 100644 --- a/test/lock.test +++ b/test/lock.test @@ -11,7 +11,7 @@ # This file implements regression tests for SQLite library. The # focus of this script is database locks. # -# $Id: lock.test,v 1.32 2005/03/29 03:11:00 danielk1977 Exp $ +# $Id: lock.test,v 1.33 2006/08/16 16:42:48 drh Exp $ set testdir [file dirname $argv0] @@ -100,13 +100,17 @@ do_test lock-1.17 { # You cannot UPDATE a table from within the callback of a SELECT # on that same table because the SELECT has the table locked. -do_test lock-1.18 { - db eval {SELECT * FROM t1} qv { - set r [catch {db eval {UPDATE t1 SET a=b, b=a}} msg] - lappend r $msg - } - set r -} {1 {database table is locked}} +# +# 2006-08-16: Reads no longer block writes within the same +# database connection. +# +#do_test lock-1.18 { +# db eval {SELECT * FROM t1} qv { +# set r [catch {db eval {UPDATE t1 SET a=b, b=a}} msg] +# lappend r $msg +# } +# set r +#} {1 {database table is locked}} # But you can UPDATE a different table from the one that is used in # the SELECT. diff --git a/test/misc2.test b/test/misc2.test index 8271846ab7..44cb47d58e 100644 --- a/test/misc2.test +++ b/test/misc2.test @@ -13,7 +13,7 @@ # This file implements tests for miscellanous features that were # left out of other test files. # -# $Id: misc2.test,v 1.24 2006/01/17 09:35:02 danielk1977 Exp $ +# $Id: misc2.test,v 1.25 2006/08/16 16:42:48 drh Exp $ set testdir [file dirname $argv0] source $testdir/tester.tcl @@ -145,6 +145,9 @@ do_test misc2-6.1 { # update a table from within the callback of a select on that same # table. # +# 2006-08-16: This has changed. It is now permitted to update +# the table being SELECTed from within the callback of the query. +# do_test misc2-7.1 { db close file delete -force test.db @@ -152,45 +155,94 @@ do_test misc2-7.1 { execsql { CREATE TABLE t1(x); INSERT INTO t1 VALUES(1); + INSERT INTO t1 VALUES(2); + INSERT INTO t1 VALUES(3); + SELECT * FROM t1; } +} {1 2 3} +do_test misc2-7.2 { set rc [catch { db eval {SELECT rowid FROM t1} {} { db eval "DELETE FROM t1 WHERE rowid=$rowid" } } msg] lappend rc $msg -} {1 {database table is locked}} -do_test misc2-7.2 { - set rc [catch { - db eval {SELECT rowid FROM t1} {} { - db eval "INSERT INTO t1 VALUES(3)" +} {0 {}} +do_test misc2-7.3 { + execsql {SELECT * FROM t1} +} {} +do_test misc2-7.4 { + execsql { + DELETE FROM t1; + INSERT INTO t1 VALUES(1); + INSERT INTO t1 VALUES(2); + INSERT INTO t1 VALUES(3); + INSERT INTO t1 VALUES(4); + } + db eval {SELECT rowid, x FROM t1} { + if {$x & 1} { + db eval {DELETE FROM t1 WHERE rowid=$rowid} } - } msg] - lappend rc $msg -} {1 {database table is locked}} -ifcapable memorydb { - do_test misc2-7.3 { - sqlite3 db :memory: - execsql { - CREATE TABLE t1(x); - INSERT INTO t1 VALUES(1); + } + execsql {SELECT * FROM t1} +} {2 4} +do_test misc2-7.5 { + execsql { + DELETE FROM t1; + INSERT INTO t1 VALUES(1); + INSERT INTO t1 VALUES(2); + INSERT INTO t1 VALUES(3); + INSERT INTO t1 VALUES(4); + } + db eval {SELECT rowid, x FROM t1} { + if {$x & 1} { + db eval {DELETE FROM t1 WHERE rowid=$rowid+1} } - set rc [catch { - db eval {SELECT rowid FROM t1} {} { - db eval "DELETE FROM t1 WHERE rowid=$rowid" - } - } msg] - lappend rc $msg - } {1 {database table is locked}} - do_test misc2-7.4 { - set rc [catch { - db eval {SELECT rowid FROM t1} {} { - db eval "INSERT INTO t1 VALUES(3)" - } - } msg] - lappend rc $msg - } {1 {database table is locked}} -} + } + execsql {SELECT * FROM t1} +} {1 3} +do_test misc2-7.6 { + execsql { + DELETE FROM t1; + INSERT INTO t1 VALUES(1); + INSERT INTO t1 VALUES(2); + INSERT INTO t1 VALUES(3); + INSERT INTO t1 VALUES(4); + } + db eval {SELECT rowid, x FROM t1} { + if {$x & 1} { + db eval {DELETE FROM t1} + } + } + execsql {SELECT * FROM t1} +} {} +do_test misc2-7.7 { + execsql { + DELETE FROM t1; + INSERT INTO t1 VALUES(1); + INSERT INTO t1 VALUES(2); + INSERT INTO t1 VALUES(3); + INSERT INTO t1 VALUES(4); + } + db eval {SELECT rowid, x FROM t1} { + if {$x & 1} { + db eval {UPDATE t1 SET x=x+100 WHERE rowid=$rowid} + } + } + execsql {SELECT * FROM t1} +} {101 2 103 4} +do_test misc2-7.8 { + execsql { + DELETE FROM t1; + INSERT INTO t1 VALUES(1); + } + db eval {SELECT rowid, x FROM t1} { + if {$x<10} { + db eval {INSERT INTO t1 VALUES($x+1)} + } + } + execsql {SELECT * FROM t1} +} {1 2 3 4 5 6 7 8 9 10} db close file delete -force test.db