Skip to content

ESQL: Fix null comparison type checking#140660

Merged
astefan merged 19 commits intoelastic:mainfrom
kanoshiou:null-comparison-type-check
Feb 18, 2026
Merged

ESQL: Fix null comparison type checking#140660
astefan merged 19 commits intoelastic:mainfrom
kanoshiou:null-comparison-type-check

Conversation

@kanoshiou
Copy link
Copy Markdown
Contributor

This PR fixes an issue where comparing null with other types incorrectly produced a type incompatibility error.

For example, ROW x = null, y = 1 | WHERE x == y would fail with a type error instead of correctly evaluating to null. Null should be compatible with any type and evaluate to null at runtime, matching the behavior of EsqlBinaryComparison.checkCompatibility.

Fixes #140460

@elasticsearchmachine elasticsearchmachine added needs:triage Requires assignment of a team area label v9.4.0 external-contributor Pull request authored by a developer outside the Elasticsearch team labels Jan 14, 2026
@kanoshiou kanoshiou changed the title ESQL: Fix ESQL null comparison type checking ESQL: Fix null comparison type checking Jan 14, 2026
kanoshiou and others added 2 commits January 14, 2026 23:07
# Conflicts:
#	x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/action/EsqlCapabilities.java
@elasticsearchmachine elasticsearchmachine added the Team:Analytics Meta label for analytical engine team (ESQL/Aggs/Geo) label Jan 22, 2026
@elasticsearchmachine
Copy link
Copy Markdown
Collaborator

Pinging @elastic/es-analytical-engine (Team:Analytics)

@elasticsearchmachine elasticsearchmachine removed the needs:triage Requires assignment of a team area label label Jan 22, 2026
# Conflicts:
#	x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/action/EsqlCapabilities.java
@astefan astefan self-assigned this Feb 10, 2026
@astefan astefan self-requested a review February 10, 2026 10:22
@astefan
Copy link
Copy Markdown
Contributor

astefan commented Feb 10, 2026

buildkite test this

Copy link
Copy Markdown
Contributor

@astefan astefan left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the fix is ok, but it needs more refining before merge:

  • in In:242 the comment is no longer accurate. That specific method has been removed.
  • check any csv-spec tests that we have for unsigned_long, null and in. If we don't have any, please add some. I would start with unsigned_long.csv-spec.
  • same ask as the previous one, but for tests that use null in boolean logical operations, for example from ul_logs | eval x = null OR bytes_in > null or from ul_logs | eval x = null or bytes_in > 2::unsigned_long

@@ -241,12 +242,10 @@ private static List<BiConsumer<LogicalPlan, Failures>> planCheckers(LogicalPlan
private static void checkOperationsOnUnsignedLong(LogicalPlan p, Failures failures) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suggest the following change to this method (simplified and with a better comment):

    /**
     * This validates only the negation operator, the rest of the validation is performed in
     * {@link org.elasticsearch.xpack.esql.analysis.Verifier#validateBinaryComparison(BinaryComparison)}
     * and
     * {@link org.elasticsearch.xpack.esql.expression.predicate.operator.comparison.EsqlBinaryComparison#areTypesCompatible(DataType, DataType)}
     */
    private static void checkOperationsOnUnsignedLong(LogicalPlan p, Failures failures) {
        p.forEachExpression(Neg.class, neg -> {
            Failure f = validateUnsignedLongNegation(neg);
            if (f != null) {
                failures.add(f);
            }
        });
    }

*
* @return a Failure if the left operand type is not allowed, null otherwise
*/
private static Failure checkBinaryComparisonTypes(BinaryComparison bc) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Change the name of this method accordingly, since now it looks only at the left side of a binary comparison.

return null;
}

/** Ensure that UNSIGNED_LONG types are not implicitly converted when used in arithmetic binary operator, as this cannot be done since:
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Save this comment somewhere, since it's valuable information regarding unsigned_long types compatibility.

* Test that null comparisons are valid and don't produce type incompatibility errors.
* Null should be compatible with any type in binary comparisons.
*/
public void testNullComparisonValidation() {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider adding tests comparing unsigned_long with null as well.

10022 | Shahaf | Famili
;

###############################################
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add more complex tests that get a null from an operation for example.
row x = null, y = 1, z = 2 | eval t = x + 1 > z


assertEquals(
"1:19: first argument of [emp_no == ?] is [numeric] so second argument must also be [numeric] but was [null]",
error("from test | where emp_no == ?", new Object[] { null })
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't remove this query entirely, but move it to the method above and validate that a param null is still being passed correctly.

@kanoshiou
Copy link
Copy Markdown
Contributor Author

Thank you for your review, @astefan!

I’ve updated the code based on your comments. I also revised the areCompatible logic in both In and DataType. If I’ve missed anything, please let me know and I’ll address it promptly.

@astefan
Copy link
Copy Markdown
Contributor

astefan commented Feb 13, 2026

buildkite test this

@kanoshiou
Copy link
Copy Markdown
Contributor Author

Hi @astefan, I believe the failing test is not caused by this PR. It appears to be an issue specific to the CI runner.

@astefan
Copy link
Copy Markdown
Contributor

astefan commented Feb 16, 2026

buildkite test this

@kanoshiou
Copy link
Copy Markdown
Contributor Author

Hi @astefan,

I've added the warnings for unsigned_long.UnsignedLongInWithNull. I wasn't able to reproduce the failures for the other two tests, but it looks like they are tracked separately in issue #138231.

@astefan
Copy link
Copy Markdown
Contributor

astefan commented Feb 16, 2026

buildkite test this

@kanoshiou
Copy link
Copy Markdown
Contributor Author

This is quite unexpected. I have switched to regex warnings and double-checked the tests to ensure they pass locally.

@astefan
Copy link
Copy Markdown
Contributor

astefan commented Feb 17, 2026

buildkite test this

@kanoshiou
Copy link
Copy Markdown
Contributor Author

Thanks for the patches, @astefan.

Actually, this removal doesn't address these failures. ":x-pack:plugin:esql:test" --tests "org.elasticsearch.xpack.esql.CsvTests" -Dtests.method="test {csv-spec:unsigned_long.UnsignedLongInWithNull}" would still fail. Issue #139262 is currently tracking the "missing warning" problem.

I previously ran EsqlSpecIT but skipped CsvTests, and now I believe adding the necessary regex escape characters (\) should resolve the current failures.

But I am not quite sure if AssertWarnings.AllowedRegexes#assertWarnings is doing the right job because it is skipped if there are no warnings.

@astefan
Copy link
Copy Markdown
Contributor

astefan commented Feb 17, 2026

buildkite test this

@astefan
Copy link
Copy Markdown
Contributor

astefan commented Feb 17, 2026

Thank you for explaining @kanoshiou. #139262 cannot be easily reproduced, actually at all so far. Those failures have been noticed in several runs in CI, but not at all locally in tests.

I have tested that query with no warnings in csv-spec file locally and both these runs were ok with it:

  • gradlew :x-pack:plugin:esql:qa:server:single-node:javaRestTest --tests "org.elasticsearch.xpack.esql.qa.single_node.EsqlSpecIT" -Dtests.method="*unsigned_long*"
  • gradlew :x-pack:plugin:esql:test -Dtests.class=org.elasticsearch.xpack.esql.CsvTests -Dtests.method="*unsigned_long*"

But, I think there is an explanation and for this test to be future-safe (multiple CI runs and highly randomized environments) I think we need to make this query (maybe others, haven't looked into it) more deterministic. Take a look at this javadoc https://github.com/elastic/elasticsearch/blob/main/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/CsvTests.java#L184-L194 and the one from SingleValueMatchQuery. This means that depending on how the Lucene index is accessed, a query that hits the index might or might not issue a Warning. On the other hand, with CsvTests this will allways happen (emitting warnings).

For that specific query I suggest a change like the following, if I am not missing anything: FROM ul_logs | WHERE id IN (42, 85) | LIMIT 5 | SORT id | WHERE bytes_in IN (to_ul(0), null) | KEEP id, bytes_in (those two IDs are the single ones from that index that have multi-values). Let me know what you think.

@kanoshiou
Copy link
Copy Markdown
Contributor Author

Thanks for the guidance, @astefan.

The Javadocs were really helpful and cleared up my confusion about the inconsistent test results. I’ve updated the test to match your suggestion. Since there appear to be other non-deterministic tests as well, I’d be happy to address those in a separate PR if you'd like.

@astefan
Copy link
Copy Markdown
Contributor

astefan commented Feb 18, 2026

buildkite test this

Copy link
Copy Markdown
Contributor

@astefan astefan left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

;


# unsigned_long field not equal to null with WHERE (null rows filtered out)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This filters out all rows not only "null rows".

@astefan astefan merged commit b30aa50 into elastic:main Feb 18, 2026
38 checks passed
astefan pushed a commit to astefan/elasticsearch that referenced this pull request Mar 17, 2026
astefan added a commit that referenced this pull request Mar 17, 2026
(cherry picked from commit b30aa50)

Co-authored-by: kanoshiou <uiaao@tuta.io>
@astefan astefan added the v9.3.3 label Mar 17, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

:Analytics/ES|QL AKA ESQL >bug external-contributor Pull request authored by a developer outside the Elasticsearch team Team:Analytics Meta label for analytical engine team (ESQL/Aggs/Geo) v9.3.3 v9.4.0

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ESQL: NULL type not working with binary comparisons

4 participants