From 0476ff98a83317642a16bca9a5b1eef97925dbd8 Mon Sep 17 00:00:00 2001 From: Richard Guo Date: Sat, 28 Mar 2026 16:52:37 +0900 Subject: [PATCH v1 2/2] Fix volatile function evaluation in eager aggregation Pushing aggregates containing volatile functions below a join can violate volatility semantics by changing the number of times the function is executed. Here we check the Aggref nodes in the targetlist and havingQual for volatile functions and disable eager aggregation when such functions are present. --- src/backend/optimizer/plan/initsplan.c | 11 ++++++++++ src/test/regress/expected/eager_aggregate.out | 20 +++++++++++++++++++ src/test/regress/sql/eager_aggregate.sql | 8 ++++++++ 3 files changed, 39 insertions(+) diff --git a/src/backend/optimizer/plan/initsplan.c b/src/backend/optimizer/plan/initsplan.c index b207b8d913b..96ee312ebdf 100644 --- a/src/backend/optimizer/plan/initsplan.c +++ b/src/backend/optimizer/plan/initsplan.c @@ -810,6 +810,17 @@ create_agg_clause_infos(PlannerInfo *root) Assert(aggref->aggorder == NIL); Assert(aggref->aggdistinct == NIL); + /* + * We cannot push down aggregates that contain volatile functions. + * Doing so would change the number of times the function is + * evaluated. + */ + if (contain_volatile_functions((Node *) aggref)) + { + eager_agg_applicable = false; + break; + } + /* * If there are any securityQuals, do not try to apply eager * aggregation if any non-leakproof aggregate functions are present. diff --git a/src/test/regress/expected/eager_aggregate.out b/src/test/regress/expected/eager_aggregate.out index 5ac966186f7..d1b86be3a62 100644 --- a/src/test/regress/expected/eager_aggregate.out +++ b/src/test/regress/expected/eager_aggregate.out @@ -428,6 +428,26 @@ GROUP BY t1.a ORDER BY t1.a; RESET geqo; RESET geqo_threshold; +-- Ensure eager aggregation is not applied because random() is a volatile +-- function +EXPLAIN (COSTS OFF) +SELECT t1.a, avg(t2.c + random()) + FROM eager_agg_t1 t1 + JOIN eager_agg_t2 t2 ON t1.b = t2.b +GROUP BY t1.a ORDER BY t1.a; + QUERY PLAN +----------------------------------------------------- + GroupAggregate + Group Key: t1.a + -> Sort + Sort Key: t1.a + -> Hash Join + Hash Cond: (t2.b = t1.b) + -> Seq Scan on eager_agg_t2 t2 + -> Hash + -> Seq Scan on eager_agg_t1 t1 +(9 rows) + DROP TABLE eager_agg_t1; DROP TABLE eager_agg_t2; DROP TABLE eager_agg_t3; diff --git a/src/test/regress/sql/eager_aggregate.sql b/src/test/regress/sql/eager_aggregate.sql index abe6d6ae09f..97e10dd7cf4 100644 --- a/src/test/regress/sql/eager_aggregate.sql +++ b/src/test/regress/sql/eager_aggregate.sql @@ -163,6 +163,14 @@ GROUP BY t1.a ORDER BY t1.a; RESET geqo; RESET geqo_threshold; +-- Ensure eager aggregation is not applied because random() is a volatile +-- function +EXPLAIN (COSTS OFF) +SELECT t1.a, avg(t2.c + random()) + FROM eager_agg_t1 t1 + JOIN eager_agg_t2 t2 ON t1.b = t2.b +GROUP BY t1.a ORDER BY t1.a; + DROP TABLE eager_agg_t1; DROP TABLE eager_agg_t2; DROP TABLE eager_agg_t3; -- 2.39.5 (Apple Git-154)