aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authordan <dan@noemail.net>2016-07-23 20:24:06 +0000
committerdan <dan@noemail.net>2016-07-23 20:24:06 +0000
commitba00e30a3a55e3800ce808373d9604983a4d1df5 (patch)
tree89345681cb394f558831a45da332eef0ad860f2c
parent4097d0ca799b94ad70bab459234075f7185d55fd (diff)
downloadsqlite-ba00e30a3a55e3800ce808373d9604983a4d1df5.tar.gz
sqlite-ba00e30a3a55e3800ce808373d9604983a4d1df5.zip
Allow vector IN(SELECT ...) expressions to use an index if either all the indexed columns are declared NOT NULL or if there is no difference between the expression evaluating to 0 and NULL (as in a WHERE clause).
FossilOrigin-Name: e2fd6f49b1b145bec09c581cc982b89429643ae9
-rw-r--r--manifest17
-rw-r--r--manifest.uuid2
-rw-r--r--src/expr.c217
-rw-r--r--src/sqliteInt.h2
-rw-r--r--src/wherecode.c2
-rw-r--r--test/rowvalue3.test52
6 files changed, 188 insertions, 104 deletions
diff --git a/manifest b/manifest
index 65a4d1d41..52753bfb1 100644
--- 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
diff --git a/manifest.uuid b/manifest.uuid
index 9342dcf6c..1150e4a10 100644
--- a/manifest.uuid
+++ b/manifest.uuid
@@ -1 +1 @@
-60fed5cdd4a44aefa1b4d505adeb7936f2f0b952 \ No newline at end of file
+e2fd6f49b1b145bec09c581cc982b89429643ae9 \ No newline at end of file
diff --git a/src/expr.c b/src/expr.c
index c43c6c04d..579207322 100644
--- a/src/expr.c
+++ b/src/expr.c
@@ -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
@@ -2310,75 +2336,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) ){
diff --git a/src/sqliteInt.h b/src/sqliteInt.h
index 3caa65d8e..b8a326cfb 100644
--- a/src/sqliteInt.h
+++ b/src/sqliteInt.h
@@ -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 *);
diff --git a/src/wherecode.c b/src/wherecode.c
index e660b3538..0f6d32024 100644
--- a/src/wherecode.c
+++ b/src/wherecode.c
@@ -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
index 000000000..63f2c8939
--- /dev/null
+++ b/test/rowvalue3.test
@@ -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
+