]> git.ipfire.org Git - thirdparty/sqlite.git/commitdiff
Revamp the EXPLAIN infrastructure to facilitate sqlite3_stmt_mode(). The
authordrh <drh@noemail.net>
Fri, 20 Mar 2020 16:13:41 +0000 (16:13 +0000)
committerdrh <drh@noemail.net>
Fri, 20 Mar 2020 16:13:41 +0000 (16:13 +0000)
currently code mostly works, but there are test failures.  This is an
incremental check-in.

FossilOrigin-Name: e9e17e2125dbbafd5da4adb3bd2893735fa4d0aaa5f3daee75f866cb32231a8d

manifest
manifest.uuid
src/vdbe.c
src/vdbeInt.h
src/vdbeapi.c
src/vdbeaux.c

index 67aab6cc0c511782c5c6682d2db7ab9df5f1ffad..b7a88540071c8d90c3ea7e2b9282533b7f48c8f7 100644 (file)
--- a/manifest
+++ b/manifest
@@ -1,5 +1,5 @@
-C Initial\scode\sfor\sa\sproposed\snew\ssqlite3_stmt_mode()\sAPI.\nThis\sis\san\sincomplete\ssnapshot\sof\sa\swork-in-progress.
-D 2020-03-19T21:17:11.830
+C Revamp\sthe\sEXPLAIN\sinfrastructure\sto\sfacilitate\ssqlite3_stmt_mode().\s\sThe\ncurrently\scode\smostly\sworks,\sbut\sthere\sare\stest\sfailures.\s\sThis\sis\san\nincremental\scheck-in.
+D 2020-03-20T16:13:41.030
 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1
 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea
 F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724
@@ -603,11 +603,11 @@ F src/upsert.c 2920de71b20f04fe25eb00b655d086f0ba60ea133c59d7fa3325c49838818e78
 F src/utf.c 95fb6e03a5ca679045c5adccd05380f0addccabef5911abddcb06af069500ab7
 F src/util.c a285c1e026907b69fa2592bd05047a565a1d8a1aef2b73c924b6a8ffe772871a
 F src/vacuum.c 813b510ba887fee6492bcb11f2bf77d7eb58b232b83649136372e0a2fc17f4b9
-F src/vdbe.c ca986d3f9961045256c1b077a6986cd2e9cfa86909adfd0acafb761e6656b5bb
+F src/vdbe.c b7b9ec1d604a12f4cafedf796027a297bc306f0d3afb340948898256cb4473b9
 F src/vdbe.h 51282fbe819ee0e8eeeaab176240860d334c20a12b14f3b363e7f1a4e05d60b9
-F src/vdbeInt.h a341e6994ffd865531089f0c94d3910acc66d25d312259c4a45f422693deab9b
-F src/vdbeapi.c e8351c4f351e492e260fb8b0f9f57575772f583fbbe97dca4c31dcadd06760d6
-F src/vdbeaux.c ce2521d1979d6e081c21766523244891149df247092202db3a0e097f4eae40f2
+F src/vdbeInt.h 1a8807b7dfac4142f5b9047767295327be41f02c17f7bb6bd8ffef6219dac588
+F src/vdbeapi.c d840fb3b80bbbd25fd44e1bb2d4a209e297832f527d21ea718fa53502a2a2d97
+F src/vdbeaux.c 734cb197ffdf83322832f9c881e9841b43b27f43677b8c75ad444cd2ccee2802
 F src/vdbeblob.c 253ed82894924c362a7fa3079551d3554cd1cdace39aa833da77d3bc67e7c1b1
 F src/vdbemem.c 39b942ecca179f4f30a32b54579a85d74ccaefa5af2a0ad2700abe5ef0768b22
 F src/vdbesort.c 2be76d26998ce2b3324cdcc9f6443728e54b6c7677c553ad909c7d7cfab587df
@@ -1860,10 +1860,7 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93
 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc
 F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e
 F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0
-P 11e0844f71e8f2d27ce9363fb505e02fd7795c61dae0b3886cf0d8df4484dd97
-R bfd344317fdbc4dd2c74ef078364da12
-T *branch * sqlite3_stmt_mode
-T *sym-sqlite3_stmt_mode *
-T -sym-trunk *
+P 3cf7537b5e14e218218b18b3c0c668c950a71fcddc68a5faf0f197519718a6c2
+R 858f863db42629ad352a7a54d45b01f7
 U drh
-Z 4fdf6d3f28007c100776cbe88029096e
+Z acdb8ce72295d433409d053ae8da03ad
index 05aae7265e73f035316fcabf1251399fe1b7478b..ce65b1e44e2cfe0ec7a5d4c7034122da865c637e 100644 (file)
@@ -1 +1 @@
-3cf7537b5e14e218218b18b3c0c668c950a71fcddc68a5faf0f197519718a6c2
\ No newline at end of file
+e9e17e2125dbbafd5da4adb3bd2893735fa4d0aaa5f3daee75f866cb32231a8d
\ No newline at end of file
index 1eae9c62940ba303225fd180306a874c15a46e3e..c7ba320ba4d049728b2cf46e67f24a826feb2a52 100644 (file)
@@ -539,9 +539,10 @@ void sqlite3VdbeMemPrettyPrint(Mem *pMem, StrAccum *pStr){
 
 #ifdef SQLITE_DEBUG
 /*
-** Print the value of a register for tracing purposes:
+** Print the value of a Mem object on standard output.
+** Used for tracing and for interactive debugging only.
 */
-static void memTracePrint(Mem *p){
+static void memPrint(Mem *p){
   if( p->flags & MEM_Undefined ){
     printf(" undefined");
   }else if( p->flags & MEM_Null ){
@@ -567,9 +568,19 @@ static void memTracePrint(Mem *p){
   }
   if( p->flags & MEM_Subtype ) printf(" subtype=0x%02x", p->eSubtype);
 }
+/* Print N Mem objects beginning with p.  Used for interactive debugging */
+void sqlite3MemPrint(Mem *p, int N){
+  int i;
+  for(i=0; i<N; i++){
+    if( N>1 ) printf("%3d:", i);
+    memPrint(p+i);
+    printf("\n");
+  }
+  fflush(stdout);
+}
 static void registerTrace(int iReg, Mem *p){
   printf("R[%d] = ", iReg);
-  memTracePrint(p);
+  memPrint(p);
   if( p->pScopyFrom ){
     printf(" <== R[%d]", (int)(p->pScopyFrom - &p[-iReg]));
   }
@@ -705,7 +716,7 @@ int sqlite3VdbeExec(
   assert( p->bIsReader || p->readOnly!=0 );
   p->iCurrentTime = 0;
   assert( p->explain==SQLITE_STMTMODE_RUN );
-  p->pResultSet = 0;
+  p->nRes = 0;
   db->busyHandler.nBusy = 0;
   if( db->u1.isInterrupted ) goto abort_due_to_interrupt;
   sqlite3VdbeIOTraceSql(p);
@@ -1493,6 +1504,7 @@ case OP_ResultRow: {
   ** a side effect.
   */
   pMem = p->pResultSet = &aMem[pOp->p1];
+  p->nRes = pOp->p2;
   for(i=0; i<pOp->p2; i++){
     assert( memIsValid(&pMem[i]) );
     Deephemeralize(&pMem[i]);
index 1717d64a6f6ce168981b56dbbe15a31f5a7dd917..5a40e29e78bf224347a42c09b6936a22720c8eac 100644 (file)
 # define SQLITE_MAX_SCHEMA_RETRY 50
 #endif
 
+/*
+** Maximum number of columns in any "EXPLAIN" output
+*/
+#define VDBE_EXPLAIN_COLS     9
+
 /*
 ** VDBE_DISPLAY_P4 is true or false depending on whether or not the
 ** "explain" P4 display logic is enabled.
@@ -337,11 +342,6 @@ struct sqlite3_context {
   sqlite3_value *argv[1]; /* Argument set */
 };
 
-/* A bitfield type for use inside of structures.  Always follow with :N where
-** N is the number of bits.
-*/
-typedef unsigned bft;  /* Bit Field Type */
-
 /* The ScanStatus object holds a single value for the
 ** sqlite3_stmt_scanstatus() interface.
 */
@@ -414,19 +414,20 @@ struct Vdbe {
   int rcApp;              /* errcode set by sqlite3_result_error_code() */
   u32 nWrite;             /* Number of write operations that have occurred */
 #endif
-  u16 nResColumn;         /* Number of columns in one row of the result set */
+  u16 nResColumn;         /* Columns in one row of a normal result set */
+  u16 nRes;               /* Columns in the current actual result set */
   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 */
-  bft expired:2;          /* 1: recompile VM immediately  2: when convenient */
-  bft explain:2;          /* True if EXPLAIN present on SQL command */
-  bft origExplain:2;      /* The original value of explain */
-  bft doingRerun:1;       /* True if rerunning after an auto-reprepare */
-  bft changeCntOn:1;      /* True to update the change-counter */
-  bft runOnlyOnce:1;      /* Automatically expire on reset */
-  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 */
+  u8 explain:2;           /* True if EXPLAIN present on SQL command */
+  u8 origExplain:2;       /* The original value of explain */
+  u8 expired:2;           /* 1: recompile VM immediately  2: when convenient */
+  u8 doingRerun:1;        /* True if rerunning after an auto-reprepare */
+  u8 changeCntOn:1;       /* True to update the change-counter */
+  u8 runOnlyOnce:1;       /* Automatically expire on reset */
+  u8 usesStmtJournal:1;   /* True if uses a statement journal */
+  u8 readOnly:1;          /* True for statements that do not write */
+  u8 bIsReader:1;         /* True for statements that read */
   yDbMask btreeMask;      /* Bitmask of db->aDb[] entries referenced */
   yDbMask lockMask;       /* Subset of btreeMask that requires a lock */
   u32 aCounter[7];        /* Counters used by sqlite3_stmt_status() */
@@ -499,6 +500,7 @@ int sqlite3VdbeIdxKeyCompare(sqlite3*,VdbeCursor*,UnpackedRecord*,int*);
 int sqlite3VdbeIdxRowid(sqlite3*, BtCursor*, i64*);
 int sqlite3VdbeExec(Vdbe*);
 #ifndef SQLITE_OMIT_EXPLAIN
+Mem *sqlite3VdbeSetExplainColumnNames(Vdbe*);
 int sqlite3VdbeList(Vdbe*);
 #endif
 int sqlite3VdbeHalt(Vdbe*);
index c007a7712167a626dfa3a22738aa02161ec0ea56..dda71d64e69e5d7ad520b54340ef8652e0d93b0b 100644 (file)
@@ -1000,7 +1000,7 @@ int sqlite3_column_count(sqlite3_stmt *pStmt){
     0,     /* SQLITE_STMTMODE_RUN  (Use pVm->nResColumn instead) */
     8,     /* SQLITE_STMTMODE_EXPLAIN    */
     4,     /* SQLITE_STMTMODE_EQP        */
-    6      /* SQLITE_STMTMODE_TABLELIST  */
+    5      /* SQLITE_STMTMODE_TABLELIST  */
   };
   Vdbe *pVm = (Vdbe *)pStmt;
   if( pVm==0 ) return 0;
@@ -1015,8 +1015,8 @@ int sqlite3_column_count(sqlite3_stmt *pStmt){
 */
 int sqlite3_data_count(sqlite3_stmt *pStmt){
   Vdbe *pVm = (Vdbe *)pStmt;
-  if( pVm==0 || pVm->pResultSet==0 ) return 0;
-  return pVm->nResColumn;
+  if( pVm==0 || pVm->nRes==0 ) return 0;
+  return sqlite3_column_count(pStmt);
 }
 
 /*
@@ -1070,7 +1070,7 @@ static Mem *columnMem(sqlite3_stmt *pStmt, int i){
   if( pVm==0 ) return (Mem*)columnNullValue();
   assert( pVm->db );
   sqlite3_mutex_enter(pVm->db->mutex);
-  if( pVm->pResultSet!=0 && i<pVm->nResColumn && i>=0 ){
+  if( i<pVm->nRes && i>=0 ){
     pOut = &pVm->pResultSet[i];
   }else{
     sqlite3Error(pVm->db, SQLITE_RANGE);
@@ -1704,7 +1704,9 @@ int sqlite3_stmt_mode(sqlite3_stmt *pStmt, int iNewMode){
    || iNewMode==SQLITE_STMTMODE_TABLELIST
    || iNewMode==v->origExplain
   ){
-    v->explain = iNewMode;
+    if( v->magic==VDBE_MAGIC_RUN && v->pc<0 ){
+      v->explain = iNewMode;
+    }
   }
   return v->explain;
 }
index 41b7e7bcc618f1066b2f2bbe332ba5c0617fcd5a..d5f2a3b094662cee9b15c9f04c5e51ef6ee0d1b5 100644 (file)
@@ -1872,7 +1872,11 @@ static void initMemArray(Mem *p, int N, sqlite3 *db, u16 flags){
 }
 
 /*
-** Release an array of N Mem elements
+** Release all content from an array of N Mem elements.  All elements
+** are left in a state of "MEM_Undefined".
+**
+** This routine does not free the array itself.  It just frees the
+** content held by the Mem elements in the array.
 */
 static void releaseMemArray(Mem *p, int N){
   if( p && N ){
@@ -1963,22 +1967,61 @@ void sqlite3VdbeFrameDelete(VdbeFrame *p){
 
 #ifndef SQLITE_OMIT_EXPLAIN
 /*
-** Give a listing of the program in the virtual machine.
+** There should are VDBE_EXPLAIN_COLS*2 extra registers on the end of the
+** Vdbe.aColName array for statements that are in one of the
+** "explain" modes.  The first VDBE_EXPLAIN_COLS registers hold the names of
+** the columns, and the last VDBE_EXPLAIN_COLS registers are used for to store
+** result set values when running the explain.
 **
-** The interface is the same as sqlite3VdbeExec().  But instead of
-** running the code, it invokes the callback once for each instruction.
-** This feature is used to implement "EXPLAIN".
+** This routine makes sure those extra register exist and return
+** a pointer to the first of them.  If the extra registers do
+** not exist, then the are allocated.
 **
-** When p->explain==1, each instruction is listed.  When
-** p->explain==2, only OP_Explain instructions are listed and these
-** are shown in a different format.  p->explain==2 is used to implement
-** EXPLAIN QUERY PLAN.
-** 2018-04-24:  In p->explain==2 mode, the OP_Init opcodes of triggers
-** are also shown, so that the boundaries between the main program and
-** each trigger are clear.
+** NULL is returned if there is a memory allocation error.
+*/
+Mem *sqlite3VdbeSetExplainColumnNames(Vdbe *p){
+  int n = p->nResColumn*COLNAME_N;
+  if( p->nColName<=n ){
+    static const char * const azColName[] = {
+       "addr", "opcode", "p1", "p2", "p3", "p4", "p5", "comment",
+       "id", "parent", "notused", "detail",
+       "schema", "type", "name", "rw", "rootpage"
+    };
+    int iFirst, cnt, i;
+    Mem *pNew = sqlite3DbRealloc(p->db, p->aColName,
+                                 (n+VDBE_EXPLAIN_COLS*2)*sizeof(Mem));
+    if( pNew==0 ){
+      return 0;
+    }
+    p->aColName = pNew;
+    p->nColName = n+VDBE_EXPLAIN_COLS*2;
+    initMemArray(p->aColName+n, VDBE_EXPLAIN_COLS*2, p->db, MEM_Null);
+    if( p->explain==SQLITE_STMTMODE_EXPLAIN ){
+      iFirst = 0;
+      cnt = 8;
+    }else if( p->explain==SQLITE_STMTMODE_EQP ){
+      iFirst = 8;
+      cnt = 4;
+    }else{
+      iFirst = 12;
+      cnt = 5;
+    }
+    for(i=0; i<cnt; i++){
+      sqlite3VdbeMemSetStr(pNew+n+i, azColName[i+iFirst], -1,
+                                SQLITE_UTF8, SQLITE_STATIC);
+    }
+  }
+  return p->aColName + n;
+}
+#endif
+
+#ifndef SQLITE_OMIT_EXPLAIN
+/*
+** Give a listing of the program in the virtual machine.
 **
-** When p->explain==1, first the main program is listed, then each of
-** the trigger subprograms are listed one by one.
+** The interface is the same as sqlite3VdbeExec().  But instead of
+** running the code, it returns rows based on the bytecode content,
+** rather than on actually running the bytecode.
 */
 int sqlite3VdbeList(
   Vdbe *p                   /* The VDBE */
@@ -1990,11 +2033,13 @@ int sqlite3VdbeList(
   sqlite3 *db = p->db;                 /* The database connection */
   int i;                               /* Loop counter */
   int rc = SQLITE_OK;                  /* Return code */
-  Mem *pMem = &p->aMem[1];             /* First Mem of result set */
+  Mem *pMem;                           /* First Mem of result set */
   int bListSubprogs = (p->explain==1 || (db->flags & SQLITE_TriggerEQP)!=0);
   Op *pOp = 0;
 
-  assert( p->explain );
+  assert( p->explain==SQLITE_STMTMODE_EXPLAIN
+       || p->explain==SQLITE_STMTMODE_EQP
+       || p->explain==SQLITE_STMTMODE_TABLELIST );
   assert( p->magic==VDBE_MAGIC_RUN );
   assert( p->rc==SQLITE_OK || p->rc==SQLITE_BUSY || p->rc==SQLITE_NOMEM );
 
@@ -2002,8 +2047,15 @@ int sqlite3VdbeList(
   ** the result, result columns may become dynamic if the user calls
   ** sqlite3_column_text16(), causing a translation to UTF-16 encoding.
   */
-  releaseMemArray(pMem, 8);
-  p->pResultSet = 0;
+  pMem = sqlite3VdbeSetExplainColumnNames(p);
+  if( pMem==0 ){
+    p->rc = SQLITE_NOMEM;
+    return SQLITE_ERROR;
+  }
+  pMem += VDBE_EXPLAIN_COLS;
+  releaseMemArray(pMem, VDBE_EXPLAIN_COLS);
+  p->pResultSet = pMem;
+  p->nRes = 0;
 
   if( p->rc==SQLITE_NOMEM ){
     /* This happens if a malloc() inside a call to sqlite3_column_text() or
@@ -2021,12 +2073,8 @@ int sqlite3VdbeList(
   */
   nRow = p->nOp;
   if( bListSubprogs ){
-    /* The first 8 memory cells are used for the result set.  So we will
-    ** commandeer the 9th cell to use as storage for an array of pointers
-    ** to trigger subprograms.  The VDBE is guaranteed to have at least 9
-    ** cells.  */
-    assert( p->nMem>9 );
-    pSub = &p->aMem[9];
+    pSub = pMem++;
+    p->pResultSet = pMem;
     if( pSub->flags&MEM_Blob ){
       /* On the first call to sqlite3_step(), pSub will hold a NULL.  It is
       ** initialized to a BLOB by the P4_SUBPROGRAM processing logic below */
@@ -2065,7 +2113,7 @@ int sqlite3VdbeList(
 
     /* When an OP_Program opcode is encounter (the only opcode that has
     ** a P4_SUBPROGRAM argument), expand the size of the array of subprograms
-    ** kept in p->aMem[9].z to hold the new program - assuming this subprogram
+    ** to hold the new program - assuming this subprogram
     ** has not already been seen.
     */
     if( bListSubprogs && pOp->p4type==P4_SUBPROGRAM ){
@@ -2087,7 +2135,7 @@ int sqlite3VdbeList(
         nRow += pOp->p4.pProgram->nOp;
       }
     }
-    if( p->explain<2 ) break;
+    if( p->explain==SQLITE_STMTMODE_EXPLAIN ) break;
     if( pOp->opcode==OP_Explain ) break;
     if( pOp->opcode==OP_Init && p->pc>1 ) break;
   }
@@ -2099,7 +2147,7 @@ int sqlite3VdbeList(
       sqlite3VdbeError(p, sqlite3ErrStr(p->rc));
     }else{
       char *zP4;
-      if( p->explain==1 ){
+      if( p->explain==SQLITE_STMTMODE_EXPLAIN ){
         pMem->flags = MEM_Int;
         pMem->u.i = i;                                /* Program counter */
         pMem++;
@@ -2140,7 +2188,7 @@ int sqlite3VdbeList(
       }
       pMem++;
 
-      if( p->explain==1 ){
+      if( p->explain==SQLITE_STMTMODE_EXPLAIN ){
         if( sqlite3VdbeMemClearAndResize(pMem, 4) ){
           assert( p->db->mallocFailed );
           return SQLITE_ERROR;
@@ -2162,10 +2210,11 @@ int sqlite3VdbeList(
 #else
         pMem->flags = MEM_Null;                       /* Comment */
 #endif
+        pMem++;
       }
 
-      p->nResColumn = 8 - 4*(p->explain-1);
-      p->pResultSet = &p->aMem[1];
+      p->nRes = (int)(pMem - p->pResultSet);
+      assert( p->nRes<=VDBE_EXPLAIN_COLS );
       p->rc = SQLITE_OK;
       rc = SQLITE_ROW;
     }
@@ -2369,25 +2418,6 @@ 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;
-    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->explain = p->origExplain = pParse->explain;
   }
   p->expired = 0;
@@ -3310,7 +3340,7 @@ int sqlite3VdbeReset(Vdbe *p){
 #endif
   sqlite3DbFree(db, p->zErrMsg);
   p->zErrMsg = 0;
-  p->pResultSet = 0;
+  p->nRes = 0;
 #ifdef SQLITE_DEBUG
   p->nWrite = 0;
 #endif