]> git.ipfire.org Git - thirdparty/sqlite.git/commitdiff
Change the way SQLite invokes the xBestIndex method of virtual tables so that N-way...
authordan <dan@noemail.net>
Sat, 5 Mar 2016 17:29:08 +0000 (17:29 +0000)
committerdan <dan@noemail.net>
Sat, 5 Mar 2016 17:29:08 +0000 (17:29 +0000)
FossilOrigin-Name: ffc65968ede2c402e616147e6e3d737e6f9de21d

manifest
manifest.uuid
src/test_bestindex.c
src/where.c
test/bestindex2.test [new file with mode: 0644]

index 10ffbe9dbe5157c51a2ee95ac41f1bc90a2f5249..f9803d8323df3a1b83dc9671ef0157da3896d00e 100644 (file)
--- a/manifest
+++ b/manifest
@@ -1,5 +1,5 @@
-C Remove\sthe\sunused\sjournal.c\ssource\sfile\s(its\sfunction\shave\sbeen\ssubsumed\sinto\nmemjournal.c).\s\sRefactor\ssome\sof\sthe\snames\sin\smemjournal.c.\s\sNo\sfunctional\nchanges.
-D 2016-03-05T15:35:09.793
+C Change\sthe\sway\sSQLite\sinvokes\sthe\sxBestIndex\smethod\sof\svirtual\stables\sso\sthat\sN-way\sjoins\sinvolving\svirtual\stables\swork\sas\sexpected.
+D 2016-03-05T17:29:08.396
 F Makefile.in f53429fb2f313c099283659d0df6f20f932c861f
 F Makefile.linux-gcc 7bc79876b875010e8c8f9502eb935ca92aa3c434
 F Makefile.msc df0bf9ff7f8b3f4dd9fb4cc43f92fe58f6ec5c66
@@ -370,7 +370,7 @@ F src/test9.c bea1e8cf52aa93695487badedd6e1886c321ea60
 F src/test_async.c 21e11293a2f72080eda70e1124e9102044531cd8
 F src/test_autoext.c dea8a01a7153b9adc97bd26161e4226329546e12
 F src/test_backup.c 2e6e6a081870150f20c526a2e9d0d29cda47d803
-F src/test_bestindex.c 1da6fa21ac9413e8985a698b962541e615a9f311
+F src/test_bestindex.c 29af3cc3b963ffe5760c85d142b9b3e5302c1e3d
 F src/test_blob.c b2551a9b5573232db5f66f292307c37067937239
 F src/test_btree.c 2e9978eca99a9a4bfa8cae949efb00886860a64f
 F src/test_config.c 0dee90328e3dedf8ba002ee94b6a7e7ea7726fe4
@@ -428,7 +428,7 @@ F src/vxworks.h d2988f4e5a61a4dfe82c6524dd3d6e4f2ce3cdb9
 F src/wal.c 10deb6b43887662691e5f53d10b3c171c401169b
 F src/wal.h 2f7c831cf3b071fa548bf2d5cac640846a7ff19c
 F src/walker.c 0f142b5bd3ed2041fc52d773880748b212e63354
-F src/where.c 56948ada5aacc3bf2628db3776986e8bf4085383
+F src/where.c bada38bf45ea4437318164f915243b3f0822ceda
 F src/whereInt.h 93297d56edd137b7ea004490690fb6e2ce028a34
 F src/wherecode.c 3ca820435c5b597bb50e63ed11e938786fe5c23e
 F src/whereexpr.c fb87944b1254234e5bba671aaf6dee476241506a
@@ -493,6 +493,7 @@ F test/badutf.test d5360fc31f643d37a973ab0d8b4fb85799c3169f
 F test/badutf2.test f5bc7f2d280670ecd79b9cf4f0f1760c607fe51f
 F test/bc_common.tcl 3eda41ef9cda7d5f6c205462c96228b301da4191
 F test/bestindex1.test e228fe1e3794dbe20271481164e000d695abcd24
+F test/bestindex2.test b5c1fbcf7a6e73b22763445262399c9fc50276bc
 F test/between.test 34d375fb5ce1ae283ffe82b6b233e9f38e84fc6c
 F test/bigfile.test aa74f4e5db51c8e54a1d9de9fa65d01d1eb20b59
 F test/bigfile2.test 1b489a3a39ae90c7f027b79110d6b4e1dbc71bfc
@@ -1452,7 +1453,10 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93
 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc
 F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e
 F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0
-P 4de09777dad6188b7e897473700af3c9655e8547
-R 323dedc301dba3a5e5eae684c7c211b0
-U drh
-Z 93b1612ed0c564573b8e6748ccaa7792
+P 5f2a262d3f6b1531001326faf1d3b3d92c20a30a
+R 5392a3186cae79bf12c69e9ed8cd8a4b
+T *branch * xbestindex-fix
+T *sym-xbestindex-fix *
+T -sym-trunk *
+U dan
+Z 7116cca1dca1d43002adf5a35b7161d9
index 2cc497c082c15b13972b95d47f49ae80f89935ca..28b8b67ddf9ffabbdd13fbe34981f0f57a49c18a 100644 (file)
@@ -1 +1 @@
-5f2a262d3f6b1531001326faf1d3b3d92c20a30a
\ No newline at end of file
+ffc65968ede2c402e616147e6e3d737e6f9de21d
\ No newline at end of file
index abbdf1a02b2eb759e1357d85bd4a2e1acc13b1f8..a955c2782c675cc3dcb1e052a237139cf6962767 100644 (file)
@@ -116,6 +116,40 @@ struct tcl_cursor {
   sqlite3_stmt *pStmt;            /* Read data from here */
 };
 
+/*
+** Dequote string z in place.
+*/
+static void tclDequote(char *z){
+  char q = z[0];
+
+  /* Set stack variable q to the close-quote character */
+  if( q=='[' || q=='\'' || q=='"' || q=='`' ){
+    int iIn = 1;
+    int iOut = 0;
+    if( q=='[' ) q = ']';  
+
+    while( ALWAYS(z[iIn]) ){
+      if( z[iIn]==q ){
+        if( z[iIn+1]!=q ){
+          /* Character iIn was the close quote. */
+          iIn++;
+          break;
+        }else{
+          /* Character iIn and iIn+1 form an escaped quote character. Skip
+          ** the input cursor past both and copy a single quote character 
+          ** to the output buffer. */
+          iIn += 2;
+          z[iOut++] = q;
+        }
+      }else{
+        z[iOut++] = z[iIn++];
+      }
+    }
+
+    z[iOut] = '\0';
+  }
+}
+
 /*
 ** This function is the implementation of both the xConnect and xCreate
 ** methods of the fs virtual table.
@@ -135,43 +169,49 @@ static int tclConnect(
   char **pzErr
 ){
   Tcl_Interp *interp = (Tcl_Interp*)pAux;
-  tcl_vtab *pTab;
-  const char *zCmd;
+  tcl_vtab *pTab = 0;
+  char *zCmd = 0;
   Tcl_Obj *pScript = 0;
-  int rc;
+  int rc = SQLITE_OK;
 
   if( argc!=4 ){
     *pzErr = sqlite3_mprintf("wrong number of arguments");
     return SQLITE_ERROR;
   }
-  zCmd = argv[3];
 
+  zCmd = sqlite3_malloc(strlen(argv[3])+1);
   pTab = (tcl_vtab*)sqlite3_malloc(sizeof(tcl_vtab));
-  if( pTab==0 ) return SQLITE_NOMEM;
-  memset(pTab, 0, sizeof(tcl_vtab));
+  if( zCmd && pTab ){
+    memcpy(zCmd, argv[3], strlen(argv[3])+1);
+    tclDequote(zCmd);
+    memset(pTab, 0, sizeof(tcl_vtab));
 
-  pTab->pCmd = Tcl_NewStringObj(zCmd, -1);
-  pTab->interp = interp;
-  pTab->db = db;
-  Tcl_IncrRefCount(pTab->pCmd);
+    pTab->pCmd = Tcl_NewStringObj(zCmd, -1);
+    pTab->interp = interp;
+    pTab->db = db;
+    Tcl_IncrRefCount(pTab->pCmd);
 
-  pScript = Tcl_DuplicateObj(pTab->pCmd);
-  Tcl_IncrRefCount(pScript);
-  Tcl_ListObjAppendElement(interp, pScript, Tcl_NewStringObj("xConnect", -1));
+    pScript = Tcl_DuplicateObj(pTab->pCmd);
+    Tcl_IncrRefCount(pScript);
+    Tcl_ListObjAppendElement(interp, pScript, Tcl_NewStringObj("xConnect", -1));
 
-  rc = Tcl_EvalObjEx(interp, pScript, TCL_EVAL_GLOBAL);
-  if( rc!=TCL_OK ){
-    *pzErr = sqlite3_mprintf("%s", Tcl_GetStringResult(interp));
-    rc = SQLITE_ERROR;
-  }else{
-    rc = sqlite3_declare_vtab(db, Tcl_GetStringResult(interp));
-  }
+    rc = Tcl_EvalObjEx(interp, pScript, TCL_EVAL_GLOBAL);
+    if( rc!=TCL_OK ){
+      *pzErr = sqlite3_mprintf("%s", Tcl_GetStringResult(interp));
+      rc = SQLITE_ERROR;
+    }else{
+      rc = sqlite3_declare_vtab(db, Tcl_GetStringResult(interp));
+    }
 
-  if( rc!=SQLITE_OK ){
-    sqlite3_free(pTab);
-    pTab = 0;
+    if( rc!=SQLITE_OK ){
+      sqlite3_free(pTab);
+      pTab = 0;
+    }
+  }else{
+    rc = SQLITE_NOMEM;
   }
 
+  sqlite3_free(zCmd);
   *ppVtab = &pTab->base;
   return rc;
 }
index ed90f6144330dc623e678ce9efce1f22c288e493..07936c59147a090b37373f384f071e00c648bac8 100644 (file)
@@ -2748,6 +2748,151 @@ static int whereLoopAddBtree(
 }
 
 #ifndef SQLITE_OMIT_VIRTUALTABLE
+
+/*
+** Argument pIdxInfo is already populated with all constraints that may
+** be used by the virtual table identified by pBuilder->pNew->iTab. This
+** function marks a subset of those constraints usable, invokes the
+** xBestIndex method and adds the returned plan to pBuilder.
+**
+** A constraint is marked usable if:
+**
+**   * Argument mUsable indicates that its prerequisites are available, and
+**
+**   * It is not one of the operators specified in the mExclude mask passed
+**     as the fourth argument (which in practice is either WO_IN or 0).
+**
+** Argument mExtra is a mask of tables that must be scanned before the
+** virtual table in question. These are added to the plans prerequisites
+** before it is added to pBuilder.
+**
+** Output parameter *pbIn is set to true if the plan added to pBuilder
+** uses one or more WO_IN terms, or false otherwise.
+*/
+static int whereLoopAddVirtualOne(
+  WhereLoopBuilder *pBuilder,
+  Bitmask mExtra,                 /* Mask of tables that must be used. */
+  Bitmask mUsable,                /* Mask of usable prereqs */
+  u16 mExclude,                   /* Exclude terms for this operator */
+  sqlite3_index_info *pIdxInfo,   /* Populated object for xBestIndex */
+  int *pbIn                       /* OUT: True if plan uses an IN(...) op */
+){
+  WhereClause *pWC = pBuilder->pWC;
+  struct sqlite3_index_constraint *pIdxCons;
+  struct sqlite3_index_constraint_usage *pUsage = pIdxInfo->aConstraintUsage;
+  int i;
+  int mxTerm;
+  int rc = SQLITE_OK;
+  WhereLoop *pNew = pBuilder->pNew;
+  Parse *pParse = pBuilder->pWInfo->pParse;
+  struct SrcList_item *pSrc = &pBuilder->pWInfo->pTabList->a[pNew->iTab];
+  int nConstraint = pIdxInfo->nConstraint;
+
+  assert( (mUsable & mExtra)==mExtra );
+  *pbIn = 0;
+  pNew->prereq = mExtra;
+
+  /* Set the usable flag on the subset of constraints identified by 
+  ** arguments mUsable and mExclude. */
+  pIdxCons = *(struct sqlite3_index_constraint**)&pIdxInfo->aConstraint;
+  for(i=0; i<nConstraint; i++, pIdxCons++){
+    WhereTerm *pTerm = &pWC->a[pIdxCons->iTermOffset];
+    pIdxCons->usable = 0;
+    if( (pTerm->prereqRight & mUsable)==pTerm->prereqRight 
+     && (pTerm->eOperator & mExclude)==0
+    ){
+      pIdxCons->usable = 1;
+    }
+  }
+
+  /* Initialize the output fields of the sqlite3_index_info structure */
+  memset(pUsage, 0, sizeof(pUsage[0])*nConstraint);
+  if( pIdxInfo->needToFreeIdxStr ) sqlite3_free(pIdxInfo->idxStr);
+  pIdxInfo->idxStr = 0;
+  pIdxInfo->idxNum = 0;
+  pIdxInfo->needToFreeIdxStr = 0;
+  pIdxInfo->orderByConsumed = 0;
+  pIdxInfo->estimatedCost = SQLITE_BIG_DBL / (double)2;
+  pIdxInfo->estimatedRows = 25;
+  pIdxInfo->idxFlags = 0;
+  pIdxInfo->colUsed = (sqlite3_int64)pSrc->colUsed;
+
+  /* Invoke the virtual table xBestIndex() method */
+  rc = vtabBestIndex(pParse, pSrc->pTab, pIdxInfo);
+  if( rc ) return rc;
+
+  mxTerm = -1;
+  assert( pNew->nLSlot>=nConstraint );
+  for(i=0; i<nConstraint; i++) pNew->aLTerm[i] = 0;
+  pNew->u.vtab.omitMask = 0;
+  pIdxCons = *(struct sqlite3_index_constraint**)&pIdxInfo->aConstraint;
+  for(i=0; i<nConstraint; i++, pIdxCons++){
+    int iTerm;
+    if( (iTerm = pUsage[i].argvIndex - 1)>=0 ){
+      WhereTerm *pTerm;
+      int j = pIdxCons->iTermOffset;
+      if( iTerm>=nConstraint
+       || j<0
+       || j>=pWC->nTerm
+       || pNew->aLTerm[iTerm]!=0
+      ){
+        rc = SQLITE_ERROR;
+        sqlite3ErrorMsg(pParse,"%s.xBestIndex() malfunction",pSrc->pTab->zName);
+        return rc;
+      }
+      testcase( iTerm==nConstraint-1 );
+      testcase( j==0 );
+      testcase( j==pWC->nTerm-1 );
+      pTerm = &pWC->a[j];
+      pNew->prereq |= pTerm->prereqRight;
+      assert( iTerm<pNew->nLSlot );
+      pNew->aLTerm[iTerm] = pTerm;
+      if( iTerm>mxTerm ) mxTerm = iTerm;
+      testcase( iTerm==15 );
+      testcase( iTerm==16 );
+      if( iTerm<16 && pUsage[i].omit ) pNew->u.vtab.omitMask |= 1<<iTerm;
+      if( (pTerm->eOperator & WO_IN)!=0 ){
+        /* A virtual table that is constrained by an IN clause may not
+        ** consume the ORDER BY clause because (1) the order of IN terms
+        ** is not necessarily related to the order of output terms and
+        ** (2) Multiple outputs from a single IN value will not merge
+        ** together.  */
+        pIdxInfo->orderByConsumed = 0;
+        pIdxInfo->idxFlags &= ~SQLITE_INDEX_SCAN_UNIQUE;
+        *pbIn = 1;
+      }
+    }
+  }
+
+  pNew->nLTerm = mxTerm+1;
+  assert( pNew->nLTerm<=pNew->nLSlot );
+  pNew->u.vtab.idxNum = pIdxInfo->idxNum;
+  pNew->u.vtab.needFree = pIdxInfo->needToFreeIdxStr;
+  pIdxInfo->needToFreeIdxStr = 0;
+  pNew->u.vtab.idxStr = pIdxInfo->idxStr;
+  pNew->u.vtab.isOrdered = (i8)(pIdxInfo->orderByConsumed ?
+      pIdxInfo->nOrderBy : 0);
+  pNew->rSetup = 0;
+  pNew->rRun = sqlite3LogEstFromDouble(pIdxInfo->estimatedCost);
+  pNew->nOut = sqlite3LogEst(pIdxInfo->estimatedRows);
+
+  /* Set the WHERE_ONEROW flag if the xBestIndex() method indicated
+  ** that the scan will visit at most one row. Clear it otherwise. */
+  if( pIdxInfo->idxFlags & SQLITE_INDEX_SCAN_UNIQUE ){
+    pNew->wsFlags |= WHERE_ONEROW;
+  }else{
+    pNew->wsFlags &= ~WHERE_ONEROW;
+  }
+  whereLoopInsert(pBuilder, pNew);
+  if( pNew->u.vtab.needFree ){
+    sqlite3_free(pNew->u.vtab.idxStr);
+    pNew->u.vtab.needFree = 0;
+  }
+
+  return SQLITE_OK;
+}
+
+
 /*
 ** Add all WhereLoop objects for a table of the join identified by
 ** pBuilder->pNew->iTab.  That table is guaranteed to be a virtual table.
@@ -2778,167 +2923,102 @@ static int whereLoopAddVirtual(
   Bitmask mExtra,              /* Tables that must be scanned before this one */
   Bitmask mUnusable            /* Tables that must be scanned after this one */
 ){
+  int rc = SQLITE_OK;          /* Return code */
   WhereInfo *pWInfo;           /* WHERE analysis context */
   Parse *pParse;               /* The parsing context */
   WhereClause *pWC;            /* The WHERE clause */
   struct SrcList_item *pSrc;   /* The FROM clause term to search */
-  Table *pTab;
-  sqlite3 *db;
-  sqlite3_index_info *pIdxInfo;
-  struct sqlite3_index_constraint *pIdxCons;
-  struct sqlite3_index_constraint_usage *pUsage;
-  WhereTerm *pTerm;
-  int i, j;
-  int iTerm, mxTerm;
-  int nConstraint;
-  int seenIn = 0;              /* True if an IN operator is seen */
-  int seenVar = 0;             /* True if a non-constant constraint is seen */
-  int iPhase;                  /* 0: const w/o IN, 1: const, 2: no IN,  2: IN */
+  sqlite3_index_info *p;       /* Object to pass to xBestIndex() */
+  int nConstraint;             /* Number of constraints in p */
+  int bIn;                     /* True if plan uses IN(...) operator */
   WhereLoop *pNew;
-  int rc = SQLITE_OK;
+  Bitmask mBest;               /* Tables used by best possible plan */
 
   assert( (mExtra & mUnusable)==0 );
   pWInfo = pBuilder->pWInfo;
   pParse = pWInfo->pParse;
-  db = pParse->db;
   pWC = pBuilder->pWC;
   pNew = pBuilder->pNew;
   pSrc = &pWInfo->pTabList->a[pNew->iTab];
-  pTab = pSrc->pTab;
-  assert( IsVirtual(pTab) );
-  pIdxInfo = allocateIndexInfo(pParse, pWC, mUnusable, pSrc,pBuilder->pOrderBy);
-  if( pIdxInfo==0 ) return SQLITE_NOMEM_BKPT;
-  pNew->prereq = 0;
+  assert( IsVirtual(pSrc->pTab) );
+  p = allocateIndexInfo(pParse, pWC, mUnusable, pSrc,pBuilder->pOrderBy);
+  if( p==0 ) return SQLITE_NOMEM_BKPT;
   pNew->rSetup = 0;
   pNew->wsFlags = WHERE_VIRTUALTABLE;
   pNew->nLTerm = 0;
   pNew->u.vtab.needFree = 0;
-  pUsage = pIdxInfo->aConstraintUsage;
-  nConstraint = pIdxInfo->nConstraint;
-  if( whereLoopResize(db, pNew, nConstraint) ){
-    sqlite3DbFree(db, pIdxInfo);
+  nConstraint = p->nConstraint;
+  if( whereLoopResize(pParse->db, pNew, nConstraint) ){
+    sqlite3DbFree(pParse->db, p);
     return SQLITE_NOMEM_BKPT;
   }
 
-  for(iPhase=0; iPhase<=3; iPhase++){
-    if( !seenIn && (iPhase&1)!=0 ){
-      iPhase++;
-      if( iPhase>3 ) break;
-    }
-    if( !seenVar && iPhase>1 ) break;
-    pIdxCons = *(struct sqlite3_index_constraint**)&pIdxInfo->aConstraint;
-    for(i=0; i<pIdxInfo->nConstraint; i++, pIdxCons++){
-      j = pIdxCons->iTermOffset;
-      pTerm = &pWC->a[j];
-      switch( iPhase ){
-        case 0:    /* Constants without IN operator */
-          pIdxCons->usable = 0;
-          if( (pTerm->eOperator & WO_IN)!=0 ){
-            seenIn = 1;
-          }
-          if( (pTerm->prereqRight & ~mExtra)!=0 ){
-            seenVar = 1;
-          }else if( (pTerm->eOperator & WO_IN)==0 ){
-            pIdxCons->usable = 1;
-          }
-          break;
-        case 1:    /* Constants with IN operators */
-          assert( seenIn );
-          pIdxCons->usable = (pTerm->prereqRight & ~mExtra)==0;
-          break;
-        case 2:    /* Variables without IN */
-          assert( seenVar );
-          pIdxCons->usable = (pTerm->eOperator & WO_IN)==0;
-          break;
-        default:   /* Variables with IN */
-          assert( seenVar && seenIn );
-          pIdxCons->usable = 1;
-          break;
-      }
-    }
-    memset(pUsage, 0, sizeof(pUsage[0])*pIdxInfo->nConstraint);
-    if( pIdxInfo->needToFreeIdxStr ) sqlite3_free(pIdxInfo->idxStr);
-    pIdxInfo->idxStr = 0;
-    pIdxInfo->idxNum = 0;
-    pIdxInfo->needToFreeIdxStr = 0;
-    pIdxInfo->orderByConsumed = 0;
-    pIdxInfo->estimatedCost = SQLITE_BIG_DBL / (double)2;
-    pIdxInfo->estimatedRows = 25;
-    pIdxInfo->idxFlags = 0;
-    pIdxInfo->colUsed = (sqlite3_int64)pSrc->colUsed;
-    rc = vtabBestIndex(pParse, pTab, pIdxInfo);
-    if( rc ) goto whereLoopAddVtab_exit;
-    pIdxCons = *(struct sqlite3_index_constraint**)&pIdxInfo->aConstraint;
-    pNew->prereq = mExtra;
-    mxTerm = -1;
-    assert( pNew->nLSlot>=nConstraint );
-    for(i=0; i<nConstraint; i++) pNew->aLTerm[i] = 0;
-    pNew->u.vtab.omitMask = 0;
-    for(i=0; i<nConstraint; i++, pIdxCons++){
-      if( (iTerm = pUsage[i].argvIndex - 1)>=0 ){
-        j = pIdxCons->iTermOffset;
-        if( iTerm>=nConstraint
-         || j<0
-         || j>=pWC->nTerm
-         || pNew->aLTerm[iTerm]!=0
-        ){
-          rc = SQLITE_ERROR;
-          sqlite3ErrorMsg(pParse, "%s.xBestIndex() malfunction", pTab->zName);
-          goto whereLoopAddVtab_exit;
-        }
-        testcase( iTerm==nConstraint-1 );
-        testcase( j==0 );
-        testcase( j==pWC->nTerm-1 );
-        pTerm = &pWC->a[j];
-        pNew->prereq |= pTerm->prereqRight;
-        assert( iTerm<pNew->nLSlot );
-        pNew->aLTerm[iTerm] = pTerm;
-        if( iTerm>mxTerm ) mxTerm = iTerm;
-        testcase( iTerm==15 );
-        testcase( iTerm==16 );
-        if( iTerm<16 && pUsage[i].omit ) pNew->u.vtab.omitMask |= 1<<iTerm;
-        if( (pTerm->eOperator & WO_IN)!=0 ){
-          /* A virtual table that is constrained by an IN clause may not
-          ** consume the ORDER BY clause because (1) the order of IN terms
-          ** is not necessarily related to the order of output terms and
-          ** (2) Multiple outputs from a single IN value will not merge
-          ** together.  */
-          pIdxInfo->orderByConsumed = 0;
-          pIdxInfo->idxFlags &= ~SQLITE_INDEX_SCAN_UNIQUE;
-        }
+  /* First call xBestIndex() with all constraints usable. */
+  rc = whereLoopAddVirtualOne(pBuilder, mExtra, (Bitmask)(-1), 0, p, &bIn);
+  mBest = pNew->prereq & ~mExtra;
+
+  /* If the call to xBestIndex() with all terms enabled produced a plan
+  ** that does not require any source tables, there is no point in making
+  ** any further calls - if the xBestIndex() method is sane they will all
+  ** return the same plan anyway.
+  */
+  if( mBest ){
+    int seenZero = 0;             /* True if a plan with no prereqs seen */
+    int seenZeroNoIN = 0;         /* Plan with no prereqs and no IN(...) seen */
+    Bitmask mPrev = 0;
+    Bitmask mBestNoIn = 0;
+
+    /* If the plan produced by the earlier call uses an IN(...) term, call
+    ** xBestIndex again, this time with IN(...) terms disabled. */
+    if( rc==SQLITE_OK && bIn ){
+      rc = whereLoopAddVirtualOne(pBuilder, mExtra, (Bitmask)-1, WO_IN, p,&bIn);
+      mBestNoIn = pNew->prereq & ~mExtra;
+      if( mBestNoIn==0 ){
+        seenZero = 1;
+        if( bIn==0 ) seenZeroNoIN = 1;
       }
     }
-    if( i>=nConstraint ){
-      pNew->nLTerm = mxTerm+1;
-      assert( pNew->nLTerm<=pNew->nLSlot );
-      pNew->u.vtab.idxNum = pIdxInfo->idxNum;
-      pNew->u.vtab.needFree = pIdxInfo->needToFreeIdxStr;
-      pIdxInfo->needToFreeIdxStr = 0;
-      pNew->u.vtab.idxStr = pIdxInfo->idxStr;
-      pNew->u.vtab.isOrdered = (i8)(pIdxInfo->orderByConsumed ?
-                                      pIdxInfo->nOrderBy : 0);
-      pNew->rSetup = 0;
-      pNew->rRun = sqlite3LogEstFromDouble(pIdxInfo->estimatedCost);
-      pNew->nOut = sqlite3LogEst(pIdxInfo->estimatedRows);
 
-      /* Set the WHERE_ONEROW flag if the xBestIndex() method indicated
-      ** that the scan will visit at most one row. Clear it otherwise. */
-      if( pIdxInfo->idxFlags & SQLITE_INDEX_SCAN_UNIQUE ){
-        pNew->wsFlags |= WHERE_ONEROW;
-      }else{
-        pNew->wsFlags &= ~WHERE_ONEROW;
+    /* Call xBestIndex once for each distinct value of (prereqRight & ~mExtra) 
+    ** in the set of terms that apply to the current virtual table.  */
+    while( rc==SQLITE_OK ){
+      int i;
+      Bitmask mNext = (Bitmask)(-1);
+      assert( mNext>0 );
+      for(i=0; i<nConstraint; i++){
+        Bitmask mThis = (
+            pWC->a[p->aConstraint[i].iTermOffset].prereqRight & ~mExtra
+        );
+        if( mThis>mPrev && mThis<mNext ) mNext = mThis;
       }
-      whereLoopInsert(pBuilder, pNew);
-      if( pNew->u.vtab.needFree ){
-        sqlite3_free(pNew->u.vtab.idxStr);
-        pNew->u.vtab.needFree = 0;
+      mPrev = mNext;
+      if( mNext==(Bitmask)(-1) ) break;
+      if( mNext==mBest || mNext==mBestNoIn ) continue;
+      rc = whereLoopAddVirtualOne(pBuilder, mExtra, mNext, 0, p, &bIn);
+      if( pNew->prereq==mExtra ){
+        seenZero = 1;
+        if( bIn==0 ) seenZeroNoIN = 1;
       }
     }
-  }  
 
-whereLoopAddVtab_exit:
-  if( pIdxInfo->needToFreeIdxStr ) sqlite3_free(pIdxInfo->idxStr);
-  sqlite3DbFree(db, pIdxInfo);
+    /* If the calls to xBestIndex() in the above loop did not find a plan
+    ** that requires no source tables at all (i.e. one guaranteed to be
+    ** usable), make a call here with all source tables disabled */
+    if( rc==SQLITE_OK && seenZero==0 ){
+      rc = whereLoopAddVirtualOne(pBuilder, mExtra, mExtra, 0, p, &bIn);
+      if( bIn==0 ) seenZeroNoIN = 1;
+    }
+
+    /* If the calls to xBestIndex() have so far failed to find a plan
+    ** that requires no source tables at all and does not use an IN(...)
+    ** operator, make a final call to obtain one here.  */
+    if( rc==SQLITE_OK && seenZeroNoIN==0 ){
+      rc = whereLoopAddVirtualOne(pBuilder, mExtra, mExtra, WO_IN, p, &bIn);
+    }
+  }
+
+  if( p->needToFreeIdxStr ) sqlite3_free(p->idxStr);
+  sqlite3DbFree(pParse->db, p);
   return rc;
 }
 #endif /* SQLITE_OMIT_VIRTUALTABLE */
diff --git a/test/bestindex2.test b/test/bestindex2.test
new file mode 100644 (file)
index 0000000..3c50d54
--- /dev/null
@@ -0,0 +1,127 @@
+# 2016 March 3
+#
+# 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 bestindex2
+
+
+#-------------------------------------------------------------------------
+# Virtual table callback for table named $tbl, with the columns specified
+# by list argument $cols. e.g. if the function is invoked as:
+#
+#   vtab_cmd t1 {a b c} ...
+#
+# The table created is:
+#
+#      "CREATE TABLE t1 (a, b, c)"
+#
+# The tables xBestIndex method behaves as if all possible combinations of
+# "=" constraints (but no others) may be optimized. The cost of a full table
+# scan is:
+#
+#      "WHERE 1"                "cost 1000000 rows 1000000"
+#
+# If one or more "=" constraints are in use, the cost and estimated number
+# of rows returned are both is (11 - nCons)*1000, where nCons is the number
+# of constraints used. e.g.
+#
+#   "WHERE a=? AND b=?"    ->   "cost  900 rows  900"
+#   "WHERE c=? AND b<?"    ->   "cost 1000 rows 1000"
+#  
+proc vtab_cmd {tbl cols method args} {
+  switch -- $method {
+    xConnect {
+      return "CREATE TABLE $tbl ([join $cols ,])"
+    }
+    xBestIndex {
+      foreach {clist orderby mask} $args {}
+
+      set cons [list]
+      set used [list]
+
+      for {set i 0} {$i < [llength $clist]} {incr i} {
+        array unset C
+        array set C [lindex $clist $i]
+        if {$C(op)=="eq" && $C(usable) && [lsearch $cons $C(column)]<0} {
+          lappend used use $i
+          lappend cons $C(column)
+        }
+      }
+
+      set nCons [llength $cons]
+      if {$nCons==0} {
+        return "cost 1000000 rows 1000000"
+      } else {
+        set cost [expr (11-$nCons) * 1000]
+        set ret [concat $used "cost $cost rows $cost"]
+
+        set txt [list]
+        foreach c $cons { lappend txt "[lindex $cols $c]=?" }
+        lappend ret idxstr "indexed([join $txt { AND }])"
+
+        return $ret
+      }
+    }
+  }
+  return ""
+}
+
+register_tcl_module db
+
+do_execsql_test 1.0 {
+  CREATE VIRTUAL TABLE t1 USING tcl("vtab_cmd t1 {a b}");
+  CREATE VIRTUAL TABLE t2 USING tcl("vtab_cmd t2 {c d}");
+  CREATE VIRTUAL TABLE t3 USING tcl("vtab_cmd t3 {e f}");
+}
+
+do_eqp_test 1.1 {
+  SELECT * FROM t1 WHERE a='abc'
+} {
+  0 0 0 {SCAN TABLE t1 VIRTUAL TABLE INDEX 0:indexed(a=?)}
+}
+do_eqp_test 1.2 {
+  SELECT * FROM t1 WHERE a='abc' AND b='def'
+} {
+  0 0 0 {SCAN TABLE t1 VIRTUAL TABLE INDEX 0:indexed(a=? AND b=?)}
+}
+do_eqp_test 1.3 {
+  SELECT * FROM t1 WHERE a='abc' AND a='def'
+} {
+  0 0 0 {SCAN TABLE t1 VIRTUAL TABLE INDEX 0:indexed(a=?)}
+}
+do_eqp_test 1.4 {
+  SELECT * FROM t1,t2 WHERE c=a
+} {
+  0 0 0 {SCAN TABLE t1 VIRTUAL TABLE INDEX 0:} 
+  0 1 1 {SCAN TABLE t2 VIRTUAL TABLE INDEX 0:indexed(c=?)}
+}
+
+do_eqp_test 1.5 {
+  SELECT * FROM t1, t2 CROSS JOIN t3 WHERE t2.c = +t1.b AND t3.e=t2.d
+} {
+  0 0 0 {SCAN TABLE t1 VIRTUAL TABLE INDEX 0:} 
+  0 1 1 {SCAN TABLE t2 VIRTUAL TABLE INDEX 0:indexed(c=?)} 
+  0 2 2 {SCAN TABLE t3 VIRTUAL TABLE INDEX 0:indexed(e=?)}
+}
+
+# This is the one that fails (as of 2016/3/3).
+#
+do_eqp_test 1.6 {
+  SELECT * FROM t1, t2, t3 WHERE t2.c = +t1.b AND t3.e = t2.d
+} {
+  0 0 0 {SCAN TABLE t1 VIRTUAL TABLE INDEX 0:} 
+  0 1 1 {SCAN TABLE t2 VIRTUAL TABLE INDEX 0:indexed(c=?)} 
+  0 2 2 {SCAN TABLE t3 VIRTUAL TABLE INDEX 0:indexed(e=?)}
+}
+
+finish_test
+