mirror of https://github.com/sqlite/sqlite
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
This commit is contained in:
parent
4097d0ca79
commit
ba00e30a3a
17
manifest
17
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
|
||||
|
|
|
@ -1 +1 @@
|
|||
60fed5cdd4a44aefa1b4d505adeb7936f2f0b952
|
||||
e2fd6f49b1b145bec09c581cc982b89429643ae9
|
217
src/expr.c
217
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
|
||||
|
@ -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, ®Free1);
|
||||
r2 = sqlite3ExprCodeTemp(pParse, pExpr->pRight, ®Free2);
|
||||
|
@ -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) ){
|
||||
|
|
|
@ -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 *);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
Loading…
Reference in New Issue