From 5525ac1049ad73e6ddb1ad444993cc32d9980e4e Mon Sep 17 00:00:00 2001 From: dan Date: Fri, 7 Jun 2024 21:00:42 +0000 Subject: [PATCH 01/18] Experimental optimization to rewrite a SELECT with an EXISTS(...) expression in the WHERE clause as a join. FossilOrigin-Name: 972a33db0b0e924b78d5309d222d8ea298bd59c72da14ea2d14e8e2caaad1e0a --- manifest | 24 ++++--- manifest.uuid | 2 +- src/build.c | 9 ++- src/resolve.c | 1 + src/select.c | 153 +++++++++++++++++++++++++++++++++++++++++++ src/sqliteInt.h | 1 + test/existsexpr.test | 116 ++++++++++++++++++++++++++++++++ 7 files changed, 292 insertions(+), 14 deletions(-) create mode 100644 test/existsexpr.test diff --git a/manifest b/manifest index f28f11b60c..6f4efd9bdc 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Allow\sthe\squery\splanner\saccess\sto\sthe\sargument\sof\sLIMIT\seven\sif\sthat\nargument\sis\sa\sbound\sparameter. -D 2024-06-06T23:56:36.923 +C Experimental\soptimization\sto\srewrite\sa\sSELECT\swith\san\sEXISTS(...)\sexpression\sin\sthe\sWHERE\sclause\sas\sa\sjoin. +D 2024-06-07T21:00:42.970 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -697,7 +697,7 @@ F src/btmutex.c 79a43670447eacc651519a429f6ece9fd638563cf95b469d6891185ddae2b522 F src/btree.c 8b42fc7d9efdb2df05c30e8f91ff6cfbd979724ae24bf90269028468b7a13333 F src/btree.h 55066f513eb095db935169dab1dc2f7c7a747ef223c533f5d4ad4dfed346cbd0 F src/btreeInt.h 98aadb6dcb77b012cab2574d6a728fad56b337fc946839b9898c4b4c969e30b6 -F src/build.c 237ccc0290d131d646be722f418e92ee0a38043aee25e7dfdc75f8ce5b3abe4e +F src/build.c 9e6a971156db6285f726fb03ddd9d47cb0a3648198b611f462021ac96fa24135 F src/callback.c db3a45e376deff6a16c0058163fe0ae2b73a2945f3f408ca32cf74960b28d490 F src/complete.c a3634ab1e687055cd002e11b8f43eb75c17da23e F src/ctime.c 64e4b1227b4ed123146f0aa2989131d1fbd9b927b11e80c9d58c6a68f9cd5ce3 @@ -753,14 +753,14 @@ F src/pragma.h e690a356c18e98414d2e870ea791c1be1545a714ba623719deb63f7f226d8bb7 F src/prepare.c d99931f45416652895e502328ca49fe782cfc4e1ebdcda13b3736d991ebf42ce F src/printf.c 8b250972305e14b365561be5117ed0fd364e4fd58968776df1ce64c6280b90f9 F src/random.c 606b00941a1d7dd09c381d3279a058d771f406c5213c9932bbd93d5587be4b9c -F src/resolve.c 7e8d23ce7cdbfedf351a47e759f2722e8182ca10fd7580be43f4ce1f1a228145 +F src/resolve.c 9c7786f032dea81487e7d94cb17849936f0e9b8891bfc91a6ac24ab193762804 F src/rowset.c 8432130e6c344b3401a8874c3cb49fefe6873fec593294de077afea2dce5ec97 -F src/select.c ea0b75fce45e1f2c22f50ed2b6e2ddd7f66640948d0fc79a397917b4236a74af +F src/select.c 669cfc0392c8a0bd43e5a199cba5f796aead3423e2c529d09148adfa57ae1152 F src/shell.c.in 77d12a0dab8724819e64a14d5fbaad91a934be2b22ad329708fba9ba78993f04 F src/sqlite.h.in cbd3e4177791a61c056fd81e37a5b21bb6c8cb2ea8cac558c625974673f50acf F src/sqlite3.rc 5121c9e10c3964d5755191c80dd1180c122fc3a8 F src/sqlite3ext.h 3f046c04ea3595d6bfda99b781926b17e672fd6d27da2ba6d8d8fc39981dcb54 -F src/sqliteInt.h 0fec1cf17d9e4c93baf29bf74b7d03de9425299443d4abd6989a004d6eb53b60 +F src/sqliteInt.h b81970d56dcb4ed5fc53841ef8b75d42bf511cb9533584546113bc131de832eb F src/sqliteLimit.h 6878ab64bdeb8c24a1d762d45635e34b96da21132179023338c93f820eee6728 F src/status.c cb11f8589a6912af2da3bb1ec509a94dd8ef27df4d4c1a97e0bcf2309ece972b F src/table.c 0f141b58a16de7e2fbe81c308379e7279f4c6b50eb08efeec5892794a0ba30d1 @@ -1114,6 +1114,7 @@ F test/exclusive.test 7ff63be7503990921838d5c9f77f6e33e68e48ed1a9d48cd28745bf650 F test/exclusive2.test 984090e8e9d1b331d2e8111daf6e5d61dda0bef7 F test/exec.test e949714dc127eaa5ecc7d723efec1ec27118fdd7 F test/exists.test 79a75323c78f02bbe9c251ea502a092f9ef63dac +F test/existsexpr.test a86e8300a2e0cd26109ba5b0d552dbc598b9ad4b79dfd26cac255839c9d5cf6a F test/expr.test 5c06696478212e5a04e04b043f993373f6f8e5ce5a80f5548a84703b123b6caa F test/expr2.test c27327ae9c017a7ff6280123f67aff496f912da74d78c888926d68b46ec75fd8 F test/exprfault.test da33606d799718e2f8e34efd0e5858884a1ad87f608774c552a7f5517cc27181 @@ -2195,8 +2196,11 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 -P f66608bd356efe492d1003663c2e1ccd7cfbf2d40393d256f8720149904ad2d5 e94dfe9928750dd98145d4d9920b298f7b0868703b487f86e0db77a41d53ccf9 -R f3d6aba1cc68a2224dd6f7df935a2c23 -U drh -Z b92cbf17475d91065d48b9fdd285839c +P c4a9dda2809c6e0e3d928e11e5553ead82cd9df551bcd35b11a7d869ef80ab8e +R 7e4221204288c4c4593aaccf38fb6e40 +T *branch * exists-to-join +T *sym-exists-to-join * +T -sym-trunk * +U dan +Z 8ebacdbf5bd3ee682966a7aab4962dc0 # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index a310670ce9..7a08d86957 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -c4a9dda2809c6e0e3d928e11e5553ead82cd9df551bcd35b11a7d869ef80ab8e \ No newline at end of file +972a33db0b0e924b78d5309d222d8ea298bd59c72da14ea2d14e8e2caaad1e0a \ No newline at end of file diff --git a/src/build.c b/src/build.c index 9747810e82..e6de79e775 100644 --- a/src/build.c +++ b/src/build.c @@ -5049,14 +5049,17 @@ void sqlite3SrcListIndexedBy(Parse *pParse, SrcList *p, Token *pIndexedBy){ ** are deleted by this function. */ SrcList *sqlite3SrcListAppendList(Parse *pParse, SrcList *p1, SrcList *p2){ - assert( p1 && p1->nSrc==1 ); + assert( p1 ); if( p2 ){ - SrcList *pNew = sqlite3SrcListEnlarge(pParse, p1, p2->nSrc, 1); + int nOld = p1->nSrc; + SrcList *pNew = sqlite3SrcListEnlarge(pParse, p1, p2->nSrc, nOld); if( pNew==0 ){ sqlite3SrcListDelete(pParse->db, p2); }else{ p1 = pNew; - memcpy(&p1->a[1], p2->a, p2->nSrc*sizeof(SrcItem)); + memcpy(&p1->a[nOld], p2->a, p2->nSrc*sizeof(SrcItem)); + assert( nOld==1 || (p2->nSrc==1 && (p2->a[0].fg.jointype&JT_LTORJ)==0) ); + assert( p1->nSrc>=2 ); sqlite3DbFree(pParse->db, p2); p1->a[0].fg.jointype |= (JT_LTORJ & p1->a[1].fg.jointype); } diff --git a/src/resolve.c b/src/resolve.c index d5c1515a74..9cb3662625 100644 --- a/src/resolve.c +++ b/src/resolve.c @@ -1367,6 +1367,7 @@ static int resolveExprStep(Walker *pWalker, Expr *pExpr){ if( nRef!=pNC->nRef ){ ExprSetProperty(pExpr, EP_VarSelect); pExpr->x.pSelect->selFlags |= SF_Correlated; + if( pExpr->op==TK_EXISTS ) pParse->bHasExists = 1; } pNC->ncFlags |= NC_Subquery; } diff --git a/src/select.c b/src/select.c index 9a0f2e40f5..332fba3b85 100644 --- a/src/select.c +++ b/src/select.c @@ -7283,6 +7283,152 @@ static int fromClauseTermCanBeCoroutine( return 1; } +/* +** sqlite3WalkExpr() callback used by exprReferencesTable(). +*/ +static int exprReferencesTableExprCb(Walker *pWalker, Expr *pExpr){ + if( pExpr->op==TK_COLUMN && pExpr->iTable==pWalker->u.iCur ){ + pWalker->eCode = 1; + } + return WRC_Continue; +} + +/* +** Return true if the expression passed as the first argument refers +** to cursor number iCur. Otherwise return false. +*/ +static int exprReferencesTable(Expr *pExpr, int iCur){ + Walker w; + memset(&w, 0, sizeof(w)); + w.u.iCur = iCur; + w.xExprCallback = exprReferencesTableExprCb; + w.xSelectCallback = sqlite3SelectWalkNoop; + sqlite3WalkExpr(&w, pExpr); + return w.eCode; +} + +/* +** Index pIdx is a UNIQUE index on the table accessed by cursor number +** iCsr. This function returns a mask of the index columns that are +** constrained to match a single, non-NULL value by the WHERE clause +** passed as the 4th argument. For example, if the index is: +** +** CREATE UNIQUE INDEX idx ON tbl(a, b, c); +** +** and pWhere: +** +** WHERE a=? AND c=? +** +** then this function returns 5. +*/ +static u64 findConstIdxTerms( + Parse *pParse, + int iCsr, + Index *pIdx, + Expr *pWhere +){ + u64 m = 0; + if( pWhere->op==TK_AND ){ + m = findConstIdxTerms(pParse, iCsr, pIdx, pWhere->pLeft); + m |= findConstIdxTerms(pParse, iCsr, pIdx, pWhere->pRight); + }else if( pWhere->op==TK_EQ ){ + Expr *pLeft = pWhere->pLeft; + Expr *pRight = pWhere->pRight; + if( pRight->op==TK_COLUMN && pRight->iTable==iCsr ){ + SWAP(Expr*, pLeft, pRight); + } + if( pLeft->op==TK_COLUMN + && pLeft->iTable==iCsr + && exprReferencesTable(pRight, iCsr)==0 + ){ + if( pIdx ){ + int ii; + for(ii=0; iinKeyCol; ii++){ + assert( pIdx->azColl[ii] ); + if( pLeft->iColumn==pIdx->aiColumn[ii] ){ + CollSeq *pColl = sqlite3ExprCompareCollSeq(pParse, pWhere); + if( sqlite3StrICmp(pColl->zName, pIdx->azColl[ii])==0 ){ + m |= ((u64)1 << ii); + break; + } + } + } + }else{ + if( pLeft->iColumn<0 ) m = 1; + } + } + } + return m; +} + +/* +** Argument pWhere is the WHERE clause belonging to SELECT statement p. This +** function attempts to transform expressions of the form: +** +** EXISTS (SELECT ...) +** +** into joins. For example, given +** +** CREATE TABLE sailors(sid INTEGER PRIMARY KEY, name TEXT); +** CREATE TABLE reserves(sid INT, day DATE, PRIMARY KEY(sid, day)); +** +** SELECT name FROM sailors AS S WHERE EXISTS ( +** SELECT * FROM reserves AS R WHERE S.sid = R.sid AND R.day = '2022-10-25' +** ); +** +** the SELECT statement may be transformed as follows: +** +** SELECT name FROM sailors AS S, reserves AS R +** WHERE S.sid = R.sid AND R.day = '2022-10-25'; +*/ +static void existsToJoin(Parse *pParse, Select *p, Expr *pWhere){ + if( pWhere && p->pSrc->nSrc>0 ){ + if( pWhere->op==TK_AND ){ + existsToJoin(pParse, p, pWhere->pLeft); + existsToJoin(pParse, p, pWhere->pRight); + } + else if( pWhere->op==TK_EXISTS && (pWhere->flags & EP_xIsSelect) ){ + Select *pSub = pWhere->x.pSelect; + if( pSub->pSrc->nSrc==1 + && (pSub->selFlags & (SF_Aggregate|SF_Correlated))==SF_Correlated + && pSub->pWhere + ){ + int bTransform = 0; /* True if EXISTS can be made into join */ + Table *pTab = pSub->pSrc->a[0].pTab; + int iCsr = pSub->pSrc->a[0].iCursor; + Index *pIdx; + if( HasRowid(pTab) && findConstIdxTerms(pParse, iCsr, 0,pSub->pWhere) ){ + bTransform = 1; + } + for(pIdx=pTab->pIndex; pIdx && bTransform==0; pIdx=pIdx->pNext){ + if( pIdx->onError && pIdx->nKeyCol<64 ){ + u64 c = findConstIdxTerms(pParse, iCsr, pIdx, pSub->pWhere); + if( c==(1 << pIdx->nKeyCol)-1 ){ + bTransform = 1; + } + } + } + if( bTransform ){ + p->pSrc = sqlite3SrcListAppendList(pParse, p->pSrc, pSub->pSrc); + pSub->pSrc = 0; + if( p->pWhere ){ + p->pWhere = sqlite3PExpr(pParse, TK_AND, p->pWhere, pSub->pWhere); + }else{ + p->pWhere = pSub->pWhere; + } + pSub->pWhere = 0; + + sqlite3SelectDelete(pParse->db, pSub); + memset(pWhere, 0, sizeof(*pWhere)); + pWhere->op = TK_INTEGER; + pWhere->u.iValue = 1; + ExprSetProperty(pWhere, EP_IntValue); + } + } + } + } +} + /* ** Generate code for the SELECT statement given in the p argument. ** @@ -7609,6 +7755,13 @@ int sqlite3Select( } #endif + /* If there may be an "EXISTS (SELECT ...)" in the WHERE clause, attempt + ** to change it into a join. */ + if( pParse->bHasExists ){ + existsToJoin(pParse, p, p->pWhere); + pTabList = p->pSrc; + } + /* Do the WHERE-clause constant propagation optimization if this is ** a join. No need to speed time on this operation for non-join queries ** as the equivalent optimization will be handled by query planner in diff --git a/src/sqliteInt.h b/src/sqliteInt.h index 8866b69ea5..8d104bc253 100644 --- a/src/sqliteInt.h +++ b/src/sqliteInt.h @@ -3827,6 +3827,7 @@ struct Parse { u8 prepFlags; /* SQLITE_PREPARE_* flags */ u8 withinRJSubrtn; /* Nesting level for RIGHT JOIN body subroutines */ u8 bHasWith; /* True if statement contains WITH */ + u8 bHasExists; /* Has a correlated "EXISTS (SELECT ....)" expression */ #if defined(SQLITE_DEBUG) || defined(SQLITE_COVERAGE_TEST) u8 earlyCleanup; /* OOM inside sqlite3ParserAddCleanup() */ #endif diff --git a/test/existsexpr.test b/test/existsexpr.test new file mode 100644 index 0000000000..4bd55db52d --- /dev/null +++ b/test/existsexpr.test @@ -0,0 +1,116 @@ +# 2024 May 25 +# +# 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. +# +#*********************************************************************** +# + +set testdir [file dirname $argv0] +source $testdir/tester.tcl +source $testdir/lock_common.tcl +set testprefix existsexpr + + +do_execsql_test 1.0 { + CREATE TABLE x1(a, b); + INSERT INTO x1 VALUES(1, 2), (3, 4), (5, 6); + CREATE UNIQUE INDEX x1a ON x1(a); + CREATE INDEX x1b ON x1(b); + + CREATE TABLE x2(x, y); + INSERT INTO x2 VALUES(1, 2), (3, 4), (5, 6); +} + +do_execsql_test 1.1 { + SELECT 1 WHERE EXISTS (SELECT 1 FROM x1 WHERE a=5) +} {1} + +do_execsql_test 1.2 { + SELECT * FROM x2 WHERE EXISTS (SELECT 1 FROM x1 WHERE a=x) +} {1 2 3 4 5 6} + +# With "a=x", the UNIQUE index means the EXIST can be transformed to a join. +# So no "SUBQUERY". With "b=x", the index is not UNIQUE and so there is a +# "SUBQUERY". +do_execsql_test 1.3.1 { + EXPLAIN QUERY PLAN + SELECT * FROM x2 WHERE EXISTS (SELECT 1 FROM x1 WHERE a=x) +} {~/SUBQUERY/} +do_execsql_test 1.3.2 { + EXPLAIN QUERY PLAN + SELECT * FROM x2 WHERE EXISTS (SELECT 1 FROM x1 WHERE b=x) +} {/SUBQUERY/} + +do_execsql_test 1.4.1 { + EXPLAIN QUERY PLAN + SELECT * FROM x2 WHERE x=1 AND EXISTS (SELECT 1 FROM x1 WHERE a=x) +} {~/SUBQUERY/} +do_execsql_test 1.4.2 { + EXPLAIN QUERY PLAN + SELECT * FROM x2 WHERE EXISTS (SELECT 1 FROM x1 WHERE a=x) AND y=2 +} {~/SUBQUERY/} + +do_execsql_test 1.5 { + SELECT count(*) FROM x2 WHERE EXISTS (SELECT 1 FROM x1 WHERE a=x) +} {3} + +#------------------------------------------------------------------------- +do_execsql_test 2.0 { + CREATE TABLE t1(a, b); + WITH s(i) AS ( + SELECT 1 UNION ALL SELECT i+1 FROM s WHERE i<1000 + ) INSERT INTO t1 SELECT i, i FROM s; + + CREATE TABLE t2(c, d); + WITH s(i) AS ( + SELECT 10 UNION ALL SELECT i+10 FROM s WHERE i<1000 + ) INSERT INTO t2 SELECT i, i FROM s; +} + +do_execsql_test 2.1 { + SELECT count(*) FROM t1; + SELECT count(*) FROM t2; +} {1000 100} + +do_execsql_test 2.2 { + SELECT count(*) FROM t1, t2 WHERE a=c; +} {100} + +do_execsql_test 2.3 { + SELECT count(*) FROM t1 WHERE EXISTS (SELECT 1 FROM t2 WHERE c=a) +} {100} +do_eqp_test 2.4 { + SELECT count(*) FROM t1 WHERE EXISTS (SELECT 1 FROM t2 WHERE c=a) +} {SCAN t1} + +do_execsql_test 2.5 { + CREATE UNIQUE INDEX t2c ON t2(c); + CREATE UNIQUE INDEX t1a ON t1(a); +} + +do_eqp_test 2.4.1 { + SELECT count(*) FROM t1 WHERE EXISTS (SELECT 1 FROM t2 WHERE c=a); +} {SCAN t1*SEARCH t2} +do_execsql_test 2.4.2 { + ANALYZE; +} +do_eqp_test 2.4.3 { + SELECT count(*) FROM t1 WHERE EXISTS (SELECT 1 FROM t2 WHERE c=a); +} {SCAN t2*SEARCH t1} +do_execsql_test 2.4.4 { + SELECT count(*) FROM t1 WHERE EXISTS (SELECT 1 FROM t2 WHERE c=a); +} {100} + +do_execsql_test 2.5.1 { + EXPLAIN QUERY PLAN + SELECT count(*) FROM t1 WHERE EXISTS (SELECT 1 FROM t2 WHERE t2.rowid=a); +} {~/SUBQUERY/} + +finish_test + + From b111f1cb2a32d0bc49436e90e162b82c5aa50060 Mon Sep 17 00:00:00 2001 From: dan Date: Sat, 8 Jun 2024 18:13:12 +0000 Subject: [PATCH 02/18] Add extra tests for the changes on this branch. FossilOrigin-Name: a6365c778f2a6d6fa78e8520553373898f382ce73bf6496533e26291648ef5d1 --- manifest | 18 ++- manifest.uuid | 2 +- src/select.c | 29 +++-- test/existsexpr.test | 258 +++++++++++++++++++++++++++++++++++++++++- test/existsfault.test | 49 ++++++++ 5 files changed, 328 insertions(+), 28 deletions(-) create mode 100644 test/existsfault.test diff --git a/manifest b/manifest index 6f4efd9bdc..55819af278 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Experimental\soptimization\sto\srewrite\sa\sSELECT\swith\san\sEXISTS(...)\sexpression\sin\sthe\sWHERE\sclause\sas\sa\sjoin. -D 2024-06-07T21:00:42.970 +C Add\sextra\stests\sfor\sthe\schanges\son\sthis\sbranch. +D 2024-06-08T18:13:12.691 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -755,7 +755,7 @@ F src/printf.c 8b250972305e14b365561be5117ed0fd364e4fd58968776df1ce64c6280b90f9 F src/random.c 606b00941a1d7dd09c381d3279a058d771f406c5213c9932bbd93d5587be4b9c F src/resolve.c 9c7786f032dea81487e7d94cb17849936f0e9b8891bfc91a6ac24ab193762804 F src/rowset.c 8432130e6c344b3401a8874c3cb49fefe6873fec593294de077afea2dce5ec97 -F src/select.c 669cfc0392c8a0bd43e5a199cba5f796aead3423e2c529d09148adfa57ae1152 +F src/select.c 06146768a11bfe8928fea0aabf8addedd4d146a8c4be70558f5c8ecdcc2ce338 F src/shell.c.in 77d12a0dab8724819e64a14d5fbaad91a934be2b22ad329708fba9ba78993f04 F src/sqlite.h.in cbd3e4177791a61c056fd81e37a5b21bb6c8cb2ea8cac558c625974673f50acf F src/sqlite3.rc 5121c9e10c3964d5755191c80dd1180c122fc3a8 @@ -1114,7 +1114,8 @@ F test/exclusive.test 7ff63be7503990921838d5c9f77f6e33e68e48ed1a9d48cd28745bf650 F test/exclusive2.test 984090e8e9d1b331d2e8111daf6e5d61dda0bef7 F test/exec.test e949714dc127eaa5ecc7d723efec1ec27118fdd7 F test/exists.test 79a75323c78f02bbe9c251ea502a092f9ef63dac -F test/existsexpr.test a86e8300a2e0cd26109ba5b0d552dbc598b9ad4b79dfd26cac255839c9d5cf6a +F test/existsexpr.test b101143b177d623c35166589d3e6772021dfe4f625ac318c91726a998939cd19 +F test/existsfault.test ff41c11f3052c1bbd4f8dd557802310026253d67d7c4e3a180c16d2f0862973e F test/expr.test 5c06696478212e5a04e04b043f993373f6f8e5ce5a80f5548a84703b123b6caa F test/expr2.test c27327ae9c017a7ff6280123f67aff496f912da74d78c888926d68b46ec75fd8 F test/exprfault.test da33606d799718e2f8e34efd0e5858884a1ad87f608774c552a7f5517cc27181 @@ -2196,11 +2197,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 -P c4a9dda2809c6e0e3d928e11e5553ead82cd9df551bcd35b11a7d869ef80ab8e -R 7e4221204288c4c4593aaccf38fb6e40 -T *branch * exists-to-join -T *sym-exists-to-join * -T -sym-trunk * +P 972a33db0b0e924b78d5309d222d8ea298bd59c72da14ea2d14e8e2caaad1e0a +R 20ea5e8daae9a69a6e09a05cc19a6a37 U dan -Z 8ebacdbf5bd3ee682966a7aab4962dc0 +Z cbb8e2989ade033c119abad0907d9ec3 # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index 7a08d86957..ce5b16061c 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -972a33db0b0e924b78d5309d222d8ea298bd59c72da14ea2d14e8e2caaad1e0a \ No newline at end of file +a6365c778f2a6d6fa78e8520553373898f382ce73bf6496533e26291648ef5d1 \ No newline at end of file diff --git a/src/select.c b/src/select.c index 332fba3b85..f21559e8da 100644 --- a/src/select.c +++ b/src/select.c @@ -7382,12 +7382,13 @@ static u64 findConstIdxTerms( ** WHERE S.sid = R.sid AND R.day = '2022-10-25'; */ static void existsToJoin(Parse *pParse, Select *p, Expr *pWhere){ - if( pWhere && p->pSrc->nSrc>0 ){ + if( pWhere && p->pSrc->nSrc>0 && pParse->db->mallocFailed==0 ){ if( pWhere->op==TK_AND ){ + Expr *pRight = pWhere->pRight; existsToJoin(pParse, p, pWhere->pLeft); - existsToJoin(pParse, p, pWhere->pRight); + existsToJoin(pParse, p, pRight); } - else if( pWhere->op==TK_EXISTS && (pWhere->flags & EP_xIsSelect) ){ + else if( pWhere->op==TK_EXISTS ){ Select *pSub = pWhere->x.pSelect; if( pSub->pSrc->nSrc==1 && (pSub->selFlags & (SF_Aggregate|SF_Correlated))==SF_Correlated @@ -7401,28 +7402,26 @@ static void existsToJoin(Parse *pParse, Select *p, Expr *pWhere){ bTransform = 1; } for(pIdx=pTab->pIndex; pIdx && bTransform==0; pIdx=pIdx->pNext){ - if( pIdx->onError && pIdx->nKeyCol<64 ){ + if( pIdx->onError && pIdx->nKeyCol<=63 ){ u64 c = findConstIdxTerms(pParse, iCsr, pIdx, pSub->pWhere); - if( c==(1 << pIdx->nKeyCol)-1 ){ + if( c==((u64)1 << pIdx->nKeyCol)-1 ){ bTransform = 1; } } } if( bTransform ){ - p->pSrc = sqlite3SrcListAppendList(pParse, p->pSrc, pSub->pSrc); - pSub->pSrc = 0; - if( p->pWhere ){ - p->pWhere = sqlite3PExpr(pParse, TK_AND, p->pWhere, pSub->pWhere); - }else{ - p->pWhere = pSub->pWhere; - } - pSub->pWhere = 0; - - sqlite3SelectDelete(pParse->db, pSub); memset(pWhere, 0, sizeof(*pWhere)); pWhere->op = TK_INTEGER; pWhere->u.iValue = 1; ExprSetProperty(pWhere, EP_IntValue); + + assert( p->pWhere!=0 ); + p->pSrc = sqlite3SrcListAppendList(pParse, p->pSrc, pSub->pSrc); + p->pWhere = sqlite3PExpr(pParse, TK_AND, p->pWhere, pSub->pWhere); + + pSub->pWhere = 0; + pSub->pSrc = 0; + sqlite3SelectDelete(pParse->db, pSub); } } } diff --git a/test/existsexpr.test b/test/existsexpr.test index 4bd55db52d..344f8cff6e 100644 --- a/test/existsexpr.test +++ b/test/existsexpr.test @@ -17,9 +17,8 @@ set testprefix existsexpr do_execsql_test 1.0 { - CREATE TABLE x1(a, b); + CREATE TABLE x1(a, b, PRIMARY KEY(a)) WITHOUT ROWID; INSERT INTO x1 VALUES(1, 2), (3, 4), (5, 6); - CREATE UNIQUE INDEX x1a ON x1(a); CREATE INDEX x1b ON x1(b); CREATE TABLE x2(x, y); @@ -111,6 +110,261 @@ do_execsql_test 2.5.1 { SELECT count(*) FROM t1 WHERE EXISTS (SELECT 1 FROM t2 WHERE t2.rowid=a); } {~/SUBQUERY/} +#------------------------------------------------------------------------- +proc do_subquery_test {tn bSub sql res} { + set r1(0) ~/SUBQUERY/ + set r1(1) /SUBQUERY/ + do_execsql_test $tn.1 "explain query plan $sql" $r1($bSub) + do_execsql_test $tn.2 $sql $res +} + +do_execsql_test 3.0 { + CREATE TABLE y1(a, b, c); + CREATE TABLE y2(x, y, z); + CREATE UNIQUE INDEX y2zy ON y2(z, y); + + INSERT INTO y1 VALUES(1, 1, 1); + INSERT INTO y1 VALUES(2, 2, 2); + INSERT INTO y1 VALUES(3, 3, 3); + INSERT INTO y1 VALUES(4, 4, 4); + + INSERT INTO y2 VALUES(1, 1, 1); + INSERT INTO y2 VALUES(3, 3, 3); +} + +do_subquery_test 3.1 0 { + SELECT * FROM y1 WHERE EXISTS ( + SELECT 1 FROM y2 WHERE z=a AND y=b AND x=z + ) +} { + 1 1 1 3 3 3 +} + +do_subquery_test 3.2 0 { + SELECT * FROM y1 WHERE EXISTS ( + SELECT 1 FROM y2 WHERE z=max(a,b) AND y=min(b,a) AND x=z + ) +} { + 1 1 1 3 3 3 +} + +do_subquery_test 3.3 0 { + SELECT * FROM y1 WHERE EXISTS ( + SELECT 1 FROM y2 WHERE z=max(a,b) AND y=min(b,a) AND c!=3 + ) +} { + 1 1 1 +} + +do_subquery_test 3.4 1 { + SELECT * FROM y1 WHERE EXISTS ( + SELECT 1 FROM y2 WHERE z=max(a,b) AND b=3 + ) +} { + 3 3 3 +} + +do_subquery_test 3.5 0 { + SELECT * FROM y1 WHERE EXISTS ( + SELECT 1 FROM y2 WHERE z=a-1 AND y=a-1 + ) +} { + 2 2 2 + 4 4 4 +} + +do_subquery_test 3.6 1 { + SELECT * FROM y1 WHERE EXISTS ( + SELECT 1 FROM y2 WHERE z=a-1 AND y+1=a + ) +} { + 2 2 2 + 4 4 4 +} + +do_subquery_test 3.7 1 { + SELECT * FROM y1 WHERE EXISTS ( + SELECT count(*) FROM y2 WHERE z=a-1 AND y=a-1 + ) +} { + 1 1 1 + 2 2 2 + 3 3 3 + 4 4 4 +} + +do_subquery_test 3.8 1 { + SELECT * FROM y1 WHERE EXISTS ( SELECT a+1 FROM y2 ) +} { + 1 1 1 + 2 2 2 + 3 3 3 + 4 4 4 +} + +do_subquery_test 3.9 1 { + SELECT * FROM y1 WHERE EXISTS ( + SELECT 1 FROM y2 one, y2 two WHERE one.z=a-1 AND one.y=a-1 + ) +} { + 2 2 2 + 4 4 4 +} + +#------------------------------------------------------------------------- +reset_db +do_execsql_test 4.0 { + CREATE TABLE tx1(a TEXT COLLATE nocase, b TEXT); + CREATE UNIQUE INDEX tx1ab ON tx1(a, b); + + INSERT INTO tx1 VALUES('a', 'a'); + INSERT INTO tx1 VALUES('B', 'b'); + INSERT INTO tx1 VALUES('c', 'c'); + INSERT INTO tx1 VALUES('D', 'd'); + INSERT INTO tx1 VALUES('e', 'e'); + + CREATE TABLE tx2(x, y); + INSERT INTO tx2 VALUES('A', 'a'); + INSERT INTO tx2 VALUES('b', 'b'); + INSERT INTO tx2 VALUES('C', 'c'); + INSERT INTO tx2 VALUES('D', 'd'); +} + +do_subquery_test 4.1 0 { + SELECT * FROM tx2 WHERE EXISTS ( + SELECT 1 FROM tx1 WHERE a=x AND b=y + ) +} { + A a + b b + C c + D d +} + +do_subquery_test 4.2 1 { + SELECT * FROM tx2 WHERE EXISTS ( + SELECT 1 FROM tx1 WHERE a=x AND b=y COLLATE nocase + ) +} { + A a + b b + C c + D d +} + +do_execsql_test 4.3 { + DROP INDEX tx1ab; + CREATE UNIQUE INDEX tx1ab ON tx1(a COLLATE binary, b); +} + +do_subquery_test 4.4 1 { + SELECT * FROM tx2 WHERE EXISTS ( + SELECT 1 FROM tx1 WHERE a=x AND b=y + ) +} { + A a + b b + C c + D d +} + +do_subquery_test 4.4 0 { + SELECT * FROM tx2 WHERE EXISTS ( + SELECT 1 FROM tx1 WHERE a=x COLLATE binary AND b=y + ) +} { + D d +} + +do_subquery_test 4.4 1 { + SELECT EXISTS ( SELECT x FROM tx1 ) FROM tx2 +} { + 1 1 1 1 +} + +do_subquery_test 4.4 1 { + SELECT (SELECT EXISTS ( SELECT x FROM tx1 ) WHERE 1) FROM tx2 +} { + 1 1 1 1 +} + +#------------------------------------------------------------------------- +proc cols {s f} { + set lCols [list] + for {set i $s} {$i<=$f} {incr i} { + lappend lCols [format "c%02d" $i] + } + join $lCols ", " +} +proc vals {n val} { + set lVal [list] + for {set i 0} {$i<$n} {incr i} { + lappend lVal $val + } + join $lVal ", " +} +proc exprs {s f} { + set lExpr [list] + for {set i $s} {$i<=$f} {incr i} { + lappend lExpr [format "c%02d = o" $i] + } + join $lExpr " AND " +} + + +do_execsql_test 5.0 " + CREATE TABLE a1( [cols 0 99] ); +" +do_execsql_test 5.1 " + -- 63 column index + CREATE UNIQUE INDEX a1idx1 ON a1( [cols 0 62] ); +" +do_execsql_test 5.2 " + -- 64 column index + CREATE UNIQUE INDEX a1idx2 ON a1( [cols 10 73] ); +" +do_execsql_test 5.2 " + -- 65 column index + CREATE UNIQUE INDEX a1idx3 ON a1( [cols 20 84] ); +" + +do_test 5.3 { + foreach v {1 2 3 4 5 6} { + execsql "INSERT INTO a1 VALUES( [vals 100 $v] )" + } +} {} + +do_execsql_test 5.4 { + CREATE TABLE a2(o); + INSERT INTO a2 VALUES(2), (5); +} + +do_subquery_test 5.5 0 " + SELECT o FROM a2 WHERE EXISTS ( + SELECT 1 FROM a1 WHERE [exprs 0 62] + ) +" { + 2 5 +} + +do_subquery_test 5.6 1 " + SELECT o FROM a2 WHERE EXISTS ( + SELECT 1 FROM a1 WHERE [exprs 10 73] + ) +" { + 2 5 +} + +do_subquery_test 5.7 1 " + SELECT o FROM a2 WHERE EXISTS ( + SELECT 1 FROM a1 WHERE [exprs 20 84] + ) +" { + 2 5 +} + + + finish_test diff --git a/test/existsfault.test b/test/existsfault.test new file mode 100644 index 0000000000..4b335d84cd --- /dev/null +++ b/test/existsfault.test @@ -0,0 +1,49 @@ +# 2024 May 25 +# +# 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. +# +#*********************************************************************** +# + +set testdir [file dirname $argv0] +source $testdir/tester.tcl +source $testdir/lock_common.tcl +source $testdir/malloc_common.tcl +set testprefix existsfault + +db close +sqlite3_shutdown +sqlite3_config_lookaside 0 0 +sqlite3_initialize +autoinstall_test_functions +sqlite3 db test.db + +do_execsql_test 1.0 { + CREATE TABLE x1(a, b); + INSERT INTO x1 VALUES(1, 2), (3, 4), (5, 6); + CREATE UNIQUE INDEX x1a ON x1(a); + CREATE INDEX x1b ON x1(b); + + CREATE TABLE x2(x, y); + INSERT INTO x2 VALUES(1, 2), (3, 4), (5, 6); +} + +do_faultsim_test 1 -faults oom* -prep { + sqlite3 db test.db + execsql { SELECT * FROM sqlite_schema } +} -body { + execsql { + SELECT count(*) FROM x2 WHERE EXISTS (SELECT 1 FROM x1 WHERE a=x) AND y!=11 + } +} -test { + faultsim_test_result {0 3} +} + +finish_test + + From dc9a262319d734b9a8023e34ded1f426459766bd Mon Sep 17 00:00:00 2001 From: dan Date: Mon, 10 Jun 2024 19:31:18 +0000 Subject: [PATCH 03/18] Fix a crash that could occur when the SELECT in an EXISTS(SELECT ...) used an unknown collation sequence. FossilOrigin-Name: f3009a6d7facd1ead520d588c5ad089db76d8641cd3dae076f2405492d7defcd --- manifest | 14 +++++++------- manifest.uuid | 2 +- src/select.c | 2 +- test/existsexpr.test | 12 ++++++++++++ 4 files changed, 21 insertions(+), 9 deletions(-) diff --git a/manifest b/manifest index 55819af278..24efae1938 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Add\sextra\stests\sfor\sthe\schanges\son\sthis\sbranch. -D 2024-06-08T18:13:12.691 +C Fix\sa\scrash\sthat\scould\soccur\swhen\sthe\sSELECT\sin\san\sEXISTS(SELECT\s...)\sused\san\sunknown\scollation\ssequence. +D 2024-06-10T19:31:18.296 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -755,7 +755,7 @@ F src/printf.c 8b250972305e14b365561be5117ed0fd364e4fd58968776df1ce64c6280b90f9 F src/random.c 606b00941a1d7dd09c381d3279a058d771f406c5213c9932bbd93d5587be4b9c F src/resolve.c 9c7786f032dea81487e7d94cb17849936f0e9b8891bfc91a6ac24ab193762804 F src/rowset.c 8432130e6c344b3401a8874c3cb49fefe6873fec593294de077afea2dce5ec97 -F src/select.c 06146768a11bfe8928fea0aabf8addedd4d146a8c4be70558f5c8ecdcc2ce338 +F src/select.c b3fa60ba7aab607daf0d15053a66073888f3dd8d6bb1c8000349bc22d85acd80 F src/shell.c.in 77d12a0dab8724819e64a14d5fbaad91a934be2b22ad329708fba9ba78993f04 F src/sqlite.h.in cbd3e4177791a61c056fd81e37a5b21bb6c8cb2ea8cac558c625974673f50acf F src/sqlite3.rc 5121c9e10c3964d5755191c80dd1180c122fc3a8 @@ -1114,7 +1114,7 @@ F test/exclusive.test 7ff63be7503990921838d5c9f77f6e33e68e48ed1a9d48cd28745bf650 F test/exclusive2.test 984090e8e9d1b331d2e8111daf6e5d61dda0bef7 F test/exec.test e949714dc127eaa5ecc7d723efec1ec27118fdd7 F test/exists.test 79a75323c78f02bbe9c251ea502a092f9ef63dac -F test/existsexpr.test b101143b177d623c35166589d3e6772021dfe4f625ac318c91726a998939cd19 +F test/existsexpr.test 7c0abb8383b492325d57e0546faf4ea52ad52c02877b797460330f17320a1314 F test/existsfault.test ff41c11f3052c1bbd4f8dd557802310026253d67d7c4e3a180c16d2f0862973e F test/expr.test 5c06696478212e5a04e04b043f993373f6f8e5ce5a80f5548a84703b123b6caa F test/expr2.test c27327ae9c017a7ff6280123f67aff496f912da74d78c888926d68b46ec75fd8 @@ -2197,8 +2197,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 -P 972a33db0b0e924b78d5309d222d8ea298bd59c72da14ea2d14e8e2caaad1e0a -R 20ea5e8daae9a69a6e09a05cc19a6a37 +P a6365c778f2a6d6fa78e8520553373898f382ce73bf6496533e26291648ef5d1 +R 4addb7a3e408fa9c0e91b39c04b20512 U dan -Z cbb8e2989ade033c119abad0907d9ec3 +Z fa336d8505987cef2cdbba1866644a8f # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index ce5b16061c..f3378323f6 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -a6365c778f2a6d6fa78e8520553373898f382ce73bf6496533e26291648ef5d1 \ No newline at end of file +f3009a6d7facd1ead520d588c5ad089db76d8641cd3dae076f2405492d7defcd \ No newline at end of file diff --git a/src/select.c b/src/select.c index f21559e8da..ac8b1b6ae0 100644 --- a/src/select.c +++ b/src/select.c @@ -7347,7 +7347,7 @@ static u64 findConstIdxTerms( assert( pIdx->azColl[ii] ); if( pLeft->iColumn==pIdx->aiColumn[ii] ){ CollSeq *pColl = sqlite3ExprCompareCollSeq(pParse, pWhere); - if( sqlite3StrICmp(pColl->zName, pIdx->azColl[ii])==0 ){ + if( pColl && sqlite3StrICmp(pColl->zName, pIdx->azColl[ii])==0 ){ m |= ((u64)1 << ii); break; } diff --git a/test/existsexpr.test b/test/existsexpr.test index 344f8cff6e..836560f2d0 100644 --- a/test/existsexpr.test +++ b/test/existsexpr.test @@ -363,6 +363,18 @@ do_subquery_test 5.7 1 " 2 5 } +#------------------------------------------------------------------------- +reset_db +do_execsql_test 6.0 { + CREATE TABLE t1(a, b UNIQUE, c UNIQUE); + CREATE TABLE t2(a INfEGER PRIMARY KEY, b); + CREATE UNIQUE INDEX t2b ON t2(b); +} + +do_catchsql_test 6.1 { + SELECT a FROM t1 WHERE EXISTS (SELECT 1 FROM t2 WHERE c COLLATE f = a) +} {1 {no such collation sequence: f}} + finish_test From df77e56cdeeb46fab3deb9ded4d5c0e873f57ee9 Mon Sep 17 00:00:00 2001 From: dan Date: Mon, 10 Jun 2024 19:45:33 +0000 Subject: [PATCH 04/18] Add missing calls to sqlite3exprSkipCollateAndLikely() to the enchancement on this branch. FossilOrigin-Name: 078537d057d638389e3ab3bc04bcac53f342c7bf1d8d75222296ef42d09e9ee7 --- manifest | 14 +++++++------- manifest.uuid | 2 +- src/select.c | 4 ++-- test/existsexpr.test | 29 +++++++++++++++++++++++++++++ 4 files changed, 39 insertions(+), 10 deletions(-) diff --git a/manifest b/manifest index 24efae1938..e555256083 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Fix\sa\scrash\sthat\scould\soccur\swhen\sthe\sSELECT\sin\san\sEXISTS(SELECT\s...)\sused\san\sunknown\scollation\ssequence. -D 2024-06-10T19:31:18.296 +C Add\smissing\scalls\sto\ssqlite3exprSkipCollateAndLikely()\sto\sthe\senchancement\son\sthis\sbranch. +D 2024-06-10T19:45:33.614 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -755,7 +755,7 @@ F src/printf.c 8b250972305e14b365561be5117ed0fd364e4fd58968776df1ce64c6280b90f9 F src/random.c 606b00941a1d7dd09c381d3279a058d771f406c5213c9932bbd93d5587be4b9c F src/resolve.c 9c7786f032dea81487e7d94cb17849936f0e9b8891bfc91a6ac24ab193762804 F src/rowset.c 8432130e6c344b3401a8874c3cb49fefe6873fec593294de077afea2dce5ec97 -F src/select.c b3fa60ba7aab607daf0d15053a66073888f3dd8d6bb1c8000349bc22d85acd80 +F src/select.c 9c65721098bad3f2cf266ef8e6aa8c0d57a691a48c075d5e8b1a44fa4fa7e351 F src/shell.c.in 77d12a0dab8724819e64a14d5fbaad91a934be2b22ad329708fba9ba78993f04 F src/sqlite.h.in cbd3e4177791a61c056fd81e37a5b21bb6c8cb2ea8cac558c625974673f50acf F src/sqlite3.rc 5121c9e10c3964d5755191c80dd1180c122fc3a8 @@ -1114,7 +1114,7 @@ F test/exclusive.test 7ff63be7503990921838d5c9f77f6e33e68e48ed1a9d48cd28745bf650 F test/exclusive2.test 984090e8e9d1b331d2e8111daf6e5d61dda0bef7 F test/exec.test e949714dc127eaa5ecc7d723efec1ec27118fdd7 F test/exists.test 79a75323c78f02bbe9c251ea502a092f9ef63dac -F test/existsexpr.test 7c0abb8383b492325d57e0546faf4ea52ad52c02877b797460330f17320a1314 +F test/existsexpr.test 6a917db207ca95a9da8733a2c3aefc8541f81d41169a69deb125a56686c73ed0 F test/existsfault.test ff41c11f3052c1bbd4f8dd557802310026253d67d7c4e3a180c16d2f0862973e F test/expr.test 5c06696478212e5a04e04b043f993373f6f8e5ce5a80f5548a84703b123b6caa F test/expr2.test c27327ae9c017a7ff6280123f67aff496f912da74d78c888926d68b46ec75fd8 @@ -2197,8 +2197,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 -P a6365c778f2a6d6fa78e8520553373898f382ce73bf6496533e26291648ef5d1 -R 4addb7a3e408fa9c0e91b39c04b20512 +P f3009a6d7facd1ead520d588c5ad089db76d8641cd3dae076f2405492d7defcd +R 93b078b964e586d7ff51ddb903d178e0 U dan -Z fa336d8505987cef2cdbba1866644a8f +Z 3d590655f4ffe6769b9e658507fd34f2 # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index f3378323f6..0d8e5a92ba 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -f3009a6d7facd1ead520d588c5ad089db76d8641cd3dae076f2405492d7defcd \ No newline at end of file +078537d057d638389e3ab3bc04bcac53f342c7bf1d8d75222296ef42d09e9ee7 \ No newline at end of file diff --git a/src/select.c b/src/select.c index ac8b1b6ae0..69a07528fa 100644 --- a/src/select.c +++ b/src/select.c @@ -7332,8 +7332,8 @@ static u64 findConstIdxTerms( m = findConstIdxTerms(pParse, iCsr, pIdx, pWhere->pLeft); m |= findConstIdxTerms(pParse, iCsr, pIdx, pWhere->pRight); }else if( pWhere->op==TK_EQ ){ - Expr *pLeft = pWhere->pLeft; - Expr *pRight = pWhere->pRight; + Expr *pLeft = sqlite3ExprSkipCollateAndLikely(pWhere->pLeft); + Expr *pRight = sqlite3ExprSkipCollateAndLikely(pWhere->pRight); if( pRight->op==TK_COLUMN && pRight->iTable==iCsr ){ SWAP(Expr*, pLeft, pRight); } diff --git a/test/existsexpr.test b/test/existsexpr.test index 836560f2d0..a5f60b944b 100644 --- a/test/existsexpr.test +++ b/test/existsexpr.test @@ -241,6 +241,35 @@ do_subquery_test 4.1 0 { D d } +do_subquery_test 4.1.1 0 { + SELECT * FROM tx2 WHERE EXISTS ( + SELECT 1 FROM tx1 WHERE (a COLLATE nocase)=x AND b=y + ) +} { + A a b b C c D d +} +do_subquery_test 4.1.2 0 { + SELECT * FROM tx2 WHERE EXISTS ( + SELECT 1 FROM tx1 WHERE a=x AND (b COLLATE binary)=y + ) +} { + A a b b C c D d +} +do_subquery_test 4.1.1 0 { + SELECT * FROM tx2 WHERE EXISTS ( + SELECT 1 FROM tx1 WHERE x=(a COLLATE nocase) AND b=y + ) +} { + A a b b C c D d +} +do_subquery_test 4.1.2 0 { + SELECT * FROM tx2 WHERE EXISTS ( + SELECT 1 FROM tx1 WHERE a=x AND y=(b COLLATE binary) + ) +} { + A a b b C c D d +} + do_subquery_test 4.2 1 { SELECT * FROM tx2 WHERE EXISTS ( SELECT 1 FROM tx1 WHERE a=x AND b=y COLLATE nocase From ddc62664bfb5e3237b20098b30e4a99815c322e0 Mon Sep 17 00:00:00 2001 From: drh <> Date: Tue, 11 Jun 2024 18:22:48 +0000 Subject: [PATCH 05/18] Provide the ability to disable the exists-to-join optimization using SQLITE_TESTCTRL_OPTIMIZATIONS. Provide tree-trace output for the optimization. FossilOrigin-Name: 33a3f327855b427ae6ba0057218d043a1417bc9d780728f47f23acdd836e1686 --- manifest | 14 +++++++------- manifest.uuid | 2 +- src/select.c | 11 +++++++++-- src/sqliteInt.h | 2 ++ 4 files changed, 19 insertions(+), 10 deletions(-) diff --git a/manifest b/manifest index 0b5c406027..13e08125d6 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Merge\strunk\senhancements\sinto\sthe\sexists-to-join\sbranch. -D 2024-06-11T17:37:36.892 +C Provide\sthe\sability\sto\sdisable\sthe\sexists-to-join\soptimization\susing\nSQLITE_TESTCTRL_OPTIMIZATIONS.\s\sProvide\stree-trace\soutput\sfor\sthe\soptimization. +D 2024-06-11T18:22:48.334 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -755,12 +755,12 @@ F src/printf.c 8b250972305e14b365561be5117ed0fd364e4fd58968776df1ce64c6280b90f9 F src/random.c 606b00941a1d7dd09c381d3279a058d771f406c5213c9932bbd93d5587be4b9c F src/resolve.c 9c7786f032dea81487e7d94cb17849936f0e9b8891bfc91a6ac24ab193762804 F src/rowset.c 8432130e6c344b3401a8874c3cb49fefe6873fec593294de077afea2dce5ec97 -F src/select.c 9c65721098bad3f2cf266ef8e6aa8c0d57a691a48c075d5e8b1a44fa4fa7e351 +F src/select.c decd5944639f604eba66bcf28161c1beb09b7762093aadc25313a0fe62ce9a5f F src/shell.c.in ad27d1d990e9e5fb7ae8fc38a717e91f55233714f59723e5618baf4a2a3d2b65 F src/sqlite.h.in cbd3e4177791a61c056fd81e37a5b21bb6c8cb2ea8cac558c625974673f50acf F src/sqlite3.rc 5121c9e10c3964d5755191c80dd1180c122fc3a8 F src/sqlite3ext.h 3f046c04ea3595d6bfda99b781926b17e672fd6d27da2ba6d8d8fc39981dcb54 -F src/sqliteInt.h c62b054b581abb051018ddc503bc292f3103db1e5ee16346f1495d46c07c5d3b +F src/sqliteInt.h b0c079cec6aa6a133af52489c14a4d8c2d750889594299d83eab1ae1b12979c0 F src/sqliteLimit.h 6878ab64bdeb8c24a1d762d45635e34b96da21132179023338c93f820eee6728 F src/status.c cb11f8589a6912af2da3bb1ec509a94dd8ef27df4d4c1a97e0bcf2309ece972b F src/table.c 0f141b58a16de7e2fbe81c308379e7279f4c6b50eb08efeec5892794a0ba30d1 @@ -2197,8 +2197,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 -P 078537d057d638389e3ab3bc04bcac53f342c7bf1d8d75222296ef42d09e9ee7 6935ac71bad3d36cc519f0325ae4447a674f257309d020cdc0741160fcce0580 -R b6e202e4f08c9d1d4b557c05911f57ed +P 5f25a9518a675efbd0525cc2f5595ee7bc7122be7cfecdf6b20c909185dea370 +R b17f1837a5067fb4548e9e703c412c5a U drh -Z 025c74f5e2115680217e065394f815d9 +Z e7e7ba1dc895b6e1b772b1a24ff19dc3 # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index 78990c287e..f005164c32 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -5f25a9518a675efbd0525cc2f5595ee7bc7122be7cfecdf6b20c909185dea370 \ No newline at end of file +33a3f327855b427ae6ba0057218d043a1417bc9d780728f47f23acdd836e1686 \ No newline at end of file diff --git a/src/select.c b/src/select.c index 69a07528fa..e546a664cd 100644 --- a/src/select.c +++ b/src/select.c @@ -7421,7 +7421,14 @@ static void existsToJoin(Parse *pParse, Select *p, Expr *pWhere){ pSub->pWhere = 0; pSub->pSrc = 0; - sqlite3SelectDelete(pParse->db, pSub); + sqlite3ParserAddCleanup(pParse, sqlite3SelectDeleteGeneric, pSub); +#if TREETRACE_ENABLED + if( sqlite3TreeTrace & 0x100000 ){ + TREETRACE(0x100000,pParse,p, + ("After EXISTS-to-JOIN optimization:\n")); + sqlite3TreeViewSelect(0, p, 0); + } +#endif } } } @@ -7756,7 +7763,7 @@ int sqlite3Select( /* If there may be an "EXISTS (SELECT ...)" in the WHERE clause, attempt ** to change it into a join. */ - if( pParse->bHasExists ){ + if( pParse->bHasExists && OptimizationEnabled(db,SQLITE_ExistsToJoin) ){ existsToJoin(pParse, p, p->pWhere); pTabList = p->pSrc; } diff --git a/src/sqliteInt.h b/src/sqliteInt.h index c976b928dd..6a1b22f92d 100644 --- a/src/sqliteInt.h +++ b/src/sqliteInt.h @@ -1126,6 +1126,7 @@ extern u32 sqlite3TreeTrace; ** 0x00020000 Transform DISTINCT into GROUP BY ** 0x00040000 SELECT tree dump after all code has been generated ** 0x00080000 NOT NULL strength reduction +** 0x00100000 EXISTS-to-JOIN optimization */ /* @@ -1924,6 +1925,7 @@ struct sqlite3 { #define SQLITE_Coroutines 0x02000000 /* Co-routines for subqueries */ #define SQLITE_NullUnusedCols 0x04000000 /* NULL unused columns in subqueries */ #define SQLITE_OnePass 0x08000000 /* Single-pass DELETE and UPDATE */ +#define SQLITE_ExistsToJoin 0x10000000 /* The EXISTS-to-JOIN optimization */ #define SQLITE_AllOpts 0xffffffff /* All optimizations */ /* From 0d92e66dfc42e23cf07fdf01b37f1bcbd0d8b6b9 Mon Sep 17 00:00:00 2001 From: dan Date: Wed, 12 Jun 2024 17:01:00 +0000 Subject: [PATCH 06/18] Do not attempt the exists-to-join optimization for expressions from the ON clause of joins. FossilOrigin-Name: 4666433cbd9af21c2e0440b10bcb39878624a39485e2bb514553b276acb8a401 --- manifest | 16 ++++++++-------- manifest.uuid | 2 +- src/select.c | 6 +++++- test/existsexpr.test | 17 +++++++++++++++++ 4 files changed, 31 insertions(+), 10 deletions(-) diff --git a/manifest b/manifest index 13e08125d6..beca64a81a 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Provide\sthe\sability\sto\sdisable\sthe\sexists-to-join\soptimization\susing\nSQLITE_TESTCTRL_OPTIMIZATIONS.\s\sProvide\stree-trace\soutput\sfor\sthe\soptimization. -D 2024-06-11T18:22:48.334 +C Do\snot\sattempt\sthe\sexists-to-join\soptimization\sfor\sexpressions\sfrom\sthe\sON\sclause\sof\sjoins. +D 2024-06-12T17:01:00.926 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -755,7 +755,7 @@ F src/printf.c 8b250972305e14b365561be5117ed0fd364e4fd58968776df1ce64c6280b90f9 F src/random.c 606b00941a1d7dd09c381d3279a058d771f406c5213c9932bbd93d5587be4b9c F src/resolve.c 9c7786f032dea81487e7d94cb17849936f0e9b8891bfc91a6ac24ab193762804 F src/rowset.c 8432130e6c344b3401a8874c3cb49fefe6873fec593294de077afea2dce5ec97 -F src/select.c decd5944639f604eba66bcf28161c1beb09b7762093aadc25313a0fe62ce9a5f +F src/select.c 757ea788e20f608d09bfef209d17bba87104cab920f80de77c92b38588ef64c5 F src/shell.c.in ad27d1d990e9e5fb7ae8fc38a717e91f55233714f59723e5618baf4a2a3d2b65 F src/sqlite.h.in cbd3e4177791a61c056fd81e37a5b21bb6c8cb2ea8cac558c625974673f50acf F src/sqlite3.rc 5121c9e10c3964d5755191c80dd1180c122fc3a8 @@ -1114,7 +1114,7 @@ F test/exclusive.test 7ff63be7503990921838d5c9f77f6e33e68e48ed1a9d48cd28745bf650 F test/exclusive2.test 984090e8e9d1b331d2e8111daf6e5d61dda0bef7 F test/exec.test e949714dc127eaa5ecc7d723efec1ec27118fdd7 F test/exists.test 79a75323c78f02bbe9c251ea502a092f9ef63dac -F test/existsexpr.test 6a917db207ca95a9da8733a2c3aefc8541f81d41169a69deb125a56686c73ed0 +F test/existsexpr.test bf1201621070e79c1060c1a8cf7d65d81fc6b336d94a371c63ecb08d357af2fd F test/existsfault.test ff41c11f3052c1bbd4f8dd557802310026253d67d7c4e3a180c16d2f0862973e F test/expr.test 5c06696478212e5a04e04b043f993373f6f8e5ce5a80f5548a84703b123b6caa F test/expr2.test c27327ae9c017a7ff6280123f67aff496f912da74d78c888926d68b46ec75fd8 @@ -2197,8 +2197,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 -P 5f25a9518a675efbd0525cc2f5595ee7bc7122be7cfecdf6b20c909185dea370 -R b17f1837a5067fb4548e9e703c412c5a -U drh -Z e7e7ba1dc895b6e1b772b1a24ff19dc3 +P 33a3f327855b427ae6ba0057218d043a1417bc9d780728f47f23acdd836e1686 +R 25697789cb2a6d5a3b4b0fd17d4887cd +U dan +Z c7e8e1ad09260243da52b623b6dff2b7 # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index f005164c32..26bcc7370b 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -33a3f327855b427ae6ba0057218d043a1417bc9d780728f47f23acdd836e1686 \ No newline at end of file +4666433cbd9af21c2e0440b10bcb39878624a39485e2bb514553b276acb8a401 \ No newline at end of file diff --git a/src/select.c b/src/select.c index e546a664cd..7a789196b6 100644 --- a/src/select.c +++ b/src/select.c @@ -7382,7 +7382,11 @@ static u64 findConstIdxTerms( ** WHERE S.sid = R.sid AND R.day = '2022-10-25'; */ static void existsToJoin(Parse *pParse, Select *p, Expr *pWhere){ - if( pWhere && p->pSrc->nSrc>0 && pParse->db->mallocFailed==0 ){ + if( pWhere + && !ExprHasProperty(pWhere, EP_OuterON|EP_InnerON) + && p->pSrc->nSrc>0 + && pParse->db->mallocFailed==0 + ){ if( pWhere->op==TK_AND ){ Expr *pRight = pWhere->pRight; existsToJoin(pParse, p, pWhere->pLeft); diff --git a/test/existsexpr.test b/test/existsexpr.test index a5f60b944b..81aa5799f0 100644 --- a/test/existsexpr.test +++ b/test/existsexpr.test @@ -404,6 +404,23 @@ do_catchsql_test 6.1 { SELECT a FROM t1 WHERE EXISTS (SELECT 1 FROM t2 WHERE c COLLATE f = a) } {1 {no such collation sequence: f}} +#------------------------------------------------------------------------- +reset_db +do_execsql_test 7.0 { + CREATE TABLE t1(x); + CREATE TABLE t2(y UNIQUE); + + INSERT INTO t1 VALUES(1), (2); + INSERT INTO t2 VALUES(1), (3); + + SELECT * FROM t1 one LEFT JOIN t1 two ON (one.x=two.x AND EXISTS ( + SELECT 1 FROM t2 WHERE y=one.x + )); +} { + 1 1 + 2 {} +} + finish_test From 62b954d545f52a8d59db654948f28ad6b6d899f6 Mon Sep 17 00:00:00 2001 From: dan Date: Fri, 14 Jun 2024 18:06:25 +0000 Subject: [PATCH 07/18] Add tests for the change on this branch. FossilOrigin-Name: ae19ff9ba819439fd107e745b6e8e503e5b68bfdb9da58b74035413704ad3caf --- manifest | 11 ++--- manifest.uuid | 2 +- test/existsexpr2.test | 96 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 103 insertions(+), 6 deletions(-) create mode 100644 test/existsexpr2.test diff --git a/manifest b/manifest index beca64a81a..5d3e1df5e2 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Do\snot\sattempt\sthe\sexists-to-join\soptimization\sfor\sexpressions\sfrom\sthe\sON\sclause\sof\sjoins. -D 2024-06-12T17:01:00.926 +C Add\stests\sfor\sthe\schange\son\sthis\sbranch. +D 2024-06-14T18:06:25.102 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -1115,6 +1115,7 @@ F test/exclusive2.test 984090e8e9d1b331d2e8111daf6e5d61dda0bef7 F test/exec.test e949714dc127eaa5ecc7d723efec1ec27118fdd7 F test/exists.test 79a75323c78f02bbe9c251ea502a092f9ef63dac F test/existsexpr.test bf1201621070e79c1060c1a8cf7d65d81fc6b336d94a371c63ecb08d357af2fd +F test/existsexpr2.test dc23e76389eff3d29f6488ff733012a3560cd67ec8cfaecbecd52cced5d5af11 F test/existsfault.test ff41c11f3052c1bbd4f8dd557802310026253d67d7c4e3a180c16d2f0862973e F test/expr.test 5c06696478212e5a04e04b043f993373f6f8e5ce5a80f5548a84703b123b6caa F test/expr2.test c27327ae9c017a7ff6280123f67aff496f912da74d78c888926d68b46ec75fd8 @@ -2197,8 +2198,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 -P 33a3f327855b427ae6ba0057218d043a1417bc9d780728f47f23acdd836e1686 -R 25697789cb2a6d5a3b4b0fd17d4887cd +P 4666433cbd9af21c2e0440b10bcb39878624a39485e2bb514553b276acb8a401 +R a04eb111a75540deb83a6459885c7580 U dan -Z c7e8e1ad09260243da52b623b6dff2b7 +Z 384cdb2f34ec5a8891b2a14db482f8c6 # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index 26bcc7370b..49d1e69efe 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -4666433cbd9af21c2e0440b10bcb39878624a39485e2bb514553b276acb8a401 \ No newline at end of file +ae19ff9ba819439fd107e745b6e8e503e5b68bfdb9da58b74035413704ad3caf \ No newline at end of file diff --git a/test/existsexpr2.test b/test/existsexpr2.test new file mode 100644 index 0000000000..f7644bf802 --- /dev/null +++ b/test/existsexpr2.test @@ -0,0 +1,96 @@ +# 2024 June 14 +# +# 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. +# +#*********************************************************************** +# + +set testdir [file dirname $argv0] +source $testdir/tester.tcl +source $testdir/lock_common.tcl +set testprefix existsexpr2 + + +do_execsql_test 1.0 { + CREATE TABLE x1(a, b, PRIMARY KEY(a)) WITHOUT ROWID; + INSERT INTO x1 VALUES(1, 2), (3, 4), (5, 6); + CREATE INDEX x1b ON x1(b); + + CREATE TABLE x2(x, y); + INSERT INTO x2 VALUES(1, 2), (3, 4), (5, 6); +} + +do_execsql_test 1.1 { + SELECT * FROM x1 WHERE EXISTS (SELECT 1 FROM x2 WHERE a!=123) +} {1 2 3 4 5 6} + +do_execsql_test 1.2 { + CREATE TABLE x3(u, v); + CREATE INDEX x3u ON x3(u); + INSERT INTO x3 VALUES + (1, 1), (1, 2), (1, 3), + (2, 1), (2, 2), (2, 3); +} + +do_execsql_test 1.3 { + SELECT * FROM x1 WHERE EXISTS ( + SELECT 1 FROM x3 WHERE u IN (1, 2, 3, 4) AND v=b + ); +} { + 1 2 +} + +#------------------------------------------------------------------------- +# +reset_db +do_execsql_test 2.0 { + CREATE TABLE t1(a, b, c); + CREATE INDEX t1ab ON t1(a,b); + + INSERT INTO t1 VALUES + ('abc', 1, 1), + ('abc', 2, 2), + ('abc', 2, 3), + + ('def', 1, 1), + ('def', 2, 2), + ('def', 2, 3); + + CREATE TABLE t2(x, y); + INSERT INTO t2 VALUES(1, 1), (2, 2), (3, 3); + + ANALYZE; + DELETE FROM sqlite_stat1; + INSERT INTO sqlite_stat1 VALUES('t1','t1ab','10000 5000 2'); + ANALYZE sqlite_master; +} + + +do_execsql_test 2.1 { + SELECT a,b,c FROM t1 WHERE b=2 ORDER BY a +} { + abc 2 2 + abc 2 3 + def 2 2 + def 2 3 +} + +do_execsql_test 2.2 { + SELECT x, y FROM t2 WHERE EXISTS ( + SELECT 1 FROM t1 WHERE b=x + ) +} { + 1 1 + 2 2 +} + + + +finish_test + + From 620a00ee32c64944289bc8ff8c37b23bb4e220a1 Mon Sep 17 00:00:00 2001 From: drh <> Date: Thu, 20 Jun 2024 12:07:25 +0000 Subject: [PATCH 08/18] Do not attempt the exists-to-join optimization if the FROM clause is full. FossilOrigin-Name: 8e3a1d2850337a902ab36b1d6a0dad4ae35030b71d1e15547f6e7487c1f86d18 --- manifest | 14 +++++++------- manifest.uuid | 2 +- src/select.c | 3 ++- 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/manifest b/manifest index 5d3e1df5e2..e374d68b4b 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Add\stests\sfor\sthe\schange\son\sthis\sbranch. -D 2024-06-14T18:06:25.102 +C Do\snot\sattempt\sthe\sexists-to-join\soptimization\sif\sthe\sFROM\sclause\sis\sfull. +D 2024-06-20T12:07:25.967 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -755,7 +755,7 @@ F src/printf.c 8b250972305e14b365561be5117ed0fd364e4fd58968776df1ce64c6280b90f9 F src/random.c 606b00941a1d7dd09c381d3279a058d771f406c5213c9932bbd93d5587be4b9c F src/resolve.c 9c7786f032dea81487e7d94cb17849936f0e9b8891bfc91a6ac24ab193762804 F src/rowset.c 8432130e6c344b3401a8874c3cb49fefe6873fec593294de077afea2dce5ec97 -F src/select.c 757ea788e20f608d09bfef209d17bba87104cab920f80de77c92b38588ef64c5 +F src/select.c 72883bd7dda5e67cdf097776ae5d83aab02fbad9c4fa3ae1e25516efb1401fd4 F src/shell.c.in ad27d1d990e9e5fb7ae8fc38a717e91f55233714f59723e5618baf4a2a3d2b65 F src/sqlite.h.in cbd3e4177791a61c056fd81e37a5b21bb6c8cb2ea8cac558c625974673f50acf F src/sqlite3.rc 5121c9e10c3964d5755191c80dd1180c122fc3a8 @@ -2198,8 +2198,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 -P 4666433cbd9af21c2e0440b10bcb39878624a39485e2bb514553b276acb8a401 -R a04eb111a75540deb83a6459885c7580 -U dan -Z 384cdb2f34ec5a8891b2a14db482f8c6 +P ae19ff9ba819439fd107e745b6e8e503e5b68bfdb9da58b74035413704ad3caf +R 2e2a21654277825e58e413fb627048c9 +U drh +Z 5d427b052ed84b4ca59ddb925bd74f93 # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index 49d1e69efe..3ef5d46fbc 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -ae19ff9ba819439fd107e745b6e8e503e5b68bfdb9da58b74035413704ad3caf \ No newline at end of file +8e3a1d2850337a902ab36b1d6a0dad4ae35030b71d1e15547f6e7487c1f86d18 diff --git a/src/select.c b/src/select.c index 7a789196b6..832b711b8b 100644 --- a/src/select.c +++ b/src/select.c @@ -7384,7 +7384,8 @@ static u64 findConstIdxTerms( static void existsToJoin(Parse *pParse, Select *p, Expr *pWhere){ if( pWhere && !ExprHasProperty(pWhere, EP_OuterON|EP_InnerON) - && p->pSrc->nSrc>0 + && p->pSrc->nSrc>0 + && p->pSrc->nSrcdb->mallocFailed==0 ){ if( pWhere->op==TK_AND ){ From 216676664d6ac56d83adc0f8214d032f6d6da1bf Mon Sep 17 00:00:00 2001 From: drh <> Date: Wed, 2 Jul 2025 13:19:24 +0000 Subject: [PATCH 09/18] If the LHS for an EXCEPT or INTERSECT operator is empty, skip over the computation of the RHS. FossilOrigin-Name: 13f096ae8a850a05d4a8684561066f11693ee66289e6568c44ef32822cca06f6 --- manifest | 21 ++++++++++++--------- manifest.uuid | 2 +- src/btree.c | 17 +++++++++++++++++ src/btree.h | 1 + src/select.c | 8 ++++++++ src/vdbe.c | 25 +++++++++++++++++++++++++ 6 files changed, 64 insertions(+), 10 deletions(-) diff --git a/manifest b/manifest index be3c7806be..e5a30cdedc 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Improve\sthe\sbytecode\sfor\sjoins\ssuch\sthat\sit\sexits\searlier\sif\sit\sdetermines\nthat\sno\soutput\sis\spossible. -D 2025-07-02T11:47:54.123 +C If\sthe\sLHS\sfor\san\sEXCEPT\sor\sINTERSECT\soperator\sis\sempty,\sskip\sover\sthe\ncomputation\sof\sthe\sRHS. +D 2025-07-02T13:19:24.955 F .fossil-settings/binary-glob 61195414528fb3ea9693577e1980230d78a1f8b0a54c78cf1b9b24d0a409ed6a x F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea @@ -726,8 +726,8 @@ F src/auth.c 54ab9c6c5803b47c0d45b76ce27eff22a03b4b1f767c5945a3a4eb13aa4c78dc F src/backup.c 5c97e8023aab1ce14a42387eb3ae00ba5a0644569e3476f38661fa6f824c3523 F src/bitvec.c e242d4496774dfc88fa278177dd23b607dce369ccafb3f61b41638eea2c9b399 F src/btmutex.c 30dada73a819a1ef5b7583786370dce1842e12e1ad941e4d05ac29695528daea -F src/btree.c 81fb44041929a605e293185bd4091cb24c468a108afe8ba1d93a81a8823de47f -F src/btree.h 18e5e7b2124c23426a283523e5f31a4bff029131b795bb82391f9d2f3136fc50 +F src/btree.c 20c243f1531c806b112dc5f2cd8e2e81618284d8d04897755338ca40e7eaf29b +F src/btree.h 53a4f92a4c79470c18f88e0cf42755e2a74833044b0b4610aa328083379c7767 F src/btreeInt.h 9c0f9ea5c9b5f4dcaea18111d43efe95f2ac276cd86d770dce10fd99ccc93886 F src/build.c 67c1db4c5e89a8519fe9b6dafc287f6bc3627696b5b8536dc5e06db570d8c05f F src/callback.c acae8c8dddda41ee85cfdf19b926eefe830f371069f8aadca3aa39adf5b1c859 @@ -785,7 +785,7 @@ F src/printf.c 71b6d3a0093bf23f473e25480ca0024e8962681506c75f4ffd3d343a3f0ab113 F src/random.c 606b00941a1d7dd09c381d3279a058d771f406c5213c9932bbd93d5587be4b9c F src/resolve.c d40fe18d7c2fd0339f5846ffcf7d6809866e380acdf14c76fb2af87e9fe13f64 F src/rowset.c 8432130e6c344b3401a8874c3cb49fefe6873fec593294de077afea2dce5ec97 -F src/select.c 0258c6c36372e64e3ecc5f9ed4ebb598c0688e112e28f9c9c0f9b61dc6500609 +F src/select.c 68b43b6a2de2440fb05aa4745e164fc2347d739b990b63ba494a7b18fc55b200 F src/shell.c.in 4f14a1f5196b6006abc8e73cc8fd6c1a62cf940396f8ba909d6711f35f074bb6 F src/sqlite.h.in 5c54f2461a1ea529bab8499148a2b238e2d4bb571d59e8ea5322d0c190abb693 F src/sqlite3.rc 015537e6ac1eec6c7050e17b616c2ffe6f70fca241835a84a4f0d5937383c479 @@ -852,7 +852,7 @@ F src/upsert.c 215328c3f91623c520ec8672c44323553f12caeb4f01b1090ebdca99fdf7b4f1 F src/utf.c 7267c3fb9e2467020507601af3354c2446c61f444387e094c779dccd5ca62165 F src/util.c 36fb1150062957280777655976f3f9a75db236cb8207a0770ceae8d5ec17fcd3 F src/vacuum.c 1bacdd0a81d2b5dc1c508fbf0d938c89fa78dd8d5b46ec92686d44030d4f4789 -F src/vdbe.c d2c13c0001f5ec40ec1f010a7f9badcff3ce09bbd7a78528682ad49d8566df54 +F src/vdbe.c 5fb85ec6f2f6cdfb8efac6c4dd4f7eed388dfb5041bf71e1c55f213eb0ed5421 F src/vdbe.h 93761ed7c6b8bc19524912fd9b9b587d41bf4f1d0ade650a00dadc10518d8958 F src/vdbeInt.h 0bc581a9763be385e3af715e8c0a503ba8422c2b7074922faf4bb0d6ae31b15e F src/vdbeapi.c f9a4881a9674fec3fa13da35044a1484d3c4b95f9ec891cc8ffb02ef2b7a41df @@ -2208,8 +2208,11 @@ F tool/version-info.c 3b36468a90faf1bbd59c65fd0eb66522d9f941eedd364fabccd7227350 F tool/warnings-clang.sh bbf6a1e685e534c92ec2bfba5b1745f34fb6f0bc2a362850723a9ee87c1b31a7 F tool/warnings.sh 1ad0169b022b280bcaaf94a7fa231591be96b514230ab5c98fbf15cd7df842dd F tool/win/sqlite.vsix deb315d026cc8400325c5863eef847784a219a2f -P d27d34fb746280e7e81335db4e195914b15403ef0da7b2955550553dd78fbe9a 63306e447efb3ac17e789a331ed3bb65459eb8b79d66e9c185ba3bd852f34ce3 -R b92b209ccf559f2de96150026272807a +P 2d2b61cba44a756a3a41ef5c95bbb0c0b7111f4b679c578fec9bd0b214cca367 +R c6049700927657d653d5f08e5f12e785 +T *branch * empty-table-optimizations +T *sym-empty-table-optimizations * +T -sym-trunk * U drh -Z 48380f5a742f367bd4ced6b10cb684be +Z de7126eae59d141656689f55b14fd34e # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index ed9a080860..3c576ca032 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -2d2b61cba44a756a3a41ef5c95bbb0c0b7111f4b679c578fec9bd0b214cca367 +13f096ae8a850a05d4a8684561066f11693ee66289e6568c44ef32822cca06f6 diff --git a/src/btree.c b/src/btree.c index 1806a914a6..1dc7b851f2 100644 --- a/src/btree.c +++ b/src/btree.c @@ -5667,6 +5667,23 @@ int sqlite3BtreeFirst(BtCursor *pCur, int *pRes){ return rc; } +/* Return true if the BTree pointed to by cursor pCur contains zero +** rows of content. Return false if the table contains content or if +** if there is some kind of error. This routine is used as an optimization. +** Returning false (a false negative) will always result in a correct +** answer, though perhaps more slowly. But a false positive (an incorrect +** return of true) can yield an incorrect answer. +*/ +int sqlite3BtreeIsEmpty(BtCursor *pCur){ + int rc; + + assert( cursorOwnsBtShared(pCur) ); + assert( sqlite3_mutex_held(pCur->pBtree->db->mutex) ); + if( pCur->eState==CURSOR_VALID ) return 0; + rc = moveToRoot(pCur); + return rc==SQLITE_EMPTY; +} + #ifdef SQLITE_DEBUG /* The cursors is CURSOR_VALID and has BTCF_AtLast set. Verify that ** this flags are true for a consistent database. diff --git a/src/btree.h b/src/btree.h index 241261dc6a..23fb127911 100644 --- a/src/btree.h +++ b/src/btree.h @@ -317,6 +317,7 @@ struct BtreePayload { int sqlite3BtreeInsert(BtCursor*, const BtreePayload *pPayload, int flags, int seekResult); int sqlite3BtreeFirst(BtCursor*, int *pRes); +int sqlite3BtreeIsEmpty(BtCursor *pCur); int sqlite3BtreeLast(BtCursor*, int *pRes); int sqlite3BtreeNext(BtCursor*, int flags); int sqlite3BtreeEof(BtCursor*); diff --git a/src/select.c b/src/select.c index 95b2925e36..e2dd24b3ef 100644 --- a/src/select.c +++ b/src/select.c @@ -3036,7 +3036,9 @@ static int multiSelect( int priorOp; /* The SRT_ operation to apply to prior selects */ Expr *pLimit; /* Saved values of p->nLimit */ int addr; + int emptyBypass = 0; /* IfEmpty opcode to bypass RHS */ SelectDest uniondest; + testcase( p->op==TK_EXCEPT ); testcase( p->op==TK_UNION ); @@ -3075,6 +3077,8 @@ static int multiSelect( */ if( p->op==TK_EXCEPT ){ op = SRT_Except; + emptyBypass = sqlite3VdbeAddOp1(v, OP_IfEmpty, unionTab); + VdbeCoverage(v); }else{ assert( p->op==TK_UNION ); op = SRT_Union; @@ -3117,6 +3121,7 @@ static int multiSelect( sqlite3VdbeResolveLabel(v, iCont); sqlite3VdbeAddOp2(v, OP_Next, unionTab, iStart); VdbeCoverage(v); sqlite3VdbeResolveLabel(v, iBreak); + if( emptyBypass ) sqlite3VdbeJumpHere(v, emptyBypass); sqlite3VdbeAddOp2(v, OP_Close, unionTab, 0); } break; @@ -3128,6 +3133,7 @@ static int multiSelect( int addr; SelectDest intersectdest; int r1; + int emptyBypass; /* INTERSECT is different from the others since it requires ** two temporary tables. Hence it has its own case. Begin @@ -3151,6 +3157,7 @@ static int multiSelect( if( rc ){ goto multi_select_end; } + emptyBypass = sqlite3VdbeAddOp1(v, OP_IfEmpty, tab1); VdbeCoverage(v); /* Code the current SELECT into temporary table "tab2" */ @@ -3194,6 +3201,7 @@ static int multiSelect( sqlite3VdbeAddOp2(v, OP_Next, tab1, iStart); VdbeCoverage(v); sqlite3VdbeResolveLabel(v, iBreak); sqlite3VdbeAddOp2(v, OP_Close, tab2, 0); + sqlite3VdbeJumpHere(v, emptyBypass); sqlite3VdbeAddOp2(v, OP_Close, tab1, 0); break; } diff --git a/src/vdbe.c b/src/vdbe.c index 0020b52bf0..895ef109c2 100644 --- a/src/vdbe.c +++ b/src/vdbe.c @@ -6398,6 +6398,31 @@ case OP_Rewind: { /* jump0, ncycle */ break; } +/* Opcode: IfEmpty P1 P2 * * * +** Synopsis: if( empty(P1) ) goto P2 +** +** Check to see if the b-tree table that cursor P1 references is empty +** and jump to P2 if it is. +*/ +case OP_IfEmpty: { /* jump */ + VdbeCursor *pC; + BtCursor *pCrsr; + int res; + + assert( pOp->p1>=0 && pOp->p1nCursor ); + assert( pOp->p2>=0 && pOp->p2nOp ); + + pC = p->apCsr[pOp->p1]; + assert( pC!=0 ); + assert( pC->eCurType==CURTYPE_BTREE ); + pCrsr = pC->uc.pCursor; + assert( pCrsr ); + res = sqlite3BtreeIsEmpty(pCrsr); + VdbeBranchTaken(res!=0,2); + if( res ) goto jump_to_p2; + break; +} + /* Opcode: Next P1 P2 P3 * P5 ** ** Advance cursor P1 so that it points to the next key/data pair in its From 33f327370541a8b66a355f588f9198b9241660a9 Mon Sep 17 00:00:00 2001 From: drh <> Date: Wed, 2 Jul 2025 14:53:48 +0000 Subject: [PATCH 10/18] Early exit if one of the inner loops of a 3-way or larger join is an empty table. FossilOrigin-Name: eaad6ac707a5960d9518d4049b7b1759e7512727ce87be3c402408144bda0a97 --- manifest | 23 ++++++++++------------- manifest.uuid | 2 +- src/btree.c | 25 ++++++++++++++++--------- src/btree.h | 2 +- src/select.c | 2 +- src/vdbe.c | 3 ++- src/where.c | 6 ++++++ 7 files changed, 37 insertions(+), 26 deletions(-) diff --git a/manifest b/manifest index e5a30cdedc..e505968bd6 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C If\sthe\sLHS\sfor\san\sEXCEPT\sor\sINTERSECT\soperator\sis\sempty,\sskip\sover\sthe\ncomputation\sof\sthe\sRHS. -D 2025-07-02T13:19:24.955 +C Early\sexit\sif\sone\sof\sthe\sinner\sloops\sof\sa\s3-way\sor\slarger\sjoin\sis\san\nempty\stable. +D 2025-07-02T14:53:48.889 F .fossil-settings/binary-glob 61195414528fb3ea9693577e1980230d78a1f8b0a54c78cf1b9b24d0a409ed6a x F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea @@ -726,8 +726,8 @@ F src/auth.c 54ab9c6c5803b47c0d45b76ce27eff22a03b4b1f767c5945a3a4eb13aa4c78dc F src/backup.c 5c97e8023aab1ce14a42387eb3ae00ba5a0644569e3476f38661fa6f824c3523 F src/bitvec.c e242d4496774dfc88fa278177dd23b607dce369ccafb3f61b41638eea2c9b399 F src/btmutex.c 30dada73a819a1ef5b7583786370dce1842e12e1ad941e4d05ac29695528daea -F src/btree.c 20c243f1531c806b112dc5f2cd8e2e81618284d8d04897755338ca40e7eaf29b -F src/btree.h 53a4f92a4c79470c18f88e0cf42755e2a74833044b0b4610aa328083379c7767 +F src/btree.c 96fcbe6db6af625e5a14c34d8f13688d1d22c5f924a436b12395aaf09ec65944 +F src/btree.h e823c46d87f63d904d735a24b76146d19f51f04445ea561f71cc3382fd1307f0 F src/btreeInt.h 9c0f9ea5c9b5f4dcaea18111d43efe95f2ac276cd86d770dce10fd99ccc93886 F src/build.c 67c1db4c5e89a8519fe9b6dafc287f6bc3627696b5b8536dc5e06db570d8c05f F src/callback.c acae8c8dddda41ee85cfdf19b926eefe830f371069f8aadca3aa39adf5b1c859 @@ -785,7 +785,7 @@ F src/printf.c 71b6d3a0093bf23f473e25480ca0024e8962681506c75f4ffd3d343a3f0ab113 F src/random.c 606b00941a1d7dd09c381d3279a058d771f406c5213c9932bbd93d5587be4b9c F src/resolve.c d40fe18d7c2fd0339f5846ffcf7d6809866e380acdf14c76fb2af87e9fe13f64 F src/rowset.c 8432130e6c344b3401a8874c3cb49fefe6873fec593294de077afea2dce5ec97 -F src/select.c 68b43b6a2de2440fb05aa4745e164fc2347d739b990b63ba494a7b18fc55b200 +F src/select.c 0d30f9718594b6c208373c28c174432dda5c555bd18a5a3d37fd623fdb64aafb F src/shell.c.in 4f14a1f5196b6006abc8e73cc8fd6c1a62cf940396f8ba909d6711f35f074bb6 F src/sqlite.h.in 5c54f2461a1ea529bab8499148a2b238e2d4bb571d59e8ea5322d0c190abb693 F src/sqlite3.rc 015537e6ac1eec6c7050e17b616c2ffe6f70fca241835a84a4f0d5937383c479 @@ -852,7 +852,7 @@ F src/upsert.c 215328c3f91623c520ec8672c44323553f12caeb4f01b1090ebdca99fdf7b4f1 F src/utf.c 7267c3fb9e2467020507601af3354c2446c61f444387e094c779dccd5ca62165 F src/util.c 36fb1150062957280777655976f3f9a75db236cb8207a0770ceae8d5ec17fcd3 F src/vacuum.c 1bacdd0a81d2b5dc1c508fbf0d938c89fa78dd8d5b46ec92686d44030d4f4789 -F src/vdbe.c 5fb85ec6f2f6cdfb8efac6c4dd4f7eed388dfb5041bf71e1c55f213eb0ed5421 +F src/vdbe.c e505b8b879a330e8dafbe3ed9582eae2fc671b44a64748d1b58c07e4e0f527da F src/vdbe.h 93761ed7c6b8bc19524912fd9b9b587d41bf4f1d0ade650a00dadc10518d8958 F src/vdbeInt.h 0bc581a9763be385e3af715e8c0a503ba8422c2b7074922faf4bb0d6ae31b15e F src/vdbeapi.c f9a4881a9674fec3fa13da35044a1484d3c4b95f9ec891cc8ffb02ef2b7a41df @@ -867,7 +867,7 @@ F src/vxworks.h d2988f4e5a61a4dfe82c6524dd3d6e4f2ce3cdb9 F src/wal.c 20be6f0a25a80b7897cf2a5369bfd37ef198e6f0b6cdef16d83eee856056b159 F src/wal.h ba252daaa94f889f4b2c17c027e823d9be47ce39da1d3799886bbd51f0490452 F src/walker.c d5006d6b005e4ea7302ad390957a8d41ed83faa177e412f89bc5600a7462a014 -F src/where.c f58d41d0923eeb21cab8e4fc87a0b36c0724ff4f279ce95ab2731b4696b8e75a +F src/where.c e03e764dcbc205d40b01a033b744c583b090f29df56d2efaa1a12a79d8a1b053 F src/whereInt.h 8d94cb116c9e06205c3d5ac87af065fc044f8cf08bfdccd94b6ea1c1308e65da F src/wherecode.c 504f3c1270c3ffd51ebcdf7a31de08aa51a63b33a2ccdf8f5736afe3dfa73d45 F src/whereexpr.c 566ca4382e07a4ba1fd86c97ae0781cdf84004c7d9c59466bf5db75733548807 @@ -2208,11 +2208,8 @@ F tool/version-info.c 3b36468a90faf1bbd59c65fd0eb66522d9f941eedd364fabccd7227350 F tool/warnings-clang.sh bbf6a1e685e534c92ec2bfba5b1745f34fb6f0bc2a362850723a9ee87c1b31a7 F tool/warnings.sh 1ad0169b022b280bcaaf94a7fa231591be96b514230ab5c98fbf15cd7df842dd F tool/win/sqlite.vsix deb315d026cc8400325c5863eef847784a219a2f -P 2d2b61cba44a756a3a41ef5c95bbb0c0b7111f4b679c578fec9bd0b214cca367 -R c6049700927657d653d5f08e5f12e785 -T *branch * empty-table-optimizations -T *sym-empty-table-optimizations * -T -sym-trunk * +P 13f096ae8a850a05d4a8684561066f11693ee66289e6568c44ef32822cca06f6 +R 004cef124bcae73b595e9860d518af27 U drh -Z de7126eae59d141656689f55b14fd34e +Z 93bde8048b1cba4fc1e568dc39724e75 # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index 3c576ca032..3c9b3a2f20 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -13f096ae8a850a05d4a8684561066f11693ee66289e6568c44ef32822cca06f6 +eaad6ac707a5960d9518d4049b7b1759e7512727ce87be3c402408144bda0a97 diff --git a/src/btree.c b/src/btree.c index 1dc7b851f2..cef65846a3 100644 --- a/src/btree.c +++ b/src/btree.c @@ -5667,21 +5667,28 @@ int sqlite3BtreeFirst(BtCursor *pCur, int *pRes){ return rc; } -/* Return true if the BTree pointed to by cursor pCur contains zero -** rows of content. Return false if the table contains content or if -** if there is some kind of error. This routine is used as an optimization. -** Returning false (a false negative) will always result in a correct -** answer, though perhaps more slowly. But a false positive (an incorrect -** return of true) can yield an incorrect answer. +/* Set *pRes to 1 (true) if the BTree pointed to by cursor pCur contains zero +** rows of content. Set *pRes to 0 (false) if the table contains content. +** Return SQLITE_OK on success or some error code (ex: SQLITE_NOMEM) if +** something goes wrong. */ -int sqlite3BtreeIsEmpty(BtCursor *pCur){ +int sqlite3BtreeIsEmpty(BtCursor *pCur, int *pRes){ int rc; assert( cursorOwnsBtShared(pCur) ); assert( sqlite3_mutex_held(pCur->pBtree->db->mutex) ); - if( pCur->eState==CURSOR_VALID ) return 0; + if( pCur->eState==CURSOR_VALID ){ + *pRes = 0; + return SQLITE_OK; + } rc = moveToRoot(pCur); - return rc==SQLITE_EMPTY; + if( rc==SQLITE_EMPTY ){ + *pRes = 1; + rc = SQLITE_OK; + }else{ + *pRes = 0; + } + return rc; } #ifdef SQLITE_DEBUG diff --git a/src/btree.h b/src/btree.h index 23fb127911..96f4c4c607 100644 --- a/src/btree.h +++ b/src/btree.h @@ -317,7 +317,7 @@ struct BtreePayload { int sqlite3BtreeInsert(BtCursor*, const BtreePayload *pPayload, int flags, int seekResult); int sqlite3BtreeFirst(BtCursor*, int *pRes); -int sqlite3BtreeIsEmpty(BtCursor *pCur); +int sqlite3BtreeIsEmpty(BtCursor *pCur, int *pRes); int sqlite3BtreeLast(BtCursor*, int *pRes); int sqlite3BtreeNext(BtCursor*, int flags); int sqlite3BtreeEof(BtCursor*); diff --git a/src/select.c b/src/select.c index e2dd24b3ef..017fde9013 100644 --- a/src/select.c +++ b/src/select.c @@ -3099,6 +3099,7 @@ static int multiSelect( if( p->op==TK_UNION ){ p->nSelectRow = sqlite3LogEstAdd(p->nSelectRow, pPrior->nSelectRow); } + if( emptyBypass ) sqlite3VdbeJumpHere(v, emptyBypass); sqlite3ExprDelete(db, p->pLimit); p->pLimit = pLimit; p->iLimit = 0; @@ -3121,7 +3122,6 @@ static int multiSelect( sqlite3VdbeResolveLabel(v, iCont); sqlite3VdbeAddOp2(v, OP_Next, unionTab, iStart); VdbeCoverage(v); sqlite3VdbeResolveLabel(v, iBreak); - if( emptyBypass ) sqlite3VdbeJumpHere(v, emptyBypass); sqlite3VdbeAddOp2(v, OP_Close, unionTab, 0); } break; diff --git a/src/vdbe.c b/src/vdbe.c index 895ef109c2..9e456a1cd5 100644 --- a/src/vdbe.c +++ b/src/vdbe.c @@ -6417,7 +6417,8 @@ case OP_IfEmpty: { /* jump */ assert( pC->eCurType==CURTYPE_BTREE ); pCrsr = pC->uc.pCursor; assert( pCrsr ); - res = sqlite3BtreeIsEmpty(pCrsr); + rc = sqlite3BtreeIsEmpty(pCrsr, &res); + if( rc ) goto abort_due_to_error; VdbeBranchTaken(res!=0,2); if( res ) goto jump_to_p2; break; diff --git a/src/where.c b/src/where.c index ddf3f74996..28af499989 100644 --- a/src/where.c +++ b/src/where.c @@ -7134,6 +7134,12 @@ WhereInfo *sqlite3WhereBegin( sqlite3VdbeAddOp4Dup8(v, OP_ColumnsUsed, pTabItem->iCursor, 0, 0, (const u8*)&pTabItem->colUsed, P4_INT64); #endif + if( ii>=2 + && (pTabItem[0].fg.jointype & (JT_LTORJ|JT_LEFT))==0 + && pLevel->addrHalt==pWInfo->a[0].addrHalt + ){ + sqlite3VdbeAddOp2(v, OP_IfEmpty, pTabItem->iCursor, pLevel->addrHalt); + } }else{ sqlite3TableLock(pParse, iDb, pTab->tnum, 0, pTab->zName); } From eb27359e5e6e0258947ef85124229ca632d838af Mon Sep 17 00:00:00 2001 From: drh <> Date: Wed, 2 Jul 2025 17:43:59 +0000 Subject: [PATCH 11/18] Fix VDBE coverage FossilOrigin-Name: ff593a16d61cc5c588d1737deb822abb90b1759475a4cabfcf608978b1191487 --- manifest | 14 +++++++------- manifest.uuid | 2 +- src/select.c | 2 +- src/where.c | 1 + 4 files changed, 10 insertions(+), 9 deletions(-) diff --git a/manifest b/manifest index e505968bd6..e466a39061 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Early\sexit\sif\sone\sof\sthe\sinner\sloops\sof\sa\s3-way\sor\slarger\sjoin\sis\san\nempty\stable. -D 2025-07-02T14:53:48.889 +C Fix\sVDBE\scoverage +D 2025-07-02T17:43:59.873 F .fossil-settings/binary-glob 61195414528fb3ea9693577e1980230d78a1f8b0a54c78cf1b9b24d0a409ed6a x F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea @@ -785,7 +785,7 @@ F src/printf.c 71b6d3a0093bf23f473e25480ca0024e8962681506c75f4ffd3d343a3f0ab113 F src/random.c 606b00941a1d7dd09c381d3279a058d771f406c5213c9932bbd93d5587be4b9c F src/resolve.c d40fe18d7c2fd0339f5846ffcf7d6809866e380acdf14c76fb2af87e9fe13f64 F src/rowset.c 8432130e6c344b3401a8874c3cb49fefe6873fec593294de077afea2dce5ec97 -F src/select.c 0d30f9718594b6c208373c28c174432dda5c555bd18a5a3d37fd623fdb64aafb +F src/select.c fc2fe502971df1205a3231d3b3c8b0cc9ed4779cecbd060952c9558e22b6b02d F src/shell.c.in 4f14a1f5196b6006abc8e73cc8fd6c1a62cf940396f8ba909d6711f35f074bb6 F src/sqlite.h.in 5c54f2461a1ea529bab8499148a2b238e2d4bb571d59e8ea5322d0c190abb693 F src/sqlite3.rc 015537e6ac1eec6c7050e17b616c2ffe6f70fca241835a84a4f0d5937383c479 @@ -867,7 +867,7 @@ F src/vxworks.h d2988f4e5a61a4dfe82c6524dd3d6e4f2ce3cdb9 F src/wal.c 20be6f0a25a80b7897cf2a5369bfd37ef198e6f0b6cdef16d83eee856056b159 F src/wal.h ba252daaa94f889f4b2c17c027e823d9be47ce39da1d3799886bbd51f0490452 F src/walker.c d5006d6b005e4ea7302ad390957a8d41ed83faa177e412f89bc5600a7462a014 -F src/where.c e03e764dcbc205d40b01a033b744c583b090f29df56d2efaa1a12a79d8a1b053 +F src/where.c b11e56a24d01ae9b293f702c9de6dd16ced6b886be0d7cccb8bdeb62c8d92362 F src/whereInt.h 8d94cb116c9e06205c3d5ac87af065fc044f8cf08bfdccd94b6ea1c1308e65da F src/wherecode.c 504f3c1270c3ffd51ebcdf7a31de08aa51a63b33a2ccdf8f5736afe3dfa73d45 F src/whereexpr.c 566ca4382e07a4ba1fd86c97ae0781cdf84004c7d9c59466bf5db75733548807 @@ -2208,8 +2208,8 @@ F tool/version-info.c 3b36468a90faf1bbd59c65fd0eb66522d9f941eedd364fabccd7227350 F tool/warnings-clang.sh bbf6a1e685e534c92ec2bfba5b1745f34fb6f0bc2a362850723a9ee87c1b31a7 F tool/warnings.sh 1ad0169b022b280bcaaf94a7fa231591be96b514230ab5c98fbf15cd7df842dd F tool/win/sqlite.vsix deb315d026cc8400325c5863eef847784a219a2f -P 13f096ae8a850a05d4a8684561066f11693ee66289e6568c44ef32822cca06f6 -R 004cef124bcae73b595e9860d518af27 +P eaad6ac707a5960d9518d4049b7b1759e7512727ce87be3c402408144bda0a97 +R 19ae64c3af637e68252816fb2a35fc7d U drh -Z 93bde8048b1cba4fc1e568dc39724e75 +Z 04fda6f3c6a7b8123cd1480694a7568d # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index 3c9b3a2f20..faaf6064f7 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -eaad6ac707a5960d9518d4049b7b1759e7512727ce87be3c402408144bda0a97 +ff593a16d61cc5c588d1737deb822abb90b1759475a4cabfcf608978b1191487 diff --git a/src/select.c b/src/select.c index 017fde9013..d35103cddf 100644 --- a/src/select.c +++ b/src/select.c @@ -3189,7 +3189,7 @@ static int multiSelect( iBreak = sqlite3VdbeMakeLabel(pParse); iCont = sqlite3VdbeMakeLabel(pParse); computeLimitRegisters(pParse, p, iBreak); - sqlite3VdbeAddOp2(v, OP_Rewind, tab1, iBreak); VdbeCoverage(v); + sqlite3VdbeAddOp1(v, OP_Rewind, tab1); r1 = sqlite3GetTempReg(pParse); iStart = sqlite3VdbeAddOp2(v, OP_RowData, tab1, r1); sqlite3VdbeAddOp4Int(v, OP_NotFound, tab2, iCont, r1, 0); diff --git a/src/where.c b/src/where.c index 28af499989..4a0c3988cb 100644 --- a/src/where.c +++ b/src/where.c @@ -7139,6 +7139,7 @@ WhereInfo *sqlite3WhereBegin( && pLevel->addrHalt==pWInfo->a[0].addrHalt ){ sqlite3VdbeAddOp2(v, OP_IfEmpty, pTabItem->iCursor, pLevel->addrHalt); + VdbeCoverage(v); } }else{ sqlite3TableLock(pParse, iDb, pTab->tnum, 0, pTab->zName); From debc8f7bb7b62fb865251e140510d853b138b88b Mon Sep 17 00:00:00 2001 From: drh <> Date: Thu, 3 Jul 2025 00:17:27 +0000 Subject: [PATCH 12/18] Minor tweaks to the exists-to-join optimization. FossilOrigin-Name: 9cb600ad576c68647ed943a0773019312c5f01c9c1ca9ff0bf1214b03a531b48 --- manifest | 16 ++++++++-------- manifest.uuid | 2 +- src/resolve.c | 8 ++++++-- src/select.c | 2 +- src/shell.c.in | 1 + 5 files changed, 17 insertions(+), 12 deletions(-) diff --git a/manifest b/manifest index 8caec32f55..7774b2396b 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Merge\sin\sthe\sexists-to-join\soptimization\sthat\shas\sbeen\smodified\nto\srelax\sthe\srequirement\sof\shaving\san\sindexed\sjoin\sconstraint. -D 2025-07-02T20:46:02.299 +C Minor\stweaks\sto\sthe\sexists-to-join\soptimization. +D 2025-07-03T00:17:27.518 F .fossil-settings/binary-glob 61195414528fb3ea9693577e1980230d78a1f8b0a54c78cf1b9b24d0a409ed6a x F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea @@ -783,10 +783,10 @@ F src/pragma.c 30b535d0a66348df844ee36f890617b4cf45e9a22dcbc47ec3ca92909c50aaf1 F src/prepare.c 1832be043fce7d489959aae6f994c452d023914714c4d5457beaed51c0f3d126 F src/printf.c 71b6d3a0093bf23f473e25480ca0024e8962681506c75f4ffd3d343a3f0ab113 F src/random.c 606b00941a1d7dd09c381d3279a058d771f406c5213c9932bbd93d5587be4b9c -F src/resolve.c 9583e6eb8066400d377db9b130ebf46d461b59fccf1d018a5010d55b9bea217c +F src/resolve.c 5b14cad58bc21341fbaea76d7e781187559627a461745ece00c2655ba7c083ec F src/rowset.c 8432130e6c344b3401a8874c3cb49fefe6873fec593294de077afea2dce5ec97 -F src/select.c 30d7e450179a123c2a4dd9000592da196f5a6766688773cb11b921294afa11bf -F src/shell.c.in 4f14a1f5196b6006abc8e73cc8fd6c1a62cf940396f8ba909d6711f35f074bb6 +F src/select.c d956136defc62a9b11d61b18c5c3559231055c0ea32f6b11d13c740a7467aeb0 +F src/shell.c.in 73c0eeb7c265d59b99219d5aa055f412f07842088d8036b6d259927d85dd1bbf F src/sqlite.h.in 5c54f2461a1ea529bab8499148a2b238e2d4bb571d59e8ea5322d0c190abb693 F src/sqlite3.rc 015537e6ac1eec6c7050e17b616c2ffe6f70fca241835a84a4f0d5937383c479 F src/sqlite3ext.h 0bfd049bb2088cc44c2ad54f2079d1c6e43091a4e1ce8868779b75f6c1484f1e @@ -2211,8 +2211,8 @@ F tool/version-info.c 3b36468a90faf1bbd59c65fd0eb66522d9f941eedd364fabccd7227350 F tool/warnings-clang.sh bbf6a1e685e534c92ec2bfba5b1745f34fb6f0bc2a362850723a9ee87c1b31a7 F tool/warnings.sh 1ad0169b022b280bcaaf94a7fa231591be96b514230ab5c98fbf15cd7df842dd F tool/win/sqlite.vsix deb315d026cc8400325c5863eef847784a219a2f -P ff593a16d61cc5c588d1737deb822abb90b1759475a4cabfcf608978b1191487 9084a4c8726a2c7ba1c381886e29c7b86121d531282be0d63d5988d84f6f448d -R f60eba1acfa64dc97c08e5ab9333e9b4 +P 1c1aef2b7feae29066d0330699ab634ef41f5b60cdcd479a60cb1a5409553138 +R 690f317201b055306be94336494e9b81 U drh -Z 4c3d6c95c910f93bc2abb148fe548800 +Z 608e6ffa5985c7c132d45ebfd75d87f4 # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index 70909c9c3e..7b5464b41b 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -1c1aef2b7feae29066d0330699ab634ef41f5b60cdcd479a60cb1a5409553138 +9cb600ad576c68647ed943a0773019312c5f01c9c1ca9ff0bf1214b03a531b48 diff --git a/src/resolve.c b/src/resolve.c index 562ca5e008..bbd1021e0b 100644 --- a/src/resolve.c +++ b/src/resolve.c @@ -1358,11 +1358,16 @@ static int resolveExprStep(Walker *pWalker, Expr *pExpr){ return WRC_Prune; } #ifndef SQLITE_OMIT_SUBQUERY + case TK_EXISTS: + assert( ExprUseXSelect(pExpr) ); + pParse->bHasExists = 1; + /* no break */ deliberate_fall_through case TK_SELECT: - case TK_EXISTS: testcase( pExpr->op==TK_EXISTS ); #endif case TK_IN: { testcase( pExpr->op==TK_IN ); + testcase( pExpr->op==TK_EXISTS ); + testcase( pExpr->op==TK_SELECT ); if( ExprUseXSelect(pExpr) ){ int nRef = pNC->nRef; testcase( pNC->ncFlags & NC_IsCheck ); @@ -1379,7 +1384,6 @@ static int resolveExprStep(Walker *pWalker, Expr *pExpr){ if( nRef!=pNC->nRef ){ ExprSetProperty(pExpr, EP_VarSelect); pExpr->x.pSelect->selFlags |= SF_Correlated; - if( pExpr->op==TK_EXISTS ) pParse->bHasExists = 1; } pNC->ncFlags |= NC_Subquery; } diff --git a/src/select.c b/src/select.c index fd99dc91fe..9c79f6c67c 100644 --- a/src/select.c +++ b/src/select.c @@ -7437,7 +7437,7 @@ static SQLITE_NOINLINE void existsToJoin( else if( pWhere->op==TK_EXISTS ){ Select *pSub = pWhere->x.pSelect; if( pSub->pSrc->nSrc==1 - && (pSub->selFlags & (SF_Aggregate|SF_Correlated))==SF_Correlated + && (pSub->selFlags & SF_Aggregate)==0 && pSub->pWhere ){ memset(pWhere, 0, sizeof(*pWhere)); diff --git a/src/shell.c.in b/src/shell.c.in index 33dd30697d..5cda6a1a1b 100644 --- a/src/shell.c.in +++ b/src/shell.c.in @@ -11710,6 +11710,7 @@ static int do_meta_command(char *zLine, ShellState *p){ { 0x08000000, 1, "OnePass" }, { 0x10000000, 1, "OrderBySubq" }, { 0x20000000, 1, "StarQuery" }, + { 0x40000000, 1, "ExistsToJoin" }, { 0xffffffff, 0, "All" }, }; unsigned int curOpt; From 1b62720fa8e0a344681ffb4d1518c4551564c2a8 Mon Sep 17 00:00:00 2001 From: drh <> Date: Fri, 4 Jul 2025 10:26:55 +0000 Subject: [PATCH 13/18] Improve the early-termination optimization so that it works in queries which use the LIKE optimization in the outer loop. FossilOrigin-Name: b4e4d148243cfcb09aa0aaca30e83812b42e2780073e55c0e8c6e3da16243dfc --- manifest | 12 ++++++------ manifest.uuid | 2 +- src/where.c | 2 +- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/manifest b/manifest index 77bee83097..8f4d2219c7 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Merge\sthe\slatest\strunk\sfixes\sand\senhancements\sinto\sthe\sempty-table-optimizations\sbranch -D 2025-07-03T20:51:08.444 +C Improve\sthe\searly-termination\soptimization\sso\sthat\sit\sworks\sin\squeries\nwhich\suse\sthe\sLIKE\soptimization\sin\sthe\souter\sloop. +D 2025-07-04T10:26:55.842 F .fossil-settings/binary-glob 61195414528fb3ea9693577e1980230d78a1f8b0a54c78cf1b9b24d0a409ed6a x F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea @@ -867,7 +867,7 @@ F src/vxworks.h d2988f4e5a61a4dfe82c6524dd3d6e4f2ce3cdb9 F src/wal.c 20be6f0a25a80b7897cf2a5369bfd37ef198e6f0b6cdef16d83eee856056b159 F src/wal.h ba252daaa94f889f4b2c17c027e823d9be47ce39da1d3799886bbd51f0490452 F src/walker.c d5006d6b005e4ea7302ad390957a8d41ed83faa177e412f89bc5600a7462a014 -F src/where.c 458b2089adc9ad65b2585fdf705b716e74abef146f20472962368cd784898f65 +F src/where.c f8139d355555e305aa5cf40ddf0f94ca606341d5196c6c597a79e6b1f7a173ee F src/whereInt.h 8d94cb116c9e06205c3d5ac87af065fc044f8cf08bfdccd94b6ea1c1308e65da F src/wherecode.c 2917e70e12f7b238285240e564329374f20e4270fe90c36e0d19b6770754a7c4 F src/whereexpr.c c3ff4d8f1ae5cb9fb41460f9d960b1f519b6115585375790c53833e5642fc1f4 @@ -2211,8 +2211,8 @@ F tool/version-info.c 3b36468a90faf1bbd59c65fd0eb66522d9f941eedd364fabccd7227350 F tool/warnings-clang.sh bbf6a1e685e534c92ec2bfba5b1745f34fb6f0bc2a362850723a9ee87c1b31a7 F tool/warnings.sh 1ad0169b022b280bcaaf94a7fa231591be96b514230ab5c98fbf15cd7df842dd F tool/win/sqlite.vsix deb315d026cc8400325c5863eef847784a219a2f -P 9cb600ad576c68647ed943a0773019312c5f01c9c1ca9ff0bf1214b03a531b48 960a8e6fc91f47add3a089dc6cef013109deadf809994c5149ad3bdfb3884de0 -R 16f14c5f6cd8a80b6f70e6c8ec90dbe2 +P d4f47e04f5880e99a53089e2dd5cde64a7ea44f059d9906b5d11324896546714 +R a697518c4d8b922117d5dad06f7688c7 U drh -Z c17d256ee21406dce84038f025b1e634 +Z ef38abde98882fa21d988c824ad69a24 # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index 600147ed5c..ab2837e8bd 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -d4f47e04f5880e99a53089e2dd5cde64a7ea44f059d9906b5d11324896546714 +b4e4d148243cfcb09aa0aaca30e83812b42e2780073e55c0e8c6e3da16243dfc diff --git a/src/where.c b/src/where.c index 69b206a22e..059b0e89ea 100644 --- a/src/where.c +++ b/src/where.c @@ -7139,7 +7139,7 @@ WhereInfo *sqlite3WhereBegin( && (pTabItem[0].fg.jointype & (JT_LTORJ|JT_LEFT))==0 && pLevel->addrHalt==pWInfo->a[0].addrHalt ){ - sqlite3VdbeAddOp2(v, OP_IfEmpty, pTabItem->iCursor, pLevel->addrHalt); + sqlite3VdbeAddOp2(v, OP_IfEmpty, pTabItem->iCursor, pWInfo->iBreak); VdbeCoverage(v); } }else{ From 478dfc7e74921f391732e03b486c786d4c7bd49b Mon Sep 17 00:00:00 2001 From: drh <> Date: Fri, 4 Jul 2025 11:06:34 +0000 Subject: [PATCH 14/18] Do not allow the EXISTS-to-JOIN optimization if the EXISTS clause is based on a view, since that view might expand into a join. FossilOrigin-Name: 872c41feddafcc21a02f1229ce017ceea9f8e309a4dd5b6e323477d67f975947 --- manifest | 12 ++++++------ manifest.uuid | 2 +- src/select.c | 3 ++- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/manifest b/manifest index 8f4d2219c7..0ed185a563 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Improve\sthe\searly-termination\soptimization\sso\sthat\sit\sworks\sin\squeries\nwhich\suse\sthe\sLIKE\soptimization\sin\sthe\souter\sloop. -D 2025-07-04T10:26:55.842 +C Do\snot\sallow\sthe\sEXISTS-to-JOIN\soptimization\sif\sthe\sEXISTS\sclause\sis\sbased\non\sa\sview,\ssince\sthat\sview\smight\sexpand\sinto\sa\sjoin. +D 2025-07-04T11:06:34.046 F .fossil-settings/binary-glob 61195414528fb3ea9693577e1980230d78a1f8b0a54c78cf1b9b24d0a409ed6a x F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea @@ -785,7 +785,7 @@ F src/printf.c 71b6d3a0093bf23f473e25480ca0024e8962681506c75f4ffd3d343a3f0ab113 F src/random.c 606b00941a1d7dd09c381d3279a058d771f406c5213c9932bbd93d5587be4b9c F src/resolve.c 5b14cad58bc21341fbaea76d7e781187559627a461745ece00c2655ba7c083ec F src/rowset.c 8432130e6c344b3401a8874c3cb49fefe6873fec593294de077afea2dce5ec97 -F src/select.c be5543b598cc0d217b34ddf804aaab7f5cf63d74958bf6e2a2f3523f4b07fced +F src/select.c 6ce33294314f05898a35205566a7d7d5034f8a6f7ad9cb9e3804f4765a100290 F src/shell.c.in 73c0eeb7c265d59b99219d5aa055f412f07842088d8036b6d259927d85dd1bbf F src/sqlite.h.in 5c54f2461a1ea529bab8499148a2b238e2d4bb571d59e8ea5322d0c190abb693 F src/sqlite3.rc 015537e6ac1eec6c7050e17b616c2ffe6f70fca241835a84a4f0d5937383c479 @@ -2211,8 +2211,8 @@ F tool/version-info.c 3b36468a90faf1bbd59c65fd0eb66522d9f941eedd364fabccd7227350 F tool/warnings-clang.sh bbf6a1e685e534c92ec2bfba5b1745f34fb6f0bc2a362850723a9ee87c1b31a7 F tool/warnings.sh 1ad0169b022b280bcaaf94a7fa231591be96b514230ab5c98fbf15cd7df842dd F tool/win/sqlite.vsix deb315d026cc8400325c5863eef847784a219a2f -P d4f47e04f5880e99a53089e2dd5cde64a7ea44f059d9906b5d11324896546714 -R a697518c4d8b922117d5dad06f7688c7 +P b4e4d148243cfcb09aa0aaca30e83812b42e2780073e55c0e8c6e3da16243dfc +R 3690d7e51c5bb54cabac58a689eae722 U drh -Z ef38abde98882fa21d988c824ad69a24 +Z c7e5a37e4d84ec49607cdfc27b218111 # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index ab2837e8bd..eb7428f280 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -b4e4d148243cfcb09aa0aaca30e83812b42e2780073e55c0e8c6e3da16243dfc +872c41feddafcc21a02f1229ce017ceea9f8e309a4dd5b6e323477d67f975947 diff --git a/src/select.c b/src/select.c index ff45394008..750de5e8ff 100644 --- a/src/select.c +++ b/src/select.c @@ -7437,9 +7437,10 @@ static SQLITE_NOINLINE void existsToJoin( } else if( pWhere->op==TK_EXISTS ){ Select *pSub = pWhere->x.pSelect; - if( pSub->pSrc->nSrc==1 + if( pSub->pSrc->nSrc==1 && (pSub->selFlags & SF_Aggregate)==0 && pSub->pWhere + && !pSub->pSrc->a[0].fg.isSubquery ){ memset(pWhere, 0, sizeof(*pWhere)); pWhere->op = TK_INTEGER; From 8bc112e3e453a9e574a800e7edab3afecb064449 Mon Sep 17 00:00:00 2001 From: drh <> Date: Sat, 5 Jul 2025 23:33:21 +0000 Subject: [PATCH 15/18] Enhance the EXISTS-to-JOIN optimization so that it works on EXISTS subqueries that do not have a WHERE clause, and so that it works on nested EXISTS subqueries. FossilOrigin-Name: c1d5295724f9cf7f49e0786d28016eff2d268a2b670f934d24c76787626089db --- manifest | 14 +++++++------- manifest.uuid | 2 +- src/select.c | 22 +++++++++++++++------- test/existsexpr.test | 2 +- 4 files changed, 24 insertions(+), 16 deletions(-) diff --git a/manifest b/manifest index 3afc7c8322..700cfd8a47 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Merge\sfixes\sfrom\strunk\sinto\sthe\sempty-table-optimizations\sbranch -D 2025-07-04T14:24:18.375 +C Enhance\sthe\sEXISTS-to-JOIN\soptimization\sso\sthat\sit\sworks\son\sEXISTS\ssubqueries\nthat\sdo\snot\shave\sa\sWHERE\sclause,\sand\sso\sthat\sit\sworks\son\snested\sEXISTS\nsubqueries. +D 2025-07-05T23:33:21.216 F .fossil-settings/binary-glob 61195414528fb3ea9693577e1980230d78a1f8b0a54c78cf1b9b24d0a409ed6a x F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea @@ -785,7 +785,7 @@ F src/printf.c 71b6d3a0093bf23f473e25480ca0024e8962681506c75f4ffd3d343a3f0ab113 F src/random.c 606b00941a1d7dd09c381d3279a058d771f406c5213c9932bbd93d5587be4b9c F src/resolve.c 5b14cad58bc21341fbaea76d7e781187559627a461745ece00c2655ba7c083ec F src/rowset.c 8432130e6c344b3401a8874c3cb49fefe6873fec593294de077afea2dce5ec97 -F src/select.c 6ce33294314f05898a35205566a7d7d5034f8a6f7ad9cb9e3804f4765a100290 +F src/select.c b1d4e1f21a32b4f33d3bf02c4167ac8da5aafb97210a0b13a9119d68183db37d F src/shell.c.in 73c0eeb7c265d59b99219d5aa055f412f07842088d8036b6d259927d85dd1bbf F src/sqlite.h.in 5c54f2461a1ea529bab8499148a2b238e2d4bb571d59e8ea5322d0c190abb693 F src/sqlite3.rc 015537e6ac1eec6c7050e17b616c2ffe6f70fca241835a84a4f0d5937383c479 @@ -1140,7 +1140,7 @@ F test/exclusive.test 7ff63be7503990921838d5c9f77f6e33e68e48ed1a9d48cd28745bf650 F test/exclusive2.test cd70b1d9c6fffd336f9795b711dcc5d9ceba133ad3f7001da3fda63615bdc91e F test/exec.test e949714dc127eaa5ecc7d723efec1ec27118fdd7 F test/exists.test 79a75323c78f02bbe9c251ea502a092f9ef63dac -F test/existsexpr.test d87e7ee394935f9b4a9a1a488f3faa55abd20dd85152efaf2034881a079c7ba1 +F test/existsexpr.test cefe49ffc2295281908e0e1dd6d289381d2c3bcbbabcf36cd799cedb0708c7e9 F test/existsexpr2.test dc23e76389eff3d29f6488ff733012a3560cd67ec8cfaecbecd52cced5d5af11 F test/existsfault.test ff41c11f3052c1bbd4f8dd557802310026253d67d7c4e3a180c16d2f0862973e F test/expr.test 4ada8eb822c45ef27a36851a258004d43c1e95e7c82585a1217e732084e4482c @@ -2211,8 +2211,8 @@ F tool/version-info.c 3b36468a90faf1bbd59c65fd0eb66522d9f941eedd364fabccd7227350 F tool/warnings-clang.sh bbf6a1e685e534c92ec2bfba5b1745f34fb6f0bc2a362850723a9ee87c1b31a7 F tool/warnings.sh 1ad0169b022b280bcaaf94a7fa231591be96b514230ab5c98fbf15cd7df842dd F tool/win/sqlite.vsix deb315d026cc8400325c5863eef847784a219a2f -P 872c41feddafcc21a02f1229ce017ceea9f8e309a4dd5b6e323477d67f975947 13af4acebe09b047756c22b800136cffaba532e7fcaa448a4edf4fedb94e9bbc -R bc9dd605b1b6086398bb04180450d2c6 +P 6f98b16d210a9f5b6ca4b4599e3dab3263eddbae7c70ddbcabf988f4a1014e8b +R 1a0b4833b41c9eafcebd2713b9567447 U drh -Z 9c89d6634e2f24fe30079554a53be2d7 +Z d76421db80a68ce3b2346aa4278aa4db # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index 7864b0e031..24edc66d6f 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -6f98b16d210a9f5b6ca4b4599e3dab3263eddbae7c70ddbcabf988f4a1014e8b +c1d5295724f9cf7f49e0786d28016eff2d268a2b670f934d24c76787626089db diff --git a/src/select.c b/src/select.c index 750de5e8ff..b15300237a 100644 --- a/src/select.c +++ b/src/select.c @@ -7418,11 +7418,17 @@ static int fromClauseTermCanBeCoroutine( ** ** SELECT name FROM sailors AS S, reserves AS R ** WHERE S.sid = R.sid AND R.day = '2022-10-25'; +** +** **Approximately**. Really, we have to ensure that the FROM-clause term +** that was formerly inside the EXISTS is only executed once. This is handled +** by setting the SrcItem.fg.fromExists flag, which then causes code in +** the where.c file to exit the corresponding loop after the first successful +** match (if any). */ static SQLITE_NOINLINE void existsToJoin( - Parse *pParse, - Select *p, - Expr *pWhere + Parse *pParse, /* Parsing context */ + Select *p, /* The SELECT statement being optimized */ + Expr *pWhere /* part of the WHERE clause currently being examined */ ){ if( pWhere && !ExprHasProperty(pWhere, EP_OuterON|EP_InnerON) @@ -7437,9 +7443,9 @@ static SQLITE_NOINLINE void existsToJoin( } else if( pWhere->op==TK_EXISTS ){ Select *pSub = pWhere->x.pSelect; + Expr *pSubWhere = pSub->pWhere; if( pSub->pSrc->nSrc==1 && (pSub->selFlags & SF_Aggregate)==0 - && pSub->pWhere && !pSub->pSrc->a[0].fg.isSubquery ){ memset(pWhere, 0, sizeof(*pWhere)); @@ -7451,9 +7457,10 @@ static SQLITE_NOINLINE void existsToJoin( pSub->pSrc->a[0].fg.fromExists = 1; pSub->pSrc->a[0].fg.jointype |= JT_CROSS; p->pSrc = sqlite3SrcListAppendList(pParse, p->pSrc, pSub->pSrc); - p->pWhere = sqlite3PExpr(pParse, TK_AND, p->pWhere, pSub->pWhere); - - pSub->pWhere = 0; + if( pSubWhere ){ + p->pWhere = sqlite3PExpr(pParse, TK_AND, p->pWhere, pSubWhere); + pSub->pWhere = 0; + } pSub->pSrc = 0; sqlite3ParserAddCleanup(pParse, sqlite3SelectDeleteGeneric, pSub); #if TREETRACE_ENABLED @@ -7463,6 +7470,7 @@ static SQLITE_NOINLINE void existsToJoin( sqlite3TreeViewSelect(0, p, 0); } #endif + existsToJoin(pParse, p, pSubWhere); } } } diff --git a/test/existsexpr.test b/test/existsexpr.test index 51b9234b7f..c28955b672 100644 --- a/test/existsexpr.test +++ b/test/existsexpr.test @@ -193,7 +193,7 @@ do_subquery_test 3.7 1 { 4 4 4 } -do_subquery_test 3.8 1 { +do_subquery_test 3.8 0 { SELECT * FROM y1 WHERE EXISTS ( SELECT a+1 FROM y2 ) } { 1 1 1 From c701d173668b07e743e7af8132842b7e6d8c1922 Mon Sep 17 00:00:00 2001 From: drh <> Date: Sun, 6 Jul 2025 01:19:09 +0000 Subject: [PATCH 16/18] Improvements to the EXPLAIN QUERY PLAN output for EXISTS-to-JOIN. FossilOrigin-Name: 6b1ecbaa2ee405be040901dceac45d027d35c313622748ba4dbbd404e297a7fa --- manifest | 16 ++++++++-------- manifest.uuid | 2 +- src/wherecode.c | 14 ++++---------- test/eqp.test | 2 +- test/existsexpr.test | 4 ++-- 5 files changed, 16 insertions(+), 22 deletions(-) diff --git a/manifest b/manifest index 700cfd8a47..4b0c4022b9 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Enhance\sthe\sEXISTS-to-JOIN\soptimization\sso\sthat\sit\sworks\son\sEXISTS\ssubqueries\nthat\sdo\snot\shave\sa\sWHERE\sclause,\sand\sso\sthat\sit\sworks\son\snested\sEXISTS\nsubqueries. -D 2025-07-05T23:33:21.216 +C Improvements\sto\sthe\sEXPLAIN\sQUERY\sPLAN\soutput\sfor\sEXISTS-to-JOIN. +D 2025-07-06T01:19:09.350 F .fossil-settings/binary-glob 61195414528fb3ea9693577e1980230d78a1f8b0a54c78cf1b9b24d0a409ed6a x F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea @@ -869,7 +869,7 @@ F src/wal.h ba252daaa94f889f4b2c17c027e823d9be47ce39da1d3799886bbd51f0490452 F src/walker.c d5006d6b005e4ea7302ad390957a8d41ed83faa177e412f89bc5600a7462a014 F src/where.c f8139d355555e305aa5cf40ddf0f94ca606341d5196c6c597a79e6b1f7a173ee F src/whereInt.h 8d94cb116c9e06205c3d5ac87af065fc044f8cf08bfdccd94b6ea1c1308e65da -F src/wherecode.c 2917e70e12f7b238285240e564329374f20e4270fe90c36e0d19b6770754a7c4 +F src/wherecode.c ff520ce2a1ac8248e7adc1cff7ea2141fb07260ece28d2c6130e87fdfc0d2afa F src/whereexpr.c d007dc41364de5902181739632380afd671e14f0c5cc9978e64a2c6df8f28c6c F src/window.c d01227141f622f24fbe36ca105fbe6ef023f9fd98f1ccd65da95f88886565db5 F test/8_3_names.test ebbb5cd36741350040fd28b432ceadf495be25b2 @@ -1131,7 +1131,7 @@ F test/enc.test b5503a87b31cea8a5084c6e447383f9ca08933bd2f29d97b6b6201081b2343eb F test/enc2.test 872afe58db772e7dfa1ad8e0759f8cc820e9efc8172d460fae83023101c2e435 F test/enc3.test 55ef64416d72975c66167310a51dc9fc544ba3ae4858b8d5ab22f4cb6500b087 F test/enc4.test c8f1ce3618508fd0909945beb8b8831feef2c020 -F test/eqp.test 800fb69fae9086b76dc767931a9973355187d673f69cd2ccfd3c455528af7859 +F test/eqp.test 746db9fe11629a0d00328e1721cc2a2e4726d574b677ab14de35fd914f54cc82 F test/eqp2.test 6e8996148de88f0e7670491e92e712a2920a369b4406f21a27c3c9b6a46b68dd F test/errmsg.test eae9f091eb39ce7e20305de45d8e5d115b68fa856fba4ea6757b6ca3705ff7f9 F test/errofst1.test 6da78363739ba8991f498396ab331b5d64e7ab5c4172c12b5884683ef523ac53 @@ -1140,7 +1140,7 @@ F test/exclusive.test 7ff63be7503990921838d5c9f77f6e33e68e48ed1a9d48cd28745bf650 F test/exclusive2.test cd70b1d9c6fffd336f9795b711dcc5d9ceba133ad3f7001da3fda63615bdc91e F test/exec.test e949714dc127eaa5ecc7d723efec1ec27118fdd7 F test/exists.test 79a75323c78f02bbe9c251ea502a092f9ef63dac -F test/existsexpr.test cefe49ffc2295281908e0e1dd6d289381d2c3bcbbabcf36cd799cedb0708c7e9 +F test/existsexpr.test 40ddd9500109579dd949cd15bbb4e3a88f79f905d1f31905b9493651f60aacf6 F test/existsexpr2.test dc23e76389eff3d29f6488ff733012a3560cd67ec8cfaecbecd52cced5d5af11 F test/existsfault.test ff41c11f3052c1bbd4f8dd557802310026253d67d7c4e3a180c16d2f0862973e F test/expr.test 4ada8eb822c45ef27a36851a258004d43c1e95e7c82585a1217e732084e4482c @@ -2211,8 +2211,8 @@ F tool/version-info.c 3b36468a90faf1bbd59c65fd0eb66522d9f941eedd364fabccd7227350 F tool/warnings-clang.sh bbf6a1e685e534c92ec2bfba5b1745f34fb6f0bc2a362850723a9ee87c1b31a7 F tool/warnings.sh 1ad0169b022b280bcaaf94a7fa231591be96b514230ab5c98fbf15cd7df842dd F tool/win/sqlite.vsix deb315d026cc8400325c5863eef847784a219a2f -P 6f98b16d210a9f5b6ca4b4599e3dab3263eddbae7c70ddbcabf988f4a1014e8b -R 1a0b4833b41c9eafcebd2713b9567447 +P c1d5295724f9cf7f49e0786d28016eff2d268a2b670f934d24c76787626089db +R e327f2cba9403b63bcd0d2189ed9005a U drh -Z d76421db80a68ce3b2346aa4278aa4db +Z 99af5d89797b6a2541e58b654e88bc24 # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index 24edc66d6f..870e5bc361 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -c1d5295724f9cf7f49e0786d28016eff2d268a2b670f934d24c76787626089db +6b1ecbaa2ee405be040901dceac45d027d35c313622748ba4dbbd404e297a7fa diff --git a/src/wherecode.c b/src/wherecode.c index 9c611001bb..5111880c05 100644 --- a/src/wherecode.c +++ b/src/wherecode.c @@ -126,7 +126,6 @@ void sqlite3WhereAddExplainText( #endif { VdbeOp *pOp = sqlite3VdbeGetOp(pParse->pVdbe, addr); - SrcItem *pItem = &pTabList->a[pLevel->iFrom]; sqlite3 *db = pParse->db; /* Database handle */ int isSearch; /* True for a SEARCH. False for SCAN. */ @@ -135,7 +134,6 @@ void sqlite3WhereAddExplainText( #if defined(SQLITE_DEBUG) && !defined(SQLITE_OMIT_EXPLAIN) char *zMsg; /* Text to add to EQP output */ #endif - const char *zFormat; StrAccum str; /* EQP output string */ char zBuf[100]; /* Initial space for EQP output string */ @@ -150,14 +148,10 @@ void sqlite3WhereAddExplainText( sqlite3StrAccumInit(&str, db, zBuf, sizeof(zBuf), SQLITE_MAX_LENGTH); str.printfFlags = SQLITE_PRINTF_INTERNAL; - if( pItem->fg.fromExists ){ - zFormat = "SINGLETON %S"; - }else if( isSearch ){ - zFormat = "SEARCH %S"; - }else{ - zFormat = "SCAN %S"; - } - sqlite3_str_appendf(&str, zFormat, pItem); + sqlite3_str_appendf(&str, "%s %S%s", + isSearch ? "SEARCH" : "SCAN", + pItem, + pItem->fg.fromExists ? " EXISTS" : ""); if( (flags & (WHERE_IPK|WHERE_VIRTUALTABLE))==0 ){ const char *zFmt = 0; Index *pIdx; diff --git a/test/eqp.test b/test/eqp.test index b7e7acd8a0..147b5ceafe 100644 --- a/test/eqp.test +++ b/test/eqp.test @@ -338,7 +338,7 @@ det 3.3.3 { } { QUERY PLAN |--SCAN t1 - `--SINGLETON t2 + `--SCAN t2 EXISTS } #------------------------------------------------------------------------- diff --git a/test/existsexpr.test b/test/existsexpr.test index c28955b672..2bf2e8223f 100644 --- a/test/existsexpr.test +++ b/test/existsexpr.test @@ -94,13 +94,13 @@ do_execsql_test 2.4.0 { do_eqp_test 2.4.1 { SELECT count(*) FROM t1 WHERE EXISTS (SELECT 1 FROM t2 WHERE c=a); -} {SCAN t1*SINGLETON t2} +} {SCAN t1*t2 EXISTS} do_execsql_test 2.4.2 { ANALYZE; } do_eqp_test 2.4.3 { SELECT count(*) FROM t1 WHERE EXISTS (SELECT 1 FROM t2 WHERE c=a); -} {SCAN t1*SINGLETON t2} +} {SCAN t1*t2 EXISTS} do_execsql_test 2.4.4 { SELECT count(*) FROM t1 WHERE EXISTS (SELECT 1 FROM t2 WHERE c=a); } {100} From 8c8443a38a69eafee4ef930cbfc2c07da4b78628 Mon Sep 17 00:00:00 2001 From: drh <> Date: Mon, 7 Jul 2025 19:03:50 +0000 Subject: [PATCH 17/18] Small performance optimization in the resolver. FossilOrigin-Name: 840646df0696706bd4dd7a04dfc8b16ad80c24cbcbc57a22c9e65a93cc17af2b --- manifest | 12 ++++++------ manifest.uuid | 2 +- src/resolve.c | 4 +--- 3 files changed, 8 insertions(+), 10 deletions(-) diff --git a/manifest b/manifest index 974b081ab2..7ead8c5181 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Merge\sthe\slatest\strunk\s\senhancements\sand\sfixes\sinto\sthe\sempty-table-optimizations\sbranch. -D 2025-07-07T18:35:51.568 +C Small\sperformance\soptimization\sin\sthe\sresolver. +D 2025-07-07T19:03:50.925 F .fossil-settings/binary-glob 61195414528fb3ea9693577e1980230d78a1f8b0a54c78cf1b9b24d0a409ed6a x F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea @@ -783,7 +783,7 @@ F src/pragma.c 30b535d0a66348df844ee36f890617b4cf45e9a22dcbc47ec3ca92909c50aaf1 F src/prepare.c 1832be043fce7d489959aae6f994c452d023914714c4d5457beaed51c0f3d126 F src/printf.c 71b6d3a0093bf23f473e25480ca0024e8962681506c75f4ffd3d343a3f0ab113 F src/random.c 606b00941a1d7dd09c381d3279a058d771f406c5213c9932bbd93d5587be4b9c -F src/resolve.c 5b14cad58bc21341fbaea76d7e781187559627a461745ece00c2655ba7c083ec +F src/resolve.c d3ee7ed308d46f4ee6d3bb6316d8d6f87158f93a7fd616732138cc953cf364f0 F src/rowset.c 8432130e6c344b3401a8874c3cb49fefe6873fec593294de077afea2dce5ec97 F src/select.c b1d4e1f21a32b4f33d3bf02c4167ac8da5aafb97210a0b13a9119d68183db37d F src/shell.c.in 73c0eeb7c265d59b99219d5aa055f412f07842088d8036b6d259927d85dd1bbf @@ -2211,8 +2211,8 @@ F tool/version-info.c 3b36468a90faf1bbd59c65fd0eb66522d9f941eedd364fabccd7227350 F tool/warnings-clang.sh bbf6a1e685e534c92ec2bfba5b1745f34fb6f0bc2a362850723a9ee87c1b31a7 F tool/warnings.sh 1ad0169b022b280bcaaf94a7fa231591be96b514230ab5c98fbf15cd7df842dd F tool/win/sqlite.vsix deb315d026cc8400325c5863eef847784a219a2f -P 6b1ecbaa2ee405be040901dceac45d027d35c313622748ba4dbbd404e297a7fa 28db0d152d90fb5e62d03ea5caceabe8901be98522aef3dc2b54564fbc35355d -R 1ad2faf618cb852b453d45306c55c408 +P f15cdf07573c05276a13885d74bae21a93544766344f19ef939b7a69edd1073b +R 2636d63ee0b11ec41a4803b9ba60214d U drh -Z 725feec2e2e8168094856eb7acaf31d4 +Z f0a94ad32bbc71db6c95d4953159e88b # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index f4fafa7e7b..0533abe09d 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -f15cdf07573c05276a13885d74bae21a93544766344f19ef939b7a69edd1073b +840646df0696706bd4dd7a04dfc8b16ad80c24cbcbc57a22c9e65a93cc17af2b diff --git a/src/resolve.c b/src/resolve.c index bbd1021e0b..57ccd0c07a 100644 --- a/src/resolve.c +++ b/src/resolve.c @@ -1359,9 +1359,6 @@ static int resolveExprStep(Walker *pWalker, Expr *pExpr){ } #ifndef SQLITE_OMIT_SUBQUERY case TK_EXISTS: - assert( ExprUseXSelect(pExpr) ); - pParse->bHasExists = 1; - /* no break */ deliberate_fall_through case TK_SELECT: #endif case TK_IN: { @@ -1375,6 +1372,7 @@ static int resolveExprStep(Walker *pWalker, Expr *pExpr){ testcase( pNC->ncFlags & NC_IdxExpr ); testcase( pNC->ncFlags & NC_GenCol ); assert( pExpr->x.pSelect ); + if( pExpr->op==TK_EXISTS ) pParse->bHasExists = 1; if( pNC->ncFlags & NC_SelfRef ){ notValidImpl(pParse, pNC, "subqueries", pExpr, pExpr); }else{ From 449b34571e9022333eb0cd0ce403a4636719194d Mon Sep 17 00:00:00 2001 From: drh <> Date: Tue, 8 Jul 2025 17:28:09 +0000 Subject: [PATCH 18/18] Enable the EXISTS-to-JOIN optimization if the outer query has no FROM clause. FossilOrigin-Name: 1b9b124f9a35ebd1ac4ea70ef1ee08a4c82c11da690d4164f6b785a6fd9730d9 --- manifest | 14 +++++++------- manifest.uuid | 2 +- src/build.c | 9 ++++++--- src/select.c | 1 - 4 files changed, 14 insertions(+), 12 deletions(-) diff --git a/manifest b/manifest index 7ead8c5181..ff1e169701 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Small\sperformance\soptimization\sin\sthe\sresolver. -D 2025-07-07T19:03:50.925 +C Enable\sthe\sEXISTS-to-JOIN\soptimization\sif\sthe\souter\squery\shas\sno\nFROM\sclause. +D 2025-07-08T17:28:09.030 F .fossil-settings/binary-glob 61195414528fb3ea9693577e1980230d78a1f8b0a54c78cf1b9b24d0a409ed6a x F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea @@ -729,7 +729,7 @@ F src/btmutex.c 30dada73a819a1ef5b7583786370dce1842e12e1ad941e4d05ac29695528daea F src/btree.c cb5b8ceb9baa02a63a2f83dec09c4153e1cfbdf9c2adef5c62c26d2160eeb067 F src/btree.h e823c46d87f63d904d735a24b76146d19f51f04445ea561f71cc3382fd1307f0 F src/btreeInt.h 9c0f9ea5c9b5f4dcaea18111d43efe95f2ac276cd86d770dce10fd99ccc93886 -F src/build.c bef89ea23db1221556a3a405b7c46cac14c03fc0a8e82954ca2359a7dc98ec69 +F src/build.c cc4f287348790bbb7219f7e8dee13b1c345c3377fcdd98eca866e7457ecd07e7 F src/callback.c acae8c8dddda41ee85cfdf19b926eefe830f371069f8aadca3aa39adf5b1c859 F src/complete.c a3634ab1e687055cd002e11b8f43eb75c17da23e F src/date.c 9db4d604e699a73e10b8e85a44db074a1f04c0591a77e2abfd77703f50dce1e9 @@ -785,7 +785,7 @@ F src/printf.c 71b6d3a0093bf23f473e25480ca0024e8962681506c75f4ffd3d343a3f0ab113 F src/random.c 606b00941a1d7dd09c381d3279a058d771f406c5213c9932bbd93d5587be4b9c F src/resolve.c d3ee7ed308d46f4ee6d3bb6316d8d6f87158f93a7fd616732138cc953cf364f0 F src/rowset.c 8432130e6c344b3401a8874c3cb49fefe6873fec593294de077afea2dce5ec97 -F src/select.c b1d4e1f21a32b4f33d3bf02c4167ac8da5aafb97210a0b13a9119d68183db37d +F src/select.c 244f2fba5f73c7ea937333bd54280e83e218a0b652fc4540cbd72d33b0f7b4d8 F src/shell.c.in 73c0eeb7c265d59b99219d5aa055f412f07842088d8036b6d259927d85dd1bbf F src/sqlite.h.in 5c54f2461a1ea529bab8499148a2b238e2d4bb571d59e8ea5322d0c190abb693 F src/sqlite3.rc 015537e6ac1eec6c7050e17b616c2ffe6f70fca241835a84a4f0d5937383c479 @@ -2211,8 +2211,8 @@ F tool/version-info.c 3b36468a90faf1bbd59c65fd0eb66522d9f941eedd364fabccd7227350 F tool/warnings-clang.sh bbf6a1e685e534c92ec2bfba5b1745f34fb6f0bc2a362850723a9ee87c1b31a7 F tool/warnings.sh 1ad0169b022b280bcaaf94a7fa231591be96b514230ab5c98fbf15cd7df842dd F tool/win/sqlite.vsix deb315d026cc8400325c5863eef847784a219a2f -P f15cdf07573c05276a13885d74bae21a93544766344f19ef939b7a69edd1073b -R 2636d63ee0b11ec41a4803b9ba60214d +P 840646df0696706bd4dd7a04dfc8b16ad80c24cbcbc57a22c9e65a93cc17af2b +R e35575e41e9bd119ef19709aeaa4c684 U drh -Z f0a94ad32bbc71db6c95d4953159e88b +Z 4da98ddcb106ab62869cc3825500f67f # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index 0533abe09d..384e800b52 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -840646df0696706bd4dd7a04dfc8b16ad80c24cbcbc57a22c9e65a93cc17af2b +1b9b124f9a35ebd1ac4ea70ef1ee08a4c82c11da690d4164f6b785a6fd9730d9 diff --git a/src/build.c b/src/build.c index cd838557fd..5495cef18f 100644 --- a/src/build.c +++ b/src/build.c @@ -5138,6 +5138,9 @@ void sqlite3SrcListIndexedBy(Parse *pParse, SrcList *p, Token *pIndexedBy){ */ SrcList *sqlite3SrcListAppendList(Parse *pParse, SrcList *p1, SrcList *p2){ assert( p1 ); + assert( p2 || pParse->nErr ); + assert( p2==0 || p2->nSrc>=1 ); + testcase( p1->nSrc==0 ); if( p2 ){ int nOld = p1->nSrc; SrcList *pNew = sqlite3SrcListEnlarge(pParse, p1, p2->nSrc, nOld); @@ -5146,10 +5149,10 @@ SrcList *sqlite3SrcListAppendList(Parse *pParse, SrcList *p1, SrcList *p2){ }else{ p1 = pNew; memcpy(&p1->a[nOld], p2->a, p2->nSrc*sizeof(SrcItem)); - assert( nOld==1 || (p2->nSrc==1 && (p2->a[0].fg.jointype&JT_LTORJ)==0) ); - assert( p1->nSrc>=2 ); + assert( nOld==1 || (p2->a[0].fg.jointype & JT_LTORJ)==0 ); + assert( p1->nSrc>=1 ); + p1->a[0].fg.jointype |= (JT_LTORJ & p2->a[0].fg.jointype); sqlite3DbFree(pParse->db, p2); - p1->a[0].fg.jointype |= (JT_LTORJ & p1->a[1].fg.jointype); } } return p1; diff --git a/src/select.c b/src/select.c index b15300237a..2dd8fc7725 100644 --- a/src/select.c +++ b/src/select.c @@ -7432,7 +7432,6 @@ static SQLITE_NOINLINE void existsToJoin( ){ if( pWhere && !ExprHasProperty(pWhere, EP_OuterON|EP_InnerON) - && p->pSrc->nSrc>0 && p->pSrc->nSrcdb->mallocFailed==0 ){