Index: src/sqlite.h.in ================================================================== --- src/sqlite.h.in +++ src/sqlite.h.in @@ -3740,10 +3740,21 @@ ** for example, in diagnostic routines to search for prepared ** statements that are holding a transaction open. */ int sqlite3_stmt_busy(sqlite3_stmt*); +/* +** CAPI3REF: Recompile A Prepared Statement If Needed +** METHOD: sqlite3_stmt +** +** ^The sqlite3_stmt_refresh(S) examines the [prepared statement] S +** and attempts to recompile it if it has expired, for example due +** to a schema change. ^If the prepared statement S is up-to-date and +** ready for use, then sqlite3_stmt_refresh(S) is a no-op. +*/ +int sqlite3_stmt_refresh(sqlite3_stmt*); + /* ** CAPI3REF: Dynamically Typed Value Object ** KEYWORDS: {protected sqlite3_value} {unprotected sqlite3_value} ** ** SQLite uses the sqlite3_value object to represent all values Index: src/test1.c ================================================================== --- src/test1.c +++ src/test1.c @@ -2635,10 +2635,36 @@ if( getStmtPointer(interp, Tcl_GetString(objv[1]), &pStmt) ) return TCL_ERROR; rc = sqlite3_stmt_busy(pStmt); Tcl_SetObjResult(interp, Tcl_NewBooleanObj(rc)); return TCL_OK; } + +/* +** Usage: sqlite3_stmt_refresh STMT +** +** Recompile the STMT prepared statement if it needs to be recompiled. +*/ +static int SQLITE_TCLAPI test_stmt_refresh( + 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_refresh(pStmt); + Tcl_SetObjResult(interp, Tcl_NewBooleanObj(rc)); + return TCL_OK; +} /* ** Usage: uses_stmt_journal STMT ** ** Return true if STMT uses a statement journal. @@ -7489,10 +7515,11 @@ { "sqlite3_sql", test_sql ,0 }, { "sqlite3_expanded_sql", test_ex_sql ,0 }, { "sqlite3_next_stmt", test_next_stmt ,0 }, { "sqlite3_stmt_readonly", test_stmt_readonly ,0 }, { "sqlite3_stmt_busy", test_stmt_busy ,0 }, + { "sqlite3_stmt_refresh", test_stmt_refresh ,0 }, { "uses_stmt_journal", uses_stmt_journal ,0 }, { "sqlite3_release_memory", test_release_memory, 0}, { "sqlite3_db_release_memory", test_db_release_memory, 0}, { "sqlite3_db_cacheflush", test_db_cacheflush, 0}, Index: src/vdbe.c ================================================================== --- src/vdbe.c +++ src/vdbe.c @@ -3189,10 +3189,13 @@ } p->expired = 1; rc = SQLITE_SCHEMA; } if( rc ) goto abort_due_to_error; + if( p->stopAfterInit && pOp[1].opcode!=OP_Transaction ){ + goto vdbe_return; + } break; } /* Opcode: ReadCookie P1 P2 P3 * * ** Index: src/vdbeInt.h ================================================================== --- src/vdbeInt.h +++ src/vdbeInt.h @@ -390,10 +390,11 @@ bft changeCntOn:1; /* True to update the change-counter */ bft runOnlyOnce:1; /* Automatically expire on reset */ bft usesStmtJournal:1; /* True if uses a statement journal */ bft readOnly:1; /* True for statements that do not write */ bft bIsReader:1; /* True for statements that read */ + bft stopAfterInit:1; /* Halt after running the last OP_Transaction */ yDbMask btreeMask; /* Bitmask of db->aDb[] entries referenced */ yDbMask lockMask; /* Subset of btreeMask that requires a lock */ u32 aCounter[7]; /* Counters used by sqlite3_stmt_status() */ char *zSql; /* Text of the SQL statement that generated this */ void *pFree; /* Free this when deleting the vdbe */ Index: src/vdbeapi.c ================================================================== --- src/vdbeapi.c +++ src/vdbeapi.c @@ -1605,10 +1605,32 @@ */ int sqlite3_stmt_busy(sqlite3_stmt *pStmt){ Vdbe *v = (Vdbe*)pStmt; return v!=0 && v->magic==VDBE_MAGIC_RUN && v->pc>=0; } + +/* +** Recompile the prepared statement if it has expired +*/ +int sqlite3_stmt_refresh(sqlite3_stmt *pStmt){ + int rc = SQLITE_OK; + if( !sqlite3_stmt_busy(pStmt) ){ + VdbeOp *aOp; + Vdbe *v = (Vdbe*)pStmt; + sqlite3_mutex_enter(v->db->mutex); + aOp = v->aOp; + assert( aOp[0].opcode==OP_Init ); + if( aOp[aOp[0].p2].opcode==OP_Transaction ){ + v->stopAfterInit = 1; + rc = sqlite3_step(pStmt); + v->stopAfterInit = 0; + sqlite3_reset(pStmt); + } + sqlite3_mutex_leave(v->db->mutex); + } + return rc; +} /* ** Return a pointer to the next prepared statement after pStmt associated ** with database connection pDb. If pStmt is NULL, return the first ** prepared statement for the database connection. Return NULL if there Index: test/capi3d.test ================================================================== --- test/capi3d.test +++ test/capi3d.test @@ -177,7 +177,32 @@ do_test capi3d-4.2.6 { sqlite3_finalize $::s1 } {SQLITE_OK} +# Tests for the sqlite3_stmt_refresh() interface +# +do_execsql_test capi3d-5.1 { + DROP TABLE IF EXISTS t5; + CREATE TABLE t5(a,b); + INSERT INTO t5 VALUES(1,2),(3,4); +} +do_test capi3d-5.2 { + set ::s1 [sqlite3_prepare_v2 db "SELECT * FROM t5" -1 notused] + sqlite3 db2 test.db + db2 eval {SELECT * FROM t5 ORDER BY a} +} {1 2 3 4} +do_test capi3d-5.3 { + sqlite3_column_count $::s1 +} 2 +do_test capi3d-5.4 { + db2 eval {ALTER TABLE t5 ADD COLUMN c DEFAULT 9} + sqlite3_column_count $::s1 +} 2 +do_test capi3d-5.5 { + sqlite3_stmt_refresh $::s1 + sqlite3_column_count $::s1 +} 3 +sqlite3_finalize $::s1 +db2 close finish_test