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

Reply via email to