Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 54 additions & 0 deletions datafusion/sql/src/unparser/rewrite.rs
Original file line number Diff line number Diff line change
Expand Up @@ -310,6 +310,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![]);
}

Comment on lines +313 to +316
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can confirm that when I remove this, the test below will panic

for (i, inner_expr) in inner_projection.expr.iter().enumerate() {
let Expr::Alias(outer_alias) = &outer_projections.expr[i] else {
return (plan, vec![]);
Expand Down Expand Up @@ -482,3 +486,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());
}
}
Loading