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


##########
pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/operator/operands/FilterOperand.java:
##########
@@ -132,12 +133,21 @@ public Integer apply(List<Object> row) {
         if (result == null) {
           return null;
         }
-        if (firstResult.equals(result)) {
+        if (valuesEqual(firstResult, result)) {
           return _isNotIn ? 0 : 1;
         }
       }
       return _isNotIn ? 1 : 0;
     }
+
+    /// Compares `BIG_DECIMAL` values by number, like a comparison does and 
like the single-stage engine does for IN.
+    /// `BigDecimal.equals()` also compares the scale, so 1.0 (e.g. a SUM, 
which is a DOUBLE) does not equal 1.
+    private static boolean valuesEqual(Object value1, Object value2) {

Review Comment:
   Good point, done. `valuesEqual` now compares two numbers by value when their 
classes differ or when one of them is a BigDecimal:
   - as BigDecimal values when one side is a BigDecimal (NaN and infinity never 
match),
   - as doubles when one side is a FLOAT or DOUBLE,
   - as longs otherwise.
   
   Two values of the same class (other than BigDecimal) still use `equals`, so 
the common path does not change. New `FilterOperatorTest` cases cover a 
BigDecimal with another scale, a BigDecimal against a LONG, and `NOT IN` with 
INT and DOUBLE values against a LONG.
   



##########
pinot-segment-spi/src/main/java/org/apache/pinot/segment/spi/AggregationFunctionType.java:
##########
@@ -456,6 +465,101 @@ public RelDataType inferReturnType(SqlOperatorBinding 
opBinding) {
     }
   }
 
+  /// Casts the value of `ARRAY_AGG(value, 'dataType'[, isDistinct])` to the 
data type, so that the return type that
+  /// [ArrayOfComponentReturnTypeInference] infers from the value is the type 
of the returned array. The function picks
+  /// its accumulator by the data type, not by the type of the value: for 
example, `ARRAY_AGG(intCol, 'LONG')` returns
+  /// longs. The cast changes the operand instead of the return type 
inference, because Calcite infers the type of an
+  /// aggregate call again after the validation (e.g. 
`Aggregate.typeMatchesInferred()`), and it cannot read the value
+  /// of a literal operand then.
+  private static class ArrayAggOperandTypeChecker implements 
SqlOperandTypeChecker {
+    @Override
+    public boolean checkOperandTypes(SqlCallBinding callBinding, boolean 
throwOnFailure) {
+      if (callBinding.getOperandCount() < 2 || 
!callBinding.isOperandLiteral(1, false)
+          || !SqlTypeUtil.inCharFamily(callBinding.getOperandType(1))) {
+        // The function fails at runtime without a data type
+        return true;
+      }
+      String dataTypeName = callBinding.getOperandLiteralValue(1, 
String.class);
+      if (dataTypeName == null) {
+        return true;
+      }
+      RelDataTypeFactory typeFactory = callBinding.getTypeFactory();
+      RelDataType dataType = toRelDataType(dataTypeName, typeFactory);
+      RelDataType valueType = callBinding.getOperandType(0);
+      // Keep multi-value columns, for which the return type inference uses 
the type of the elements
+      if (dataType == null || valueType.getComponentType() != null || 
!needsCast(valueType, dataType)) {
+        return true;
+      }
+      RelDataType castType = typeFactory.createTypeWithNullability(dataType, 
valueType.isNullable());
+      SqlCall call = callBinding.getCall();
+      SqlNode cast = SqlStdOperatorTable.CAST.createCall(SqlParserPos.ZERO, 
call.operand(0),
+          
SqlTypeUtil.convertTypeToSpec(castType).withNullable(castType.isNullable()));
+      call.setOperand(0, cast);
+      callBinding.getValidator().setValidatedNodeType(cast, castType);
+      return true;
+    }
+
+    /// Returns the SQL type of a Pinot data type name, or `null` when it is 
not a data type that ARRAY_AGG supports.
+    @Nullable
+    private static RelDataType toRelDataType(String dataTypeName, 
RelDataTypeFactory typeFactory) {
+      switch (dataTypeName.toUpperCase()) {
+        case "INT":
+          return typeFactory.createSqlType(SqlTypeName.INTEGER);
+        case "LONG":
+          return typeFactory.createSqlType(SqlTypeName.BIGINT);
+        case "FLOAT":
+          return typeFactory.createSqlType(SqlTypeName.REAL);
+        case "DOUBLE":
+          return typeFactory.createSqlType(SqlTypeName.DOUBLE);
+        case "BIG_DECIMAL":
+          return typeFactory.createSqlType(SqlTypeName.DECIMAL);

Review Comment:
   Good catch, done. For `'BIG_DECIMAL'`, the checker now casts a DECIMAL value 
only when the planner gives it INT or LONG (scale 0 and up to 18 digits). The 
other DECIMAL types plan as arrays (`DOUBLE_ARRAY`) that the BIG_DECIMAL result 
converts to, so they keep their type and get no cast.
   
   When a cast is necessary, I kept DECIMAL(2000, 1000). It is Pinot's 
BIG_DECIMAL type (the type of a BIG_DECIMAL column, and of 
`ColumnDataType.BIG_DECIMAL.toType()`), so a BIG_DECIMAL column gets no cast. 
The runtime CAST only gets the name `BIG_DECIMAL`, so the scale does not change 
any value.
   
   A new plan test shows no cast for a BIG_DECIMAL column and for a DECIMAL(20, 
0) value, and a cast for a DECIMAL(5, 0) value. A runtime test checks the 
values.
   



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