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 2bdc6af448 [CALCITE-7242] Implement a rule to eliminate LITERAL_AGG so 
that other databases can handle it
2bdc6af448 is described below

commit 2bdc6af448693c81da3e16208b1af2325fee1d2a
Author: Zhen Chen <[email protected]>
AuthorDate: Wed Jul 1 10:21:56 2026 +0800

    [CALCITE-7242] Implement a rule to eliminate LITERAL_AGG so that other 
databases can handle it
    
    Co-authored-by: Weihua Zhang <[email protected]>
---
 .../rel/rules/AggregateRemoveLiteralAggRule.java   | 156 +++++++++++++++++++++
 .../org/apache/calcite/rel/rules/CoreRules.java    |   5 +
 .../org/apache/calcite/test/InterpreterTest.java   |  36 +++++
 .../org/apache/calcite/test/RelOptRulesTest.java   |  86 ++++++++++++
 .../org/apache/calcite/test/RelOptRulesTest.xml    | 134 ++++++++++++++++++
 5 files changed, 417 insertions(+)

diff --git 
a/core/src/main/java/org/apache/calcite/rel/rules/AggregateRemoveLiteralAggRule.java
 
b/core/src/main/java/org/apache/calcite/rel/rules/AggregateRemoveLiteralAggRule.java
new file mode 100644
index 0000000000..6aedbb612c
--- /dev/null
+++ 
b/core/src/main/java/org/apache/calcite/rel/rules/AggregateRemoveLiteralAggRule.java
@@ -0,0 +1,156 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to you under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.calcite.rel.rules;
+
+import org.apache.calcite.plan.RelOptRuleCall;
+import org.apache.calcite.plan.RelRule;
+import org.apache.calcite.rel.RelCollations;
+import org.apache.calcite.rel.RelNode;
+import org.apache.calcite.rel.core.Aggregate;
+import org.apache.calcite.rel.core.AggregateCall;
+import org.apache.calcite.rel.logical.LogicalAggregate;
+import org.apache.calcite.rex.RexNode;
+import org.apache.calcite.sql.SqlKind;
+import org.apache.calcite.sql.fun.SqlStdOperatorTable;
+import org.apache.calcite.tools.RelBuilder;
+import org.apache.calcite.util.ImmutableBitSet;
+
+import org.immutables.value.Value;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Planner rule that removes {@code LITERAL_AGG} aggregate calls from an
+ * {@link Aggregate}.
+ *
+ * <p>{@code LITERAL_AGG} is an internal aggregate used by Calcite to mark
+ * whether a group exists, and many external databases cannot implement it. 
This
+ * rule keeps the grouping operation, removes the literal aggregate calls, and
+ * adds a {@code Project} that restores the original row type with the literal
+ * values.
+ *
+ * <p>For example,
+ *
+ * <pre>{@code
+ * LogicalAggregate(group=[{0}], i=[LITERAL_AGG(true)])
+ *   LogicalTableScan(table=[[EMP]])
+ * }</pre>
+ *
+ * <p>becomes
+ *
+ * <pre>{@code
+ * LogicalProject(DEPTNO=[$0], i=[true])
+ *   LogicalAggregate(group=[{0}])
+ *     LogicalTableScan(table=[[EMP]])
+ * }</pre>
+ */
[email protected]
+public class AggregateRemoveLiteralAggRule
+    extends RelRule<AggregateRemoveLiteralAggRule.Config>
+    implements TransformationRule {
+
+  /** Creates an AggregateRemoveLiteralAggRule. */
+  protected AggregateRemoveLiteralAggRule(Config config) {
+    super(config);
+  }
+
+  @Override public void onMatch(RelOptRuleCall call) {
+    final RelBuilder relBuilder = call.builder();
+    final Aggregate aggregate = call.rel(0);
+    final List<AggregateCall> aggCalls = aggregate.getAggCallList();
+    if (aggCalls.isEmpty()) {
+      return;
+    }
+
+    final boolean[] literalAggs = new boolean[aggCalls.size()];
+    int literalAggCount = 0;
+    for (int i = 0; i < aggCalls.size(); i++) {
+      if (aggCalls.get(i).getAggregation().getKind() == SqlKind.LITERAL_AGG) {
+        literalAggs[i] = true;
+        literalAggCount++;
+      }
+    }
+    if (literalAggCount == 0) {
+      return;
+    }
+
+    final List<AggregateCall> newAggCalls =
+        new ArrayList<>(aggCalls.size() - literalAggCount);
+    final int[] oldAggIndexToNewAggIndex = new int[aggCalls.size()];
+    int newAggPos = 0;
+    for (int i = 0; i < aggCalls.size(); i++) {
+      if (!literalAggs[i]) {
+        newAggCalls.add(aggCalls.get(i));
+        oldAggIndexToNewAggIndex[i] = newAggPos++;
+      }
+    }
+    if (newAggCalls.isEmpty() && aggregate.getGroupCount() == 0) {
+      newAggCalls.add(
+          AggregateCall.create(SqlStdOperatorTable.COUNT, false, false, false,
+              Collections.emptyList(), Collections.emptyList(), -1,
+              null, RelCollations.EMPTY,
+              aggregate.getGroupSets().contains(ImmutableBitSet.of()),
+              aggregate.getInput(), null, null));
+    }
+
+    final RelNode newAggregate =
+        aggregate.copy(aggregate.getTraitSet(), aggregate.getInput(),
+            aggregate.getGroupSet(), aggregate.getGroupSets(), newAggCalls);
+    relBuilder.push(newAggregate);
+
+    final int groupCount = aggregate.getGroupCount();
+    final int outputCount = aggregate.getRowType().getFieldCount();
+    final List<RexNode> projects = new ArrayList<>(outputCount);
+    for (int outPos = 0; outPos < outputCount; outPos++) {
+      if (outPos < groupCount) {
+        projects.add(relBuilder.field(outPos));
+      } else {
+        final int aggIndex = outPos - groupCount;
+        final AggregateCall aggCall = aggCalls.get(aggIndex);
+        if (literalAggs[aggIndex]) {
+          projects.add(aggCall.rexList.get(0));
+        } else {
+          projects.add(relBuilder.field(groupCount + 
oldAggIndexToNewAggIndex[aggIndex]));
+        }
+      }
+    }
+
+    relBuilder.project(projects, aggregate.getRowType().getFieldNames());
+    call.transformTo(relBuilder.build());
+  }
+
+  /** Rule configuration. */
+  @Value.Immutable
+  public interface Config extends RelRule.Config {
+    Config DEFAULT = ImmutableAggregateRemoveLiteralAggRule.Config.of()
+        .withOperandSupplier(b0 ->
+            b0.operand(LogicalAggregate.class).anyInputs());
+
+    @Override default AggregateRemoveLiteralAggRule toRule() {
+      return new AggregateRemoveLiteralAggRule(this);
+    }
+
+    /** Defines an operand tree for the given aggregate class. */
+    default Config withOperandFor(Class<? extends Aggregate> aggregateClass) {
+      return withOperandSupplier(b0 ->
+          b0.operand(aggregateClass).anyInputs())
+          .as(Config.class);
+    }
+  }
+}
diff --git a/core/src/main/java/org/apache/calcite/rel/rules/CoreRules.java 
b/core/src/main/java/org/apache/calcite/rel/rules/CoreRules.java
index bc21c41dd3..104e34bfae 100644
--- a/core/src/main/java/org/apache/calcite/rel/rules/CoreRules.java
+++ b/core/src/main/java/org/apache/calcite/rel/rules/CoreRules.java
@@ -990,6 +990,11 @@ private CoreRules() {}
   public static final AggregateGroupingSetsToUnionRule 
AGGREGATE_GROUPING_SETS_TO_UNION =
       AggregateGroupingSetsToUnionRule.Config.DEFAULT.toRule();
 
+  /** Rule that removes {@code LITERAL_AGG} aggregate calls by replacing them
+   * with literal expressions in a {@link Project}. */
+  public static final AggregateRemoveLiteralAggRule 
AGGREGATE_REMOVE_LITERAL_AGG =
+      AggregateRemoveLiteralAggRule.Config.DEFAULT.toRule();
+
   /** Rule that converts a {@link Correlate} after an {@link Uncollect} into a 
simple
    * Uncollect, if possible. */
   public static final RelOptRule UNNEST_DECORRELATE =
diff --git a/core/src/test/java/org/apache/calcite/test/InterpreterTest.java 
b/core/src/test/java/org/apache/calcite/test/InterpreterTest.java
index 7470e21636..c138056e72 100644
--- a/core/src/test/java/org/apache/calcite/test/InterpreterTest.java
+++ b/core/src/test/java/org/apache/calcite/test/InterpreterTest.java
@@ -371,6 +371,42 @@ private static void assertRows(Interpreter interpreter,
             "[1943, 1, true, -3]");
   }
 
+  /** Tests a GROUP BY query after replacing
+   * {@link org.apache.calcite.sql.fun.SqlInternalOperators#LITERAL_AGG}. */
+  @Test void testAggregateRemoveLiteralAggOnEmptyInput() {
+    rootSchema().add("beatles", new ScannableTableTest.BeatlesTable());
+    final Function<RelBuilder, RelNode> relFn =
+        b -> applyAggregateRemoveLiteralAggRule(b.scan("beatles")
+            .empty()
+            .aggregate(b.groupKey("k"),
+                b.literalAgg(true).as("t"))
+            .build());
+    fixture().withRel(relFn)
+        .returnsRows();
+  }
+
+  /** Tests a GROUP BY () query after replacing
+   * {@link org.apache.calcite.sql.fun.SqlInternalOperators#LITERAL_AGG}. */
+  @Test void testAggregateRemoveLiteralAggWithOnlyLiteralAgg() {
+    rootSchema().add("beatles", new ScannableTableTest.BeatlesTable());
+    final Function<RelBuilder, RelNode> relFn =
+        b -> applyAggregateRemoveLiteralAggRule(b.scan("beatles")
+            .aggregate(b.groupKey(),
+                b.literalAgg(true).as("t"))
+            .build());
+    fixture().withRel(relFn)
+        .returnsRows("[true]");
+  }
+
+  private static RelNode applyAggregateRemoveLiteralAggRule(RelNode rel) {
+    final HepProgram program = HepProgram.builder()
+        .addRuleInstance(CoreRules.AGGREGATE_REMOVE_LITERAL_AGG)
+        .build();
+    final HepPlanner hep = new HepPlanner(program);
+    hep.setRoot(rel);
+    return hep.findBestExp();
+  }
+
   /** Tests executing a plan on a single-column
    * {@link org.apache.calcite.schema.ScannableTable} using an interpreter. */
   @Test void testInterpretSimpleScannableTable() {
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 72d94a5a08..0dee9a6b74 100644
--- a/core/src/test/java/org/apache/calcite/test/RelOptRulesTest.java
+++ b/core/src/test/java/org/apache/calcite/test/RelOptRulesTest.java
@@ -12003,6 +12003,92 @@ private void 
checkLoptOptimizeJoinRule(LoptOptimizeJoinRule rule) {
         .check();
   }
 
+  /** Test case of
+   * <a 
href="https://issues.apache.org/jira/browse/CALCITE-7242";>[CALCITE-7242]
+   * Implement a rule to eliminate LITERAL_AGG so that other databases can 
handle it</a>. */
+  @Test void testAggregateRemoveLiteralAggRuleWithAnySubQuery() {
+    final String sql = "select deptno, name = ANY (\n"
+        + "  select mgr from emp)\n"
+        + "from dept";
+    sql(sql)
+        .withSubQueryRules()
+        .withLateDecorrelate(true)
+        .withAfter((fixture, rel) -> applyAggregateRemoveLiteralAggRule(rel))
+        .check();
+  }
+
+  /** Test case of
+   * <a 
href="https://issues.apache.org/jira/browse/CALCITE-7242";>[CALCITE-7242]
+   * Implement a rule to eliminate LITERAL_AGG so that other databases can 
handle it</a>. */
+  @Test void testAggregateRemoveLiteralAggRuleWithInSubQuery() {
+    final String sql = "select empno\n"
+        + "from sales.emp\n"
+        + "where deptno in (select deptno from sales.emp where empno < 20)\n"
+        + "or emp.sal < 100";
+    sql(sql)
+        .withSubQueryRules()
+        .withLateDecorrelate(true)
+        .withAfter((fixture, rel) -> applyAggregateRemoveLiteralAggRule(rel))
+        .check();
+  }
+
+  /** Test case of
+   * <a 
href="https://issues.apache.org/jira/browse/CALCITE-7242";>[CALCITE-7242]
+   * Implement a rule to eliminate LITERAL_AGG so that other databases can 
handle it</a>. */
+  @Test void testAggregateRemoveLiteralAggRuleWithRegularAggCall() {
+    final Function<RelBuilder, RelNode> relFn = b -> b
+        .scan("EMP")
+        .aggregate(b.groupKey("DEPTNO"),
+            b.count().as("c"),
+            b.literalAgg(true).as("i"))
+        .build();
+    relFn(relFn)
+        .withRule(CoreRules.AGGREGATE_REMOVE_LITERAL_AGG)
+        .check();
+  }
+
+  /** Test case of
+   * <a 
href="https://issues.apache.org/jira/browse/CALCITE-7242";>[CALCITE-7242]
+   * Implement a rule to eliminate LITERAL_AGG so that other databases can 
handle it</a>. */
+  @Test void testAggregateRemoveLiteralAggRuleWithEmptyInput() {
+    final Function<RelBuilder, RelNode> relFn = b -> {
+      final RelBuilder builder =
+          RelBuilderTest.createBuilder(c -> c.withAggregateUnique(true));
+      return builder
+          .scan("EMP")
+          .empty()
+          .aggregate(builder.groupKey("DEPTNO"),
+              builder.literalAgg(true).as("i"))
+          .build();
+    };
+    relFn(relFn)
+        .withRule(CoreRules.AGGREGATE_REMOVE_LITERAL_AGG)
+        .check();
+  }
+
+  /** Test case of
+   * <a 
href="https://issues.apache.org/jira/browse/CALCITE-7242";>[CALCITE-7242]
+   * Implement a rule to eliminate LITERAL_AGG so that other databases can 
handle it</a>. */
+  @Test void testAggregateRemoveLiteralAggRuleWithOnlyLiteralAgg() {
+    final Function<RelBuilder, RelNode> relFn = b -> b
+        .scan("EMP")
+        .aggregate(b.groupKey(),
+            b.literalAgg(true).as("i"))
+        .build();
+    relFn(relFn)
+        .withRule(CoreRules.AGGREGATE_REMOVE_LITERAL_AGG)
+        .check();
+  }
+
+  private static RelNode applyAggregateRemoveLiteralAggRule(RelNode rel) {
+    final HepProgram program = HepProgram.builder()
+        .addRuleInstance(CoreRules.AGGREGATE_REMOVE_LITERAL_AGG)
+        .build();
+    final HepPlanner hep = new HepPlanner(program);
+    hep.setRoot(rel);
+    return hep.findBestExp();
+  }
+
   /** Test case of
    * <a 
href="https://issues.apache.org/jira/browse/CALCITE-7178";>[CALCITE-7178]
    * FETCH and OFFSET in EnumerableMergeUnionRule do not support BIGINT</a>. */
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 4eae45cc8a..4cb1da4092 100644
--- a/core/src/test/resources/org/apache/calcite/test/RelOptRulesTest.xml
+++ b/core/src/test/resources/org/apache/calcite/test/RelOptRulesTest.xml
@@ -1130,6 +1130,140 @@ LogicalProject(MGR=[$0], SUM_SAL=[$2])
     LogicalAggregate(group=[{0, 1}], SUM_SAL=[SUM($2)])
       LogicalProject(MGR=[$3], DEPTNO=[$7], SAL=[$5])
         LogicalTableScan(table=[[CATALOG, SALES, EMP]])
+]]>
+    </Resource>
+  </TestCase>
+  <TestCase name="testAggregateRemoveLiteralAggRuleWithAnySubQuery">
+    <Resource name="sql">
+      <![CDATA[select deptno, name = ANY (
+  select mgr from emp)
+from dept]]>
+    </Resource>
+    <Resource name="planBefore">
+      <![CDATA[
+LogicalProject(DEPTNO=[$0], EXPR$1=[IN(CAST($1):INTEGER NOT NULL, {
+LogicalProject(MGR=[$3])
+  LogicalTableScan(table=[[CATALOG, SALES, EMP]])
+})])
+  LogicalTableScan(table=[[CATALOG, SALES, DEPT]])
+]]>
+    </Resource>
+    <Resource name="planMid">
+      <![CDATA[
+LogicalProject(DEPTNO=[$0], EXPR$1=[OR(AND(IS NOT NULL($5), <>($2, 0)), 
AND(<($3, $2), null, <>($2, 0), IS NULL($5)))])
+  LogicalJoin(condition=[=(CAST($1):INTEGER NOT NULL, $4)], joinType=[left])
+    LogicalJoin(condition=[true], joinType=[inner])
+      LogicalTableScan(table=[[CATALOG, SALES, DEPT]])
+      LogicalAggregate(group=[{}], c=[COUNT()], ck=[COUNT($0)])
+        LogicalProject(MGR=[$3])
+          LogicalTableScan(table=[[CATALOG, SALES, EMP]])
+    LogicalAggregate(group=[{0}], i=[LITERAL_AGG(true)])
+      LogicalProject(MGR=[$3])
+        LogicalTableScan(table=[[CATALOG, SALES, EMP]])
+]]>
+    </Resource>
+    <Resource name="planAfter">
+      <![CDATA[
+LogicalProject(DEPTNO=[$0], EXPR$1=[OR(AND(IS NOT NULL($5), <>($2, 0)), 
AND(<($3, $2), null, <>($2, 0), IS NULL($5)))])
+  LogicalJoin(condition=[=(CAST($1):INTEGER NOT NULL, $4)], joinType=[left])
+    LogicalJoin(condition=[true], joinType=[inner])
+      LogicalTableScan(table=[[CATALOG, SALES, DEPT]])
+      LogicalAggregate(group=[{}], c=[COUNT()], ck=[COUNT($0)])
+        LogicalProject(MGR=[$3])
+          LogicalTableScan(table=[[CATALOG, SALES, EMP]])
+    LogicalProject(MGR=[$0], i=[true])
+      LogicalAggregate(group=[{0}])
+        LogicalProject(MGR=[$3])
+          LogicalTableScan(table=[[CATALOG, SALES, EMP]])
+]]>
+    </Resource>
+  </TestCase>
+  <TestCase name="testAggregateRemoveLiteralAggRuleWithEmptyInput">
+    <Resource name="planBefore">
+      <![CDATA[
+LogicalAggregate(group=[{7}], i=[LITERAL_AGG(true)])
+  LogicalValues(tuples=[[]])
+]]>
+    </Resource>
+    <Resource name="planAfter">
+      <![CDATA[
+LogicalProject(DEPTNO=[$0], i=[true])
+  LogicalAggregate(group=[{7}])
+    LogicalValues(tuples=[[]])
+]]>
+    </Resource>
+  </TestCase>
+  <TestCase name="testAggregateRemoveLiteralAggRuleWithInSubQuery">
+    <Resource name="sql">
+      <![CDATA[select empno
+from sales.emp
+where deptno in (select deptno from sales.emp where empno < 20)
+or emp.sal < 100]]>
+    </Resource>
+    <Resource name="planBefore">
+      <![CDATA[
+LogicalProject(EMPNO=[$0])
+  LogicalFilter(condition=[OR(IN($7, {
+LogicalProject(DEPTNO=[$7])
+  LogicalFilter(condition=[<($0, 20)])
+    LogicalTableScan(table=[[CATALOG, SALES, EMP]])
+}), <($5, 100))])
+    LogicalTableScan(table=[[CATALOG, SALES, EMP]])
+]]>
+    </Resource>
+    <Resource name="planMid">
+      <![CDATA[
+LogicalProject(EMPNO=[$0])
+  LogicalProject(EMPNO=[$0], ENAME=[$1], JOB=[$2], MGR=[$3], HIREDATE=[$4], 
SAL=[$5], COMM=[$6], DEPTNO=[$7], SLACKER=[$8])
+    LogicalFilter(condition=[OR(IS NOT NULL($10), <($5, 100))])
+      LogicalJoin(condition=[=($7, $9)], joinType=[left])
+        LogicalTableScan(table=[[CATALOG, SALES, EMP]])
+        LogicalAggregate(group=[{0}], i=[LITERAL_AGG(true)])
+          LogicalProject(DEPTNO=[$7])
+            LogicalFilter(condition=[<($0, 20)])
+              LogicalTableScan(table=[[CATALOG, SALES, EMP]])
+]]>
+    </Resource>
+    <Resource name="planAfter">
+      <![CDATA[
+LogicalProject(EMPNO=[$0])
+  LogicalProject(EMPNO=[$0], ENAME=[$1], JOB=[$2], MGR=[$3], HIREDATE=[$4], 
SAL=[$5], COMM=[$6], DEPTNO=[$7], SLACKER=[$8])
+    LogicalFilter(condition=[OR(IS NOT NULL($10), <($5, 100))])
+      LogicalJoin(condition=[=($7, $9)], joinType=[left])
+        LogicalTableScan(table=[[CATALOG, SALES, EMP]])
+        LogicalProject(DEPTNO=[$0], i=[true])
+          LogicalAggregate(group=[{0}])
+            LogicalProject(DEPTNO=[$7])
+              LogicalFilter(condition=[<($0, 20)])
+                LogicalTableScan(table=[[CATALOG, SALES, EMP]])
+]]>
+    </Resource>
+  </TestCase>
+  <TestCase name="testAggregateRemoveLiteralAggRuleWithOnlyLiteralAgg">
+    <Resource name="planBefore">
+      <![CDATA[
+LogicalAggregate(group=[{}], i=[LITERAL_AGG(true)])
+  LogicalTableScan(table=[[scott, EMP]])
+]]>
+    </Resource>
+    <Resource name="planAfter">
+      <![CDATA[
+LogicalValues(tuples=[[{ true }]])
+]]>
+    </Resource>
+  </TestCase>
+  <TestCase name="testAggregateRemoveLiteralAggRuleWithRegularAggCall">
+    <Resource name="planBefore">
+      <![CDATA[
+LogicalAggregate(group=[{7}], c=[COUNT()], i=[LITERAL_AGG(true)])
+  LogicalTableScan(table=[[scott, EMP]])
+]]>
+    </Resource>
+    <Resource name="planAfter">
+      <![CDATA[
+LogicalProject(DEPTNO=[$0], c=[$1], i=[true])
+  LogicalAggregate(group=[{7}], c=[COUNT()])
+    LogicalTableScan(table=[[scott, EMP]])
 ]]>
     </Resource>
   </TestCase>

Reply via email to