Skip to content

[Bug] A null literal in a procedure where-clause NPEs while merging range predicates #9646

Description

@LuciferYang

Search before asking

  • I searched in the issues and found nothing similar.

Paimon version

master, 475be566f (2.1-SNAPSHOT).

Compute Engine

Spark, through a procedure's where argument.

Minimal reproduce step

Pass a procedure filter that compares a column to NULL on both sides of a range:

CALL sys.compact(table => 'db.t', where => 'dt >= 1 AND dt <= null');

The call fails with a NullPointerException out of Between.optimize. PredicateBuilder.and asks it to merge the >= and <= on the same field, and it compares the two bounds directly:

Object lowerBound = greaterOrEqual.literals().get(0);
Object upperBound = lessOrEqual.literals().get(0);
if (compareLiteral(type, lowerBound, upperBound) >= 0) {

compareLiteral then runs ((Comparable<Object>) v1).compareTo(v2), which throws when v2 is null; with the null on the other side it falls through to RuntimeException("Unsupported type") instead.

The procedure path is what makes this reachable: ExpressionHelper.resolveFilter runs ConstantFolding only, not NullPropagation or ReplaceNullWithFalseInPredicate, so dt <= null survives resolution, and SparkV2FilterConverter passes the null literal through. A plain SELECT ... WHERE dt <= null does not reach it, because Spark's optimizer folds the comparison to false first.

What doesn't meet your expectations?

A null literal is a legal input elsewhere in the predicate framework, so it should not crash the optimizer. LeafBinaryFunction.test already treats a null literal as no match:

public boolean test(DataType type, Object field, List<Object> literals) {
    Object literal = literals.get(0);
    return field != null && literal != null && test(type, field, literal);
}

Merging two range predicates into a BETWEEN is an optimization; when one bound is null there is nothing to order, and the pair can simply stay unmerged and be evaluated by the rules above.

Anything else?

compareLiteral is also called from NotIn, NotBetween and LessThan. Those all null-check the literal before calling, or are only reached with non-null bounds, so Between.optimize is the one caller that got there with a null.

Are you willing to submit a PR?

  • I'm willing to submit a PR!

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions