From 4f5062f0ba87dc4e4daa0fbc9155c47938d837f5 Mon Sep 17 00:00:00 2001 From: dan Date: Tue, 20 Oct 2026 14:07:59 +0000 Subject: [PATCH] Correctly handle aggregate functions that aggregate over the rows of a window function query, even if they are nested within sub-selects. Report [bugs:/info/2026-10-16T08:07:09Z | 2026-10-16T08:07:09Z]. FossilOrigin-Name: 7522512ff96d869c46596e7d514b65f210530db09e6359c2f37911d45f34979d --- manifest | 16 +++++++------- manifest.uuid | 2 +- src/window.c | 28 +++++++++++++++++++------ test/window1.test | 53 +++++++++++++++++++++++++++++++++++++++++++++-- 4 files changed, 82 insertions(+), 17 deletions(-) diff --git a/manifest b/manifest index a77ddd29f6..26a604a6c7 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Fix\sthe\sCLI\sto\scorrectly\shandle\sa\s".exit"\simmediately\sfollowing\s\na\s".output\smemory".\s[bugs:/info/2026-10-20T13:13:50Z|Bug\s2026-10-20T13:13:50Z] -D 2026-10-20T13:45:54.887 +C Correctly\shandle\saggregate\sfunctions\sthat\saggregate\sover\sthe\srows\sof\sa\swindow\sfunction\squery,\seven\sif\sthey\sare\snested\swithin\ssub-selects.\sReport\s[bugs:/info/2026-10-16T08:07:09Z\s|\s2026-10-16T08:07:09Z]. +D 2026-10-20T14:07:59.158 F .fossil-settings/binary-glob 61195414528fb3ea9693577e1980230d78a1f8b0a54c78cf1b9b24d0a409ed6a x F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea @@ -888,7 +888,7 @@ F src/where.c c8e8de067a8f6401dd7cde59b9cfbbe49ad13d5ae60d529110d2bd52df8b9a8b F src/whereInt.h f039ad303bc2913de673e431821ef8db3b619d249e6e5548d90072a684e72285 F src/wherecode.c 1a6d2999159b28d8c0b968458b90ec183b3813cb332c11515ab60a73db5c07fd F src/whereexpr.c 76f72a95c5cf0e8a8edb679ca4fb7afa17ba45ff4effe73c414266510109e701 -F src/window.c 79a17de595e62aa45961ef1cbe3392e7ebae69fdde3fd0b0200254bc4647025e +F src/window.c 88ae7299cabf3124639236accbc2fa461a81473232e758c85d016922bf5061ab F test/8_3_names.test ebbb5cd36741350040fd28b432ceadf495be25b2 F test/affinity2.test 4d7a34d328e58ca2a2d78fd76c27614a41ca7ddf4312ded9c68c04f430b3b47d F test/affinity3.test 9b7d1133e11d5edd7805573c4ab6f3ba73b0b74a1f280d5b130d4bf3506a93ff @@ -2147,7 +2147,7 @@ F test/win32lock.test e56d7a9b6cf9d5f3867c2dd19ff36c5326881e4038c6867610ecb3a986 F test/win32longpath.test 2641e3a5dbb59f49456f6caf78c0acd6ec7dbba27cb56363bab8fbfe93995caa F test/win32nolock.test 95854dc0206b8a95e4aee15a76acc082767b38f079b2e24676aed6cbb0f32798 F test/win32shm.test 7fe1aba2287033af64187597b6e9afd9878431d0ab4391da94f8ed85b0fea228 -F test/window1.test b46d28b9698559e66aa4adafd8074b940faee498bf0c4fbdb62548bfcccc67e7 +F test/window1.test 62864c1d6dd0f446908ec6eb13b08f2806a3cd93ba6d19f7f7b3be0b1945fc95 F test/window2.tcl 492c125fa550cda1dd3555768a2303b3effbeceee215293adf8871efc25f1476 F test/window2.test e466a88bd626d66edc3d352d7d7e1d5531e0079b549ba44efb029d1fbff9fd3c F test/window3.tcl acea6e86a4324a210fd608d06741010ca83ded9fde438341cb978c49928faf03 @@ -2287,8 +2287,8 @@ F tool/warnings-clang.sh bbf6a1e685e534c92ec2bfba5b1745f34fb6f0bc2a362850723a9ee F tool/warnings.sh a554d13f6e5cf3760f041b87939e3d616ec6961859c3245e8ef701d1eafc2ca2 F tool/win/sqlite.vsix deb315d026cc8400325c5863eef847784a219a2f F tool/winmain.c 00c8fb88e365c9017db14c73d3c78af62194d9644feaf60e220ab0f411f3604c -P 10b8f4166da26908aef9182824b0c967af7d861ff6c83daed7cd50321ee563b3 -R ca4dc3eb2fbea5aa3011790dd25c24ba -U drh -Z ef34b785826a1f9d21d718f271f1c2cb +P 2fdd752dc92692e08c805eb10bf78d97e93c8436433c8175fbff310513f340d7 +R 8853bfb7529eba79f848109b3f25e41a +U dan +Z 0c52888c8f52d7499ce6e91f05521555 # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index 395a4719c8..b365ea92ab 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -2fdd752dc92692e08c805eb10bf78d97e93c8436433c8175fbff310513f340d7 +7522512ff96d869c46596e7d514b65f210530db09e6359c2f37911d45f34979d diff --git a/src/window.c b/src/window.c index 5b07761eb5..52859f890b 100644 --- a/src/window.c +++ b/src/window.c @@ -756,11 +756,16 @@ static int selectWindowRewriteExprCb(Walker *pWalker, Expr *pExpr){ /* If this function is being called from within a scalar sub-select ** that used by the SELECT statement being processed, only process - ** TK_COLUMN expressions that refer to it (the outer SELECT). Do - ** not process aggregates or window functions at all, as they belong - ** to the scalar sub-select. */ + ** TK_COLUMN and TK_AGGREGATE expressions that refer to it (the outer + ** SELECT). Do not process window functions at all, as they belong to the + ** scalar sub-select. */ if( p->pSubSelect ){ - if( pExpr->op!=TK_COLUMN ){ + if( pExpr->op==TK_AGG_FUNCTION ){ + /* If op2==walkerDepth, then this expression aggregates over the rows + ** of the SELECT being processed, even though it is within a scalar + ** sub-select. In that case proceed, otherwise skip this node. */ + if( pExpr->op2!=pWalker->walkerDepth ) return WRC_Continue; + }else if( pExpr->op!=TK_COLUMN ){ return WRC_Continue; }else{ int nSrc = p->pSrc->nSrc; @@ -841,7 +846,9 @@ static int selectWindowRewriteSelectCb(Walker *pWalker, Select *pSelect){ return WRC_Continue; }else{ p->pSubSelect = pSelect; + pWalker->walkerDepth++; sqlite3WalkSelect(pWalker, pSelect); + pWalker->walkerDepth--; p->pSubSelect = pSave; } return WRC_Prune; @@ -945,8 +952,15 @@ static int sqlite3WindowExtraAggFuncDepth(Walker *pWalker, Expr *pExpr){ return WRC_Continue; } +/* +** Walker callback used to detect aggregate functions in the ORDER BY +** clause of an otherwise non-aggregate SELECT. +*/ static int disallowAggregatesInOrderByCb(Walker *pWalker, Expr *pExpr){ - if( pExpr->op==TK_AGG_FUNCTION && pExpr->pAggInfo==0 ){ + if( pExpr->op==TK_AGG_FUNCTION + && pExpr->pAggInfo==0 + && pExpr->op2==pWalker->walkerDepth + ){ assert( !ExprHasProperty(pExpr, EP_IntValue) ); sqlite3ErrorMsg(pWalker->pParse, "misuse of aggregate: %s()", pExpr->u.zToken); @@ -993,7 +1007,9 @@ int sqlite3WindowRewrite(Parse *pParse, Select *p){ sqlite3WalkSelect(&w, p); if( (p->selFlags & SF_Aggregate)==0 ){ w.xExprCallback = disallowAggregatesInOrderByCb; - w.xSelectCallback = 0; + w.xSelectCallback = sqlite3WalkerDepthIncrease; + w.xSelectCallback2 = sqlite3WalkerDepthDecrease; + w.walkerDepth = 0; sqlite3WalkExprList(&w, p->pOrderBy); } diff --git a/test/window1.test b/test/window1.test index db9af62ac8..c391580733 100644 --- a/test/window1.test +++ b/test/window1.test @@ -1894,7 +1894,7 @@ do_execsql_test 58.1 { } {347 678.0} # 2020-06-06 ticket e5504e987e419fb0 -do_catchsql_test 59.1 { +do_catchsql_test 59.1.1 { DROP TABLE IF EXISTS t1; CREATE TABLE t1(x INTEGER PRIMARY KEY); INSERT INTO t1 VALUES (123); @@ -1902,7 +1902,16 @@ do_catchsql_test 59.1 { ntile( (SELECT sum(x)) ) OVER(ORDER BY x), min(x) OVER(ORDER BY x) FROM t1; -} {1 {misuse of aggregate: sum()}} +} {0 {1 123}} +do_catchsql_test 59.1.2 { + DROP TABLE IF EXISTS t1; + CREATE TABLE t1(x INTEGER PRIMARY KEY); + INSERT INTO t1 VALUES (123); + SELECT + ntile( sum(x) ) OVER(ORDER BY x), + min(x) OVER(ORDER BY x) + FROM t1; +} {0 {1 123}} # 2020-06-07 ticket f7d890858f361402 do_execsql_test 60.1 { @@ -2424,4 +2433,44 @@ do_execsql_test 79.3 { FROM t0; } {6 1} +#------------------------------------------------------------------------- +# Test aggregates within scalar queries in window functions are handled +# correctly. Report +# +reset_db +do_execsql_test 80.0 { + DROP TABLE IF EXISTS pst_agg_scope; + CREATE TABLE pst_agg_scope(x); + INSERT INTO pst_agg_scope VALUES(456); +} + +do_execsql_test 80.2 { + SELECT (SELECT MIN(t.x)) FROM pst_agg_scope AS t; +} {456} + +do_execsql_test 80.3 { + SELECT COUNT(*) OVER (), MIN(t.x) FROM pst_agg_scope AS t; +} {1 456} + +do_execsql_test 80.4 { + SELECT COUNT(*) OVER (), (SELECT MIN(t.x)) FROM pst_agg_scope AS t +} {1 456} + +#------------------------------------------------------------------------- +reset_db +do_execsql_test 81.0 { + CREATE TABLE t1(a INT,b TEXT,c BLOB,d REAL); + CREATE INDEX t1a ON t1(a); + CREATE TABLE t2(x,y,z PRIMARY KEY) WITHOUT ROWID; +} + +do_execsql_test 81.1 { + SELECT json_group_object( + (SELECT json_group_object( a NOTNULL , a ) FILTER(WHERE a) OVER FROM t2 GROUP BY 1 HAVING d+7<10) + , b IN(1,2 IN(SELECT 0),3)) + OVER ( ORDER BY d RANGE BETWEEN CURRENT ROW AND CURRENT ROW ) AS d + FROM t1 NATURAL FULL JOIN t1 a; +} {"{}"} + + finish_test