feat: support EXISTS/IN subquery expressions outside filters via mark joins - #3
Open
fornwall wants to merge 1 commit into
Open
feat: support EXISTS/IN subquery expressions outside filters via mark joins#3fornwall wants to merge 1 commit into
fornwall wants to merge 1 commit into
Conversation
… joins DecorrelatePredicateSubquery only rewrote IN/EXISTS subqueries appearing in a Filter predicate, so a subquery expression anywhere else — a projection, a CASE branch, a sort key — reached the physical planner intact and failed with "Physical plan does not support logical expression Exists(..)". Reuse the existing mark join machinery (so far only used for subqueries in filter disjunctions) for these positions: each EXISTS/IN expression in a Projection or Sort becomes a LeftMark join on the node input, and the expression is replaced by the join's boolean mark column. The mark column is exactly EXISTS semantics, so EXISTS (correlated or not, negated or not) is always rewritten. The mark column is two-valued, though, while IN is three-valued; in a filter that difference is unobservable (NULL drops rows just like FALSE, as before), but in a value position the rewrite is only applied when neither side of the IN can produce a NULL. Other IN subqueries keep failing in the physical planner rather than returning wrong NULL results. Also: - Allow Sort in the In/Exists subquery position invariant, which previously rejected ORDER BY EXISTS(..) during analysis. - Handle RightMark joins in the sort-pushdown column index (a mark join under a Sort gets side-swapped into one, which previously panicked). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WS4N9hte6f1W5r6EuDzobY
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Which issue does this PR close?
No tracking issue in this repository. Upstream tracks the same gap as apache#23022 (under the subquery epic apache#5483); this PR is fork-internal.
Rationale for this change
DecorrelatePredicateSubqueryonly rewritesIN/EXISTSsubqueries that appear in aFilterpredicate (or join condition). A subquery expression anywhere else — the select list, aCASEbranch, anORDER BYkey — survives optimization untouched and the physical planner fails with a raw internal error:DataFusion already has exactly the right tool: the
LeftMarkjoin, whose boolean mark column is preciselyEXISTSsemantics, already used by this rule for subqueries inside filter disjunctions.What changes are included in this PR?
DecorrelatePredicateSubquerynow also matchesProjectionandSortnodes whose expressions containEXISTS/INsubqueries, turns each subquery into aLeftMarkjoin on the node's input via the existingmark_joinpath, and replaces the expression with the mark column (aliased to keep output column names; a projection restores the schema above aSort).EXISTSis always rewritten — correlated or not, negated or not — since the mark column is exactly its two-valued semantics. Non-equality correlations work too (PullUpCorrelatedExprpulls them into the join filter).INis only rewritten when exact: the mark column is two-valued whileINis three-valued. In a filter that difference is unobservable (a NULL predicate drops rows just like FALSE — unchanged behavior), but in a value position the rewrite is applied only when neither the left-hand expression nor the subquery output can produce a NULL. Nullable cases keep failing in the physical planner rather than silently returning FALSE where NULL is required.In/Existssubqueries inSortnodes during analysis now allows them (they previously failed with a planning error before the rewrite could run).build_join_column_indexin sort-pushdown now handlesRightMarkjoins (a mark join under aSortgets side-swapped into one by hash-join optimization, which previously hitunreachable!).Are these changes tested?
subquery.slt: uncorrelated/empty/negatedEXISTSin the select list, correlatedEXISTS(equality and non-equality correlation),EXISTSinsideCASE, non-nullableIN/NOT INin the select list, andEXISTS/INas sort keys (theIN-as-sort-key case exercises theRightMarksort-pushdown fix).subquery.sltcase asserting the old analyzer rejection ofORDER BY ... IN (subquery)now asserts the physical planner's not-implemented error for the nullable-INcase.cargo fmt, workspacecargo clippy --all-targets --all-features -- -D warnings, the full sqllogictest suite (494/494 files), and the extended workspace test suite (9901 tests) all pass.Are there any user-facing changes?
EXISTS(...)and non-nullableIN (subquery)expressions now work in select lists,CASEexpressions, andORDER BYkeys instead of erroring. NullableINsubqueries in those positions still return the not-implemented error (never a wrong three-valued result).🤖 Generated with Claude Code
https://claude.ai/code/session_01WS4N9hte6f1W5r6EuDzobY