aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authordrh <>2023-07-15 16:48:14 +0000
committerdrh <>2023-07-15 16:48:14 +0000
commite393f6eff00bd78bbda6ecfaf1feb957a845226b (patch)
tree374504fd6f2db62416024c83105750249615ec56 /src
parenta02d6d82378765d5ada96412c6210bfde6cdda77 (diff)
downloadsqlite-e393f6eff00bd78bbda6ecfaf1feb957a845226b.tar.gz
sqlite-e393f6eff00bd78bbda6ecfaf1feb957a845226b.zip
Add the experimental sqlite3_stmt_explain(S,E) interface.
FossilOrigin-Name: 5683743ddf0bb051f2fe5d137cc18407e000e77e9faa9010a22e3134b575638b
Diffstat (limited to 'src')
-rw-r--r--src/parse.y4
-rw-r--r--src/prepare.c7
-rw-r--r--src/shell.c.in29
-rw-r--r--src/sqlite.h.in31
-rw-r--r--src/test1.c29
-rw-r--r--src/vdbeapi.c16
6 files changed, 92 insertions, 24 deletions
diff --git a/src/parse.y b/src/parse.y
index 6085c4bbe..867b62aa7 100644
--- a/src/parse.y
+++ b/src/parse.y
@@ -148,8 +148,8 @@ ecmd ::= SEMI.
ecmd ::= cmdx SEMI.
%ifndef SQLITE_OMIT_EXPLAIN
ecmd ::= explain cmdx SEMI. {NEVER-REDUCE}
-explain ::= EXPLAIN. { pParse->explain = 1; }
-explain ::= EXPLAIN QUERY PLAN. { pParse->explain = 2; }
+explain ::= EXPLAIN. { if( pParse->pReprepare==0 ) pParse->explain = 1; }
+explain ::= EXPLAIN QUERY PLAN. { if( pParse->pReprepare==0 ) pParse->explain = 2; }
%endif SQLITE_OMIT_EXPLAIN
cmdx ::= cmd. { sqlite3FinishCoding(pParse); }
diff --git a/src/prepare.c b/src/prepare.c
index 39e8dcf65..9f843faa8 100644
--- a/src/prepare.c
+++ b/src/prepare.c
@@ -700,7 +700,12 @@ static int sqlite3Prepare(
sParse.pOuterParse = db->pParse;
db->pParse = &sParse;
sParse.db = db;
- sParse.pReprepare = pReprepare;
+ if( pReprepare ){
+ sParse.pReprepare = pReprepare;
+ sParse.explain = sqlite3_stmt_isexplain((sqlite3_stmt*)pReprepare);
+ }else{
+ assert( sParse.pReprepare==0 );
+ }
assert( ppStmt && *ppStmt==0 );
if( db->mallocFailed ){
sqlite3ErrorMsg(&sParse, "out of memory");
diff --git a/src/shell.c.in b/src/shell.c.in
index 9165f21f7..ed1d8e6c4 100644
--- a/src/shell.c.in
+++ b/src/shell.c.in
@@ -3453,8 +3453,6 @@ static int str_in_array(const char *zStr, const char **azArray){
** and "Goto" by 2 spaces.
*/
static void explain_data_prepare(ShellState *p, sqlite3_stmt *pSql){
- const char *zSql; /* The text of the SQL statement */
- const char *z; /* Used to check if this is an EXPLAIN */
int *abYield = 0; /* True if op is an OP_Yield */
int nAlloc = 0; /* Allocated size of p->aiIndent[], abYield */
int iOp; /* Index of operation in p->aiIndent[] */
@@ -3471,10 +3469,7 @@ static void explain_data_prepare(ShellState *p, sqlite3_stmt *pSql){
p->cMode = p->mode;
return;
}
- zSql = sqlite3_sql(pSql);
- if( zSql==0 ) return;
- for(z=zSql; *z==' ' || *z=='\t' || *z=='\n' || *z=='\f' || *z=='\r'; z++);
- if( sqlite3_strnicmp(z, "explain", 7) ){
+ if( sqlite3_stmt_isexplain(pSql)!=1 ){
p->cMode = p->mode;
return;
}
@@ -4332,16 +4327,15 @@ static int shell_exec(
/* Show the EXPLAIN QUERY PLAN if .eqp is on */
if( pArg && pArg->autoEQP && sqlite3_stmt_isexplain(pStmt)==0 ){
sqlite3_stmt *pExplain;
- char *zEQP;
int triggerEQP = 0;
disable_debug_trace_modes();
sqlite3_db_config(db, SQLITE_DBCONFIG_TRIGGER_EQP, -1, &triggerEQP);
if( pArg->autoEQP>=AUTOEQP_trigger ){
sqlite3_db_config(db, SQLITE_DBCONFIG_TRIGGER_EQP, 1, 0);
}
- zEQP = sqlite3_mprintf("EXPLAIN QUERY PLAN %s", zStmtSql);
- shell_check_oom(zEQP);
- rc = sqlite3_prepare_v2(db, zEQP, -1, &pExplain, 0);
+ pExplain = pStmt;
+ sqlite3_reset(pExplain);
+ rc = sqlite3_stmt_explain(pExplain, 2);
if( rc==SQLITE_OK ){
while( sqlite3_step(pExplain)==SQLITE_ROW ){
const char *zEQPLine = (const char*)sqlite3_column_text(pExplain,3);
@@ -4353,29 +4347,22 @@ static int shell_exec(
}
eqp_render(pArg, 0);
}
- sqlite3_finalize(pExplain);
- sqlite3_free(zEQP);
if( pArg->autoEQP>=AUTOEQP_full ){
/* Also do an EXPLAIN for ".eqp full" mode */
- zEQP = sqlite3_mprintf("EXPLAIN %s", zStmtSql);
- shell_check_oom(zEQP);
- rc = sqlite3_prepare_v2(db, zEQP, -1, &pExplain, 0);
+ sqlite3_reset(pExplain);
+ rc = sqlite3_stmt_explain(pExplain, 1);
if( rc==SQLITE_OK ){
pArg->cMode = MODE_Explain;
explain_data_prepare(pArg, pExplain);
exec_prepared_stmt(pArg, pExplain);
explain_data_delete(pArg);
}
- sqlite3_finalize(pExplain);
- sqlite3_free(zEQP);
}
if( pArg->autoEQP>=AUTOEQP_trigger && triggerEQP==0 ){
sqlite3_db_config(db, SQLITE_DBCONFIG_TRIGGER_EQP, 0, 0);
- /* Reprepare pStmt before reactivating trace modes */
- sqlite3_finalize(pStmt);
- sqlite3_prepare_v2(db, zSql, -1, &pStmt, 0);
- if( pArg ) pArg->pStmt = pStmt;
}
+ sqlite3_reset(pStmt);
+ sqlite3_stmt_explain(pStmt, 0);
restore_debug_trace_modes();
}
diff --git a/src/sqlite.h.in b/src/sqlite.h.in
index 59d9986c5..9e165c1e5 100644
--- a/src/sqlite.h.in
+++ b/src/sqlite.h.in
@@ -4422,6 +4422,37 @@ int sqlite3_stmt_readonly(sqlite3_stmt *pStmt);
int sqlite3_stmt_isexplain(sqlite3_stmt *pStmt);
/*
+** CAPI3REF: Change The EXPLAIN Setting For A Prepared Statement
+** METHOD: sqlite3_stmt
+**
+** ^The sqlite3_stmt_explain(S,E) interface changes the EXPLAIN
+** setting for prepared statement S. If E is zero, then S becomes
+** a normal prepared statement. If E is 1, then S behaves as if
+** its SQL text began with "EXPLAIN". If E is 2, then S behaves as if
+** its SQL text began with "EXPLAIN QUERY PLAN".
+**
+** Calling sqlite3_stmt_explain(S,E) causes prepared statement S
+** to be reprepared. A call to sqlite3_stmt_explain(S,E) will fail
+** with SQLITE_ERROR if S cannot be re-prepared because it was created
+** using sqlite3_prepare() instead of the newer sqlite_prepare_v2() or
+** sqlite3_prepare_v3() interfaces and hence has no saved SQL text with
+** which to reprepare. Changing the explain setting for a prepared
+** statement does not change the original SQL text for the statement.
+** Hence, if the SQL text originally began with EXPLAIN or EXPLAIN
+** QUERY PLAN, but sqlite3_stmt_explain(S,0) is called to convert the
+** statement into an ordinary statement, the EXPLAIN or EXPLAIN QUERY
+** PLAN keywords still appear in the sqlite3_sql(S) output, even though
+** the statement now acts like a normal SQL statement.
+**
+** This routine returns SQLITE_OK if the explain mode is successfully
+** changed, or an error code if the explain mode could not be changed.
+** The explain mode cannot be changed while a statement is active.
+** Hence, it is good practice to call [sqlite3_reset(S)]
+** immediately prior to calling sqlite3_stmt_explain(S,E).
+*/
+int sqlite3_stmt_explain(sqlite3_stmt *pStmt, int eMode);
+
+/*
** CAPI3REF: Determine If A Prepared Statement Has Been Reset
** METHOD: sqlite3_stmt
**
diff --git a/src/test1.c b/src/test1.c
index adc862156..520508d1c 100644
--- a/src/test1.c
+++ b/src/test1.c
@@ -2923,6 +2923,34 @@ static int SQLITE_TCLAPI test_stmt_isexplain(
}
/*
+** Usage: sqlite3_stmt_explain STMT INT
+**
+** Set the explain to normal (0), EXPLAIN (1) or EXPLAIN QUERY PLAN (2).
+*/
+static int SQLITE_TCLAPI test_stmt_explain(
+ void * clientData,
+ Tcl_Interp *interp,
+ int objc,
+ Tcl_Obj *CONST objv[]
+){
+ sqlite3_stmt *pStmt;
+ int eMode = 0;
+ int rc;
+
+ if( objc!=3 ){
+ Tcl_AppendResult(interp, "wrong # args: should be \"",
+ Tcl_GetStringFromObj(objv[0], 0), " STMT INT", 0);
+ return TCL_ERROR;
+ }
+
+ if( getStmtPointer(interp, Tcl_GetString(objv[1]), &pStmt) ) return TCL_ERROR;
+ if( Tcl_GetIntFromObj(interp, objv[2], &eMode) ) return TCL_ERROR;
+ rc = sqlite3_stmt_explain(pStmt, eMode);
+ Tcl_SetObjResult(interp, Tcl_NewIntObj(rc));
+ return TCL_OK;
+}
+
+/*
** Usage: sqlite3_stmt_busy STMT
**
** Return true if STMT is a non-NULL pointer to a statement
@@ -8991,6 +9019,7 @@ int Sqlitetest1_Init(Tcl_Interp *interp){
{ "sqlite3_next_stmt", test_next_stmt ,0 },
{ "sqlite3_stmt_readonly", test_stmt_readonly ,0 },
{ "sqlite3_stmt_isexplain", test_stmt_isexplain,0 },
+ { "sqlite3_stmt_explain", test_stmt_explain ,0 },
{ "sqlite3_stmt_busy", test_stmt_busy ,0 },
{ "uses_stmt_journal", uses_stmt_journal ,0 },
diff --git a/src/vdbeapi.c b/src/vdbeapi.c
index 920780a89..885b17f76 100644
--- a/src/vdbeapi.c
+++ b/src/vdbeapi.c
@@ -1815,6 +1815,22 @@ int sqlite3_stmt_isexplain(sqlite3_stmt *pStmt){
}
/*
+** Set the explain mode for a statement.
+*/
+int sqlite3_stmt_explain(sqlite3_stmt *pStmt, int eMode){
+ Vdbe *v = (Vdbe*)pStmt;
+ int rc;
+ if( v->eVdbeState!=VDBE_READY_STATE ) return SQLITE_BUSY;
+ if( v->explain==eMode ) return SQLITE_OK;
+ if( v->zSql==0 || eMode<0 || eMode>2 ) return SQLITE_ERROR;
+ sqlite3_mutex_enter(v->db->mutex);
+ v->explain = eMode;
+ rc = sqlite3Reprepare(v);
+ sqlite3_mutex_leave(v->db->mutex);
+ return rc;
+}
+
+/*
** Return true if the prepared statement is in need of being reset.
*/
int sqlite3_stmt_busy(sqlite3_stmt *pStmt){