fix(unparser): inline a predicate's reference to an unnamed projection output - #200
Open
grokspice wants to merge 1 commit into
Open
fix(unparser): inline a predicate's reference to an unnamed projection output#200grokspice wants to merge 1 commit into
grokspice wants to merge 1 commit into
Conversation
…n output
When a Filter references a Projection output that the projection does not
name, the unparser emitted the column's logical name as a quoted identifier:
SELECT (t.a + t.b) FROM t WHERE ("t.a + t.b" > 1)
That name describes the expression, it is not an identifier the statement
carries -- t has no such column and the SELECT list introduces none -- so
PostgreSQL answers 42703 and DuckDB a Binder Error. Federated pushdown of a
filter over a computed column therefore failed at the remote engine rather
than at plan time.
Inline the expression at the point of use instead, which needs no name:
SELECT (t.a + t.b) FROM t WHERE ((t.a + t.b) > 1)
An aliased output and a bare column already carry a name the emitted SELECT
exposes, so both are left as they were. A volatile expression is also left
alone: inlining would evaluate it a second time, in a clause that can see a
different value than the SELECT list did, which trades a loud unbindable
reference for silently wrong rows.
Fixes spiceai/spiceai#12599.
There was a problem hiding this comment.
Pull request overview
Fixes SQL unparsing for filters referencing unnamed computed projection outputs.
Changes:
- Locates flattened projections and inlines unnamed deterministic expressions.
- Preserves volatile expressions.
- Adds regression snapshots for projection-filter scenarios.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
datafusion/sql/src/unparser/utils.rs |
Adds projection lookup and expression substitution helpers. |
datafusion/sql/src/unparser/plan.rs |
Applies substitution while generating WHERE. |
datafusion/sql/tests/cases/plan_to_sql.rs |
Adds regression tests and formatting cleanup. |
Suppressed comments (2)
datafusion/sql/src/unparser/utils.rs:339
SELECTaliases are not visible toWHEREin PostgreSQL or DuckDB. The newaliasedcase therefore still emits an unbindableWHERE (s > 1)(or can silently bind to an unrelated source column nameds). Alias outputs must be replaced with the alias's inner expression here, just like unnamed computed outputs, and the test expectation should useWHERE ((t.a + t.b) > 1).
if matches!(expr, Expr::Alias(_) | Expr::Column(_)) || expr.is_volatile() {
datafusion/sql/src/unparser/utils.rs:295
- This stops at any node other than another
Filter, although some such nodes are flattened into the sameSelectBuilder. For example,Filter -> Sort(fetch=None) -> Projection(a + b)passes through theSortarm without deriving, so this returnsNoneand the predicate is still emitted as the unbindable quoted logical name. Continue through same-select transparent nodes (at least a non-fetchSort) while retaining boundaries for operators such as fetching sorts/limits.
// Stacked filters collapse into one `WHERE`, so keep looking through them.
LogicalPlan::Filter(_) => {
find_projection_node_within_select(input, already_projected)
}
_ => None,
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
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.
Summary
When a
Filterreferences aProjectionoutput that the projection does not name, the unparser emitted the column's logical name as a quoted identifier:"t.a + t.b"is a single quoted identifier.thas no such column and theSELECTlist introduces none, so PostgreSQL answers 42703 and DuckDB a Binder Error. Federated pushdown of a filter over a computed column failed at the remote engine rather than at plan time.Root cause
The
LogicalPlan::Filterarm ofselect_to_sql_recursivelyhas an "unproject" step for the aggregate (HAVING) and window (QUALIFY) paths, but the plainWHEREpath unparsedfilter.predicateverbatim. A column referring to a projection output carries that output's logical name, which is a description of the expression rather than an identifier the emitted statement carries.Changes
find_projection_node_within_select— locates theProjectionthat will be flattened into the sameSELECT, mirroring the existingfind_agg_node_within_select. ReturnsNoneonce theSELECTlist is taken, since a projection reached after that becomes a derived table whose columns are addressable from outside.unproject_unnamed_projection_exprs— replaces a reference to an unnamed projection output with the expression that produces it, so the predicate needs no name at all:Deliberately left unchanged:
… AS s) and a bare column — both already carry a name the emittedSELECTexposes, so a reference to one binds as written;SELECTlist did. That trades a loud unbindable reference for silently wrong rows, so the loud failure is kept.Test plan
Five tests in
datafusion/sql/tests/cases/plan_to_sql.rs:test_filter_on_unnamed_projection_output_bindstest_filter_on_named_projection_output_is_unchangedtest_filter_on_unnamed_projection_output_used_twicetest_stacked_filters_on_unnamed_projection_outputWHEREtest_filter_on_unnamed_volatile_projection_output_is_not_inlinedcargo test -p datafusion-sql— 505 passed, 22 failed, against a 500 passed, 22 failed baseline onspiceai-54atedd8861e6. The 22 failures are identical in both runs (verified by diffing the failing-test sets) and pre-date this change; this PR adds 5 passing tests and regresses none.cargo clippy -p datafusion-sql --all-targetsandcargo fmt --all -- --checkreport nothing in the added code. Note thatspiceai-54is not currently rustfmt-clean, socargo fmt --allrewrites unrelated files; those were reverted to keep this diff in scope — including two hunks inunproject_sort_exprthat PR #198 also touches.