Skip to content

ESQL: Allow the data type of null in filters #118324

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 12 commits into from
Jan 8, 2025
6 changes: 6 additions & 0 deletions docs/changelog/118324.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
pr: 118324
summary: Allow the data type of `null` in filters
area: ES|QL
type: bug
issues:
- 116351
Original file line number Diff line number Diff line change
Expand Up @@ -191,3 +191,15 @@ emp_no:integer | languages:integer | height:double | x:double | y:double | z:dou
10020 | null | 1.41 | 1.41 | 1.41 | 40031.0 | 40031
10021 | null | 1.47 | 1.47 | 1.47 | 60408.0 | 60408
;

whereNull
FROM employees
| WHERE NULL and emp_no <= 10021
| SORT first_name, last_name
| EVAL fullname = CONCAT(first_name, " ", last_name)
| KEEP fullname, job_positions, salary, salary_change
| limit 5
;

fullname:keyword | job_positions:keyword | salary:integer | salary_change:double
;
Original file line number Diff line number Diff line change
Expand Up @@ -237,9 +237,13 @@ private void checkSort(LogicalPlan p, Set<Failure> failures) {
private static void checkFilterConditionType(LogicalPlan p, Set<Failure> localFailures) {
if (p instanceof Filter f) {
Expression condition = f.condition();
if (condition.dataType() != BOOLEAN) {
localFailures.add(fail(condition, "Condition expression needs to be boolean, found [{}]", condition.dataType()));
}
checkConditionExpressionDataType(condition, localFailures);
}
}

private static void checkConditionExpressionDataType(Expression expression, Set<Failure> localFailures) {
if (expression.dataType() != NULL && expression.dataType() != BOOLEAN) {
localFailures.add(fail(expression, "Condition expression needs to be boolean, found [{}]", expression.dataType()));
}
}

Expand Down Expand Up @@ -432,9 +436,7 @@ private static void checkInvalidNamedExpressionUsage(
}
Expression f = fe.filter();
// check the filter has to be a boolean term, similar as checkFilterConditionType
if (f.dataType() != NULL && f.dataType() != BOOLEAN) {
failures.add(fail(f, "Condition expression needs to be boolean, found [{}]", f.dataType()));
}
checkConditionExpressionDataType(f, failures);
// but that the filter doesn't use grouping or aggregate functions
fe.filter().forEachDown(c -> {
if (c instanceof AggregateFunction af) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -945,6 +945,33 @@ public void testPeriodAndDurationInEval() {

public void testFilterNonBoolField() {
assertEquals("1:19: Condition expression needs to be boolean, found [INTEGER]", error("from test | where emp_no"));

assertEquals(
"1:19: Condition expression needs to be boolean, found [KEYWORD]",
error("from test | where concat(first_name, \"foobar\")")
);
}

public void testFilterNullField() {
// `where null` should return empty result set
query("from test | where null");

// Value null of type `BOOLEAN`
query("from test | where null::boolean");

// Provide `NULL` type in `EVAL`
query("from t | EVAL x = null | where x");

// `to_string(null)` is of `KEYWORD` type null, resulting in `to_string(null) == "abc"` being of `BOOLEAN`
query("from t | where to_string(null) == \"abc\"");

// Other DataTypes can contain null values
assertEquals("1:19: Condition expression needs to be boolean, found [KEYWORD]", error("from test | where null::string"));
assertEquals("1:19: Condition expression needs to be boolean, found [INTEGER]", error("from test | where null::integer"));
assertEquals(
"1:45: Condition expression needs to be boolean, found [DATETIME]",
error("from test | EVAL x = null::datetime | where x")
);
}

public void testFilterDateConstant() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6808,4 +6808,17 @@ public void testMatchFunctionIsNotNullable() {
containsString("[MATCH] function cannot operate on [text::keyword], which is not a field from an index mapping")
);
}

public void testWhereNull() {
var plan = plan("""
from test
| sort salary
| rename emp_no as e, first_name as f
| keep salary, e, f
| where null
| LIMIT 12
""");
var local = as(plan, LocalRelation.class);
assertThat(local.supplier(), equalTo(LocalSupplier.EMPTY));
}
}