diff --git a/docs/changelog/125832.yaml b/docs/changelog/125832.yaml new file mode 100644 index 0000000000000..4877a02e9e6d0 --- /dev/null +++ b/docs/changelog/125832.yaml @@ -0,0 +1,6 @@ +pr: 125832 +summary: "ESQL: Fix `NULL` handling in `IN` clause" +area: ES|QL +type: bug +issues: + - 119950 diff --git a/x-pack/plugin/esql/qa/testFixtures/src/main/resources/null.csv-spec b/x-pack/plugin/esql/qa/testFixtures/src/main/resources/null.csv-spec index 2be656020811f..ccf8dde563f37 100644 --- a/x-pack/plugin/esql/qa/testFixtures/src/main/resources/null.csv-spec +++ b/x-pack/plugin/esql/qa/testFixtures/src/main/resources/null.csv-spec @@ -203,3 +203,16 @@ FROM employees fullname:keyword | job_positions:keyword | salary:integer | salary_change:double ; + +inConvertedNull +required_capability: filter_in_converted_null +FROM employees +| WHERE emp_no in (10021, 10022, null::int) +| KEEP emp_no, first_name, last_name +| SORT emp_no +; + +emp_no:integer | first_name:keyword | last_name:keyword +10021 | Ramzi | Erde +10022 | Shahaf | Famili +; diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/action/EsqlCapabilities.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/action/EsqlCapabilities.java index 2ece0fdc92d11..406a7f5f6f08d 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/action/EsqlCapabilities.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/action/EsqlCapabilities.java @@ -954,6 +954,12 @@ public enum Cap { */ FIX_REPLACE_MISSING_FIELD_WITH_NULL_DUPLICATE_NAME_ID_IN_LAYOUT, + /** + * Support for filter in converted null. + * See ESQL: Fix `NULL` handling in `IN` clause #125832 + */ + FILTER_IN_CONVERTED_NULL, + /** * When creating constant null blocks in {@link org.elasticsearch.compute.lucene.ValuesSourceReaderOperator}, we also handed off * the ownership of that block - but didn't account for the fact that the caller might close it, leading to double releases diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/predicate/operator/comparison/In.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/predicate/operator/comparison/In.java index 6bd57a44c6131..5100da1dfa0b5 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/predicate/operator/comparison/In.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/predicate/operator/comparison/In.java @@ -478,7 +478,7 @@ private Query translate(TranslatorHandler handler) { List queries = new ArrayList<>(); for (Expression rhs : list()) { - if (DataType.isNull(rhs.dataType()) == false) { + if (Expressions.isGuaranteedNull(rhs) == false) { if (needsTypeSpecificValueHandling(attribute.dataType())) { // delegates to BinaryComparisons translator to ensure consistent handling of date and time values // TODO: diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/predicate/operator/comparison/InTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/predicate/operator/comparison/InTests.java index c286b2a1109cb..e26b6ae616865 100644 --- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/predicate/operator/comparison/InTests.java +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/predicate/operator/comparison/InTests.java @@ -13,12 +13,16 @@ import org.elasticsearch.geo.GeometryTestUtils; import org.elasticsearch.geo.ShapeTestUtils; import org.elasticsearch.xpack.esql.core.expression.Expression; +import org.elasticsearch.xpack.esql.core.expression.FieldAttribute; import org.elasticsearch.xpack.esql.core.expression.FoldContext; import org.elasticsearch.xpack.esql.core.expression.Literal; +import org.elasticsearch.xpack.esql.core.querydsl.query.TermsQuery; import org.elasticsearch.xpack.esql.core.tree.Source; import org.elasticsearch.xpack.esql.core.type.DataType; +import org.elasticsearch.xpack.esql.core.type.EsField; import org.elasticsearch.xpack.esql.expression.function.AbstractFunctionTestCase; import org.elasticsearch.xpack.esql.expression.function.TestCaseSupplier; +import org.elasticsearch.xpack.esql.planner.TranslatorHandler; import org.junit.AfterClass; import java.io.IOException; @@ -26,6 +30,8 @@ import java.util.Arrays; import java.util.List; import java.util.Locale; +import java.util.Map; +import java.util.Set; import java.util.function.Supplier; import java.util.stream.IntStream; @@ -81,6 +87,16 @@ private static Literal L(Object value) { return of(EMPTY, value); } + public void testConvertedNull() { + In in = new In( + EMPTY, + new FieldAttribute(Source.EMPTY, "field", new EsField("suffix", DataType.KEYWORD, Map.of(), true)), + Arrays.asList(ONE, new Literal(Source.EMPTY, null, randomFrom(DataType.types())), THREE) + ); + var query = in.asQuery(TranslatorHandler.TRANSLATOR_HANDLER); + assertEquals(new TermsQuery(EMPTY, "field", Set.of(1, 3)), query); + } + @ParametersFactory public static Iterable parameters() { List suppliers = new ArrayList<>();