This is an automated email from the ASF dual-hosted git repository.
gianm pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/druid.git
The following commit(s) were added to refs/heads/master by this push:
new f580b2c9735 fix: Cast for comparison for LONG + IN. (#19834)
f580b2c9735 is described below
commit f580b2c97354e5940920d6c7dff3aa6d7c76d9e4
Author: Gian Merlino <[email protected]>
AuthorDate: Thu Jul 30 21:38:56 2026 -0700
fix: Cast for comparison for LONG + IN. (#19834)
This patch fixes two places where plain coercion was used rather than
cast-for-comparison:
1) TypedInFilter, when creating a predicate for a LONG column against
non-LONG user values.
2) ScalarLongColumnAndIndexSupplier, when creating ValueIndexes against
non-LONG user values.
Prior to this patch, for example, the user value 10.5 would match the
stored value 10.
---
.../apache/druid/query/filter/TypedInFilter.java | 16 ++-
.../nested/ScalarLongColumnAndIndexSupplier.java | 28 +++--
.../apache/druid/segment/filter/InFilterTests.java | 115 +++++++++++++++++++++
3 files changed, 147 insertions(+), 12 deletions(-)
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 8bcd3e7075f..d7d516cf5a8 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
@@ -621,12 +621,18 @@ public class TypedInFilter extends
AbstractOptimizableDimFilter implements Filte
}
};
}
- // convert set to longs
- LongOpenHashSet longs = new LongOpenHashSet();
+ // Convert set to longs, dropping any values which cannot be equal to a
long.
+ final ExpressionType matchExpressionType =
ExpressionType.fromColumnTypeStrict(matchValueType);
+ final LongOpenHashSet longs = new LongOpenHashSet(sortedValues.size());
for (Object value : sortedValues) {
- final Long longValue = DimensionHandlerUtils.convertObjectToLong(value);
- if (longValue != null) {
- longs.add(longValue.longValue());
+ if (value == null) {
+ // Nulls are handled by matchNulls.
+ continue;
+ }
+ final ExprEval<?> castForComparison =
+
ExprEval.castForEqualityComparison(ExprEval.ofType(matchExpressionType, value),
ExpressionType.LONG);
+ if (castForComparison != null) {
+ longs.add(castForComparison.asLong());
}
}
return new DruidLongPredicate()
diff --git
a/processing/src/main/java/org/apache/druid/segment/nested/ScalarLongColumnAndIndexSupplier.java
b/processing/src/main/java/org/apache/druid/segment/nested/ScalarLongColumnAndIndexSupplier.java
index 0818354ed5b..f648ea2c626 100644
---
a/processing/src/main/java/org/apache/druid/segment/nested/ScalarLongColumnAndIndexSupplier.java
+++
b/processing/src/main/java/org/apache/druid/segment/nested/ScalarLongColumnAndIndexSupplier.java
@@ -21,7 +21,6 @@ package org.apache.druid.segment.nested;
import com.google.common.base.Supplier;
import com.google.common.collect.ImmutableList;
-import com.google.common.collect.Iterables;
import it.unimi.dsi.fastutil.ints.IntIntPair;
import it.unimi.dsi.fastutil.ints.IntIterator;
import it.unimi.dsi.fastutil.longs.LongArraySet;
@@ -79,6 +78,7 @@ import javax.annotation.Nullable;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
+import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
@@ -349,14 +349,28 @@ public class ScalarLongColumnAndIndexSupplier implements
Supplier<NestedCommonFo
unknownsIndex
);
} else {
- // values in set are not sorted in double order, transform them on the
fly and iterate them all
+ // The values in the set are not sorted in long order, so we need to
coerce them to longs and iterate them all.
+ // Values which cannot be equal to a long are dropped instead of being
truncated into a value that would match
+ // incorrectly.
+ final ExpressionType matchExpressionType =
ExpressionType.fromColumnTypeStrict(matchValueType);
+ final List<Long> longs = new ArrayList<>(sortedValues.size());
+ for (Object value : sortedValues) {
+ if (value == null) {
+ longs.add(null);
+ continue;
+ }
+ final ExprEval<?> castForComparison =
ExprEval.castForEqualityComparison(
+ ExprEval.ofType(matchExpressionType, value),
+ ExpressionType.LONG
+ );
+ if (castForComparison != null) {
+ longs.add(castForComparison.asLong());
+ }
+ }
return ValueSetIndexes.buildBitmapColumnIndexFromIteratorBinarySearch(
bitmapFactory,
- Iterables.transform(
- sortedValues,
- DimensionHandlerUtils::convertObjectToLong
- ),
- sortedValues.size(),
+ longs,
+ longs.size(),
dictionary,
valueIndexes,
unknownsIndex
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 31d87502363..e00b8953d3c 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
@@ -357,6 +357,121 @@ public class InFilterTests
);
}
+ @Test
+ public void testNumericMatchValuesAreNotTruncatedWhenMatchingLongs()
+ {
+ // Match values with a fractional part must not be truncated into a
value that matches a stored long.
+ // l0 is [0, 100, 40, null, 9001, 12345].
+ assertTypedFilterMatches(
+ inFilter("l0", ColumnType.DOUBLE, Collections.singletonList(0.5)),
+ ImmutableList.of()
+ );
+ assertTypedFilterMatches(
+ inFilter("l0", ColumnType.DOUBLE, Collections.singletonList(-0.5)),
+ ImmutableList.of()
+ );
+ assertTypedFilterMatches(
+ inFilter("l0", ColumnType.DOUBLE, Collections.singletonList(100.5)),
+ ImmutableList.of()
+ );
+ assertTypedFilterMatches(
+ inFilter("l0", ColumnType.DOUBLE, Arrays.asList(40.5, 9001.5,
12345.5)),
+ ImmutableList.of()
+ );
+ assertTypedFilterMatches(
+ NotDimFilter.of(inFilter("l0", ColumnType.DOUBLE,
Collections.singletonList(100.5))),
+ ImmutableList.of("a", "b", "c", "e", "f")
+ );
+
+ // Same as above, but with FLOAT.
+ assertTypedFilterMatches(
+ inFilter("l0", ColumnType.FLOAT, Collections.singletonList(0.5f)),
+ ImmutableList.of()
+ );
+ assertTypedFilterMatches(
+ inFilter("l0", ColumnType.FLOAT, Collections.singletonList(-0.5f)),
+ ImmutableList.of()
+ );
+ assertTypedFilterMatches(
+ inFilter("l0", ColumnType.FLOAT, Collections.singletonList(100.5f)),
+ ImmutableList.of()
+ );
+ assertTypedFilterMatches(
+ inFilter("l0", ColumnType.FLOAT, Arrays.asList(40.5f, 9001.5f,
12345.5f)),
+ ImmutableList.of()
+ );
+ assertTypedFilterMatches(
+ NotDimFilter.of(inFilter("l0", ColumnType.FLOAT,
Collections.singletonList(100.5f))),
+ ImmutableList.of("a", "b", "c", "e", "f")
+ );
+
+ // Same as above, but with STRING.
+ assertTypedFilterMatches(
+ inFilter("l0", ColumnType.STRING, Collections.singletonList("0.5")),
+ ImmutableList.of()
+ );
+ assertTypedFilterMatches(
+ inFilter("l0", ColumnType.STRING, Collections.singletonList("-0.5")),
+ ImmutableList.of()
+ );
+ assertTypedFilterMatches(
+ inFilter("l0", ColumnType.STRING,
Collections.singletonList("100.5")),
+ ImmutableList.of()
+ );
+ assertTypedFilterMatches(
+ inFilter("l0", ColumnType.STRING, Arrays.asList("12345.5", "40.5",
"9001.5")),
+ ImmutableList.of()
+ );
+ assertTypedFilterMatches(
+ NotDimFilter.of(inFilter("l0", ColumnType.STRING,
Collections.singletonList("100.5"))),
+ ImmutableList.of("a", "b", "c", "e", "f")
+ );
+
+ // Exactly representable values still match, and are not thrown out
along with their fractional neighbors.
+ assertTypedFilterMatches(
+ inFilter("l0", ColumnType.DOUBLE, Arrays.asList(100.0, 9001.5)),
+ ImmutableList.of("b")
+ );
+ assertTypedFilterMatches(
+ inFilter("l0", ColumnType.DOUBLE, Arrays.asList(100.5, 9001.0)),
+ ImmutableList.of("e")
+ );
+ assertTypedFilterMatches(
+ NotDimFilter.of(inFilter("l0", ColumnType.DOUBLE,
Arrays.asList(100.5, 9001.0))),
+ ImmutableList.of("a", "b", "c", "f")
+ );
+
+ // Nulls are still matched too.
+ assertTypedFilterMatches(
+ inFilter("l0", ColumnType.DOUBLE, Arrays.asList(null, 100.5)),
+ ImmutableList.of("d")
+ );
+
+ // Same story for FLOAT match values.
+ assertTypedFilterMatches(
+ inFilter("l0", ColumnType.FLOAT, Collections.singletonList(100.5f)),
+ ImmutableList.of()
+ );
+ assertTypedFilterMatches(
+ inFilter("l0", ColumnType.FLOAT, Arrays.asList(0.5f, 9001.0f)),
+ ImmutableList.of("e")
+ );
+ assertTypedFilterMatches(
+ inFilter("l0", ColumnType.FLOAT, Arrays.asList(null, 100.5f)),
+ ImmutableList.of("d")
+ );
+
+ // And for STRING match values that spell out a non-integral number.
+ assertTypedFilterMatches(
+ inFilter("l0", ColumnType.STRING,
Collections.singletonList("100.5")),
+ ImmutableList.of()
+ );
+ assertTypedFilterMatches(
+ inFilter("l0", ColumnType.STRING, Arrays.asList("100.5", "9001.0")),
+ ImmutableList.of("e")
+ );
+ }
+
@Test
public void testLegacyNumericDefaults()
{
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]