]> git.ipfire.org Git - thirdparty/sqlite.git/commitdiff
Experimental changes to EXPLAIN QUERY PLAN.
authordan <dan@noemail.net>
Mon, 8 Nov 2010 19:01:16 +0000 (19:01 +0000)
committerdan <dan@noemail.net>
Mon, 8 Nov 2010 19:01:16 +0000 (19:01 +0000)
FossilOrigin-Name: f4747eb83dacce6430ad6e5eb20155ffad975514

manifest
manifest.uuid
src/prepare.c
src/select.c
src/sqliteInt.h
src/vdbeaux.c
src/where.c
test/eqp.test [new file with mode: 0644]

index fdf2f98586c7a0f0277084b49604595184610d24..aa49581051155cddea842e2ffa72313fedaf9b20 100644 (file)
--- a/manifest
+++ b/manifest
@@ -1,5 +1,5 @@
-C Fix\sto\sxTruncate\sand\smore\sjournal\smode\stests\sfor\sthe\smultiplex\sVFS.
-D 2010-11-05T20:50:44
+C Experimental\schanges\sto\sEXPLAIN\sQUERY\sPLAN.
+D 2010-11-08T19:01:16
 F Makefile.arm-wince-mingw32ce-gcc d6df77f1f48d690bd73162294bbba7f59507c72f
 F Makefile.in e7a59672eaeb04408d1fa8501618d7501a3c5e39
 F Makefile.linux-gcc 91d710bdc4998cb015f39edf3cb314ec4f4d7e23
@@ -169,16 +169,16 @@ F src/pcache.c 09d38c44ab275db581f7a2f6ff8b9bc7f8c0faaa
 F src/pcache.h c683390d50f856d4cd8e24342ae62027d1bb6050
 F src/pcache1.c e9578a3beac26f229ee558a4e16c863f2498185f
 F src/pragma.c 216d12e4546e65ca6cfcd3221e64573889ae8f34
-F src/prepare.c ce4c35a2b1d5fe916e4a46b70d24a6e997d7c4c6
+F src/prepare.c c2b318037d626fed27905c9446730b560637217a
 F src/printf.c 8ae5082dd38a1b5456030c3755ec3a392cd51506
 F src/random.c cd4a67b3953b88019f8cd4ccd81394a8ddfaba50
 F src/resolve.c 1c0f32b64f8e3f555fe1f732f9d6f501a7f05706
 F src/rowset.c 69afa95a97c524ba6faf3805e717b5b7ae85a697
-F src/select.c a03ec6a313ef8311f081ee478f96ae04ff691608
+F src/select.c c32d6da90895abe7ede0c18377c756d706f25e41
 F src/shell.c 8517fc1f9c59ae4007e6cc8b9af91ab231ea2056
 F src/sqlite.h.in f47e09412fc9a129f759fa4d96ef21f4b3d529eb
 F src/sqlite3ext.h c90bd5507099f62043832d73f6425d8d5c5da754
-F src/sqliteInt.h c63b0340dfdfde18ff255ddccf004edd2d073288
+F src/sqliteInt.h 7349903d18e2444e38414aa6da86b9d32ae1af92
 F src/sqliteLimit.h a17dcd3fb775d63b64a43a55c54cb282f9726f44
 F src/status.c 496913d4e8441195f6f2a75b1c95993a45b9b30b
 F src/table.c 2cd62736f845d82200acfa1287e33feb3c15d62e
@@ -231,7 +231,7 @@ F src/vdbe.c e1aa917961e69f71c80f46ce231b496d3c841ae1
 F src/vdbe.h 4de0efb4b0fdaaa900cf419b35c458933ef1c6d2
 F src/vdbeInt.h 7f4cf1b2b69bef3a432b1f23dfebef57275436b4
 F src/vdbeapi.c 5368714fa750270cf6430160287c21adff44582d
-F src/vdbeaux.c de0b06b11a25293e820a49159eca9f1c51a64716
+F src/vdbeaux.c 762c2b146cf5fe7a7f743af1bbfed4a966aa937a
 F src/vdbeblob.c e0ce3c54cc0c183af2ec67b63a289acf92251df4
 F src/vdbemem.c 23723a12cd3ba7ab3099193094cbb2eb78956aa9
 F src/vdbetrace.c 864cef96919323482ebd9986f2132435115e9cc2
@@ -239,7 +239,7 @@ F src/vtab.c b297e8fa656ab5e66244ab15680d68db0adbec30
 F src/wal.c f26b8d297bd11cb792e609917f9d4c6718ac8e0e
 F src/wal.h c1aac6593a0b02b15dc625987e619edeab39292e
 F src/walker.c 3112bb3afe1d85dc52317cb1d752055e9a781f8f
-F src/where.c d9a31eb3d59466b6c53567c8c9a6c2fe68bbd565
+F src/where.c ddfe0e1ac1a2c9d382b1df5bd6f9e2b0282ecc39
 F test/aggerror.test a867e273ef9e3d7919f03ef4f0e8c0d2767944f2
 F test/alias.test 4529fbc152f190268a15f9384a5651bbbabc9d87
 F test/all.test 6745008c144bd2956d58864d21f7b304689c1cce
@@ -365,6 +365,7 @@ F test/enc.test e54531cd6bf941ee6760be041dff19a104c7acea
 F test/enc2.test 6d91a5286f59add0cfcbb2d0da913b76f2242398
 F test/enc3.test 5c550d59ff31dccdba5d1a02ae11c7047d77c041
 F test/enc4.test 4b575ef09e0eff896e73bd24076f96c2aa6a42de
+F test/eqp.test d8ad22f65ad29c93708e8986c568b5b6ef043dce
 F test/eval.test bc269c365ba877554948441e91ad5373f9f91be3
 F test/exclusive.test 53e1841b422e554cecf0160f937c473d6d0e3062
 F test/exclusive2.test 76e63c05349cb70d09d60b99d2ae625525ff5155
@@ -885,7 +886,10 @@ F tool/speedtest2.tcl ee2149167303ba8e95af97873c575c3e0fab58ff
 F tool/speedtest8.c 2902c46588c40b55661e471d7a86e4dd71a18224
 F tool/speedtest8inst1.c 293327bc76823f473684d589a8160bde1f52c14e
 F tool/vdbe-compress.tcl d70ea6d8a19e3571d7ab8c9b75cba86d1173ff0f
-P 72ba3e368bec34532ec7b5e856a4daa7e1c8cccb
-R 0042c6a8b282a3f9ef09bfa98caed3ba
-U shaneh
-Z 7b8161cae1f9c054697e43cb6ccb5f60
+P 65fa1164f035d270db48db6474da888aacfba3bd
+R 5aabb390d1e5d171e6d1ec14ea681c6d
+T *branch * experimental
+T *sym-experimental *
+T -sym-trunk *
+U dan
+Z 0909e073f28cae2ed26163f9bcbdaa3d
index cc3e0f4a7bed439b5c390fd72a3aa06cc15f3b5e..02fa406790ee158084e1993bd96917e642dc6031 100644 (file)
@@ -1 +1 @@
-65fa1164f035d270db48db6474da888aacfba3bd
\ No newline at end of file
+f4747eb83dacce6430ad6e5eb20155ffad975514
\ No newline at end of file
index 74accd761f00e0fab8a75b26b0829c2b609e4638..fa64a00dfd51d694c23be23241fce30aa0dbf642 100644 (file)
@@ -628,13 +628,13 @@ static int sqlite3Prepare(
   if( rc==SQLITE_OK && pParse->pVdbe && pParse->explain ){
     static const char * const azColName[] = {
        "addr", "opcode", "p1", "p2", "p3", "p4", "p5", "comment",
-       "order", "from", "detail"
+       "selectid", "order", "from", "detail"
     };
     int iFirst, mx;
     if( pParse->explain==2 ){
-      sqlite3VdbeSetNumCols(pParse->pVdbe, 3);
+      sqlite3VdbeSetNumCols(pParse->pVdbe, 4);
       iFirst = 8;
-      mx = 11;
+      mx = 12;
     }else{
       sqlite3VdbeSetNumCols(pParse->pVdbe, 8);
       iFirst = 0;
index 766f67f8dc7d7f39a399e738679bad2e016014d2..a8fcfee1364c974a4fb28191811dfac2a78447dd 100644 (file)
@@ -771,6 +771,19 @@ static KeyInfo *keyInfoFromExprList(Parse *pParse, ExprList *pList){
   return pInfo;
 }
 
+#ifndef SQLITE_OMIT_EXPLAIN
+static void explainTempTable(Parse *pParse, const char *zUsage){
+  if( pParse->explain==2 ){
+    Vdbe *v = pParse->pVdbe;
+    char *zMsg = sqlite3MPrintf(pParse->db, "USE TEMP B-TREE FOR %s", zUsage);
+    sqlite3VdbeAddOp4(v, OP_Explain, pParse->iSelectId, 0, 0, zMsg, P4_DYNAMIC);
+  }
+}
+# define explainRestoreSelectId() pParse->iSelectId = iRestoreSelectId
+#else
+# define explainRestoreSelectId()
+# define explainTempTable(y,z)
+#endif
 
 /*
 ** If the inner loop was generated using a non-null pOrderBy argument,
@@ -3590,6 +3603,11 @@ int sqlite3Select(
   int iEnd;              /* Address of the end of the query */
   sqlite3 *db;           /* The database connection */
 
+#ifndef SQLITE_OMIT_EXPLAIN
+  int iRestoreSelectId = pParse->iSelectId;
+  pParse->iSelectId = pParse->iNextSelectId++;
+#endif
+
   db = pParse->db;
   if( p==0 || db->mallocFailed || pParse->nErr ){
     return 1;
@@ -3696,9 +3714,10 @@ int sqlite3Select(
       mxSelect = db->aLimit[SQLITE_LIMIT_COMPOUND_SELECT];
       if( mxSelect && cnt>mxSelect ){
         sqlite3ErrorMsg(pParse, "too many terms in compound SELECT");
-        return 1;
+        goto select_end;
       }
     }
+    explainRestoreSelectId();
     return multiSelect(pParse, p, pDest);
   }
 #endif
@@ -3711,7 +3730,6 @@ int sqlite3Select(
     p->pGroupBy = sqlite3ExprListDup(db, p->pEList, 0);
     pGroupBy = p->pGroupBy;
     p->selFlags &= ~SF_Distinct;
-    isDistinct = 0;
   }
 
   /* If there is both a GROUP BY and an ORDER BY clause and they are
@@ -3758,7 +3776,7 @@ int sqlite3Select(
 
   /* Open a virtual index to use for the distinct set.
   */
-  if( isDistinct ){
+  if( p->selFlags & SF_Distinct ){
     KeyInfo *pKeyInfo;
     assert( isAgg || pGroupBy );
     distinct = pParse->nTab++;
@@ -3917,6 +3935,9 @@ int sqlite3Select(
         int nCol;
         int nGroupBy;
 
+        explainTempTable(pParse, 
+            isDistinct && !(p->selFlags&SF_Distinct)?"DISTINCT":"GROUP BY");
+
         groupBySort = 1;
         nGroupBy = pGroupBy->nExpr;
         nCol = nGroupBy + 1;
@@ -4178,10 +4199,15 @@ int sqlite3Select(
     
   } /* endif aggregate query */
 
+  if( distinct>=0 ){
+    explainTempTable(pParse, "DISTINCT");
+  }
+
   /* If there is an ORDER BY clause, then we need to sort the results
   ** and send them to the callback one by one.
   */
   if( pOrderBy ){
+    explainTempTable(pParse, "ORDER BY");
     generateSortTail(pParse, p, v, pEList->nExpr, pDest);
   }
 
@@ -4198,6 +4224,7 @@ int sqlite3Select(
   ** successful coding of the SELECT.
   */
 select_end:
+  explainRestoreSelectId();
 
   /* Identify column names if results of the SELECT are to be output.
   */
index 8515efcc49fa3b8a4efed6bd7bf40e8ba7d9068f..0179dfbe2273bb7dc43e2659b0b435b3f12db0d4 100644 (file)
@@ -1859,6 +1859,7 @@ struct SrcList {
 struct WherePlan {
   u32 wsFlags;                   /* WHERE_* flags that describe the strategy */
   u32 nEq;                       /* Number of == constraints */
+  double nRow;                   /* Estimated number of rows (for EQP) */
   union {
     Index *pIdx;                   /* Index when WHERE_INDEXED is true */
     struct WhereTerm *pTerm;       /* WHERE clause term for OR-search */
@@ -2213,6 +2214,11 @@ struct Parse {
   int nHeight;            /* Expression tree height of current sub-select */
   Table *pZombieTab;      /* List of Table objects to delete after code gen */
   TriggerPrg *pTriggerPrg;    /* Linked list of coded triggers */
+
+#ifndef SQLITE_OMIT_EXPLAIN
+  int iSelectId;
+  int iNextSelectId;
+#endif
 };
 
 #ifdef SQLITE_OMIT_VIRTUALTABLE
index c0227d596f1518741223b46579b6f2b3fbe8979d..e22387433bff3a3da118c6bacedf456fa03c358c 100644 (file)
@@ -1182,12 +1182,10 @@ int sqlite3VdbeList(
     pMem->type = SQLITE_INTEGER;
     pMem++;
 
-    if( p->explain==1 ){
-      pMem->flags = MEM_Int;
-      pMem->u.i = pOp->p3;                          /* P3 */
-      pMem->type = SQLITE_INTEGER;
-      pMem++;
-    }
+    pMem->flags = MEM_Int;
+    pMem->u.i = pOp->p3;                          /* P3 */
+    pMem->type = SQLITE_INTEGER;
+    pMem++;
 
     if( sqlite3VdbeMemGrow(pMem, 32, 0) ){            /* P4 */
       assert( p->db->mallocFailed );
@@ -1232,7 +1230,7 @@ int sqlite3VdbeList(
       }
     }
 
-    p->nResColumn = 8 - 5*(p->explain-1);
+    p->nResColumn = 8 - 4*(p->explain-1);
     p->rc = SQLITE_OK;
     rc = SQLITE_ROW;
   }
index eb0e8ead763855122e3f368e00fecc48fc161dd7..7bc360ca1b515b80c2c91d86a77599bef59d42cc 100644 (file)
@@ -192,7 +192,6 @@ struct WhereMaskSet {
 struct WhereCost {
   WherePlan plan;    /* The lookup strategy */
   double rCost;      /* Overall cost of pursuing this search strategy */
-  double nRow;       /* Estimated number of output rows */
   Bitmask used;      /* Bitmask of cursors used by this plan */
 };
 
@@ -1621,7 +1620,7 @@ static void bestOrClauseIndex(
           continue;
         }
         rTotal += sTermCost.rCost;
-        nRow += sTermCost.nRow;
+        nRow += sTermCost.plan.nRow;
         used |= sTermCost.used;
         if( rTotal>=pCost->rCost ) break;
       }
@@ -1640,8 +1639,8 @@ static void bestOrClauseIndex(
       WHERETRACE(("... multi-index OR cost=%.9g nrow=%.9g\n", rTotal, nRow));
       if( rTotal<pCost->rCost ){
         pCost->rCost = rTotal;
-        pCost->nRow = nRow;
         pCost->used = used;
+        pCost->plan.nRow = nRow;
         pCost->plan.wsFlags = flags;
         pCost->plan.u.pTerm = pTerm;
       }
@@ -1725,7 +1724,7 @@ static void bestAutomaticIndex(
       WHERETRACE(("auto-index reduces cost from %.2f to %.2f\n",
                     pCost->rCost, costTempIdx));
       pCost->rCost = costTempIdx;
-      pCost->nRow = logN + 1;
+      pCost->plan.nRow = logN + 1;
       pCost->plan.wsFlags = WHERE_TEMP_INDEX;
       pCost->used = pTerm->prereqRight;
       break;
@@ -2798,11 +2797,11 @@ static void bestBtreeIndex(
     ** index and its cost in the pCost structure.
     */
     if( (!pIdx || wsFlags)
-     && (cost<pCost->rCost || (cost<=pCost->rCost && nRow<pCost->nRow))
+     && (cost<pCost->rCost || (cost<=pCost->rCost && nRow<pCost->plan.nRow))
     ){
       pCost->rCost = cost;
-      pCost->nRow = nRow;
       pCost->used = used;
+      pCost->plan.nRow = nRow;
       pCost->plan.wsFlags = (wsFlags&wsFlagMask);
       pCost->plan.nEq = nEq;
       pCost->plan.u.pIdx = pIdx;
@@ -3131,6 +3130,98 @@ static int codeAllEqualityTerms(
   return regBase;
 }
 
+#ifndef SQLITE_OMIT_EXPLAIN
+static char *indexRangeText(sqlite3 *db, WhereLevel *pLevel, Table *pTab){
+  WherePlan *pPlan = &pLevel->plan;
+  Index *pIndex = pPlan->u.pIdx;
+  int nEq = pPlan->nEq;
+  char *zRet = 0;
+  int i;
+
+  for(i=0; i<nEq; i++){
+    char *zCol = pTab->aCol[pIndex->aiColumn[i]].zName;
+    zRet = sqlite3MAppendf(db, zRet, 
+        "%s%s%s=?", (zRet?zRet:""), (zRet?" AND ":""), zCol);
+  }
+
+  if( pPlan->wsFlags&WHERE_BTM_LIMIT ){
+    zRet = sqlite3MAppendf(db, zRet,
+        "%s%s%s>?", (zRet?zRet:""), (zRet?" AND ":""), pTab->aCol[nEq].zName);
+  }
+  if( pPlan->wsFlags&WHERE_TOP_LIMIT ){
+    zRet = sqlite3MAppendf(db, zRet,
+        "%s%s%s<?", (zRet?zRet:""), (zRet?" AND ":""), pTab->aCol[nEq].zName);
+  }
+
+  if( zRet ){
+    zRet = sqlite3MAppendf(db, zRet, " (%s)", zRet);
+  }
+
+  return zRet;
+}
+
+static void codeOneLoopExplain(
+  Parse *pParse,                  /* Parse context */
+  SrcList *pTabList,              /* Table list this loop refers to */
+  WhereLevel *pLevel,             /* Scan to write OP_Explain opcode for */
+  int iLevel,                     /* Value for "level" column of output */
+  int iFrom                       /* Value for "from" column of output */
+){
+  if( pParse->explain==2 ){
+    u32 flags = pLevel->plan.wsFlags;
+    struct SrcList_item *pItem = &pTabList->a[pLevel->iFrom];
+    Vdbe *v = pParse->pVdbe;
+    sqlite3 *db = pParse->db;
+    char *zMsg;
+
+    if( flags & WHERE_MULTI_OR ) return;
+
+    zMsg = sqlite3MPrintf(db, "TABLE %s", pItem->zName);
+    if( pItem->zAlias ){
+      zMsg = sqlite3MAppendf(db, zMsg, "%s AS %s", zMsg, pItem->zAlias);
+    }
+    if( (flags & WHERE_INDEXED)!=0 ){
+      char *zWhere = indexRangeText(db, pLevel, pItem->pTab);
+      zMsg = sqlite3MAppendf(db, zMsg, "%s WITH %s%sINDEX%s%s%s", zMsg, 
+          ((flags & WHERE_TEMP_INDEX)?"AUTOMATIC ":""),
+          ((flags & WHERE_IDX_ONLY)?"COVERING ":""),
+          ((flags & WHERE_TEMP_INDEX)?"":" "),
+          ((flags & WHERE_TEMP_INDEX)?"": pLevel->plan.u.pIdx->zName),
+          zWhere
+      );
+      sqlite3DbFree(db, zWhere);
+    }else if( flags & (WHERE_ROWID_EQ|WHERE_ROWID_RANGE) ){
+      zMsg = sqlite3MAppendf(db, zMsg, "%s USING INTEGER PRIMARY KEY", zMsg);
+
+      if( flags&WHERE_ROWID_EQ ){
+        zMsg = sqlite3MAppendf(db, zMsg, "%s (rowid=?)", zMsg);
+      }else if( flags&WHERE_BTM_LIMIT && flags&WHERE_TOP_LIMIT ){
+        zMsg = sqlite3MAppendf(db, zMsg, "%s (rowid>? AND rowid<?)", zMsg);
+      }else if( flags&WHERE_BTM_LIMIT ){
+        zMsg = sqlite3MAppendf(db, zMsg, "%s (rowid>?)", zMsg);
+      }else if( flags&WHERE_TOP_LIMIT ){
+        zMsg = sqlite3MAppendf(db, zMsg, "%s (rowid<?)", zMsg);
+      }
+    }
+#ifndef SQLITE_OMIT_VIRTUALTABLE
+    else if( (flags & WHERE_VIRTUALTABLE)!=0 ){
+      sqlite3_index_info *pVtabIdx = pLevel->plan.u.pVtabIdx;
+      zMsg = sqlite3MAppendf(db, zMsg, "%s VIRTUAL TABLE INDEX %d:%s", zMsg,
+                  pVtabIdx->idxNum, pVtabIdx->idxStr);
+    }
+#endif
+    zMsg = sqlite3MAppendf(db, zMsg, 
+        "%s (~%lld rows)", zMsg, (sqlite3_int64)(pLevel->plan.nRow)
+    );
+    sqlite3VdbeAddOp4(
+        v, OP_Explain, pParse->iSelectId, iLevel, iFrom, zMsg, P4_DYNAMIC);
+  }
+}
+#else
+# define codeOneLoopExplain(w,x,y.z)
+#endif /* SQLITE_OMIT_EXPLAIN */
+
+
 /*
 ** Generate code for the start of the iLevel-th loop in the WHERE clause
 ** implementation described by pWInfo.
@@ -3672,6 +3763,9 @@ static Bitmask codeOneLoopStart(
                         WHERE_OMIT_OPEN | WHERE_OMIT_CLOSE |
                         WHERE_FORCE_TABLE | WHERE_ONETABLE_ONLY);
         if( pSubWInfo ){
+          codeOneLoopExplain(
+              pParse, pOrTab, &pSubWInfo->a[0], iLevel, pLevel->iFrom
+          );
           if( (wctrlFlags & WHERE_DUPLICATES_OK)==0 ){
             int iSet = ((ii==pOrWc->nTerm-1)?-1:ii);
             int r;
@@ -4184,11 +4278,12 @@ WhereInfo *sqlite3WhereBegin(
             && (nUnconstrained==0 || pTabItem->pIndex==0   /* (3) */
                 || NEVER((sCost.plan.wsFlags & WHERE_NOT_FULLSCAN)!=0))
             && (bestJ<0 || sCost.rCost<bestPlan.rCost      /* (4) */
-                || (sCost.rCost<=bestPlan.rCost && sCost.nRow<bestPlan.nRow))
+                || (sCost.rCost<=bestPlan.rCost 
+                 && sCost.plan.nRow<bestPlan.plan.nRow))
         ){
           WHERETRACE(("=== table %d is best so far"
                       " with cost=%g and nRow=%g\n",
-                      j, sCost.rCost, sCost.nRow));
+                      j, sCost.rCost, sCost.plan.nRow));
           bestPlan = sCost;
           bestJ = j;
         }
@@ -4199,7 +4294,7 @@ WhereInfo *sqlite3WhereBegin(
     assert( notReady & getMask(pMaskSet, pTabList->a[bestJ].iCursor) );
     WHERETRACE(("*** Optimizer selects table %d for loop %d"
                 " with cost=%g and nRow=%g\n",
-                bestJ, pLevel-pWInfo->a, bestPlan.rCost, bestPlan.nRow));
+                bestJ, pLevel-pWInfo->a, bestPlan.rCost, bestPlan.plan.nRow));
     if( (bestPlan.plan.wsFlags & WHERE_ORDERBY)!=0 ){
       *ppOrderBy = 0;
     }
@@ -4214,7 +4309,9 @@ WhereInfo *sqlite3WhereBegin(
     }
     notReady &= ~getMask(pMaskSet, pTabList->a[bestJ].iCursor);
     pLevel->iFrom = (u8)bestJ;
-    if( bestPlan.nRow>=(double)1 ) pParse->nQueryLoop *= bestPlan.nRow;
+    if( bestPlan.plan.nRow>=(double)1 ){
+      pParse->nQueryLoop *= bestPlan.plan.nRow;
+    }
 
     /* Check that if the table scanned by this loop iteration had an
     ** INDEXED BY clause attached to it, that the named index is being
@@ -4266,37 +4363,6 @@ WhereInfo *sqlite3WhereBegin(
     Table *pTab;     /* Table to open */
     int iDb;         /* Index of database containing table/index */
 
-#ifndef SQLITE_OMIT_EXPLAIN
-    if( pParse->explain==2 ){
-      char *zMsg;
-      struct SrcList_item *pItem = &pTabList->a[pLevel->iFrom];
-      zMsg = sqlite3MPrintf(db, "TABLE %s", pItem->zName);
-      if( pItem->zAlias ){
-        zMsg = sqlite3MAppendf(db, zMsg, "%s AS %s", zMsg, pItem->zAlias);
-      }
-      if( (pLevel->plan.wsFlags & WHERE_TEMP_INDEX)!=0 ){
-        zMsg = sqlite3MAppendf(db, zMsg, "%s WITH AUTOMATIC INDEX", zMsg);
-      }else if( (pLevel->plan.wsFlags & WHERE_INDEXED)!=0 ){
-        zMsg = sqlite3MAppendf(db, zMsg, "%s WITH INDEX %s",
-           zMsg, pLevel->plan.u.pIdx->zName);
-      }else if( pLevel->plan.wsFlags & WHERE_MULTI_OR ){
-        zMsg = sqlite3MAppendf(db, zMsg, "%s VIA MULTI-INDEX UNION", zMsg);
-      }else if( pLevel->plan.wsFlags & (WHERE_ROWID_EQ|WHERE_ROWID_RANGE) ){
-        zMsg = sqlite3MAppendf(db, zMsg, "%s USING PRIMARY KEY", zMsg);
-      }
-#ifndef SQLITE_OMIT_VIRTUALTABLE
-      else if( (pLevel->plan.wsFlags & WHERE_VIRTUALTABLE)!=0 ){
-        sqlite3_index_info *pVtabIdx = pLevel->plan.u.pVtabIdx;
-        zMsg = sqlite3MAppendf(db, zMsg, "%s VIRTUAL TABLE INDEX %d:%s", zMsg,
-                    pVtabIdx->idxNum, pVtabIdx->idxStr);
-      }
-#endif
-      if( pLevel->plan.wsFlags & WHERE_ORDERBY ){
-        zMsg = sqlite3MAppendf(db, zMsg, "%s ORDER BY", zMsg);
-      }
-      sqlite3VdbeAddOp4(v, OP_Explain, i, pLevel->iFrom, 0, zMsg, P4_DYNAMIC);
-    }
-#endif /* SQLITE_OMIT_EXPLAIN */
     pTabItem = &pTabList->a[pLevel->iFrom];
     pTab = pTabItem->pTab;
     pLevel->iTabCur = pTabItem->iCursor;
@@ -4355,6 +4421,9 @@ WhereInfo *sqlite3WhereBegin(
   */
   notReady = ~(Bitmask)0;
   for(i=0; i<nTabList; i++){
+    if( (wctrlFlags&WHERE_ONETABLE_ONLY)==0 ){
+      codeOneLoopExplain(pParse, pTabList, &pWInfo->a[i],i,pWInfo->a[i].iFrom);
+    }
     notReady = codeOneLoopStart(pWInfo, i, wctrlFlags, notReady);
     pWInfo->iContinue = pWInfo->a[i].addrCont;
   }
diff --git a/test/eqp.test b/test/eqp.test
new file mode 100644 (file)
index 0000000..6e56e74
--- /dev/null
@@ -0,0 +1,193 @@
+# 2010 November 6
+#
+# 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 eqp
+
+#-------------------------------------------------------------------------
+#
+# eqp-1.*:        Assorted tests.
+# eqp-2.*:        Tests for single select statements.
+# eqp-3.*:        Select statements that execute sub-selects.
+# eqp-4.*:        Compound select statements.
+#
+
+proc do_eqp_test {name sql res} {
+  set res [list {*}$res]
+  uplevel do_execsql_test $name [list "EXPLAIN QUERY PLAN $sql"] [list $res]
+}
+
+do_execsql_test 1.1 {
+  CREATE TABLE t1(a, b);
+  CREATE INDEX i1 ON t1(a);
+  CREATE INDEX i2 ON t1(b);
+  CREATE TABLE t2(a, b);
+  CREATE TABLE t3(a, b);
+}
+
+do_eqp_test 1.2 {
+  SELECT * FROM t2, t1 WHERE t1.a=1 OR t1.b=2;
+} {
+  0 0 1 {TABLE t1 WITH INDEX i1 (a=?) (~10 rows)} 
+  0 0 1 {TABLE t1 WITH INDEX i2 (b=?) (~10 rows)} 
+  0 1 0 {TABLE t2 (~1000000 rows)}
+}
+do_eqp_test 1.3 {
+  SELECT * FROM t2 CROSS JOIN t1 WHERE t1.a=1 OR t1.b=2;
+} {
+  0 0 0 {TABLE t2 (~1000000 rows)}
+  0 1 1 {TABLE t1 WITH INDEX i1 (a=?) (~10 rows)} 
+  0 1 1 {TABLE t1 WITH INDEX i2 (b=?) (~10 rows)} 
+}
+do_eqp_test 1.3 {
+  SELECT a FROM t1 ORDER BY a
+} {
+  0 0 0 {TABLE t1 WITH COVERING INDEX i1 (~1000000 rows)}
+}
+do_eqp_test 1.4 {
+  SELECT a FROM t1 ORDER BY +a
+} {
+  0 0 0 {TABLE t1 (~1000000 rows)}
+  0 0 0 {USE TEMP B-TREE FOR ORDER BY}
+}
+do_eqp_test 1.5 {
+  SELECT a FROM t1 WHERE a=4
+} {
+  0 0 0 {TABLE t1 WITH COVERING INDEX i1 (a=?) (~10 rows)}
+}
+do_eqp_test 1.6 {
+  SELECT DISTINCT count(*) FROM t3 GROUP BY a;
+} {
+  0 0 0 {TABLE t3 (~1000000 rows)}
+  0 0 0 {USE TEMP B-TREE FOR GROUP BY}
+  0 0 0 {USE TEMP B-TREE FOR DISTINCT}
+}
+
+#-------------------------------------------------------------------------
+# Test cases eqp-2.* - tests for single select statements.
+#
+drop_all_tables
+do_execsql_test 2.1 {
+  CREATE TABLE t1(x, y);
+
+  CREATE TABLE t2(x, y);
+  CREATE INDEX t2i1 ON t2(x);
+}
+
+do_eqp_test 2.2.1 {
+  SELECT DISTINCT min(x), max(x) FROM t1 GROUP BY x ORDER BY 1
+} {
+  0 0 0 {TABLE t1 (~1000000 rows)}
+  0 0 0 {USE TEMP B-TREE FOR GROUP BY}
+  0 0 0 {USE TEMP B-TREE FOR DISTINCT}
+  0 0 0 {USE TEMP B-TREE FOR ORDER BY}
+}
+
+do_eqp_test 2.2.2 {
+  SELECT DISTINCT min(x), max(x) FROM t2 GROUP BY x ORDER BY 1
+} {
+  0 0 0 {TABLE t2 WITH COVERING INDEX t2i1 (~1000000 rows)}
+  0 0 0 {USE TEMP B-TREE FOR DISTINCT}
+  0 0 0 {USE TEMP B-TREE FOR ORDER BY}
+}
+
+#-------------------------------------------------------------------------
+# Test cases eqp-3.* - tests for select statements that use sub-selects.
+#
+do_eqp_test 3.1.1 {
+  SELECT (SELECT x FROM t1 AS sub) FROM t1;
+} {
+  0 0 0 {TABLE t1 (~1000000 rows)} 
+  1 0 0 {TABLE t1 AS sub (~1000000 rows)}
+}
+
+#-------------------------------------------------------------------------
+# Test cases eqp-4.* - tests for select statements that use sub-selects.
+#
+do_eqp_test 4.1.1 {
+  SELECT * FROM t1 UNION ALL SELECT * FROM t2
+} {
+  1 0 0 {TABLE t1 (~1000000 rows)} 
+  2 0 0 {TABLE t2 (~1000000 rows)} 
+}
+do_eqp_test 4.1.2 {
+  SELECT * FROM t1 UNION ALL SELECT * FROM t2 ORDER BY 2
+} {
+  1 0 0 {TABLE t1 (~1000000 rows)} 
+  1 0 0 {USE TEMP B-TREE FOR ORDER BY}
+  2 0 0 {TABLE t2 (~1000000 rows)} 
+  2 0 0 {USE TEMP B-TREE FOR ORDER BY}
+}
+do_eqp_test 4.1.3 {
+  SELECT * FROM t1 UNION SELECT * FROM t2 ORDER BY 2
+} {
+  1 0 0 {TABLE t1 (~1000000 rows)} 
+  1 0 0 {USE TEMP B-TREE FOR ORDER BY}
+  2 0 0 {TABLE t2 (~1000000 rows)} 
+  2 0 0 {USE TEMP B-TREE FOR ORDER BY}
+}
+do_eqp_test 4.1.4 {
+  SELECT * FROM t1 INTERSECT SELECT * FROM t2 ORDER BY 2
+} {
+  1 0 0 {TABLE t1 (~1000000 rows)} 
+  1 0 0 {USE TEMP B-TREE FOR ORDER BY}
+  2 0 0 {TABLE t2 (~1000000 rows)} 
+  2 0 0 {USE TEMP B-TREE FOR ORDER BY}
+}
+do_eqp_test 4.1.5 {
+  SELECT * FROM t1 EXCEPT SELECT * FROM t2 ORDER BY 2
+} {
+  1 0 0 {TABLE t1 (~1000000 rows)} 
+  1 0 0 {USE TEMP B-TREE FOR ORDER BY}
+  2 0 0 {TABLE t2 (~1000000 rows)} 
+  2 0 0 {USE TEMP B-TREE FOR ORDER BY}
+}
+
+do_eqp_test 4.2.2 {
+  SELECT * FROM t1 UNION ALL SELECT * FROM t2 ORDER BY 1
+} {
+  1 0 0 {TABLE t1 (~1000000 rows)} 
+  1 0 0 {USE TEMP B-TREE FOR ORDER BY}
+  2 0 0 {TABLE t2 WITH INDEX t2i1 (~1000000 rows)} 
+}
+
+# Todo: Why are the following not the same as the UNION ALL case above?
+do_eqp_test 4.2.3 {
+  SELECT * FROM t1 UNION SELECT * FROM t2 ORDER BY 1
+} {
+  1 0 0 {TABLE t1 (~1000000 rows)} 
+  1 0 0 {USE TEMP B-TREE FOR ORDER BY}
+  2 0 0 {TABLE t2 (~1000000 rows)} 
+  2 0 0 {USE TEMP B-TREE FOR ORDER BY}
+}
+do_eqp_test 4.2.4 {
+  SELECT * FROM t1 INTERSECT SELECT * FROM t2 ORDER BY 1
+} {
+  1 0 0 {TABLE t1 (~1000000 rows)} 
+  1 0 0 {USE TEMP B-TREE FOR ORDER BY}
+  2 0 0 {TABLE t2 (~1000000 rows)} 
+  2 0 0 {USE TEMP B-TREE FOR ORDER BY}
+}
+do_eqp_test 4.2.5 {
+  SELECT * FROM t1 EXCEPT SELECT * FROM t2 ORDER BY 1
+} {
+  1 0 0 {TABLE t1 (~1000000 rows)} 
+  1 0 0 {USE TEMP B-TREE FOR ORDER BY}
+  2 0 0 {TABLE t2 (~1000000 rows)} 
+  2 0 0 {USE TEMP B-TREE FOR ORDER BY}
+}
+
+
+finish_test
+