1
0
mirror of https://github.com/sqlite/sqlite.git synced 2025-11-11 01:42:22 +03:00

Enhance the sqlite3_stmt_explain() interface so that avoids unnecessary

reprepare operations.

FossilOrigin-Name: 050f773addd605f6690348c98e9000a9a3663bbc26288a71973fd7b40468e8ab
This commit is contained in:
drh
2023-07-18 17:29:05 +00:00
parent e393f6eff0
commit d1db37a2f3
7 changed files with 103 additions and 69 deletions

View File

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

View File

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

View File

@@ -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() */

View File

@@ -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<n && 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( N<n ){
u8 prior_mallocFailed = db->mallocFailed;
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;
}

View File

@@ -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; i<mx; i++){
sqlite3VdbeSetColName(p, i-iFirst, COLNAME_NAME,
azColName[i], SQLITE_STATIC);
}
p->nResColumn = 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( idx<p->nResColumn );
assert( idx<p->nResAlloc );
assert( var<COLNAME_N );
if( p->db->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){