Copilot commented on code in PR #19264:
URL: https://github.com/apache/pinot/pull/19264#discussion_r3790092774


##########
pinot-core/src/main/java/org/apache/pinot/core/util/GroupByUtils.java:
##########
@@ -87,6 +98,125 @@ public static GroupByResultsBlock 
buildGroupingSetsResultsBlock(QueryContext que
     return resultsBlock;
   }
 
+  /// Builds the segment-level [GroupByResultsBlock] for a grouping-set query 
from BASE groups, i.e. groups
+  /// aggregated once over the union of all grouping-set columns (a plain 
GROUP BY), rather than by expanding
+  /// every input row into one group per grouping set. Each base group is then 
projected into each grouping set:
+  /// its rolled-up (non-participating) columns are set to `null`, the 
`$groupingId` discriminator is appended,
+  /// and the base group's aggregation intermediates are merged into the 
derived group. This moves the per-set
+  /// fan-out from O(rows) to O(base groups), reusing the fast plain-GROUP-BY 
scan path.
+  ///
+  /// Because a base group's intermediate result flows into every grouping 
set, and [AggregationFunction#merge]
+  /// mutates its first argument, each base intermediate is cloned per set 
before it becomes a merge target (see
+  /// [#cloneIntermediate]). This keeps the derivation exact for object-backed 
accumulators (AVG, DISTINCTCOUNT,
+  /// percentiles, ...) as well as scalar ones.
+  ///
+  /// @param discriminatorColumnIndex index of the synthetic $groupingId 
column, i.e. the number of union
+  ///                                 group-by columns
+  public static GroupByResultsBlock 
buildGroupingSetsResultsBlockFromBaseGroups(QueryContext queryContext,
+      DataSchema dataSchema, AggregationGroupByResult baseResult, int 
discriminatorColumnIndex,
+      boolean numGroupsLimitReached, boolean numGroupsWarningLimitReached) {
+    AggregationFunction[] aggregationFunctions = 
queryContext.getAggregationFunctions();
+    assert aggregationFunctions != null;
+    int numAggregationFunctions = aggregationFunctions.length;
+    List<int[]> groupingSets = queryContext.getGroupingSets();
+    int numSets = groupingSets.size();
+    int numUnionColumns = discriminatorColumnIndex;
+    // Per grouping set: membership mask over the union columns (true = 
participates, false = rolled up to NULL).
+    boolean[][] setContains = new boolean[numSets][numUnionColumns];
+    for (int s = 0; s < numSets; s++) {
+      for (int columnIndex : groupingSets.get(s)) {
+        setContains[s][columnIndex] = true;
+      }
+    }
+
+    // Derived group table keyed on (projected union values..., $groupingId). 
Values layout mirrors the record
+    // schema: key columns first, then the aggregation intermediates.
+    Map<Key, Record> derived = new HashMap<>();

Review Comment:
   The derived table is no longer bounded by `numGroupsLimit`. The base 
generator caps only base groups, but each retained base group can create 
records in every set; for example, a limit of 2 with `ROLLUP(d1)` can 
materialize 3 derived groups, and the worst case is 100,000 base groups × up to 
4,096 sets. The optional trim runs only after this map is fully built, so this 
bypasses the segment memory guardrail and can exhaust heap. Bound 
materialization while deriving (with clearly defined global/per-set semantics) 
and include derived counts in the limit/warning accounting.



##########
pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/groupby/GroupingSetsGroupKeyGenerator.java:
##########
@@ -378,13 +545,39 @@ private Object[] buildKeysFromIds(FixedIntArray keyList) {
     int[] ids = keyList.elements();
     Object[] keys = new Object[_numGroupByExpressions + 1];
     for (int i = 0; i < _numGroupByExpressions; i++) {
-      keys[i] = ids[i] == ID_FOR_NULL ? null : 
_onTheFlyDictionaries[i].get(ids[i]);
+      if (ids[i] == ID_FOR_NULL) {
+        keys[i] = null;
+      } else if (_dictionaries[i] != null) {
+        keys[i] = _dictionaries[i].getInternal(ids[i]);
+      } else {
+        keys[i] = _onTheFlyDictionaries[i].get(ids[i]);
+      }
     }
     /// The trailing slot stores the grouping-set ordinal directly (not a 
dictionary id).
     keys[_numGroupByExpressions] = ids[_numGroupByExpressions];
     return keys;
   }
 
+  /// Unpacks a long-packed composite key into the output row: each union 
column value (NULL where the packed
+  /// id is the reserved sentinel) followed by the integer 
grouping-set-ordinal discriminator. Inverse of the
+  /// packing done in [#generateKeysForBlockSvLongPacked] / 
[#expandGroupIdsLongPacked].
+  private Object[] buildKeysFromLong(long key) {
+    Object[] keys = new Object[_numGroupByExpressions + 1];
+    for (int i = 0; i < _numGroupByExpressions; i++) {
+      int packedId = extractPackedId(key, i);
+      keys[i] = packedId == _nullPackedIds[i] ? null : 
_dictionaries[i].getInternal(packedId);
+    }
+    keys[_numGroupByExpressions] = (int) (key >>> 
_bitShifts[_numGroupByExpressions]);

Review Comment:
   When the column slots consume exactly 64 bits and there is one grouping set, 
the discriminator shift is 64. Java masks long shift distances modulo 64, so 
`key >>> 64` is actually `key >>> 0`; the emitted `$groupingId` becomes the low 
32 key bits instead of ordinal 0. Handle the single-set case explicitly (and 
add a 64-bit boundary test).



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to