This is an automated email from the ASF dual-hosted git repository.
xuzifu666 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 068c03dfcc [CALCITE-7492] Support expression that has a constant value
within the group involving only GROUP BY keys as aggregate arguments
068c03dfcc is described below
commit 068c03dfcc37798acfe60a369149888a97f1ba8c
Author: Yu Xu <[email protected]>
AuthorDate: Wed Apr 29 17:25:31 2026 +0800
[CALCITE-7492] Support expression that has a constant value within the
group involving only GROUP BY keys as aggregate arguments
---
.../AggregateReduceFunctionsOnGroupKeysRule.java | 144 ++++++++++--
...ggregateReduceFunctionsOnGroupKeysRuleTest.java | 88 ++++++++
...AggregateReduceFunctionsOnGroupKeysRuleTest.xml | 248 ++++++++++++++++++++-
3 files changed, 462 insertions(+), 18 deletions(-)
diff --git
a/core/src/main/java/org/apache/calcite/rel/rules/AggregateReduceFunctionsOnGroupKeysRule.java
b/core/src/main/java/org/apache/calcite/rel/rules/AggregateReduceFunctionsOnGroupKeysRule.java
index 10d30d620e..987a25fea2 100644
---
a/core/src/main/java/org/apache/calcite/rel/rules/AggregateReduceFunctionsOnGroupKeysRule.java
+++
b/core/src/main/java/org/apache/calcite/rel/rules/AggregateReduceFunctionsOnGroupKeysRule.java
@@ -18,15 +18,19 @@
import org.apache.calcite.plan.RelOptRuleCall;
import org.apache.calcite.plan.RelRule;
+import org.apache.calcite.plan.hep.HepRelVertex;
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.core.Project;
import org.apache.calcite.rel.core.RelFactories;
import org.apache.calcite.rel.logical.LogicalAggregate;
import org.apache.calcite.rex.RexBuilder;
import org.apache.calcite.rex.RexInputRef;
import org.apache.calcite.rex.RexNode;
+import org.apache.calcite.rex.RexShuttle;
+import org.apache.calcite.rex.RexUtil;
import org.apache.calcite.sql.SqlKind;
import org.apache.calcite.tools.RelBuilder;
@@ -45,7 +49,8 @@
* {@code SELECT sal, sal FROM emp GROUP BY sal}.
*
* <p>Currently supports the following aggregate functions when their
- * arguments exist in the aggregate's group set:
+ * arguments exist in the aggregate's group set or are deterministic
+ * expressions involving only group set columns and constants:
* <ul>
* <li>{@code MAX}</li>
* <li>{@code MIN}</li>
@@ -53,6 +58,12 @@
* <li>{@code ANY_VALUE}</li>
* </ul>
*
+ * <p>Note: This optimization preserves NULL semantics correctly. For aggregate
+ * functions like MAX, MIN, and ANY_VALUE, NULL values in the source columns or
+ * expressions are handled the same way before and after the transformation:
+ * nulls are ignored by the aggregation, and if all grouped values are NULL,
+ * the result is NULL.
+ *
* @see CoreRules#AGGREGATE_REDUCE_FUNCTIONS_ON_GROUP_KEYS
*/
@Value.Enclosing
@@ -74,6 +85,8 @@ protected AggregateReduceFunctionsOnGroupKeysRule(Config
config) {
final List<AggregateCall> newCalls = new ArrayList<>();
final List<RexNode> projects = new ArrayList<>();
+ final List<String> fieldNames =
+ new ArrayList<>(aggregate.getRowType().getFieldNames());
// Pass through group keys.
for (int i = 0; i < groupCount; i++) {
@@ -108,12 +121,13 @@ protected AggregateReduceFunctionsOnGroupKeysRule(Config
config) {
aggregate.getGroupSets(),
newCalls);
relBuilder.push(newAggregate);
- relBuilder.project(projects);
+ relBuilder.project(projects, fieldNames);
call.transformTo(relBuilder.build());
}
/**
- * Tries to reduce an aggregate call to a reference to a group-by key.
+ * Tries to reduce an aggregate call to a reference to a group-by key
+ * or to an expression involving only group-by keys and constants.
*
* @return the reduced expression, or null if cannot reduce
*/
@@ -129,14 +143,6 @@ protected AggregateReduceFunctionsOnGroupKeysRule(Config
config) {
|| call.collation != RelCollations.EMPTY) {
return null;
}
- final List<Integer> argList = call.getArgList();
- if (argList.size() != 1) {
- return null;
- }
- final int arg = argList.get(0);
- if (!aggregate.getGroupSet().get(arg)) {
- return null;
- }
final SqlKind kind = call.getAggregation().getKind();
switch (kind) {
case AVG:
@@ -147,12 +153,118 @@ protected AggregateReduceFunctionsOnGroupKeysRule(Config
config) {
default:
return null;
}
- final int groupIndex = aggregate.getGroupSet().asList().indexOf(arg);
- RexNode ref = RexInputRef.of(groupIndex,
aggregate.getRowType().getFieldList());
- if (!ref.getType().equals(call.getType())) {
- ref = rexBuilder.makeCast(call.getParserPosition(), call.getType(), ref);
+ final List<Integer> argList = call.getArgList();
+ if (argList.size() != 1) {
+ return null;
+ }
+ final int arg = argList.get(0);
+
+ // Case 1: argument directly references a group-by key
+ if (aggregate.getGroupSet().get(arg)) {
+ final int groupIndex = aggregate.getGroupSet().asList().indexOf(arg);
+ RexNode ref = RexInputRef.of(groupIndex,
aggregate.getRowType().getFieldList());
+ if (!ref.getType().equals(call.getType())) {
+ ref = rexBuilder.makeCast(call.getParserPosition(), call.getType(),
ref);
+ }
+ return ref;
+ }
+
+ // Case 2: argument is an expression in a Project below the Aggregate
+ RelNode input = aggregate.getInput();
+ if (input instanceof HepRelVertex) {
+ input = ((HepRelVertex) input).getCurrentRel();
+ }
+ if (!(input instanceof Project)) {
+ return null;
+ }
+ final Project project = (Project) input;
+ if (arg < 0 || arg >= project.getProjects().size()) {
+ return null;
+ }
+ final RexNode expr = project.getProjects().get(arg);
+ if (!RexUtil.isDeterministic(expr)) {
+ return null;
+ }
+ // Check that all columns referenced in the expression are group-by keys.
+ // This ensures that the expression value is constant within each group.
+ final @Nullable RexNode translated =
+ translateToGroupRefs(expr, project, aggregate);
+ if (translated == null) {
+ return null;
+ }
+ if (!translated.getType().equals(call.getType())) {
+ return rexBuilder.makeCast(call.getParserPosition(), call.getType(),
translated);
+ }
+ return translated;
+ }
+
+ /**
+ * Translates an expression so that its {@link RexInputRef}s reference
+ * the group keys of the aggregate rather than the input to the project.
+ *
+ * @return the translated expression, or null if the expression references
+ * columns that are not group-by keys
+ */
+ private static @Nullable RexNode translateToGroupRefs(
+ RexNode expr, Project project, Aggregate aggregate) {
+ final List<RexNode> projects = project.getProjects();
+ final GroupRefTranslator translator = new GroupRefTranslator(projects,
aggregate);
+ final RexNode result = expr.accept(translator);
+ return translator.failed ? null : result;
+ }
+
+ /**
+ * Shuttle that translates input refs to aggregate group key refs.
+ *
+ * <p>For each column reference in the expression being examined:
+ * 1. If the expression is a direct pass-through of a project column,
+ * check if that project column is in the GROUP BY set
+ * 2. If the expression contains references to input columns,
+ * verify that those input columns are in the GROUP BY set
+ * 3. Map to the corresponding group key index in the aggregate
+ *
+ * <p>This ensures the expression references only columns that are constant
+ * within each group.
+ */
+ private static class GroupRefTranslator extends RexShuttle {
+ private final List<RexNode> projects;
+ private final Aggregate aggregate;
+ private boolean failed = false;
+
+ GroupRefTranslator(List<RexNode> projects, Aggregate aggregate) {
+ this.projects = projects;
+ this.aggregate = aggregate;
+ }
+
+ @Override public RexNode visitInputRef(RexInputRef inputRef) {
+ if (failed) {
+ return inputRef;
+ }
+ final int inputIndex = inputRef.getIndex();
+ // Look for a project column that is a direct pass-through of this input.
+ // For example, if a project has SAL=[$5], and the expression references
$5,
+ // we need to map it to the corresponding group key.
+ int projectOutputIndex = -1;
+ for (int i = 0; i < projects.size(); i++) {
+ final RexNode projExpr = projects.get(i);
+ if (projExpr instanceof RexInputRef
+ && ((RexInputRef) projExpr).getIndex() == inputIndex) {
+ projectOutputIndex = i;
+ break;
+ }
+ }
+ // The input column must be available through a project column that is in
+ // the GROUP BY set. If not found, the input is embedded in a computed
+ // expression, which means the optimization cannot proceed safely.
+ if (projectOutputIndex < 0
+ || !aggregate.getGroupSet().get(projectOutputIndex)) {
+ failed = true;
+ return inputRef;
+ }
+ final int groupIndex =
+ aggregate.getGroupSet().asList().indexOf(projectOutputIndex);
+ return RexInputRef.of(groupIndex, aggregate.getRowType().getFieldList());
}
- return ref;
}
/** Rule configuration. */
diff --git
a/core/src/test/java/org/apache/calcite/test/AggregateReduceFunctionsOnGroupKeysRuleTest.java
b/core/src/test/java/org/apache/calcite/test/AggregateReduceFunctionsOnGroupKeysRuleTest.java
index 739ce1cb0b..d4de2092aa 100644
---
a/core/src/test/java/org/apache/calcite/test/AggregateReduceFunctionsOnGroupKeysRuleTest.java
+++
b/core/src/test/java/org/apache/calcite/test/AggregateReduceFunctionsOnGroupKeysRuleTest.java
@@ -55,12 +55,100 @@ private static RelOptFixture sql(String sql) {
sql(sql).withRule(AGGREGATE_REDUCE_FUNCTIONS_ON_GROUP_KEYS).check();
}
+ @Test void testAggregateFunctionOfGroupByKeysNullExpression() {
+ String sql = "select comm, max(comm + 1) as max_plus\n"
+ + "from empnullables group by comm";
+ sql(sql).withRule(AGGREGATE_REDUCE_FUNCTIONS_ON_GROUP_KEYS).check();
+ }
+
+ @Test void testAggregateFunctionOfGroupByKeysNullGroupKey() {
+ String sql = "select comm, max(comm) as comm_max\n"
+ + "from empnullables group by comm";
+ sql(sql).withRule(AGGREGATE_REDUCE_FUNCTIONS_ON_GROUP_KEYS).check();
+ }
+
@Test void testAggregateFunctionOfGroupByKeysNoChange() {
String sql = "select sal, max(comm) as comm_max\n"
+ "from emp group by sal, deptno";
sql(sql).withRule(AGGREGATE_REDUCE_FUNCTIONS_ON_GROUP_KEYS).checkUnchanged();
}
+ @Test void testAggregateFunctionOfGroupByKeysDeterministicExpression() {
+ String sql = "select sal, max(sal + 1) as max_plus\n"
+ + "from emp group by sal, deptno";
+ sql(sql).withRule(AGGREGATE_REDUCE_FUNCTIONS_ON_GROUP_KEYS).check();
+ }
+
+ @Test void testAggregateFunctionOfGroupByKeysUnaryMinus() {
+ String sql = "select sal, max(-sal) as max_neg\n"
+ + "from emp group by sal, deptno";
+ sql(sql).withRule(AGGREGATE_REDUCE_FUNCTIONS_ON_GROUP_KEYS).check();
+ }
+
+ @Test void testAggregateFunctionOfGroupByKeysBinaryExpression() {
+ String sql = "select sal, max(sal * 2) as max_double\n"
+ + "from emp group by sal, deptno";
+ sql(sql).withRule(AGGREGATE_REDUCE_FUNCTIONS_ON_GROUP_KEYS).check();
+ }
+
+ @Test void testAggregateFunctionOfGroupByKeysMultipleGroupKeys() {
+ String sql = "select sal, max(sal + deptno) as max_sum\n"
+ + "from emp group by sal, deptno";
+ sql(sql).withRule(AGGREGATE_REDUCE_FUNCTIONS_ON_GROUP_KEYS).check();
+ }
+
+ @Test void testAggregateFunctionOfGroupByKeysNestedExpression() {
+ // Nested expressions like (sal + 1) * 2 can be optimized by mapping the
+ // input references (sal) to group key references. The shuttle translates
+ // all input refs in the expression to their corresponding group keys.
+ String sql = "select sal, max((sal + 1) * 2) as max_expr\n"
+ + "from emp group by sal, deptno";
+ sql(sql).withRule(AGGREGATE_REDUCE_FUNCTIONS_ON_GROUP_KEYS).check();
+ }
+
+ @Test void testAggregateFunctionOfGroupByKeysWithCastWider() {
+ // Test case where a cast is needed because the group key type differs
+ // from the aggregate result type. Cast to a wider type (BIGINT) is safe.
+ // The rule should preserve the cast.
+ String sql = "select cast(sal as bigint) as sal_big, max(sal) as sal_max\n"
+ + "from emp group by sal";
+ sql(sql).withRule(AGGREGATE_REDUCE_FUNCTIONS_ON_GROUP_KEYS).check();
+ }
+
+ @Test void testAggregateFunctionOfGroupByKeysWithCastNarrower() {
+ // Test case where a cast is needed and the type is narrower than the
source.
+ // Casting to SMALLINT could potentially lose information if sal has
larger values,
+ // but this is the user's explicit choice. The rule should still optimize
and
+ // preserve the cast, allowing SQL semantics to handle any data loss.
+ String sql = "select cast(sal as smallint) as sal_small, max(sal) as
sal_max\n"
+ + "from emp group by sal";
+ sql(sql).withRule(AGGREGATE_REDUCE_FUNCTIONS_ON_GROUP_KEYS).check();
+ }
+
+ @Test void testAggregateFunctionWithMixedColumnsNoOptimization() {
+ // Negative test: expression references both group-by and non-group-by
columns.
+ // The rule should NOT optimize because the expression is not constant
within the group.
+ String sql = "select sal, max(sal + comm) as max_sum\n"
+ + "from emp group by sal, deptno";
+
sql(sql).withRule(AGGREGATE_REDUCE_FUNCTIONS_ON_GROUP_KEYS).checkUnchanged();
+ }
+
+ @Test void testAggregateFunctionWithNonGroupByColumnNoOptimization() {
+ // Negative test: expression references only non-group-by columns.
+ // The rule should NOT optimize because the column is not in the GROUP BY
set.
+ String sql = "select sal, max(comm) as comm_max\n"
+ + "from emp group by sal";
+
sql(sql).withRule(AGGREGATE_REDUCE_FUNCTIONS_ON_GROUP_KEYS).checkUnchanged();
+ }
+
+ @Test void testAggregateFunctionWithMixedGroupByColumnsNoOptimization() {
+ // Negative test: expression contains GROUP BY column but also references
+ // a column from elsewhere that is not in GROUP BY.
+ String sql = "select sal, max(sal + empno) as max_sum\n"
+ + "from emp group by sal, deptno";
+
sql(sql).withRule(AGGREGATE_REDUCE_FUNCTIONS_ON_GROUP_KEYS).checkUnchanged();
+ }
+
@AfterAll static void checkActualAndReferenceFiles() {
fixture().diffRepos.checkActualAndReferenceFiles();
}
diff --git
a/core/src/test/resources/org/apache/calcite/test/AggregateReduceFunctionsOnGroupKeysRuleTest.xml
b/core/src/test/resources/org/apache/calcite/test/AggregateReduceFunctionsOnGroupKeysRuleTest.xml
index e7eb9d5a92..2083a969d7 100644
---
a/core/src/test/resources/org/apache/calcite/test/AggregateReduceFunctionsOnGroupKeysRuleTest.xml
+++
b/core/src/test/resources/org/apache/calcite/test/AggregateReduceFunctionsOnGroupKeysRuleTest.xml
@@ -33,10 +33,102 @@ LogicalProject(SAL=[$0], SAL_MAX=[$2], SAL_MIN=[$3],
SAL_AVG=[$4], SAL_VAL=[$5])
<Resource name="planAfter">
<![CDATA[
LogicalProject(SAL=[$0], SAL_MAX=[$2], SAL_MIN=[$3], SAL_AVG=[$4],
SAL_VAL=[$5])
- LogicalProject(SAL=[$0], DEPTNO=[$1], SAL0=[$0], SAL1=[$0], SAL2=[$0],
SAL3=[$0])
+ LogicalProject(SAL=[$0], DEPTNO=[$1], SAL_MAX=[$0], SAL_MIN=[$0],
SAL_AVG=[$0], SAL_VAL=[$0])
LogicalAggregate(group=[{0, 1}])
LogicalProject(SAL=[$5], DEPTNO=[$7])
LogicalTableScan(table=[[CATALOG, SALES, EMP]])
+]]>
+ </Resource>
+ </TestCase>
+ <TestCase name="testAggregateFunctionOfGroupByKeysBinaryExpression">
+ <Resource name="sql">
+ <![CDATA[select sal, max(sal * 2) as max_double
+from emp group by sal, deptno]]>
+ </Resource>
+ <Resource name="planBefore">
+ <![CDATA[
+LogicalProject(SAL=[$0], MAX_DOUBLE=[$2])
+ LogicalAggregate(group=[{0, 1}], MAX_DOUBLE=[MAX($2)])
+ LogicalProject(SAL=[$5], DEPTNO=[$7], $f2=[*($5, 2)])
+ LogicalTableScan(table=[[CATALOG, SALES, EMP]])
+]]>
+ </Resource>
+ <Resource name="planAfter">
+ <![CDATA[
+LogicalProject(SAL=[$0], MAX_DOUBLE=[$2])
+ LogicalProject(SAL=[$0], DEPTNO=[$1], MAX_DOUBLE=[*($0, 2)])
+ LogicalAggregate(group=[{0, 1}])
+ LogicalProject(SAL=[$5], DEPTNO=[$7], $f2=[*($5, 2)])
+ LogicalTableScan(table=[[CATALOG, SALES, EMP]])
+]]>
+ </Resource>
+ </TestCase>
+ <TestCase name="testAggregateFunctionOfGroupByKeysDeterministicExpression">
+ <Resource name="sql">
+ <![CDATA[select sal, max(sal + 1) as max_plus
+from emp group by sal, deptno]]>
+ </Resource>
+ <Resource name="planBefore">
+ <![CDATA[
+LogicalProject(SAL=[$0], MAX_PLUS=[$2])
+ LogicalAggregate(group=[{0, 1}], MAX_PLUS=[MAX($2)])
+ LogicalProject(SAL=[$5], DEPTNO=[$7], $f2=[+($5, 1)])
+ LogicalTableScan(table=[[CATALOG, SALES, EMP]])
+]]>
+ </Resource>
+ <Resource name="planAfter">
+ <![CDATA[
+LogicalProject(SAL=[$0], MAX_PLUS=[$2])
+ LogicalProject(SAL=[$0], DEPTNO=[$1], MAX_PLUS=[+($0, 1)])
+ LogicalAggregate(group=[{0, 1}])
+ LogicalProject(SAL=[$5], DEPTNO=[$7], $f2=[+($5, 1)])
+ LogicalTableScan(table=[[CATALOG, SALES, EMP]])
+]]>
+ </Resource>
+ </TestCase>
+ <TestCase name="testAggregateFunctionOfGroupByKeysMultipleGroupKeys">
+ <Resource name="sql">
+ <![CDATA[select sal, max(sal + deptno) as max_sum
+from emp group by sal, deptno]]>
+ </Resource>
+ <Resource name="planBefore">
+ <![CDATA[
+LogicalProject(SAL=[$0], MAX_SUM=[$2])
+ LogicalAggregate(group=[{0, 1}], MAX_SUM=[MAX($2)])
+ LogicalProject(SAL=[$5], DEPTNO=[$7], $f2=[+($5, $7)])
+ LogicalTableScan(table=[[CATALOG, SALES, EMP]])
+]]>
+ </Resource>
+ <Resource name="planAfter">
+ <![CDATA[
+LogicalProject(SAL=[$0], MAX_SUM=[$2])
+ LogicalProject(SAL=[$0], DEPTNO=[$1], MAX_SUM=[+($0, $1)])
+ LogicalAggregate(group=[{0, 1}])
+ LogicalProject(SAL=[$5], DEPTNO=[$7], $f2=[+($5, $7)])
+ LogicalTableScan(table=[[CATALOG, SALES, EMP]])
+]]>
+ </Resource>
+ </TestCase>
+ <TestCase name="testAggregateFunctionOfGroupByKeysNestedExpression">
+ <Resource name="sql">
+ <![CDATA[select sal, max((sal + 1) * 2) as max_expr
+from emp group by sal, deptno]]>
+ </Resource>
+ <Resource name="planBefore">
+ <![CDATA[
+LogicalProject(SAL=[$0], MAX_EXPR=[$2])
+ LogicalAggregate(group=[{0, 1}], MAX_EXPR=[MAX($2)])
+ LogicalProject(SAL=[$5], DEPTNO=[$7], $f2=[*(+($5, 1), 2)])
+ LogicalTableScan(table=[[CATALOG, SALES, EMP]])
+]]>
+ </Resource>
+ <Resource name="planAfter">
+ <![CDATA[
+LogicalProject(SAL=[$0], MAX_EXPR=[$2])
+ LogicalProject(SAL=[$0], DEPTNO=[$1], MAX_EXPR=[*(+($0, 1), 2)])
+ LogicalAggregate(group=[{0, 1}])
+ LogicalProject(SAL=[$5], DEPTNO=[$7], $f2=[*(+($5, 1), 2)])
+ LogicalTableScan(table=[[CATALOG, SALES, EMP]])
]]>
</Resource>
</TestCase>
@@ -51,6 +143,48 @@ LogicalProject(SAL=[$0], COMM_MAX=[$2])
LogicalAggregate(group=[{0, 1}], COMM_MAX=[MAX($2)])
LogicalProject(SAL=[$5], DEPTNO=[$7], COMM=[$6])
LogicalTableScan(table=[[CATALOG, SALES, EMP]])
+]]>
+ </Resource>
+ </TestCase>
+ <TestCase name="testAggregateFunctionOfGroupByKeysNullExpression">
+ <Resource name="sql">
+ <![CDATA[select comm, max(comm + 1) as max_plus
+from empnullables group by comm]]>
+ </Resource>
+ <Resource name="planBefore">
+ <![CDATA[
+LogicalAggregate(group=[{0}], MAX_PLUS=[MAX($1)])
+ LogicalProject(COMM=[$6], $f1=[+($6, 1)])
+ LogicalTableScan(table=[[CATALOG, SALES, EMPNULLABLES]])
+]]>
+ </Resource>
+ <Resource name="planAfter">
+ <![CDATA[
+LogicalProject(COMM=[$0], MAX_PLUS=[+($0, 1)])
+ LogicalAggregate(group=[{0}])
+ LogicalProject(COMM=[$6], $f1=[+($6, 1)])
+ LogicalTableScan(table=[[CATALOG, SALES, EMPNULLABLES]])
+]]>
+ </Resource>
+ </TestCase>
+ <TestCase name="testAggregateFunctionOfGroupByKeysNullGroupKey">
+ <Resource name="sql">
+ <![CDATA[select comm, max(comm) as comm_max
+from empnullables group by comm]]>
+ </Resource>
+ <Resource name="planBefore">
+ <![CDATA[
+LogicalAggregate(group=[{0}], COMM_MAX=[MAX($0)])
+ LogicalProject(COMM=[$6])
+ LogicalTableScan(table=[[CATALOG, SALES, EMPNULLABLES]])
+]]>
+ </Resource>
+ <Resource name="planAfter">
+ <![CDATA[
+LogicalProject(COMM=[$0], COMM_MAX=[$0])
+ LogicalAggregate(group=[{0}])
+ LogicalProject(COMM=[$6])
+ LogicalTableScan(table=[[CATALOG, SALES, EMPNULLABLES]])
]]>
</Resource>
</TestCase>
@@ -70,10 +204,120 @@ LogicalProject(SAL=[$0], SAL_MAX=[$2], COMM_SUM=[$3])
<Resource name="planAfter">
<![CDATA[
LogicalProject(SAL=[$0], SAL_MAX=[$2], COMM_SUM=[$3])
- LogicalProject(SAL=[$0], DEPTNO=[$1], SAL0=[$0], COMM_SUM=[$2])
+ LogicalProject(SAL=[$0], DEPTNO=[$1], SAL_MAX=[$0], COMM_SUM=[$2])
LogicalAggregate(group=[{0, 1}], COMM_SUM=[SUM($2)])
LogicalProject(SAL=[$5], DEPTNO=[$7], COMM=[$6])
LogicalTableScan(table=[[CATALOG, SALES, EMP]])
+]]>
+ </Resource>
+ </TestCase>
+ <TestCase name="testAggregateFunctionOfGroupByKeysUnaryMinus">
+ <Resource name="sql">
+ <![CDATA[select sal, max(-sal) as max_neg
+from emp group by sal, deptno]]>
+ </Resource>
+ <Resource name="planBefore">
+ <![CDATA[
+LogicalProject(SAL=[$0], MAX_NEG=[$2])
+ LogicalAggregate(group=[{0, 1}], MAX_NEG=[MAX($2)])
+ LogicalProject(SAL=[$5], DEPTNO=[$7], $f2=[-($5)])
+ LogicalTableScan(table=[[CATALOG, SALES, EMP]])
+]]>
+ </Resource>
+ <Resource name="planAfter">
+ <![CDATA[
+LogicalProject(SAL=[$0], MAX_NEG=[$2])
+ LogicalProject(SAL=[$0], DEPTNO=[$1], MAX_NEG=[-($0)])
+ LogicalAggregate(group=[{0, 1}])
+ LogicalProject(SAL=[$5], DEPTNO=[$7], $f2=[-($5)])
+ LogicalTableScan(table=[[CATALOG, SALES, EMP]])
+]]>
+ </Resource>
+ </TestCase>
+ <TestCase name="testAggregateFunctionOfGroupByKeysWithCastNarrower">
+ <Resource name="sql">
+ <![CDATA[select cast(sal as smallint) as sal_small, max(sal) as sal_max
+from emp group by sal]]>
+ </Resource>
+ <Resource name="planBefore">
+ <![CDATA[
+LogicalProject(SAL_SMALL=[CAST($0):SMALLINT NOT NULL], SAL_MAX=[$1])
+ LogicalAggregate(group=[{0}], SAL_MAX=[MAX($0)])
+ LogicalProject(SAL=[$5])
+ LogicalTableScan(table=[[CATALOG, SALES, EMP]])
+]]>
+ </Resource>
+ <Resource name="planAfter">
+ <![CDATA[
+LogicalProject(SAL_SMALL=[CAST($0):SMALLINT NOT NULL], SAL_MAX=[$1])
+ LogicalProject(SAL=[$0], SAL_MAX=[$0])
+ LogicalAggregate(group=[{0}])
+ LogicalProject(SAL=[$5])
+ LogicalTableScan(table=[[CATALOG, SALES, EMP]])
+]]>
+ </Resource>
+ </TestCase>
+ <TestCase name="testAggregateFunctionOfGroupByKeysWithCastWider">
+ <Resource name="sql">
+ <![CDATA[select cast(sal as bigint) as sal_big, max(sal) as sal_max
+from emp group by sal]]>
+ </Resource>
+ <Resource name="planBefore">
+ <![CDATA[
+LogicalProject(SAL_BIG=[CAST($0):BIGINT NOT NULL], SAL_MAX=[$1])
+ LogicalAggregate(group=[{0}], SAL_MAX=[MAX($0)])
+ LogicalProject(SAL=[$5])
+ LogicalTableScan(table=[[CATALOG, SALES, EMP]])
+]]>
+ </Resource>
+ <Resource name="planAfter">
+ <![CDATA[
+LogicalProject(SAL_BIG=[CAST($0):BIGINT NOT NULL], SAL_MAX=[$1])
+ LogicalProject(SAL=[$0], SAL_MAX=[$0])
+ LogicalAggregate(group=[{0}])
+ LogicalProject(SAL=[$5])
+ LogicalTableScan(table=[[CATALOG, SALES, EMP]])
+]]>
+ </Resource>
+ </TestCase>
+ <TestCase name="testAggregateFunctionWithMixedColumnsNoOptimization">
+ <Resource name="sql">
+ <![CDATA[select sal, max(sal + comm) as max_sum
+from emp group by sal, deptno]]>
+ </Resource>
+ <Resource name="planBefore">
+ <![CDATA[
+LogicalProject(SAL=[$0], MAX_SUM=[$2])
+ LogicalAggregate(group=[{0, 1}], MAX_SUM=[MAX($2)])
+ LogicalProject(SAL=[$5], DEPTNO=[$7], $f2=[+($5, $6)])
+ LogicalTableScan(table=[[CATALOG, SALES, EMP]])
+]]>
+ </Resource>
+ </TestCase>
+ <TestCase name="testAggregateFunctionWithMixedGroupByColumnsNoOptimization">
+ <Resource name="sql">
+ <![CDATA[select sal, max(sal + empno) as max_sum
+from emp group by sal, deptno]]>
+ </Resource>
+ <Resource name="planBefore">
+ <![CDATA[
+LogicalProject(SAL=[$0], MAX_SUM=[$2])
+ LogicalAggregate(group=[{0, 1}], MAX_SUM=[MAX($2)])
+ LogicalProject(SAL=[$5], DEPTNO=[$7], $f2=[+($5, $0)])
+ LogicalTableScan(table=[[CATALOG, SALES, EMP]])
+]]>
+ </Resource>
+ </TestCase>
+ <TestCase name="testAggregateFunctionWithNonGroupByColumnNoOptimization">
+ <Resource name="sql">
+ <![CDATA[select sal, max(comm) as comm_max
+from emp group by sal]]>
+ </Resource>
+ <Resource name="planBefore">
+ <![CDATA[
+LogicalAggregate(group=[{0}], COMM_MAX=[MAX($1)])
+ LogicalProject(SAL=[$5], COMM=[$6])
+ LogicalTableScan(table=[[CATALOG, SALES, EMP]])
]]>
</Resource>
</TestCase>