diff --git a/manifest b/manifest index 74ce05e3f4..e756547505 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Fix\sOOM\shandling\sin\slittle-used\sextension\sfile\s"expert.c".\sReport\s[bugs:/info/2026-08-25T16:02:31Z\s|\s2026-08-25T16:02:31Z]. -D 2026-08-25T16:15:15.839 +C New\soptimizations\sfor\simproved\sperformance\sof\sUNION\swith\sLIMIT\s1,\swith\nor\swithout\sORDER\sBY. +D 2026-08-25T17:12:09.970 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 ba33125b1a93984f702883c730002a466860a7226dd9ce811bf75246d4ed021e -F src/shell.c.in be2c75686ba31e189d82f79f6003109a7a0ea34a1290d72b6d695b59f87eec04 +F src/select.c a962dd2573404110b438d9226494d57f71609cbc5f8365e40d1f8fa25e12f6aa +F src/shell.c.in 3f015aa86179b6c4615f0cb2352a20af228aa2cad23fc0daa88aa82b36ce2d0f F src/sqlite.h.in efc3f7aa00a2bb01c087435e6fa5f86103e310e6c3ea072b12cc110cf2715a75 F src/sqlite3.rc 015537e6ac1eec6c7050e17b616c2ffe6f70fca241835a84a4f0d5937383c479 F src/sqlite3ext.h 0efd4723bad9124ea1f581d9f1ea0254ac1c6f3e5fb29e4f3dcf36c72485a456 -F src/sqliteInt.h b4cb30785d99c6b04399a0ca35fa701d5f0b6e6df0ea8bdb7a40878cb87decc6 +F src/sqliteInt.h d58ab43b8a44f3d8c216ea042f7790fe038fce2a3207a011a175110ad322f256 F src/sqliteLimit.h c433b3fdb5e52f4226bf78d7e8081dff8fd8fbb0ce0cb1a5fee282aaa8976813 F src/status.c 7565d63a79aa2f326339a24a0461a60096d0bd2bce711fefb50b5c89335f3592 F src/table.c 0f141b58a16de7e2fbe81c308379e7279f4c6b50eb08efeec5892794a0ba30d1 @@ -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 @@ -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 f5afdbe971a9dd05f0c094d44eb7ee157d61326e7e15357a2a84f20e6937ed52 F test/select2.test 352480e0e9c66eda9c3044e412abdf5be0215b56 F test/select3.test 152ce3978c8600c70413e921e39e4c5aeb8ff1e96dd722442a9981e1b8afc702 F test/select4.test 21941409ac6b65bfca83de020afb5d976d802d3d8ad216dc774a3fbbf55fe277 @@ -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,9 @@ F tool/warnings-clang.sh bbf6a1e685e534c92ec2bfba5b1745f34fb6f0bc2a362850723a9ee F tool/warnings.sh a554d13f6e5cf3760f041b87939e3d616ec6961859c3245e8ef701d1eafc2ca2 F tool/win/sqlite.vsix deb315d026cc8400325c5863eef847784a219a2f F tool/winmain.c 00c8fb88e365c9017db14c73d3c78af62194d9644feaf60e220ab0f411f3604c -P f3ad1b4394ff0902e4ae9e8d0ea36ee198149c5ce8745851eaf84105484fedf2 -R 3bdc73eba82986d23e6b27dd2317c611 -U dan -Z ab1005716313da03aef3665d5bfce64d +P c89f4f79676f34cd8399f4a8a3934ad63d4547b9528e736bad1ee0e6f5fe6305 e78b72fadfe4c263f5af56aa887891a49055edfccda9397267804b3aaf4afd53 +R f0bf0f57d459e0ea6ee0b13b3376f8b2 +T +closed e78b72fadfe4c263f5af56aa887891a49055edfccda9397267804b3aaf4afd53 +U drh +Z 197d97631f296c0f81b1f94a1483a388 # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index b7d32fb8e1..57dc2174da 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -c89f4f79676f34cd8399f4a8a3934ad63d4547b9528e736bad1ee0e6f5fe6305 +b5b8ede613c40cf4a1ad0fe2af9b98d410982e0680b1da00c67ff1c2c34e0d6b diff --git a/src/select.c b/src/select.c index be38cbf8e1..e1e45bb3ad 100644 --- a/src/select.c +++ b/src/select.c @@ -2943,11 +2943,33 @@ static int hasAnchor(Select *p){ } /* -** This routine is called to process a compound query form from +** 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. +** +** 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 unionWithLimitOne(sqlite3 *db, Select *p){ + int v; + if( p->op!=TK_UNION ) 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; + if( OptimizationDisabled(db, SQLITE_UnionLimit) ) return 0; + 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. ** @@ -3031,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 ){ + }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 @@ -3043,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 */ @@ -3594,7 +3617,7 @@ static int multiSelectByMerge( /* Compute the limit registers */ computeLimitRegisters(pParse, p, labelEnd); - if( p->iLimit && op==TK_ALL ){ + 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/src/shell.c.in b/src/shell.c.in index b8723f98e3..bde0653e30 100644 --- a/src/shell.c.in +++ b/src/shell.c.in @@ -12594,6 +12594,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 3763fbf48b..a662cb6ce0 100644 --- a/src/sqliteInt.h +++ b/src/sqliteInt.h @@ -1968,6 +1968,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 */ /* 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/select1.test b/test/select1.test index 44e63d252d..385970d49f 100644 --- a/test/select1.test +++ b/test/select1.test @@ -1210,4 +1210,25 @@ 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