2727import org .apache .calcite .plan .RelOptTable ;
2828import org .apache .calcite .plan .ViewExpanders ;
2929import org .apache .calcite .rel .RelNode ;
30+ import org .apache .calcite .rel .core .Aggregate ;
3031import org .apache .calcite .rel .type .RelDataTypeField ;
3132import org .apache .calcite .rex .RexCall ;
3233import org .apache .calcite .rex .RexCorrelVariable ;
4344import org .checkerframework .checker .nullness .qual .Nullable ;
4445import org .opensearch .sql .ast .AbstractNodeVisitor ;
4546import org .opensearch .sql .ast .Node ;
47+ import org .opensearch .sql .ast .dsl .AstDSL ;
4648import org .opensearch .sql .ast .expression .AllFields ;
4749import org .opensearch .sql .ast .expression .AllFieldsExcludeMeta ;
4850import 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
0 commit comments