From: dan Date: Thu, 28 Jul 2016 19:47:15 +0000 (+0000) Subject: Fix further issues with multi-column IN(...) operators. Also some error handling... X-Git-Tag: version-3.15.0~110^2~100 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=7b35a77b1a389d87cb5df356266b400e07d55afb;p=thirdparty%2Fsqlite.git Fix further issues with multi-column IN(...) operators. Also some error handling cases surrounding row values. FossilOrigin-Name: cc3f6542bec99b00d2698889bcea2aa0b587efa0 --- diff --git a/manifest b/manifest index 98dcf120d9..a1321d1be1 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Merge\slatest\strunk\schanges\sinto\sthis\sbranch. -D 2016-07-28T13:59:21.728 +C Fix\sfurther\sissues\swith\smulti-column\sIN(...)\soperators.\sAlso\ssome\serror\shandling\scases\ssurrounding\srow\svalues. +D 2016-07-28T19:47:15.803 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 82bc40bb27bc68b45303b560ab14eb16e39786a7 +F src/expr.c 473ce0ac25868b750f3bf9f59bbe7e8f06a0bdd5 F src/fault.c 160a0c015b6c2629d3899ed2daf63d75754a32bb F src/fkey.c bc4145347595b7770f9a598cff1c848302cf5413 F src/func.c 61a4114cf7004f10c542cfabbab9f2bcb9033045 @@ -466,7 +466,7 @@ F src/walker.c 0f142b5bd3ed2041fc52d773880748b212e63354 F src/where.c df58d6ad7878a08aa96c652ccbc6d0949f8fa472 F src/whereInt.h 14dd243e13b81cbb0a66063d38b70f93a7d6e613 F src/wherecode.c 3aff7683566af3428f865904aafa7efb1fbd8701 -F src/whereexpr.c b896f8ff6a53cbd3daaee84ec33e39098762bb46 +F src/whereexpr.c bc85d04c6751ca40cab99a10de308e44893c76b9 F test/8_3_names.test ebbb5cd36741350040fd28b432ceadf495be25b2 F test/affinity2.test a6d901b436328bd67a79b41bb0ac2663918fe3bd F test/aggerror.test a867e273ef9e3d7919f03ef4f0e8c0d2767944f2 @@ -1021,6 +1021,7 @@ F test/rowid.test 5b7509f384f4f6fae1af3c8c104c8ca299fea18d F test/rowvalue.test 979738b3d49f1d93e3fee56a71d4446217917abc F test/rowvalue2.test 8d5dfe75b8f4d1868a2f91f0356f20d36cba64ff F test/rowvalue3.test 587c1056016fa6b7a40a9bf6cb85d5443fa47d96 +F test/rowvalue4.test 4480898d62d6813e3e38d9d38c02b9a0be5f94be F test/rtree.test 0c8d9dd458d6824e59683c19ab2ffa9ef946f798 F test/run-wordcount.sh 891e89c4c2d16e629cd45951d4ed899ad12afc09 F test/savepoint.test c671fdbd34cd3bfe1518a777526ada595180cf8d @@ -1511,7 +1512,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 719a3b2035a335ca8b9704646b1d641011e3ea0e 6feff15cae8f0427be790355841d49c479c1c586 -R 3c287799720eaa100d938a8b5fd4645d +P 9685880f7baeb670739fdcf2d9df08e22abaa699 +R 66ab918b4b1a726fe3dea7b50bb8fdb0 U dan -Z 3b75e2ba5b9150b77f0261ae152b7ba4 +Z 5baa8fdab10ded1fdce6b30d35067148 diff --git a/manifest.uuid b/manifest.uuid index 88af2bcaa6..c951167b81 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -9685880f7baeb670739fdcf2d9df08e22abaa699 \ No newline at end of file +cc3f6542bec99b00d2698889bcea2aa0b587efa0 \ No newline at end of file diff --git a/src/expr.c b/src/expr.c index 26587309f2..2921898d35 100644 --- a/src/expr.c +++ b/src/expr.c @@ -1730,7 +1730,7 @@ int sqlite3IsRowid(const char *z){ ** */ #ifndef SQLITE_OMIT_SUBQUERY -static Select *isCandidateForInOpt(Expr *pX, int bNullSensitive){ +static Select *isCandidateForInOpt(Expr *pX){ Select *p; SrcList *pSrc; ExprList *pEList; @@ -1759,16 +1759,11 @@ static Select *isCandidateForInOpt(Expr *pX, int bNullSensitive){ if( IsVirtual(pTab) ) return 0; /* FROM clause not a virtual table */ pEList = p->pEList; - /* All SELECT results must be columns. If the SELECT returns more than - ** one column and the bNullSensitive flag is set, all returned columns - ** must be declared NOT NULL. */ + /* All SELECT results must be columns. */ for(i=0; inExpr; i++){ Expr *pRes = pEList->a[i].pExpr; if( pRes->op!=TK_COLUMN ) return 0; assert( pRes->iTable==pSrc->a[0].iCursor ); /* Not a correlated subquery */ - if( pEList->nExpr>1 && bNullSensitive ){ - if( pTab->aCol[pRes->iColumn].notNull==0 ) return 0; - } } return p; } @@ -1904,11 +1899,27 @@ int sqlite3FindInIndex( assert( pX->op==TK_IN ); mustBeUnique = (inFlags & IN_INDEX_LOOP)!=0; + /* If the RHS of this IN(...) operator is a SELECT, and if it matters + ** whether or not the SELECT result contains NULL values, check whether + ** or not NULL is actuall possible (it may not be, for example, due + ** to NOT NULL constraints in the schema). If no NULL values are possible, + ** set prRhsHasNull to 0 before continuing. + */ + if( prRhsHasNull && (pX->flags & EP_xIsSelect) ){ + int i; + ExprList *pEList = pX->x.pSelect->pEList; + for(i=0; inExpr; i++){ + if( sqlite3ExprCanBeNull(pEList->a[i].pExpr) ) break; + } + if( i==pEList->nExpr ){ + prRhsHasNull = 0; + } + } + /* Check to see if an existing table or index can be used to ** satisfy the query. This is preferable to generating a new - ** ephemeral table. - */ - if( pParse->nErr==0 && (p = isCandidateForInOpt(pX, prRhsHasNull!=0))!=0 ){ + ** ephemeral table. */ + if( pParse->nErr==0 && (p = isCandidateForInOpt(pX))!=0 ){ sqlite3 *db = pParse->db; /* Database connection */ Table *pTab; /* Table . */ i16 iDb; /* Database idx for pTab */ @@ -1996,16 +2007,16 @@ int sqlite3FindInIndex( assert( IN_INDEX_INDEX_DESC == IN_INDEX_INDEX_ASC+1 ); eType = IN_INDEX_INDEX_ASC + pIdx->aSortOrder[0]; - if( prRhsHasNull && nExpr==1 - && !pTab->aCol[pEList->a[0].pExpr->iColumn].notNull - ){ + if( prRhsHasNull ){ + *prRhsHasNull = ++pParse->nMem; #ifdef SQLITE_ENABLE_COLUMN_USED_MASK i64 mask = (1<nMem; - sqlite3SetHasNullFlag(v, iTab, *prRhsHasNull); + if( nExpr==1 ){ + sqlite3SetHasNullFlag(v, iTab, *prRhsHasNull); + } } sqlite3VdbeJumpHere(v, iAddr); } @@ -2352,6 +2363,32 @@ int sqlite3CodeSubselect( } #endif /* SQLITE_OMIT_SUBQUERY */ +#ifndef SQLITE_OMIT_SUBQUERY +/* +** Expr pIn is an IN(...) expression. This function checks that the +** sub-select on the RHS of the IN() operator has the same number of +** columns as the vector on the LHS. Or, if the RHS of the IN() is not +** a sub-query, that the LHS is a vector of size 1. +*/ +int sqlite3ExprCheckIN(Parse *pParse, Expr *pIn){ + int nVector = sqlite3ExprVectorSize(pIn->pLeft); + if( (pIn->flags & EP_xIsSelect) ){ + if( nVector!=pIn->x.pSelect->pEList->nExpr ){ + sqlite3SubselectError(pParse, pIn->x.pSelect->pEList->nExpr, nVector); + return 1; + } + }else if( nVector!=1 ){ + if( (pIn->pLeft->flags & EP_xIsSelect) ){ + sqlite3SubselectError(pParse, nVector, 1); + }else{ + sqlite3ErrorMsg(pParse, "invalid use of row value"); + } + return 1; + } + return 0; +} +#endif + #ifndef SQLITE_OMIT_SUBQUERY /* ** Generate code for an IN expression. @@ -2387,6 +2424,7 @@ static void sqlite3ExprCodeIN( Expr *pLeft = pExpr->pLeft; int i; + if( sqlite3ExprCheckIN(pParse, pExpr) ) return; nVector = sqlite3ExprVectorSize(pExpr->pLeft); aiMap = (int*)sqlite3DbMallocZero( pParse->db, nVector*(sizeof(int) + sizeof(char)) + 1 @@ -2394,6 +2432,7 @@ static void sqlite3ExprCodeIN( if( !aiMap ) return; zAff = (char*)&aiMap[nVector]; + /* 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, @@ -2479,8 +2518,15 @@ static void sqlite3ExprCodeIN( sqlite3ReleaseTempReg(pParse, regCkNull); }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 any value on the LHS is NULL, the result of the IN(...) operator + ** must be either false or NULL. If these two are handled identically, + ** test the LHS for NULLs and jump directly to destIfNull if any are + ** found. + ** + ** Otherwise, if NULL and false are handled differently, and the + ** IN(...) operation is not a vector operation, and the LHS of the + ** operator is NULL, then the result is false if the index is + ** completely empty, or NULL otherwise. */ if( destIfNull==destIfFalse ){ for(i=0; ipLeft, i); @@ -2497,70 +2543,62 @@ static void sqlite3ExprCodeIN( } if( eType==IN_INDEX_ROWID ){ - /* In this case, the RHS is the ROWID of table b-tree - */ + /* In this case, the RHS is the ROWID of table b-tree */ sqlite3VdbeAddOp3(v, OP_SeekRowid, pExpr->iTable, destIfFalse, r1); VdbeCoverage(v); - }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; iiTable, 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. - */ + /* In this case, the RHS is an index b-tree. Apply the comparison + ** affinities to each value on the LHS of the operator. */ 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 - ** contains no NULL values, then the result is 0. If the set - ** contains one or more NULL values, then the result of the - ** expression is also NULL. - */ - assert( destIfFalse!=destIfNull || rRhsHasNull==0 ); - if( rRhsHasNull==0 ){ + + if( nVector>1 && destIfNull!=destIfFalse ){ + int iIdx = pExpr->iTable; + int addr; + int addrNext; + + /* Search the index for the key. */ + addr = sqlite3VdbeAddOp4Int(v, OP_Found, iIdx, 0, r1, nVector); + + /* At this point the specified key is not present in the index, + ** so the result of the IN(..) operator must be either NULL or + ** 0. The vdbe code generated below figures out which. */ + addrNext = 1+sqlite3VdbeAddOp2(v, OP_Rewind, iIdx, destIfFalse); + + for(i=0; ipLeft, i); + if( sqlite3ExprCanBeNull(p) ){ + sqlite3VdbeAddOp2(v, OP_IsNull, r1+aiMap[i], destIfNull); + } + } + + }else if( rRhsHasNull==0 ){ /* This branch runs if it is known at compile time that the RHS - ** cannot contain NULL values. This happens as the result - ** of a "NOT NULL" constraint in the database schema. + ** cannot contain NULL values. This happens as a result + ** of "NOT NULL" constraints in the database schema. ** ** Also run this branch if NULL is equivalent to FALSE - ** for this particular IN operator. - */ + ** for this particular IN operator. */ sqlite3VdbeAddOp4Int( v, OP_NotFound, pExpr->iTable, destIfFalse, r1, nVector ); @@ -2571,7 +2609,7 @@ static void sqlite3ExprCodeIN( ** outcome. */ int addr1; - + /* First check to see if the LHS is contained in the RHS. If so, ** then the answer is TRUE the presence of NULLs in the RHS does ** not matter. If the LHS is not contained in the RHS, then the @@ -3003,7 +3041,7 @@ int sqlite3ExprCodeTarget(Parse *pParse, Expr *pExpr, int target){ int inReg = target; /* Results stored in register inReg */ int regFree1 = 0; /* If non-zero free this temporary register */ int regFree2 = 0; /* If non-zero free this temporary register */ - int r1, r2, r3, r4; /* Various register numbers */ + int r1, r2; /* Various register numbers */ sqlite3 *db = pParse->db; /* The database connection */ Expr tempX; /* Temporary expression node */ int p5 = 0; @@ -3404,30 +3442,6 @@ int sqlite3ExprCodeTarget(Parse *pParse, Expr *pExpr, int target){ */ case TK_BETWEEN: { exprCodeBetween(pParse, pExpr, target, 0, 0); -#if 0 - Expr *pLeft = pExpr->pLeft; - struct ExprList_item *pLItem = pExpr->x.pList->a; - Expr *pRight = pLItem->pExpr; - - r1 = sqlite3ExprCodeTemp(pParse, pLeft, ®Free1); - r2 = sqlite3ExprCodeTemp(pParse, pRight, ®Free2); - testcase( regFree1==0 ); - testcase( regFree2==0 ); - r3 = sqlite3GetTempReg(pParse); - r4 = sqlite3GetTempReg(pParse); - codeCompare(pParse, pLeft, pRight, OP_Ge, - r1, r2, r3, SQLITE_STOREP2); VdbeCoverage(v); - pLItem++; - pRight = pLItem->pExpr; - sqlite3ReleaseTempReg(pParse, regFree2); - r2 = sqlite3ExprCodeTemp(pParse, pRight, ®Free2); - testcase( regFree2==0 ); - codeCompare(pParse, pLeft, pRight, OP_Le, r1, r2, r4, SQLITE_STOREP2); - VdbeCoverage(v); - sqlite3VdbeAddOp3(v, OP_And, r3, r4, target); - sqlite3ReleaseTempReg(pParse, r3); - sqlite3ReleaseTempReg(pParse, r4); -#endif break; } case TK_SPAN: @@ -3908,7 +3922,7 @@ void sqlite3ExprIfTrue(Parse *pParse, Expr *pExpr, int dest, int jumpIfNull){ if( NEVER(v==0) ) return; /* Existence of VDBE checked by caller */ if( NEVER(pExpr==0) ) return; /* No way this can happen */ op = pExpr->op; - switch( op | (pExpr->pLeft ? (pExpr->pLeft->flags & EP_Vector) : 0)){ + switch( op ){ case TK_AND: { int d2 = sqlite3VdbeMakeLabel(v); testcase( jumpIfNull==0 ); @@ -3945,6 +3959,7 @@ void sqlite3ExprIfTrue(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); @@ -3991,6 +4006,7 @@ void sqlite3ExprIfTrue(Parse *pParse, Expr *pExpr, int dest, int jumpIfNull){ } #endif default: { + default_expr: if( exprAlwaysTrue(pExpr) ){ sqlite3VdbeGoto(v, dest); }else if( exprAlwaysFalse(pExpr) ){ diff --git a/src/whereexpr.c b/src/whereexpr.c index 67b2e67baa..9c9be99039 100644 --- a/src/whereexpr.c +++ b/src/whereexpr.c @@ -934,6 +934,7 @@ static void exprAnalyze( op = pExpr->op; if( op==TK_IN ){ assert( pExpr->pRight==0 ); + if( sqlite3ExprCheckIN(pParse, pExpr) ) return; if( ExprHasProperty(pExpr, EP_xIsSelect) ){ pTerm->prereqRight = exprSelectUsage(pMaskSet, pExpr->x.pSelect); }else{ diff --git a/test/rowvalue4.test b/test/rowvalue4.test new file mode 100644 index 0000000000..1dfcfb903a --- /dev/null +++ b/test/rowvalue4.test @@ -0,0 +1,48 @@ +# 2016 July 29 +# +# 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 syntax errors involving row-value constructors +# and sub-selects that return multiple arguments. +# + +set testdir [file dirname $argv0] +source $testdir/tester.tcl +set ::testprefix rowvalue4 + +do_execsql_test 0 { + CREATE TABLE t1(a, b, c); + CREATE INDEX t1bac ON t1(b, a, c); +} + +foreach {tn e} { + 1 "(1, 2, 3)" + 2 "1 + (1, 2)" + 3 "(1,2,3) == (1, 2)" +} { + do_catchsql_test 1.$tn "SELECT $e" {1 {invalid use of row value}} +} + +foreach {tn s error} { + 1 "SELECT * FROM t1 WHERE a = (1, 2)" {invalid use of row value} + 2 "SELECT * FROM t1 WHERE b = (1, 2)" {invalid use of row value} + 3 "SELECT * FROM t1 WHERE NOT (b = (1, 2))" {invalid use of row value} + 4 "SELECT * FROM t1 LIMIT (1, 2)" {invalid use of row value} + 5 "SELECT (a, b) IN (SELECT * FROM t1) FROM t1" + {sub-select returns 3 columns - expected 2} + + 6 "SELECT * FROM t1 WHERE (a, b) IN (SELECT * FROM t1)" + {sub-select returns 3 columns - expected 2} +} { + do_catchsql_test 2.$tn "$s" [list 1 $error] +} + +finish_test +