Skip to content

fix(unparser): apply empty projection fallback to Projection nodes - #27333

Open
dreverri wants to merge 1 commit into
apache:mainfrom
dreverri:unparser-empty-projection
Open

dreverri wants to merge 1 commit into
apache:mainfrom
dreverri:unparser-empty-projection

Conversation

@dreverri

Copy link
Copy Markdown

Which issue does this PR close?

Rationale for this change

Unparsing the optimized plan of select count(*) from t where id > 10 produces SELECT count(1) AS "count(*)" FROM (SELECT FROM t WHERE (t.id > 10)), even for dialects whose supports_empty_select_list() is false. Engines that don't accept an empty select list, such as BigQuery ("SELECT list must not be empty"), reject the generated SQL.

optimize_projections prunes the filter column above the Filter, which leaves a Projection with no expressions. #19221 added the SELECT 1 fallback for empty projections, but only the TableScan path uses it.

What changes are included in this PR?

  • reconstruct_select_statement now uses empty_projection_fallback() when the Projection has no expressions, the same as the TableScan path. Dialects without empty select list support get SELECT 1 FROM .... PostgreSQL output is unchanged (SELECT FROM ...).

Are these changes tested?

Yes.

  • datafusion/core/tests/sql/unparser.rs: optimized_count_over_filter_empty_projection runs the real query through the optimizer and unparser. It fails without the fix.
  • datafusion/sql/tests/cases/plan_to_sql.rs: snapshot tests for an empty Projection node with the default, PostgreSQL and BigQuery dialects.

Are there any user-facing changes?

Yes, generated SQL changes for dialects that don't support an empty select list: an empty Projection now unparses as SELECT 1 FROM ... instead of SELECT FROM .... There are no public API changes.

🤖 Generated with Claude Code

A `Projection` with no expressions (as `optimize_projections` leaves
under `count(*)` when a filter column is not otherwise needed) was
unparsed as `SELECT FROM ...` regardless of
`Dialect::supports_empty_select_list`. Only the `TableScan` path
consulted the dialect, so e.g. BigQuery received

    SELECT COUNT(1) FROM (SELECT FROM `t` WHERE ...)

and rejected it with "SELECT list must not be empty".

`reconstruct_select_statement` now uses `empty_projection_fallback`
for an empty projection, the same as the table scan path: `SELECT 1`
for dialects without empty select lists, unchanged for PostgreSQL.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions github-actions Bot added sql SQL Planner core Core DataFusion crate labels Nov 11, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

core Core DataFusion crate sql SQL Planner

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Unparser emits empty SELECT list for empty Projection nodes, ignoring supports_empty_select_list

1 participant