]> git.ipfire.org Git - thirdparty/sqlite.git/commitdiff
Allow the planner to convert an EXISTS(SELECT...) expression in a WHERE clause to...
authordan <Dan Kennedy>
Thu, 14 Jan 2021 20:50:40 +0000 (20:50 +0000)
committerdan <Dan Kennedy>
Thu, 14 Jan 2021 20:50:40 +0000 (20:50 +0000)
FossilOrigin-Name: 9f90a88221d0694951c353e58efce342eb0b868b8ca6a4469c8205e5c7855b24

manifest
manifest.uuid
src/whereexpr.c

index b050b37ce04969b52d04d5a07151c8938937a7aa..a99b42e9cc0e729830ee653bbb114e1cbaa6f1f3 100644 (file)
--- 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
index ccaf3daab692b1ff120689de863986c12d776afc..e8520659ef47da643a86bc6d9e018c5db6f56c31 100644 (file)
@@ -1 +1 @@
-11e4eb095746602961a178044809a68a77ba7b367596997bef726e54062423d9
\ No newline at end of file
+9f90a88221d0694951c353e58efce342eb0b868b8ca6a4469c8205e5c7855b24
\ No newline at end of file
index a77eb36106cf36fa12467dd5bc1a57031cf79c67..9326a57fcb3d01cb0efbbb820e4f6f480a0b3f60 100644 (file)
@@ -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; ii<pSrc->nSrc; 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 <srclist> WHERE <e1> = <e2> AND <e3>)
+**
+** The virtual IN() term added is:
+**
+**     <e1> IN (SELECT <e2> FROM <srclist> WHERE <e3>)
+**
+** 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 <e1> 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 <srclist>).
+**
+**     4. <e2> and <e3> must not refer to any values from the outer query.
+**        In other words, once <e1> 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.
   */