This is an automated email from the ASF dual-hosted git repository.
xiedeyantu pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/calcite.git
The following commit(s) were added to refs/heads/main by this push:
new 2f89de1f4a [CALCITE-7643] AggregateMinMaxToLimitRule drops FILTER
condition for filtered MIN/MAX aggregates
2f89de1f4a is described below
commit 2f89de1f4a227ba8cdf4bc9b7ed718720f211eae
Author: Zhen Chen <[email protected]>
AuthorDate: Sun Jul 5 13:15:39 2026 +0800
[CALCITE-7643] AggregateMinMaxToLimitRule drops FILTER condition for
filtered MIN/MAX aggregates
Signed-off-by: xiedeyantu <[email protected]>
---
.../rel/rules/AggregateMinMaxToLimitRule.java | 23 +++++++---
.../org/apache/calcite/test/RelOptRulesTest.java | 13 ++++++
.../org/apache/calcite/test/RelOptRulesTest.xml | 29 +++++++++++++
core/src/test/resources/sql/planner.iq | 49 ++++++++++++++++++++++
4 files changed, 108 insertions(+), 6 deletions(-)
diff --git
a/core/src/main/java/org/apache/calcite/rel/rules/AggregateMinMaxToLimitRule.java
b/core/src/main/java/org/apache/calcite/rel/rules/AggregateMinMaxToLimitRule.java
index 51c2996176..e76b3de3de 100644
---
a/core/src/main/java/org/apache/calcite/rel/rules/AggregateMinMaxToLimitRule.java
+++
b/core/src/main/java/org/apache/calcite/rel/rules/AggregateMinMaxToLimitRule.java
@@ -92,12 +92,23 @@ protected AggregateMinMaxToLimitRule(Config config) {
// MIN is ASC, MAX is DESC
final boolean isDesc = aggCall.getAggregation().kind == SqlKind.MAX;
- RexNode subQuery = builder.scalarQuery(b -> b.push(aggInput)
- .project(r)
- .filter(b.isNotNull(r))
- .sortLimit(0, 1,
- isDesc ? builder.desc(r) : r)
- .build());
+ RexNode subQuery = builder.scalarQuery(b -> {
+ b.push(aggInput);
+ final RexNode inputField = b.field(idx);
+ final List<RexNode> predicates = new ArrayList<>();
+ predicates.add(b.isNotNull(inputField));
+ if (aggCall.hasFilter()) {
+ // filterArg references the aggregate input, so apply it before
projection
+ predicates.add(b.field(aggCall.filterArg));
+ }
+ b.filter(predicates)
+ // Scalar sub-query must return only the MIN/MAX argument
+ .project(inputField);
+ final RexNode sortField = b.field(0);
+ return b.sortLimit(0, 1,
+ isDesc ? b.desc(sortField) : sortField)
+ .build();
+ });
newProjects.add(subQuery);
}
diff --git a/core/src/test/java/org/apache/calcite/test/RelOptRulesTest.java
b/core/src/test/java/org/apache/calcite/test/RelOptRulesTest.java
index 18777b1ad7..d94ae46526 100644
--- a/core/src/test/java/org/apache/calcite/test/RelOptRulesTest.java
+++ b/core/src/test/java/org/apache/calcite/test/RelOptRulesTest.java
@@ -11699,6 +11699,19 @@ private void
checkLoptOptimizeJoinRule(LoptOptimizeJoinRule rule) {
.check();
}
+ /** Test case for
+ * <a
href="https://issues.apache.org/jira/browse/CALCITE-7643">[CALCITE-7643]
+ * AggregateMinMaxToLimitRule drops FILTER condition for filtered
+ * MIN/MAX aggregates</a>. */
+ @Test void testAggregateMinMaxToLimitRuleWithFilter() {
+ final String sql = "select min(v) filter (where p), max(v) filter (where
p)\n"
+ + "from (values (10, false), (100, true), (200, true),\n"
+ + " (300, false), (cast(null as integer), true)) as t(v, p)";
+ sql(sql)
+ .withRule(CoreRules.AGGREGATE_MIN_MAX_TO_LIMIT)
+ .check();
+ }
+
@Test void testOuterJoinForDphyp() {
HepProgram program = new HepProgramBuilder()
.addMatchOrder(HepMatchOrder.BOTTOM_UP)
diff --git
a/core/src/test/resources/org/apache/calcite/test/RelOptRulesTest.xml
b/core/src/test/resources/org/apache/calcite/test/RelOptRulesTest.xml
index 2eb42b693c..64061b8910 100644
--- a/core/src/test/resources/org/apache/calcite/test/RelOptRulesTest.xml
+++ b/core/src/test/resources/org/apache/calcite/test/RelOptRulesTest.xml
@@ -931,6 +931,35 @@ LogicalSort(sort0=[$0], dir0=[DESC], fetch=[1])
<![CDATA[select min(deptno), max(deptno) from emp]]>
</Resource>
</TestCase>
+ <TestCase name="testAggregateMinMaxToLimitRuleWithFilter">
+ <Resource name="sql">
+ <![CDATA[select min(v) filter (where p), max(v) filter (where p)
+from (values (10, false), (100, true), (200, true),
+ (300, false), (cast(null as integer), true)) as t(v, p)]]>
+ </Resource>
+ <Resource name="planBefore">
+ <![CDATA[
+LogicalAggregate(group=[{}], EXPR$0=[MIN($0) FILTER $1], EXPR$1=[MAX($0)
FILTER $1])
+ LogicalValues(tuples=[[{ 10, false }, { 100, true }, { 200, true }, { 300,
false }, { null, true }]])
+]]>
+ </Resource>
+ <Resource name="planAfter">
+ <![CDATA[
+LogicalProject($f0=[$SCALAR_QUERY({
+LogicalSort(sort0=[$0], dir0=[ASC], fetch=[1])
+ LogicalProject(V=[$0])
+ LogicalFilter(condition=[AND($1, IS NOT NULL($0))])
+ LogicalValues(tuples=[[{ 10, false }, { 100, true }, { 200, true }, {
300, false }, { null, true }]])
+})], $f1=[$SCALAR_QUERY({
+LogicalSort(sort0=[$0], dir0=[DESC], fetch=[1])
+ LogicalProject(V=[$0])
+ LogicalFilter(condition=[AND($1, IS NOT NULL($0))])
+ LogicalValues(tuples=[[{ 10, false }, { 100, true }, { 200, true }, {
300, false }, { null, true }]])
+})])
+ LogicalValues(tuples=[[{ 1 }]])
+]]>
+ </Resource>
+ </TestCase>
<TestCase name="testAggregateProjectMerge">
<Resource name="sql">
<![CDATA[select x, sum(z), y from (
diff --git a/core/src/test/resources/sql/planner.iq
b/core/src/test/resources/sql/planner.iq
index 141b31d714..0adbe5785b 100644
--- a/core/src/test/resources/sql/planner.iq
+++ b/core/src/test/resources/sql/planner.iq
@@ -146,6 +146,55 @@ EnumerableCalc(expr#0..2=[{inputs}], $f0=[$t1], $f1=[$t2])
EnumerableValues(tuples=[[{ 10 }, { 10 }, { 20 }, { 30 }, { 30 }, {
50 }, { 50 }, { 60 }, { null }]])
!plan
!set planner-rules original
+
+# [CALCITE-7643] AggregateMinMaxToLimitRule drops FILTER condition for
filtered MIN/MAX aggregates
+select min(v) filter (where p), max(v) filter (where p)
+from (values (10, false), (100, true), (200, true),
+ (300, false), (cast(null as integer), true)) as t(v, p);
++--------+--------+
+| EXPR$0 | EXPR$1 |
++--------+--------+
+| 100 | 200 |
++--------+--------+
+(1 row)
+
+!ok
+
+EnumerableAggregate(group=[{}], EXPR$0=[MIN($0) FILTER $1], EXPR$1=[MAX($0)
FILTER $1])
+ EnumerableValues(tuples=[[{ 10, false }, { 100, true }, { 200, true }, {
300, false }, { null, true }]])
+!plan
+
+!set planner-rules "
++AGGREGATE_MIN_MAX_TO_LIMIT,
+-EnumerableRules.ENUMERABLE_AGGREGATE_RULE,
++PROJECT_SUB_QUERY_TO_CORRELATE"
+select min(v) filter (where p), max(v) filter (where p)
+from (values (10, false), (100, true), (200, true),
+ (300, false), (cast(null as integer), true)) as t(v, p);
++--------+--------+
+| EXPR$0 | EXPR$1 |
++--------+--------+
+| 100 | 200 |
++--------+--------+
+(1 row)
+
+!ok
+
+EnumerableCalc(expr#0..2=[{inputs}], $f0=[$t1], $f1=[$t2])
+ EnumerableNestedLoopJoin(condition=[true], joinType=[left])
+ EnumerableNestedLoopJoin(condition=[true], joinType=[left])
+ EnumerableValues(tuples=[[{ 1 }]])
+ EnumerableCalc(expr#0..1=[{inputs}], V=[$t0])
+ EnumerableLimit(fetch=[1])
+ EnumerableCalc(expr#0..1=[{inputs}], expr#2=[IS NOT NULL($t0)],
expr#3=[AND($t1, $t2)], proj#0..1=[{exprs}], $condition=[$t3])
+ EnumerableValues(tuples=[[{ 10, false }, { 100, true }, { 200,
true }, { 300, false }, { null, true }]])
+ EnumerableCalc(expr#0..1=[{inputs}], V=[$t0])
+ EnumerableLimit(fetch=[1])
+ EnumerableSort(sort0=[$0], dir0=[DESC])
+ EnumerableCalc(expr#0..1=[{inputs}], expr#2=[IS NOT NULL($t0)],
expr#3=[AND($t1, $t2)], proj#0..1=[{exprs}], $condition=[$t3])
+ EnumerableValues(tuples=[[{ 10, false }, { 100, true }, { 200,
true }, { 300, false }, { null, true }]])
+!plan
+!set planner-rules original
!use blank
# Test case for [CALCITE-7501] Assertion error in alias expansion for LEFT
JOIN USING