]> git.ipfire.org Git - thirdparty/sqlite.git/commitdiff
Add the experimental sqlite3_stmt_scanstatus() API.
authordan <dan@noemail.net>
Fri, 31 Oct 2014 20:11:32 +0000 (20:11 +0000)
committerdan <dan@noemail.net>
Fri, 31 Oct 2014 20:11:32 +0000 (20:11 +0000)
FossilOrigin-Name: 6a9bab34aeb6a01b612211a28c140de60a3e883c

12 files changed:
manifest
manifest.uuid
src/sqlite.h.in
src/tclsqlite.c
src/test1.c
src/vdbe.c
src/vdbe.h
src/vdbeInt.h
src/vdbeapi.c
src/vdbeaux.c
src/where.c
test/scanstatus.test [new file with mode: 0644]

index ce0614fbee700d9d41c216fa452eb80dce47c233..39eaf23ca49a8ca9cf14b35466824ed37d36471b 100644 (file)
--- a/manifest
+++ b/manifest
@@ -1,5 +1,5 @@
-C Change\sthe\scommand-line\sshell\sman-page\sto\suse\sthe\s".tr"\stroff\sdirective\ninstead\sof\s".cc"\sfor\sescaping\sthe\sinitial\s"."\scharacters\sin\sthe\s".help"\noutput.
-D 2014-10-31T14:46:51.896
+C Add\sthe\sexperimental\ssqlite3_stmt_scanstatus()\sAPI.
+D 2014-10-31T20:11:32.562
 F Makefile.arm-wince-mingw32ce-gcc d6df77f1f48d690bd73162294bbba7f59507c72f
 F Makefile.in cf57f673d77606ab0f2d9627ca52a9ba1464146a
 F Makefile.linux-gcc 91d710bdc4998cb015f39edf3cb314ec4f4d7e23
@@ -229,15 +229,15 @@ F src/resolve.c 4965007d6497b6a4d7a6d98751cc39712885f952
 F src/rowset.c eccf6af6d620aaa4579bd3b72c1b6395d9e9fa1e
 F src/select.c 428165951748151e87a15295b7357221433e311b
 F src/shell.c 282f8f5278e0c78eb442217531172ec9e1538796
-F src/sqlite.h.in 737b7dd0f3f81fe183646d22828b39f85ef3e68c
+F src/sqlite.h.in 6e90cdb404e4fa8c0eb149ca79c11c6608a97ece
 F src/sqlite3.rc 992c9f5fb8285ae285d6be28240a7e8d3a7f2bad
 F src/sqlite3ext.h 17d487c3c91b0b8c584a32fbeb393f6f795eea7d
 F src/sqliteInt.h 4f86ac648ea398c1bb3db036062934cde257ea23
 F src/sqliteLimit.h 164b0e6749d31e0daa1a4589a169d31c0dec7b3d
 F src/status.c 81712116e826b0089bb221b018929536b2b5406f
 F src/table.c f142bba7903e93ca8d113a5b8877a108ad1a27dc
-F src/tclsqlite.c c67d310c833046cccc192125d64ad422ab882684
-F src/test1.c 63d4b1707c4052cf9c05c1cbb4a62666d70a0b48
+F src/tclsqlite.c 7cdd4dd3c2a4183483feca260070d73d6e22cd47
+F src/test1.c b53f4da2f386efa5c248716cd4bdc8344a87e952
 F src/test2.c 98049e51a17dc62606a99a9eb95ee477f9996712
 F src/test3.c 1c0e5d6f080b8e33c1ce8b3078e7013fdbcd560c
 F src/test4.c 9b32d22f5f150abe23c1830e2057c4037c45b3df
@@ -289,11 +289,11 @@ F src/update.c 3c4ecc282accf12d39edb8d524cf089645e55a13
 F src/utf.c fc6b889ba0779b7722634cdeaa25f1930d93820c
 F src/util.c 3b627daa45c7308c1e36e3dbaa3f9ce7e5c7fa73
 F src/vacuum.c 59f03f92bcff57faa6a8ca256eb29ccddfb0614a
-F src/vdbe.c 49e659bc165e99b28492004b440e22146dc898ab
-F src/vdbe.h 09f5b4e3719fa454f252322b1cdab5cf1f361327
-F src/vdbeInt.h acc36ac461f973f46ac7942f86abdd93d2f8cfbc
-F src/vdbeapi.c 02d8afcff710eb35e3d9e49cb677308296b00009
-F src/vdbeaux.c 3d6b2b412ef2193aa4729922dfc5df1efadbf6df
+F src/vdbe.c 80329be857cff3c2d6e8c1f594ac689166c44ae7
+F src/vdbe.h f8e5388173dbf8408da4ae1dcbf75911caf2253d
+F src/vdbeInt.h 284b2294c188474daa9b5ce2bd0200560cb0940d
+F src/vdbeapi.c 2afa2e162f290879ca9c51e8795a377035f1662a
+F src/vdbeaux.c 1e2561eeb94749f6b8c0a1eb02b5fecb645ed48c
 F src/vdbeblob.c 8b5442ff0954c44b45cbabbe2e94091a2e16fdef
 F src/vdbemem.c 31d8eabb0cd78bfeab4e5124c7363c3e9e54db9f
 F src/vdbesort.c 975aeffa99acb0991b2f288d30294756bff41438
@@ -302,7 +302,7 @@ F src/vtab.c 2a30791bbd7926b589401bd09c3abb33de563793
 F src/wal.c 10e7de7ce90865a68153f001a61f1d985cd17983
 F src/wal.h df01efe09c5cb8c8e391ff1715cca294f89668a4
 F src/walker.c c253b95b4ee44b21c406e2a1052636c31ea27804
-F src/where.c 5665df88cbd2b38eb72b4b94c8892c8afb360181
+F src/where.c 344beac082f8f4f69ec7c4a669d74294475e5abe
 F src/whereInt.h 19279cd0664ce1d90b9ad3ef0108cb494acfe455
 F test/8_3_names.test ebbb5cd36741350040fd28b432ceadf495be25b2
 F test/aggerror.test a867e273ef9e3d7919f03ef4f0e8c0d2767944f2
@@ -801,6 +801,7 @@ F test/savepoint4.test c8f8159ade6d2acd9128be61e1230f1c1edc6cc0
 F test/savepoint5.test 0735db177e0ebbaedc39812c8d065075d563c4fd
 F test/savepoint6.test f41279c5e137139fa5c21485773332c7adb98cd7
 F test/savepoint7.test fbf319a7b2dda089ec5be30a424a0e95f121d423
+F test/scanstatus.test a62dad3311fc51eab1d6977b082738bd2148fe07
 F test/schema.test 8f7999be894260f151adf15c2c7540f1c6d6a481
 F test/schema2.test 906408621ea881fdb496d878b1822572a34e32c5
 F test/schema3.test 1bc1008e1f8cb5654b248c55f27249366eb7ed38
@@ -1210,7 +1211,10 @@ F tool/vdbe_profile.tcl 67746953071a9f8f2f668b73fe899074e2c6d8c1
 F tool/warnings-clang.sh f6aa929dc20ef1f856af04a730772f59283631d4
 F tool/warnings.sh 0abfd78ceb09b7f7c27c688c8e3fe93268a13b32
 F tool/win/sqlite.vsix deb315d026cc8400325c5863eef847784a219a2f
-P a07078b60007e88adea67bec5f0caf91f707ad78
-R f904ce78baced6aeb6bcb91c631714db
-U drh
-Z c463256d5d45e3bce4a212ca4dd9f3e4
+P 67f0d469da28c023200239a1f3d0c6cef9ef0e45
+R 4c76f3e7b97f67c1dd65bda3a6e96149
+T *branch * scanstatus
+T *sym-scanstatus *
+T -sym-trunk *
+U dan
+Z 4994ca943ee260eeca8f493b4f59766a
index 4735237141e4b3b2c7251db22332af7dedadb938..b7984cac38a94bd201cc4f6f07018d3a17afb605 100644 (file)
@@ -1 +1 @@
-67f0d469da28c023200239a1f3d0c6cef9ef0e45
\ No newline at end of file
+6a9bab34aeb6a01b612211a28c140de60a3e883c
\ No newline at end of file
index b4081f2a022087ff48c23e077a87c8f6a654e3d5..ba20cb98908d8a527099aad8c53bdf7f3880dab3 100644 (file)
@@ -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
index 756d0daa5a914287f55b0faeb431caf6d0a40b03..bff4a92421460269a968301fcdb68f03f93fa733 100644 (file)
@@ -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);
index 802aa99b5bd20a15ddc68eed58fb4abc9d40cc1b..72e70513bc747a8946b8e19c74c17cf7ba70f208 100644 (file)
@@ -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;
index c17bfdfe14190e59fecdd5b4c610f8d92820da8d..a92c0509b3f9acaefee7cf84b5061e6b7c700097 100644 (file)
@@ -167,6 +167,18 @@ int sqlite3_found_count = 0;
 /* Return true if the cursor was opened using the OP_OpenSorter opcode. */
 #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 * * * * *
 **
index f975f95543a34c4ac407fefcb184537e82378fd0..1b470441bed1a15b77030d657843ab4b9a4b73a4 100644 (file)
@@ -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
@@ -100,6 +102,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
 */
@@ -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
index 623c5fdde8d7d64aef3d93a4d8d42387d601cd3c..839108f6bc721057889d55a1bef573f36630dc6d 100644 (file)
@@ -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 */
 };
 
 /*
index daf7b101d27fc9ec1077878f4ea914680137eebe..b09faa1fa5b4568808f1dc45d6179af375d8d04f 100644 (file)
@@ -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;
+  }
+}
+
index 7dfb64130dda61c94bb31473baab001ab46fdc66..a8223890bd34836aa28613af9541c7f3efed790b 100644 (file)
@@ -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);
 }
 
 /*
index feccf2d11de79d9fe0c3957639acba21759f5116..3565327b526f5c6eaf0d54e4de80f4ae6264bda5 100644 (file)
@@ -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
diff --git a/test/scanstatus.test b/test/scanstatus.test
new file mode 100644 (file)
index 0000000..69a92dc
--- /dev/null
@@ -0,0 +1,69 @@
+# 2014 November 1
+#
+# The author disclaims copyright to this source code.  In place of
+# a legal notice, here is a blessing:
+#
+#    May you do good and not evil.
+#    May you find forgiveness for yourself and forgive others.
+#    May you share freely, never taking more than you give.
+#
+#***********************************************************************
+#
+
+set testdir [file dirname $argv0]
+source $testdir/tester.tcl
+set testprefix scanstatus
+
+do_execsql_test 1.0 {
+  CREATE TABLE t1(a, b);
+  CREATE TABLE t2(x, y);
+  INSERT INTO t1 VALUES(1, 2);
+  INSERT INTO t1 VALUES(3, 4);
+  INSERT INTO t2 VALUES('a', 'b');
+  INSERT INTO t2 VALUES('c', 'd');
+  INSERT INTO t2 VALUES('e', 'f');
+}
+
+proc do_scanstatus_test {tn res} {
+  set stmt [db_last_stmt_ptr db]
+  set idx 0
+  set ret [list]
+  while {1} {
+    set r [sqlite3_stmt_scanstatus $stmt $idx]
+    if {[llength $r]==0} break
+    lappend ret {*}$r
+    incr idx
+  }
+
+  uplevel [list do_test $tn [list set {} $ret] [list {*}$res]]
+}
+
+do_execsql_test 1.1 { SELECT count(*) FROM t1, t2; } 6
+do_scanstatus_test 1.2 {
+  nLoop 1 nVisit 2 nEst 1048576 zName t1 zExplain {SCAN TABLE t1}
+  nLoop 2 nVisit 6 nEst 1048576 zName t2 zExplain {SCAN TABLE t2}
+}
+
+do_execsql_test 1.3 {
+  ANALYZE;
+  SELECT count(*) FROM t1, t2;
+} 6
+do_scanstatus_test 1.4 {
+  nLoop 1 nVisit 2 nEst 2 zName t1 zExplain {SCAN TABLE t1}
+  nLoop 2 nVisit 6 nEst 3 zName t2 zExplain {SCAN TABLE t2}
+}
+
+do_execsql_test 1.5 {
+  ANALYZE;
+  SELECT count(*) FROM t1, t2 WHERE t2.rowid>1;
+} 4
+do_scanstatus_test 1.6 {
+  nLoop 1 nVisit 2 nEst 2 zName t2 zExplain 
+  {SEARCH TABLE t2 USING INTEGER PRIMARY KEY (rowid>?)}
+  nLoop 2 nVisit 4 nEst 2 zName t1 zExplain {SCAN TABLE t1}
+}
+
+
+
+
+finish_test