From: drh <> Date: Sat, 20 Jul 2024 03:57:10 +0000 (+0000) Subject: Do not do an early abort if a lateral subquery is an empty set. X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=19b62a175f66d418448484b2a9a57345bea16a10;p=thirdparty%2Fsqlite.git Do not do an early abort if a lateral subquery is an empty set. FossilOrigin-Name: 9e50d338ebd974b5d1b8baf4c7a400abded28ee997414e896c2e8b9d61d39a94 --- diff --git a/manifest b/manifest index 55f65b4aae..c0bcc98a1c 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C "LATERAL"\smay\snot\sbe\sa\skeyword,\sas\sthat\swould\scause\sproblems\sfor\slegacy\ndatabases\sthat\shave\stables\snamed\s"lateral".\s\sIt\shas\sto\sbe\sparsed\sas\san\nidentifier. -D 2024-07-20T03:19:40.623 +C Do\snot\sdo\san\searly\sabort\sif\sa\slateral\ssubquery\sis\san\sempty\sset. +D 2024-07-20T03:57:10.925 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -842,7 +842,7 @@ F src/wal.h ba252daaa94f889f4b2c17c027e823d9be47ce39da1d3799886bbd51f0490452 F src/walker.c 7c7ea0115345851c3da4e04e2e239a29983b61fb5b038b94eede6aba462640e2 F src/where.c e411ef973e520428a3cbbf8a348c7b37bc8f7f639ebdabd64f835861364be3ee F src/whereInt.h 002adc3aa2cc10733b9b27958fdbe893987cd989fab25a9853941c1f9b9b0a65 -F src/wherecode.c c9cac0b0b8e809c5e7e79d7796918907fb685ad99be2aaa9737f9787aa47349c +F src/wherecode.c e6518bac30e75554996c50ffd4d6d01522de0beb54020446fd498dfc2111a193 F src/whereexpr.c 7d0d34b42b9edfd8e8ca66beb3a6ef63fe211c001af54caf2ccbcd989b783290 F src/window.c 1e40ffc509bae21e466f6106382d238e91eb73edd4ba10e66ca4fd7af2b96896 F test/8_3_names.test ebbb5cd36741350040fd28b432ceadf495be25b2 @@ -1347,7 +1347,7 @@ F test/joinD.test 2ce62e7353a0702ca5e70008faf319c1d4686aa19fba34275c6d1da0e960be F test/joinE.test d5d182f3812771e2c0d97c9dcf5dbe4c41c8e21c82560e59358731c4a3981d6b F test/joinF.test 53dd66158806823ea680dd7543b5406af151b5aafa5cd06a7f3231cd94938127 F test/joinH.test 55f69e64da74d4eca2235237f3acb657aef181e22e45daa228e35bba865e0255 -F test/joinL.test d18dc1f85a8254b260ef5d0e1d3695dca65309f20f04b79e0b1cb754fdef9334 +F test/joinL.test 63ce1df014bb648f99b24def9af6f67f8bec0017a87c5bd707a81d417c6213c2 F test/journal1.test c7b768041b7f494471531e17abc2f4f5ebf9e5096984f43ed17c4eb80ba34497 F test/journal2.test 9dac6b4ba0ca79c3b21446bbae993a462c2397c4 F test/journal3.test 7c3cf23ffc77db06601c1fcfc9743de8441cb77db9d1aa931863d94f5ffa140e @@ -2196,8 +2196,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 3c045a96bc65713e5544af82b8547cdcdf93fa20f9b7b0851d77f8c090cc650b -R f090a6ec615c741ecafebf974126b522 +P 8217bddaf8c0697799c7518746996665bbd1f5a327315d18091a6ab3250f1112 +R 5c22f2a95f0481fb172addeee95820dd U drh -Z ff3d32fb657fae9cfdb48424d2f017eb +Z 86dc0c94580a9c5283fa406f79f2dc76 # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index 5cd623bdcf..23a4e09203 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -8217bddaf8c0697799c7518746996665bbd1f5a327315d18091a6ab3250f1112 +9e50d338ebd974b5d1b8baf4c7a400abded28ee997414e896c2e8b9d61d39a94 diff --git a/src/wherecode.c b/src/wherecode.c index 098af7375e..83fce7e3bb 100644 --- a/src/wherecode.c +++ b/src/wherecode.c @@ -1418,6 +1418,32 @@ static int whereLoopIsOneRow(WhereLoop *pLoop){ return 0; } +/* +** Find an appropriate label for iLevel loop to jump to if it the table +** for that loop is empty. +** +** For a simple query, we might as well jump to the break-address of the +** outermost loop, halting the query, since if one of the joined tables +** is empty, the result set will be empty. But that does not work if +** there are outer joins. Nor does it work if the empty table is a +** correlated subquery (with the LATERAL keyword). +*/ +static SQLITE_NOINLINE int haltAddress( + WhereInfo *pWInfo, + int iLevel, + SrcItem *pTabItem +){ + if( pTabItem->fg.isLateral==0 ){ + while( 1 /*exit-by-break*/ ){ + if( pWInfo->a[iLevel].iLeftJoin ) break; + if( pWInfo->a[iLevel].pRJ ) break; + if( iLevel==0 ) break; + iLevel--; + }; + } + return pWInfo->a[iLevel].addrBrk; +} + /* ** Generate code for the start of the iLevel-th loop in the WHERE clause ** implementation described by pWInfo. @@ -1440,7 +1466,6 @@ Bitmask sqlite3WhereCodeOneLoopStart( sqlite3 *db; /* Database connection */ SrcItem *pTabItem; /* FROM clause term being coded */ int addrBrk; /* Jump here to break out of the loop */ - int addrHalt; /* addrBrk for the outermost loop */ int addrCont; /* Jump here to continue with next cycle */ int iRowidReg = 0; /* Rowid is stored in this register, if not zero */ int iReleaseReg = 0; /* Temp register to free before returning */ @@ -1499,14 +1524,6 @@ Bitmask sqlite3WhereCodeOneLoopStart( VdbeComment((v, "init LEFT JOIN match flag")); } - /* Compute a safe address to jump to if we discover that the table for - ** this loop is empty and can never contribute content. */ - for(j=iLevel; j>0; j--){ - if( pWInfo->a[j].iLeftJoin ) break; - if( pWInfo->a[j].pRJ ) break; - } - addrHalt = pWInfo->a[j].addrBrk; - /* Special case of a FROM clause subquery implemented as a co-routine */ if( pTabItem->fg.viaCoroutine ){ int regYield = pTabItem->regReturn; @@ -1738,7 +1755,8 @@ Bitmask sqlite3WhereCodeOneLoopStart( VdbeCoverageIf(v, pX->op==TK_GE); sqlite3ReleaseTempReg(pParse, rTemp); }else{ - sqlite3VdbeAddOp2(v, bRev ? OP_Last : OP_Rewind, iCur, addrHalt); + sqlite3VdbeAddOp2(v, bRev ? OP_Last : OP_Rewind, iCur, + haltAddress(pWInfo, iLevel, pTabItem)); VdbeCoverageIf(v, bRev==0); VdbeCoverageIf(v, bRev!=0); } @@ -2533,7 +2551,8 @@ Bitmask sqlite3WhereCodeOneLoopStart( codeCursorHint(pTabItem, pWInfo, pLevel, 0); pLevel->op = aStep[bRev]; pLevel->p1 = iCur; - pLevel->p2 = 1 + sqlite3VdbeAddOp2(v, aStart[bRev], iCur, addrHalt); + pLevel->p2 = 1 + sqlite3VdbeAddOp2(v, aStart[bRev], iCur, + haltAddress(pWInfo, iLevel, pTabItem)); VdbeCoverageIf(v, bRev==0); VdbeCoverageIf(v, bRev!=0); pLevel->p5 = SQLITE_STMTSTATUS_FULLSCAN_STEP; diff --git a/test/joinL.test b/test/joinL.test index 508f2634c8..7006bdfbce 100644 --- a/test/joinL.test +++ b/test/joinL.test @@ -141,7 +141,7 @@ do_execsql_test 2.1 { SELECT user_id, first_order_time, next_order_time, id FROM LATERAL (SELECT user_id, min(created_at) AS first_order_time FROM orders GROUP BY user_id) AS o1 - LEFT JOIN LATERAL + JOIN LATERAL (SELECT id, created_at AS next_order_time FROM orders WHERE user_id = o1.user_id AND created_at > o1.first_order_time @@ -149,7 +149,6 @@ do_execsql_test 2.1 { ON true; } { 1 2024-07-20T01:35:03 2024-07-20T01:58:10 4 - 2 2024-07-20T01:35:07 NULL NULL 3 2024-07-20T01:35:10 2024-07-20T01:58:17 5 }