]> git.ipfire.org Git - thirdparty/sqlite.git/commitdiff
Allow vector IN(SELECT ...) expressions to use an index if either all the indexed...
authordan <dan@noemail.net>
Sat, 23 Jul 2016 20:24:06 +0000 (20:24 +0000)
committerdan <dan@noemail.net>
Sat, 23 Jul 2016 20:24:06 +0000 (20:24 +0000)
FossilOrigin-Name: e2fd6f49b1b145bec09c581cc982b89429643ae9

manifest
manifest.uuid
src/expr.c
src/sqliteInt.h
src/wherecode.c
test/rowvalue3.test [new file with mode: 0644]

index 65a4d1d4138357ed3e4aa4b70c2dc1e722c78bb4..52753bfb101386c456892ca87372d8618cf3bd75 100644 (file)
--- a/manifest
+++ b/manifest
@@ -1,5 +1,5 @@
-C Merge\slatest\strunk\schanges\swith\sthis\sbranch.
-D 2016-07-22T17:58:05.771
+C Allow\svector\sIN(SELECT\s...)\sexpressions\sto\suse\san\sindex\sif\seither\sall\sthe\sindexed\scolumns\sare\sdeclared\sNOT\sNULL\sor\sif\sthere\sis\sno\sdifference\sbetween\sthe\sexpression\sevaluating\sto\s0\sand\sNULL\s(as\sin\sa\sWHERE\sclause).
+D 2016-07-23T20:24:06.382
 F Makefile.in 6c20d44f72d4564f11652b26291a214c8367e5db
 F Makefile.linux-gcc 7bc79876b875010e8c8f9502eb935ca92aa3c434
 F Makefile.msc d66d0395c38571aab3804f8db0fa20707ae4609a
@@ -337,7 +337,7 @@ F src/ctime.c 61949e83c4c36e37195a8398ebc752780b534d95
 F src/date.c 1cc9fb516ec9932c6fd4d2a0d2f8bc4480145c39
 F src/dbstat.c 4f6f7f52b49beb9636ffbd517cfe44a402ba4ad0
 F src/delete.c 4aba4214a377ce8ddde2d2e609777bcc8235200f
-F src/expr.c 939362d26f5e99a4802ae94ae6e47d4def72b8f3
+F src/expr.c 8ff9d70cc2077020327d1fa551558bb03e267da4
 F src/fault.c 160a0c015b6c2629d3899ed2daf63d75754a32bb
 F src/fkey.c bc4145347595b7770f9a598cff1c848302cf5413
 F src/func.c 61a4114cf7004f10c542cfabbab9f2bcb9033045
@@ -388,7 +388,7 @@ F src/shell.c a8a9e392a6a2777fabf5feb536931cb190f235e5
 F src/sqlite.h.in b9ba728c1083b7a8ab5f6a628b25cd2a00325fbf
 F src/sqlite3.rc 5121c9e10c3964d5755191c80dd1180c122fc3a8
 F src/sqlite3ext.h 2a170163d121095c6ab1ef05ed0413722f391d01
-F src/sqliteInt.h dd2dd1d880ffd33137d20dc6da21f169836b8f5a
+F src/sqliteInt.h c4877fb0519c13558d18d08775bc8e79476cb56c
 F src/sqliteLimit.h c0373387c287c8d0932510b5547ecde31b5da247
 F src/status.c 5b18f9526900f61189ab0b83f1ef41d9f871a2ab
 F src/table.c 5226df15ab9179b9ed558d89575ea0ce37b03fc9
@@ -465,7 +465,7 @@ F src/wal.h 6dd221ed384afdc204bc61e25c23ef7fd5a511f2
 F src/walker.c 0f142b5bd3ed2041fc52d773880748b212e63354
 F src/where.c 898a45969bae1298cbaaaf05e6aeacfb45971293
 F src/whereInt.h 1ad3be2a43cb821418e1100476a3a14cd57635c4
-F src/wherecode.c 877ceb19cf00a5fd5aeea4e3ff633dcdf173f164
+F src/wherecode.c eb0f5e8700afb110cb96fb873c0e9a015a9f63ff
 F src/whereexpr.c d88ee6ce356cb9fd986d0e81249a2cd66a513093
 F test/8_3_names.test ebbb5cd36741350040fd28b432ceadf495be25b2
 F test/affinity2.test a6d901b436328bd67a79b41bb0ac2663918fe3bd
@@ -1019,6 +1019,7 @@ F test/rowhash.test 0bc1d31415e4575d10cacf31e1a66b5cc0f8be81
 F test/rowid.test 5b7509f384f4f6fae1af3c8c104c8ca299fea18d
 F test/rowvalue.test 979738b3d49f1d93e3fee56a71d4446217917abc
 F test/rowvalue2.test 8d5dfe75b8f4d1868a2f91f0356f20d36cba64ff
+F test/rowvalue3.test 72a9fe5ad30df2d422876466e180c148ab88dc42
 F test/rtree.test 0c8d9dd458d6824e59683c19ab2ffa9ef946f798
 F test/run-wordcount.sh 891e89c4c2d16e629cd45951d4ed899ad12afc09
 F test/savepoint.test c671fdbd34cd3bfe1518a777526ada595180cf8d
@@ -1508,7 +1509,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 34e35c71b25b0aa2d8931040feb260a78cc48c49 87e25fc472604b3978811be53991104c665a95e7
-R 2fbaab60c614a16797a19958a11968ca
+P 60fed5cdd4a44aefa1b4d505adeb7936f2f0b952
+R 1dc4c60c16780168f167fe34d6f77ee9
 U dan
-Z c65487e55e425163e0e854585a16b213
+Z f82474a4f9b8fbec7649c9e9d834d395
index 9342dcf6ce5d2b2c9c0dd6ec323d481e6e5bc25f..1150e4a104a42615af1d24e90f55b8ac0f8e89ff 100644 (file)
@@ -1 +1 @@
-60fed5cdd4a44aefa1b4d505adeb7936f2f0b952
\ No newline at end of file
+e2fd6f49b1b145bec09c581cc982b89429643ae9
\ No newline at end of file
index c43c6c04dee9253b8efe10cf95818246326be4c9..5792073228ec118f4e911c1ff115960b93bcecf9 100644 (file)
@@ -323,6 +323,12 @@ int sqlite3ExprVectorSize(Expr *pExpr){
   return pExpr->x.pList->nExpr;
 }
 
+/*
+** If the expression passed as the first argument is a TK_VECTOR, return
+** a pointer to the i'th field of the vector. Or, if the first argument
+** points to a sub-select, return a pointer to the i'th returned column 
+** value. Otherwise, return a copy of the first argument.
+*/
 static Expr *exprVectorField(Expr *pVector, int i){
   if( (pVector->flags & EP_Vector)==0 ){
     assert( i==0 );
@@ -1710,6 +1716,13 @@ int sqlite3IsRowid(const char *z){
 ** a pointer to the SELECT statement.  If pX is not a SELECT statement,
 ** or if the SELECT statement needs to be manifested into a transient
 ** table, then return NULL.
+** 
+** If parameter bNullSensitive is 0, then this operation will be
+** used in a context in which there is no difference between a result
+** of 0 and one of NULL. For example:
+**
+**     ... WHERE (?,?) IN (SELECT ...)
+**
 */
 #ifndef SQLITE_OMIT_SUBQUERY
 static Select *isCandidateForInOpt(Expr *pX, int bNullSensitive){
@@ -1870,7 +1883,13 @@ static int sqlite3InRhsIsConstant(Expr *pIn){
 ** NULL values.
 */
 #ifndef SQLITE_OMIT_SUBQUERY
-int sqlite3FindInIndex(Parse *pParse, Expr *pX, u32 inFlags, int *prRhsHasNull){
+int sqlite3FindInIndex(
+  Parse *pParse, 
+  Expr *pX, 
+  u32 inFlags, 
+  int *prRhsHasNull,
+  int *aiMap
+){
   Select *p;                            /* SELECT to the right of IN operator */
   int eType = 0;                        /* Type of RHS table. IN_INDEX_* */
   int iTab = pParse->nTab++;            /* Cursor of the RHS table */
@@ -1887,9 +1906,9 @@ int sqlite3FindInIndex(Parse *pParse, Expr *pX, u32 inFlags, int *prRhsHasNull){
   if( pParse->nErr==0 && (p = isCandidateForInOpt(pX, prRhsHasNull!=0))!=0 ){
     sqlite3 *db = pParse->db;              /* Database connection */
     Table *pTab;                           /* Table <table>. */
+    i16 iDb;                               /* Database idx for pTab */
     ExprList *pEList = p->pEList;
     int nExpr = pEList->nExpr;
-    i16 iDb;                               /* Database idx for pTab */
 
     assert( p->pEList!=0 );             /* Because of isCandidateForInOpt(p) */
     assert( p->pEList->a[0].pExpr!=0 ); /* Because of isCandidateForInOpt(p) */
@@ -1961,6 +1980,7 @@ int sqlite3FindInIndex(Parse *pParse, Expr *pX, u32 inFlags, int *prRhsHasNull){
             break;
           }
           if( j==nExpr ) break;
+          if( aiMap ) aiMap[i] = j;
         }
 
         if( i==nExpr ){
@@ -2023,6 +2043,12 @@ int sqlite3FindInIndex(Parse *pParse, Expr *pX, u32 inFlags, int *prRhsHasNull){
   }else{
     pX->iTable = iTab;
   }
+
+  if( aiMap && eType!=IN_INDEX_INDEX_ASC && eType!=IN_INDEX_INDEX_DESC ){
+    int i, n;
+    n = sqlite3ExprVectorSize(pX->pLeft);
+    for(i=0; i<n; i++) aiMap[i] = i;
+  }
   return eType;
 }
 #endif
@@ -2309,75 +2335,6 @@ int sqlite3CodeSubselect(
 }
 #endif /* SQLITE_OMIT_SUBQUERY */
 
-#ifndef SQLITE_OMIT_SUBQUERY
-void exprCodeVectorIN(
-  Parse *pParse,        /* Parsing and code generating context */
-  Expr *pExpr,          /* The IN expression */
-  int destIfFalse,      /* Jump here if LHS is not contained in the RHS */
-  int destIfNull        /* Jump here if the results are unknown due to NULLs */
-){
-  int i;
-  int addrNext;
-  int iSkip;
-  int r1;
-  int r2 = sqlite3GetTempReg(pParse);
-  int r3 = sqlite3GetTempReg(pParse);
-  int r4 = sqlite3GetTempReg(pParse);
-  int regResult = sqlite3GetTempReg(pParse);
-  int nVal = sqlite3ExprVectorSize(pExpr->pLeft);
-
-  Expr *pLeft = pExpr->pLeft;
-  Vdbe *v = pParse->pVdbe;
-
-  /* Code the LHS, the <expr> from "<expr> IN (...)". Leave the results in
-  ** an array of nVal registers starting at r1.  */
-  sqlite3ExprCachePush(pParse);
-  if( pLeft->flags & EP_xIsSelect ){
-    r1 = sqlite3CodeSubselect(pParse, pLeft, 0, 0);
-  }else{
-    r1 = pParse->nMem + 1;
-    pParse->nMem += nVal;
-    sqlite3ExprCodeExprList(pParse, pLeft->x.pList, r1, 0, 0);
-  }
-
-  /* Generate an epheremal index containing the contents of the SELECT
-  ** to the right of the "<expr> IN (SELECT ...)" expression. The cursor
-  ** number for the epheremal table is left in pExpr->iTable.  */
-  assert( pExpr->flags & EP_xIsSelect );
-  sqlite3CodeSubselect(pParse, pExpr, 0, 0);
-
-  sqlite3VdbeAddOp2(v, OP_Integer, 0, regResult);
-
-  /* Iterate through the ephemeral table just populated */
-  addrNext = 1 + sqlite3VdbeAddOp2(v, OP_Rewind, pExpr->iTable, destIfFalse);
-  for(i=0; i<nVal; i++){
-    Expr *p;
-    CollSeq *pColl;
-    p = exprVectorField(pLeft, i);
-    pColl = sqlite3ExprCollSeq(pParse, p);
-    sqlite3VdbeAddOp3(v, OP_Column, pExpr->iTable, i, r2);
-    sqlite3VdbeAddOp4(v, OP_Eq, r1+i, i==0?r3:r4, r2, (void*)pColl,P4_COLLSEQ); 
-    sqlite3VdbeChangeP5(v, SQLITE_STOREP2);
-    VdbeCoverage(v);
-    if( i!=0 ){
-      sqlite3VdbeAddOp3(v, OP_And, r3, r4, r4);
-    }
-  }
-  sqlite3VdbeAddOp2(v, OP_If, r4, sqlite3VdbeCurrentAddr(v)+6);
-  sqlite3VdbeAddOp2(v, OP_IfNot, r4, sqlite3VdbeCurrentAddr(v)+2);
-  sqlite3VdbeAddOp2(v, OP_Null, 0, regResult);
-  sqlite3VdbeAddOp2(v, OP_Next, pExpr->iTable, addrNext);
-  sqlite3VdbeAddOp3(v, OP_If, regResult, destIfNull, 1);
-  sqlite3VdbeAddOp2(v, OP_Goto, 0, destIfFalse);
-
-  sqlite3ReleaseTempReg(pParse, r2);
-  sqlite3ReleaseTempReg(pParse, r3);
-  sqlite3ReleaseTempReg(pParse, r4);
-  sqlite3ReleaseTempReg(pParse, regResult);
-  sqlite3ExprCachePop(pParse);
-}
-#endif
-
 #ifndef SQLITE_OMIT_SUBQUERY
 /*
 ** Generate code for an IN expression.
@@ -2403,36 +2360,64 @@ static void sqlite3ExprCodeIN(
   int destIfNull        /* Jump here if the results are unknown due to NULLs */
 ){
   int rRhsHasNull = 0;  /* Register that is true if RHS contains NULL values */
-  char affinity;        /* Comparison affinity to use */
   int eType;            /* Type of the RHS */
   int r1;               /* Temporary use register */
   Vdbe *v;              /* Statement under construction */
+  int *aiMap = 0;       /* Map from vector field to index column */
+  char *zAff = 0;       /* Affinity string for comparisons */
+  int nVector;          /* Size of vectors for this IN(...) op */
+  int regSelect = 0;
+  Expr *pLeft = pExpr->pLeft;
+  int i;
 
-  if( pExpr->pLeft->flags & EP_Vector ){
-    return exprCodeVectorIN(pParse, pExpr, destIfFalse, destIfNull);
-  }
+  nVector = sqlite3ExprVectorSize(pExpr->pLeft);
+  aiMap = (int*)sqlite3DbMallocZero(
+      pParse->db, nVector*(sizeof(int) + sizeof(char)) + 1
+  );
+  if( !aiMap ) return;
+  zAff = (char*)&aiMap[nVector];
 
-  /* Compute the RHS.   After this step, the table with cursor
-  ** pExpr->iTable will contains the values that make up the RHS.
-  */
+  /* Attempt to compute the RHS. After this step, if anything other than
+  ** IN_INDEX_NOOP is returned, the table opened ith cursor pExpr->iTable 
+  ** contains the values that make up the RHS. If IN_INDEX_NOOP is returned,
+  ** the RHS has not yet been coded.  */
   v = pParse->pVdbe;
   assert( v!=0 );       /* OOM detected prior to this routine */
   VdbeNoopComment((v, "begin IN expr"));
   eType = sqlite3FindInIndex(pParse, pExpr,
                              IN_INDEX_MEMBERSHIP | IN_INDEX_NOOP_OK,
-                             destIfFalse==destIfNull ? 0 : &rRhsHasNull);
+                             destIfFalse==destIfNull ? 0 : &rRhsHasNull, aiMap);
 
-  /* Figure out the affinity to use to create a key from the results
-  ** of the expression. affinityStr stores a static string suitable for
-  ** P4 of OP_MakeRecord.
-  */
-  affinity = comparisonAffinity(pExpr);
+  assert( pParse->nErr || nVector==1 || eType==IN_INDEX_EPH
+       || eType==IN_INDEX_INDEX_ASC || eType==IN_INDEX_INDEX_DESC 
+  );
 
-  /* Code the LHS, the <expr> from "<expr> IN (...)".
+  /* Code the LHS, the <expr> from "<expr> IN (...)". If the LHS is a 
+  ** vector, then it is stored in an array of nVector registers starting 
+  ** at r1.
   */
+  r1 = sqlite3GetTempRange(pParse, nVector);
   sqlite3ExprCachePush(pParse);
-  r1 = sqlite3GetTempReg(pParse);
-  sqlite3ExprCode(pParse, pExpr->pLeft, r1);
+  if( nVector>1 && (pLeft->flags & EP_xIsSelect) ){
+    regSelect = sqlite3CodeSubselect(pParse, pLeft, 0, 0);
+  }
+  for(i=0; i<nVector; i++){
+    int iCol = aiMap[i];
+    Expr *pLhs = exprVectorField(pLeft, i);
+
+    if( regSelect ){
+      sqlite3VdbeAddOp3(v, OP_Copy, regSelect+i, r1+iCol, 0);
+    }else{
+      sqlite3ExprCode(pParse, pLhs, r1+iCol);
+    }
+
+    zAff[iCol] = sqlite3ExprAffinity(pLhs);
+    if( pExpr->flags & EP_xIsSelect ){
+      zAff[iCol] = sqlite3CompareAffinity(
+          pExpr->x.pSelect->pEList->a[iCol].pExpr, zAff[iCol]
+      );
+    }
+  }
 
   /* If sqlite3FindInIndex() did not find or create an index that is
   ** suitable for evaluating the IN operator, then evaluate using a
@@ -2460,12 +2445,12 @@ static void sqlite3ExprCodeIN(
                           (void*)pColl, P4_COLLSEQ);
         VdbeCoverageIf(v, ii<pList->nExpr-1);
         VdbeCoverageIf(v, ii==pList->nExpr-1);
-        sqlite3VdbeChangeP5(v, affinity);
+        sqlite3VdbeChangeP5(v, zAff[0]);
       }else{
         assert( destIfNull==destIfFalse );
         sqlite3VdbeAddOp4(v, OP_Ne, r1, destIfFalse, r2,
                           (void*)pColl, P4_COLLSEQ); VdbeCoverage(v);
-        sqlite3VdbeChangeP5(v, affinity | SQLITE_JUMPIFNULL);
+        sqlite3VdbeChangeP5(v, zAff[0] | SQLITE_JUMPIFNULL);
       }
       sqlite3ReleaseTempReg(pParse, regToFree);
     }
@@ -2480,7 +2465,7 @@ static void sqlite3ExprCodeIN(
     /* If the LHS is NULL, then the result is either false or NULL depending
     ** on whether the RHS is empty or not, respectively.
     */
-    if( sqlite3ExprCanBeNull(pExpr->pLeft) ){
+    if( nVector==1 && sqlite3ExprCanBeNull(pExpr->pLeft) ){
       if( destIfNull==destIfFalse ){
         /* Shortcut for the common case where the false and NULL outcomes are
         ** the same. */
@@ -2499,10 +2484,50 @@ static void sqlite3ExprCodeIN(
       */
       sqlite3VdbeAddOp3(v, OP_SeekRowid, pExpr->iTable, destIfFalse, r1);
       VdbeCoverage(v);
+    }else if( nVector>1 && eType==IN_INDEX_EPH ){
+      int regNull = sqlite3GetTempReg(pParse);
+      int r2 = sqlite3GetTempReg(pParse);
+      int r3 = sqlite3GetTempReg(pParse);
+      int r4 = sqlite3GetTempReg(pParse);
+      int addrNext;
+      int addrIf;
+
+      if( destIfFalse!=destIfNull ){
+        sqlite3VdbeAddOp2(v, OP_Integer, 0, regNull);
+      }
+      addrNext = sqlite3VdbeAddOp2(v, OP_Rewind, pExpr->iTable, destIfFalse);
+      for(i=0; i<nVector; i++){
+        Expr *p;
+        CollSeq *pColl;
+        p = exprVectorField(pLeft, i);
+        pColl = sqlite3ExprCollSeq(pParse, p);
+
+        sqlite3VdbeAddOp3(v, OP_Column, pExpr->iTable, i, r2);
+        sqlite3VdbeAddOp4(v, OP_Eq, r1+i, i?r3:r4, r2, (void*)pColl,P4_COLLSEQ);
+        sqlite3VdbeChangeP5(v, SQLITE_STOREP2);
+        if( i!=0 ){
+          sqlite3VdbeAddOp3(v, OP_And, r3, r4, r4);
+        }
+      }
+      addrIf = sqlite3VdbeAddOp1(v, OP_If, r4);
+      if( destIfNull!=destIfFalse ){
+        sqlite3VdbeAddOp2(v, OP_IfNot, r4, sqlite3VdbeCurrentAddr(v)+2);
+        sqlite3VdbeAddOp2(v, OP_Integer, 1, regNull);
+      }
+      sqlite3VdbeAddOp2(v, OP_Next, pExpr->iTable, addrNext+1);
+      if( destIfNull!=destIfFalse ){
+        sqlite3VdbeAddOp2(v, OP_If, regNull, destIfNull);
+      }
+      sqlite3VdbeAddOp2(v, OP_Goto, 0, destIfFalse);
+      sqlite3VdbeChangeP2(v, addrIf, sqlite3VdbeCurrentAddr(v));
+      sqlite3ReleaseTempReg(pParse, regNull);
+      sqlite3ReleaseTempReg(pParse, r2);
+      sqlite3ReleaseTempReg(pParse, r3);
+      sqlite3ReleaseTempReg(pParse, r4);
     }else{
       /* In this case, the RHS is an index b-tree.
       */
-      sqlite3VdbeAddOp4(v, OP_Affinity, r1, 1, 0, &affinity, 1);
+      sqlite3VdbeAddOp4(v, OP_Affinity, r1, nVector, 0, zAff, nVector);
   
       /* If the set membership test fails, then the result of the 
       ** "x IN (...)" expression must be either 0 or NULL. If the set
@@ -2519,7 +2544,9 @@ static void sqlite3ExprCodeIN(
         ** Also run this branch if NULL is equivalent to FALSE
         ** for this particular IN operator.
         */
-        sqlite3VdbeAddOp4Int(v, OP_NotFound, pExpr->iTable, destIfFalse, r1, 1);
+        sqlite3VdbeAddOp4Int(
+            v, OP_NotFound, pExpr->iTable, destIfFalse, r1, nVector
+        );
         VdbeCoverage(v);
       }else{
         /* In this branch, the RHS of the IN might contain a NULL and
@@ -2545,6 +2572,7 @@ static void sqlite3ExprCodeIN(
   }
   sqlite3ReleaseTempReg(pParse, r1);
   sqlite3ExprCachePop(pParse);
+  sqlite3DbFree(pParse->db, aiMap);
   VdbeComment((v, "end IN expr"));
 }
 #endif /* SQLITE_OMIT_SUBQUERY */
@@ -4010,7 +4038,7 @@ void sqlite3ExprIfFalse(Parse *pParse, Expr *pExpr, int dest, int jumpIfNull){
   assert( pExpr->op!=TK_GT || op==OP_Le );
   assert( pExpr->op!=TK_GE || op==OP_Lt );
 
-  switch( pExpr->op | (pExpr->pLeft ? (pExpr->pLeft->flags & EP_Vector) : 0)){
+  switch( pExpr->op ){
     case TK_AND: {
       testcase( jumpIfNull==0 );
       sqlite3ExprIfFalse(pParse, pExpr->pLeft, dest, jumpIfNull);
@@ -4047,6 +4075,8 @@ void sqlite3ExprIfFalse(Parse *pParse, Expr *pExpr, int dest, int jumpIfNull){
     case TK_GE:
     case TK_NE:
     case TK_EQ: {
+      if( pExpr->pLeft->flags & EP_Vector ) goto default_expr;
+
       testcase( jumpIfNull==0 );
       r1 = sqlite3ExprCodeTemp(pParse, pExpr->pLeft, &regFree1);
       r2 = sqlite3ExprCodeTemp(pParse, pExpr->pRight, &regFree2);
@@ -4093,6 +4123,7 @@ void sqlite3ExprIfFalse(Parse *pParse, Expr *pExpr, int dest, int jumpIfNull){
     }
 #endif
     default: {
+    default_expr: 
       if( exprAlwaysFalse(pExpr) ){
         sqlite3VdbeGoto(v, dest);
       }else if( exprAlwaysTrue(pExpr) ){
index 3caa65d8e30d40606b2d9634ded2b6d9e96835ef..b8a326cfbc3c5e0e743a2f917c3ed49debe8fe56 100644 (file)
@@ -4134,7 +4134,7 @@ const char *sqlite3JournalModename(int);
 #define IN_INDEX_NOOP_OK     0x0001  /* OK to return IN_INDEX_NOOP */
 #define IN_INDEX_MEMBERSHIP  0x0002  /* IN operator used for membership test */
 #define IN_INDEX_LOOP        0x0004  /* IN operator used as a loop */
-int sqlite3FindInIndex(Parse *, Expr *, u32, int*);
+int sqlite3FindInIndex(Parse *, Expr *, u32, int*, int*);
 
 int sqlite3JournalOpen(sqlite3_vfs *, const char *, sqlite3_file *, int, int);
 int sqlite3JournalSize(sqlite3_vfs *);
index e660b3538708edf4c29d84a62a8d4f16024bed7a..0f6d320241a932184393ace88e27088b3607f2fb 100644 (file)
@@ -379,7 +379,7 @@ static int codeEqualityTerm(
     }
     assert( pX->op==TK_IN );
     iReg = iTarget;
-    eType = sqlite3FindInIndex(pParse, pX, IN_INDEX_LOOP, 0);
+    eType = sqlite3FindInIndex(pParse, pX, IN_INDEX_LOOP, 0, 0);
     if( eType==IN_INDEX_INDEX_DESC ){
       testcase( bRev );
       bRev = !bRev;
diff --git a/test/rowvalue3.test b/test/rowvalue3.test
new file mode 100644 (file)
index 0000000..63f2c89
--- /dev/null
@@ -0,0 +1,52 @@
+# 2016 June 17
+#
+# 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.
+#
+#***********************************************************************
+# This file implements regression tests for SQLite library.  The
+# focus of this file is testing "(...) IN (SELECT ...)" expressions
+# where the SELECT statement returns more than one column.
+#
+
+set testdir [file dirname $argv0]
+source $testdir/tester.tcl
+set ::testprefix rowvalue3
+
+
+do_execsql_test 1.0 {
+  CREATE TABLE t1(a, b, c);
+  CREATE INDEX i1 ON t1(a, b);
+  INSERT INTO t1 VALUES(1, 2, 3);
+  INSERT INTO t1 VALUES(4, 5, 6);
+  INSERT INTO t1 VALUES(7, 8, 9);
+}
+
+foreach {tn sql res} {
+  1  "SELECT 1 WHERE (4, 5) IN (SELECT a, b FROM t1)"  1
+  2  "SELECT 1 WHERE (5, 5) IN (SELECT a, b FROM t1)"  {}
+  3  "SELECT 1 WHERE (5, 4) IN (SELECT a, b FROM t1)"  {}
+  4  "SELECT 1 WHERE (5, 4) IN (SELECT b, a FROM t1)"  1
+  5  "SELECT 1 WHERE (SELECT a, b FROM t1 WHERE c=6) IN (SELECT a, b FROM t1)" 1
+  6  "SELECT (5, 4) IN (SELECT a, b FROM t1)" 0
+  7  "SELECT 1 WHERE (5, 4) IN (SELECT +b, +a FROM t1)"  1
+  8  "SELECT (5, 4) IN (SELECT +b, +a FROM t1)"  1
+  9  "SELECT (1, 2) IN (SELECT rowid, b FROM t1)"  1
+  10 "SELECT 1 WHERE (1, 2) IN (SELECT rowid, b FROM t1)"  1
+  11 "SELECT 1 WHERE (1, NULL) IN (SELECT rowid, b FROM t1)"  {}
+} {
+  do_execsql_test 1.$tn $sql $res
+}
+
+#explain_i { SELECT (4, NULL) IN (SELECT a, b FROM t1) } 
+#do_execsql_test 2 { SELECT (4, NULL) IN (SELECT a, b FROM t1) } {}
+
+
+
+
+finish_test
+