]> git.ipfire.org Git - thirdparty/sqlite.git/commitdiff
Fix some cases where executing SQL from within a user-function callback could cause...
authordanielk1977 <danielk1977@noemail.net>
Wed, 18 Mar 2009 10:33:00 +0000 (10:33 +0000)
committerdanielk1977 <danielk1977@noemail.net>
Wed, 18 Mar 2009 10:33:00 +0000 (10:33 +0000)
FossilOrigin-Name: a60f4191791dd7bb49d5c95b350a9924845b59a8

13 files changed:
manifest
manifest.uuid
src/btree.c
src/btree.h
src/btreeInt.h
src/main.c
src/sqliteInt.h
src/test3.c
src/vdbe.c
src/vdbeInt.h
src/vdbeaux.c
test/memdb.test
test/tkt3718.test [new file with mode: 0644]

index e89d0e548fbdf80ccc9f22c2b4b966119b129500..5626aee46fec5c7713047109e607d9f1a3ef25f8 100644 (file)
--- 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
index 643d99ae91c84fed414da59a2a98e868098554ea..48b10606ed78003a6e30f0fe6aeaaa12292a7a3f 100644 (file)
@@ -1 +1 @@
-189785832a7dc9f4a0a2113d850b92b987e0f9bf
\ No newline at end of file
+a60f4191791dd7bb49d5c95b350a9924845b59a8
\ No newline at end of file
index 15fe1e8a3db1994d6b1356c6a294eb1d9efbc961..06e7536870c0e1b3c42b0ef066f7acdc3cd4ec3f 100644 (file)
@@ -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.
 */
index 52c5c0c517ee9ed0ca1bbc471ad367481f8c756d..283e556016ec0fb539f99ae7a85383d53f536862 100644 (file)
@@ -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 *));
index 8ef5ef09db41ebdd66aeb91f80806384adae705d..3d2d02c9ea5c264635d824eeeec8d9264bc143b6 100644 (file)
@@ -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
index 9f900cc891784c86882fd6cd0817a0f2a88580b8..605f138ab5af44c59c0b7f12af0b2df04aaf40d4 100644 (file)
@@ -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;
 }
 
index 015b386ed2033de90166ed97eff35393946db643..f2833180da389b9103688ee2a2fb105e3eb29b8e 100644 (file)
@@ -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
index 0d915f3cb56ed5f340d2fd94f8d8300bb4f64d99..0f1044799b91ad5424ddeed4b262dedcb356b447 100644 (file)
@@ -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);
index d509068f24dcd20ccf5e2125d59310c2286b4525..97f5ae69610fff2a98ae9350fadff5d61a85460d 100644 (file)
@@ -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<<i))!=0 );
-    if( !sqlite3BtreeIsInStmt(pBt) ){
-      rc = sqlite3BtreeBeginStmt(pBt);
-      p->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;
index 1d7ed689daaf6db1994f9387986602ddfe700e2f..c564d98afa0b9ee1a0e3cf9175c318d5ae3cc794 100644 (file)
@@ -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
index 42e3f13c72c70c821091344710d99f4ed3061ab9..5402acdbd2b073bca0afcbf05b9ad1ae2b00934c 100644 (file)
@@ -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; i<db->nDb; 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 && i<db->nDb; 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;
 }
 
index 7a26981109264009f794c38f7559dbd35d7d15f2..fd1a83dcbfbb48c33fca14fc8fd40133b1057bdc 100644 (file)
@@ -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 (file)
index 0000000..df8ef06
--- /dev/null
@@ -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(<arg>)
+#
+# Uses database handle [db] to execute "SELECT f2(<arg>)". Returns either
+# the results or error message from the "SELECT f2(<arg>)" 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(<arg>)
+#
+# Return the value of <arg>. Unless <arg> 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
+