From 2410243e9d27b5f1f5862368eb52ded8898250d8 Mon Sep 17 00:00:00 2001 From: drh Date: Fri, 17 Nov 2017 21:01:04 +0000 Subject: [PATCH] Improved fix for ticket [da78413751863] that does not require disabling the query flattener as was done in [005d5b870625]. This also makes the code generator for vector IN operators a little easier to understand. FossilOrigin-Name: 723f1be3d4a905a6a16333f8ef3e1067dcd4944497b303033c49946fc37c780f --- manifest | 14 ++-- manifest.uuid | 2 +- src/wherecode.c | 170 +++++++++++++++++++++++++++--------------------- 3 files changed, 104 insertions(+), 82 deletions(-) diff --git a/manifest b/manifest index 5615a01c67..b8aae7e011 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Add\ssome\smissing\s"finish_test"\slines\sto\sthe\send\sof\stest\sscripts. -D 2017-11-17T20:07:19.718 +C Improved\sfix\sfor\sticket\s[da78413751863]\sthat\sdoes\snot\srequire\sdisabling\sthe\nquery\sflattener\sas\swas\sdone\sin\s[005d5b870625].\s\sThis\salso\smakes\sthe\scode\ngenerator\sfor\svector\sIN\soperators\sa\slittle\seasier\sto\sunderstand. +D 2017-11-17T21:01:04.980 F Makefile.in b142eb20482922153ebc77b261cdfd0a560ed05a81e9f6d9a2b0e8192922a1d2 F Makefile.linux-gcc 7bc79876b875010e8c8f9502eb935ca92aa3c434 F Makefile.msc a55372a22454e742ba7c8f6edf05b83213ec01125166ad7dcee0567e2f7fc81b @@ -555,7 +555,7 @@ F src/wal.h 8de5d2d3de0956d6f6cb48c83a4012d5f227b8fe940f3a349a4b7e85ebcb492a F src/walker.c da987a20d40145c0a03c07d8fefcb2ed363becc7680d0500d9c79915591f5b1f F src/where.c 031a80bcafe93934fd7052f3031c9e7eb36b61754c6c84d6bf0833184abad3db F src/whereInt.h 82c04c5075308abbac59180c8bad5ecb45b07453981f60a53f3c7dee21e1e971 -F src/wherecode.c 8605c0ca0c34d4692011cf68a5f4cfc85352c1df917dc6eada320cecc4f5ea73 +F src/wherecode.c 611fcabd05592ed2febd7d182f9621425b0466c5232d70e0981c842d429356d5 F src/whereexpr.c 427ea8e96ec24f2a7814c67b8024ad664a9c7656264c4566c34743cb23186e46 F test/8_3_names.test ebbb5cd36741350040fd28b432ceadf495be25b2 F test/affinity2.test a6d901b436328bd67a79b41bb0ac2663918fe3bd @@ -1677,7 +1677,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 00c328317473cee8fd7bd92c58409a356337b727cfa562bd8de59350d978769c -R 7da8d3fa9a019a6fbc5c0bb38be51e2d -U dan -Z 96ccfa676c7df4dbf371fdb6ce8e1bc3 +P c21406ab3281480d3eddca0cdf5aea3abc224425ee52c10eed3ff702a0ae5c26 +R 04b5e48559de23ef7902e501e8dcaac8 +U drh +Z cc87a690f1d098ebb337ab16a1f38777 diff --git a/manifest.uuid b/manifest.uuid index de5b268438..99c0990a4c 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -c21406ab3281480d3eddca0cdf5aea3abc224425ee52c10eed3ff702a0ae5c26 \ No newline at end of file +723f1be3d4a905a6a16333f8ef3e1067dcd4944497b303033c49946fc37c780f \ No newline at end of file diff --git a/src/wherecode.c b/src/wherecode.c index cc2759eeaf..40c5f41182 100644 --- a/src/wherecode.c +++ b/src/wherecode.c @@ -377,25 +377,100 @@ static void updateRangeAffinityStr( } } -#ifdef SQLITE_DEBUG -/* Return true if the pSub ExprList is a subset of pMain. The terms -** of pSub can be in a different order from pMain. The only requirement -** is that every term in pSub must exist somewhere in pMain. + +/* +** pX is an expression of the form: (vector) IN (SELECT ...) +** In other words, it is a vector IN operator with a SELECT clause on the +** LHS. But not all terms in the vector are indexable and the terms might +** not be in the correct order for indexing. ** -** Return false if pSub contains any term that is not found in pMain. +** This routine makes a copy of the input pX expression and then adjusts +** the vector on the LHS with corresponding changes to the SELECT so that +** the vector contains only index terms and those terms are in the correct +** order. The modified IN expression is returned. The caller is responsible +** for deleting the returned expression. +** +** Example: +** +** CREATE TABLE t1(a,b,c,d,e,f); +** CREATE INDEX t1x1 ON t1(e,c); +** SELECT * FROM t1 WHERE (a,b,c,d,e) IN (SELECT v,w,x,y,z FROM t2) +** \_______________________________________/ +** The pX expression +** +** Since only columns e and c can be used with the index, in that order, +** the modified IN expression that is returned will be: +** +** (e,c) IN (SELECT z,x FROM t2) +** +** The reduced pX is different from the original (obviously) and thus is +** only used for indexing, to improve performance. The original unaltered +** IN expression must also be run on each output row for correctness. */ -static int exprListSubset(ExprList *pSub, ExprList *pMain){ - int i, j; - for(i=0; inExpr; i++){ - Expr *p = pSub->a[i].pExpr; - for(j=0; jnExpr; j++){ - if( sqlite3ExprCompare(0, p, pMain->a[j].pExpr, 0)==0 ) break; +static Expr *removeUnindexableInClauseTerms( + Parse *pParse, /* The parsing context */ + int iEq, /* Look at loop terms starting here */ + WhereLoop *pLoop, /* The current loop */ + Expr *pX /* The IN expression to be reduced */ +){ + sqlite3 *db = pParse->db; + Expr *pNew = sqlite3ExprDup(db, pX, 0); + if( db->mallocFailed==0 ){ + ExprList *pOrigRhs = pNew->x.pSelect->pEList; /* Original unmodified RHS */ + ExprList *pOrigLhs = pNew->pLeft->x.pList; /* Original unmodified LHS */ + ExprList *pRhs = 0; /* New RHS after modifications */ + ExprList *pLhs = 0; /* New LHS after mods */ + int i; /* Loop counter */ + Select *pSelect; /* Pointer to the SELECT on the RHS */ + + for(i=iEq; inLTerm; i++){ + if( pLoop->aLTerm[i]->pExpr==pX ){ + int iField = pLoop->aLTerm[i]->iField - 1; + assert( pOrigRhs->a[iField].pExpr!=0 ); + pRhs = sqlite3ExprListAppend(pParse, pRhs, pOrigRhs->a[iField].pExpr); + pOrigRhs->a[iField].pExpr = 0; + assert( pOrigLhs->a[iField].pExpr!=0 ); + pLhs = sqlite3ExprListAppend(pParse, pLhs, pOrigLhs->a[iField].pExpr); + pOrigLhs->a[iField].pExpr = 0; + } } - if( j>=pMain->nExpr ) return 0; + sqlite3ExprListDelete(db, pOrigRhs); + sqlite3ExprListDelete(db, pOrigLhs); + pNew->pLeft->x.pList = pLhs; + pNew->x.pSelect->pEList = pRhs; + if( pLhs && pLhs->nExpr==1 ){ + /* Take care here not to generate a TK_VECTOR containing only a + ** single value. Since the parser never creates such a vector, some + ** of the subroutines do not handle this case. */ + Expr *p = pLhs->a[0].pExpr; + pLhs->a[0].pExpr = 0; + sqlite3ExprDelete(db, pNew->pLeft); + pNew->pLeft = p; + } + pSelect = pNew->x.pSelect; + if( pSelect->pOrderBy ){ + /* If the SELECT statement has an ORDER BY clause, zero the + ** iOrderByCol variables. These are set to non-zero when an + ** ORDER BY term exactly matches one of the terms of the + ** result-set. Since the result-set of the SELECT statement may + ** have been modified or reordered, these variables are no longer + ** set correctly. Since setting them is just an optimization, + ** it's easiest just to zero them here. */ + ExprList *pOrderBy = pSelect->pOrderBy; + for(i=0; inExpr; i++){ + pOrderBy->a[i].u.x.iOrderByCol = 0; + } + } + +#if 0 + printf("For indexing, change the IN expr:\n"); + sqlite3TreeViewExpr(0, pX, 0); + printf("Into:\n"); + sqlite3TreeViewExpr(0, pNew, 0); +#endif } - return 1; + return pNew; } -#endif /* SQLITE_DEBUG */ /* @@ -460,76 +535,23 @@ static int codeEqualityTerm( } } for(i=iEq;inLTerm; i++){ - if( ALWAYS(pLoop->aLTerm[i]) && pLoop->aLTerm[i]->pExpr==pX ) nEq++; + assert( pLoop->aLTerm[i]!=0 ); + if( pLoop->aLTerm[i]->pExpr==pX ) nEq++; } if( (pX->flags & EP_xIsSelect)==0 || pX->x.pSelect->pEList->nExpr==1 ){ eType = sqlite3FindInIndex(pParse, pX, IN_INDEX_LOOP, 0, 0); }else{ - Select *pSelect = pX->x.pSelect; sqlite3 *db = pParse->db; - u16 savedDbOptFlags = db->dbOptFlags; - ExprList *pOrigRhs = pSelect->pEList; - ExprList *pOrigLhs = pX->pLeft->x.pList; - ExprList *pRhs = 0; /* New Select.pEList for RHS */ - ExprList *pLhs = 0; /* New pX->pLeft vector */ - - for(i=iEq;inLTerm; i++){ - if( pLoop->aLTerm[i]->pExpr==pX ){ - int iField = pLoop->aLTerm[i]->iField - 1; - Expr *pNewRhs = sqlite3ExprDup(db, pOrigRhs->a[iField].pExpr, 0); - Expr *pNewLhs = sqlite3ExprDup(db, pOrigLhs->a[iField].pExpr, 0); - - pRhs = sqlite3ExprListAppend(pParse, pRhs, pNewRhs); - pLhs = sqlite3ExprListAppend(pParse, pLhs, pNewLhs); - } - } - - /* pRhs should be a subset of pOrigRhs (though possibly in a different - ** order). And pLhs should be a subset of pOrigLhs. To put it - ** another way: Every term of pRhs should exist in pOrigRhs and - ** every term of pLhs should exist in pOrigLhs. */ - assert( db->mallocFailed || exprListSubset(pRhs, pOrigRhs) ); - assert( db->mallocFailed || exprListSubset(pLhs, pOrigLhs) ); + pX = removeUnindexableInClauseTerms(pParse, iEq, pLoop, pX); if( !db->mallocFailed ){ - Expr *pLeft = pX->pLeft; - - if( pSelect->pOrderBy ){ - /* If the SELECT statement has an ORDER BY clause, zero the - ** iOrderByCol variables. These are set to non-zero when an - ** ORDER BY term exactly matches one of the terms of the - ** result-set. Since the result-set of the SELECT statement may - ** have been modified or reordered, these variables are no longer - ** set correctly. Since setting them is just an optimization, - ** it's easiest just to zero them here. */ - ExprList *pOrderBy = pSelect->pOrderBy; - for(i=0; inExpr; i++){ - pOrderBy->a[i].u.x.iOrderByCol = 0; - } - } - - /* Take care here not to generate a TK_VECTOR containing only a - ** single value. Since the parser never creates such a vector, some - ** of the subroutines do not handle this case. */ - if( pLhs->nExpr==1 ){ - pX->pLeft = pLhs->a[0].pExpr; - }else{ - pLeft->x.pList = pLhs; - aiMap = (int*)sqlite3DbMallocZero(pParse->db, sizeof(int) * nEq); - testcase( aiMap==0 ); - } - pSelect->pEList = pRhs; - db->dbOptFlags |= SQLITE_QueryFlattener; + aiMap = (int*)sqlite3DbMallocZero(pParse->db, sizeof(int)*nEq); eType = sqlite3FindInIndex(pParse, pX, IN_INDEX_LOOP, 0, aiMap); - db->dbOptFlags = savedDbOptFlags; - testcase( aiMap!=0 && aiMap[0]!=0 ); - pSelect->pEList = pOrigRhs; - pLeft->x.pList = pOrigLhs; - pX->pLeft = pLeft; + pTerm->pExpr->iTable = pX->iTable; } - sqlite3ExprListDelete(pParse->db, pLhs); - sqlite3ExprListDelete(pParse->db, pRhs); + sqlite3ExprDelete(db, pX); + pX = pTerm->pExpr; } if( eType==IN_INDEX_INDEX_DESC ){