This is an automated email from the ASF dual-hosted git repository. github-merge-queue[bot] pushed a commit to branch gh-readonly-queue/main/pr-25547-0576a0b400437ade5a6f3b102465d954a539f263 in repository https://gitbox.apache.org/repos/asf/datafusion.git
commit 4fe342d715ed3078494c772038e1dfe4b31adc98 Author: Nuno Faria <[email protected]> AuthorDate: Tue Sep 22 03:23:43 2026 +0000 fix: Prevent SubqueryAlias to be pushed past FilterExec when unparsing (#25547) ## Which issue does this PR close? - Closes #25545. ## Rationale for this change Pushing a `SubqueryAlias` past a `FilterExec` can make the predicate refer to an invalid table name. This PR simply avoids that and keeps the subquery. ## What changes are included in this PR? - Table is not aliased from a `SubqueryAlias` when there is a `FilterExec` below it. ## What is the testing strategy for this PR? Unit test. ## Are there any user-facing changes? No. --- datafusion/core/tests/sql/unparser.rs | 27 ++++++++++++++++++++++++++- datafusion/sql/src/unparser/plan.rs | 6 +++++- 2 files changed, 31 insertions(+), 2 deletions(-) diff --git a/datafusion/core/tests/sql/unparser.rs b/datafusion/core/tests/sql/unparser.rs index 3982c60dbc..b77f53cb46 100644 --- a/datafusion/core/tests/sql/unparser.rs +++ b/datafusion/core/tests/sql/unparser.rs @@ -51,8 +51,8 @@ use datafusion_catalog::memory::MemorySchemaProvider; use datafusion_catalog::{CatalogProvider, MemoryCatalogProvider, SchemaProvider}; use datafusion_common::Column; use datafusion_expr::Expr; -use datafusion_sql::unparser::Unparser; use datafusion_sql::unparser::dialect::{DefaultDialect, DuckDBDialect}; +use datafusion_sql::unparser::{Unparser, plan_to_sql}; use itertools::Itertools; use recursive::{set_minimum_stack_size, set_stack_allocation_size}; @@ -747,6 +747,31 @@ async fn optimized_duckdb_unparse_top_level_sort_over_agg_uses_select_alias() -> Ok(()) } +#[tokio::test] +async fn optimized_filter_with_subquery_alias() -> Result<()> { + let ctx = SessionContext::new(); + ctx.sql("create table t (a int)").await?.collect().await?; + let df = ctx + .sql( + " + select * + from ( + select a + from t + ) t2 + where a = 1 + ", + ) + .await?; + let plan = df.into_optimized_plan()?; + let sql = plan_to_sql(&plan)?.to_string(); + assert_eq!( + sql, + "SELECT * FROM (SELECT t.a FROM t WHERE (t.a = 1)) AS t2" + ); + Ok(()) +} + /// The outcome of running a single roundtrip test. /// /// A successful test produces [`TestCaseResult::Success`]. diff --git a/datafusion/sql/src/unparser/plan.rs b/datafusion/sql/src/unparser/plan.rs index 18af08fc18..69babae4e0 100644 --- a/datafusion/sql/src/unparser/plan.rs +++ b/datafusion/sql/src/unparser/plan.rs @@ -1632,7 +1632,11 @@ impl Unparser<'_> { // we must emit a derived subquery: (SELECT ...) AS alias. // Without this, the recursive handler would merge those clauses // into the outer SELECT, losing the subquery structure entirely. - if unparsed_table_scan.is_none() && Self::requires_derived_subquery(plan) + // Also, do not add a table alias past a Filter, as otherwise the predicates might + // refer to invalid tables. + if (unparsed_table_scan.is_none() + && Self::requires_derived_subquery(plan)) + || matches!(plan, LogicalPlan::Filter(_)) { // When the dialect does not support column aliases in // table aliases (e.g. SQLite), inject the aliases into --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
