Skip to content

Commit f7f2396

Browse files
[CALCITE-7242] Implement a rule to eliminate LITERAL_AGG so that other databases can handle it
1 parent f398d74 commit f7f2396

File tree

2 files changed

+163
-0
lines changed

2 files changed

+163
-0
lines changed
Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to you under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
package org.apache.calcite.rel.rules;
18+
19+
import org.apache.calcite.plan.RelOptRuleCall;
20+
import org.apache.calcite.plan.RelRule;
21+
import org.apache.calcite.rel.RelNode;
22+
import org.apache.calcite.rel.core.Aggregate;
23+
import org.apache.calcite.rel.core.AggregateCall;
24+
import org.apache.calcite.rel.logical.LogicalAggregate;
25+
import org.apache.calcite.rel.logical.LogicalProject;
26+
import org.apache.calcite.rel.type.RelDataType;
27+
import org.apache.calcite.rex.RexBuilder;
28+
import org.apache.calcite.rex.RexLiteral;
29+
import org.apache.calcite.rex.RexNode;
30+
import org.apache.calcite.tools.RelBuilder;
31+
32+
import com.google.common.collect.ImmutableSet;
33+
34+
import org.immutables.value.Value;
35+
36+
import java.util.ArrayList;
37+
import java.util.HashMap;
38+
import java.util.List;
39+
import java.util.Map;
40+
41+
/**
42+
* AggregateExtractLiteralAggRule gets rid of the LITERAL_AGG into most databases can handle.
43+
*/
44+
@Value.Enclosing
45+
public class AggregateExtractLiteralAggRule
46+
extends RelRule<AggregateExtractLiteralAggRule.Config>
47+
implements TransformationRule {
48+
49+
protected AggregateExtractLiteralAggRule(Config config) {
50+
super(config);
51+
}
52+
53+
@Override public void onMatch(RelOptRuleCall call) {
54+
final LogicalAggregate aggregate = call.rel(0);
55+
56+
final List<AggregateCall> aggCalls = aggregate.getAggCallList();
57+
if (aggCalls == null || aggCalls.isEmpty()) {
58+
return;
59+
}
60+
61+
// Collect indices of LITERAL_AGG calls.
62+
final List<Integer> literalAggIndices = new ArrayList<>();
63+
for (int i = 0; i < aggCalls.size(); i++) {
64+
final AggregateCall ac = aggCalls.get(i);
65+
if (ac.getAggregation().getName().equals("LITERAL_AGG")) {
66+
literalAggIndices.add(i);
67+
}
68+
}
69+
70+
if (literalAggIndices.isEmpty()) {
71+
// nothing to do
72+
return;
73+
}
74+
75+
// Build new AggregateCall list without LITERAL_AGG entries.
76+
final List<AggregateCall> newAggCalls = new ArrayList<>();
77+
final Map<Integer, Integer> oldAggIndexToNewAggIndex = new HashMap<>();
78+
int newAggPos = 0;
79+
for (int i = 0; i < aggCalls.size(); i++) {
80+
if (!literalAggIndices.contains(i)) {
81+
newAggCalls.add(aggCalls.get(i));
82+
oldAggIndexToNewAggIndex.put(i, newAggPos++);
83+
}
84+
}
85+
86+
// Create new Aggregate preserving groupSet/groupSets/hints.
87+
final LogicalAggregate newAggregate =
88+
LogicalAggregate.create(aggregate.getInput(),
89+
aggregate.getHints(),
90+
aggregate.getGroupSet(),
91+
aggregate.getGroupSets(),
92+
newAggCalls);
93+
94+
final RexBuilder rexBuilder = aggregate.getCluster().getRexBuilder();
95+
96+
// Number of group columns in output (group keys appear first).
97+
final int groupCount = aggregate.getGroupSet().cardinality();
98+
final int origAggCount = aggCalls.size();
99+
final int origOutputCount = groupCount + origAggCount;
100+
101+
// Build projection expressions to restore original output layout.
102+
final List<RexNode> projects = new ArrayList<>(origOutputCount);
103+
104+
for (int outPos = 0; outPos < origOutputCount; outPos++) {
105+
if (outPos < groupCount) {
106+
// Group key columns remain in the same positions.
107+
projects.add(rexBuilder.makeInputRef(newAggregate, outPos));
108+
} else {
109+
// Aggregate output: determine original aggregate index.
110+
final int origAggIndex = outPos - groupCount;
111+
if (literalAggIndices.contains(origAggIndex)) {
112+
// Replacement for LITERAL_AGG: try to extract literal from the original AggregateCall.
113+
final AggregateCall origAgg = aggCalls.get(origAggIndex);
114+
RexNode literalExpr = null;
115+
116+
if (origAgg.rexList != null && !origAgg.rexList.isEmpty()
117+
&& origAgg.rexList.get(0) instanceof RexLiteral) {
118+
literalExpr = origAgg.rexList.get(0);
119+
// Cast literal to expected output type if needed.
120+
final RelDataType targetType =
121+
aggregate.getRowType().getFieldList().get(outPos).getType();
122+
if (!literalExpr.getType().equals(targetType)) {
123+
literalExpr = rexBuilder.makeCast(targetType, literalExpr);
124+
}
125+
} else {
126+
// No literal available on the AggregateCall; create a typed NULL as a safe fallback.
127+
final RelDataType targetType =
128+
aggregate.getRowType().getFieldList().get(outPos).getType();
129+
literalExpr = rexBuilder.makeNullLiteral(targetType);
130+
}
131+
projects.add(literalExpr);
132+
} else {
133+
// Non-literal aggregate: compute its new output index in newAggregate.
134+
final Integer newAggIndex = oldAggIndexToNewAggIndex.get(origAggIndex);
135+
final int newOutputIndex = groupCount + newAggIndex;
136+
projects.add(rexBuilder.makeInputRef(newAggregate, newOutputIndex));
137+
}
138+
}
139+
}
140+
141+
final RelNode projectRelNode =
142+
LogicalProject.create(newAggregate, new ArrayList<>(), projects,
143+
aggregate.getRowType(), ImmutableSet.of());
144+
call.transformTo(projectRelNode);
145+
}
146+
147+
/** Rule configuration. */
148+
@Value.Immutable
149+
public interface Config extends RelRule.Config {
150+
Config DEFAULT = ImmutableAggregateExtractLiteralAggRule.Config.of()
151+
.withRelBuilderFactory(RelBuilder.proto())
152+
.withOperandSupplier(b0 ->
153+
b0.operand(Aggregate.class).anyInputs());
154+
155+
@Override default AggregateExtractLiteralAggRule toRule() {
156+
return new AggregateExtractLiteralAggRule(this);
157+
}
158+
}
159+
}

core/src/main/java/org/apache/calcite/rel/rules/CoreRules.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -945,4 +945,8 @@ private CoreRules() {}
945945
* into equivalent {@link Union} ALL of GROUP BY operations. */
946946
public static final AggregateGroupingSetsToUnionRule AGGREGATE_GROUPING_SETS_TO_UNION =
947947
AggregateGroupingSetsToUnionRule.Config.DEFAULT.toRule();
948+
949+
/** Rule that gets rid of the LITERAL_AGG into most databases can handle */
950+
public static final AggregateExtractLiteralAggRule AGGREGATE_EXTRACT_LITERAL_AGG =
951+
AggregateExtractLiteralAggRule.Config.DEFAULT.toRule();
948952
}

0 commit comments

Comments
 (0)