]> git.ipfire.org Git - thirdparty/sqlite.git/commitdiff
Fix some problems with multi-column IN(SELECT...) processing.
authordan <dan@noemail.net>
Wed, 27 Jul 2016 19:33:04 +0000 (19:33 +0000)
committerdan <dan@noemail.net>
Wed, 27 Jul 2016 19:33:04 +0000 (19:33 +0000)
FossilOrigin-Name: 719a3b2035a335ca8b9704646b1d641011e3ea0e

1  2 
manifest
manifest.uuid
src/expr.c
src/resolve.c
src/sqliteInt.h
src/where.c
test/in.test
test/rowvalue3.test

diff --cc manifest
index fd048d6c5d31af1de09d08f269f389235054fcf7,185e06b3cc8c6732a40abe59a253110f4877f2f1..8b4409d2ff284fd131120d39b453702aa67f1feb
+++ b/manifest
@@@ -1,5 -1,5 +1,5 @@@
- C Merge\slatest\strunk\schanges\sinto\sthis\sbranch.
- D 2016-07-26T18:15:35.133
 -C Initialize\sa\svariable\sin\swhere.c\sto\savoid\sa\svalgrind\swarning.
 -D 2016-07-27T16:03:54.108
++C Fix\ssome\sproblems\swith\smulti-column\sIN(SELECT...)\sprocessing.
++D 2016-07-27T19:33:04.107
  F Makefile.in 6c20d44f72d4564f11652b26291a214c8367e5db
  F Makefile.linux-gcc 7bc79876b875010e8c8f9502eb935ca92aa3c434
  F Makefile.msc d66d0395c38571aab3804f8db0fa20707ae4609a
@@@ -337,7 -337,7 +337,7 @@@ F src/ctime.c 61949e83c4c36e37195a8398e
  F src/date.c 1cc9fb516ec9932c6fd4d2a0d2f8bc4480145c39
  F src/dbstat.c 4f6f7f52b49beb9636ffbd517cfe44a402ba4ad0
  F src/delete.c 4aba4214a377ce8ddde2d2e609777bcc8235200f
- F src/expr.c f84861eaaf557df45bb8f4513a78a05fbb0ad368
 -F src/expr.c 21b153e1046c624e9387a17d3261f69b461e700c
++F src/expr.c bee29fbb017db26ad1189d35b2a7fe6ff8da3722
  F src/fault.c 160a0c015b6c2629d3899ed2daf63d75754a32bb
  F src/fkey.c bc4145347595b7770f9a598cff1c848302cf5413
  F src/func.c 61a4114cf7004f10c542cfabbab9f2bcb9033045
@@@ -381,14 -381,14 +381,14 @@@ F src/pragma.h 64c78a648751b9f4f297276c
  F src/prepare.c 22df6171aec1d86904ed2ad30c2348a5748aa04e
  F src/printf.c a5f0ca08ddede803c241266abb46356ec748ded1
  F src/random.c ba2679f80ec82c4190062d756f22d0c358180696
- F src/resolve.c 5c4d301a855d0245ddcc27365ddcbddd2f244665
 -F src/resolve.c cca3aa77b95706df5d635a2141a4d1de60ae6598
++F src/resolve.c d5516a3818748c50f98c18e4601a746e90929f1c
  F src/rowset.c 7b7e7e479212e65b723bf40128c7b36dc5afdfac
 -F src/select.c f3c6e9065fb34f6a23af27ec7f1f717ffbfc2ee4
 +F src/select.c 0115f5d222f5cf9b5511ec4072088417354d738a
  F src/shell.c 9351fc6de11e1d908648c0a92d85627138e3dee5
  F src/sqlite.h.in c6e68a4a47610631822a4f8f83a44c9f75339331
  F src/sqlite3.rc 5121c9e10c3964d5755191c80dd1180c122fc3a8
  F src/sqlite3ext.h 46f300b6e300e0fa916d7d58c44b53415b8471a9
- F src/sqliteInt.h a78e53083262e9a10fd1f09a8777755253c00b44
 -F src/sqliteInt.h 49081ceab08eda9943d555aee57392c5b35d1c60
++F src/sqliteInt.h 9a6af8787f03f54ce02ff88bd45f89bb600c3a4c
  F src/sqliteLimit.h c0373387c287c8d0932510b5547ecde31b5da247
  F src/status.c 5b18f9526900f61189ab0b83f1ef41d9f871a2ab
  F src/table.c 5226df15ab9179b9ed558d89575ea0ce37b03fc9
@@@ -463,10 -463,10 +463,10 @@@ F src/vxworks.h d2988f4e5a61a4dfe82c652
  F src/wal.c 02eeecc265f6ffd0597378f5d8ae9070b62a406a
  F src/wal.h 6dd221ed384afdc204bc61e25c23ef7fd5a511f2
  F src/walker.c 0f142b5bd3ed2041fc52d773880748b212e63354
- F src/where.c e7054b2c1fe31fef5136e5735d7958f5c2c7707d
 -F src/where.c 9981707ac01438baea5caa0ca6108237be6630da
 -F src/whereInt.h e5b939701a7ceffc5a3a8188a37f9746416ebcd0
 -F src/wherecode.c 99707d11907c71d289ee9553d2d1a22f1fd8ba41
 -F src/whereexpr.c d7dcbf14ce1b5876c1f76496162c30fcba669563
++F src/where.c 0b370db8ce08fac93a098f27eaf425d2269be376
 +F src/whereInt.h 14dd243e13b81cbb0a66063d38b70f93a7d6e613
 +F src/wherecode.c 3aff7683566af3428f865904aafa7efb1fbd8701
 +F src/whereexpr.c b896f8ff6a53cbd3daaee84ec33e39098762bb46
  F test/8_3_names.test ebbb5cd36741350040fd28b432ceadf495be25b2
  F test/affinity2.test a6d901b436328bd67a79b41bb0ac2663918fe3bd
  F test/aggerror.test a867e273ef9e3d7919f03ef4f0e8c0d2767944f2
@@@ -822,7 -822,7 +822,7 @@@ F test/hook.test 3b7b99d0eece6d279812c2
  F test/icu.test 73956798bace8982909c00476b216714a6d0559a
  F test/ieee754.test 806fc0ce7f305f57e3331eaceeddcfec9339e607
  F test/imposter1.test c3f1db2d3db2c24611a6596a3fc0ffc14f1466c8
- F test/in.test 41d18d4bcd27c55d0bc6b6ddc8ff9e85e91728a4
 -F test/in.test 61d940ced6817bee66e4e0b09d5bc8608f57134b
++F test/in.test 20c5529986998949908f889c8208b2cd894b2cc9
  F test/in2.test 5d4c61d17493c832f7d2d32bef785119e87bde75
  F test/in3.test 3cbf58c87f4052cee3a58b37b6389777505aa0c0
  F test/in4.test d2b38cba404bc4320f4fe1b595b3d163f212c068
@@@ -1017,9 -1017,6 +1017,9 @@@ F test/rollbackfault.test 0e646aeab8840
  F test/rowallock.test 3f88ec6819489d0b2341c7a7528ae17c053ab7cc
  F test/rowhash.test 0bc1d31415e4575d10cacf31e1a66b5cc0f8be81
  F test/rowid.test 5b7509f384f4f6fae1af3c8c104c8ca299fea18d
- F test/rowvalue3.test eeec47b4de27217a012dd142956b01af4e9bd6f2
 +F test/rowvalue.test 979738b3d49f1d93e3fee56a71d4446217917abc
 +F test/rowvalue2.test 8d5dfe75b8f4d1868a2f91f0356f20d36cba64ff
++F test/rowvalue3.test 587c1056016fa6b7a40a9bf6cb85d5443fa47d96
  F test/rtree.test 0c8d9dd458d6824e59683c19ab2ffa9ef946f798
  F test/run-wordcount.sh 891e89c4c2d16e629cd45951d4ed899ad12afc09
  F test/savepoint.test c671fdbd34cd3bfe1518a777526ada595180cf8d
@@@ -1510,7 -1507,7 +1510,7 @@@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a9
  F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc
  F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e
  F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0
- P 061b8006034f06a0311b4304c8b14d2c8b0153df 483994a54dee3c7a3801e0e9d3c96fa9dbd8d2fd
- R 675e1e72a3d3f117d790dda9dd43e7d1
 -P 483994a54dee3c7a3801e0e9d3c96fa9dbd8d2fd
 -R d3646a4fd33e16c76415db60501a55b1
++P d4f3d52c5a11fc7ad7e8cad76361edbcef13a12f 4d59df02d3713b3e3804e1a88e676749b2794286
++R 1d3755c62e27d021f9887ac09a170360
  U dan
- Z b0c1b915de5ec6a4159f97e64d5655ae
 -Z 64999ea9da24ca9027e2e455a4d899cc
++Z 2549ddc5292b60c5848a8cde952880f9
diff --cc manifest.uuid
index e9f6e44c64dafb048ba53298f044f82b773e5a19,83f63152b327323d139e609ed37f7ed61f591b03..21c7478815e960f98dba36be94c4523655ccdd3b
@@@ -1,1 -1,1 +1,1 @@@
- d4f3d52c5a11fc7ad7e8cad76361edbcef13a12f
 -4d59df02d3713b3e3804e1a88e676749b2794286
++719a3b2035a335ca8b9704646b1d641011e3ea0e
diff --cc src/expr.c
index f35ac56f80ffd969efcdd53654a13ce847d2ad75,ea52d6625345b6aaa5589a7b41b3f1f3995b521c..3c95136c5cfbe11e5d3ac9e595457e0cc55295f2
@@@ -2480,20 -2208,20 +2480,20 @@@ static void sqlite3ExprCodeIN
    }else{
    
      /* If the LHS is NULL, then the result is either false or NULL depending
--    ** on whether the RHS is empty or not, respectively.
--    */
-     if( nVector==1 && sqlite3ExprCanBeNull(pExpr->pLeft) ){
 -    if( sqlite3ExprCanBeNull(pExpr->pLeft) ){
--      if( destIfNull==destIfFalse ){
--        /* Shortcut for the common case where the false and NULL outcomes are
--        ** the same. */
--        sqlite3VdbeAddOp2(v, OP_IsNull, r1, destIfNull); VdbeCoverage(v);
--      }else{
--        int addr1 = sqlite3VdbeAddOp1(v, OP_NotNull, r1); VdbeCoverage(v);
--        sqlite3VdbeAddOp2(v, OP_Rewind, pExpr->iTable, destIfFalse);
--        VdbeCoverage(v);
--        sqlite3VdbeGoto(v, destIfNull);
--        sqlite3VdbeJumpHere(v, addr1);
++    ** on whether the RHS is empty or not, respectively.  */
++    if( destIfNull==destIfFalse ){
++      for(i=0; i<nVector; i++){
++        Expr *p = exprVectorField(pExpr->pLeft, i);
++        if( sqlite3ExprCanBeNull(p) ){
++          sqlite3VdbeAddOp2(v, OP_IsNull, r1+aiMap[i], destIfNull);
++        }
        }
++    }else if( nVector==1 && sqlite3ExprCanBeNull(pExpr->pLeft) ){
++      int addr1 = sqlite3VdbeAddOp1(v, OP_NotNull, r1); VdbeCoverage(v);
++      sqlite3VdbeAddOp2(v, OP_Rewind, pExpr->iTable, destIfFalse);
++      VdbeCoverage(v);
++      sqlite3VdbeGoto(v, destIfNull);
++      sqlite3VdbeJumpHere(v, addr1);
      }
    
      if( eType==IN_INDEX_ROWID ){
        */
        sqlite3VdbeAddOp3(v, OP_SeekRowid, pExpr->iTable, destIfFalse, r1);
        VdbeCoverage(v);
-     }else if( nVector>1 && eType==IN_INDEX_EPH ){
++    }else if( nVector>1 && eType==IN_INDEX_EPH && destIfNull!=destIfFalse ){
 +      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.
        */
@@@ -3493,22 -3172,6 +3493,22 @@@ int sqlite3ExprCodeTarget(Parse *pParse
        break;
      }
  
-       sqlite3ErrorMsg(pParse, "invalid use of row value (1)");
 +    case TK_VECTOR: {
++      sqlite3ErrorMsg(pParse, "invalid use of row value");
 +      break;
 +    }
 +
 +    case TK_SELECT_COLUMN: {
 +      Expr *pLeft = pExpr->pLeft;
 +      assert( pLeft );
 +      assert( pLeft->op==TK_SELECT || pLeft->op==TK_REGISTER );
 +      if( pLeft->op==TK_SELECT ){
 +        pLeft->iTable = sqlite3CodeSubselect(pParse, pLeft, 0, 0);
 +        pLeft->op = TK_REGISTER;
 +      }
 +      inReg = pLeft->iTable + pExpr->iColumn;
 +      break;
 +    }
  
      /*
      ** Form A:
diff --cc src/resolve.c
index cb9f2fbaa6ad1b5ffabf32edbe05db13f830d3e9,77ce37f6d7893b5454565f7930e95bf5261c58e7..4d8873963a23a186426b8afcac710879b48e16ee
@@@ -765,17 -765,6 +765,10 @@@ static int resolveExprStep(Walker *pWal
            ExprSetProperty(pExpr, EP_VarSelect);
            pNC->ncFlags |= NC_VarSelect;
          }
-           if( !ExprHasProperty(pExpr, EP_VectorOk) && 0 ){
-             sqlite3ErrorMsg(pParse, "invalid use of row value");
-           }else{
-             ExprSetProperty(pExpr, EP_Vector);
-           }
-         }
-         if( pExpr->op==TK_IN ){
-           ExprSetProperty(pExpr->pLeft, EP_VectorOk);
 +
 +        if( pExpr->op==TK_SELECT && pExpr->x.pSelect->pEList->nExpr>1 ){
++          ExprSetProperty(pExpr, EP_Vector);
 +        }
        }
        break;
      }
        notValid(pParse, pNC, "parameters", NC_IsCheck|NC_PartIdx|NC_IdxExpr);
        break;
      }
-     case TK_BETWEEN: {
-       ExprSetProperty(pExpr->pLeft, EP_VectorOk);
-       ExprSetProperty(pExpr->x.pList->a[0].pExpr, EP_VectorOk);
-       ExprSetProperty(pExpr->x.pList->a[1].pExpr, EP_VectorOk);
-       break;
-     }
-     case TK_EQ: case TK_NE: case TK_IS: case TK_ISNOT: 
-     case TK_LE: case TK_LT: case TK_GE: case TK_GT: 
-     {
-       ExprSetProperty(pExpr->pLeft, EP_VectorOk);
-       ExprSetProperty(pExpr->pRight, EP_VectorOk);
-       break;
-     };
 +
-       if( !ExprHasProperty(pExpr, EP_VectorOk) ){
-         sqlite3ErrorMsg(pParse, "invalid use of row value");
-       }else{
-         ExprSetProperty(pExpr, EP_Vector);
-       }
 +    case TK_VECTOR: {
++      ExprSetProperty(pExpr, EP_Vector);
 +      break;
 +    }
    }
    return (pParse->nErr || pParse->db->mallocFailed) ? WRC_Abort : WRC_Continue;
  }
diff --cc src/sqliteInt.h
index 1f653c08f844fc2b7bc7180dbb4553d44b5103e3,879e6703ca59f56a539cf328479e058254a9c17d..7219732bc32c1ddf24f88d1b755eaa23c15944e0
@@@ -2327,8 -2327,6 +2327,7 @@@ struct Expr 
  #define EP_CanBeNull 0x100000 /* Can be null despite NOT NULL constraint */
  #define EP_Subquery  0x200000 /* Tree contains a TK_SELECT operator */
  #define EP_Alias     0x400000 /* Is an alias for a result set column */
- #define EP_VectorOk  0x800000 /* This expression may be a row value */
- #define EP_Vector    0x1000000/* This expression is a row value */
++#define EP_Vector    0x800000 /* This expression is a row value */
  
  /*
  ** Combinations of two or more EP_* flags
diff --cc src/where.c
index 095891842f80e8e978c4ea0fc84ab38aca5a47b6,23ec21c160771329cfc2640b797b489a9a90c531..6c039c6b7c1f0ea23d4a3d7bd3eee12f17c44e52
@@@ -3477,20 -3414,20 +3477,38 @@@ static i8 wherePathSatisfiesOrderBy
        rev = revSet = 0;
        distinctColumns = 0;
        for(j=0; j<nColumn; j++){
--        u8 bOnce;   /* True to run the ORDER BY search loop */
++        u8 bOnce = 1; /* True to run the ORDER BY search loop */
  
--        /* Skip over == and IS and ISNULL terms.
--        ** (Also skip IN terms when doing WHERE_ORDERBY_LIMIT processing)
--        */
--        if( j<pLoop->u.btree.nEq
--         && pLoop->nSkip==0
--         && ((i = pLoop->aLTerm[j]->eOperator) & eqOpMask)!=0
--        ){
--          if( i & WO_ISNULL ){
--            testcase( isOrderDistinct );
--            isOrderDistinct = 0;
++        assert( j>=pLoop->u.btree.nEq 
++            || (pLoop->aLTerm[j]==0)==(j<pLoop->nSkip)
++        );
++        if( j<pLoop->u.btree.nEq && j>=pLoop->nSkip ){
++          u16 eOp = pLoop->aLTerm[j]->eOperator;
++
++          /* Skip over == and IS and ISNULL terms.  (Also skip IN terms when
++          ** doing WHERE_ORDERBY_LIMIT processing). 
++          **
++          ** If the current term is a column of an ((?,?) IN (SELECT...)) 
++          ** expression for which the SELECT returns more than one column,
++          ** check that it is the only column used by this loop. Otherwise,
++          ** if it is one of two or more, none of the columns can be
++          ** considered to match an ORDER BY term.  */
++          if( (eOp & eqOpMask)!=0 ){
++            if( eOp & WO_ISNULL ){
++              testcase( isOrderDistinct );
++              isOrderDistinct = 0;
++            }
++            continue;  
++          }else if( eOp & WO_IN ){
++            Expr *pX = pLoop->aLTerm[j]->pExpr;
++            for(i=j+1; i<pLoop->u.btree.nEq; i++){
++              if( pLoop->aLTerm[i]->pExpr==pX ){
++                assert( (pLoop->aLTerm[i]->eOperator & WO_IN) );
++                bOnce = 0;
++                break;
++              }
++            }
            }
--          continue;  
          }
  
          /* Get the column number in the table (iColumn) and sort order
          /* Find the ORDER BY term that corresponds to the j-th column
          ** of the index and mark that ORDER BY term off 
          */
--        bOnce = 1;
          isMatch = 0;
          for(i=0; bOnce && i<nOrderBy; i++){
            if( MASKBIT(i) & obSat ) continue;
diff --cc test/in.test
Simple merge
index 78bf21b61599c28ace8dac16ba9f15c0fd707b31,0000000000000000000000000000000000000000..32c86251d06a64f0805b9a1017e95f1a335d0ff6
mode 100644,000000..100644
--- /dev/null
@@@ -1,97 -1,0 +1,214 @@@
-   2 {  CREATE INDEX z1idx ON z1(x, y) }
-   3 {  CREATE UNIQUE INDEX z1idx ON z1(x, y) }
 +# 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
 +
++# Drop all auxiliary indexes from the main database opened by handle [db].
++#
++proc drop_all_indexes {} {
++  set L [db eval {
++    SELECT name FROM sqlite_master WHERE type='index' AND sql LIKE 'create%'
++  }]
++  foreach idx $L { db eval "DROP INDEX $idx" }
++}
 +
 +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
 +}
 +
 +#-------------------------------------------------------------------------
 +
 +do_execsql_test 2.0 {
 +  CREATE TABLE z1(x, y, z);
 +  CREATE TABLE kk(a, b);
 +
 +  INSERT INTO z1 VALUES('a', 'b', 'c');
 +  INSERT INTO z1 VALUES('d', 'e', 'f');
 +  INSERT INTO z1 VALUES('g', 'h', 'i');
 +
 +  -- INSERT INTO kk VALUES('y', 'y');
 +  INSERT INTO kk VALUES('d', 'e');
 +  -- INSERT INTO kk VALUES('x', 'x');
 +
 +}
 +
 +foreach {tn idx} {
 +  1 { }
- explain_i {
-   SELECT * FROM z1 WHERE (x, y) IN (SELECT a, b FROM kk)
- } 
++  2 { CREATE INDEX z1idx ON z1(x, y) }
++  3 { CREATE UNIQUE INDEX z1idx ON z1(x, y) }
++  4 { CREATE INDEX z1idx ON kk(a, b) }
 +} {
 +  execsql "DROP INDEX IF EXISTS z1idx"
 +  execsql $idx
 +
 +  do_execsql_test 2.$tn.1 {
 +    SELECT * FROM z1 WHERE x IN (SELECT a FROM kk)
 +  } {d e f}
 +
 +  do_execsql_test 2.$tn.2 {
 +    SELECT * FROM z1 WHERE (x,y) IN (SELECT a, b FROM kk)
 +  } {d e f}
 +
 +  do_execsql_test 2.$tn.3 {
 +    SELECT * FROM z1 WHERE (x, +y) IN (SELECT a, b FROM kk)
 +  } {d e f}
 +  
 +  do_execsql_test 2.$tn.4 {
 +    SELECT * FROM z1 WHERE (x, +y) IN (SELECT a, b||'x' FROM kk)
 +  } {}
 +
 +  do_execsql_test 2.$tn.5 {
 +    SELECT * FROM z1 WHERE (+x, y) IN (SELECT a, b FROM kk)
 +  } {d e f}
 +}
 +
- finish_test
++#-------------------------------------------------------------------------
++#
++
++do_execsql_test 3.0 {
++  CREATE TABLE c1(a, b, c, d);
++  INSERT INTO c1(rowid, a, b) VALUES(1,   NULL, 1);
++  INSERT INTO c1(rowid, a, b) VALUES(2,   2, NULL);
++  INSERT INTO c1(rowid, a, b) VALUES(3,   2, 2);
++  INSERT INTO c1(rowid, a, b) VALUES(4,   3, 3);
++
++  INSERT INTO c1(rowid, a, b, c, d) VALUES(101, 'a', 'b', 1, 1);
++  INSERT INTO c1(rowid, a, b, c, d) VALUES(102, 'a', 'b', 1, 2);
++  INSERT INTO c1(rowid, a, b, c, d) VALUES(103, 'a', 'b', 1, 3);
++  INSERT INTO c1(rowid, a, b, c, d) VALUES(104, 'a', 'b', 2, 1);
++  INSERT INTO c1(rowid, a, b, c, d) VALUES(105, 'a', 'b', 2, 2);
++  INSERT INTO c1(rowid, a, b, c, d) VALUES(106, 'a', 'b', 2, 3);
++  INSERT INTO c1(rowid, a, b, c, d) VALUES(107, 'a', 'b', 3, 1);
++  INSERT INTO c1(rowid, a, b, c, d) VALUES(108, 'a', 'b', 3, 2);
++  INSERT INTO c1(rowid, a, b, c, d) VALUES(109, 'a', 'b', 3, 3);
++}
 +
 +
++foreach {tn idx} {
++  1 { }
++  2 { CREATE INDEX c1ab ON c1(a, b); }
++  3 { CREATE INDEX c1ba ON c1(b, a); }
++
++  4 { CREATE INDEX c1cd ON c1(c, d); }
++  5 { CREATE INDEX c1dc ON c1(d, c); }
++} {
++  drop_all_indexes
++
++  foreach {tn2 sql res} {
++    1 "SELECT (1, 2) IN (SELECT a, b FROM c1)" {0}
++    2 "SELECT (1, 1) IN (SELECT a, b FROM c1)" {{}}
++    3 "SELECT (2, 1) IN (SELECT a, b FROM c1)" {{}}
++    4 "SELECT (2, 2) IN (SELECT a, b FROM c1)" {1}
++    5 "SELECT c, d FROM c1 WHERE (c, d) IN (SELECT d, c FROM c1)"
++      { 1 1 1 2 1 3   2 1 2 2 2 3   3 1 3 2 3 3 }
++
++    6 "SELECT c, d FROM c1 WHERE (c,d) IN (SELECT d, c FROM c1) ORDER BY c DESC"
++      { 3 1 3 2 3 3   2 1 2 2 2 3   1 1 1 2 1 3 }
++
++    7 {
++        SELECT c, d FROM c1 WHERE (c,d) IN (SELECT d, c FROM c1) 
++        ORDER BY c DESC, d ASC
++      } { 3 1 3 2 3 3   2 1 2 2 2 3   1 1 1 2 1 3 }
++
++    8 {
++        SELECT c, d FROM c1 WHERE (c,d) IN (SELECT d, c FROM c1) 
++        ORDER BY c ASC, d DESC
++      } { 1 3 1 2 1 1   2 3 2 2 2 1   3 3 3 2 3 1 }
++
++    9 {
++        SELECT c, d FROM c1 WHERE (c,d) IN (SELECT d, c FROM c1) 
++        ORDER BY c ASC, d ASC
++      } { 1 1 1 2 1 3   2 1 2 2 2 3   3 1 3 2 3 3 }
++    10 {
++        SELECT c, d FROM c1 WHERE (c,d) IN (SELECT d, c FROM c1) 
++        ORDER BY c DESC, d DESC
++      } { 3 3 3 2 3 1   2 3 2 2 2 1   1 3 1 2 1 1 }
++
++  } {
++    do_execsql_test 3.$tn.$tn2 $sql $res
++  }
++}
++
++#-------------------------------------------------------------------------
++
++do_execsql_test 4.0 {
++  CREATE TABLE hh(a, b, c);
++
++  INSERT INTO hh VALUES('a', 'a', 1);
++  INSERT INTO hh VALUES('a', 'b', 2);
++  INSERT INTO hh VALUES('b', 'a', 3);
++  INSERT INTO hh VALUES('b', 'b', 4);
++
++  CREATE TABLE k1(x, y);
++  INSERT INTO k1 VALUES('a', 'a');
++  INSERT INTO k1 VALUES('b', 'b');
++  INSERT INTO k1 VALUES('a', 'b');
++  INSERT INTO k1 VALUES('b', 'a');
++}
++
++foreach {tn idx} {
++  1 { }
++  2 { CREATE INDEX h1 ON hh(a, b); }
++  3 { CREATE UNIQUE INDEX k1idx ON k1(x, y) }
++  4 { CREATE UNIQUE INDEX k1idx ON k1(x, y DESC) }
++  5 { 
++    CREATE INDEX h1 ON hh(a, b);
++    CREATE UNIQUE INDEX k1idx ON k1(x, y); 
++  }
++  6 { 
++    CREATE INDEX h1 ON hh(a, b);
++    CREATE UNIQUE INDEX k1idx ON k1(x, y DESC); 
++  }
++} {
++  drop_all_indexes
++  execsql $idx
++  foreach {tn2 orderby res} {
++    1 "a ASC, b ASC"  {1 2 3 4}
++    2 "a ASC, b DESC" {2 1 4 3}
++    3 "a DESC, b ASC" {3 4 1 2}
++    4 "a DESC, b DESC" {4 3 2 1}
++  } {
++    do_execsql_test 4.$tn.$tn2 "
++      SELECT c FROM hh WHERE (a, b) in (SELECT x, y FROM k1) ORDER BY $orderby
++    " $res
++  }
++}
++
++
++finish_test
 +
 +