77
88import com .google .common .collect .ImmutableList ;
99import java .util .ArrayList ;
10+ import java .util .LinkedHashSet ;
1011import java .util .List ;
1112import java .util .Map ;
1213import java .util .Objects ;
14+ import java .util .Set ;
1315import java .util .stream .Collectors ;
1416import javax .annotation .Nullable ;
1517import lombok .Getter ;
3436import org .apache .calcite .rel .type .RelDataTypeFactory ;
3537import org .apache .calcite .rel .type .RelDataTypeField ;
3638import org .apache .calcite .rex .RexBuilder ;
39+ import org .apache .calcite .rex .RexInputRef ;
3740import org .apache .calcite .rex .RexNode ;
41+ import org .apache .calcite .rex .RexVisitorImpl ;
3842import org .apache .calcite .sql .fun .SqlStdOperatorTable ;
3943import org .apache .calcite .sql .type .SqlTypeName ;
4044import org .apache .commons .lang3 .tuple .Pair ;
@@ -414,9 +418,12 @@ private AbstractRelNode pushDownAggregate(
414418 // Try partial mode before analyze: since #5646 a text/keyword conflict pushes down as a slow
415419 // _source script instead of failing, so a post-failure fallback would never fire.
416420 if (allowPartialFallback ) {
417- AbstractRelNode partial = tryPartialResultAggregate (aggregate , project , bucketNames );
418- if (partial != null ) {
419- return partial ;
421+ List <String > partitionFields = resolvePartitionFields (aggregate , project );
422+ if (partitionFields != null ) {
423+ AbstractRelNode partial = tryPartialResultAggregate (aggregate , project , partitionFields );
424+ if (partial != null ) {
425+ return partial ;
426+ }
420427 }
421428 }
422429 int queryBucketSize = osIndex .getQueryBucketSize ();
@@ -451,15 +458,58 @@ private AbstractRelNode pushDownAggregate(
451458 return null ;
452459 }
453460
461+ /**
462+ * Resolve the aggregation's group keys to the underlying scan fields to partition indices on. A
463+ * key may be a bare field ({@code ... by city}) or an expression over fields ({@code eval g =
464+ * lower(city) | ... by g}); in the latter case we partition on every field the expression reads,
465+ * since a kept index must map all of them aggregatably. The {@code project} (when present) sits
466+ * directly on the scan, so its input refs index into this scan's row type. Returns {@code null}
467+ * if any key is a pure constant with no field to key on.
468+ */
469+ @ Nullable
470+ private List <String > resolvePartitionFields (Aggregate aggregate , @ Nullable Project project ) {
471+ List <String > scanFields = getRowType ().getFieldNames ();
472+ List <String > fields = new ArrayList <>();
473+ for (int group : aggregate .getGroupSet ()) {
474+ Set <Integer > refs = new LinkedHashSet <>();
475+ if (project == null ) {
476+ refs .add (group ); // group key indexes directly into the scan
477+ } else {
478+ project
479+ .getProjects ()
480+ .get (group )
481+ .accept (
482+ new RexVisitorImpl <Void >(true ) {
483+ @ Override
484+ public Void visitInputRef (RexInputRef ref ) {
485+ refs .add (ref .getIndex ());
486+ return null ;
487+ }
488+ });
489+ }
490+ if (refs .isEmpty ()) {
491+ return null ; // constant group key -> nothing to partition on
492+ }
493+ for (int ref : refs ) {
494+ String name = scanFields .get (ref );
495+ if (!fields .contains (name )) {
496+ fields .add (name );
497+ }
498+ }
499+ }
500+ return fields ;
501+ }
502+
454503 /**
455504 * On a text/keyword mapping conflict, narrow the scan to the index subset where the group field
456505 * is aggregatable, push the aggregation over just that subset, and record a warning naming the
457506 * excluded indices. Only runs behind the opt-in setting and only when the response format can
458507 * carry the warning ({@link QueryContext#isWarningsSupported}); returns {@code null} otherwise.
459- * Partitioning lives in {@link PartialResultAggregatePushdown}.
508+ * {@code partitionFields} are the scan fields the group keys resolve to (see {@link
509+ * #resolvePartitionFields}). Partitioning lives in {@link PartialResultAggregatePushdown}.
460510 */
461511 private AbstractRelNode tryPartialResultAggregate (
462- Aggregate aggregate , @ Nullable Project project , List <String > bucketNames ) {
512+ Aggregate aggregate , @ Nullable Project project , List <String > partitionFields ) {
463513 if (!QueryContext .isPartialResultEnabled (osIndex .getSettings ())) {
464514 return null ;
465515 }
@@ -470,7 +520,7 @@ private AbstractRelNode tryPartialResultAggregate(
470520 try {
471521 Map <String , IndexMapping > mappings = osIndex .getIndexMappings ();
472522 PartialResultAggregatePushdown .Plan plan =
473- PartialResultAggregatePushdown .plan (bucketNames , mappings );
523+ PartialResultAggregatePushdown .plan (partitionFields , mappings );
474524 if (plan == null ) {
475525 return null ;
476526 }
0 commit comments