This is an automated email from the ASF dual-hosted git repository.

mihaibudiu 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 4062e2b1c0 [CALCITE-7793] RelMdDistinctRowCount passes an Aggregate 
predicate to the Aggregate's input without translating column references
4062e2b1c0 is described below

commit 4062e2b1c05088488dc98c749a1ae4951f14f355
Author: Mihai Budiu <[email protected]>
AuthorDate: Thu Sep 17 14:43:50 2026 -0700

    [CALCITE-7793] RelMdDistinctRowCount passes an Aggregate predicate to the 
Aggregate's input without translating column references
    
    Signed-off-by: Mihai Budiu <[email protected]>
---
 .../rel/metadata/RelMdDistinctRowCount.java        | 15 +++++
 .../org/apache/calcite/test/RelMetadataTest.java   | 70 ++++++++++++++++++++++
 core/src/test/resources/sql/sub-query.iq           | 31 ++++++++++
 3 files changed, 116 insertions(+)

diff --git 
a/core/src/main/java/org/apache/calcite/rel/metadata/RelMdDistinctRowCount.java 
b/core/src/main/java/org/apache/calcite/rel/metadata/RelMdDistinctRowCount.java
index 7058dcbb4d..f70fa5dfef 100644
--- 
a/core/src/main/java/org/apache/calcite/rel/metadata/RelMdDistinctRowCount.java
+++ 
b/core/src/main/java/org/apache/calcite/rel/metadata/RelMdDistinctRowCount.java
@@ -191,6 +191,21 @@ protected RelMdDistinctRowCount() {}
     RexNode childPreds =
         RexUtil.composeConjunction(rexBuilder, pushable, true);
 
+    if (childPreds != null) {
+      // Convert aggregate-output references to the corresponding input fields.
+      final int[] adjustments = new int[rel.getGroupCount()];
+      int j = 0;
+      for (int i : rel.getGroupSet()) {
+        adjustments[j] = i - j;
+        j++;
+      }
+      childPreds =
+          childPreds.accept(
+              new RelOptUtil.RexInputConverter(rexBuilder,
+              rel.getRowType().getFieldList(),
+              rel.getInput().getRowType().getFieldList(), adjustments));
+    }
+
     // set the bits as they correspond to the child input
     ImmutableBitSet.Builder childKey = ImmutableBitSet.builder();
     RelMdUtil.setAggChildKeys(groupKey, rel, childKey);
diff --git a/core/src/test/java/org/apache/calcite/test/RelMetadataTest.java 
b/core/src/test/java/org/apache/calcite/test/RelMetadataTest.java
index a1780aa319..b6625ab0b3 100644
--- a/core/src/test/java/org/apache/calcite/test/RelMetadataTest.java
+++ b/core/src/test/java/org/apache/calcite/test/RelMetadataTest.java
@@ -5414,6 +5414,76 @@ static <K, V> List<String> toSortedStringList(Map<K, V> 
map) {
         .assertThatDistinctRowCount(bitSetOf(0), is(1d));
   }
 
+  /** Test case for
+   * <a 
href="https://issues.apache.org/jira/browse/CALCITE-7793";>[CALCITE-7793]
+   * RelMdDistinctRowCount passes an Aggregate predicate to the Aggregate's
+   * input without translating column references</a>. */
+  @Test void testAggregateDistinctRowCountPredicateIsTranslated() {
+    // select id, sum(sal) from t where id > 1 group by id having id > 1
+    //
+    // LogicalFilter(condition=[>($0, 1)])        <- Aggregate output ordinals;
+    //   LogicalAggregate(group=[{1}], sumsal=[SUM($0)])
+    //     LogicalFilter(condition=[>($1, 1)])    <- Aggregate input ordinals;
+    //       LogicalValues(tuples=[[{ 10, 1 }, { 20, 2 }, { 30, 3 }]])
+    // The HAVING has the same condition as the WHERE:
+    // Thus the Aggregate's output $0 is the same as its input $1:
+    // the two conditions restrict the key column in the same way.
+    //
+    // The plan is built by hand because SqlToRel produces a different plan.
+    final RelNode query = fixture()
+        .withRelFn(b -> {
+          b.values(new String[]{"sal", "id"}, 10, 1, 20, 2, 30, 3);
+          b.filter(b.greaterThan(b.field(1), b.literal(1)));
+          b.aggregate(b.groupKey(1), b.sum(false, "sumsal", b.field(0)));
+          return b.filter(b.greaterThan(b.field(0), b.literal(1))).build();
+        })
+        .toRel();
+
+    // The two plans compared below differ only by the HAVING.
+    final RelNode aggregate = query.getInput(0);
+    assertThat(aggregate, instanceOf(Aggregate.class));
+
+    // A HAVING that restricts nothing new does not change the estimated group 
count.
+    final RelMetadataQuery mq = query.getCluster().getMetadataQuery();
+    assertThat(mq.getDistinctRowCount(query, bitSetOf(0), null),
+        is(mq.getDistinctRowCount(aggregate, bitSetOf(0), null)));
+  }
+
+  /** Test case for
+   * <a 
href="https://issues.apache.org/jira/browse/CALCITE-7793";>[CALCITE-7793]
+   * RelMdDistinctRowCount passes an Aggregate predicate to the Aggregate's
+   * input without translating column references</a>. */
+  @Test void testAggregateDistinctRowCountStructPredicate() {
+    // select r, sum(sal) from
+    //   (select sal, cast(row(id) as row(d integer)) as r from t)
+    // group by r
+    // having (r).d = 2
+    //
+    // LogicalFilter(condition=[=($0.d, 2)])       <- Aggregate output 
ordinals;
+    //   LogicalAggregate(group=[{1}], sumsal=[SUM($0)])
+    //     LogicalProject(sal=[$0], r=[ROW($1)])   <- Aggregate input ordinals;
+    //       LogicalValues(tuples=[[{ 10, 1 }, { 20, 2 }, { 30, 3 }]])
+    //
+    // The plan is built by hand because SqlToRel produces a different plan.
+    fixture()
+        .withRelFn(b -> {
+          b.values(new String[]{"sal", "id"}, 10, 1, 20, 2, 30, 3);
+          b.project(
+              ImmutableList.of(b.field(0),
+                  b.getRexBuilder().makeCall(
+                      b.getTypeFactory().builder()
+                          .add("d", b.field(1).getType()).build(),
+                      SqlStdOperatorTable.ROW, ImmutableList.of(b.field(1)))),
+              ImmutableList.of("sal", "r"));
+          b.aggregate(b.groupKey(1), b.sum(false, "sumsal", b.field(0)));
+          return b.filter(
+              b.equals(b.getRexBuilder().makeFieldAccess(b.field(0), 0),
+                  b.literal(2))).build();
+        })
+        // Without the fix the next statement throws.
+        .assertThatDistinctRowCount(bitSetOf(0), notNullValue(Double.class));
+  }
+
   @Test void testAggregateDistinctRowCountLosslessCast() {
     final String sql =
         "select name, sal, cast(sal as varchar(11)) "
diff --git a/core/src/test/resources/sql/sub-query.iq 
b/core/src/test/resources/sql/sub-query.iq
index 4bc3fae31a..4a71d6a388 100644
--- a/core/src/test/resources/sql/sub-query.iq
+++ b/core/src/test/resources/sql/sub-query.iq
@@ -10200,3 +10200,34 @@ from (select array(select ROW(ROW(1, 2), 3) from 
(values (0))) as a) as t;
 (1 row)
 
 !ok
+
+!set trimfields false
+
+# [CALCITE-7793] RelMdDistinctRowCount passes an Aggregate predicate to the
+# Aggregate's input without translating column references.
+# PostgreSQL 14.13 returns the same empty result for this query.
+with t(n, r) as (
+  values (1, cast(row(10) as row(d integer))),
+         (2, cast(row(20) as row(d integer)))
+)
+select e.n
+from t e
+where e.n > (
+  select avg(e2.n)
+  from t e2
+  where (e2.r).d = (e.r).d
+  and exists (
+     select 1
+     from (values (1, cast(row(10) as row(d integer)))) as b(k, br)
+     where b.k = e2.n
+     and (b.br).d = (e.r).d
+  )
+);
++---+
+| N |
++---+
++---+
+(0 rows)
+
+!ok
+!set trimfields true

Reply via email to