From d5b10f8b4ae93cd35038564cd305dca34d5808fd Mon Sep 17 00:00:00 2001 From: Clint Wylie Date: Wed, 12 Jun 2024 21:26:08 -0700 Subject: [PATCH 1/3] fix equality and typed in filter behavior for numeric match values on string columns changes: * EqualityFilter and TypedInfilter numeric match values against string columns will now use StringComparators.NUMERIC instead of converting the numeric values directly to string for pure string equality. This effectively is an implicit cast of the STRING values to the numeric match value type, which is consistent with the casts which are eaten in the SQL layer, as well as classic druid behavior * added tests to cover numeric equality matching. Double match values in particular would fail to match the string values since `1.0` would become `'1.0'` which does not match `'1'`. --- .../druid/query/filter/EqualityFilter.java | 61 ++++++++++++++----- .../druid/query/filter/TypedInFilter.java | 18 ++++-- .../segment/filter/EqualityFilterTests.java | 6 ++ .../druid/segment/filter/InFilterTests.java | 10 +++ 4 files changed, 77 insertions(+), 18 deletions(-) diff --git a/processing/src/main/java/org/apache/druid/query/filter/EqualityFilter.java b/processing/src/main/java/org/apache/druid/query/filter/EqualityFilter.java index 06506c64d1af..cc171ceadc72 100644 --- a/processing/src/main/java/org/apache/druid/query/filter/EqualityFilter.java +++ b/processing/src/main/java/org/apache/druid/query/filter/EqualityFilter.java @@ -35,6 +35,7 @@ import org.apache.druid.query.cache.CacheKeyBuilder; import org.apache.druid.query.filter.vector.VectorValueMatcher; import org.apache.druid.query.filter.vector.VectorValueMatcherColumnProcessorFactory; +import org.apache.druid.query.ordering.StringComparators; import org.apache.druid.segment.BaseDoubleColumnValueSelector; import org.apache.druid.segment.BaseFloatColumnValueSelector; import org.apache.druid.segment.BaseLongColumnValueSelector; @@ -49,6 +50,7 @@ import org.apache.druid.segment.column.ColumnType; import org.apache.druid.segment.column.TypeSignature; import org.apache.druid.segment.column.TypeStrategy; +import org.apache.druid.segment.column.Types; import org.apache.druid.segment.column.ValueType; import org.apache.druid.segment.filter.Filters; import org.apache.druid.segment.filter.PredicateValueMatcherFactory; @@ -244,8 +246,9 @@ public ValueMatcher makeMatcher(ColumnSelectorFactory factory) public VectorValueMatcher makeVectorMatcher(VectorColumnSelectorFactory factory) { final ColumnCapabilities capabilities = factory.getColumnCapabilities(column); - - if (matchValueType.isPrimitive() && (capabilities == null || capabilities.isPrimitive())) { + final boolean primitiveMatch = matchValueType.isPrimitive() && (capabilities == null || capabilities.isPrimitive()); + if (primitiveMatch && useSimpleEquality(capabilities, matchValueType)) { + // if possible, use simplified value matcher instead of predicate return ColumnProcessors.makeVectorProcessor( column, VectorValueMatcherColumnProcessorFactory.instance(), @@ -298,6 +301,20 @@ public Filter rewriteRequiredColumns(Map columnRewrites) ); } + /** + * Can the match value type be cast directly to column type for equality comparison? For non-numeric match types, we + * just use exact string equality regardless of the column type. For numeric match value types against string columns, + * we instead use {@link StringComparators#NUMERIC} for matching equality, which effectively allows implicit casting + * of the string column to the numeric type for classic druid style behavior. + */ + public static boolean useSimpleEquality(TypeSignature columnType, ColumnType matchValueType) + { + if (Types.is(columnType, ValueType.STRING)) { + return !matchValueType.isNumeric(); + } + return true; + } + public static BitmapColumnIndex getEqualityIndex( String column, ExprEval matchValueEval, @@ -311,20 +328,22 @@ public static BitmapColumnIndex getEqualityIndex( return new AllUnknownBitmapColumnIndex(selector); } - final ValueIndexes valueIndexes = indexSupplier.as(ValueIndexes.class); - if (valueIndexes != null) { - // matchValueEval.value() cannot be null here due to check in the constructor - //noinspection DataFlowIssue - return valueIndexes.forValue(matchValueEval.value(), matchValueType); - } - - if (matchValueType.isPrimitive()) { - final StringValueSetIndexes stringValueSetIndexes = indexSupplier.as(StringValueSetIndexes.class); - if (stringValueSetIndexes != null) { + if (useSimpleEquality(selector.getColumnCapabilities(column), matchValueType)) { + final ValueIndexes valueIndexes = indexSupplier.as(ValueIndexes.class); + if (valueIndexes != null) { + // matchValueEval.value() cannot be null here due to check in the constructor + //noinspection DataFlowIssue + return valueIndexes.forValue(matchValueEval.value(), matchValueType); + } + if (matchValueType.isPrimitive()) { + final StringValueSetIndexes stringValueSetIndexes = indexSupplier.as(StringValueSetIndexes.class); + if (stringValueSetIndexes != null) { - return stringValueSetIndexes.forValue(matchValueEval.asString()); + return stringValueSetIndexes.forValue(matchValueEval.asString()); + } } } + // fall back to predicate based index if it is available final DruidPredicateIndexes predicateIndexes = indexSupplier.as(DruidPredicateIndexes.class); if (predicateIndexes != null) { @@ -412,7 +431,17 @@ private Supplier> makeStringPredicateSupplier() if (castForComparison == null) { return DruidObjectPredicate.alwaysFalseWithNullUnknown(); } - return DruidObjectPredicate.equalTo(castForComparison.asString()); + // when matching strings to numeric match values, use numeric comparator to implicitly cast the string to number + if (matchValue.type().isNumeric()) { + return value -> { + if (value == null) { + return DruidPredicateMatch.UNKNOWN; + } + return DruidPredicateMatch.of(StringComparators.NUMERIC.compare(value, castForComparison.asString()) == 0); + }; + } else { + return DruidObjectPredicate.equalTo(castForComparison.asString()); + } }); } @@ -548,6 +577,10 @@ public ColumnType defaultType() @Override public ValueMatcher makeDimensionProcessor(DimensionSelector selector, boolean multiValue) { + // use the predicate matcher when matching numeric values since it uses StringComparators.NUMERIC + if (matchValue.type().isNumeric()) { + return predicateMatcherFactory.makeDimensionProcessor(selector, multiValue); + } final ExprEval castForComparison = ExprEval.castForEqualityComparison(matchValue, ExpressionType.STRING); if (castForComparison == null) { return ValueMatchers.makeAlwaysFalseWithNullUnknownDimensionMatcher(selector, multiValue); diff --git a/processing/src/main/java/org/apache/druid/query/filter/TypedInFilter.java b/processing/src/main/java/org/apache/druid/query/filter/TypedInFilter.java index 63e3fbd45415..54fd41267834 100644 --- a/processing/src/main/java/org/apache/druid/query/filter/TypedInFilter.java +++ b/processing/src/main/java/org/apache/druid/query/filter/TypedInFilter.java @@ -39,6 +39,7 @@ import it.unimi.dsi.fastutil.doubles.DoubleOpenHashSet; import it.unimi.dsi.fastutil.floats.FloatOpenHashSet; import it.unimi.dsi.fastutil.longs.LongOpenHashSet; +import it.unimi.dsi.fastutil.objects.ObjectAVLTreeSet; import it.unimi.dsi.fastutil.objects.ObjectArrays; import org.apache.druid.common.config.NullHandling; import org.apache.druid.error.InvalidInput; @@ -48,6 +49,7 @@ import org.apache.druid.query.cache.CacheKeyBuilder; import org.apache.druid.query.filter.vector.VectorValueMatcher; import org.apache.druid.query.filter.vector.VectorValueMatcherColumnProcessorFactory; +import org.apache.druid.query.ordering.StringComparators; import org.apache.druid.segment.ColumnInspector; import org.apache.druid.segment.ColumnProcessors; import org.apache.druid.segment.ColumnSelectorFactory; @@ -301,9 +303,11 @@ public BitmapColumnIndex getBitmapColumnIndex(ColumnIndexSelector selector) } } - final ValueSetIndexes valueSetIndexes = indexSupplier.as(ValueSetIndexes.class); - if (valueSetIndexes != null) { - return valueSetIndexes.forSortedValues(sortedMatchValues.get(), matchValueType); + if (EqualityFilter.useSimpleEquality(selector.getColumnCapabilities(column), matchValueType)) { + final ValueSetIndexes valueSetIndexes = indexSupplier.as(ValueSetIndexes.class); + if (valueSetIndexes != null) { + return valueSetIndexes.forSortedValues(sortedMatchValues.get(), matchValueType); + } } return Filters.makePredicateIndex( @@ -541,8 +545,14 @@ private static DruidObjectPredicate createStringPredicate( return DruidPredicateMatch.of(index >= 0); }; } + final Set stringSet; + // when matching strings to numeric match values, use numeric comparator to implicitly cast the string to number + if (matchValueType.isNumeric()) { + stringSet = new ObjectAVLTreeSet<>(StringComparators.NUMERIC); + } else { + stringSet = Sets.newHashSetWithExpectedSize(sortedValues.size()); + } // convert set to strings - final Set stringSet = Sets.newHashSetWithExpectedSize(sortedValues.size()); for (Object o : sortedValues) { stringSet.add(Evals.asString(o)); } diff --git a/processing/src/test/java/org/apache/druid/segment/filter/EqualityFilterTests.java b/processing/src/test/java/org/apache/druid/segment/filter/EqualityFilterTests.java index 3d35817531f4..fd87969a042d 100644 --- a/processing/src/test/java/org/apache/druid/segment/filter/EqualityFilterTests.java +++ b/processing/src/test/java/org/apache/druid/segment/filter/EqualityFilterTests.java @@ -108,6 +108,12 @@ public void testSingleValueStringColumnWithoutNulls() NotDimFilter.of(new EqualityFilter("dim0", ColumnType.LONG, 1L, null)), ImmutableList.of("0", "2", "3", "4", "5") ); + + assertFilterMatches(new EqualityFilter("dim0", ColumnType.DOUBLE, 1, null), ImmutableList.of("1")); + assertFilterMatches( + NotDimFilter.of(new EqualityFilter("dim0", ColumnType.DOUBLE, 1, null)), + ImmutableList.of("0", "2", "3", "4", "5") + ); } @Test diff --git a/processing/src/test/java/org/apache/druid/segment/filter/InFilterTests.java b/processing/src/test/java/org/apache/druid/segment/filter/InFilterTests.java index fd8c79096c4a..fa661ce010a3 100644 --- a/processing/src/test/java/org/apache/druid/segment/filter/InFilterTests.java +++ b/processing/src/test/java/org/apache/druid/segment/filter/InFilterTests.java @@ -138,6 +138,16 @@ public void testSingleValueStringColumnWithoutNulls() NotDimFilter.of(inFilter("dim0", ColumnType.STRING, Arrays.asList("e", "x"))), ImmutableList.of("a", "b", "c", "d", "f") ); + + assertTypedFilterMatches( + inFilter("dim1", ColumnType.LONG, Arrays.asList(2L, 10L)), + ImmutableList.of("b", "c") + ); + + assertTypedFilterMatches( + inFilter("dim1", ColumnType.DOUBLE, Arrays.asList(2.0, 10.0)), + ImmutableList.of("b", "c") + ); } @Test public void testSingleValueStringColumnWithNulls() From fb420c80e54641b589e43e6c94d810a6d31cc7b6 Mon Sep 17 00:00:00 2001 From: Clint Wylie Date: Wed, 12 Jun 2024 23:37:57 -0700 Subject: [PATCH 2/3] fix test --- .../druid/segment/filter/InFilterTests.java | 29 ++++++++++++++----- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/processing/src/test/java/org/apache/druid/segment/filter/InFilterTests.java b/processing/src/test/java/org/apache/druid/segment/filter/InFilterTests.java index fa661ce010a3..6f5c4b72eb1a 100644 --- a/processing/src/test/java/org/apache/druid/segment/filter/InFilterTests.java +++ b/processing/src/test/java/org/apache/druid/segment/filter/InFilterTests.java @@ -139,15 +139,28 @@ public void testSingleValueStringColumnWithoutNulls() ImmutableList.of("a", "b", "c", "d", "f") ); - assertTypedFilterMatches( - inFilter("dim1", ColumnType.LONG, Arrays.asList(2L, 10L)), - ImmutableList.of("b", "c") - ); + if (NullHandling.sqlCompatible()) { + assertTypedFilterMatches( + inFilter("dim1", ColumnType.LONG, Arrays.asList(2L, 10L)), + ImmutableList.of("b", "c") + ); - assertTypedFilterMatches( - inFilter("dim1", ColumnType.DOUBLE, Arrays.asList(2.0, 10.0)), - ImmutableList.of("b", "c") - ); + assertTypedFilterMatches( + inFilter("dim1", ColumnType.DOUBLE, Arrays.asList(2.0, 10.0)), + ImmutableList.of("b", "c") + ); + } else { + // in default value mode, we actually end up using a classic InDimFilter, it does not match numbers well + assertTypedFilterMatches( + inFilter("dim1", ColumnType.LONG, Arrays.asList(2L, 10L)), + ImmutableList.of("b", "c") + ); + + assertTypedFilterMatches( + inFilter("dim1", ColumnType.DOUBLE, Arrays.asList(2.0, 10.0)), + ImmutableList.of() + ); + } } @Test public void testSingleValueStringColumnWithNulls() From 9b84c15d439a5c04f22dabf2b254a72bc3a012f8 Mon Sep 17 00:00:00 2001 From: Clint Wylie Date: Thu, 4 Jul 2024 04:56:31 -0700 Subject: [PATCH 3/3] adjust comparisons --- .../druid/query/filter/EqualityFilter.java | 46 +++++++++---- .../druid/query/filter/RangeFilter.java | 3 +- .../druid/query/filter/TypedInFilter.java | 68 +++++++++++++++---- .../index/IndexedUtf8ValueIndexes.java | 2 +- 4 files changed, 87 insertions(+), 32 deletions(-) diff --git a/processing/src/main/java/org/apache/druid/query/filter/EqualityFilter.java b/processing/src/main/java/org/apache/druid/query/filter/EqualityFilter.java index cc171ceadc72..f7b2dd1cdb97 100644 --- a/processing/src/main/java/org/apache/druid/query/filter/EqualityFilter.java +++ b/processing/src/main/java/org/apache/druid/query/filter/EqualityFilter.java @@ -31,11 +31,11 @@ import org.apache.druid.error.InvalidInput; import org.apache.druid.java.util.common.IAE; import org.apache.druid.math.expr.ExprEval; +import org.apache.druid.math.expr.ExprType; import org.apache.druid.math.expr.ExpressionType; import org.apache.druid.query.cache.CacheKeyBuilder; import org.apache.druid.query.filter.vector.VectorValueMatcher; import org.apache.druid.query.filter.vector.VectorValueMatcherColumnProcessorFactory; -import org.apache.druid.query.ordering.StringComparators; import org.apache.druid.segment.BaseDoubleColumnValueSelector; import org.apache.druid.segment.BaseFloatColumnValueSelector; import org.apache.druid.segment.BaseLongColumnValueSelector; @@ -44,6 +44,7 @@ import org.apache.druid.segment.ColumnProcessorFactory; import org.apache.druid.segment.ColumnProcessors; import org.apache.druid.segment.ColumnSelectorFactory; +import org.apache.druid.segment.DimensionHandlerUtils; import org.apache.druid.segment.DimensionSelector; import org.apache.druid.segment.column.ColumnCapabilities; import org.apache.druid.segment.column.ColumnIndexSupplier; @@ -304,8 +305,7 @@ public Filter rewriteRequiredColumns(Map columnRewrites) /** * Can the match value type be cast directly to column type for equality comparison? For non-numeric match types, we * just use exact string equality regardless of the column type. For numeric match value types against string columns, - * we instead use {@link StringComparators#NUMERIC} for matching equality, which effectively allows implicit casting - * of the string column to the numeric type for classic druid style behavior. + * we instead cast the string to the match value type number for matching equality. */ public static boolean useSimpleEquality(TypeSignature columnType, ColumnType matchValueType) { @@ -315,6 +315,7 @@ public static boolean useSimpleEquality(TypeSignature columnType, Col return true; } + @Nullable public static BitmapColumnIndex getEqualityIndex( String column, ExprEval matchValueEval, @@ -427,19 +428,36 @@ public DruidObjectPredicate makeObjectPredicate() private Supplier> makeStringPredicateSupplier() { return Suppliers.memoize(() -> { - final ExprEval castForComparison = ExprEval.castForEqualityComparison(matchValue, ExpressionType.STRING); - if (castForComparison == null) { - return DruidObjectPredicate.alwaysFalseWithNullUnknown(); - } // when matching strings to numeric match values, use numeric comparator to implicitly cast the string to number if (matchValue.type().isNumeric()) { - return value -> { - if (value == null) { - return DruidPredicateMatch.UNKNOWN; - } - return DruidPredicateMatch.of(StringComparators.NUMERIC.compare(value, castForComparison.asString()) == 0); - }; + if (matchValue.type().is(ExprType.LONG)) { + return value -> { + if (value == null) { + return DruidPredicateMatch.UNKNOWN; + } + final Long l = DimensionHandlerUtils.convertObjectToLong(value); + if (l == null) { + return DruidPredicateMatch.FALSE; + } + return DruidPredicateMatch.of(matchValue.asLong() == l); + }; + } else { + return value -> { + if (value == null) { + return DruidPredicateMatch.UNKNOWN; + } + final Double d = DimensionHandlerUtils.convertObjectToDouble(value); + if (d == null) { + return DruidPredicateMatch.FALSE; + } + return DruidPredicateMatch.of(matchValue.asDouble() == d); + }; + } } else { + final ExprEval castForComparison = ExprEval.castForEqualityComparison(matchValue, ExpressionType.STRING); + if (castForComparison == null) { + return DruidObjectPredicate.alwaysFalseWithNullUnknown(); + } return DruidObjectPredicate.equalTo(castForComparison.asString()); } }); @@ -577,7 +595,7 @@ public ColumnType defaultType() @Override public ValueMatcher makeDimensionProcessor(DimensionSelector selector, boolean multiValue) { - // use the predicate matcher when matching numeric values since it uses StringComparators.NUMERIC + // use the predicate matcher when matching numeric values since it casts the strings to numeric types if (matchValue.type().isNumeric()) { return predicateMatcherFactory.makeDimensionProcessor(selector, multiValue); } diff --git a/processing/src/main/java/org/apache/druid/query/filter/RangeFilter.java b/processing/src/main/java/org/apache/druid/query/filter/RangeFilter.java index 63fc48559acb..527b59122080 100644 --- a/processing/src/main/java/org/apache/druid/query/filter/RangeFilter.java +++ b/processing/src/main/java/org/apache/druid/query/filter/RangeFilter.java @@ -314,8 +314,7 @@ public BitmapColumnIndex getBitmapColumnIndex(ColumnIndexSelector selector) final String upper = hasUpperBound() ? upperEval.asString() : null; return rangeIndexes.forRange(lower, lowerOpen, upper, upperOpen); } - } - if (matchValueType.isNumeric()) { + } else if (matchValueType.isNumeric()) { final NumericRangeIndexes rangeIndexes = indexSupplier.as(NumericRangeIndexes.class); if (rangeIndexes != null) { final Number lower = (Number) lowerEval.value(); diff --git a/processing/src/main/java/org/apache/druid/query/filter/TypedInFilter.java b/processing/src/main/java/org/apache/druid/query/filter/TypedInFilter.java index 54fd41267834..1230b5221115 100644 --- a/processing/src/main/java/org/apache/druid/query/filter/TypedInFilter.java +++ b/processing/src/main/java/org/apache/druid/query/filter/TypedInFilter.java @@ -36,20 +36,24 @@ import com.google.common.collect.TreeRangeSet; import com.google.common.hash.Hasher; import com.google.common.hash.Hashing; +import com.google.common.primitives.Doubles; import it.unimi.dsi.fastutil.doubles.DoubleOpenHashSet; +import it.unimi.dsi.fastutil.doubles.DoubleSet; import it.unimi.dsi.fastutil.floats.FloatOpenHashSet; import it.unimi.dsi.fastutil.longs.LongOpenHashSet; -import it.unimi.dsi.fastutil.objects.ObjectAVLTreeSet; +import it.unimi.dsi.fastutil.longs.LongSet; import it.unimi.dsi.fastutil.objects.ObjectArrays; import org.apache.druid.common.config.NullHandling; +import org.apache.druid.common.guava.GuavaUtils; import org.apache.druid.error.InvalidInput; import org.apache.druid.java.util.common.IAE; import org.apache.druid.java.util.common.StringUtils; import org.apache.druid.math.expr.Evals; +import org.apache.druid.math.expr.ExprEval; +import org.apache.druid.math.expr.ExpressionType; import org.apache.druid.query.cache.CacheKeyBuilder; import org.apache.druid.query.filter.vector.VectorValueMatcher; import org.apache.druid.query.filter.vector.VectorValueMatcherColumnProcessorFactory; -import org.apache.druid.query.ordering.StringComparators; import org.apache.druid.segment.ColumnInspector; import org.apache.druid.segment.ColumnProcessors; import org.apache.druid.segment.ColumnSelectorFactory; @@ -456,20 +460,20 @@ private static boolean checkSorted(List unsortedValues, ColumnType matchValue } @Nullable - private static Object coerceValue(@Nullable Object o, ColumnType matchValueType) + private static T coerceValue(@Nullable Object o, ColumnType matchValueType) { if (o == null) { return null; } switch (matchValueType.getType()) { case STRING: - return DimensionHandlerUtils.convertObjectToString(o); + return (T) DimensionHandlerUtils.convertObjectToString(o); case LONG: - return DimensionHandlerUtils.convertObjectToLong(o); + return (T) DimensionHandlerUtils.convertObjectToLong(o); case FLOAT: - return DimensionHandlerUtils.convertObjectToFloat(o); + return (T) DimensionHandlerUtils.convertObjectToFloat(o); case DOUBLE: - return DimensionHandlerUtils.convertObjectToDouble(o); + return (T) DimensionHandlerUtils.convertObjectToDouble(o); default: throw InvalidInput.exception("Unsupported matchValueType[%s]", matchValueType); } @@ -544,17 +548,51 @@ private static DruidObjectPredicate createStringPredicate( final int index = Collections.binarySearch(sortedValues, value, comparator); return DruidPredicateMatch.of(index >= 0); }; + } else if (matchValueType.is(ValueType.LONG)) { + final LongSet valueSet = new LongOpenHashSet(sortedValues.size()); + for (Object o : sortedValues) { + final Long l = DimensionHandlerUtils.convertObjectToLong(o); + if (l != null) { + valueSet.add(l.longValue()); + } + } + return value -> { + if (value == null) { + return containsNull ? DruidPredicateMatch.TRUE : DruidPredicateMatch.UNKNOWN; + } + final Long castValue = GuavaUtils.tryParseLong(value); + if (castValue == null) { + return DruidPredicateMatch.FALSE; + } + return DruidPredicateMatch.of(valueSet.contains(castValue)); + }; + } else if (matchValueType.isNumeric()) { + // double or float + final DoubleSet valueSet = new DoubleOpenHashSet(sortedValues.size()); + for (Object o : sortedValues) { + Double d = DimensionHandlerUtils.convertObjectToDouble(o); + if (d != null) { + valueSet.add(d.doubleValue()); + } + } + return value -> { + if (value == null) { + return containsNull ? DruidPredicateMatch.TRUE : DruidPredicateMatch.UNKNOWN; + } + + final Double d = Doubles.tryParse(value); + if (d == null) { + return DruidPredicateMatch.FALSE; + } + return DruidPredicateMatch.of(valueSet.contains(d)); + }; } - final Set stringSet; - // when matching strings to numeric match values, use numeric comparator to implicitly cast the string to number - if (matchValueType.isNumeric()) { - stringSet = new ObjectAVLTreeSet<>(StringComparators.NUMERIC); - } else { - stringSet = Sets.newHashSetWithExpectedSize(sortedValues.size()); - } + // convert set to strings + final ExpressionType matchExpressionType = ExpressionType.fromColumnTypeStrict(matchValueType); + final Set stringSet = Sets.newHashSetWithExpectedSize(sortedValues.size()); for (Object o : sortedValues) { - stringSet.add(Evals.asString(o)); + stringSet.add(ExprEval.ofType(matchExpressionType, o).castTo(ExpressionType.STRING).asString()); } return value -> { if (value == null) { diff --git a/processing/src/main/java/org/apache/druid/segment/index/IndexedUtf8ValueIndexes.java b/processing/src/main/java/org/apache/druid/segment/index/IndexedUtf8ValueIndexes.java index 65395d148b26..6015088d558d 100644 --- a/processing/src/main/java/org/apache/druid/segment/index/IndexedUtf8ValueIndexes.java +++ b/processing/src/main/java/org/apache/druid/segment/index/IndexedUtf8ValueIndexes.java @@ -230,7 +230,7 @@ public BitmapColumnIndex forSortedValues(@Nonnull List sortedValues, TypeSign final Object minValueInColumn = dictionary.get(0); final int position = Collections.binarySearch( sortedValues, - StringUtils.fromUtf8((ByteBuffer) minValueInColumn), + StringUtils.fromUtf8Nullable((ByteBuffer) minValueInColumn), matchValueType.getNullableStrategy() ); tailSet = baseSet.subList(position >= 0 ? position : -(position + 1), baseSet.size());