From c54d941b8df7c935a184be0517348267df87a1e5 Mon Sep 17 00:00:00 2001 From: waterWang Date: Mon, 3 Aug 2026 04:01:24 +0800 Subject: [PATCH] fix: add column-type precondition check in isFitForNonScanBasedPlan for MINLONG/MAXLONG/MINSTRING/MAXSTRING (#19145) The non-scan (metadata/dictionary based) aggregation path treats MINLONG, MAXLONG, MINSTRING, and MAXSTRING as resolvable if the column merely has a dictionary, without checking the column type is one the function actually supports. This causes: - MINLONG/MAXLONG over FLOAT/DOUBLE/BIG_DECIMAL: IllegalArgumentException (the non-scan path is too strict) - MINSTRING/MAXSTRING over numeric columns: silently returns wrong result from dictionary (the non-scan path is too lax) Fix: Add type precondition checks in isFitForNonScanBasedPlan() so unsupported combinations fall back to the scan path: - MINLONG/MAXLONG: require stored type INT or LONG - MINSTRING/MAXSTRING: require stored type STRING --- .../pinot/core/plan/AggregationPlanNode.java | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/pinot-core/src/main/java/org/apache/pinot/core/plan/AggregationPlanNode.java b/pinot-core/src/main/java/org/apache/pinot/core/plan/AggregationPlanNode.java index e73e1b20c2e2..85c5dfeb52b8 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/plan/AggregationPlanNode.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/plan/AggregationPlanNode.java @@ -38,6 +38,7 @@ import org.apache.pinot.segment.spi.SegmentContext; import org.apache.pinot.segment.spi.datasource.DataSource; import org.apache.pinot.segment.spi.index.reader.NullValueVectorReader; +import org.apache.pinot.spi.data.FieldSpec; import static org.apache.pinot.segment.spi.AggregationFunctionType.*; @@ -179,6 +180,25 @@ private boolean isFitForNonScanBasedPlan() { } DataSource dataSource = _indexSegment.getDataSource(argument.getIdentifier(), _queryContext.getSchema()); if (DICTIONARY_BASED_FUNCTIONS.contains(aggregationFunction.getType())) { + // MINLONG/MAXLONG can only be resolved from dictionary for INT/LONG columns, because the resolver + // (getMinValueLong/getMaxValueLong) requires an integer stored type. + AggregationFunctionType functionType = aggregationFunction.getType(); + if (functionType == MINLONG || functionType == MAXLONG) { + FieldSpec.DataType storedType = + dataSource.getDataSourceMetadata().getDataType().getStoredType(); + if (storedType != FieldSpec.DataType.INT && storedType != FieldSpec.DataType.LONG) { + return false; + } + } + // MINSTRING/MAXSTRING can only be resolved from dictionary for STRING columns, because the + // function rejects numeric columns on the scan path. + if (functionType == MINSTRING || functionType == MAXSTRING) { + FieldSpec.DataType storedType = + dataSource.getDataSourceMetadata().getDataType().getStoredType(); + if (storedType != FieldSpec.DataType.STRING) { + return false; + } + } if (dataSource.getDictionary() != null) { continue; }