yashmayya commented on code in PR #19990:
URL: https://github.com/apache/pinot/pull/19990#discussion_r4382872091


##########
pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/AnyValueAggregationFunction.java:
##########
@@ -178,78 +242,83 @@ public Object deserializeIntermediateResult(CustomObject 
customObject) {
   }
 
   @FunctionalInterface
-  private interface ValueProcessor<T> {
-    boolean process(int index, T value); // Returns true to stop processing, 
false to continue
+  private interface ValueProcessor {
+    /// Returns true to stop processing, false to continue. `values` reads the 
value of a row, so that a row is read
+    /// only when it is needed.
+    boolean process(int index, IntFunction<Object> values);
   }
 
   /// Generic helper for processing values with dictionary optimization for 
all supported data types
-  private void aggregateHelper(int length, BlockValSet bvs, 
ValueProcessor<Object> processor) {
-    // Use dictionary-based access for efficiency when available
-    if (bvs.isDictionaryEncoded()) {
-      final int[] dictIds = bvs.getDictionaryIdsSV();
-      final Dictionary dict = bvs.getDictionary();
-      forEachNotNull(length, bvs, (from, to) -> {
-        for (int i = from; i < to; i++) {
-          Object value = getDictionaryValue(dict, dictIds[i], 
bvs.getValueType().getStoredType());
-          if (processor.process(i, value)) {
-            break;
-          }
-        }
-      });
-    } else {
-      // Fall back to direct value access based on type
-      forEachNotNull(length, bvs, (from, to) -> {
-        for (int i = from; i < to; i++) {
-          Object value = getDirectValue(bvs, i);
-          if (value != null && processor.process(i, value)) {
-            break;
-          }
+  private void aggregateHelper(int length, BlockValSet bvs, ValueProcessor 
processor) {
+    // Reading through the bound type keeps values consistent when segments 
store the column as different types
+    DataType storedType = _dataType != null ? _dataType.getStoredType() : 
bvs.getValueType().getStoredType();
+    IntFunction<Object> values = getValueReader(bvs, storedType);

Review Comment:
   Regression with the flag off: `getValueReader` evaluates the expression 
before `forEachNotNull`, so it also runs on blocks that are all null. Master 
read values only inside non-null ranges. If the expression fails on the null 
placeholder, a query that works today now fails. With null handling on (or a 
table with column-based null handling, in MSE with no `SET` needed):
   ```sql
   SELECT ANY_VALUE(CAST(s_null AS INT)) FROM t           -- master: null, PR: 
For input string: "null"
   SELECT ANY_VALUE(JSON_EXTRACT_SCALAR(j, '$.a', 'INT')) FROM t  -- PR: Cannot 
resolve JSON path
   ```
   The GROUP BY form fails the same way. With one all-null segment and one 
segment with `"123"`, master returns 123 and the PR fails.
   
   Suggestion: create the reader lazily in the first non-null range, or return 
early when the null bitmap covers the whole block. Please add a test: null 
handling on, an all-null segment plus a non-null segment, with 
`ANY_VALUE(CAST(strCol AS INT))`.



##########
pinot-query-planner/src/main/java/org/apache/pinot/calcite/rel/rules/PinotAggregateTypeArgumentRewriteRule.java:
##########
@@ -33,31 +34,50 @@
 import org.apache.calcite.rel.hint.RelHint;
 import org.apache.calcite.rel.logical.LogicalAggregate;
 import org.apache.calcite.rel.logical.LogicalProject;
+import org.apache.calcite.rel.type.RelDataType;
 import org.apache.calcite.rex.RexBuilder;
+import org.apache.calcite.rex.RexLiteral;
 import org.apache.calcite.rex.RexNode;
 import org.apache.calcite.sql.type.SqlTypeName;
 import org.apache.calcite.tools.RelBuilderFactory;
+import org.apache.pinot.common.utils.DataSchema.ColumnDataType;
+import org.apache.pinot.query.planner.logical.RelToPlanNodeConverter;
 import org.apache.pinot.segment.spi.AggregationFunctionType;
 
 
 /// Supplies the explicit data type argument from the type of the data 
argument:
 /// - FIRST_WITH_TIME(dataColumn, timeColumn) -> FIRST_WITH_TIME(dataColumn, 
timeColumn, 'dataType')
 /// - LAST_WITH_TIME(dataColumn, timeColumn) -> LAST_WITH_TIME(dataColumn, 
timeColumn, 'dataType')
+/// - ANY_VALUE(expr) -> ANY_VALUE(expr, 'dataType'), except by 
[#WITHOUT_ANY_VALUE]
 ///
 /// The result type of the call is unchanged. The literal is projected 
directly beneath the aggregate, appended to the
 /// existing project when there is one, because 
[PinotAggregateExchangeNodeInsertRule] and `AggregatePushdownRule`
 /// inline literal arguments only from the immediate project.
 ///
-/// The servers require the data type argument, so the rule is required rather 
than an optimization: `QueryEnvironment`
-/// always applies it, and `skipPlannerRules` cannot turn it off.
+/// The servers require the data type argument of FIRST_WITH_TIME / 
LAST_WITH_TIME, so the rule is required rather than
+/// an optimization: `QueryEnvironment` always applies [#WITHOUT_ANY_VALUE], 
or [#INSTANCE] when the `inferAnyValueType`
+/// query option is true, and `skipPlannerRules` cannot turn it off.
 public class PinotAggregateTypeArgumentRewriteRule extends RelOptRule {
+  private static final Set<ColumnDataType> ANY_VALUE_DATA_TYPES =
+      EnumSet.of(ColumnDataType.INT, ColumnDataType.LONG, 
ColumnDataType.FLOAT, ColumnDataType.DOUBLE,
+          ColumnDataType.BIG_DECIMAL, ColumnDataType.BOOLEAN, 
ColumnDataType.TIMESTAMP, ColumnDataType.STRING,
+          ColumnDataType.BYTES, ColumnDataType.UUID);
+
   public static final PinotAggregateTypeArgumentRewriteRule INSTANCE =
-      new 
PinotAggregateTypeArgumentRewriteRule(PinotRuleUtils.PINOT_REL_FACTORY);
+      new PinotAggregateTypeArgumentRewriteRule(true, 
PinotRuleUtils.PINOT_REL_FACTORY, null);
+  /// Leaves ANY_VALUE without the data type argument.
+  public static final PinotAggregateTypeArgumentRewriteRule WITHOUT_ANY_VALUE =
+      new PinotAggregateTypeArgumentRewriteRule(false, 
PinotRuleUtils.PINOT_REL_FACTORY,
+          "PinotAggregateTypeArgumentRewriteRuleWithoutAnyValue");

Review Comment:
   Minor: the default program now runs `WITHOUT_ANY_VALUE`, but 
`QueryPlannerRuleOptionsTest` (line 101) still disables `INSTANCE.toString()`. 
So that test does not cover the rule that runs by default. It passes only 
because the rule is always added. Use `WITHOUT_ANY_VALUE.toString()`, or test 
both.



-- 
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