]> git.ipfire.org Git - thirdparty/sqlite.git/commitdiff
Enhance the vtab interface to handle IS, !=, IS NOT, IS NULL and IS NOT NULL
authordan <dan@noemail.net>
Sat, 9 Sep 2017 19:41:12 +0000 (19:41 +0000)
committerdan <dan@noemail.net>
Sat, 9 Sep 2017 19:41:12 +0000 (19:41 +0000)
constraints.

FossilOrigin-Name: 34c8e952616013deb6fffec701ac5989afac9bef1bf92458a2e4ba92c7ee924f

manifest
manifest.uuid
src/sqlite.h.in
src/test8.c
src/test_bestindex.c
src/where.c
src/wherecode.c
src/whereexpr.c
test/bestindex5.test [new file with mode: 0644]

index 4b71f03671af55a5aebf469cfbb40e5f2bc27ec1..3e1369face9560ab1a40eada3afd7cbb5fba1205 100644 (file)
--- a/manifest
+++ b/manifest
@@ -1,5 +1,5 @@
-C Fix\sharmless\scompiler\swarnings\sseen\swith\sMSVC.
-D 2017-09-09T00:51:36.496
+C Enhance\sthe\svtab\sinterface\sto\shandle\sIS,\s!=,\sIS\sNOT,\sIS\sNULL\sand\sIS\sNOT\sNULL\nconstraints.
+D 2017-09-09T19:41:12.986
 F Makefile.in c644bbe8ebe4aae82ad6783eae6b6beea4c727b99ff97568b847ced5e2ac7afb
 F Makefile.linux-gcc 7bc79876b875010e8c8f9502eb935ca92aa3c434
 F Makefile.msc 6a7a74bf60ad395098c0bd175ab054cd65ef85d7f034198d52bcc4d9e5fb4c6b
@@ -460,7 +460,7 @@ F src/rowset.c 7b7e7e479212e65b723bf40128c7b36dc5afdfac
 F src/select.c c9b3d8444bbf6f167d84f41ca6f3672e2521cb163a8c706b19058dc82fffe9b8
 F src/shell.c c1206a23d9239f8f51751d3be9b8c3b02fa4103546bea1add7f864d84a8276ab
 F src/shell.c.in bb9720a8c5c98d3984b16ab7540e7142bcae959666ecf248bfc523a1d44220ee
-F src/sqlite.h.in 21f62793a3611b43f6fb31f0a4c8b38489a4df025416e9b7db7cc01bf5ef5aaa
+F src/sqlite.h.in d0ab3cae93cc9819f9e7ba5c8c8e3708e657c6cdbc61ecfa7dfadd19c0308ffa
 F src/sqlite3.rc 5121c9e10c3964d5755191c80dd1180c122fc3a8
 F src/sqlite3ext.h a1fd3aa82f967da436164e0728a7d6841651fd0c6e27b9044e0eb9f6c8462e47
 F src/sqliteInt.h f9ae3609a583aa51712083e1d5817f62c7d97c0a203036a9a62c439059e8258b
@@ -475,12 +475,12 @@ F src/test4.c 18ec393bb4d0ad1de729f0b94da7267270f3d8e6
 F src/test5.c 328aae2c010c57a9829d255dc099d6899311672d
 F src/test6.c e8d839fbc552ce044bec8234561a2d5b8819b48e29548ad0ba400471697946a8
 F src/test7.c 5612e9aecf934d6df7bba6ce861fdf5ba5456010
-F src/test8.c 4f4904721167b32f7a4fa8c7b32a07a673d6cc86
+F src/test8.c 3f7d0cc4e12e06832ba3db4455cb16867ccadafa602eb6ff5fcf097bffce56ed
 F src/test9.c 12e5ba554d2d1cbe0158f6ab3f7ffcd7a86ee4e5
 F src/test_async.c 195ab49da082053fdb0f949c114b806a49ca770a
 F src/test_autoext.c 915d245e736652a219a907909bb6710f0d587871
 F src/test_backup.c bf5da90c9926df0a4b941f2d92825a01bbe090a0
-F src/test_bestindex.c d23f80d334c59662af69191854c76b8d3d0c8c96
+F src/test_bestindex.c 78809f11026f18a93fcfd798d9479cba37e1201c830260bf1edc674b2fa9b857
 F src/test_blob.c ae4a0620b478548afb67963095a7417cd06a4ec0a56adb453542203bfdcb31ce
 F src/test_btree.c 8b2dc8b8848cf3a4db93f11578f075e82252a274
 F src/test_config.c abf6fc1fe9d041b699578c42e3db81f8831c4f5b804f1927958102ee8f2b773e
@@ -539,10 +539,10 @@ F src/vxworks.h d2988f4e5a61a4dfe82c6524dd3d6e4f2ce3cdb9
 F src/wal.c 839db09792fead5052bb35e533fa485e134913d547d05b5f42e537b73e63f07a
 F src/wal.h 8de5d2d3de0956d6f6cb48c83a4012d5f227b8fe940f3a349a4b7e85ebcb492a
 F src/walker.c 3ccfa8637f95355bff61144e01a615b8ef26f79c312880848da73f03367da1e6
-F src/where.c 101f0a645c45c12141b38a61b593232555fc001bf7786dcb03eb8f313783b404
+F src/where.c 7cc9692dc4f270f5a196d33d2ee1011ce6218a6061b73df592771a404ee3500c
 F src/whereInt.h 93bb90b77d39901eda31b44d8e90da1351193ccfe96876f89b58a93a33b84c3d
-F src/wherecode.c d246d19f5453d3f154ed8fcea892ce6d70ae4a5ddaebae34bd49d73f4c913bc7
-F src/whereexpr.c fe1fe600d7334e91f3d9d487021362d543fba8ab2f1be5e0d68063d619379c05
+F src/wherecode.c e8c2ece5843ea56e6c90277d421f2d628f3f7b7c976642369cc519f008e1d2b1
+F src/whereexpr.c ffc3c90f68ad28c6eca1c8b05029f361bc151187be578985d992788d31f295ae
 F test/8_3_names.test ebbb5cd36741350040fd28b432ceadf495be25b2
 F test/affinity2.test a6d901b436328bd67a79b41bb0ac2663918fe3bd
 F test/affinity3.test 6a101af2fc945ce2912f6fe54dd646018551710d
@@ -610,6 +610,7 @@ F test/bestindex1.test 0cf1bd2d7b97d3a3a8c10736125274f64765c4ee
 F test/bestindex2.test 4a06b8922ab2fd09434870da8d1cdf525aaf7060
 F test/bestindex3.test 578b6a52dab819e63f28e3640e04b32c85aed320
 F test/bestindex4.test 4cb5ff7dbaebadb87d366f51969271778423b455
+F test/bestindex5.test a7f1c32dc21d5c85afd4e9611e1160247107387b85a371fded36852c1c4959e0
 F test/between.test 34d375fb5ce1ae283ffe82b6b233e9f38e84fc6c
 F test/bigfile.test aa74f4e5db51c8e54a1d9de9fa65d01d1eb20b59
 F test/bigfile2.test 1b489a3a39ae90c7f027b79110d6b4e1dbc71bfc
@@ -1652,7 +1653,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 e1e3ca7ea43a68b9b57dc38d8855f63b63a53feb8128b666a1becf87a2c70341
-R 14621bd82abfa0f650a4c6242c2108d7
-U mistachkin
-Z 11f83c8a0999c8fee516d7917728daf2
+P faa22e29a5a05a16d46a428d838acedfa7d6fad6239495d59a6a1f4764e1b1b6
+R 71eba3dbcd321cff2772c9856db2cb4e
+T *branch * vtab-extra-ops
+T *sym-vtab-extra-ops *
+T -sym-trunk *
+U dan
+Z f5cb136cb2d9007b3611d202913746bf
index ce2953ddb73cafbf2e9767f64d4cb7f1c7cf0a75..e02149a44ec09f9c7cd59cd74750d8c463fcf066 100644 (file)
@@ -1 +1 @@
-faa22e29a5a05a16d46a428d838acedfa7d6fad6239495d59a6a1f4764e1b1b6
\ No newline at end of file
+34c8e952616013deb6fffec701ac5989afac9bef1bf92458a2e4ba92c7ee924f
\ No newline at end of file
index 8b3c22c92dbc174e994468d85618702e1d2cfa5f..e2edfe46052e75264cb947f89bbacddabd782bad 100644 (file)
@@ -6256,6 +6256,11 @@ struct sqlite3_index_info {
 #define SQLITE_INDEX_CONSTRAINT_LIKE   65
 #define SQLITE_INDEX_CONSTRAINT_GLOB   66
 #define SQLITE_INDEX_CONSTRAINT_REGEXP 67
+#define SQLITE_INDEX_CONSTRAINT_NE        68
+#define SQLITE_INDEX_CONSTRAINT_ISNOT     69
+#define SQLITE_INDEX_CONSTRAINT_ISNOTNULL 70
+#define SQLITE_INDEX_CONSTRAINT_ISNULL    71
+#define SQLITE_INDEX_CONSTRAINT_IS        72
 
 /*
 ** CAPI3REF: Register A Virtual Table Implementation
index daab504e4ea683a917f2abb6da262dea5237830f..2684f801fa76f82835be566afd7f653316df35cf 100644 (file)
@@ -897,17 +897,18 @@ static int echoBestIndex(sqlite3_vtab *tab, sqlite3_index_info *pIdxInfo){
         case SQLITE_INDEX_CONSTRAINT_REGEXP:
           zOp = "regexp"; break;
       }
-      if( zOp[0]=='L' ){
-        zNew = sqlite3_mprintf(" %s %s LIKE (SELECT '%%'||?||'%%')", 
-                               zSep, zNewCol);
-      } else {
-        zNew = sqlite3_mprintf(" %s %s %s ?", zSep, zNewCol, zOp);
+      if( zOp ){
+        if( zOp[0]=='L' ){
+          zNew = sqlite3_mprintf(" %s %s LIKE (SELECT '%%'||?||'%%')", 
+              zSep, zNewCol);
+        } else {
+          zNew = sqlite3_mprintf(" %s %s %s ?", zSep, zNewCol, zOp);
+        }
+        string_concat(&zQuery, zNew, 1, &rc);
+        zSep = "AND";
+        pUsage->argvIndex = ++nArg;
+        pUsage->omit = 1;
       }
-      string_concat(&zQuery, zNew, 1, &rc);
-
-      zSep = "AND";
-      pUsage->argvIndex = ++nArg;
-      pUsage->omit = 1;
     }
   }
 
index 94017f0349fbb03fff781e42ceecc77c8159e769..2cd79baf2bc182246a57674e297d5c90ac3e38d7 100644 (file)
@@ -414,6 +414,16 @@ static int tclBestIndex(sqlite3_vtab *tab, sqlite3_index_info *pIdxInfo){
         zOp = "glob"; break;
       case SQLITE_INDEX_CONSTRAINT_REGEXP:
         zOp = "regexp"; break;
+      case SQLITE_INDEX_CONSTRAINT_NE:
+        zOp = "ne"; break;
+      case SQLITE_INDEX_CONSTRAINT_ISNOT:
+        zOp = "isnot"; break;
+      case SQLITE_INDEX_CONSTRAINT_ISNOTNULL:
+        zOp = "isnotnull"; break;
+      case SQLITE_INDEX_CONSTRAINT_ISNULL:
+        zOp = "isnull"; break;
+      case SQLITE_INDEX_CONSTRAINT_IS:
+        zOp = "is"; break;
     }
 
     Tcl_ListObjAppendElement(0, pElem, Tcl_NewStringObj("op", -1));
index 754187302364cc22612734e2ff3fd0d247f3875d..0e908e151676be9ef6dd0612990638aa7e1ca05d 100644 (file)
@@ -868,7 +868,7 @@ static sqlite3_index_info *allocateIndexInfo(
     testcase( pTerm->eOperator & WO_ISNULL );
     testcase( pTerm->eOperator & WO_IS );
     testcase( pTerm->eOperator & WO_ALL );
-    if( (pTerm->eOperator & ~(WO_ISNULL|WO_EQUIV|WO_IS))==0 ) continue;
+    if( (pTerm->eOperator & ~(WO_EQUIV))==0 ) continue;
     if( pTerm->wtFlags & TERM_VNULL ) continue;
     assert( pTerm->u.leftColumn>=(-1) );
     nTerm++;
@@ -916,7 +916,7 @@ static sqlite3_index_info *allocateIndexInfo(
                                                                    pUsage;
 
   for(i=j=0, pTerm=pWC->a; i<pWC->nTerm; i++, pTerm++){
-    u8 op;
+    u16 op;
     if( pTerm->leftCursor != pSrc->iCursor ) continue;
     if( pTerm->prereqRight & mUnusable ) continue;
     assert( IsPowerOfTwo(pTerm->eOperator & ~WO_EQUIV) );
@@ -924,34 +924,41 @@ static sqlite3_index_info *allocateIndexInfo(
     testcase( pTerm->eOperator & WO_IS );
     testcase( pTerm->eOperator & WO_ISNULL );
     testcase( pTerm->eOperator & WO_ALL );
-    if( (pTerm->eOperator & ~(WO_ISNULL|WO_EQUIV|WO_IS))==0 ) continue;
+    if( (pTerm->eOperator & ~(WO_EQUIV))==0 ) continue;
     if( pTerm->wtFlags & TERM_VNULL ) continue;
     assert( pTerm->u.leftColumn>=(-1) );
     pIdxCons[j].iColumn = pTerm->u.leftColumn;
     pIdxCons[j].iTermOffset = i;
-    op = (u8)pTerm->eOperator & WO_ALL;
+    op = pTerm->eOperator & WO_ALL;
     if( op==WO_IN ) op = WO_EQ;
     if( op==WO_MATCH ){
-      op = pTerm->eMatchOp;
-    }
-    pIdxCons[j].op = op;
-    /* The direct assignment in the previous line is possible only because
-    ** the WO_ and SQLITE_INDEX_CONSTRAINT_ codes are identical.  The
-    ** following asserts verify this fact. */
-    assert( WO_EQ==SQLITE_INDEX_CONSTRAINT_EQ );
-    assert( WO_LT==SQLITE_INDEX_CONSTRAINT_LT );
-    assert( WO_LE==SQLITE_INDEX_CONSTRAINT_LE );
-    assert( WO_GT==SQLITE_INDEX_CONSTRAINT_GT );
-    assert( WO_GE==SQLITE_INDEX_CONSTRAINT_GE );
-    assert( WO_MATCH==SQLITE_INDEX_CONSTRAINT_MATCH );
-    assert( pTerm->eOperator & (WO_IN|WO_EQ|WO_LT|WO_LE|WO_GT|WO_GE|WO_MATCH) );
-
-    if( op & (WO_LT|WO_LE|WO_GT|WO_GE)
-     && sqlite3ExprIsVector(pTerm->pExpr->pRight) 
-    ){
-      if( i<16 ) mNoOmit |= (1 << i);
-      if( op==WO_LT ) pIdxCons[j].op = WO_LE;
-      if( op==WO_GT ) pIdxCons[j].op = WO_GE;
+      pIdxCons[j].op = pTerm->eMatchOp;
+    }else if( op & (WO_ISNULL|WO_IS) ){
+      if( op==WO_ISNULL ){
+        pIdxCons[j].op = SQLITE_INDEX_CONSTRAINT_ISNULL;
+      }else{
+        pIdxCons[j].op = SQLITE_INDEX_CONSTRAINT_IS;
+      }
+    }else{
+      pIdxCons[j].op = (u8)op;
+      /* The direct assignment in the previous line is possible only because
+      ** the WO_ and SQLITE_INDEX_CONSTRAINT_ codes are identical.  The
+      ** following asserts verify this fact. */
+      assert( WO_EQ==SQLITE_INDEX_CONSTRAINT_EQ );
+      assert( WO_LT==SQLITE_INDEX_CONSTRAINT_LT );
+      assert( WO_LE==SQLITE_INDEX_CONSTRAINT_LE );
+      assert( WO_GT==SQLITE_INDEX_CONSTRAINT_GT );
+      assert( WO_GE==SQLITE_INDEX_CONSTRAINT_GE );
+      assert( WO_MATCH==SQLITE_INDEX_CONSTRAINT_MATCH );
+      assert( pTerm->eOperator&(WO_IN|WO_EQ|WO_LT|WO_LE|WO_GT|WO_GE|WO_MATCH) );
+
+      if( op & (WO_LT|WO_LE|WO_GT|WO_GE)
+          && sqlite3ExprIsVector(pTerm->pExpr->pRight) 
+        ){
+        if( i<16 ) mNoOmit |= (1 << i);
+        if( op==WO_LT ) pIdxCons[j].op = WO_LE;
+        if( op==WO_GT ) pIdxCons[j].op = WO_GE;
+      }
     }
 
     j++;
index 30ff3f25c44ce2292b3b4fde7c7917a75be147b8..6cdf7b566b7812c8ac96adf2dc6fe7dc036b52df 100644 (file)
@@ -1017,7 +1017,7 @@ static void codeDeferredSeek(
 */
 static void codeExprOrVector(Parse *pParse, Expr *p, int iReg, int nReg){
   assert( nReg>0 );
-  if( sqlite3ExprIsVector(p) ){
+  if( p && sqlite3ExprIsVector(p) ){
 #ifndef SQLITE_OMIT_SUBQUERY
     if( (p->flags & EP_xIsSelect) ){
       Vdbe *v = pParse->pVdbe;
index ffd31a6ee51f3d6b06e34bf78ffbb5ee0f98ac06..45d3a7e33fedb5cc2127733402419b422536078f 100644 (file)
@@ -317,43 +317,77 @@ static int isLikeOrGlob(
 **         column OP expr
 **
 ** where OP is one of MATCH, GLOB, LIKE or REGEXP and "column" is a 
-** column of a virtual table.
+** column of a virtual table. If so, set *ppLeft to point to the
+** expression for "column", *ppRight to "expr" and return 1.
 **
-** If it is then return TRUE.  If not, return FALSE.
+** Also check if the expression is one of:
+**
+**         column != expr
+**         column IS NOT expr
+**         column IS NOT NULL
+**
+** where "column" is a column of a virtual table. If so, set *ppLeft
+** to point to "column", *ppRight to "expr" and return 1. Or, if "expr"
+** is also a column of a virtual table, return 2.
+**
+** If the expression matches none of the patterns above, return 0.
 */
 static int isMatchOfColumn(
   Expr *pExpr,                    /* Test this expression */
-  unsigned char *peOp2            /* OUT: 0 for MATCH, or else an op2 value */
+  unsigned char *peOp2,           /* OUT: 0 for MATCH, or else an op2 value */
+  Expr **ppLeft,                  /* Column expression to left of MATCH/op2 */
+  Expr **ppRight                  /* Expression to left of MATCH/op2 */
 ){
-  static const struct Op2 {
-    const char *zOp;
-    unsigned char eOp2;
-  } aOp[] = {
-    { "match",  SQLITE_INDEX_CONSTRAINT_MATCH },
-    { "glob",   SQLITE_INDEX_CONSTRAINT_GLOB },
-    { "like",   SQLITE_INDEX_CONSTRAINT_LIKE },
-    { "regexp", SQLITE_INDEX_CONSTRAINT_REGEXP }
-  };
-  ExprList *pList;
-  Expr *pCol;                     /* Column reference */
-  int i;
+  if( pExpr->op==TK_FUNCTION ){
+    static const struct Op2 {
+      const char *zOp;
+      unsigned char eOp2;
+    } aOp[] = {
+      { "match",  SQLITE_INDEX_CONSTRAINT_MATCH },
+      { "glob",   SQLITE_INDEX_CONSTRAINT_GLOB },
+      { "like",   SQLITE_INDEX_CONSTRAINT_LIKE },
+      { "regexp", SQLITE_INDEX_CONSTRAINT_REGEXP }
+    };
+    ExprList *pList;
+    Expr *pCol;                     /* Column reference */
+    int i;
 
-  if( pExpr->op!=TK_FUNCTION ){
-    return 0;
-  }
-  pList = pExpr->x.pList;
-  if( pList==0 || pList->nExpr!=2 ){
-    return 0;
-  }
-  pCol = pList->a[1].pExpr;
-  if( pCol->op!=TK_COLUMN || !IsVirtual(pCol->pTab) ){
-    return 0;
-  }
-  for(i=0; i<ArraySize(aOp); i++){
-    if( sqlite3StrICmp(pExpr->u.zToken, aOp[i].zOp)==0 ){
-      *peOp2 = aOp[i].eOp2;
-      return 1;
+    if( pExpr->op!=TK_FUNCTION ){
+      return 0;
+    }
+    pList = pExpr->x.pList;
+    if( pList==0 || pList->nExpr!=2 ){
+      return 0;
+    }
+    pCol = pList->a[1].pExpr;
+    if( pCol->op!=TK_COLUMN || !IsVirtual(pCol->pTab) ){
+      return 0;
+    }
+    for(i=0; i<ArraySize(aOp); i++){
+      if( sqlite3StrICmp(pExpr->u.zToken, aOp[i].zOp)==0 ){
+        *peOp2 = aOp[i].eOp2;
+        *ppRight = pList->a[0].pExpr;
+        *ppLeft = pCol;
+        return 1;
+      }
+    }
+  }else if( pExpr->op==TK_NE || pExpr->op==TK_ISNOT || pExpr->op==TK_NOTNULL ){
+    int res = 0;
+    Expr *pLeft = pExpr->pLeft;
+    Expr *pRight = pExpr->pRight;
+    if( pLeft->op==TK_COLUMN && IsVirtual(pLeft->pTab) ){
+      res++;
     }
+    if( pRight && pRight->op==TK_COLUMN && IsVirtual(pRight->pTab) ){
+      res++;
+      SWAP(Expr*, pLeft, pRight);
+    }
+    *ppLeft = pLeft;
+    *ppRight = pRight;
+    if( pExpr->op==TK_NE ) *peOp2 = SQLITE_INDEX_CONSTRAINT_NE;
+    if( pExpr->op==TK_ISNOT ) *peOp2 = SQLITE_INDEX_CONSTRAINT_ISNOT;
+    if( pExpr->op==TK_NOTNULL ) *peOp2 = SQLITE_INDEX_CONSTRAINT_ISNOTNULL;
+    return res;
   }
   return 0;
 }
@@ -1192,35 +1226,38 @@ static void exprAnalyze(
   ** virtual tables.  The native query optimizer does not attempt
   ** to do anything with MATCH functions.
   */
-  if( pWC->op==TK_AND && isMatchOfColumn(pExpr, &eOp2) ){
-    int idxNew;
+  if( pWC->op==TK_AND ){
     Expr *pRight, *pLeft;
-    WhereTerm *pNewTerm;
-    Bitmask prereqColumn, prereqExpr;
-
-    pRight = pExpr->x.pList->a[0].pExpr;
-    pLeft = pExpr->x.pList->a[1].pExpr;
-    prereqExpr = sqlite3WhereExprUsage(pMaskSet, pRight);
-    prereqColumn = sqlite3WhereExprUsage(pMaskSet, pLeft);
-    if( (prereqExpr & prereqColumn)==0 ){
-      Expr *pNewExpr;
-      pNewExpr = sqlite3PExpr(pParse, TK_MATCH, 
-                              0, sqlite3ExprDup(db, pRight, 0));
-      if( ExprHasProperty(pExpr, EP_FromJoin) && pNewExpr ){
-        ExprSetProperty(pNewExpr, EP_FromJoin);
+    int i;
+    int res = isMatchOfColumn(pExpr, &eOp2, &pLeft, &pRight);
+    for(i=0; i<res; i++){
+      int idxNew;
+      WhereTerm *pNewTerm;
+      Bitmask prereqColumn, prereqExpr;
+
+      prereqExpr = sqlite3WhereExprUsage(pMaskSet, pRight);
+      prereqColumn = sqlite3WhereExprUsage(pMaskSet, pLeft);
+      if( (prereqExpr & prereqColumn)==0 ){
+        Expr *pNewExpr;
+        pNewExpr = sqlite3PExpr(pParse, TK_MATCH, 
+            0, sqlite3ExprDup(db, pRight, 0));
+        if( ExprHasProperty(pExpr, EP_FromJoin) && pNewExpr ){
+          ExprSetProperty(pNewExpr, EP_FromJoin);
+        }
+        idxNew = whereClauseInsert(pWC, pNewExpr, TERM_VIRTUAL|TERM_DYNAMIC);
+        testcase( idxNew==0 );
+        pNewTerm = &pWC->a[idxNew];
+        pNewTerm->prereqRight = prereqExpr;
+        pNewTerm->leftCursor = pLeft->iTable;
+        pNewTerm->u.leftColumn = pLeft->iColumn;
+        pNewTerm->eOperator = WO_MATCH;
+        pNewTerm->eMatchOp = eOp2;
+        markTermAsChild(pWC, idxNew, idxTerm);
+        pTerm = &pWC->a[idxTerm];
+        pTerm->wtFlags |= TERM_COPIED;
+        pNewTerm->prereqAll = pTerm->prereqAll;
       }
-      idxNew = whereClauseInsert(pWC, pNewExpr, TERM_VIRTUAL|TERM_DYNAMIC);
-      testcase( idxNew==0 );
-      pNewTerm = &pWC->a[idxNew];
-      pNewTerm->prereqRight = prereqExpr;
-      pNewTerm->leftCursor = pLeft->iTable;
-      pNewTerm->u.leftColumn = pLeft->iColumn;
-      pNewTerm->eOperator = WO_MATCH;
-      pNewTerm->eMatchOp = eOp2;
-      markTermAsChild(pWC, idxNew, idxTerm);
-      pTerm = &pWC->a[idxTerm];
-      pTerm->wtFlags |= TERM_COPIED;
-      pNewTerm->prereqAll = pTerm->prereqAll;
+      SWAP(Expr*, pLeft, pRight);
     }
   }
 #endif /* SQLITE_OMIT_VIRTUALTABLE */
diff --git a/test/bestindex5.test b/test/bestindex5.test
new file mode 100644 (file)
index 0000000..237041f
--- /dev/null
@@ -0,0 +1,192 @@
+# 2017 September 10
+#
+# 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.
+#
+#***********************************************************************
+# Test the virtual table interface. In particular the xBestIndex
+# method.
+#
+
+set testdir [file dirname $argv0]
+source $testdir/tester.tcl
+set testprefix bestindex4
+
+ifcapable !vtab {
+  finish_test
+  return
+}
+
+#-------------------------------------------------------------------------
+# Virtual table callback for a virtual table named $tbl.
+#  
+proc vtab_cmd {method args} {
+
+  set binops(ne)    !=
+  set binops(eq)    =
+  set binops(isnot) "IS NOT"
+  set binops(is)    "IS"
+
+  set unops(isnotnull) "IS NOT NULL"
+  set unops(isnull)    "IS NULL"
+
+  set cols(0) a
+  set cols(1) b
+  set cols(2) c
+
+  switch -- $method {
+    xConnect {
+      return "CREATE TABLE t1(a, b, c)"
+    }
+
+    xBestIndex {
+      foreach {clist orderby mask} $args {}
+
+      set cost 1000000.0
+      set ret [list]
+      set str [list]
+
+      set v 0
+      for {set i 0} {$i < [llength $clist]} {incr i} {
+        array unset C
+        array set C [lindex $clist $i]
+        if {$C(usable)} {
+          if {[info exists binops($C(op))]} {
+            lappend ret omit $i
+            lappend str "$cols($C(column)) $binops($C(op)) %$v%"
+            incr v
+            set cost [expr $cost / 2]
+          }
+          if {[info exists unops($C(op))]} {
+            lappend ret omit $i
+            lappend str "$cols($C(column)) $unops($C(op))"
+            incr v
+            set cost [expr $cost / 2]
+          }
+        }
+      }
+
+      lappend ret idxstr [join $str " AND "]
+      lappend ret cost $cost
+      return $ret
+    }
+
+    xFilter {
+      set q [lindex $args 1]
+      set a [lindex $args 2]
+      for {set v 0} {$v < [llength $a]} {incr v} {
+        set val [lindex $a $v]
+        set q [string map [list %$v% '$val'] $q]
+      }
+      if {$q==""} { set q 1 }
+      lappend ::xFilterQueries "WHERE $q"
+      return [list sql "SELECT rowid, * FROM t1x WHERE $q"]
+    }
+  }
+  return ""
+}
+
+proc vtab_simple {method args} {
+  switch -- $method {
+    xConnect {
+      return "CREATE TABLE t2(x)"
+    }
+    xBestIndex {
+      return [list cost 999999.0]
+    }
+    xFilter {
+      return [list sql "SELECT rowid, * FROM t2x"]
+    }
+  }
+  return ""
+}
+
+register_tcl_module db
+
+proc do_vtab_query_test {tn query result} {
+  set ::xFilterQueries [list]
+  uplevel [list
+    do_test $tn [string map [list %QUERY% $query] {
+      set r [execsql {%QUERY%}]
+      set r [concat $::xFilterQueries $r]
+      set r
+    }] [list {*}$result]
+  ]
+}
+
+do_execsql_test 1.0 {
+  CREATE VIRTUAL TABLE t1 USING tcl('vtab_cmd');
+  CREATE TABLE t1x(a INTEGER, b TEXT, c REAL);
+  INSERT INTO t1x VALUES(1, 2, 3);
+  INSERT INTO t1x VALUES(4, 5, 6);
+  INSERT INTO t1x VALUES(7, 8, 9);
+
+  CREATE VIRTUAL TABLE t2 USING tcl('vtab_simple');
+  CREATE TABLE t2x(x INTEGER);
+  INSERT INTO t2x VALUES(1);
+}
+
+do_vtab_query_test 1.1 { SELECT * FROM t1 WHERE a!='hello'; } {
+  "WHERE a != 'hello'"
+  1 2 3.0 4 5 6.0 7 8 9.0
+}
+
+do_vtab_query_test 1.2.1 { SELECT * FROM t1 WHERE b!=8 } {
+  "WHERE b != '8'"
+  1 2 3.0 4 5 6.0
+}
+do_vtab_query_test 1.2.2 { SELECT * FROM t1 WHERE 8!=b } {
+  "WHERE b != '8'"
+  1 2 3.0 4 5 6.0
+}
+
+do_vtab_query_test 1.3 { SELECT * FROM t1 WHERE c IS NOT 3 } {
+  "WHERE c IS NOT '3'"
+  4 5 6.0 7 8 9.0
+}
+do_vtab_query_test 1.3.2 { SELECT * FROM t1 WHERE 3 IS NOT c } {
+  "WHERE c IS NOT '3'"
+  4 5 6.0 7 8 9.0
+}
+
+do_vtab_query_test 1.4.1 { SELECT * FROM t1, t2 WHERE x != a } {
+  "WHERE a != '1'"
+  4 5 6.0 1   7 8 9.0 1
+}
+do_vtab_query_test 1.4.2 { SELECT * FROM t1, t2 WHERE a != x } {
+  "WHERE a != '1'"
+  4 5 6.0 1   7 8 9.0 1
+}
+
+do_vtab_query_test 1.5.1 { SELECT * FROM t1 WHERE a IS NOT NULL } {
+  "WHERE a IS NOT NULL"
+  1 2 3.0 4 5 6.0 7 8 9.0
+}
+do_vtab_query_test 1.5.2 { SELECT * FROM t1 WHERE NULL IS NOT a } {
+  "WHERE a IS NOT ''"
+  1 2 3.0 4 5 6.0 7 8 9.0
+}
+
+do_vtab_query_test 1.6.1 { SELECT * FROM t1 WHERE a IS NULL } {
+  "WHERE a IS NULL"
+}
+
+do_vtab_query_test 1.6.2 { SELECT * FROM t1 WHERE NULL IS a } {
+  "WHERE a IS ''"
+}
+
+do_vtab_query_test 1.7.1 { SELECT * FROM t1 WHERE (a, b) IS (1, 2) } {
+  "WHERE a IS '1' AND b IS '2'"
+  1 2 3.0
+}
+do_vtab_query_test 1.7.2 { SELECT * FROM t1 WHERE (5, 4) IS (b, a) } {
+  {WHERE b IS '5' AND a IS '4'} 
+  4 5 6.0
+}
+
+finish_test
+