aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/sqlite.h.in36
-rw-r--r--src/tclsqlite.c42
-rw-r--r--src/test1.c73
-rw-r--r--src/vdbe.c44
-rw-r--r--src/vdbe.h16
-rw-r--r--src/vdbeInt.h4
-rw-r--r--src/vdbeapi.c37
-rw-r--r--src/vdbeaux.c13
-rw-r--r--src/where.c55
9 files changed, 305 insertions, 15 deletions
diff --git a/src/sqlite.h.in b/src/sqlite.h.in
index b4081f2a0..ba20cb989 100644
--- a/src/sqlite.h.in
+++ b/src/sqlite.h.in
@@ -7407,6 +7407,42 @@ int sqlite3_vtab_on_conflict(sqlite3 *);
#define SQLITE_REPLACE 5
+/*
+** Return status data for a single loop within query pStmt.
+**
+** Parameter "idx" identifies the specific loop to retrieve statistics for.
+** Loops are numbered starting from zero. If idx is out of range - less than
+** zero or greater than or equal to the total number of loops used to implement
+** the statement - a non-zero value is returned. In this case the final value
+** of all five output parameters is undefined. Otherwise, if idx is in range,
+** the output parameters are populated and zero returned.
+**
+** Statistics may not be available for all loops in all statements. In cases
+** where there exist loops with no available statistics, this function ignores
+** them completely.
+**
+** This API is only available if the library is built with pre-processor
+** symbol SQLITE_ENABLE_STMT_SCANSTATUS defined.
+*/
+SQLITE_EXPERIMENTAL int sqlite3_stmt_scanstatus(
+ sqlite3_stmt *pStmt,
+ int idx, /* Index of loop to report on */
+ sqlite3_int64 *pnLoop, /* OUT: Number of times loop was run */
+ sqlite3_int64 *pnVisit, /* OUT: Number of rows visited (all loops) */
+ sqlite3_int64 *pnEst, /* OUT: Number of rows estimated (per loop) */
+ const char **pzName, /* OUT: Object name (table or index) */
+ const char **pzExplain /* OUT: EQP string */
+);
+
+
+/*
+** Zero all sqlite3_stmt_scanstatus() related event counters.
+**
+** This API is only available if the library is built with pre-processor
+** symbol SQLITE_ENABLE_STMT_SCANSTATUS defined.
+*/
+SQLITE_EXPERIMENTAL void sqlite3_stmt_scanstatus_reset(sqlite3_stmt*);
+
/*
** Undo the hack that converts floating point types to integer for
diff --git a/src/tclsqlite.c b/src/tclsqlite.c
index 756d0daa5..bff4a9242 100644
--- a/src/tclsqlite.c
+++ b/src/tclsqlite.c
@@ -3641,6 +3641,45 @@ static int db_use_legacy_prepare_cmd(
Tcl_ResetResult(interp);
return TCL_OK;
}
+
+/*
+** Tclcmd: db_last_stmt_ptr DB
+**
+** If the statement cache associated with database DB is not empty,
+** return the text representation of the most recently used statement
+** handle.
+*/
+static int db_last_stmt_ptr(
+ ClientData cd,
+ Tcl_Interp *interp,
+ int objc,
+ Tcl_Obj *CONST objv[]
+){
+ extern int sqlite3TestMakePointerStr(Tcl_Interp*, char*, void*);
+ Tcl_CmdInfo cmdInfo;
+ SqliteDb *pDb;
+ sqlite3_stmt *pStmt = 0;
+ char zBuf[100];
+
+ if( objc!=2 ){
+ Tcl_WrongNumArgs(interp, 1, objv, "DB");
+ return TCL_ERROR;
+ }
+
+ if( !Tcl_GetCommandInfo(interp, Tcl_GetString(objv[1]), &cmdInfo) ){
+ Tcl_AppendResult(interp, "no such db: ", Tcl_GetString(objv[1]), (char*)0);
+ return TCL_ERROR;
+ }
+ pDb = (SqliteDb*)cmdInfo.objClientData;
+
+ if( pDb->stmtList ) pStmt = pDb->stmtList->pStmt;
+ if( sqlite3TestMakePointerStr(interp, zBuf, pStmt) ){
+ return TCL_ERROR;
+ }
+ Tcl_SetResult(interp, zBuf, TCL_VOLATILE);
+
+ return TCL_OK;
+}
#endif
/*
@@ -3760,6 +3799,9 @@ static void init_all(Tcl_Interp *interp){
Tcl_CreateObjCommand(
interp, "db_use_legacy_prepare", db_use_legacy_prepare_cmd, 0, 0
);
+ Tcl_CreateObjCommand(
+ interp, "db_last_stmt_ptr", db_last_stmt_ptr, 0, 0
+ );
#ifdef SQLITE_SSE
Sqlitetestsse_Init(interp);
diff --git a/src/test1.c b/src/test1.c
index 802aa99b5..72e70513b 100644
--- a/src/test1.c
+++ b/src/test1.c
@@ -2301,6 +2301,75 @@ static int test_stmt_status(
return TCL_OK;
}
+#ifdef SQLITE_ENABLE_STMT_SCANSTATUS
+/*
+** Usage: sqlite3_stmt_scanstatus STMT IDX
+*/
+static int test_stmt_scanstatus(
+ void * clientData,
+ Tcl_Interp *interp,
+ int objc,
+ Tcl_Obj *CONST objv[]
+){
+ sqlite3_stmt *pStmt; /* First argument */
+ int idx; /* Second argument */
+
+ const char *zName;
+ const char *zExplain;
+ sqlite3_int64 nLoop;
+ sqlite3_int64 nVisit;
+ sqlite3_int64 nEst;
+ int res;
+
+ if( objc!=3 ){
+ Tcl_WrongNumArgs(interp, 1, objv, "STMT IDX");
+ return TCL_ERROR;
+ }
+ if( getStmtPointer(interp, Tcl_GetString(objv[1]), &pStmt) ) return TCL_ERROR;
+ if( Tcl_GetIntFromObj(interp, objv[2], &idx) ) return TCL_ERROR;
+
+ res = sqlite3_stmt_scanstatus(
+ pStmt, idx, &nLoop, &nVisit, &nEst, &zName, &zExplain
+ );
+ if( res==0 ){
+ Tcl_Obj *pRet = Tcl_NewObj();
+ Tcl_ListObjAppendElement(0, pRet, Tcl_NewStringObj("nLoop", -1));
+ Tcl_ListObjAppendElement(0, pRet, Tcl_NewWideIntObj(nLoop));
+ Tcl_ListObjAppendElement(0, pRet, Tcl_NewStringObj("nVisit", -1));
+ Tcl_ListObjAppendElement(0, pRet, Tcl_NewWideIntObj(nVisit));
+ Tcl_ListObjAppendElement(0, pRet, Tcl_NewStringObj("nEst", -1));
+ Tcl_ListObjAppendElement(0, pRet, Tcl_NewWideIntObj(nEst));
+ Tcl_ListObjAppendElement(0, pRet, Tcl_NewStringObj("zName", -1));
+ Tcl_ListObjAppendElement(0, pRet, Tcl_NewStringObj(zName, -1));
+ Tcl_ListObjAppendElement(0, pRet, Tcl_NewStringObj("zExplain", -1));
+ Tcl_ListObjAppendElement(0, pRet, Tcl_NewStringObj(zExplain, -1));
+ Tcl_SetObjResult(interp, pRet);
+ }else{
+ Tcl_ResetResult(interp);
+ }
+ return TCL_OK;
+}
+
+/*
+** Usage: sqlite3_stmt_scanstatus_reset STMT
+*/
+static int test_stmt_scanstatus_reset(
+ void * clientData,
+ Tcl_Interp *interp,
+ int objc,
+ Tcl_Obj *CONST objv[]
+){
+ sqlite3_stmt *pStmt; /* First argument */
+ if( objc!=2 ){
+ Tcl_WrongNumArgs(interp, 1, objv, "STMT");
+ return TCL_ERROR;
+ }
+ if( getStmtPointer(interp, Tcl_GetString(objv[1]), &pStmt) ) return TCL_ERROR;
+ sqlite3_stmt_scanstatus_reset(pStmt);
+ return TCL_OK;
+}
+#endif
+
/*
** Usage: sqlite3_next_stmt DB STMT
**
@@ -6868,6 +6937,10 @@ int Sqlitetest1_Init(Tcl_Interp *interp){
{ "sqlite3_user_change", test_user_change, 0 },
{ "sqlite3_user_delete", test_user_delete, 0 },
#endif
+#ifdef SQLITE_ENABLE_STMT_SCANSTATUS
+ { "sqlite3_stmt_scanstatus", test_stmt_scanstatus, 0 },
+ { "sqlite3_stmt_scanstatus_reset", test_stmt_scanstatus_reset, 0 },
+#endif
};
static int bitmask_size = sizeof(Bitmask)*8;
diff --git a/src/vdbe.c b/src/vdbe.c
index c17bfdfe1..a92c0509b 100644
--- a/src/vdbe.c
+++ b/src/vdbe.c
@@ -168,6 +168,18 @@ int sqlite3_found_count = 0;
#define isSorter(x) ((x)->pSorter!=0)
/*
+** The first argument passed to the IncrementExplainCounter() macro must
+** be a non-NULL pointer to an object of type VdbeCursor. The second
+** argument must be either "nLoop" or "nVisit" (without the double-quotes).
+*/
+#ifdef SQLITE_ENABLE_STMT_SCANSTATUS
+# define IncrementExplainCounter(pCsr, counter) \
+ if( (pCsr)->pExplain ) (pCsr)->pExplain->counter++
+#else
+# define IncrementExplainCounter(pCsr, counter)
+#endif
+
+/*
** Allocate VdbeCursor number iCur. Return a pointer to it. Return NULL
** if we run out of memory.
*/
@@ -3556,6 +3568,7 @@ case OP_SeekGT: { /* jump, in3 */
#ifdef SQLITE_DEBUG
pC->seekOp = pOp->opcode;
#endif
+ IncrementExplainCounter(pC, nLoop);
if( pC->isTable ){
/* The input value in P3 might be of any type: integer, real, string,
** blob, or NULL. But it needs to be an integer before we can do
@@ -3664,6 +3677,8 @@ case OP_SeekGT: { /* jump, in3 */
VdbeBranchTaken(res!=0,2);
if( res ){
pc = pOp->p2 - 1;
+ }else{
+ IncrementExplainCounter(pC, nVisit);
}
break;
}
@@ -4449,6 +4464,8 @@ case OP_Last: { /* jump */
res = 0;
assert( pCrsr!=0 );
rc = sqlite3BtreeLast(pCrsr, &res);
+ IncrementExplainCounter(pC, nLoop);
+ if( res==0 ) IncrementExplainCounter(pC, nVisit);
pC->nullRow = (u8)res;
pC->deferredMoveto = 0;
pC->cacheStatus = CACHE_STALE;
@@ -4488,9 +4505,9 @@ case OP_Sort: { /* jump */
**
** The next use of the Rowid or Column or Next instruction for P1
** will refer to the first entry in the database table or index.
-** If the table or index is empty and P2>0, then jump immediately to P2.
-** If P2 is 0 or if the table or index is not empty, fall through
-** to the following instruction.
+** If the table or index is empty, jump immediately to P2.
+** If the table or index is not empty, fall through to the following
+** instruction.
**
** This opcode leaves the cursor configured to move in forward order,
** from the beginning toward the end. In other words, the cursor is
@@ -4518,11 +4535,14 @@ case OP_Rewind: { /* jump */
pC->deferredMoveto = 0;
pC->cacheStatus = CACHE_STALE;
}
+ IncrementExplainCounter(pC, nLoop);
pC->nullRow = (u8)res;
assert( pOp->p2>0 && pOp->p2<p->nOp );
VdbeBranchTaken(res!=0,2);
if( res ){
pc = pOp->p2 - 1;
+ }else{
+ IncrementExplainCounter(pC, nVisit);
}
break;
}
@@ -4633,6 +4653,7 @@ next_tail:
pC->cacheStatus = CACHE_STALE;
VdbeBranchTaken(res==0,2);
if( res==0 ){
+ IncrementExplainCounter(pC, nVisit);
pC->nullRow = 0;
pc = pOp->p2 - 1;
p->aCounter[pOp->p5]++;
@@ -6055,7 +6076,10 @@ case OP_VFilter: { /* jump */
VdbeBranchTaken(res!=0,2);
if( res ){
pc = pOp->p2 - 1;
+ }else{
+ IncrementExplainCounter(pCur, nVisit);
}
+ IncrementExplainCounter(pCur, nLoop);
}
pCur->nullRow = 0;
@@ -6148,6 +6172,7 @@ case OP_VNext: { /* jump */
if( !res ){
/* If there is data, jump to P2 */
pc = pOp->p2 - 1;
+ IncrementExplainCounter(pCur, nVisit);
}
goto check_for_interrupt;
}
@@ -6347,6 +6372,19 @@ case OP_Init: { /* jump */
break;
}
+#ifdef SQLITE_ENABLE_STMT_SCANSTATUS
+case OP_Explain: {
+ ExplainArg *pArg;
+ VdbeCursor *pCur;
+ if( pOp->p4type==P4_EXPLAIN && (pArg = pOp->p4.pExplain)->iCsr>=0 ){
+ pArg = pOp->p4.pExplain;
+ pCur = p->apCsr[pArg->iCsr];
+ pCur->pExplain = pArg;
+ }
+ break;
+}
+#endif
+
/* Opcode: Noop * * * * *
**
diff --git a/src/vdbe.h b/src/vdbe.h
index f975f9554..1b470441b 100644
--- a/src/vdbe.h
+++ b/src/vdbe.h
@@ -25,6 +25,7 @@
** of this structure.
*/
typedef struct Vdbe Vdbe;
+typedef struct ExplainArg ExplainArg;
/*
** The names of the following types declared in vdbeInt.h are required
@@ -59,6 +60,7 @@ struct VdbeOp {
KeyInfo *pKeyInfo; /* Used when p4type is P4_KEYINFO */
int *ai; /* Used when p4type is P4_INTARRAY */
SubProgram *pProgram; /* Used when p4type is P4_SUBPROGRAM */
+ ExplainArg *pExplain; /* Used when p4type is P4_EXPLAIN */
int (*xAdvance)(BtCursor *, int *);
} p4;
#ifdef SQLITE_ENABLE_EXPLAIN_COMMENTS
@@ -101,6 +103,19 @@ struct VdbeOpList {
typedef struct VdbeOpList VdbeOpList;
/*
+** Structure used as the P4 parameter for OP_Explain opcodes.
+*/
+struct ExplainArg {
+ int iCsr; /* Cursor number this applies to */
+ i64 nLoop; /* Number of times loop has run */
+ i64 nVisit; /* Total number of rows visited */
+ i64 nEst; /* Estimated number of rows per scan */
+ const char *zName; /* Name of table/index being scanned */
+ const char *zExplain; /* EQP text for this loop */
+};
+
+
+/*
** Allowed values of VdbeOp.p4type
*/
#define P4_NOTUSED 0 /* The P4 parameter is not used */
@@ -119,6 +134,7 @@ typedef struct VdbeOpList VdbeOpList;
#define P4_INTARRAY (-15) /* P4 is a vector of 32-bit integers */
#define P4_SUBPROGRAM (-18) /* P4 is a pointer to a SubProgram structure */
#define P4_ADVANCE (-19) /* P4 is a pointer to BtreeNext() or BtreePrev() */
+#define P4_EXPLAIN (-20) /* P4 is a pointer to an instance of ExplainArg */
/* Error message codes for OP_Halt */
#define P5_ConstraintNotNull 1
diff --git a/src/vdbeInt.h b/src/vdbeInt.h
index 623c5fdde..839108f6b 100644
--- a/src/vdbeInt.h
+++ b/src/vdbeInt.h
@@ -83,6 +83,7 @@ struct VdbeCursor {
i64 seqCount; /* Sequence counter */
i64 movetoTarget; /* Argument to the deferred sqlite3BtreeMoveto() */
VdbeSorter *pSorter; /* Sorter object for OP_SorterOpen cursors */
+ ExplainArg *pExplain; /* Object to store seek/visit counts (may be NULL) */
/* Cached information about the header for the data record that the
** cursor is currently pointing to. Only valid if cacheStatus matches
@@ -291,6 +292,7 @@ struct Explain {
char zBase[100]; /* Initial space */
};
+
/* A bitfield type for use inside of structures. Always follow with :N where
** N is the number of bits.
*/
@@ -368,6 +370,8 @@ struct Vdbe {
int nOnceFlag; /* Size of array aOnceFlag[] */
u8 *aOnceFlag; /* Flags for OP_Once */
AuxData *pAuxData; /* Linked list of auxdata allocations */
+ ExplainArg **apExplain; /* Array of pointers to P4_EXPLAIN p4 values */
+ int nExplain; /* Number of entries in array apExplain */
};
/*
diff --git a/src/vdbeapi.c b/src/vdbeapi.c
index daf7b101d..b09faa1fa 100644
--- a/src/vdbeapi.c
+++ b/src/vdbeapi.c
@@ -1475,3 +1475,40 @@ int sqlite3_stmt_status(sqlite3_stmt *pStmt, int op, int resetFlag){
if( resetFlag ) pVdbe->aCounter[op] = 0;
return (int)v;
}
+
+/*
+** Return status data for a single loop within query pStmt.
+*/
+int sqlite3_stmt_scanstatus(
+ sqlite3_stmt *pStmt,
+ int idx, /* Index of loop to report on */
+ sqlite3_int64 *pnLoop, /* OUT: Number of times loop was run */
+ sqlite3_int64 *pnVisit, /* OUT: Number of rows visited (all loops) */
+ sqlite3_int64 *pnEst, /* OUT: Number of rows estimated (per loop) */
+ const char **pzName, /* OUT: Object name (table or index) */
+ const char **pzExplain /* OUT: EQP string */
+){
+ Vdbe *p = (Vdbe*)pStmt;
+ ExplainArg *pExplain;
+ if( idx<0 || idx>=p->nExplain ) return 1;
+ pExplain = p->apExplain[idx];
+ if( pnLoop ) *pnLoop = pExplain->nLoop;
+ if( pnVisit ) *pnVisit = pExplain->nVisit;
+ if( pnEst ) *pnEst = pExplain->nEst;
+ if( *pzName ) *pzName = pExplain->zName;
+ if( *pzExplain ) *pzExplain = pExplain->zExplain;
+ return 0;
+}
+
+/*
+** Zero all counters associated with the sqlite3_stmt_scanstatus() data.
+*/
+void sqlite3_stmt_scanstatus_reset(sqlite3_stmt *pStmt){
+ Vdbe *p = (Vdbe*)pStmt;
+ int i;
+ for(i=0; i<p->nExplain; i++){
+ p->apExplain[i]->nLoop = 0;
+ p->apExplain[i]->nVisit = 0;
+ }
+}
+
diff --git a/src/vdbeaux.c b/src/vdbeaux.c
index 7dfb64130..a8223890b 100644
--- a/src/vdbeaux.c
+++ b/src/vdbeaux.c
@@ -672,6 +672,7 @@ static void freeP4(sqlite3 *db, int p4type, void *p4){
if( p4 ){
assert( db );
switch( p4type ){
+ case P4_EXPLAIN:
case P4_REAL:
case P4_INT64:
case P4_DYNAMIC:
@@ -820,6 +821,13 @@ void sqlite3VdbeChangeP4(Vdbe *p, int addr, const char *zP4, int n){
pOp->p4type = P4_VTAB;
sqlite3VtabLock((VTable *)zP4);
assert( ((VTable *)zP4)->db==p->db );
+ }else if( n==P4_EXPLAIN ){
+ pOp->p4.p = (void*)zP4;
+ pOp->p4type = P4_EXPLAIN;
+ p->apExplain = (ExplainArg**)sqlite3DbReallocOrFree(
+ p->db, p->apExplain, sizeof(ExplainArg*) * p->nExplain + 1
+ );
+ if( p->apExplain ) p->apExplain[p->nExplain++] = (ExplainArg*)zP4;
}else if( n<0 ){
pOp->p4.p = (void*)zP4;
pOp->p4type = (signed char)n;
@@ -1103,6 +1111,10 @@ static char *displayP4(Op *pOp, char *zTemp, int nTemp){
zTemp[0] = 0;
break;
}
+ case P4_EXPLAIN: {
+ zP4 = (char*)pOp->p4.pExplain->zExplain;
+ break;
+ }
default: {
zP4 = pOp->p4.z;
if( zP4==0 ){
@@ -2685,6 +2697,7 @@ void sqlite3VdbeClearObject(sqlite3 *db, Vdbe *p){
sqlite3DbFree(db, p->aColName);
sqlite3DbFree(db, p->zSql);
sqlite3DbFree(db, p->pFree);
+ sqlite3DbFree(db, p->apExplain);
}
/*
diff --git a/src/where.c b/src/where.c
index feccf2d11..3565327b5 100644
--- a/src/where.c
+++ b/src/where.c
@@ -2820,7 +2820,7 @@ static void explainOneScan(
int iFrom, /* Value for "from" column of output */
u16 wctrlFlags /* Flags passed to sqlite3WhereBegin() */
){
-#ifndef SQLITE_DEBUG
+#if !defined(SQLITE_DEBUG) && !defined(SQLITE_ENABLE_STMT_SCANSTATUS)
if( pParse->explain==2 )
#endif
{
@@ -2831,20 +2831,38 @@ static void explainOneScan(
int isSearch; /* True for a SEARCH. False for SCAN. */
WhereLoop *pLoop; /* The controlling WhereLoop object */
u32 flags; /* Flags that describe this loop */
- char *zMsg; /* Text to add to EQP output */
StrAccum str; /* EQP output string */
char zBuf[100]; /* Initial space for EQP output string */
+ const char *zObj;
+ ExplainArg *pExplain;
+ i64 nEstRow; /* Estimated rows per scan of pLevel */
pLoop = pLevel->pWLoop;
flags = pLoop->wsFlags;
if( (flags&WHERE_MULTI_OR) || (wctrlFlags&WHERE_ONETABLE_ONLY) ) return;
+ sqlite3StrAccumInit(&str, zBuf, sizeof(zBuf), SQLITE_MAX_LENGTH);
+ str.db = db;
+
+ /* Reserve space at the start of the buffer managed by the StrAccum
+ ** object for *pExplain. */
+ assert( sizeof(*pExplain)<=sizeof(zBuf) );
+ str.nChar = sizeof(*pExplain);
+
+ /* Append the object (table or index) name to the buffer. */
+ if( pItem->pSelect ){
+ zObj = "";
+ }else if( (flags & (WHERE_IPK|WHERE_VIRTUALTABLE))==0 ){
+ zObj = pLoop->u.btree.pIndex->zName;
+ }else{
+ zObj = pItem->zName;
+ }
+ sqlite3StrAccumAppend(&str, zObj, sqlite3Strlen30(zObj)+1);
+
+ /* Append the EQP text to the buffer */
isSearch = (flags&(WHERE_BTM_LIMIT|WHERE_TOP_LIMIT))!=0
|| ((flags&WHERE_VIRTUALTABLE)==0 && (pLoop->u.btree.nEq>0))
|| (wctrlFlags&(WHERE_ORDERBY_MIN|WHERE_ORDERBY_MAX));
-
- sqlite3StrAccumInit(&str, zBuf, sizeof(zBuf), SQLITE_MAX_LENGTH);
- str.db = db;
sqlite3StrAccumAppendAll(&str, isSearch ? "SEARCH" : "SCAN");
if( pItem->pSelect ){
sqlite3XPrintf(&str, 0, " SUBQUERY %d", pItem->iSelectId);
@@ -2901,15 +2919,28 @@ static void explainOneScan(
pLoop->u.vtab.idxNum, pLoop->u.vtab.idxStr);
}
#endif
+ nEstRow = pLoop->nOut>=10 ? sqlite3LogEstToInt(pLoop->nOut) : 1;
#ifdef SQLITE_EXPLAIN_ESTIMATED_ROWS
- if( pLoop->nOut>=10 ){
- sqlite3XPrintf(&str, 0, " (~%llu rows)", sqlite3LogEstToInt(pLoop->nOut));
- }else{
- sqlite3StrAccumAppend(&str, " (~1 row)", 9);
- }
+ sqlite3XPrintf(&str, 0, " (~%llu rows)", nEstRow);
#endif
- zMsg = sqlite3StrAccumFinish(&str);
- sqlite3VdbeAddOp4(v, OP_Explain, iId, iLevel, iFrom, zMsg, P4_DYNAMIC);
+ pExplain = (ExplainArg*)sqlite3StrAccumFinish(&str);
+ assert( pExplain || db->mallocFailed );
+ if( pExplain ){
+ memset(pExplain, 0, sizeof(*pExplain));
+ if( pLoop->wsFlags & WHERE_INDEXED ){
+ pExplain->iCsr = pLevel->iIdxCur;
+ }else if( pItem->pSelect==0 ){
+ pExplain->iCsr = pLevel->iTabCur;
+ }else{
+ pExplain->iCsr = -1;
+ }
+ pExplain->nEst = nEstRow;
+ pExplain->zName = (const char*)&pExplain[1];
+ pExplain->zExplain = &pExplain->zName[sqlite3Strlen30(pExplain->zName)+1];
+ sqlite3VdbeAddOp4(v, OP_Explain,
+ iId, iLevel, iFrom, (char*)pExplain,P4_EXPLAIN
+ );
+ }
}
}
#else