diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/sqlite.h.in | 36 | ||||
-rw-r--r-- | src/tclsqlite.c | 42 | ||||
-rw-r--r-- | src/test1.c | 73 | ||||
-rw-r--r-- | src/vdbe.c | 44 | ||||
-rw-r--r-- | src/vdbe.h | 16 | ||||
-rw-r--r-- | src/vdbeInt.h | 4 | ||||
-rw-r--r-- | src/vdbeapi.c | 37 | ||||
-rw-r--r-- | src/vdbeaux.c | 13 | ||||
-rw-r--r-- | src/where.c | 55 |
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 |