Skip to content

Commit deeeadc

Browse files
authored
ESQL: Allow the data type of null in filters (#118324)
* Allow the data type of `null` in filters
1 parent 6f43d89 commit deeeadc

File tree

5 files changed

+66
-6
lines changed

5 files changed

+66
-6
lines changed

docs/changelog/118324.yaml

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
pr: 118324
2+
summary: Allow the data type of `null` in filters
3+
area: ES|QL
4+
type: bug
5+
issues:
6+
- 116351

x-pack/plugin/esql/qa/testFixtures/src/main/resources/null.csv-spec

+12
Original file line numberDiff line numberDiff line change
@@ -191,3 +191,15 @@ emp_no:integer | languages:integer | height:double | x:double | y:double | z:dou
191191
10020 | null | 1.41 | 1.41 | 1.41 | 40031.0 | 40031
192192
10021 | null | 1.47 | 1.47 | 1.47 | 60408.0 | 60408
193193
;
194+
195+
whereNull
196+
FROM employees
197+
| WHERE NULL and emp_no <= 10021
198+
| SORT first_name, last_name
199+
| EVAL fullname = CONCAT(first_name, " ", last_name)
200+
| KEEP fullname, job_positions, salary, salary_change
201+
| limit 5
202+
;
203+
204+
fullname:keyword | job_positions:keyword | salary:integer | salary_change:double
205+
;

x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/analysis/Verifier.java

+8-6
Original file line numberDiff line numberDiff line change
@@ -237,9 +237,13 @@ private void checkSort(LogicalPlan p, Set<Failure> failures) {
237237
private static void checkFilterConditionType(LogicalPlan p, Set<Failure> localFailures) {
238238
if (p instanceof Filter f) {
239239
Expression condition = f.condition();
240-
if (condition.dataType() != BOOLEAN) {
241-
localFailures.add(fail(condition, "Condition expression needs to be boolean, found [{}]", condition.dataType()));
242-
}
240+
checkConditionExpressionDataType(condition, localFailures);
241+
}
242+
}
243+
244+
private static void checkConditionExpressionDataType(Expression expression, Set<Failure> localFailures) {
245+
if (expression.dataType() != NULL && expression.dataType() != BOOLEAN) {
246+
localFailures.add(fail(expression, "Condition expression needs to be boolean, found [{}]", expression.dataType()));
243247
}
244248
}
245249

@@ -432,9 +436,7 @@ private static void checkInvalidNamedExpressionUsage(
432436
}
433437
Expression f = fe.filter();
434438
// check the filter has to be a boolean term, similar as checkFilterConditionType
435-
if (f.dataType() != NULL && f.dataType() != BOOLEAN) {
436-
failures.add(fail(f, "Condition expression needs to be boolean, found [{}]", f.dataType()));
437-
}
439+
checkConditionExpressionDataType(f, failures);
438440
// but that the filter doesn't use grouping or aggregate functions
439441
fe.filter().forEachDown(c -> {
440442
if (c instanceof AggregateFunction af) {

x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/analysis/VerifierTests.java

+27
Original file line numberDiff line numberDiff line change
@@ -945,6 +945,33 @@ public void testPeriodAndDurationInEval() {
945945

946946
public void testFilterNonBoolField() {
947947
assertEquals("1:19: Condition expression needs to be boolean, found [INTEGER]", error("from test | where emp_no"));
948+
949+
assertEquals(
950+
"1:19: Condition expression needs to be boolean, found [KEYWORD]",
951+
error("from test | where concat(first_name, \"foobar\")")
952+
);
953+
}
954+
955+
public void testFilterNullField() {
956+
// `where null` should return empty result set
957+
query("from test | where null");
958+
959+
// Value null of type `BOOLEAN`
960+
query("from test | where null::boolean");
961+
962+
// Provide `NULL` type in `EVAL`
963+
query("from t | EVAL x = null | where x");
964+
965+
// `to_string(null)` is of `KEYWORD` type null, resulting in `to_string(null) == "abc"` being of `BOOLEAN`
966+
query("from t | where to_string(null) == \"abc\"");
967+
968+
// Other DataTypes can contain null values
969+
assertEquals("1:19: Condition expression needs to be boolean, found [KEYWORD]", error("from test | where null::string"));
970+
assertEquals("1:19: Condition expression needs to be boolean, found [INTEGER]", error("from test | where null::integer"));
971+
assertEquals(
972+
"1:45: Condition expression needs to be boolean, found [DATETIME]",
973+
error("from test | EVAL x = null::datetime | where x")
974+
);
948975
}
949976

950977
public void testFilterDateConstant() {

x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/optimizer/LogicalPlanOptimizerTests.java

+13
Original file line numberDiff line numberDiff line change
@@ -6808,4 +6808,17 @@ public void testMatchFunctionIsNotNullable() {
68086808
containsString("[MATCH] function cannot operate on [text::keyword], which is not a field from an index mapping")
68096809
);
68106810
}
6811+
6812+
public void testWhereNull() {
6813+
var plan = plan("""
6814+
from test
6815+
| sort salary
6816+
| rename emp_no as e, first_name as f
6817+
| keep salary, e, f
6818+
| where null
6819+
| LIMIT 12
6820+
""");
6821+
var local = as(plan, LocalRelation.class);
6822+
assertThat(local.supplier(), equalTo(LocalSupplier.EMPTY));
6823+
}
68116824
}

0 commit comments

Comments
 (0)