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]

Reply via email to