-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
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
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
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
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
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
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
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
-189785832a7dc9f4a0a2113d850b92b987e0f9bf
\ No newline at end of file
+a60f4191791dd7bb49d5c95b350a9924845b59a8
\ No newline at end of file
** 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.
releasePage(pBt->pPage1);
}
pBt->pPage1 = 0;
- pBt->inStmt = 0;
}
}
}
}
- if( rc==SQLITE_OK ){
- if( wrflag ) pBt->inStmt = 0;
- }else{
+ if( rc!=SQLITE_OK ){
unlockBtreeIfUnused(pBt);
}
}while( rc==SQLITE_BUSY && pBt->inTransaction==TRANS_NONE &&
return rc;
}
pBt->inTransaction = TRANS_READ;
- pBt->inStmt = 0;
}
clearAllSharedCacheTableLocks(p);
btreeClearHasContent(pBt);
p->inTrans = TRANS_NONE;
- pBt->inStmt = 0;
unlockBtreeIfUnused(pBt);
btreeIntegrity(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 );
** 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;
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);
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.
*/
** 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_
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 *));
** 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
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
** 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"
sqlite3DbFree(db, pTmp);
}
db->nSavepoint = 0;
+ db->nStatement = 0;
db->isTransactionSavepoint = 0;
}
*************************************************************************
** 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_
#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
** 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"
}
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);
}
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);
}
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);
** 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"
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;
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;
}
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);
goto vdbe_return;
}
}
+ assert( db->nStatement==0 );
sqlite3CloseSavepoints(db);
if( p->rc==SQLITE_OK ){
rc = SQLITE_DONE;
** 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_
#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 */
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
** 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"
p->nChange = 0;
p->cacheCtr = 1;
p->minWriteFileFormat = 255;
- p->openedStatement = 0;
+ p->iStatement = 0;
#ifdef VDBE_PROFILE
{
int i;
}
}
+/*
+** 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
** 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
/* 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);
*/
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.
}
}
- /* 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.
** 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;
}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);
}
}
- /* 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);
sqlite3ConnectionUnlocked(db);
}
+ assert( db->activeVdbeCnt>0 || db->autoCommit==0 || db->nStatement==0 );
return SQLITE_OK;
}
# 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]
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;
$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}]
--- /dev/null
+# 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
+