From: dan Date: Thu, 14 Jan 2021 20:50:40 +0000 (+0000) Subject: Allow the planner to convert an EXISTS(SELECT...) expression in a WHERE clause to... X-Git-Tag: version-3.35.0~133^2~13 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=6bfc167a67586d465ed995ae6dd3216967fc83c6;p=thirdparty%2Fsqlite.git Allow the planner to convert an EXISTS(SELECT...) expression in a WHERE clause to the equivalent IN(...) expression in situations where this is possible and advantageous. FossilOrigin-Name: 9f90a88221d0694951c353e58efce342eb0b868b8ca6a4469c8205e5c7855b24 --- diff --git a/manifest b/manifest index b050b37ce0..a99b42e9cc 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C cli:\sOmit\ssurplus\swhitespace\sat\sthe\send\sof\slines\sin\s.explain\soutput. -D 2021-01-13T12:59:20.834 +C Allow\sthe\splanner\sto\sconvert\san\sEXISTS(SELECT...)\sexpression\sin\sa\sWHERE\sclause\sto\sthe\sequivalent\sIN(...)\sexpression\sin\ssituations\swhere\sthis\sis\spossible\sand\sadvantageous. +D 2021-01-14T20:50:40.571 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -630,7 +630,7 @@ F src/walker.c d9c4e454ebb9499e908aa62d55b8994c375cf5355ac78f60d45af17f7890701c F src/where.c 3d31871d03906312d7d71a9c0b28c97bcbaead7606dfc15f9b3d080b18702385 F src/whereInt.h 9a3f577619f07700d16d89eeb2f3d94d6b7ed7f109c2dacf0ce8844921549506 F src/wherecode.c a3a1aff30fe99a818d8e7c607980f033f40c68d890e03ed25838b9dbb7908bee -F src/whereexpr.c 3a463e156ea388083c501502229c2c7f4f5c6b5330ea59bdf40d6eb6e155a25f +F src/whereexpr.c e48e3edea45b4afabbf6f6ece9647734c828a20c49a8cc780ff4d69b42f55fa4 F src/window.c edd6f5e25a1e8f2b6f5305b7f5f7da7bb35f07f0d432b255b1d4c2fcab4205aa F test/8_3_names.test ebbb5cd36741350040fd28b432ceadf495be25b2 F test/affinity2.test ce1aafc86e110685b324e9a763eab4f2a73f737842ec3b687bd965867de90627 @@ -1895,7 +1895,10 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 -P db0ecfe66433f8915b6eb16d3735a4a0d0f8e0bbc395bc9c1364387506fc4657 -R 2ad612629a79dbad03e8b7f173945d59 -U drh -Z f407a95e341e109313c0922f7d2a229b +P 11e4eb095746602961a178044809a68a77ba7b367596997bef726e54062423d9 +R 1a9ea60bc9bb7dab69ccea9e0b8c40d8 +T *branch * exists-to-in +T *sym-exists-to-in * +T -sym-trunk * +U dan +Z d4d95c9878f460cd53a2ce8ada6afd2a diff --git a/manifest.uuid b/manifest.uuid index ccaf3daab6..e8520659ef 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -11e4eb095746602961a178044809a68a77ba7b367596997bef726e54062423d9 \ No newline at end of file +9f90a88221d0694951c353e58efce342eb0b868b8ca6a4469c8205e5c7855b24 \ No newline at end of file diff --git a/src/whereexpr.c b/src/whereexpr.c index a77eb36106..9326a57fcb 100644 --- a/src/whereexpr.c +++ b/src/whereexpr.c @@ -1007,6 +1007,202 @@ static int exprMightBeIndexed( return exprMightBeIndexed2(pFrom,mPrereq,aiCurCol,pExpr); } +/* +** Expression callback for exprUsesSrclist(). +*/ +static int exprUsesSrclistCb(Walker *p, Expr *pExpr){ + if( pExpr->op==TK_COLUMN ){ + SrcList *pSrc = p->u.pSrcList; + int iCsr = pExpr->iTable; + int ii; + for(ii=0; iinSrc; ii++){ + if( pSrc->a[ii].iCursor==iCsr ){ + return p->eCode ? WRC_Abort : WRC_Continue; + } + } + return p->eCode ? WRC_Continue : WRC_Abort; + } + return WRC_Continue; +} + +/* +** Select callback for exprUsesSrclist(). +*/ +static int exprUsesSrclistSelectCb(Walker *p, Select *pSelect){ + return WRC_Abort; +} + +/* +** This function always returns true if expression pExpr contains +** a sub-select. +** +** If there is no sub-select and bUses is 1, then true is returned +** if the expression contains at least one TK_COLUMN node that refers +** to a table in pSrc. +** +** Or, if there is no sub-select and bUses is 0, then true is returned +** if the expression contains at least one TK_COLUMN node that refers +** to a table that is not in pSrc. +*/ +static int exprUsesSrclist(SrcList *pSrc, Expr *pExpr, int bUses){ + Walker sWalker; + memset(&sWalker, 0, sizeof(Walker)); + sWalker.eCode = bUses; + sWalker.u.pSrcList = pSrc; + sWalker.xExprCallback = exprUsesSrclistCb; + sWalker.xSelectCallback = exprUsesSrclistSelectCb; + return (sqlite3WalkExpr(&sWalker, pExpr)==WRC_Abort); +} + +struct ExistsToInCtx { + SrcList *pSrc; + Expr *pInLhs; + Expr *pEq; + Expr **ppAnd; + Expr **ppParent; +}; + +static int exprExistsToInIter( + struct ExistsToInCtx *p, + Expr *pExpr, + Expr **ppExpr +){ + assert( ppExpr==0 || *ppExpr==pExpr ); + switch( pExpr->op ){ + case TK_AND: + p->ppParent = ppExpr; + if( exprExistsToInIter(p, pExpr->pLeft, &pExpr->pLeft) ) return 1; + p->ppParent = ppExpr; + if( exprExistsToInIter(p, pExpr->pRight, &pExpr->pRight) ) return 1; + break; + case TK_EQ: { + int bLeft = exprUsesSrclist(p->pSrc, pExpr->pLeft, 0); + int bRight = exprUsesSrclist(p->pSrc, pExpr->pRight, 0); + if( bLeft || bRight ){ + if( (bLeft && bRight) || p->pInLhs ) return 1; + p->pInLhs = bLeft ? pExpr->pLeft : pExpr->pRight; + p->pEq = pExpr; + p->ppAnd = p->ppParent; + if( exprUsesSrclist(p->pSrc, p->pInLhs, 1) ) return 1; + } + break; + } + default: + if( exprUsesSrclist(p->pSrc, pExpr, 0) ){ + return 1; + } + break; + } + + return 0; +} + +static Expr *exprAnalyzeExistsFindEq( + SrcList *pSrc, + Expr *pWhere, /* WHERE clause to traverse */ + Expr **ppEq, /* OUT: == node from WHERE clause */ + Expr ***pppAnd /* OUT: Pointer to parent of ==, if any */ +){ + struct ExistsToInCtx ctx; + memset(&ctx, 0, sizeof(ctx)); + ctx.pSrc = pSrc; + if( exprExistsToInIter(&ctx, pWhere, 0) ){ + return 0; + } + if( ppEq ) *ppEq = ctx.pEq; + if( pppAnd ) *pppAnd = ctx.ppAnd; + return ctx.pInLhs; +} + +/* +** Term idxTerm of the WHERE clause passed as the second argument is an +** EXISTS expression with a correlated SELECT statement on the RHS. +** This function analyzes the SELECT statement, and if possible adds an +** equivalent "? IN(SELECT...)" virtual term to the WHERE clause. +** +** For an EXISTS term such as the following: +** +** EXISTS (SELECT ... FROM WHERE = AND ) +** +** The virtual IN() term added is: +** +** IN (SELECT FROM WHERE ) +** +** The virtual term is only added if the following conditions are met: +** +** 1. The sub-select must not be an aggregate or use window functions, +** +** 2. The sub-select must not be a compound SELECT, +** +** 3. Expression must refer to at least one column from the outer +** query, and must not refer to any column from the inner query +** (i.e. from ). +** +** 4. and must not refer to any values from the outer query. +** In other words, once has been removed, the inner query +** must not be correlated. +** +*/ +static void exprAnalyzeExists( + SrcList *pSrc, /* the FROM clause */ + WhereClause *pWC, /* the WHERE clause */ + int idxTerm /* Index of the term to be analyzed */ +){ + Parse *pParse = pWC->pWInfo->pParse; + WhereTerm *pTerm = &pWC->a[idxTerm]; + Expr *pExpr = pTerm->pExpr; + Select *pSel = pExpr->x.pSelect; + Expr *pDup = 0; + Expr *pEq = 0; + Expr *pRet = 0; + Expr *pInLhs = 0; + Expr **ppAnd = 0; + int idxNew; + + assert( pExpr->op==TK_EXISTS ); + assert( (pExpr->flags & EP_VarSelect) && (pExpr->flags & EP_xIsSelect) ); + + if( (pSel->selFlags & SF_Aggregate) || pSel->pWin ) return; + if( pSel->pPrior ) return; + if( pSel->pWhere==0 ) return; + if( 0==exprAnalyzeExistsFindEq(pSel->pSrc, pSel->pWhere, 0, 0) ) return; + + pDup = sqlite3ExprDup(pParse->db, pExpr, 0); + if( pDup==0 ) return; + pSel = pDup->x.pSelect; + sqlite3ExprListDelete(pParse->db, pSel->pEList); + pSel->pEList = 0; + + pInLhs = exprAnalyzeExistsFindEq(pSel->pSrc, pSel->pWhere, &pEq, &ppAnd); + + assert( pDup->pLeft==0 ); + pDup->op = TK_IN; + pDup->pLeft = pInLhs; + pDup->flags &= ~EP_VarSelect; + pRet = (pInLhs==pEq->pLeft) ? pEq->pRight : pEq->pLeft; + pSel->pEList = sqlite3ExprListAppend(pParse, 0, pRet); + pEq->pLeft = 0; + pEq->pRight = 0; + if( ppAnd ){ + Expr *pAnd = *ppAnd; + Expr *pOther = (pAnd->pLeft==pEq) ? pAnd->pRight : pAnd->pLeft; + pAnd->pLeft = pAnd->pRight = 0; + sqlite3ExprDelete(pParse->db, pAnd); + *ppAnd = pOther; + }else{ + assert( pSel->pWhere==pEq ); + pSel->pWhere = 0; + } + sqlite3ExprDelete(pParse->db, pEq); + + idxNew = whereClauseInsert(pWC, pDup, TERM_VIRTUAL|TERM_DYNAMIC); + if( idxNew ){ + exprAnalyze(pSrc, pWC, idxNew); + markTermAsChild(pWC, idxNew, idxTerm); + pWC->a[idxTerm].wtFlags |= TERM_COPIED; + } +} + /* ** The input to this routine is an WhereTerm structure with only the ** "pExpr" field filled in. The job of this routine is to analyze the @@ -1418,6 +1614,10 @@ static void exprAnalyze( } #endif /* SQLITE_ENABLE_STAT4 */ + if( pExpr->op==TK_EXISTS && (pExpr->flags & EP_VarSelect) ){ + exprAnalyzeExists(pSrc, pWC, idxTerm); + } + /* Prevent ON clause terms of a LEFT JOIN from being used to drive ** an index for tables to the left of the join. */