diff --git a/manifest b/manifest index e89d0e548f..5626aee46f 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Move\sthe\srowid\scache\sout\sof\sVdbeCursor\sand\sinto\sBtCursor.\s\sWhen\smultiple\nBtCursors\sare\sopen\son\sthe\ssame\stable,\sset\stheir\srowid\scache\sall\sat\sthe\nsame\stime.\s\sTicket\s#3731.\s(CVS\s6354) -D 2009-03-17T22:33:01 +C Fix\ssome\scases\swhere\sexecuting\sSQL\sfrom\swithin\sa\suser-function\scallback\scould\scause\sproblems\srelated\sto\sstatement-transactions.\s(CVS\s6355) +D 2009-03-18T10:33:01 F Makefile.arm-wince-mingw32ce-gcc fcd5e9cd67fe88836360bb4f9ef4cb7f8e2fb5a0 F Makefile.in 583e87706abc3026960ed759aff6371faf84c211 F Makefile.linux-gcc d53183f4aa6a9192d249731c90dbdffbd2c68654 @@ -103,9 +103,9 @@ F src/auth.c c8b2ab5c8bad4bd90ed7c294694f48269162c627 F src/backup.c 0082d0e5a63f04e88faee0dff0a7d63d3e92a78d F src/bitvec.c 44f7059ac1f874d364b34af31b9617e52223ba75 F src/btmutex.c 341502bc496dc0840dcb00cde65680fb0e85c3ab -F src/btree.c 805ca9606405ae80fe1afa001e7054f13ecb6046 -F src/btree.h 8ed34391fe4288c48bc540d80103f3ffabf3d3a5 -F src/btreeInt.h 6d0c6fad247f27a09af1bb4f810bfad232aefaa7 +F src/btree.c 081e1aac3ce4c79c5ed628a1fec533857d175eea +F src/btree.h e302c5747494067cd4f5763000fbe7bca767d816 +F src/btreeInt.h 17697718bb5281b12da5c65ecb0bffc45f2c0125 F src/build.c 5f050f06ee4219689e211fa47fd3cc8a817ede57 F src/callback.c 09c6fedc77a45db99ba25a75d61382830314b357 F src/complete.c cb14e06dbe79dee031031f0d9e686ff306afe07c @@ -122,7 +122,7 @@ F src/insert.c 71286d081a919a27ef22eaeccbe2718f93dc6aa9 F src/journal.c e00df0c0da8413ab6e1bb7d7cab5665d4a9000d0 F src/legacy.c 8b3b95d48d202614946d7ce7256e7ba898905c3b F src/loadext.c 3f96631089fc4f3871a67f02f2e4fc7ea4d51edc -F src/main.c 99955156dd3167e79a492187cf05e3a73196fbe2 +F src/main.c 76f953dabeed1096ae605a1a4c31aae0bbc426a1 F src/malloc.c b9c59b33539af74641362a7496ecc3efd6477f6d F src/mem0.c f2f84062d1f35814d6535c9f9e33de3bfb3b132c F src/mem1.c 3bfb39e4f60b0179713a7c087b2d4f0dc205735f @@ -159,14 +159,14 @@ F src/select.c 4d0b77fd76ff80f09a798ee98953e344c9de8fbb F src/shell.c 0a11f831603f17fea20ca97133c0f64e716af4a7 F src/sqlite.h.in 0f756e9e8db9d491d0f17ea9c07952974975e43d F src/sqlite3ext.h 1db7d63ab5de4b3e6b83dd03d1a4e64fef6d2a17 -F src/sqliteInt.h a48dc31146b89d5ed73fba50101030fd3c56ec4e +F src/sqliteInt.h 250be86c98646e0d48436d6455b6fe916742bffa F src/sqliteLimit.h ffe93f5a0c4e7bd13e70cd7bf84cfb5c3465f45d F src/status.c 237b193efae0cf6ac3f0817a208de6c6c6ef6d76 F src/table.c 332ab0ea691e63862e2a8bdfe2c0617ee61062a3 F src/tclsqlite.c 8a472804b901d4559213eeda538c2eadb2ad7f2a F src/test1.c 17300af44640eea439778f5b5e03e0d68a6f00a2 F src/test2.c 71c22e2974f8094fe0fd1eba8f27872dde9b2a39 -F src/test3.c 88a246b56b824275300e6c899634fbac1dc94b14 +F src/test3.c d3115b301c6ee761b102f315fe24125f3d6c3a4d F src/test4.c f79ab52d27ff49b784b631a42e2ccd52cfd5c84c F src/test5.c 162a1cea2105a2c460a3f39fa6919617b562a288 F src/test6.c 1a0a7a1f179469044b065b4a88aab9faee114101 @@ -200,11 +200,11 @@ F src/update.c 8ededddcde6f7b6da981dd0429a5d34518a475b7 F src/utf.c 1da9c832dba0fa8f865b5b902d93f420a1ee4245 F src/util.c 469d74f5bf09ed6398702c7da2ef8a34e979a1c1 F src/vacuum.c 4929a585ef0fb1dfaf46302f8a9c4aa30c2d9cf5 -F src/vdbe.c 2ae831d43cb22b42dbbe90555bd57e053af53469 +F src/vdbe.c f8164c2830f82714a77b1f2a97c2e9c4efbcb3bb F src/vdbe.h d70a68bee196ab228914a3902c79dbd24342a0f2 -F src/vdbeInt.h 8732228974ca23aedfd74c37a444b7bede857927 +F src/vdbeInt.h 53a2f4696871712646c77351904576cca6ad9752 F src/vdbeapi.c ffd5d8b493590da6e09fd54b1bea1a9d38247f11 -F src/vdbeaux.c feeafee5f9de51c0d30907e0600ce4db5d032df8 +F src/vdbeaux.c e9b76cf2ca8f78b692be984381cc4b27defc902a F src/vdbeblob.c 2852bae14c87129835938db58a77c3121e3ae962 F src/vdbemem.c 543a79d722734d2f8b7ad70f08218c30bcc5bbf5 F src/vtab.c bf409d2dc068e1bb82beeb9eef120ccfff541afb @@ -460,7 +460,7 @@ F test/mallocJ.test b5d1839da331d96223e5f458856f8ffe1366f62e F test/mallocK.test d79968641d1b70d88f6c01bdb9a7eb4a55582cc9 F test/malloc_common.tcl 984baeb6c6b185e798827d1187d426acc2bc4962 F test/manydb.test b3d3bc4c25657e7f68d157f031eb4db7b3df0d3c -F test/memdb.test 97d59567b282f0807a25eba8df34743476bf9ca8 +F test/memdb.test 09240c19e95b063e193b95d6e3eb99f528fbc2dd F test/memleak.test d2d2a1ff7105d32dc3fdf691458cf6cba58c7217 F test/memsubsys1.test bdc24a38d198679d777ca4efcc089fd3948567a6 F test/memsubsys2.test 72a731225997ad5e8df89fdbeae9224616b6aecc @@ -628,6 +628,7 @@ F test/tkt3554.test 0463fea3650ac18d3694a8f39e6e25cddc1ac1fc F test/tkt3581.test 1966b7193f1e3f14951cce8c66907ae69454e9a3 F test/tkt35xx.test 53bca895091e968126a858ee7da186f59f328994 F test/tkt3630.test 929f64852103054125200bc825c316d5f75d42f7 +F test/tkt3718.test 3ee5e25702f3f5a31340b2766d7a7fac2b5ce99c F test/tkt3731.test 8a6e3732f5a8a24eb875a6faf287ef77bb8c0579 F test/tokenize.test ce430a7aed48fc98301611429595883fdfcab5d7 F test/trace.test 951cd0f5f571e7f36bf7bfe04be70f90fb16fb00 @@ -708,7 +709,7 @@ F tool/speedtest16.c c8a9c793df96db7e4933f0852abb7a03d48f2e81 F tool/speedtest2.tcl ee2149167303ba8e95af97873c575c3e0fab58ff F tool/speedtest8.c 2902c46588c40b55661e471d7a86e4dd71a18224 F tool/speedtest8inst1.c 293327bc76823f473684d589a8160bde1f52c14e -P afadddc34eee3d6a39102b790ce1a869b33d4286 -R a87ad391e70921cc51f370f982e9d47a -U drh -Z 4d6edfbd554e5429583cecea6d7e5900 +P 189785832a7dc9f4a0a2113d850b92b987e0f9bf +R d44f7a2fcf465cdcd3971d1da17f54cb +U danielk1977 +Z 855899ed3ec24df7586ecfc973fa4034 diff --git a/manifest.uuid b/manifest.uuid index 643d99ae91..48b10606ed 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -189785832a7dc9f4a0a2113d850b92b987e0f9bf \ No newline at end of file +a60f4191791dd7bb49d5c95b350a9924845b59a8 \ No newline at end of file diff --git a/src/btree.c b/src/btree.c index 15fe1e8a3d..06e7536870 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.574 2009/03/17 22:33:01 drh Exp $ +** $Id: btree.c,v 1.575 2009/03/18 10:33:01 danielk1977 Exp $ ** ** This file implements a external (disk-based) database using BTrees. ** See the header comment on "btreeInt.h" for additional information. @@ -1996,7 +1996,6 @@ static void unlockBtreeIfUnused(BtShared *pBt){ releasePage(pBt->pPage1); } pBt->pPage1 = 0; - pBt->inStmt = 0; } } @@ -2141,9 +2140,7 @@ int sqlite3BtreeBeginTrans(Btree *p, int wrflag){ } } - if( rc==SQLITE_OK ){ - if( wrflag ) pBt->inStmt = 0; - }else{ + if( rc!=SQLITE_OK ){ unlockBtreeIfUnused(pBt); } }while( rc==SQLITE_BUSY && pBt->inTransaction==TRANS_NONE && @@ -2643,7 +2640,6 @@ int sqlite3BtreeCommitPhaseTwo(Btree *p){ return rc; } pBt->inTransaction = TRANS_READ; - pBt->inStmt = 0; } clearAllSharedCacheTableLocks(p); @@ -2800,7 +2796,6 @@ int sqlite3BtreeRollback(Btree *p){ btreeClearHasContent(pBt); p->inTrans = TRANS_NONE; - pBt->inStmt = 0; unlockBtreeIfUnused(pBt); btreeIntegrity(p); @@ -2809,29 +2804,33 @@ int sqlite3BtreeRollback(Btree *p){ } /* -** Start a statement subtransaction. The subtransaction can -** can be rolled back independently of the main transaction. -** You must start a transaction before starting a subtransaction. -** The subtransaction is ended automatically if the main transaction -** commits or rolls back. -** -** Only one subtransaction may be active at a time. It is an error to try -** to start a new subtransaction if another subtransaction is already active. +** Start a statement subtransaction. The subtransaction can can be rolled +** back independently of the main transaction. You must start a transaction +** before starting a subtransaction. The subtransaction is ended automatically +** if the main transaction commits or rolls back. ** ** Statement subtransactions are used around individual SQL statements ** that are contained within a BEGIN...COMMIT block. If a constraint ** error occurs within the statement, the effect of that one statement ** can be rolled back without having to rollback the entire transaction. +** +** A statement sub-transaction is implemented as an anonymous savepoint. The +** value passed as the second parameter is the total number of savepoints, +** including the new anonymous savepoint, open on the B-Tree. i.e. if there +** are no active savepoints and no other statement-transactions open, +** iStatement is 1. This anonymous savepoint can be released or rolled back +** using the sqlite3BtreeSavepoint() function. */ -int sqlite3BtreeBeginStmt(Btree *p){ +int sqlite3BtreeBeginStmt(Btree *p, int iStatement){ int rc; BtShared *pBt = p->pBt; sqlite3BtreeEnter(p); pBt->db = p->db; assert( p->inTrans==TRANS_WRITE ); - assert( !pBt->inStmt ); assert( pBt->readOnly==0 ); - if( NEVER(p->inTrans!=TRANS_WRITE || pBt->inStmt || pBt->readOnly) ){ + assert( iStatement>0 ); + assert( iStatement>p->db->nSavepoint ); + if( NEVER(p->inTrans!=TRANS_WRITE || pBt->readOnly) ){ rc = SQLITE_INTERNAL; }else{ assert( pBt->inTransaction==TRANS_WRITE ); @@ -2840,53 +2839,7 @@ int sqlite3BtreeBeginStmt(Btree *p){ ** SQL statements. It is illegal to open, release or rollback any ** such savepoints while the statement transaction savepoint is active. */ - rc = sqlite3PagerOpenSavepoint(pBt->pPager, p->db->nSavepoint+1); - pBt->inStmt = 1; - } - sqlite3BtreeLeave(p); - return rc; -} - -/* -** Commit the statment subtransaction currently in progress. If no -** subtransaction is active, this is a no-op. -*/ -int sqlite3BtreeCommitStmt(Btree *p){ - int rc = SQLITE_OK; - BtShared *pBt = p->pBt; - sqlite3BtreeEnter(p); - pBt->db = p->db; - if( p->inTrans==TRANS_WRITE && pBt->inStmt ){ - int iStmtpoint = p->db->nSavepoint; - assert( pBt->readOnly==0 ); - rc = sqlite3PagerSavepoint(pBt->pPager, SAVEPOINT_RELEASE, iStmtpoint); - pBt->inStmt = 0; - } - sqlite3BtreeLeave(p); - return rc; -} - -/* -** Rollback the active statement subtransaction. If no subtransaction -** is active this routine is a no-op. -** -** All cursors will be invalidated by this operation. Any attempt -** to use a cursor that was open at the beginning of this operation -** will result in an error. -*/ -int sqlite3BtreeRollbackStmt(Btree *p){ - int rc = SQLITE_OK; - BtShared *pBt = p->pBt; - sqlite3BtreeEnter(p); - pBt->db = p->db; - if( p->inTrans==TRANS_WRITE && pBt->inStmt ){ - int iStmtpoint = p->db->nSavepoint; - assert( pBt->readOnly==0 ); - rc = sqlite3PagerSavepoint(pBt->pPager, SAVEPOINT_ROLLBACK, iStmtpoint); - if( rc==SQLITE_OK ){ - rc = sqlite3PagerSavepoint(pBt->pPager, SAVEPOINT_RELEASE, iStmtpoint); - } - pBt->inStmt = 0; + rc = sqlite3PagerOpenSavepoint(pBt->pPager, iStatement); } sqlite3BtreeLeave(p); return rc; @@ -2908,7 +2861,6 @@ int sqlite3BtreeSavepoint(Btree *p, int op, int iSavepoint){ int rc = SQLITE_OK; if( p && p->inTrans==TRANS_WRITE ){ BtShared *pBt = p->pBt; - assert( pBt->inStmt==0 ); assert( op==SAVEPOINT_RELEASE || op==SAVEPOINT_ROLLBACK ); assert( iSavepoint>=0 || (iSavepoint==-1 && op==SAVEPOINT_ROLLBACK) ); sqlite3BtreeEnter(p); @@ -7401,14 +7353,6 @@ int sqlite3BtreeIsInTrans(Btree *p){ return (p && (p->inTrans==TRANS_WRITE)); } -/* -** Return non-zero if a statement transaction is active. -*/ -int sqlite3BtreeIsInStmt(Btree *p){ - assert( sqlite3BtreeHoldsMutex(p) ); - return ALWAYS(p->pBt) && p->pBt->inStmt; -} - /* ** Return non-zero if a read (or write) transaction is active. */ diff --git a/src/btree.h b/src/btree.h index 52c5c0c517..283e556016 100644 --- a/src/btree.h +++ b/src/btree.h @@ -13,7 +13,7 @@ ** subsystem. See comments in the source code for a detailed description ** of what each interface routine does. ** -** @(#) $Id: btree.h,v 1.110 2009/03/17 22:33:01 drh Exp $ +** @(#) $Id: btree.h,v 1.111 2009/03/18 10:33:01 danielk1977 Exp $ */ #ifndef _BTREE_H_ #define _BTREE_H_ @@ -91,12 +91,9 @@ int sqlite3BtreeCommitPhaseOne(Btree*, const char *zMaster); int sqlite3BtreeCommitPhaseTwo(Btree*); int sqlite3BtreeCommit(Btree*); int sqlite3BtreeRollback(Btree*); -int sqlite3BtreeBeginStmt(Btree*); -int sqlite3BtreeCommitStmt(Btree*); -int sqlite3BtreeRollbackStmt(Btree*); +int sqlite3BtreeBeginStmt(Btree*,int); int sqlite3BtreeCreateTable(Btree*, int*, int flags); int sqlite3BtreeIsInTrans(Btree*); -int sqlite3BtreeIsInStmt(Btree*); int sqlite3BtreeIsInReadTrans(Btree*); int sqlite3BtreeIsInBackup(Btree*); void *sqlite3BtreeSchema(Btree *, int, void(*)(void *)); diff --git a/src/btreeInt.h b/src/btreeInt.h index 8ef5ef09db..3d2d02c9ea 100644 --- a/src/btreeInt.h +++ b/src/btreeInt.h @@ -9,7 +9,7 @@ ** May you share freely, never taking more than you give. ** ************************************************************************* -** $Id: btreeInt.h,v 1.44 2009/03/17 22:33:01 drh Exp $ +** $Id: btreeInt.h,v 1.45 2009/03/18 10:33:01 danielk1977 Exp $ ** ** This file implements a external (disk-based) database using BTrees. ** For a detailed discussion of BTrees, refer to @@ -380,7 +380,6 @@ struct BtShared { sqlite3 *db; /* Database connection currently using this Btree */ BtCursor *pCursor; /* A list of all open cursors */ MemPage *pPage1; /* First page of the database */ - u8 inStmt; /* True if we are in a statement subtransaction */ u8 readOnly; /* True if the underlying file is readonly */ u8 pageSizeFixed; /* True if the page size can no longer be changed */ #ifndef SQLITE_OMIT_AUTOVACUUM diff --git a/src/main.c b/src/main.c index 9f900cc891..605f138ab5 100644 --- a/src/main.c +++ b/src/main.c @@ -14,7 +14,7 @@ ** other files are for internal use by SQLite and should not be ** accessed by users of the library. ** -** $Id: main.c,v 1.531 2009/03/16 13:19:36 danielk1977 Exp $ +** $Id: main.c,v 1.532 2009/03/18 10:33:01 danielk1977 Exp $ */ #include "sqliteInt.h" @@ -560,6 +560,7 @@ void sqlite3CloseSavepoints(sqlite3 *db){ sqlite3DbFree(db, pTmp); } db->nSavepoint = 0; + db->nStatement = 0; db->isTransactionSavepoint = 0; } diff --git a/src/sqliteInt.h b/src/sqliteInt.h index 015b386ed2..f2833180da 100644 --- a/src/sqliteInt.h +++ b/src/sqliteInt.h @@ -11,7 +11,7 @@ ************************************************************************* ** Internal interface definitions for SQLite. ** -** @(#) $Id: sqliteInt.h,v 1.842 2009/03/17 17:49:00 danielk1977 Exp $ +** @(#) $Id: sqliteInt.h,v 1.843 2009/03/18 10:33:01 danielk1977 Exp $ */ #ifndef _SQLITEINT_H_ #define _SQLITEINT_H_ @@ -807,6 +807,7 @@ struct sqlite3 { #endif Savepoint *pSavepoint; /* List of active savepoints */ int nSavepoint; /* Number of non-transaction savepoints */ + int nStatement; /* Number of nested statement-transactions */ u8 isTransactionSavepoint; /* True if the outermost savepoint is a TS */ #ifdef SQLITE_ENABLE_UNLOCK_NOTIFY diff --git a/src/test3.c b/src/test3.c index 0d915f3cb5..0f1044799b 100644 --- a/src/test3.c +++ b/src/test3.c @@ -13,7 +13,7 @@ ** is not included in the SQLite library. It is used for automated ** testing of the SQLite library. ** -** $Id: test3.c,v 1.102 2008/10/27 13:59:34 danielk1977 Exp $ +** $Id: test3.c,v 1.103 2009/03/18 10:33:02 danielk1977 Exp $ */ #include "sqliteInt.h" #include "btreeInt.h" @@ -235,7 +235,7 @@ static int btree_begin_statement( } pBt = sqlite3TestTextToPtr(argv[1]); sqlite3BtreeEnter(pBt); - rc = sqlite3BtreeBeginStmt(pBt); + rc = sqlite3BtreeBeginStmt(pBt, 1); sqlite3BtreeLeave(pBt); if( rc!=SQLITE_OK ){ Tcl_AppendResult(interp, errorName(rc), 0); @@ -264,7 +264,10 @@ static int btree_rollback_statement( } pBt = sqlite3TestTextToPtr(argv[1]); sqlite3BtreeEnter(pBt); - rc = sqlite3BtreeRollbackStmt(pBt); + rc = sqlite3BtreeSavepoint(pBt, SAVEPOINT_ROLLBACK, 0); + if( rc==SQLITE_OK ){ + rc = sqlite3BtreeSavepoint(pBt, SAVEPOINT_RELEASE, 0); + } sqlite3BtreeLeave(pBt); if( rc!=SQLITE_OK ){ Tcl_AppendResult(interp, errorName(rc), 0); @@ -293,7 +296,7 @@ static int btree_commit_statement( } pBt = sqlite3TestTextToPtr(argv[1]); sqlite3BtreeEnter(pBt); - rc = sqlite3BtreeCommitStmt(pBt); + rc = sqlite3BtreeSavepoint(pBt, SAVEPOINT_RELEASE, 0); sqlite3BtreeLeave(pBt); if( rc!=SQLITE_OK ){ Tcl_AppendResult(interp, errorName(rc), 0); diff --git a/src/vdbe.c b/src/vdbe.c index d509068f24..97f5ae6961 100644 --- a/src/vdbe.c +++ b/src/vdbe.c @@ -43,7 +43,7 @@ ** in this file for details. If in doubt, do not deviate from existing ** commenting and indentation practices when changing or adding code. ** -** $Id: vdbe.c,v 1.826 2009/03/17 22:33:01 drh Exp $ +** $Id: vdbe.c,v 1.827 2009/03/18 10:33:02 danielk1977 Exp $ */ #include "sqliteInt.h" #include "vdbeInt.h" @@ -1086,6 +1086,23 @@ case OP_ResultRow: { assert( pOp->p1>0 ); assert( pOp->p1+pOp->p2<=p->nMem+1 ); + /* If the SQLITE_CountRows flag is set in sqlite3.flags mask, then + ** DML statements invoke this opcode to return the number of rows + ** modified to the user. This is the only way that a VM that + ** opens a statement transaction may invoke this opcode. + ** + ** In case this is such a statement, close any statement transaction + ** opened by this VM before returning control to the user. This is to + ** ensure that statement-transactions are always nested, not overlapping. + ** If the open statement-transaction is not closed here, then the user + ** may step another VM that opens its own statement transaction. This + ** may lead to overlapping statement transactions. + */ + assert( p->iStatement==0 || db->flags&SQLITE_CountRows ); + if( SQLITE_OK!=(rc = sqlite3VdbeCloseStatement(p, SAVEPOINT_RELEASE)) ){ + break; + } + /* Invalidate all ephemeral cursor row caches */ p->cacheCtr = (p->cacheCtr + 2)|1; @@ -2386,10 +2403,12 @@ case OP_Statement: { pBt = db->aDb[i].pBt; assert( sqlite3BtreeIsInTrans(pBt) ); assert( (p->btreeMask & (1<openedStatement = 1; + if( p->iStatement==0 ){ + assert( db->nStatement>=0 && db->nSavepoint>=0 ); + db->nStatement++; + p->iStatement = db->nSavepoint + db->nStatement; } + rc = sqlite3BtreeBeginStmt(pBt, p->iStatement); } break; } @@ -2496,7 +2515,7 @@ case OP_Savepoint: { rc = sqlite3BtreeSavepoint(db->aDb[ii].pBt, p1, iSavepoint); if( rc!=SQLITE_OK ){ goto abort_due_to_error; - } + } } if( p1==SAVEPOINT_ROLLBACK && (db->flags&SQLITE_InternChanges)!=0 ){ sqlite3ExpirePreparedStatements(db); @@ -2576,6 +2595,7 @@ case OP_AutoCommit: { goto vdbe_return; } } + assert( db->nStatement==0 ); sqlite3CloseSavepoints(db); if( p->rc==SQLITE_OK ){ rc = SQLITE_DONE; diff --git a/src/vdbeInt.h b/src/vdbeInt.h index 1d7ed689da..c564d98afa 100644 --- a/src/vdbeInt.h +++ b/src/vdbeInt.h @@ -15,7 +15,7 @@ ** 6000 lines long) it was split up into several smaller files and ** this header information was factored out. ** -** $Id: vdbeInt.h,v 1.165 2009/03/17 22:33:01 drh Exp $ +** $Id: vdbeInt.h,v 1.166 2009/03/18 10:33:02 danielk1977 Exp $ */ #ifndef _VDBEINT_H_ #define _VDBEINT_H_ @@ -306,7 +306,7 @@ struct Vdbe { #ifdef SQLITE_DEBUG FILE *trace; /* Write an execution trace here, if not NULL */ #endif - int openedStatement; /* True if this VM has opened a statement journal */ + int iStatement; /* Statement number (or 0 if has not opened stmt) */ #ifdef SQLITE_SSE int fetchId; /* Statement number used by sqlite3_fetch_statement */ int lru; /* Counter used for LRU cache replacement */ @@ -374,6 +374,7 @@ int sqlite3VdbeMemFinalize(Mem*, FuncDef*); const char *sqlite3OpcodeName(int); int sqlite3VdbeOpcodeHasProperty(int, int); int sqlite3VdbeMemGrow(Mem *pMem, int n, int preserve); +int sqlite3VdbeCloseStatement(Vdbe *, int); #ifdef SQLITE_ENABLE_MEMORY_MANAGEMENT int sqlite3VdbeReleaseBuffers(Vdbe *p); #endif diff --git a/src/vdbeaux.c b/src/vdbeaux.c index 42e3f13c72..5402acdbd2 100644 --- a/src/vdbeaux.c +++ b/src/vdbeaux.c @@ -14,7 +14,7 @@ ** to version 2.8.7, all this code was combined into the vdbe.c source file. ** But that file was getting too big so this subroutines were split out. ** -** $Id: vdbeaux.c,v 1.442 2009/03/16 13:19:36 danielk1977 Exp $ +** $Id: vdbeaux.c,v 1.443 2009/03/18 10:33:02 danielk1977 Exp $ */ #include "sqliteInt.h" #include "vdbeInt.h" @@ -1145,7 +1145,7 @@ void sqlite3VdbeMakeReady( p->nChange = 0; p->cacheCtr = 1; p->minWriteFileFormat = 255; - p->openedStatement = 0; + p->iStatement = 0; #ifdef VDBE_PROFILE { int i; @@ -1564,6 +1564,48 @@ static void invalidateCursorsOnModifiedBtrees(sqlite3 *db){ } } +/* +** If the Vdbe passed as the first argument opened a statement-transaction, +** close it now. Argument eOp must be either SAVEPOINT_ROLLBACK or +** SAVEPOINT_RELEASE. If it is SAVEPOINT_ROLLBACK, then the statement +** transaction is rolled back. If eOp is SAVEPOINT_RELEASE, then the +** statement transaction is commtted. +** +** If an IO error occurs, an SQLITE_IOERR_XXX error code is returned. +** Otherwise SQLITE_OK. +*/ +int sqlite3VdbeCloseStatement(Vdbe *p, int eOp){ + int rc = SQLITE_OK; + if( p->iStatement ){ + int i; + const int iSavepoint = p->iStatement-1; + sqlite3 *const db = p->db; + + assert( eOp==SAVEPOINT_ROLLBACK || eOp==SAVEPOINT_RELEASE); + assert( db->nStatement>0 ); + assert( p->iStatement==(db->nStatement+db->nSavepoint) ); + + for(i=0; inDb; i++){ + int rc2 = SQLITE_OK; + Btree *pBt = db->aDb[i].pBt; + if( pBt ){ + if( eOp==SAVEPOINT_ROLLBACK ){ + rc2 = sqlite3BtreeSavepoint(pBt, SAVEPOINT_ROLLBACK, iSavepoint); + } + if( rc2==SQLITE_OK ){ + rc2 = sqlite3BtreeSavepoint(pBt, SAVEPOINT_RELEASE, iSavepoint); + } + if( rc==SQLITE_OK ){ + rc = rc2; + } + } + } + db->nStatement--; + p->iStatement = 0; + } + return rc; +} + /* ** This routine is called the when a VDBE tries to halt. If the VDBE ** has made changes and is in autocommit mode, then commit those @@ -1578,10 +1620,8 @@ static void invalidateCursorsOnModifiedBtrees(sqlite3 *db){ ** means the close did not happen and needs to be repeated. */ int sqlite3VdbeHalt(Vdbe *p){ + int rc; /* Used to store transient return codes */ sqlite3 *db = p->db; - int i; - int (*xFunc)(Btree *pBt) = 0; /* Function to call on each btree backend */ - int isSpecialError; /* Set to true if SQLITE_NOMEM or IOERR */ /* This function contains the logic that determines if a statement or ** transaction will be committed or rolled back as a result of the @@ -1611,6 +1651,8 @@ int sqlite3VdbeHalt(Vdbe *p){ /* No commit or rollback needed if the program never started */ if( p->pc>=0 ){ int mrc; /* Primary error code from p->rc */ + int eStatementOp = 0; + int isSpecialError; /* Set to true if a 'special' error */ /* Lock all btrees used by the statement */ sqlite3BtreeMutexArrayEnter(&p->aMutex); @@ -1625,11 +1667,11 @@ int sqlite3VdbeHalt(Vdbe *p){ */ if( !p->readOnly || mrc!=SQLITE_INTERRUPT ){ if( p->rc==SQLITE_IOERR_BLOCKED && p->usesStmtJournal ){ - xFunc = sqlite3BtreeRollbackStmt; + eStatementOp = SAVEPOINT_ROLLBACK; p->rc = SQLITE_BUSY; }else if( (mrc==SQLITE_NOMEM || mrc==SQLITE_FULL) && p->usesStmtJournal ){ - xFunc = sqlite3BtreeRollbackStmt; + eStatementOp = SAVEPOINT_ROLLBACK; }else{ /* We are forced to roll back the active transaction. Before doing ** so, abort any other statements this handle currently has active. @@ -1642,8 +1684,8 @@ int sqlite3VdbeHalt(Vdbe *p){ } } - /* If the auto-commit flag is set and this is the only active vdbe, then - ** we do either a commit or rollback of the current transaction. + /* If the auto-commit flag is set and this is the only active writer + ** VM, then we do either a commit or rollback of the current transaction. ** ** Note: This block also runs if one of the special errors handled ** above has occurred. @@ -1658,7 +1700,7 @@ int sqlite3VdbeHalt(Vdbe *p){ ** successful or hit an 'OR FAIL' constraint. This means a commit ** is required. */ - int rc = vdbeCommit(db, p); + rc = vdbeCommit(db, p); if( rc==SQLITE_BUSY ){ sqlite3BtreeMutexArrayLeave(&p->aMutex); return SQLITE_BUSY; @@ -1671,13 +1713,12 @@ int sqlite3VdbeHalt(Vdbe *p){ }else{ sqlite3RollbackAll(db); } - }else if( !xFunc ){ + db->nStatement = 0; + }else if( eStatementOp==0 ){ if( p->rc==SQLITE_OK || p->errorAction==OE_Fail ){ - if( p->openedStatement ){ - xFunc = sqlite3BtreeCommitStmt; - } + eStatementOp = SAVEPOINT_RELEASE; }else if( p->errorAction==OE_Abort ){ - xFunc = sqlite3BtreeRollbackStmt; + eStatementOp = SAVEPOINT_ROLLBACK; }else{ invalidateCursorsOnModifiedBtrees(db); sqlite3RollbackAll(db); @@ -1686,33 +1727,26 @@ int sqlite3VdbeHalt(Vdbe *p){ } } - /* If xFunc is not NULL, then it is one of sqlite3BtreeRollbackStmt or - ** sqlite3BtreeCommitStmt. Call it once on each backend. If an error occurs - ** and the return code is still SQLITE_OK, set the return code to the new - ** error value. + /* If eStatementOp is non-zero, then a statement transaction needs to + ** be committed or rolled back. Call sqlite3VdbeCloseStatement() to + ** do so. If this operation returns an error, and the current statement + ** error code is SQLITE_OK or SQLITE_CONSTRAINT, then set the error + ** code to the new value. */ - assert(!xFunc || - xFunc==sqlite3BtreeCommitStmt || - xFunc==sqlite3BtreeRollbackStmt - ); - for(i=0; xFunc && inDb; i++){ - int rc; - Btree *pBt = db->aDb[i].pBt; - if( pBt ){ - rc = xFunc(pBt); - if( rc && (p->rc==SQLITE_OK || p->rc==SQLITE_CONSTRAINT) ){ - p->rc = rc; - sqlite3DbFree(db, p->zErrMsg); - p->zErrMsg = 0; - } + if( eStatementOp ){ + rc = sqlite3VdbeCloseStatement(p, eStatementOp); + if( rc && (p->rc==SQLITE_OK || p->rc==SQLITE_CONSTRAINT) ){ + p->rc = rc; + sqlite3DbFree(db, p->zErrMsg); + p->zErrMsg = 0; } } - /* If this was an INSERT, UPDATE or DELETE and the statement was committed, - ** set the change counter. + /* If this was an INSERT, UPDATE or DELETE and no statement transaction + ** has been rolled back, update the database connection change-counter. */ if( p->changeCntOn && p->pc>=0 ){ - if( !xFunc || xFunc==sqlite3BtreeCommitStmt ){ + if( eStatementOp!=SAVEPOINT_ROLLBACK ){ sqlite3VdbeSetChanges(db, p->nChange); }else{ sqlite3VdbeSetChanges(db, 0); @@ -1752,6 +1786,7 @@ int sqlite3VdbeHalt(Vdbe *p){ sqlite3ConnectionUnlocked(db); } + assert( db->activeVdbeCnt>0 || db->autoCommit==0 || db->nStatement==0 ); return SQLITE_OK; } diff --git a/test/memdb.test b/test/memdb.test index 7a26981109..fd1a83dcbf 100644 --- a/test/memdb.test +++ b/test/memdb.test @@ -11,7 +11,7 @@ # This file implements regression tests for SQLite library. The # focus of this script is in-memory database backend. # -# $Id: memdb.test,v 1.17 2009/01/09 14:29:35 drh Exp $ +# $Id: memdb.test,v 1.18 2009/03/18 10:33:02 danielk1977 Exp $ set testdir [file dirname $argv0] @@ -244,7 +244,7 @@ foreach {i conf1 conf2 cmd t0 t1 t2} { do_test memdb-5.$i { if {$conf1!=""} {set conf1 "ON CONFLICT $conf1"} if {$conf2!=""} {set conf2 "ON CONFLICT $conf2"} - set r0 [catch {execsql [subst { + set r0 [catch {execsql " DROP TABLE t1; CREATE TABLE t1(a,b,c, UNIQUE(a) $conf1); INSERT INTO t1 SELECT * FROM t2; @@ -253,7 +253,7 @@ foreach {i conf1 conf2 cmd t0 t1 t2} { $cmd t3 SET x=1; $cmd t1 SET b=b*2; $cmd t1 SET a=c+5; - }]} r1] + "} r1] catch {execsql {COMMIT}} if {!$r0} {set r1 [execsql {SELECT a FROM t1 ORDER BY b}]} set r2 [execsql {SELECT x FROM t3}] diff --git a/test/tkt3718.test b/test/tkt3718.test new file mode 100644 index 0000000000..df8ef06f9f --- /dev/null +++ b/test/tkt3718.test @@ -0,0 +1,231 @@ +# 2001 September 15 +# +# 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. +# +#*********************************************************************** +# This file implements regression tests for SQLite library. The +# focus of this file is testing the execution of SQL statements from +# within callbacks generated by VMs that themselves open statement +# transactions. +# +# $Id: tkt3718.test,v 1.1 2009/03/18 10:33:02 danielk1977 Exp $ + +set testdir [file dirname $argv0] +source $testdir/tester.tcl + +do_test tkt3718-1.1 { + execsql { + CREATE TABLE t1(a PRIMARY KEY, b); + INSERT INTO t1 VALUES(1, 'one'); + INSERT INTO t1 VALUES(2, 'two'); + INSERT INTO t1 VALUES(3, 'three'); + INSERT INTO t1 VALUES(4, 'four'); + INSERT INTO t1 VALUES(5, 'five'); + CREATE TABLE t2(a PRIMARY KEY, b); + } +} {} + +# SQL scalar function: +# +# f1() +# +# Uses database handle [db] to execute "SELECT f2()". Returns either +# the results or error message from the "SELECT f2()" query to the +# caller. +# +proc f1 {args} { + set a [lindex $args 0] + catch { db eval {SELECT f2($a)} } msg + set msg +} + +# SQL scalar function: +# +# f2() +# +# Return the value of . Unless is "three", in which case throw +# an exception. +# +proc f2 {args} { + set a [lindex $args 0] + if {$a == "three"} { error "Three!!" } + return $a +} + +db func f1 f1 +db func f2 f2 + +# The second INSERT statement below uses the f1 user function such that +# half-way through the INSERT operation f1() will run an SQL statement +# that throws an exception. At one point, before #3718 was fixed, this +# caused the statement transaction belonging to the INSERT statement to +# be rolled back. The result was that some (but not all) of the rows that +# should have been inserted went missing. +# +do_test tkt3718-1.2 { + execsql { + BEGIN; + INSERT INTO t2 SELECT a, b FROM t1; + INSERT INTO t2 SELECT a+5, f1(b) FROM t1; + COMMIT; + } + execsql { + SELECT a FROM t2; + } +} {1 2 3 4 5 6 7 8 9 10} + +# This test turns on the count_changes pragma (causing DML statements to +# return SQLITE_ROW once, with a single integer result value reporting the +# number of rows affected by the statement). It then executes an INSERT +# statement that requires a statement journal. After stepping the statement +# once, so that it returns SQLITE_ROW, a second SQL statement that throws an +# exception is run. At one point, before #3718 was fixed, this caused the +# statement transaction belonging to the INSERT statement to be rolled back. +# The result was that none of the rows were actually inserted. +# +# +do_test tkt3718-1.3 { + execsql { + DELETE FROM t2 WHERE a > 5; + PRAGMA count_changes = 1; + BEGIN; + } + db eval {INSERT INTO t2 SELECT a+5, b||'+5' FROM t1} { + catch { db eval {SELECT f2('three')} } msg + } + execsql { + COMMIT; + SELECT a FROM t2; + } +} {1 2 3 4 5 6 7 8 9 10} + +do_test tkt3718-1.4 { + execsql {pragma count_changes=0} +} {} + +# This SQL function executes the SQL specified as an argument against +# database [db]. +# +proc sql {doit zSql} { + if {$doit} { catchsql $zSql } +} +db func sql [list sql] + +# The following tests, tkt3718-2.*, test that a nested statement +# transaction can be successfully committed or reverted without +# affecting the parent statement transaction. +# +do_test tkt3718-2.1 { + execsql { SELECT sql(1, 'DELETE FROM t2 WHERE a = '||a ) FROM t2 WHERE a>5 } + execsql { SELECT a from t2 } +} {1 2 3 4 5} +do_test tkt3718-2.2 { + execsql { + DELETE FROM t2 WHERE a > 5; + BEGIN; + INSERT INTO t2 SELECT a+5, sql(a==3, + 'INSERT INTO t2 SELECT a+10, f2(b) FROM t1' + ) FROM t1; + } + execsql { + COMMIT; + SELECT a FROM t2; + } +} {1 2 3 4 5 6 7 8 9 10} +do_test tkt3718-2.3 { + execsql { + DELETE FROM t2 WHERE a > 5; + BEGIN; + INSERT INTO t2 SELECT a+5, sql(a==3, + 'INSERT INTO t2 SELECT a+10, b FROM t1' + ) FROM t1; + COMMIT; + } + execsql { SELECT a FROM t2 ORDER BY a+0} +} {1 2 3 4 5 6 7 8 9 10 11 12 13 14 15} +integrity_check tkt3718.2-4 + +# The next set of tests, tkt3718-3.*, test that a statement transaction +# that has a committed statement transaction nested inside of it can +# be committed or reverted. +# +foreach {tn io ii results} { + 1 0 10 {1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20} + 2 1 10 {6 7 8 9 10 16 17 18 19 20} + 3 0 11 {1 2 3 4 5 6 7 8 9 10 16 17 18 19 20} + 4 1 11 {6 7 8 9 10 16 17 18 19 20} +} { + do_test tkt3718-3.$tn { + execsql { + DELETE FROM t2; + INSERT INTO t2 SELECT a+5, b FROM t1; + INSERT INTO t2 SELECT a+15, b FROM t1; + } + + catchsql " + BEGIN; + INSERT INTO t2 SELECT a+$io, sql(a==3, + 'INSERT INTO t2 SELECT a+$ii, b FROM t1' + ) FROM t1; + " + + execsql { COMMIT } + + execsql { SELECT a FROM t2 ORDER BY a+0} + } $results + + integrity_check tkt3718-3.$tn.integrity +} + +# This is the same test as tkt3718-3.*, but with 3 levels of nesting. +# +foreach {tn i1 i2 i3 results} { + 1 0 10 20 {5 10 15 20 25 30} + 2 0 10 21 {5 10 15 20 30} + 3 0 11 20 {5 10 20 30} + 4 0 11 21 {5 10 20 30} + 5 1 10 20 {10 20 30} + 6 1 10 21 {10 20 30} + 7 1 11 20 {10 20 30} + 8 1 11 21 {10 20 30} +} { + do_test tkt3718-4.$tn { + execsql { + DELETE FROM t2; + INSERT INTO t2 SELECT a+5, b FROM t1; + INSERT INTO t2 SELECT a+15, b FROM t1; + INSERT INTO t2 SELECT a+25, b FROM t1; + } + + catchsql " + BEGIN; + INSERT INTO t2 SELECT a+$i1, sql(a==3, + 'INSERT INTO t2 SELECT a+$i2, sql(a==3, + ''INSERT INTO t2 SELECT a+$i3, b FROM t1'' + ) FROM t1' + ) FROM t1; + " + + execsql { COMMIT } + + execsql { SELECT a FROM t2 WHERE (a%5)==0 ORDER BY a+0} + } $results + + do_test tkt3718-4.$tn.extra { + execsql { + SELECT + (SELECT sum(a) FROM t2)==(SELECT sum(a*5-10) FROM t2 WHERE (a%5)==0) + } + } {1} + + integrity_check tkt3718-4.$tn.integrity +} + + +finish_test +