Skip to content

Commit f23a7a2

Browse files
committed
address comments
Signed-off-by: Lantao Jin <ltjin@amazon.com>
1 parent 8976d27 commit f23a7a2

4 files changed

Lines changed: 106 additions & 94 deletions

File tree

core/src/main/java/org/opensearch/sql/calcite/CalciteAggCallVisitor.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,8 @@ public AggCall visitAlias(Alias node, CalcitePlanContext context) {
3737

3838
@Override
3939
public AggCall visitAggregateFunction(AggregateFunction node, CalcitePlanContext context) {
40-
RexNode field = rexNodeVisitor.analyze(node.getField(), context);
40+
RexNode field =
41+
node.getField() == null ? null : rexNodeVisitor.analyze(node.getField(), context);
4142
List<RexNode> argList = new ArrayList<>();
4243
for (UnresolvedExpression arg : node.getArgList()) {
4344
argList.add(rexNodeVisitor.analyze(arg, context));

core/src/main/java/org/opensearch/sql/calcite/CalciteRelNodeVisitor.java

Lines changed: 96 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
import org.apache.calcite.plan.RelOptTable;
2828
import org.apache.calcite.plan.ViewExpanders;
2929
import org.apache.calcite.rel.RelNode;
30+
import org.apache.calcite.rel.core.Aggregate;
3031
import org.apache.calcite.rel.type.RelDataTypeField;
3132
import org.apache.calcite.rex.RexCall;
3233
import org.apache.calcite.rex.RexCorrelVariable;
@@ -43,6 +44,7 @@
4344
import org.checkerframework.checker.nullness.qual.Nullable;
4445
import org.opensearch.sql.ast.AbstractNodeVisitor;
4546
import org.opensearch.sql.ast.Node;
47+
import org.opensearch.sql.ast.dsl.AstDSL;
4648
import org.opensearch.sql.ast.expression.AllFields;
4749
import org.opensearch.sql.ast.expression.AllFieldsExcludeMeta;
4850
import org.opensearch.sql.ast.expression.Argument;
@@ -378,70 +380,102 @@ private void projectPlusOverriding(
378380
context.relBuilder.rename(expectedRenameFields);
379381
}
380382

381-
private Pair<List<AggCall>, List<RexNode>> resolveAggCallAndGroupBy(
382-
Aggregation node, CalcitePlanContext context) {
383+
/**
384+
* Resolve the aggregation with trimming unused fields to avoid bugs in {@link
385+
* org.apache.calcite.sql2rel.RelDecorrelator#decorrelateRel(Aggregate, boolean)}
386+
*
387+
* @param groupExprList group by expression list
388+
* @param aggExprList aggregate expression list
389+
* @param context CalcitePlanContext
390+
* @return Pair of (group-by list, field list, aggregate list)
391+
*/
392+
private Pair<List<RexNode>, List<AggCall>> aggregateWithTrimming(
393+
List<UnresolvedExpression> groupExprList,
394+
List<UnresolvedExpression> aggExprList,
395+
CalcitePlanContext context) {
396+
// Example 1: source=t | where a > 1 | stats avg(b + 1) by c
397+
// Before: Aggregate(avg(b + 1))
398+
// \- Filter(a > 1)
399+
// \- Scan t
400+
// After: Aggregate(avg(b + 1))
401+
// \- Project([c, b])
402+
// \- Filter(a > 1)
403+
// \- Scan t
404+
//
405+
// Example 2: source=t | where a > 1 | top b by c
406+
// Before: Aggregate(count)
407+
// \-Filter(a > 1)
408+
// \- Scan t
409+
// After: Aggregate(count)
410+
// \- Project([c, b])
411+
// \- Filter(a > 1)
412+
// \- Scan t
413+
Pair<List<RexNode>, List<AggCall>> resolved =
414+
resolveAttributesForAggregation(groupExprList, aggExprList, context);
415+
List<RexInputRef> trimmedRefs = new ArrayList<>();
416+
trimmedRefs.addAll(PlanUtils.getInputRefs(resolved.getLeft())); // group-by keys first
417+
trimmedRefs.addAll(PlanUtils.getInputRefsFromAggCall(resolved.getRight()));
418+
context.relBuilder.project(trimmedRefs);
419+
420+
// Re-resolve all attributes based on adding trimmed Project.
421+
// Using re-resolving rather than Calcite Mapping (ref Calcite ProjectTableScanRule)
422+
// because that Mapping only works for RexNode, but we need both AggCall and RexNode list.
423+
Pair<List<RexNode>, List<AggCall>> reResolved =
424+
resolveAttributesForAggregation(groupExprList, aggExprList, context);
425+
context.relBuilder.aggregate(
426+
context.relBuilder.groupKey(reResolved.getLeft()), reResolved.getRight());
427+
return Pair.of(reResolved.getLeft(), reResolved.getRight());
428+
}
429+
430+
/**
431+
* Resolve attributes for aggregation.
432+
*
433+
* @param groupExprList group by expression list
434+
* @param aggExprList aggregate expression list
435+
* @param context CalcitePlanContext
436+
* @return Pair of (group-by list, aggregate list)
437+
*/
438+
private Pair<List<RexNode>, List<AggCall>> resolveAttributesForAggregation(
439+
List<UnresolvedExpression> groupExprList,
440+
List<UnresolvedExpression> aggExprList,
441+
CalcitePlanContext context) {
383442
List<AggCall> aggCallList =
384-
node.getAggExprList().stream()
385-
.map(expr -> aggVisitor.analyze(expr, context))
386-
.collect(Collectors.toList());
387-
// The span column is always the first column in result whatever
388-
// the order of span in query is first or last one
389-
List<RexNode> groupByList = new ArrayList<>();
390-
UnresolvedExpression span = node.getSpan();
391-
if (!Objects.isNull(span)) {
392-
RexNode spanRex = rexVisitor.analyze(span, context);
393-
groupByList.add(spanRex);
394-
// add span's group alias field (most recent added expression)
395-
}
396-
groupByList.addAll(
397-
node.getGroupExprList().stream().map(expr -> rexVisitor.analyze(expr, context)).toList());
398-
return Pair.of(aggCallList, groupByList);
443+
aggExprList.stream().map(expr -> aggVisitor.analyze(expr, context)).toList();
444+
List<RexNode> groupByList =
445+
groupExprList.stream().map(expr -> rexVisitor.analyze(expr, context)).toList();
446+
return Pair.of(groupByList, aggCallList);
399447
}
400448

401449
@Override
402450
public RelNode visitAggregation(Aggregation node, CalcitePlanContext context) {
403451
visitChildren(node, context);
404-
// Add a trimmed Project before Aggregate.
405-
// to avoid bugs in RelDecorrelator.decorrelateRel(Aggregate rel)
406-
// For example:
407-
// source=t | where a > 1 | stats avg(b+1) by c
408-
// Before:
409-
// Aggregate
410-
// \- Filter(a>1)
411-
// \- Scan t
412-
// After:
413-
// Aggregate
414-
// \- Project([c,b])
415-
// \- Filter(a>1)
416-
// \- Scan t
417-
Pair<List<AggCall>, List<RexNode>> resolved = resolveAggCallAndGroupBy(node, context);
418-
List<RexInputRef> trimmedRefs = new ArrayList<>();
419-
trimmedRefs.addAll(PlanUtils.getInputRefs(resolved.getRight())); // group-by keys first
420-
trimmedRefs.addAll(PlanUtils.getInputRefsFromAggCall(resolved.getLeft()));
421-
context.relBuilder.project(trimmedRefs);
422452

423-
// Re-resolve aggCalls and group-by list based on adding trimmed Project.
424-
// Using re-resolving rather than Calcite Mapping (ref Calcite ProjectTableScanRule)
425-
// because that Mapping only works for RexNode, but we need both AggCall and RexNode list.
426-
Pair<List<AggCall>, List<RexNode>> reResolved = resolveAggCallAndGroupBy(node, context);
427-
List<AggCall> aggList = reResolved.getLeft();
428-
List<RexNode> groupByList = reResolved.getRight();
429-
context.relBuilder.aggregate(context.relBuilder.groupKey(groupByList), aggList);
453+
List<UnresolvedExpression> aggExprList = node.getAggExprList();
454+
List<UnresolvedExpression> groupExprList = new ArrayList<>();
455+
// The span column is always the first column in result whatever
456+
// the order of span in query is first or last one
457+
UnresolvedExpression span = node.getSpan();
458+
if (!Objects.isNull(span)) {
459+
groupExprList.add(span);
460+
}
461+
groupExprList.addAll(node.getGroupExprList());
462+
Pair<List<RexNode>, List<AggCall>> aggregationAttributes =
463+
aggregateWithTrimming(groupExprList, aggExprList, context);
430464

431465
// schema reordering
432466
// As an example, in command `stats count() by colA, colB`,
433467
// the sequence of output schema is "count, colA, colB".
434468
List<RexNode> outputFields = context.relBuilder.fields();
435469
int numOfOutputFields = outputFields.size();
436-
int numOfAggList = aggList.size();
470+
int numOfAggList = aggExprList.size();
437471
List<RexNode> reordered = new ArrayList<>(numOfOutputFields);
438472
// Add aggregation results first
439473
List<RexNode> aggRexList =
440474
outputFields.subList(numOfOutputFields - numOfAggList, numOfOutputFields);
441475
reordered.addAll(aggRexList);
442476
// Add group by columns
443477
List<RexNode> aliasedGroupByList =
444-
groupByList.stream()
478+
aggregationAttributes.getLeft().stream()
445479
.map(this::extractAliasLiteral)
446480
.flatMap(Optional::stream)
447481
.map(ref -> ((RexLiteral) ref).getValueAs(String.class))
@@ -744,45 +778,30 @@ public RelNode visitKmeans(Kmeans node, CalcitePlanContext context) {
744778
throw new CalciteUnsupportedException("Kmeans command is unsupported in Calcite");
745779
}
746780

747-
List<RexNode> resolveGroupByPlusFieldList(RareTopN node, CalcitePlanContext context) {
748-
List<RexNode> groupByList = resolveGroupByList(node, context);
749-
List<RexNode> filedsList =
750-
node.getFields().stream().map(g -> rexVisitor.analyze(g, context)).toList();
751-
List<RexNode> all = new ArrayList<>(groupByList);
752-
all.addAll(filedsList);
753-
return all;
754-
}
755-
756-
List<RexNode> resolveGroupByList(RareTopN node, CalcitePlanContext context) {
757-
return node.getGroupExprList().stream().map(g -> rexVisitor.analyze(g, context)).toList();
758-
}
759-
760781
@Override
761782
public RelNode visitRareTopN(RareTopN node, CalcitePlanContext context) {
762783
visitChildren(node, context);
763784

764-
// 1. before aggregating, add a trim project
765-
List<RexInputRef> trimmedRefs =
766-
PlanUtils.getInputRefs(resolveGroupByPlusFieldList(node, context));
767-
context.relBuilder.project(trimmedRefs);
768-
769785
ArgumentMap arguments = ArgumentMap.of(node.getArguments());
770-
// 2. group the group-by list + field list and add a count() aggregation
771-
List<RexNode> firstGroupBy = resolveGroupByPlusFieldList(node, context);
772786
String countFieldName = (String) arguments.get("countField").getValue();
773787
if (context.relBuilder.peek().getRowType().getFieldNames().contains(countFieldName)) {
774788
throw new IllegalArgumentException(
775789
"Field `"
776790
+ countFieldName
777791
+ "` is existed, change the count field by setting countfield='xyz'");
778792
}
779-
AggCall aggCall =
780-
PlanUtils.makeAggCall(context, BuiltinFunctionName.COUNT, false, null, List.of())
781-
.as(countFieldName);
782-
context.relBuilder.aggregate(context.relBuilder.groupKey(firstGroupBy), aggCall);
783793

784-
// 3. add a window column
785-
List<RexNode> partitionKeys = new ArrayList<>(resolveGroupByList(node, context));
794+
// 1. group the group-by list + field list and add a count() aggregation
795+
List<UnresolvedExpression> groupExprList = new ArrayList<>(node.getGroupExprList());
796+
List<UnresolvedExpression> fieldList =
797+
node.getFields().stream().map(f -> (UnresolvedExpression) f).toList();
798+
groupExprList.addAll(fieldList);
799+
List<UnresolvedExpression> aggExprList =
800+
List.of(AstDSL.alias(countFieldName, AstDSL.aggregate("count", null)));
801+
aggregateWithTrimming(groupExprList, aggExprList, context);
802+
803+
// 2. add a window column
804+
List<RexNode> partitionKeys = rexVisitor.analyze(node.getGroupExprList(), context);
786805
RexNode countField;
787806
if (node.getCommandType() == RareTopN.CommandType.TOP) {
788807
countField = context.relBuilder.desc(context.relBuilder.field(countFieldName));
@@ -801,19 +820,21 @@ public RelNode visitRareTopN(RareTopN node, CalcitePlanContext context) {
801820
context.relBuilder.projectPlus(
802821
context.relBuilder.alias(rowNumberWindowOver, ROW_NUMBER_COLUMN_NAME));
803822

804-
// 4. filter row_number() <= k in each partition
823+
// 3. filter row_number() <= k in each partition
805824
Integer N = (Integer) arguments.get("noOfResults").getValue();
806825
context.relBuilder.filter(
807826
context.relBuilder.lessThanOrEqual(
808827
context.relBuilder.field(ROW_NUMBER_COLUMN_NAME), context.relBuilder.literal(N)));
809828

810-
// 5. project final output. the default output is group by list + field list
811-
List<RexNode> finalProjectList = new ArrayList<>(resolveGroupByPlusFieldList(node, context));
829+
// 4. project final output. the default output is group by list + field list
812830
Boolean showCount = (Boolean) arguments.get("showCount").getValue();
813831
if (showCount) {
814-
finalProjectList.add(context.relBuilder.field(countFieldName));
832+
context.relBuilder.projectExcept(context.relBuilder.field(ROW_NUMBER_COLUMN_NAME));
833+
} else {
834+
context.relBuilder.projectExcept(
835+
context.relBuilder.field(ROW_NUMBER_COLUMN_NAME),
836+
context.relBuilder.field(countFieldName));
815837
}
816-
context.relBuilder.project(finalProjectList);
817838
return context.relBuilder.peek();
818839
}
819840

core/src/main/java/org/opensearch/sql/calcite/CalciteRexNodeVisitor.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,10 @@ public RexNode analyze(UnresolvedExpression unresolved, CalcitePlanContext conte
7474
return unresolved.accept(this, context);
7575
}
7676

77+
public List<RexNode> analyze(List<UnresolvedExpression> list, CalcitePlanContext context) {
78+
return list.stream().map(u -> u.accept(this, context)).toList();
79+
}
80+
7781
public RexNode analyzeJoinCondition(UnresolvedExpression unresolved, CalcitePlanContext context) {
7882
return context.resolveJoinCondition(unresolved, this::analyze);
7983
}

core/src/main/java/org/opensearch/sql/calcite/utils/PlanUtils.java

Lines changed: 4 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,7 @@ static RexNode makeOver(
114114
return withOver(
115115
makeAggCall(context, functionName, false, field, argList),
116116
partitions,
117+
orderKeys,
117118
rows,
118119
lowerBound,
119120
upperBound);
@@ -127,7 +128,8 @@ private static RexNode sumOver(
127128
boolean rows,
128129
RexWindowBound lowerBound,
129130
RexWindowBound upperBound) {
130-
return withOver(ctx.relBuilder.sum(operation), partitions, rows, lowerBound, upperBound);
131+
return withOver(
132+
ctx.relBuilder.sum(operation), partitions, List.of(), rows, lowerBound, upperBound);
131133
}
132134

133135
private static RexNode countOver(
@@ -140,28 +142,12 @@ private static RexNode countOver(
140142
return withOver(
141143
ctx.relBuilder.count(ImmutableList.of(operation)),
142144
partitions,
145+
List.of(),
143146
rows,
144147
lowerBound,
145148
upperBound);
146149
}
147150

148-
private static RexNode withOver(
149-
RelBuilder.AggCall aggCall,
150-
List<RexNode> partitions,
151-
boolean rows,
152-
RexWindowBound lowerBound,
153-
RexWindowBound upperBound) {
154-
return aggCall
155-
.over()
156-
.partitionBy(partitions)
157-
.let(
158-
c ->
159-
rows
160-
? c.rowsBetween(lowerBound, upperBound)
161-
: c.rangeBetween(lowerBound, upperBound))
162-
.toRex();
163-
}
164-
165151
private static RexNode withOver(
166152
RelBuilder.AggCall aggCall,
167153
List<RexNode> partitions,

0 commit comments

Comments
 (0)