Fix some cases where executing SQL from within a user-function callback could cause problems related to statement-transactions. (CVS 6355)

FossilOrigin-Name: a60f4191791dd7bb49d5c95b350a9924845b59a8
This commit is contained in:
danielk1977 2009-03-18 10:33:00 +00:00
parent 7f75122149
commit bd43455c38
13 changed files with 383 additions and 150 deletions

View File

@ -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

View File

@ -1 +1 @@
189785832a7dc9f4a0a2113d850b92b987e0f9bf
a60f4191791dd7bb49d5c95b350a9924845b59a8

View 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.
*/

View 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 *));

View 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

View 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;
}

View 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

View 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);

View 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;

View 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

View 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;
}

View 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}]

231
test/tkt3718.test Normal file
View File

@ -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