From e393f6eff00bd78bbda6ecfaf1feb957a845226b Mon Sep 17 00:00:00 2001 From: drh <> Date: Sat, 15 Jul 2023 16:48:14 +0000 Subject: [PATCH 1/2] Add the experimental sqlite3_stmt_explain(S,E) interface. FossilOrigin-Name: 5683743ddf0bb051f2fe5d137cc18407e000e77e9faa9010a22e3134b575638b --- manifest | 27 +++++++++++++++------------ manifest.uuid | 2 +- src/parse.y | 4 ++-- src/prepare.c | 7 ++++++- src/shell.c.in | 29 ++++++++--------------------- src/sqlite.h.in | 31 +++++++++++++++++++++++++++++++ src/test1.c | 29 +++++++++++++++++++++++++++++ src/vdbeapi.c | 16 ++++++++++++++++ 8 files changed, 108 insertions(+), 37 deletions(-) diff --git a/manifest b/manifest index 918e28c08b..560436430f 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Minor\sinternal\scleanups\sin\sthe\sOPFS\sVFS. -D 2023-07-14T21:06:00.870 +C Add\sthe\sexperimental\ssqlite3_stmt_explain(S,E)\sinterface. +D 2023-07-15T16:48:14.281 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -626,20 +626,20 @@ F src/os_win.c 7038223a1cda0a47e2ab4db47f63bf1833fe53ba0542f0f283a062ea13894103 F src/os_win.h 7b073010f1451abe501be30d12f6bc599824944a F src/pager.c 5ddf3a74c633a008ea6b2f5b3186167e88e2c8ca8a252ecab06ab3f1eb48e60f F src/pager.h f82e9844166e1585f5786837ddc7709966138ced17f568c16af7ccf946c2baa3 -F src/parse.y 8828f9e15f04d469eab9c0f2aed504e534b1c97c68836bed6f07afab29c2ac0b +F src/parse.y aeb7760d41cfa86465e3adba506500c021597049fd55f82a30e5b7045862c28c F src/pcache.c 4cd4a0043167da9ba7e19b4d179a0e6354e7fe32c16f781ecf9bf0a5ff63b40b F src/pcache.h 1497ce1b823cf00094bb0cf3bac37b345937e6f910890c626b16512316d3abf5 F src/pcache1.c 602acb23c471bb8d557a6f0083cc2be641d6cafcafa19e481eba7ef4c9ca0f00 F src/pragma.c 37b8fb02d090262280c86e1e2654bf59d8dbfbfe8dc6733f2b968a11374c095a F src/pragma.h e690a356c18e98414d2e870ea791c1be1545a714ba623719deb63f7f226d8bb7 -F src/prepare.c d6c4354f8ea0dc06962fbabc4b68c4471a45276a2918c929be00f9f537f69eb1 +F src/prepare.c 80548297dc0e1fb3139cdebffb5a1bcac3dfac66d791012dd74838e70445072d F src/printf.c 84b7b4b647f336934a5ab2e7f0c52555833cc0778d2d60e016cca52ee8c6cd8f F src/random.c 606b00941a1d7dd09c381d3279a058d771f406c5213c9932bbd93d5587be4b9c F src/resolve.c 37953a5f36c60bea413c3c04efcd433b6177009f508ef2ace0494728912fe2e9 F src/rowset.c 8432130e6c344b3401a8874c3cb49fefe6873fec593294de077afea2dce5ec97 F src/select.c 3ab1186290a311a8ceed1286c0e286209f7fe97b2d02c7593258004ce295dd88 -F src/shell.c.in d320d8a13636de06d777cc1eab981caca304e175464e98183cf4ea68d93db818 -F src/sqlite.h.in f999ef3642f381d69679b2516b430dbcb6c5a2a951b7f5e43dc4751b474a5774 +F src/shell.c.in 3f125426a25c717fbf8b84b3b75a8b60fa989bf7a039ed38926d455329f9dd0f +F src/sqlite.h.in 13d5458cd23e7b4759cff4d978ad09591a457b4a2821993579953a9a4257ce0f F src/sqlite3.rc 5121c9e10c3964d5755191c80dd1180c122fc3a8 F src/sqlite3ext.h da473ce2b3d0ae407a6300c4a164589b9a6bfdbec9462688a8593ff16f3bb6e4 F src/sqliteInt.h dcb1a885e8b6cb78df618944b89d44361a99d0fe33e1bba2c150a855f7dc5599 @@ -647,7 +647,7 @@ F src/sqliteLimit.h 33b1c9baba578d34efe7dfdb43193b366111cdf41476b1e82699e14c11ee F src/status.c 160c445d7d28c984a0eae38c144f6419311ed3eace59b44ac6dafc20db4af749 F src/table.c 0f141b58a16de7e2fbe81c308379e7279f4c6b50eb08efeec5892794a0ba30d1 F src/tclsqlite.c ecbc3c99c0d0c3ed122a913f143026c26d38d57f33e06bb71185dd5c1efe37cd -F src/test1.c 9111b12427b3b94429aff68bbcf5b125ae70438f6ce2f3f033e2a69626ec26c9 +F src/test1.c dfd07574689ee7c57af219c5f627ff32dae65dba478c124ba0359a1bde9983be F src/test2.c 827446e259a3b7ab949da1542953edda7b5117982576d3e6f1c24a0dd20a5cef F src/test3.c e5178558c41ff53236ae0271e9acb3d6885a94981d2eb939536ee6474598840e F src/test4.c 4533b76419e7feb41b40582554663ed3cd77aaa54e135cf76b3205098cd6e664 @@ -710,7 +710,7 @@ F src/vacuum.c 604fcdaebe76f3497c855afcbf91b8fa5046b32de3045bab89cc008d68e40104 F src/vdbe.c 74282a947234513872a83b0bab1b8c644ece64b3e27b053ef17677c8ff9c81e0 F src/vdbe.h 41485521f68e9437fdb7ec4a90f9d86ab294e9bb8281e33b235915e29122cfc0 F src/vdbeInt.h 7bd49eef8f89c1a271fbf12d80a206bf56c876814c5fc6bee340f4e1907095ae -F src/vdbeapi.c de9703f8705afc393cc2864669ce28cf9516983c8331d59aa2b978de01634365 +F src/vdbeapi.c c528ef4fafc8be172cbe4f48d8f15c9526bebde9f90185917fcc43fad264bcb6 F src/vdbeaux.c b5e3f7e158518b4eca6f166ac43900640a3fe9735c710e12bfa119af21059339 F src/vdbeblob.c 2516697b3ee8154eb8915f29466fb5d4f1ae39ee8b755ea909cefaf57ec5e2ce F src/vdbemem.c cf4a1556dd5b18c071cf7c243373c29ce752eb516022e3ad49ba72f08b785033 @@ -2043,8 +2043,11 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 -P 816b503f093c4e6d92d0eb2f9fbd841acd01cc9bc89ee58d961b56c64f71406a -R 6597235c929ad6ed0b90d6716177ff1b -U stephan -Z b7b5463364c5290e11704a96855173c7 +P 984d491eb3fe06f714bf07d6873321f3992a072812b46508e599bfefd39dff3e +R d7676f37f2e59b0f85cc021dadb3d7bc +T *branch * sqlite3_stmt_explain +T *sym-sqlite3_stmt_explain * +T -sym-trunk * +U drh +Z bc12630434faa3e2cbe8d73f4285c221 # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index d9fb0905d5..8363f6b593 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -984d491eb3fe06f714bf07d6873321f3992a072812b46508e599bfefd39dff3e \ No newline at end of file +5683743ddf0bb051f2fe5d137cc18407e000e77e9faa9010a22e3134b575638b \ No newline at end of file diff --git a/src/parse.y b/src/parse.y index 6085c4bbe2..867b62aa7a 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 39e8dcf655..9f843faa86 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 9165f21f71..ed1d8e6c4b 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 59d9986c52..9e165c1e55 100644 --- a/src/sqlite.h.in +++ b/src/sqlite.h.in @@ -4421,6 +4421,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 adc862156f..520508d1ca 100644 --- a/src/test1.c +++ b/src/test1.c @@ -2922,6 +2922,34 @@ static int SQLITE_TCLAPI test_stmt_isexplain( return TCL_OK; } +/* +** 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 ** @@ -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 920780a896..885b17f762 100644 --- a/src/vdbeapi.c +++ b/src/vdbeapi.c @@ -1814,6 +1814,22 @@ int sqlite3_stmt_isexplain(sqlite3_stmt *pStmt){ return pStmt ? ((Vdbe*)pStmt)->explain : 0; } +/* +** 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. */ From d1db37a2f32b06d0cd53e66b18ee47ffea91ca87 Mon Sep 17 00:00:00 2001 From: drh <> Date: Tue, 18 Jul 2023 17:29:05 +0000 Subject: [PATCH 2/2] Enhance the sqlite3_stmt_explain() interface so that avoids unnecessary reprepare operations. FossilOrigin-Name: 050f773addd605f6690348c98e9000a9a3663bbc26288a71973fd7b40468e8ab --- manifest | 23 +++++++---------- manifest.uuid | 2 +- src/select.c | 7 ----- src/sqlite.h.in | 30 +++++++++++---------- src/vdbeInt.h | 4 ++- src/vdbeapi.c | 69 ++++++++++++++++++++++++++++++++++++++++++++----- src/vdbeaux.c | 37 +++++++------------------- 7 files changed, 103 insertions(+), 69 deletions(-) diff --git a/manifest b/manifest index 560436430f..0926a795e7 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Add\sthe\sexperimental\ssqlite3_stmt_explain(S,E)\sinterface. -D 2023-07-15T16:48:14.281 +C Enhance\sthe\ssqlite3_stmt_explain()\sinterface\sso\sthat\savoids\sunnecessary\nreprepare\soperations. +D 2023-07-18T17:29:05.187 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -637,9 +637,9 @@ F src/printf.c 84b7b4b647f336934a5ab2e7f0c52555833cc0778d2d60e016cca52ee8c6cd8f F src/random.c 606b00941a1d7dd09c381d3279a058d771f406c5213c9932bbd93d5587be4b9c F src/resolve.c 37953a5f36c60bea413c3c04efcd433b6177009f508ef2ace0494728912fe2e9 F src/rowset.c 8432130e6c344b3401a8874c3cb49fefe6873fec593294de077afea2dce5ec97 -F src/select.c 3ab1186290a311a8ceed1286c0e286209f7fe97b2d02c7593258004ce295dd88 +F src/select.c 3328b8c758016b400a4fea053ab1f5927929083a49d0c3921802ce99eea73cde F src/shell.c.in 3f125426a25c717fbf8b84b3b75a8b60fa989bf7a039ed38926d455329f9dd0f -F src/sqlite.h.in 13d5458cd23e7b4759cff4d978ad09591a457b4a2821993579953a9a4257ce0f +F src/sqlite.h.in 7b95e5b11914726ebcf95c633be3341ad2cb3c8aac62ecf188a14f74827aa68a F src/sqlite3.rc 5121c9e10c3964d5755191c80dd1180c122fc3a8 F src/sqlite3ext.h da473ce2b3d0ae407a6300c4a164589b9a6bfdbec9462688a8593ff16f3bb6e4 F src/sqliteInt.h dcb1a885e8b6cb78df618944b89d44361a99d0fe33e1bba2c150a855f7dc5599 @@ -709,9 +709,9 @@ F src/util.c fea6fffdee3cdae917a66b70deec59d4f238057cfd6de623d15cf990c196940d F src/vacuum.c 604fcdaebe76f3497c855afcbf91b8fa5046b32de3045bab89cc008d68e40104 F src/vdbe.c 74282a947234513872a83b0bab1b8c644ece64b3e27b053ef17677c8ff9c81e0 F src/vdbe.h 41485521f68e9437fdb7ec4a90f9d86ab294e9bb8281e33b235915e29122cfc0 -F src/vdbeInt.h 7bd49eef8f89c1a271fbf12d80a206bf56c876814c5fc6bee340f4e1907095ae -F src/vdbeapi.c c528ef4fafc8be172cbe4f48d8f15c9526bebde9f90185917fcc43fad264bcb6 -F src/vdbeaux.c b5e3f7e158518b4eca6f166ac43900640a3fe9735c710e12bfa119af21059339 +F src/vdbeInt.h 7a2329efe451ef0d1280736419f321cbc261146e453cdd22b27a0785b8742b15 +F src/vdbeapi.c 97a9dfdff49183ad7f7911f9d919eb8eb72677c39cdee6249ee5a060d3ce515a +F src/vdbeaux.c 499b68a5585663ef36c05b421d8c9c60448aa3b9f01bba5ae7accfc89ee6ac50 F src/vdbeblob.c 2516697b3ee8154eb8915f29466fb5d4f1ae39ee8b755ea909cefaf57ec5e2ce F src/vdbemem.c cf4a1556dd5b18c071cf7c243373c29ce752eb516022e3ad49ba72f08b785033 F src/vdbesort.c 0d40dca073c94e158ead752ef4225f4fee22dee84145e8c00ca2309afb489015 @@ -2043,11 +2043,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 -P 984d491eb3fe06f714bf07d6873321f3992a072812b46508e599bfefd39dff3e -R d7676f37f2e59b0f85cc021dadb3d7bc -T *branch * sqlite3_stmt_explain -T *sym-sqlite3_stmt_explain * -T -sym-trunk * +P 5683743ddf0bb051f2fe5d137cc18407e000e77e9faa9010a22e3134b575638b +R a56fcd1282b166331b87ac37d0175ebe U drh -Z bc12630434faa3e2cbe8d73f4285c221 +Z 96b7009f78463b1b25a8594262247cfd # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index 8363f6b593..ab125fc443 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -5683743ddf0bb051f2fe5d137cc18407e000e77e9faa9010a22e3134b575638b \ No newline at end of file +050f773addd605f6690348c98e9000a9a3663bbc26288a71973fd7b40468e8ab \ No newline at end of file diff --git a/src/select.c b/src/select.c index 4af812fa06..098b745849 100644 --- a/src/select.c +++ b/src/select.c @@ -2101,13 +2101,6 @@ void sqlite3GenerateColumnNames( int fullName; /* TABLE.COLUMN if no AS clause and is a direct table ref */ int srcName; /* COLUMN or TABLE.COLUMN if no AS clause and is direct */ -#ifndef SQLITE_OMIT_EXPLAIN - /* If this is an EXPLAIN, skip this step */ - if( pParse->explain ){ - return; - } -#endif - if( pParse->colNamesSet ) return; /* Column names are determined by the left-most term of a compound select */ while( pSelect->pPrior ) pSelect = pSelect->pPrior; diff --git a/src/sqlite.h.in b/src/sqlite.h.in index 9e165c1e55..668cfa54ba 100644 --- a/src/sqlite.h.in +++ b/src/sqlite.h.in @@ -4425,24 +4425,28 @@ 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 +** 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. +** Calling sqlite3_stmt_explain(S,E) might cause S to be reprepared. +** SQLite tries to avoid a reprepare, but a reprepare might be necessary +** on the first transition into EXPLAIN or EXPLAIN QUERY PLAN mode. +** +** Because of the potential need to reprepare, a call to +** sqlite3_stmt_explain(S,E) will fail with SQLITE_ERROR if S cannot be +** reprepared 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 will 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. diff --git a/src/vdbeInt.h b/src/vdbeInt.h index 4c3394716b..baed0527d6 100644 --- a/src/vdbeInt.h +++ b/src/vdbeInt.h @@ -465,16 +465,18 @@ struct Vdbe { u32 nWrite; /* Number of write operations that have occurred */ #endif u16 nResColumn; /* Number of columns in one row of the result set */ + u16 nResAlloc; /* Column slots allocated to aColName[] */ u8 errorAction; /* Recovery action to do in case of an error */ u8 minWriteFileFormat; /* Minimum file format for writable database files */ u8 prepFlags; /* SQLITE_PREPARE_* flags */ u8 eVdbeState; /* On of the VDBE_*_STATE values */ bft expired:2; /* 1: recompile VM immediately 2: when convenient */ - bft explain:2; /* True if EXPLAIN present on SQL command */ + bft explain:2; /* 0: normal, 1: EXPLAIN, 2: EXPLAIN QUERY PLAN */ bft changeCntOn:1; /* True to update the change-counter */ 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 haveEqpOps:1; /* Bytecode supports EXPLAIN QUERY PLAN */ yDbMask btreeMask; /* Bitmask of db->aDb[] entries referenced */ yDbMask lockMask; /* Subset of btreeMask that requires a lock */ u32 aCounter[9]; /* Counters used by sqlite3_stmt_status() */ diff --git a/src/vdbeapi.c b/src/vdbeapi.c index 885b17f762..3185191ed9 100644 --- a/src/vdbeapi.c +++ b/src/vdbeapi.c @@ -1126,7 +1126,8 @@ int sqlite3_aggregate_count(sqlite3_context *p){ */ int sqlite3_column_count(sqlite3_stmt *pStmt){ Vdbe *pVm = (Vdbe *)pStmt; - return pVm ? pVm->nResColumn : 0; + if( pVm==0 ) return 0; + return pVm->nResColumn; } /* @@ -1299,6 +1300,32 @@ int sqlite3_column_type(sqlite3_stmt *pStmt, int i){ return iType; } +/* +** Column names appropriate for EXPLAIN or EXPLAIN QUERY PLAN. +*/ +static const char * const azExplainColNames8[] = { + "addr", "opcode", "p1", "p2", "p3", "p4", "p5", "comment", /* EXPLAIN */ + "id", "parent", "notused", "detail" /* EQP */ +}; +static const u16 azExplainColNames16data[] = { + /* 0 */ 'a', 'd', 'd', 'r', 0, + /* 5 */ 'o', 'p', 'c', 'o', 'd', 'e', 0, + /* 12 */ 'p', '1', 0, + /* 15 */ 'p', '2', 0, + /* 18 */ 'p', '3', 0, + /* 21 */ 'p', '4', 0, + /* 24 */ 'p', '5', 0, + /* 27 */ 'c', 'o', 'm', 'm', 'e', 'n', 't', 0, + /* 35 */ 'i', 'd', 0, + /* 38 */ 'p', 'a', 'r', 'e', 'n', 't', 0, + /* 45 */ 'n', 'o', 't', 'u', 's', 'e', 'd', 0, + /* 53 */ 'd', 'e', 't', 'a', 'i', 'l', 0 +}; +static const u8 iExplainColNames16[] = { + 0, 5, 12, 15, 18, 21, 24, 27, + 35, 38, 45, 53 +}; + /* ** Convert the N-th element of pStmt->pColName[] into a string using ** xFunc() then return that string. If N is out of range, return 0. @@ -1331,15 +1358,29 @@ static const void *columnName( return 0; } #endif + if( N<0 ) return 0; ret = 0; p = (Vdbe *)pStmt; db = p->db; assert( db!=0 ); - n = sqlite3_column_count(pStmt); - if( N=0 ){ + sqlite3_mutex_enter(db->mutex); + + if( p->explain ){ + if( useType>0 ) goto columnName_end; + n = p->explain==1 ? 8 : 4; + if( N>=n ) goto columnName_end; + if( useUtf16 ){ + int i = iExplainColNames16[N + 8*p->explain - 8]; + ret = (void*)&azExplainColNames16data[i]; + }else{ + ret = (void*)azExplainColNames8[N + 8*p->explain - 8]; + } + goto columnName_end; + } + n = p->nResColumn; + if( NmallocFailed; N += useType*n; - sqlite3_mutex_enter(db->mutex); #ifndef SQLITE_OMIT_UTF16 if( useUtf16 ){ ret = sqlite3_value_text16((sqlite3_value*)&p->aColName[N]); @@ -1356,8 +1397,9 @@ static const void *columnName( sqlite3OomClear(db); ret = 0; } - sqlite3_mutex_leave(db->mutex); } +columnName_end: + sqlite3_mutex_leave(db->mutex); return ret; } @@ -1824,8 +1866,21 @@ int sqlite3_stmt_explain(sqlite3_stmt *pStmt, int eMode){ 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); + if( v->nMem>=10 && (eMode!=2 || v->haveEqpOps) ){ + /* No reprepare necessary */ + v->explain = eMode; + rc = SQLITE_OK; + }else{ + int haveEqpOps = v->explain==2 || v->haveEqpOps; + v->explain = eMode; + rc = sqlite3Reprepare(v); + v->haveEqpOps = haveEqpOps!=0; + } + if( v->explain ){ + v->nResColumn = 12 - 4*v->explain; + }else{ + v->nResColumn = v->nResAlloc; + } sqlite3_mutex_leave(v->db->mutex); return rc; } diff --git a/src/vdbeaux.c b/src/vdbeaux.c index a0eff155dd..eec93a6b1a 100644 --- a/src/vdbeaux.c +++ b/src/vdbeaux.c @@ -2418,8 +2418,8 @@ int sqlite3VdbeList( sqlite3VdbeMemSetInt64(pMem, pOp->p1); sqlite3VdbeMemSetInt64(pMem+1, pOp->p2); sqlite3VdbeMemSetInt64(pMem+2, pOp->p3); - sqlite3VdbeMemSetStr(pMem+3, zP4, -1, SQLITE_UTF8, sqlite3_free); - p->nResColumn = 4; + sqlite3VdbeMemSetStr(pMem+3, zP4, -1, SQLITE_UTF8, sqlite3_free); + assert( p->nResColumn==4 ); }else{ sqlite3VdbeMemSetInt64(pMem+0, i); sqlite3VdbeMemSetStr(pMem+1, (char*)sqlite3OpcodeName(pOp->opcode), @@ -2438,7 +2438,7 @@ int sqlite3VdbeList( sqlite3VdbeMemSetNull(pMem+7); #endif sqlite3VdbeMemSetStr(pMem+5, zP4, -1, SQLITE_UTF8, sqlite3_free); - p->nResColumn = 8; + assert( p->nResColumn==8 ); } p->pResultRow = pMem; if( db->mallocFailed ){ @@ -2652,26 +2652,9 @@ void sqlite3VdbeMakeReady( resolveP2Values(p, &nArg); p->usesStmtJournal = (u8)(pParse->isMultiWrite && pParse->mayAbort); if( pParse->explain ){ - static const char * const azColName[] = { - "addr", "opcode", "p1", "p2", "p3", "p4", "p5", "comment", - "id", "parent", "notused", "detail" - }; - int iFirst, mx, i; if( nMem<10 ) nMem = 10; p->explain = pParse->explain; - if( pParse->explain==2 ){ - sqlite3VdbeSetNumCols(p, 4); - iFirst = 8; - mx = 12; - }else{ - sqlite3VdbeSetNumCols(p, 8); - iFirst = 0; - mx = 8; - } - for(i=iFirst; inResColumn = 12 - 4*p->explain; } p->expired = 0; @@ -2824,12 +2807,12 @@ void sqlite3VdbeSetNumCols(Vdbe *p, int nResColumn){ int n; sqlite3 *db = p->db; - if( p->nResColumn ){ - releaseMemArray(p->aColName, p->nResColumn*COLNAME_N); + if( p->nResAlloc ){ + releaseMemArray(p->aColName, p->nResAlloc*COLNAME_N); sqlite3DbFree(db, p->aColName); } n = nResColumn*COLNAME_N; - p->nResColumn = (u16)nResColumn; + p->nResColumn = p->nResAlloc = (u16)nResColumn; p->aColName = (Mem*)sqlite3DbMallocRawNN(db, sizeof(Mem)*n ); if( p->aColName==0 ) return; initMemArray(p->aColName, n, db, MEM_Null); @@ -2854,14 +2837,14 @@ int sqlite3VdbeSetColName( ){ int rc; Mem *pColName; - assert( idxnResColumn ); + assert( idxnResAlloc ); assert( vardb->mallocFailed ){ assert( !zName || xDel!=SQLITE_DYNAMIC ); return SQLITE_NOMEM_BKPT; } assert( p->aColName!=0 ); - pColName = &(p->aColName[idx+var*p->nResColumn]); + pColName = &(p->aColName[idx+var*p->nResAlloc]); rc = sqlite3VdbeMemSetStr(pColName, zName, -1, SQLITE_UTF8, xDel); assert( rc!=0 || !zName || (pColName->flags&MEM_Term)!=0 ); return rc; @@ -3685,7 +3668,7 @@ static void sqlite3VdbeClearObject(sqlite3 *db, Vdbe *p){ assert( db!=0 ); assert( p->db==0 || p->db==db ); if( p->aColName ){ - releaseMemArray(p->aColName, p->nResColumn*COLNAME_N); + releaseMemArray(p->aColName, p->nResAlloc*COLNAME_N); sqlite3DbNNFreeNN(db, p->aColName); } for(pSub=p->pProgram; pSub; pSub=pNext){