Index: src/sqliteInt.h ================================================================== --- src/sqliteInt.h +++ src/sqliteInt.h @@ -869,13 +869,10 @@ void *pProgressArg; /* Argument to the progress callback */ int nProgressOps; /* Number of opcodes for progress callback */ #endif #ifndef SQLITE_OMIT_VIRTUALTABLE Hash aModule; /* populated by sqlite3_create_module() */ -#if 0 - Table *pVTab; /* vtab with active Connect/Create method */ -#endif VtabCtx *pVtabCtx; /* Context for active vtab connect/create */ VTable **aVTrans; /* Virtual tables with open transactions */ int nVTrans; /* Allocated size of aVTrans */ VTable *pDisconnect; /* Disconnect these in next sqlite3_prepare() */ #endif Index: src/test1.c ================================================================== --- src/test1.c +++ src/test1.c @@ -12,10 +12,11 @@ ** Code for testing all sorts of SQLite interfaces. This code ** is not included in the SQLite library. It is used for automated ** testing of the SQLite library. */ #include "sqliteInt.h" +#include "vdbeInt.h" #include "tcl.h" #include #include /* @@ -2323,10 +2324,36 @@ if( getStmtPointer(interp, Tcl_GetString(objv[1]), &pStmt) ) return TCL_ERROR; rc = sqlite3_stmt_readonly(pStmt); Tcl_SetObjResult(interp, Tcl_NewBooleanObj(rc)); return TCL_OK; } + +/* +** Usage: uses_stmt_journal STMT +** +** Return true if STMT uses a statement journal. +*/ +static int uses_stmt_journal( + void * clientData, + Tcl_Interp *interp, + int objc, + Tcl_Obj *CONST objv[] +){ + sqlite3_stmt *pStmt; + int rc; + + if( objc!=2 ){ + Tcl_AppendResult(interp, "wrong # args: should be \"", + Tcl_GetStringFromObj(objv[0], 0), " STMT", 0); + return TCL_ERROR; + } + + if( getStmtPointer(interp, Tcl_GetString(objv[1]), &pStmt) ) return TCL_ERROR; + rc = sqlite3_stmt_readonly(pStmt); + Tcl_SetObjResult(interp, Tcl_NewBooleanObj(((Vdbe *)pStmt)->usesStmtJournal)); + return TCL_OK; +} /* ** Usage: sqlite3_reset STMT ** @@ -5581,10 +5608,11 @@ { "sqlite3_changes", test_changes ,0 }, { "sqlite3_step", test_step ,0 }, { "sqlite3_sql", test_sql ,0 }, { "sqlite3_next_stmt", test_next_stmt ,0 }, { "sqlite3_stmt_readonly", test_stmt_readonly ,0 }, + { "uses_stmt_journal", uses_stmt_journal ,0 }, { "sqlite3_release_memory", test_release_memory, 0}, { "sqlite3_soft_heap_limit", test_soft_heap_limit, 0}, { "sqlite3_thread_cleanup", test_thread_cleanup, 0}, { "sqlite3_pager_refcounts", test_pager_refcounts, 0}, Index: src/vdbe.c ================================================================== --- src/vdbe.c +++ src/vdbe.c @@ -2578,10 +2578,18 @@ "SQL statements in progress"); rc = SQLITE_BUSY; }else{ nName = sqlite3Strlen30(zName); + /* This call is Ok even if this savepoint is actually a transaction + ** savepoint (and therefore should not prompt xSavepoint()) callbacks. + ** If this is a transaction savepoint being opened, it is guaranteed + ** that the db->aVTrans[] array is empty. */ + assert( db->autoCommit==0 || db->nVTrans==0 ); + rc = sqlite3VtabSavepoint(db, SAVEPOINT_BEGIN, p->iStatement); + if( rc!=SQLITE_OK ) goto abort_due_to_error; + /* Create a new savepoint structure. */ pNew = sqlite3DbMallocRaw(db, sizeof(Savepoint)+nName+1); if( pNew ){ pNew->zName = (char *)&pNew[1]; memcpy(pNew->zName, zName, nName+1); @@ -2684,10 +2692,15 @@ db->nSavepoint--; } }else{ db->nDeferredCons = pSavepoint->nDeferredCons; } + + if( !isTransaction ){ + rc = sqlite3VtabSavepoint(db, p1, iSavepoint); + if( rc!=SQLITE_OK ) goto abort_due_to_error; + } } } break; } Index: src/vtab.c ================================================================== --- src/vtab.c +++ src/vtab.c @@ -654,12 +654,11 @@ int rc = SQLITE_OK; Table *pTab; char *zErr = 0; sqlite3_mutex_enter(db->mutex); - pTab = db->pVtabCtx->pTab; - if( !pTab ){ + if( !db->pVtabCtx || !(pTab = db->pVtabCtx->pTab) ){ sqlite3Error(db, SQLITE_MISUSE, 0); sqlite3_mutex_leave(db->mutex); return SQLITE_MISUSE_BKPT; } assert( (pTab->tabFlags & TF_Virtual)!=0 ); @@ -850,30 +849,47 @@ } } return rc; } +/* +** Invoke either the xSavepoint, xRollbackTo or xRelease method of all +** virtual tables that currently have an open transaction. Pass iSavepoint +** as the second argument to the virtual table method invoked. +** +** If op is SAVEPOINT_BEGIN, the xSavepoint method is invoked. If it is +** SAVEPOINT_ROLLBACK, the xRollbackTo method. Otherwise, if op is +** SAVEPOINT_RELEASE, then the xRelease method of each virtual table with +** an open transaction is invoked. +** +** If any virtual table method returns an error code other than SQLITE_OK, +** processing is abandoned and the error returned to the caller of this +** function immediately. If all calls to virtual table methods are successful, +** SQLITE_OK is returned. +*/ int sqlite3VtabSavepoint(sqlite3 *db, int op, int iSavepoint){ - int i; int rc = SQLITE_OK; assert( op==SAVEPOINT_RELEASE||op==SAVEPOINT_ROLLBACK||op==SAVEPOINT_BEGIN ); - - for(i=0; rc==SQLITE_OK && inVTrans; i++){ - sqlite3_vtab *pVtab = db->aVTrans[i]->pVtab; - sqlite3_module *pMod = db->aVTrans[i]->pMod->pModule; - if( pMod->iVersion>=1 ){ - switch( op ){ - case SAVEPOINT_BEGIN: - rc = pMod->xSavepoint(pVtab, iSavepoint); - break; - case SAVEPOINT_ROLLBACK: - rc = pMod->xRollbackTo(pVtab, iSavepoint); - break; - default: - rc = pMod->xRelease(pVtab, iSavepoint); - break; + if( db->aVTrans ){ + int i; + for(i=0; rc==SQLITE_OK && inVTrans; i++){ + const sqlite3_module *pMod = db->aVTrans[i]->pMod->pModule; + if( pMod->iVersion>=1 ){ + int (*xMethod)(sqlite3_vtab *, int); + switch( op ){ + case SAVEPOINT_BEGIN: + xMethod = pMod->xSavepoint; + break; + case SAVEPOINT_ROLLBACK: + xMethod = pMod->xRollbackTo; + break; + default: + xMethod = pMod->xRelease; + break; + } + if( xMethod ) rc = xMethod(db->aVTrans[i]->pVtab, iSavepoint); } } } return rc; } Index: test/fts3conf.test ================================================================== --- test/fts3conf.test +++ test/fts3conf.test @@ -54,10 +54,18 @@ } uplevel [list do_test $tn [list set {} $m1] $m2] } +proc sql_uses_stmt {db sql} { + set stmt [sqlite3_prepare db $sql -1 dummy] + set uses [uses_stmt_journal $stmt] + sqlite3_finalize $stmt + return $uses +} + + do_execsql_test 1.0.1 { CREATE VIRTUAL TABLE t1 USING fts3(x); INSERT INTO t1(rowid, x) VALUES(1, 'a b c d'); @@ -73,34 +81,34 @@ set T2 "INTO t1(rowid, x) SELECT * FROM source" set T3 "t1 SET docid = 2 WHERE docid = 1" set T4 "t1 SET docid = CASE WHEN docid = 1 THEN 4 ELSE 3 END WHERE docid <=2" -foreach {tn sql constraint data} [subst { - 1 "INSERT OR ROLLBACK $T1" 1 {{a b c d} {e f g h}} - 2 "INSERT OR ABORT $T1" 1 {{a b c d} {e f g h} {i j k l}} - 3 "INSERT OR FAIL $T1" 1 {{a b c d} {e f g h} {i j k l}} - 4 "INSERT OR IGNORE $T1" 0 {{a b c d} {e f g h} {i j k l}} - 5 "INSERT OR REPLACE $T1" 0 {x {e f g h} {i j k l}} - - 6 "INSERT OR ROLLBACK $T2" 1 {{a b c d} {e f g h}} - 7 "INSERT OR ABORT $T2" 1 {{a b c d} {e f g h} {i j k l}} - 8 "INSERT OR FAIL $T2" 1 {{a b c d} {e f g h} {i j k l} z} - 9 "INSERT OR IGNORE $T2" 0 {{a b c d} {e f g h} {i j k l} z} - 10 "INSERT OR REPLACE $T2" 0 {{a b c d} y {i j k l} z} - - 11 "UPDATE OR ROLLBACK $T3" 1 {{a b c d} {e f g h}} - 12 "UPDATE OR ABORT $T3" 1 {{a b c d} {e f g h} {i j k l}} - 13 "UPDATE OR FAIL $T3" 1 {{a b c d} {e f g h} {i j k l}} - 14 "UPDATE OR IGNORE $T3" 0 {{a b c d} {e f g h} {i j k l}} - 15 "UPDATE OR REPLACE $T3" 0 {{a b c d} {i j k l}} - - 16 "UPDATE OR ROLLBACK $T4" 1 {{a b c d} {e f g h}} - 17 "UPDATE OR ABORT $T4" 1 {{a b c d} {e f g h} {i j k l}} - 18 "UPDATE OR FAIL $T4" 1 {{e f g h} {i j k l} {a b c d}} - 19 "UPDATE OR IGNORE $T4" 0 {{e f g h} {i j k l} {a b c d}} - 20 "UPDATE OR REPLACE $T4" 0 {{e f g h} {a b c d}} +foreach {tn sql uses constraint data} [subst { + 1 "INSERT OR ROLLBACK $T1" 0 1 {{a b c d} {e f g h}} + 2 "INSERT OR ABORT $T1" 0 1 {{a b c d} {e f g h} {i j k l}} + 3 "INSERT OR FAIL $T1" 0 1 {{a b c d} {e f g h} {i j k l}} + 4 "INSERT OR IGNORE $T1" 0 0 {{a b c d} {e f g h} {i j k l}} + 5 "INSERT OR REPLACE $T1" 0 0 {x {e f g h} {i j k l}} + + 6 "INSERT OR ROLLBACK $T2" 1 1 {{a b c d} {e f g h}} + 7 "INSERT OR ABORT $T2" 1 1 {{a b c d} {e f g h} {i j k l}} + 8 "INSERT OR FAIL $T2" 1 1 {{a b c d} {e f g h} {i j k l} z} + 9 "INSERT OR IGNORE $T2" 1 0 {{a b c d} {e f g h} {i j k l} z} + 10 "INSERT OR REPLACE $T2" 1 0 {{a b c d} y {i j k l} z} + + 11 "UPDATE OR ROLLBACK $T3" 1 1 {{a b c d} {e f g h}} + 12 "UPDATE OR ABORT $T3" 1 1 {{a b c d} {e f g h} {i j k l}} + 13 "UPDATE OR FAIL $T3" 1 1 {{a b c d} {e f g h} {i j k l}} + 14 "UPDATE OR IGNORE $T3" 1 0 {{a b c d} {e f g h} {i j k l}} + 15 "UPDATE OR REPLACE $T3" 1 0 {{a b c d} {i j k l}} + + 16 "UPDATE OR ROLLBACK $T4" 1 1 {{a b c d} {e f g h}} + 17 "UPDATE OR ABORT $T4" 1 1 {{a b c d} {e f g h} {i j k l}} + 18 "UPDATE OR FAIL $T4" 1 1 {{e f g h} {i j k l} {a b c d}} + 19 "UPDATE OR IGNORE $T4" 1 0 {{e f g h} {i j k l} {a b c d}} + 20 "UPDATE OR REPLACE $T4" 1 0 {{e f g h} {a b c d}} }] { db_restore_and_reopen execsql { BEGIN; INSERT INTO t1(rowid, x) VALUES(3, 'i j k l'); @@ -110,9 +118,22 @@ do_catchsql_test 1.$tn.1 $sql $R($constraint) do_catchsql_test 1.$tn.2 { SELECT * FROM t1 } [list 0 $data] catchsql COMMIT fts3_integrity 1.$tn.3 db t1 + + do_test 1.$tn.4 [list sql_uses_stmt db $sql] $uses +} + +do_execsql_test 2.1.1 { + DELETE FROM t1; + BEGIN; + INSERT INTO t1 VALUES('a b c'); + SAVEPOINT a; + INSERT INTO t1 VALUES('x y z'); + ROLLBACK TO a; + COMMIT; } +fts3_integrity 2.1.2 db t1 finish_test