From ccfd5e09416c73924e5394cb3a1c2857a6c638a2 Mon Sep 17 00:00:00 2001 From: drh <> Date: Sat, 22 Aug 2026 10:39:18 +0000 Subject: [PATCH 1/6] Add an optimization that converts UNION into UNION ALL if there is a LIMIT 1 and no ORDER BY. FossilOrigin-Name: fb83742dd163363b9c18c530b6f95964562a0fca2a563462379595632af94387 --- manifest | 21 ++++++++++++--------- manifest.tags | 4 ++-- manifest.uuid | 2 +- src/select.c | 32 +++++++++++++++++++++++++++++--- test/colname.test | 2 +- test/e_expr.test | 2 +- test/window9.test | 1 + 7 files changed, 47 insertions(+), 17 deletions(-) diff --git a/manifest b/manifest index 487083cae3..25907f534e 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Fix\scheck-in\s[468e730036edac22]\sso\sthat\sit\sworks\swith\sDESC\sindexes.\n[bugs:/info/2026-08-22T07:01:45Z|Bug\s2026-08-22T07:01:45Z]. -D 2026-08-22T09:33:37.424 +C Add\san\soptimization\sthat\sconverts\sUNION\sinto\sUNION\sALL\sif\sthere\sis\na\sLIMIT\s1\sand\sno\sORDER\sBY. +D 2026-08-22T10:39:18.894 F .fossil-settings/binary-glob 61195414528fb3ea9693577e1980230d78a1f8b0a54c78cf1b9b24d0a409ed6a x F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea @@ -743,7 +743,7 @@ F src/printf.c b480176bd0aaed9d64e804cfa3ca27fc585f65a73e7c7ca2fe9b9016db2cf6d5 F src/random.c 606b00941a1d7dd09c381d3279a058d771f406c5213c9932bbd93d5587be4b9c F src/resolve.c 0e0990f60c93db38c735d85e5d1741130e72dd0670aec204b5a39118b53c8044 F src/rowset.c 8432130e6c344b3401a8874c3cb49fefe6873fec593294de077afea2dce5ec97 -F src/select.c f9da7b070c559f249d6cb7911f8467b6cb8fcb1c785c8d4bf3aaecccdd472789 +F src/select.c 6f42083ad3d4e8e4753c736b39bc435378b573ff8ef37e800629fe74d92e4a97 F src/shell.c.in 38548c326452780d6119e508d3c020432df042aa13a4e41bd4c1ea8ad95eb48c F src/sqlite.h.in efc3f7aa00a2bb01c087435e6fa5f86103e310e6c3ea072b12cc110cf2715a75 F src/sqlite3.rc 015537e6ac1eec6c7050e17b616c2ffe6f70fca241835a84a4f0d5937383c479 @@ -992,7 +992,7 @@ F test/collate9.test 3adcc799229545940df2f25308dd1ad65869145a F test/collateA.test b8218ab90d1fa5c59dcf156efabb1b2599c580d6 F test/collateB.test 9c840e21f9aead6fc533cea310f0bd202d5c11511088811b7e93ae7b47fccb24 F test/colmeta.test 248a644cec4c7c371cf1e107fd8fdba708dc290866c572672b6260e3466cce79 -F test/colname.test 0e32125de701c6bd86247194c6f5639b740b4f71a0d88ee1e67ff3bda9ae99ca +F test/colname.test 30f7fff482c141bf5baecfbbba4e22686ac4c581fae53a511908be8439fe48a7 F test/columncount.test 6fe99c2f35738b0129357a1cf3fa483f76140f4cd8a89014c88c33c876d2638f F test/conflict.test 3307ffdf988e04b01c4e942d8aa369a977f085bf629f43a627c9a77f39d65926 F test/conflict2.test 5557909ce683b1073982f5d1b61dfb1d41e369533bfdaf003180c5bc87282dd1 @@ -1083,7 +1083,7 @@ F test/e_createtable.test 31b9bcb6ac8876bc7ec342d86d9c231a84c62b442093a6651dfd0f F test/e_delete.test ab39084f26ae1f033c940b70ebdbbd523dc4962e F test/e_droptrigger.test 235c610f8bf8ec44513e222b9085c7e49fad65ad0c1975ac2577109dd06fd8fa F test/e_dropview.test 74e405df7fa0f762e0c9445b166fe03955856532e2bb234c372f7c51228d75e7 -F test/e_expr.test 9bdb347b78b9f4eff9153ea97797facc179a821898588471a70808b4471a69b0 +F test/e_expr.test 5225396116ed93d68b60933bbe1668f5652f268a4074ef0b7de008f1ae773f66 F test/e_fkey.test 2a4ad815372a4b57dad91cd37932e18e68bf545f6c946e9a4cb3e7bde92b8e84 F test/e_fts3.test 17ba7c373aba4d4f5696ba147ee23fd1a1ef70782af050e03e262ca187c5ee07 F test/e_insert.test f02f7f17852b2163732c6611d193f84fc67bc641fb4882c77a464076e5eba80e @@ -2092,7 +2092,7 @@ F test/window7.tcl 6a1210f05d40ec89c22960213a22cd3f98d4e2f2eb20646c83c8c30d4d761 F test/window7.test 1d31276961ae7801edc72173edaf7593e3cbc79c06d1f1f09e20d8418af403cd F test/window8.tcl c57364e64d816f6e26df60437e1202e2c1031c7b818a1a67535d1006862a026a F test/window8.test 3d931e58802b8ab8063da00f0cf30aa3351640238a952c0efb5a129e2349a4bb -F test/window9.test 7b98a7916dd87763ea35f56ea023e3b29e99744582204ccf2937a3bac411cd4d +F test/window9.test 234309555c3d449753e8773a0339cc380ec61e6f03d778cb54bbfa3fc42ba676 F test/windowA.test 6d63dc1260daa17141a55007600581778523a8b420629f1282d2acfc36af23be F test/windowB.test bba3bee77c1d3321077ff8951d543161d74f08f78633d84433ced7d6261dc2c2 F test/windowC.test 6fd75f5bb2f1343d34e470e36e68f0ff638d8a42f6aa7d99471261b31a0d42f2 @@ -2219,8 +2219,11 @@ F tool/warnings-clang.sh bbf6a1e685e534c92ec2bfba5b1745f34fb6f0bc2a362850723a9ee F tool/warnings.sh a554d13f6e5cf3760f041b87939e3d616ec6961859c3245e8ef701d1eafc2ca2 F tool/win/sqlite.vsix deb315d026cc8400325c5863eef847784a219a2f F tool/winmain.c 00c8fb88e365c9017db14c73d3c78af62194d9644feaf60e220ab0f411f3604c -P 3a01099c45bd5965286640c23d727ef0c4a61bd97146bf01bac63e296808cba9 -R b0542f919a1a303e26fc4257e0d7830d +P d1f4fa43f30d0aaf04af1b2afe9ec488654df43444bd49fae31ba1e2ab16b985 +R 73d3b1d66fa9f92279c7b8f5080b0803 +T *branch * union-to-union-all +T *sym-union-to-union-all * +T -sym-trunk * U drh -Z c2d675ebb5ec9d5669a5dadef886b467 +Z 3538658371b1dba2aeccaecf17e70473 # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.tags b/manifest.tags index bec971799f..5a0bb156f0 100644 --- a/manifest.tags +++ b/manifest.tags @@ -1,2 +1,2 @@ -branch trunk -tag trunk +branch union-to-union-all +tag union-to-union-all diff --git a/manifest.uuid b/manifest.uuid index 4434c1b883..705a57da4e 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -d1f4fa43f30d0aaf04af1b2afe9ec488654df43444bd49fae31ba1e2ab16b985 +fb83742dd163363b9c18c530b6f95964562a0fca2a563462379595632af94387 diff --git a/src/select.c b/src/select.c index cdd712b8a3..aea0962295 100644 --- a/src/select.c +++ b/src/select.c @@ -2945,11 +2945,37 @@ static int hasAnchor(Select *p){ } /* -** This routine is called to process a compound query form from +** If a compound SELECT is of the form: +** +** SELECT ... UNION SELECT ... LIMIT 1; +** +** That is, a UNION with a LIMIT of exact 1 and no OFFET or ORDER BY, +** then convert the UNION to a UNION ALL, as that will typically run +** faster. +** +** Return true (non-zero) if the optimization is successful. Return +** false (zero) of the constraints are not met. +*/ +static int convertUnionToAll(Select *p){ + int v; + if( p->op!=TK_UNION ) return 0; + if( p->pOrderBy ) return 0; + if( p->pLimit==0 ) return 0; + if( p->pLimit->pRight ) return 0; /* No OFFSET */ + v = 0; + assert( p->pLimit->pLeft!=0 ); + if( sqlite3ExprIsInteger(p->pLimit->pLeft, &v, 0, 0)==0 ) return 0; + if( v!=1 ) return 0; + p->op = TK_ALL; + return 1; +} + +/* +** This routine is called to process a compound query formed from ** two or more separate queries using UNION, UNION ALL, EXCEPT, or ** INTERSECT ** -** "p" points to the right-most of the two queries. the query on the +** "p" points to the right-most of the two queries. The query on the ** left is p->pPrior. The left query could also be a compound query ** in which case this routine will be called recursively. ** @@ -3033,7 +3059,7 @@ static int multiSelect( /* If the compound has an ORDER BY clause, then always use the merge ** algorithm. */ return multiSelectByMerge(pParse, p, pDest); - }else if( p->op!=TK_ALL ){ + }else if( p->op!=TK_ALL && !convertUnionToAll(p) ){ /* If the compound is EXCEPT, INTERSECT, or UNION (anything other than ** UNION ALL) then also always use the merge algorithm. However, the ** multiSelectByMerge() routine requires that the compound have an diff --git a/test/colname.test b/test/colname.test index 0907df641e..12d0f2a43d 100644 --- a/test/colname.test +++ b/test/colname.test @@ -403,7 +403,7 @@ do_execsql_test colname-9.330 { -- added 2019-08-10 to invalidate DROP TABLE IF EXISTS t1; -- a couple assert()s that were CREATE TABLE t1(a); -- added by ticket 3b44500725 INSERT INTO t1 VALUES(17),(2),(99),(-3),(7); - SELECT (SELECT avg(a) UNION SELECT min(a) OVER()) FROM t1; + SELECT (SELECT avg(a) UNION SELECT min(a) OVER() ORDER BY 1) FROM t1; } {17} # Issue detected by OSSFuzz on 2017-12-24 (Christmas Eve) diff --git a/test/e_expr.test b/test/e_expr.test index 5c0bfb0c1f..f24e58c40f 100644 --- a/test/e_expr.test +++ b/test/e_expr.test @@ -1896,7 +1896,7 @@ do_expr_test e_expr-35.1.3 { (SELECT count(*) FROM t2) } integer 3 do_expr_test e_expr-35.1.4 { (SELECT 4 FROM t2) } integer 4 do_expr_test e_expr-35.1.5 { - (SELECT b FROM t2 UNION SELECT a+1 FROM t2) + (SELECT b FROM t2 UNION SELECT a+1 FROM t2 ORDER BY 1) } null {} do_expr_test e_expr-35.1.6 { (SELECT a FROM t2 UNION SELECT COALESCE(b, 55) FROM t2 ORDER BY 1) diff --git a/test/window9.test b/test/window9.test index 0548624f82..999ce02b6a 100644 --- a/test/window9.test +++ b/test/window9.test @@ -261,6 +261,7 @@ do_execsql_test 8.4 { SELECT( SELECT x UNION SELECT sum( avg((SELECT x FROM v1)) ) OVER() + ORDER BY 1 ) FROM v1; } {0.0 0.0} From 32db3f9843da289e3a38813dabb596b2cc95fd35 Mon Sep 17 00:00:00 2001 From: drh <> Date: Sat, 22 Aug 2026 11:10:24 +0000 Subject: [PATCH 2/6] Improved testability. FossilOrigin-Name: e8e1ce6daecc2a142e9ea86b77aea159dd9f5040f02dd3f89bdbd8a915df8077 --- manifest | 15 ++++++--------- manifest.uuid | 2 +- src/select.c | 11 ++++++++--- 3 files changed, 15 insertions(+), 13 deletions(-) diff --git a/manifest b/manifest index 25907f534e..af6b41bbbb 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Add\san\soptimization\sthat\sconverts\sUNION\sinto\sUNION\sALL\sif\sthere\sis\na\sLIMIT\s1\sand\sno\sORDER\sBY. -D 2026-08-22T10:39:18.894 +C Improved\stestability. +D 2026-08-22T11:10:24.251 F .fossil-settings/binary-glob 61195414528fb3ea9693577e1980230d78a1f8b0a54c78cf1b9b24d0a409ed6a x F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea @@ -743,7 +743,7 @@ F src/printf.c b480176bd0aaed9d64e804cfa3ca27fc585f65a73e7c7ca2fe9b9016db2cf6d5 F src/random.c 606b00941a1d7dd09c381d3279a058d771f406c5213c9932bbd93d5587be4b9c F src/resolve.c 0e0990f60c93db38c735d85e5d1741130e72dd0670aec204b5a39118b53c8044 F src/rowset.c 8432130e6c344b3401a8874c3cb49fefe6873fec593294de077afea2dce5ec97 -F src/select.c 6f42083ad3d4e8e4753c736b39bc435378b573ff8ef37e800629fe74d92e4a97 +F src/select.c 09d7b54180a3469821bedeb43baa0381f915d588de83dc2f15fd8e28a061bd41 F src/shell.c.in 38548c326452780d6119e508d3c020432df042aa13a4e41bd4c1ea8ad95eb48c F src/sqlite.h.in efc3f7aa00a2bb01c087435e6fa5f86103e310e6c3ea072b12cc110cf2715a75 F src/sqlite3.rc 015537e6ac1eec6c7050e17b616c2ffe6f70fca241835a84a4f0d5937383c479 @@ -2219,11 +2219,8 @@ F tool/warnings-clang.sh bbf6a1e685e534c92ec2bfba5b1745f34fb6f0bc2a362850723a9ee F tool/warnings.sh a554d13f6e5cf3760f041b87939e3d616ec6961859c3245e8ef701d1eafc2ca2 F tool/win/sqlite.vsix deb315d026cc8400325c5863eef847784a219a2f F tool/winmain.c 00c8fb88e365c9017db14c73d3c78af62194d9644feaf60e220ab0f411f3604c -P d1f4fa43f30d0aaf04af1b2afe9ec488654df43444bd49fae31ba1e2ab16b985 -R 73d3b1d66fa9f92279c7b8f5080b0803 -T *branch * union-to-union-all -T *sym-union-to-union-all * -T -sym-trunk * +P fb83742dd163363b9c18c530b6f95964562a0fca2a563462379595632af94387 +R 0c5202e033d62f2bf09c84712b305ae1 U drh -Z 3538658371b1dba2aeccaecf17e70473 +Z f9f668285a435c64551374834079867c # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index 705a57da4e..39cd728279 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -fb83742dd163363b9c18c530b6f95964562a0fca2a563462379595632af94387 +e8e1ce6daecc2a142e9ea86b77aea159dd9f5040f02dd3f89bdbd8a915df8077 diff --git a/src/select.c b/src/select.c index aea0962295..192ea26436 100644 --- a/src/select.c +++ b/src/select.c @@ -2956,16 +2956,21 @@ static int hasAnchor(Select *p){ ** Return true (non-zero) if the optimization is successful. Return ** false (zero) of the constraints are not met. */ -static int convertUnionToAll(Select *p){ +static int convertUnionToAll(sqlite3 *db, Select *p){ int v; if( p->op!=TK_UNION ) return 0; - if( p->pOrderBy ) return 0; + if( NEVER(p->pOrderBy) ) return 0; /* Prevented by caller */ if( p->pLimit==0 ) return 0; if( p->pLimit->pRight ) return 0; /* No OFFSET */ v = 0; assert( p->pLimit->pLeft!=0 ); if( sqlite3ExprIsInteger(p->pLimit->pLeft, &v, 0, 0)==0 ) return 0; if( v!=1 ) return 0; + /* This is not the BalancedMerge optimization. But it does have to do + ** with Merge (the UNION operator) and we are out of optimization bits, + ** so BalancedMerge also serves to disable this optimization for testing + ** purposes. ----------------vvvvvvvvvvvvvvvvvvvv */ + if( OptimizationDisabled(db, SQLITE_BalancedMerge) ) return 0; p->op = TK_ALL; return 1; } @@ -3059,7 +3064,7 @@ static int multiSelect( /* If the compound has an ORDER BY clause, then always use the merge ** algorithm. */ return multiSelectByMerge(pParse, p, pDest); - }else if( p->op!=TK_ALL && !convertUnionToAll(p) ){ + }else if( p->op!=TK_ALL && !convertUnionToAll(db,p) ){ /* If the compound is EXCEPT, INTERSECT, or UNION (anything other than ** UNION ALL) then also always use the merge algorithm. However, the ** multiSelectByMerge() routine requires that the compound have an From ea9d7d9db142037c2eca287c86a000132f00b2b0 Mon Sep 17 00:00:00 2001 From: drh <> Date: Sat, 22 Aug 2026 14:41:42 +0000 Subject: [PATCH 3/6] New optimization bit SQLITE_UnionLimit. FossilOrigin-Name: 5890dbe54dd06fc7770267f59a430a47a39f7aaa0563274b851931e0dae754b2 --- manifest | 16 ++++++++-------- manifest.uuid | 2 +- src/select.c | 8 ++------ src/shell.c.in | 1 + src/sqliteInt.h | 1 + 5 files changed, 13 insertions(+), 15 deletions(-) diff --git a/manifest b/manifest index af6b41bbbb..b905f928ca 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Improved\stestability. -D 2026-08-22T11:10:24.251 +C New\soptimization\sbit\sSQLITE_UnionLimit. +D 2026-08-22T14:41:42.722 F .fossil-settings/binary-glob 61195414528fb3ea9693577e1980230d78a1f8b0a54c78cf1b9b24d0a409ed6a x F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea @@ -743,12 +743,12 @@ F src/printf.c b480176bd0aaed9d64e804cfa3ca27fc585f65a73e7c7ca2fe9b9016db2cf6d5 F src/random.c 606b00941a1d7dd09c381d3279a058d771f406c5213c9932bbd93d5587be4b9c F src/resolve.c 0e0990f60c93db38c735d85e5d1741130e72dd0670aec204b5a39118b53c8044 F src/rowset.c 8432130e6c344b3401a8874c3cb49fefe6873fec593294de077afea2dce5ec97 -F src/select.c 09d7b54180a3469821bedeb43baa0381f915d588de83dc2f15fd8e28a061bd41 -F src/shell.c.in 38548c326452780d6119e508d3c020432df042aa13a4e41bd4c1ea8ad95eb48c +F src/select.c cbfe97678be5a2f3ad0a9a353bbe0df11b7776092aefc28a629d1d9ce6e98ae0 +F src/shell.c.in 6ad4a5903364f5b1135fe43b935bc078689c0f4ead2076316ea058050eef6276 F src/sqlite.h.in efc3f7aa00a2bb01c087435e6fa5f86103e310e6c3ea072b12cc110cf2715a75 F src/sqlite3.rc 015537e6ac1eec6c7050e17b616c2ffe6f70fca241835a84a4f0d5937383c479 F src/sqlite3ext.h 0efd4723bad9124ea1f581d9f1ea0254ac1c6f3e5fb29e4f3dcf36c72485a456 -F src/sqliteInt.h b3d850874673b4697d55955771a08ef7ec6d155323d2ff04855383e4c7ec2eef +F src/sqliteInt.h 62277fe3c6f25a530e1a2150339be6df75c704a6e10d35773b869d81f1784331 F src/sqliteLimit.h c433b3fdb5e52f4226bf78d7e8081dff8fd8fbb0ce0cb1a5fee282aaa8976813 F src/status.c 7565d63a79aa2f326339a24a0461a60096d0bd2bce711fefb50b5c89335f3592 F src/table.c 0f141b58a16de7e2fbe81c308379e7279f4c6b50eb08efeec5892794a0ba30d1 @@ -2219,8 +2219,8 @@ F tool/warnings-clang.sh bbf6a1e685e534c92ec2bfba5b1745f34fb6f0bc2a362850723a9ee F tool/warnings.sh a554d13f6e5cf3760f041b87939e3d616ec6961859c3245e8ef701d1eafc2ca2 F tool/win/sqlite.vsix deb315d026cc8400325c5863eef847784a219a2f F tool/winmain.c 00c8fb88e365c9017db14c73d3c78af62194d9644feaf60e220ab0f411f3604c -P fb83742dd163363b9c18c530b6f95964562a0fca2a563462379595632af94387 -R 0c5202e033d62f2bf09c84712b305ae1 +P e8e1ce6daecc2a142e9ea86b77aea159dd9f5040f02dd3f89bdbd8a915df8077 +R 24dc681af8a861b2ae2469b0b7c4c4a9 U drh -Z f9f668285a435c64551374834079867c +Z d3b9fa1a61a42c63390cc2c33250bf05 # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index 39cd728279..74dcd47fd0 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -e8e1ce6daecc2a142e9ea86b77aea159dd9f5040f02dd3f89bdbd8a915df8077 +5890dbe54dd06fc7770267f59a430a47a39f7aaa0563274b851931e0dae754b2 diff --git a/src/select.c b/src/select.c index 192ea26436..cdcbda25ce 100644 --- a/src/select.c +++ b/src/select.c @@ -2958,19 +2958,15 @@ static int hasAnchor(Select *p){ */ static int convertUnionToAll(sqlite3 *db, Select *p){ int v; + assert( p->pOrderBy==0 ); /* Prevented by caller */ if( p->op!=TK_UNION ) return 0; - if( NEVER(p->pOrderBy) ) return 0; /* Prevented by caller */ if( p->pLimit==0 ) return 0; if( p->pLimit->pRight ) return 0; /* No OFFSET */ v = 0; assert( p->pLimit->pLeft!=0 ); if( sqlite3ExprIsInteger(p->pLimit->pLeft, &v, 0, 0)==0 ) return 0; if( v!=1 ) return 0; - /* This is not the BalancedMerge optimization. But it does have to do - ** with Merge (the UNION operator) and we are out of optimization bits, - ** so BalancedMerge also serves to disable this optimization for testing - ** purposes. ----------------vvvvvvvvvvvvvvvvvvvv */ - if( OptimizationDisabled(db, SQLITE_BalancedMerge) ) return 0; + if( OptimizationDisabled(db, SQLITE_UnionLimit) ) return 0; p->op = TK_ALL; return 1; } diff --git a/src/shell.c.in b/src/shell.c.in index 56f366bec7..abf9fa1d5e 100644 --- a/src/shell.c.in +++ b/src/shell.c.in @@ -12596,6 +12596,7 @@ static int do_meta_command(const char *zLine, ShellState *p){ { 0x10000000, 1, "OrderBySubq" }, { 0x20000000, 1, "StarQuery" }, { 0x40000000, 1, "ExistsToJoin" }, + { 0x80000000, 1, "UnionLimit" }, { 0xffffffff, 0, "All" }, }; unsigned int curOpt; diff --git a/src/sqliteInt.h b/src/sqliteInt.h index 2ad281533c..2b274c0a4e 100644 --- a/src/sqliteInt.h +++ b/src/sqliteInt.h @@ -1967,6 +1967,7 @@ struct sqlite3 { #define SQLITE_OrderBySubq 0x10000000 /* ORDER BY in subquery helps outer */ #define SQLITE_StarQuery 0x20000000 /* Heurists for star queries */ #define SQLITE_ExistsToJoin 0x40000000 /* The EXISTS-to-JOIN optimization */ +#define SQLITE_UnionLimit 0x80000000 /* Optimizations for UNION + LIMIT */ #define SQLITE_AllOpts 0xffffffff /* All optimizations */ /* From 2a71db26fa87496eb91f0d2070dcd757bee3f95c Mon Sep 17 00:00:00 2001 From: drh <> Date: Sat, 22 Aug 2026 15:56:08 +0000 Subject: [PATCH 4/6] Improved use of LIMIT in UNION operators. FossilOrigin-Name: 7304e5aeafe3981670d2f415b42a4ecc2371647c492d3b13098e564b27ba541e --- manifest | 12 ++++++------ manifest.uuid | 2 +- src/select.c | 2 +- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/manifest b/manifest index b905f928ca..7884517bf2 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C New\soptimization\sbit\sSQLITE_UnionLimit. -D 2026-08-22T14:41:42.722 +C Improved\suse\sof\sLIMIT\sin\sUNION\soperators. +D 2026-08-22T15:56:08.197 F .fossil-settings/binary-glob 61195414528fb3ea9693577e1980230d78a1f8b0a54c78cf1b9b24d0a409ed6a x F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea @@ -743,7 +743,7 @@ F src/printf.c b480176bd0aaed9d64e804cfa3ca27fc585f65a73e7c7ca2fe9b9016db2cf6d5 F src/random.c 606b00941a1d7dd09c381d3279a058d771f406c5213c9932bbd93d5587be4b9c F src/resolve.c 0e0990f60c93db38c735d85e5d1741130e72dd0670aec204b5a39118b53c8044 F src/rowset.c 8432130e6c344b3401a8874c3cb49fefe6873fec593294de077afea2dce5ec97 -F src/select.c cbfe97678be5a2f3ad0a9a353bbe0df11b7776092aefc28a629d1d9ce6e98ae0 +F src/select.c 63d63ab7ac7d59f3f559864fbe321c587f000ed4823d19cc571a313a98d62efa F src/shell.c.in 6ad4a5903364f5b1135fe43b935bc078689c0f4ead2076316ea058050eef6276 F src/sqlite.h.in efc3f7aa00a2bb01c087435e6fa5f86103e310e6c3ea072b12cc110cf2715a75 F src/sqlite3.rc 015537e6ac1eec6c7050e17b616c2ffe6f70fca241835a84a4f0d5937383c479 @@ -2219,8 +2219,8 @@ F tool/warnings-clang.sh bbf6a1e685e534c92ec2bfba5b1745f34fb6f0bc2a362850723a9ee F tool/warnings.sh a554d13f6e5cf3760f041b87939e3d616ec6961859c3245e8ef701d1eafc2ca2 F tool/win/sqlite.vsix deb315d026cc8400325c5863eef847784a219a2f F tool/winmain.c 00c8fb88e365c9017db14c73d3c78af62194d9644feaf60e220ab0f411f3604c -P e8e1ce6daecc2a142e9ea86b77aea159dd9f5040f02dd3f89bdbd8a915df8077 -R 24dc681af8a861b2ae2469b0b7c4c4a9 +P 5890dbe54dd06fc7770267f59a430a47a39f7aaa0563274b851931e0dae754b2 +R 9b1a5d38aeac06666007be486cdcac10 U drh -Z d3b9fa1a61a42c63390cc2c33250bf05 +Z e3c40687527f5431e836020f9f128de6 # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index 74dcd47fd0..4f93df4436 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -5890dbe54dd06fc7770267f59a430a47a39f7aaa0563274b851931e0dae754b2 +7304e5aeafe3981670d2f415b42a4ecc2371647c492d3b13098e564b27ba541e diff --git a/src/select.c b/src/select.c index cdcbda25ce..0b9e4bc81f 100644 --- a/src/select.c +++ b/src/select.c @@ -3623,7 +3623,7 @@ static int multiSelectByMerge( /* Compute the limit registers */ computeLimitRegisters(pParse, p, labelEnd); - if( p->iLimit && op==TK_ALL ){ + if( p->iLimit && (op==TK_ALL || op==TK_UNION) ){ regLimitA = ++pParse->nMem; regLimitB = ++pParse->nMem; sqlite3VdbeAddOp2(v, OP_Copy, p->iOffset ? p->iOffset+1 : p->iLimit, From 46c7844d76d8d2d5ded1ca5e70b6840b2dbea8ae Mon Sep 17 00:00:00 2001 From: drh <> Date: Tue, 25 Aug 2026 16:29:27 +0000 Subject: [PATCH 5/6] Make the optimization of UNION with ORDER BY and LIMIT sound by restricing it to LIMIT 1. FossilOrigin-Name: 7e6e665d05e5d5895bac60d68b49bdf28e2ec6d8dc4ef8c18703f2e6d4f8870f --- manifest | 14 +++++++------- manifest.uuid | 2 +- src/select.c | 26 +++++++++++--------------- test/select1.test | 28 ++++++++++++++++++++++++++++ 4 files changed, 47 insertions(+), 23 deletions(-) diff --git a/manifest b/manifest index 88f45368e7..8f07d307ab 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Merge\sthe\slatest\strunk\senhancements\sinto\sthe\sunion-to-union-all\sbranch. -D 2026-08-25T15:49:27.176 +C Make\sthe\soptimization\sof\sUNION\swith\sORDER\sBY\sand\sLIMIT\ssound\sby\srestricing\nit\sto\sLIMIT\s1. +D 2026-08-25T16:29:27.136 F .fossil-settings/binary-glob 61195414528fb3ea9693577e1980230d78a1f8b0a54c78cf1b9b24d0a409ed6a x F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea @@ -743,7 +743,7 @@ F src/printf.c b480176bd0aaed9d64e804cfa3ca27fc585f65a73e7c7ca2fe9b9016db2cf6d5 F src/random.c 606b00941a1d7dd09c381d3279a058d771f406c5213c9932bbd93d5587be4b9c F src/resolve.c 0e0990f60c93db38c735d85e5d1741130e72dd0670aec204b5a39118b53c8044 F src/rowset.c 8432130e6c344b3401a8874c3cb49fefe6873fec593294de077afea2dce5ec97 -F src/select.c 5881655693910e832d15c7e9f65b8519d688bd0771268cecf0776198bb0e7d23 +F src/select.c a962dd2573404110b438d9226494d57f71609cbc5f8365e40d1f8fa25e12f6aa F src/shell.c.in 3f015aa86179b6c4615f0cb2352a20af228aa2cad23fc0daa88aa82b36ce2d0f F src/sqlite.h.in efc3f7aa00a2bb01c087435e6fa5f86103e310e6c3ea072b12cc110cf2715a75 F src/sqlite3.rc 015537e6ac1eec6c7050e17b616c2ffe6f70fca241835a84a4f0d5937383c479 @@ -1606,7 +1606,7 @@ F test/schemafault.test 1936bceca55ac82c5efbcc9fc91a1933e45c8d1e1d106b9a7e56c972 F test/securedel.test 2f70b2449186a1921bd01ec9da407fbfa98c3a7a5521854c300c194b2ff09384 F test/securedel2.test 2d54c28e46eb1fd6902089958b20b1b056c6f1c5 F test/seekscan1.test 2148a174ba55fa378f18fdf62eeed1f3a3cc9b6f360a948db613cad0c80b1f04 -F test/select1.test 692e84cfa29c405854c69e8a4027183d64c22952866a123fabbce741a379e889 +F test/select1.test ce7ba6aef289a8a1fd6e140fb58bcc8e095034cbc6d8aab7d0eea9145cd49818 F test/select2.test 352480e0e9c66eda9c3044e412abdf5be0215b56 F test/select3.test 152ce3978c8600c70413e921e39e4c5aeb8ff1e96dd722442a9981e1b8afc702 F test/select4.test 21941409ac6b65bfca83de020afb5d976d802d3d8ad216dc774a3fbbf55fe277 @@ -2219,8 +2219,8 @@ F tool/warnings-clang.sh bbf6a1e685e534c92ec2bfba5b1745f34fb6f0bc2a362850723a9ee F tool/warnings.sh a554d13f6e5cf3760f041b87939e3d616ec6961859c3245e8ef701d1eafc2ca2 F tool/win/sqlite.vsix deb315d026cc8400325c5863eef847784a219a2f F tool/winmain.c 00c8fb88e365c9017db14c73d3c78af62194d9644feaf60e220ab0f411f3604c -P 7304e5aeafe3981670d2f415b42a4ecc2371647c492d3b13098e564b27ba541e f3ad1b4394ff0902e4ae9e8d0ea36ee198149c5ce8745851eaf84105484fedf2 -R 4cf2fbc81f77e56271b06c93e322178e +P c58631b26ad6ee39d8db2700a26413249fd88d137c3a7944bad7af07cd7fe121 +R 76e0f6f942ae68e31165a889e51c3470 U drh -Z 65583537f8dbcdebda89f9514069d603 +Z 52028eaf171cca386a7f527b8fa6e46f # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index 57fef555d6..bf97a8177d 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -c58631b26ad6ee39d8db2700a26413249fd88d137c3a7944bad7af07cd7fe121 +7e6e665d05e5d5895bac60d68b49bdf28e2ec6d8dc4ef8c18703f2e6d4f8870f diff --git a/src/select.c b/src/select.c index 74bb22346d..e1e45bb3ad 100644 --- a/src/select.c +++ b/src/select.c @@ -2943,20 +2943,16 @@ static int hasAnchor(Select *p){ } /* -** If a compound SELECT is of the form: +** Return TRUE if p is a UNION with a LIMIT of exactly 1 and no OFFSET +** clause. This is a precondition for a couple of related optimizations. ** -** SELECT ... UNION SELECT ... LIMIT 1; -** -** That is, a UNION with a LIMIT of exact 1 and no OFFET or ORDER BY, -** then convert the UNION to a UNION ALL, as that will typically run -** faster. -** -** Return true (non-zero) if the optimization is successful. Return -** false (zero) of the constraints are not met. +** False negatives are harmless (apart from resulting in a slower query). +** False positives can result in incorrect answers, however. To provoke +** false negatives for testing purposes, disable the SQLITE_UnionLimit +** optimization. */ -static int convertUnionToAll(sqlite3 *db, Select *p){ +static int unionWithLimitOne(sqlite3 *db, Select *p){ int v; - assert( p->pOrderBy==0 ); /* Prevented by caller */ if( p->op!=TK_UNION ) return 0; if( p->pLimit==0 ) return 0; if( p->pLimit->pRight ) return 0; /* No OFFSET */ @@ -2965,7 +2961,6 @@ static int convertUnionToAll(sqlite3 *db, Select *p){ if( sqlite3ExprIsInteger(p->pLimit->pLeft, &v, 0, 0)==0 ) return 0; if( v!=1 ) return 0; if( OptimizationDisabled(db, SQLITE_UnionLimit) ) return 0; - p->op = TK_ALL; return 1; } @@ -3058,7 +3053,7 @@ static int multiSelect( /* If the compound has an ORDER BY clause, then always use the merge ** algorithm. */ return multiSelectByMerge(pParse, p, pDest); - }else if( p->op!=TK_ALL && !convertUnionToAll(db,p) ){ + }else if( p->op!=TK_ALL && !unionWithLimitOne(db,p) ){ /* If the compound is EXCEPT, INTERSECT, or UNION (anything other than ** UNION ALL) then also always use the merge algorithm. However, the ** multiSelectByMerge() routine requires that the compound have an @@ -3070,7 +3065,8 @@ static int multiSelect( p->pOrderBy->a[0].u.x.iOrderByCol = 1; return multiSelectByMerge(pParse, p, pDest); }else{ - /* For a UNION ALL compound without ORDER BY, simply run the left + /* For a UNION ALL compound without ORDER BY, or for a UNION with a + ** LIMIT of exactly 1 and no OFFSET and no ORDER BY, simply run the left ** query, then run the right query */ int addr = 0; int nLimit = 0; /* Initialize to suppress harmless compiler warning */ @@ -3621,7 +3617,7 @@ static int multiSelectByMerge( /* Compute the limit registers */ computeLimitRegisters(pParse, p, labelEnd); - if( p->iLimit && (op==TK_ALL || op==TK_UNION) ){ + if( p->iLimit && (op==TK_ALL || unionWithLimitOne(db,p)) ){ regLimitA = ++pParse->nMem; regLimitB = ++pParse->nMem; sqlite3VdbeAddOp2(v, OP_Copy, p->iOffset ? p->iOffset+1 : p->iLimit, diff --git a/test/select1.test b/test/select1.test index 44e63d252d..cca5bae4d1 100644 --- a/test/select1.test +++ b/test/select1.test @@ -1210,4 +1210,32 @@ do_execsql_test select1-21.1 { SELECT a,(+a)b,(+a)b,(+a)b,NOT EXISTS(SELECT null FROM t2),CASE z WHEN 487 THEN 992 WHEN 391 THEN 203 WHEN 10 THEN '?k Date: Tue, 25 Aug 2026 17:08:19 +0000 Subject: [PATCH 6/6] Fix two test cases that do not work when optimizations are disabled. FossilOrigin-Name: e78b72fadfe4c263f5af56aa887891a49055edfccda9397267804b3aaf4afd53 --- manifest | 12 ++++++------ manifest.uuid | 2 +- test/select1.test | 7 ------- 3 files changed, 7 insertions(+), 14 deletions(-) diff --git a/manifest b/manifest index 8f07d307ab..04dece4611 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Make\sthe\soptimization\sof\sUNION\swith\sORDER\sBY\sand\sLIMIT\ssound\sby\srestricing\nit\sto\sLIMIT\s1. -D 2026-08-25T16:29:27.136 +C Fix\stwo\stest\scases\sthat\sdo\snot\swork\swhen\soptimizations\sare\sdisabled. +D 2026-08-25T17:08:19.067 F .fossil-settings/binary-glob 61195414528fb3ea9693577e1980230d78a1f8b0a54c78cf1b9b24d0a409ed6a x F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea @@ -1606,7 +1606,7 @@ F test/schemafault.test 1936bceca55ac82c5efbcc9fc91a1933e45c8d1e1d106b9a7e56c972 F test/securedel.test 2f70b2449186a1921bd01ec9da407fbfa98c3a7a5521854c300c194b2ff09384 F test/securedel2.test 2d54c28e46eb1fd6902089958b20b1b056c6f1c5 F test/seekscan1.test 2148a174ba55fa378f18fdf62eeed1f3a3cc9b6f360a948db613cad0c80b1f04 -F test/select1.test ce7ba6aef289a8a1fd6e140fb58bcc8e095034cbc6d8aab7d0eea9145cd49818 +F test/select1.test f5afdbe971a9dd05f0c094d44eb7ee157d61326e7e15357a2a84f20e6937ed52 F test/select2.test 352480e0e9c66eda9c3044e412abdf5be0215b56 F test/select3.test 152ce3978c8600c70413e921e39e4c5aeb8ff1e96dd722442a9981e1b8afc702 F test/select4.test 21941409ac6b65bfca83de020afb5d976d802d3d8ad216dc774a3fbbf55fe277 @@ -2219,8 +2219,8 @@ F tool/warnings-clang.sh bbf6a1e685e534c92ec2bfba5b1745f34fb6f0bc2a362850723a9ee F tool/warnings.sh a554d13f6e5cf3760f041b87939e3d616ec6961859c3245e8ef701d1eafc2ca2 F tool/win/sqlite.vsix deb315d026cc8400325c5863eef847784a219a2f F tool/winmain.c 00c8fb88e365c9017db14c73d3c78af62194d9644feaf60e220ab0f411f3604c -P c58631b26ad6ee39d8db2700a26413249fd88d137c3a7944bad7af07cd7fe121 -R 76e0f6f942ae68e31165a889e51c3470 +P 7e6e665d05e5d5895bac60d68b49bdf28e2ec6d8dc4ef8c18703f2e6d4f8870f +R d3e75f49084b217af9b302a35ef67efa U drh -Z 52028eaf171cca386a7f527b8fa6e46f +Z 34e45bf4bccec09351565be06ae13be1 # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index bf97a8177d..a2c2359659 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -7e6e665d05e5d5895bac60d68b49bdf28e2ec6d8dc4ef8c18703f2e6d4f8870f +e78b72fadfe4c263f5af56aa887891a49055edfccda9397267804b3aaf4afd53 diff --git a/test/select1.test b/test/select1.test index cca5bae4d1..385970d49f 100644 --- a/test/select1.test +++ b/test/select1.test @@ -1227,15 +1227,8 @@ do_execsql_test select1-22.3 { do_execsql_test select1-22.3 { SELECT a FROM t1 UNION SELECT b FROM t2 LIMIT 1; } {1} -do_execsql_test select1-22.3-eqp { - EXPLAIN SELECT a FROM t1 UNION SELECT b FROM t2 LIMIT 1; -} {/UNION ALL/} do_execsql_test select1-22.4 { SELECT a FROM t1 UNION SELECT b FROM t2 LIMIT 2; } {1 2} -do_execsql_test select1-22.4-eqp { - EXPLAIN SELECT a FROM t1 UNION SELECT b FROM t2 LIMIT 2; -} {~/UNION ALL/} - finish_test