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]

Reply via email to