xiangfu0 commented on code in PR #19101:
URL: https://github.com/apache/pinot/pull/19101#discussion_r3920906239


##########
pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/operator/operands/FilterOperand.java:
##########
@@ -193,7 +197,12 @@ public Predicate(List<RexExpression> operands, DataSchema 
dataSchema, IntPredica
 
       ColumnDataType lhsType = _lhs.getResultType();
       ColumnDataType rhsType = _rhs.getResultType();
-      if (lhsType == rhsType) {
+      // Reject raw VARIANT operands only; other non-orderable types (OBJECT, 
arrays, MAP) keep their existing
+      // best-effort comparison behavior. VARIANT is opaque because its PVAR 
byte encoding is not a canonical
+      // semantic ordering, so a comparison must extract a typed scalar first.
+      Preconditions.checkArgument(lhsType != ColumnDataType.VARIANT && rhsType 
!= ColumnDataType.VARIANT,
+          "Raw VARIANT values do not support comparison; extract a typed path 
with variantGet first");
+      if (lhsType == ColumnDataType.UNKNOWN || rhsType == 
ColumnDataType.UNKNOWN || lhsType == rhsType) {

Review Comment:
   Kept the SQL-null short-circuit in both engines and added an explicit 
compatibility note in the PR description. The existing left/right NULL-literal 
regressions remain part of verification.



##########
pinot-segment-local/src/main/java/org/apache/pinot/segment/local/segment/creator/impl/ColumnarValueNormalizer.java:
##########
@@ -28,21 +28,21 @@
 /// (`buildColumnar`) path.
 ///
 /// The row-major build runs each record through a `TransformPipeline` whose
-/// `NullValueTransformer` substitutes [FieldSpec#getDefaultNullValue()] for 
`null`
-/// and whose `DataTypeTransformer` coerces every value to the column's stored 
type (e.g.
+/// `DataTypeTransformer` coerces every non-null value to the column's stored 
type (e.g.
 /// `Boolean` → `Integer` for a `BOOLEAN` column stored as `INT`,
-/// `Timestamp` → `Long` for `TIMESTAMP`). The column-major driver 
deliberately runs
-/// with no transform pipeline, so a non-segment source (e.g. Arrow) delivers 
values in the source's
+/// `Timestamp` → `Long` for `TIMESTAMP`) and whose `NullValueTransformer` 
substitutes
+/// [FieldSpec#getDefaultNullValue()] for values that remain `null`. The 
column-major driver deliberately runs with
+/// no transform pipeline, so a non-segment source (e.g. Arrow) delivers 
values in the source's
 /// logical type with raw `null`s — which the typed collectors / index 
creators do not accept.
 ///
 /// This helper applies the equivalent of those two transformers to one value, 
in the same order:
 ///
-/// 1. `NullValueTransformer`: a `null` value becomes the column default — the 
scalar
-///       default for single-value columns, or a one-element `Object[]` of 
that scalar for
-///       multi-value columns (matching 
`NullValueTransformerUtils.getDefaultNullValue`).
-/// 2. `DataTypeTransformer`: [DataTypeTransformerUtils#transformValue] 
standardizes the
+/// 1. `DataTypeTransformer`: [DataTypeTransformerUtils#transformValue] 
standardizes the

Review Comment:
   Documented the columnar normalization ordering change and its shared-path 
impact in the PR description. The implementation now matches the row pipeline.



##########
pinot-segment-local/src/test/java/org/apache/pinot/segment/local/segment/creator/impl/ColumnarValueNormalizerTest.java:
##########
@@ -0,0 +1,43 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.pinot.segment.local.segment.creator.impl;
+
+import org.apache.pinot.spi.data.DimensionFieldSpec;
+import org.apache.pinot.spi.data.FieldSpec;
+import org.apache.pinot.spi.data.FieldSpec.DataType;
+import org.apache.pinot.spi.utils.PinotDataType;
+import org.testng.annotations.Test;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertSame;
+
+
+public class ColumnarValueNormalizerTest {
+  private static final String COLUMN = "payload";
+
+  @Test
+  public void testNullVariantReturnsDefaultSentinelWithoutDecoding() {

Review Comment:
   Expanded `ColumnarValueNormalizerTest` to pin BOOLEAN as `Integer 0`, 
TIMESTAMP as `Long 0`, a one-element MV default array, and the Variant sentinel.



##########
pinot-segment-local/src/main/java/org/apache/pinot/segment/local/utils/TableConfigUtils.java:
##########
@@ -1970,7 +2060,7 @@ private static void 
validateStarTreeIndexConfigs(List<StarTreeIndexConfig> starT
       List<String> dimensionsSplitOrder = 
starTreeIndexConfig.getDimensionsSplitOrder();
       assert CollectionUtils.isNotEmpty(dimensionsSplitOrder);
       for (String dimension : dimensionsSplitOrder) {
-        if (timestampIndexColumns.contains(dimension)) {
+        if (timestampIndexColumns.contains(dimension) && 
schema.getFieldSpecFor(dimension) == null) {

Review Comment:
   Reworked this to preserve the unconditional timestamp-derived-name skip, 
avoiding schema-provenance-dependent validation. A schema-declared collision is 
checked only to reject VARIANT; declared non-Variant and generated-column cases 
stay valid. Both paths are tested, and the change is isolated in `07d3dea1da`.



##########
pinot-common/pom.xml:
##########
@@ -200,6 +200,18 @@
       <groupId>org.apache.pinot</groupId>
       <artifactId>pinot-timeseries-spi</artifactId>
     </dependency>
+    <dependency>
+      <groupId>org.apache.parquet</groupId>
+      <artifactId>parquet-variant</artifactId>

Review Comment:
   Removed the unsound parquet-column exclusion, excluded unused 
parquet-jackson, and added the requested global-shade / apache/pinot#18459 
note. `mvn -pl pinot-common dependency:tree -Dincludes=org.apache.parquet` now 
shows parquet-variant -> parquet-common/format-structures plus 
parquet-column/encoding, with no parquet-jackson.



##########
pinot-query-planner/src/main/java/org/apache/pinot/calcite/sql/fun/PinotOperatorTable.java:
##########
@@ -470,6 +472,11 @@ public void lookupOperatorOverloads(SqlIdentifier opName, 
@Nullable SqlFunctionC
     if (!opName.isSimple()) {
       return;
     }
+    if (!_nullHandlingEnabled && 
TransformFunctionType.requiresNullHandling(opName.getSimple())) {

Review Comment:
   The operator table now canonicalizes once, returns on map miss, and consults 
a constructor-cached empty-or-canonical gated-name set only for Pinot operator 
hits. The user error is a `QueryException` with `QUERY_VALIDATION`, matching 
broker/result validation.



##########
pinot-common/src/main/java/org/apache/pinot/common/function/TransformFunctionType.java:
##########
@@ -117,6 +122,17 @@ public enum TransformFunctionType {
   JSON_EXTRACT_KEY("jsonExtractKey", ReturnTypes.TO_ARRAY,
       OperandTypes.family(
           List.of(SqlTypeFamily.CHARACTER, SqlTypeFamily.CHARACTER, 
SqlTypeFamily.CHARACTER), i -> i > 1)),
+  VARIANT_GET("variantGet", 
TransformFunctionType::variantGetReturnTypeInference, 
variantGetOperandTypeChecker()),
+  TRY_VARIANT_GET("tryVariantGet", 
TransformFunctionType::variantGetReturnTypeInference,
+      variantGetOperandTypeChecker()),
+  VARIANT_EXISTS("variantExists", ReturnTypes.BOOLEAN_NULLABLE, 
variantPathOperandTypeChecker()),
+  IS_VARIANT_NULL("isVariantNull", ReturnTypes.BOOLEAN, 
optionalVariantPathOperandTypeChecker()),
+  VARIANT_TYPE_OF("variantTypeOf", ReturnTypes.VARCHAR_2000_NULLABLE, 
optionalVariantPathOperandTypeChecker()),
+  VARIANT_TO_JSON("variantToJson", ReturnTypes.VARCHAR_2000_NULLABLE, 
OperandTypes.ANY),
+  PARSE_JSON_TO_VARIANT("parseJson", 
TransformFunctionType::nullableVariantReturnTypeInference,

Review Comment:
   Made `parseJsonToVariant` / `tryParseJsonToVariant` the primary names and 
retained Spark-compatible `parseJson` aliases. `variantToJson` now uses a 
VARIANT operand checker, so non-Variant arguments fail during planning.



##########
pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/operator/HashJoinOperator.java:
##########
@@ -79,18 +99,55 @@ public HashJoinOperator(OpChainExecutionContext context, 
MultiStageOperator left
     _nullKeyRightRows = needUnmatchedRightRows() ? new ArrayList<>() : null;
   }
 
-  /// Constructor that takes the schema for NonEquiEvaluator as an argument
+  /// Constructor that takes the schema for NonEquiEvaluator as an argument.
+  ///
+  /// <p>For SEMI and ANTI joins whose node does not carry its inputs, the 
result schema contains only left columns, so
+  /// this legacy constructor cannot validate the right key's logical type. 
New callers that need right-side VARIANT
+  /// validation must use the overload that accepts {@code rightSchema}.
   public HashJoinOperator(OpChainExecutionContext context, MultiStageOperator 
leftInput, DataSchema leftSchema,
       MultiStageOperator rightInput, JoinNode node, DataSchema 
nonEquiEvaluationSchema) {
+    this(context, leftInput, leftSchema, rightInput, 
tryInferRightSchema(leftSchema, node), node,
+        nonEquiEvaluationSchema, false);
+  }
+
+  /// Constructor that takes the schema for NonEquiEvaluator as an argument
+  public HashJoinOperator(OpChainExecutionContext context, MultiStageOperator 
leftInput, DataSchema leftSchema,
+      MultiStageOperator rightInput, DataSchema rightSchema, JoinNode node, 
DataSchema nonEquiEvaluationSchema) {
+    this(context, leftInput, leftSchema, rightInput, rightSchema, node, 
nonEquiEvaluationSchema, true);
+  }
+
+  private HashJoinOperator(OpChainExecutionContext context, MultiStageOperator 
leftInput, DataSchema leftSchema,
+      MultiStageOperator rightInput, @Nullable DataSchema rightSchema, 
JoinNode node,
+      DataSchema nonEquiEvaluationSchema, boolean rightSchemaRequired) {
     super(context, leftInput, leftSchema, rightInput, node, 
nonEquiEvaluationSchema);
     List<Integer> leftKeys = node.getLeftKeys();
     Preconditions.checkState(!leftKeys.isEmpty(), "Hash join operator requires 
join keys");
+    Preconditions.checkArgument(!rightSchemaRequired || rightSchema != null, 
"Right input schema must not be null");
+    JoinKeyTypeValidator.validate(node, leftSchema, rightSchema);
     _leftKeySelector = KeySelectorFactory.getKeySelector(leftKeys);
     _rightKeySelector = KeySelectorFactory.getKeySelector(node.getRightKeys());
     _rightTable = createLookupTable(leftKeys, leftSchema);
     _matchedRightRows = needUnmatchedRightRows() ? new HashMap<>() : null;
   }
 
+  @Nullable
+  private static DataSchema tryInferRightSchema(DataSchema leftSchema, 
JoinNode node) {

Review Comment:
   Deprecated both legacy constructors and made the null-right-schema path 
still validate left keys. All in-tree construction now passes the right schema 
explicitly, including SEMI/ANTI joins, and tests cover right-side raw VARIANT 
rejection. The limitation is disclosed in the PR body.



##########
pinot-core/src/main/java/org/apache/pinot/core/plan/DistinctPlanNode.java:
##########
@@ -54,6 +55,18 @@ public DistinctPlanNode(SegmentContext segmentContext, 
QueryContext queryContext
   @Override
   public Operator<DistinctResultsBlock> run() {
     List<ExpressionContext> expressions = _queryContext.getSelectExpressions();
+    for (ExpressionContext expression : expressions) {
+      String column = expression.getIdentifier();
+      if (column != null) {
+        DataType dataType = _indexSegment.getDataSource(column, 
_queryContext.getSchema())

Review Comment:
   Added a one-shot cached `QueryContext.hasVariantColumns()` flag. DISTINCT 
and aggregation identifier validation skip all per-expression data-source 
lookups when the query schema has no VARIANT; focused tests cover both 
fast-skip and validation paths.



##########
compatibility-verifier/compCheck.sh:
##########
@@ -496,14 +498,24 @@ setupControllerVariables
 setupBrokerVariables
 setupServerVariables
 
-export JAVA_OPTS="-DControllerPort=${CONTROLLER_PORT} 
-DBrokerQueryPort=${BROKER_QUERY_PORT} -DServerAdminPort=${SERVER_ADMIN_PORT}"
-
 mkdir ${PID_DIR}
 mkdir ${LOG_DIR}
 
 oldTargetDir="$workingDir"/oldTargetDir
 newTargetDir="$workingDir"/newTargetDir
 
+oldServerSupportsVariant=false
+oldExpressionsProto="${oldTargetDir}/pinot-common/src/main/proto/expressions.proto"
+if [ -f "${oldExpressionsProto}" ] \
+    && grep -Eq '^[[:space:]]*VARIANT[[:space:]]*=[[:space:]]*24[[:space:]]*;' 
"${oldExpressionsProto}"; then

Review Comment:
   Replaced source-text grep with capability detection against the built old 
`pinot-common` artifact via `FileContainsOp`; inability to inspect is a hard 
failure rather than a silent skip. Also documented intentional generation reuse 
for the old-broker/new-server phase.



##########
pinot-plugins/pinot-input-format/pinot-parquet/src/main/java/org/apache/pinot/plugin/inputformat/parquet/ParquetNativeRecordReader.java:
##########
@@ -122,9 +159,11 @@ public GenericRow next(GenericRow reuse)
     } catch (Exception e) {

Review Comment:
   Moved the full reader init/rewind/close robustness changes into standalone 
commit `55f1aa6d37` for backportability. `useAvroParquetRecordReader()` now 
retains explicit selected-reader state before init and after close, with a 
dedicated regression 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