This is an automated email from the ASF dual-hosted git repository.

github-bot pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/datafusion.git


The following commit(s) were added to refs/heads/main by this push:
     new 2b986c8872 Fix index panic in unparser with mismatched stacked 
projections (#21094)
2b986c8872 is described below

commit 2b986c887224d818ee30b111d6c9047a45d20c35
Author: Matthew Kim <[email protected]>
AuthorDate: Mon Mar 23 14:03:53 2026 -0400

    Fix index panic in unparser with mismatched stacked projections (#21094)
    
    ## Rationale for this change
    
    This PR adds a length guard in `subquery_alias_inner_query_and_columns`
    to prevent a panic when outer and inner projections have different
    expression counts
    
    Without this fix, optimizer passes like `CommonSubexprEliminate` can
    produce stacked projections where the inner projections has more exprs
    than the outer. The function iterates over `inner_projection.expr` by
    index and uses the _same_ index to access `outer_projections.expr`,
    causing an index oob panic
    
    This PR bails out early when the lengths don't match. This is consistent
    with all the other early returns in the function that reject
    non-matching plan shapes
---
 datafusion/sql/src/unparser/rewrite.rs | 54 ++++++++++++++++++++++++++++++++++
 1 file changed, 54 insertions(+)

diff --git a/datafusion/sql/src/unparser/rewrite.rs 
b/datafusion/sql/src/unparser/rewrite.rs
index e3b644f33f..723cdcd687 100644
--- a/datafusion/sql/src/unparser/rewrite.rs
+++ b/datafusion/sql/src/unparser/rewrite.rs
@@ -318,6 +318,10 @@ pub(super) fn subquery_alias_inner_query_and_columns(
     // Check if the inner projection and outer projection have a matching 
pattern like
     //     Projection: j1.j1_id AS id
     //       Projection: j1.j1_id
+    if outer_projections.expr.len() != inner_projection.expr.len() {
+        return (plan, vec![]);
+    }
+
     for (i, inner_expr) in inner_projection.expr.iter().enumerate() {
         let Expr::Alias(outer_alias) = &outer_projections.expr[i] else {
             return (plan, vec![]);
@@ -490,3 +494,53 @@ impl TreeNodeRewriter for TableAliasRewriter<'_> {
         }
     }
 }
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use arrow::datatypes::{DataType, Field};
+    use datafusion_expr::{LogicalPlanBuilder, col, table_scan};
+
+    // this is a regression test: when the outer projection has fewer 
expressions than
+    // the inner projection, `subquery_alias_inner_query_and_columns` must not 
panic
+    // with an index oob error
+    // note: this happens when optimizer passes (e.g. CommonSubexprEliminate)
+    // insert an inner projection with extra columns that a subsequent 
projection narrows
+    // back down
+    #[test]
+    fn test_stacked_projections_mismatched_lengths_no_panic() {
+        let schema = Schema::new(vec![
+            Field::new("id", DataType::Int32, false),
+            Field::new("name", DataType::Utf8, false),
+        ]);
+
+        // Inner projection has 2 expressions, outer has 0 (empty).
+        let inner_plan = LogicalPlanBuilder::from(
+            table_scan(Some("t"), &schema, Some(vec![0, 1]))
+                .unwrap()
+                .build()
+                .unwrap(),
+        )
+        .project(vec![col("t.id"), col("t.name")])
+        .unwrap()
+        .build()
+        .unwrap();
+
+        // Build an empty outer projection over the inner.
+        let outer_plan = LogicalPlanBuilder::from(inner_plan)
+            .project(Vec::<Expr>::new())
+            .unwrap()
+            .alias("sub")
+            .unwrap()
+            .build()
+            .unwrap();
+
+        let LogicalPlan::SubqueryAlias(subquery_alias) = &outer_plan else {
+            panic!("expected SubqueryAlias");
+        };
+
+        // should return early without panicking
+        let (_plan, columns) = 
subquery_alias_inner_query_and_columns(subquery_alias);
+        assert!(columns.is_empty());
+    }
+}


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to