Skip to content

fix: restrict array_filter array_compact fast path to the lambda variable#4848

Merged
andygrove merged 3 commits into
apache:mainfrom
andygrove:fix-4830-array-filter-fast-path
Jul 13, 2026
Merged

fix: restrict array_filter array_compact fast path to the lambda variable#4848
andygrove merged 3 commits into
apache:mainfrom
andygrove:fix-4830-array-filter-fast-path

Conversation

@andygrove

Copy link
Copy Markdown
Member

Which issue does this PR close?

Closes #4830.

Rationale for this change

CometArrayFilter.convert has a fast path that lowers filter(arr, x -> x IS NOT NULL) to the native array_compact serde, to avoid the per-batch JNI cost of the codegen dispatcher. The guard only checked that the lambda body is an IsNotNull, not that the IsNotNull operand is the lambda's element variable.

As a result, a lambda whose body is IsNotNull of something other than the element (for example a captured column, x -> c IS NOT NULL) was incorrectly treated as array_compact. Spark keeps every element of arr when c is non-null and returns an empty array when c is null; the fast path instead dropped the null elements of arr regardless of c. This produced silently wrong results whenever arr contained null elements or the captured column was null.

What changes are included in this PR?

Tighten the guard in CometArrayFilter.convert to fire the array_compact fast path only when the IsNotNull operand is the lambda's own NamedLambdaVariable (matched by exprId, which also handles nested-lambda shadowing). Any other shape falls through to the codegen dispatcher, which runs Spark's own evaluation and matches Spark exactly.

How are these changes tested?

Added regression queries to array_filter.sql: a genuine array_compact case (x -> x IS NOT NULL) that must still fast-path, and the bug case (x -> c IS NOT NULL with a captured column and null array elements) that must match Spark. Verified the captured-column case fails against the pre-fix code and passes with the fix, and that the genuine fast path still runs natively.

@andygrove andygrove force-pushed the fix-4830-array-filter-fast-path branch from b6967a0 to 01d0b62 Compare July 7, 2026 17:12
@andygrove andygrove marked this pull request as draft July 7, 2026 17:36
…able

CometArrayFilter lowered filter(arr, x -> <IsNotNull>) to array_compact by only
checking that the lambda body is an IsNotNull, not that its operand is the
lambda's element variable. A body like x -> c IS NOT NULL (a captured column)
was wrongly rewritten as array_compact, dropping the null elements of arr
instead of Spark's semantics (keep all elements when c is non-null, empty array
when c is null). Tighten the guard to require the IsNotNull operand to be the
lambda's own NamedLambdaVariable; any other shape falls through to the codegen
dispatcher.

Closes apache#4830
@andygrove andygrove force-pushed the fix-4830-array-filter-fast-path branch from 01d0b62 to 59b1ca6 Compare July 7, 2026 17:41
@andygrove andygrove marked this pull request as ready for review July 7, 2026 17:42
@andygrove andygrove requested a review from parthchandra July 7, 2026 17:43
@parthchandra

Copy link
Copy Markdown
Contributor

Can we add tests for the following cases -

  • IsNotNull on an expression of the lambda var (x -> (x + 1) IS NOT NULL) — exercises the NamedLambdaVariable type check
    • Compound predicate (x -> x IS NOT NULL AND x > 0) — exercises the structural match
    • Null array input — verifies null propagation through the fast path
    • Empty array — boundary condition

@parthchandra

Copy link
Copy Markdown
Contributor

Also, Spark4xCometExprShim.scala has a shim for this case. Does that need to be updated?

… guard

Address review feedback on apache#4848:

- Add SQL fixtures for four additional cases: IsNotNull on an expression of
  the lambda variable (x -> (x + 1) IS NOT NULL), a compound predicate
  (x -> x IS NOT NULL AND x > 0), a null-array input row, and an empty-array
  input row. Each exercises a distinct part of the guard or the fast path's
  null propagation.
- Apply the same NamedLambdaVariable/exprId guard to the
  KnownNotContainsNull(ArrayFilter(IsNotNull)) branch in
  Spark4xCometExprShim so the Spark 4.x rewrite path rejects captured-column
  IsNotNull the same way the general serde does.
@andygrove

Copy link
Copy Markdown
Member Author

Thanks for the review @parthchandra. I have addressed feedback. Could you take another look?

@parthchandra parthchandra left a comment

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.

lgtm.

@andygrove andygrove merged commit 8e7cee8 into apache:main Jul 13, 2026
70 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

array_filter: array_compact fast path misfires when IsNotNull operand is not the lambda variable

2 participants