Skip to content

Commit ac7156f

Browse files
authored
fix: unparse join without projection (#15693)
* fix: unparse join without projection * update based on goldmedal's suggestion
1 parent 3818b7a commit ac7156f

File tree

4 files changed

+125
-1
lines changed

4 files changed

+125
-1
lines changed

datafusion/sql/src/unparser/ast.rs

+5
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,11 @@ impl SelectBuilder {
155155
self.projection = value;
156156
self
157157
}
158+
pub fn pop_projections(&mut self) -> Vec<ast::SelectItem> {
159+
let ret = self.projection.clone();
160+
self.projection.clear();
161+
ret
162+
}
158163
pub fn already_projected(&self) -> bool {
159164
!self.projection.is_empty()
160165
}

datafusion/sql/src/unparser/plan.rs

+36
Original file line numberDiff line numberDiff line change
@@ -582,6 +582,10 @@ impl Unparser<'_> {
582582
}
583583
_ => (&join.left, &join.right),
584584
};
585+
// If there's an outer projection plan, it will already set up the projection.
586+
// In that case, we don't need to worry about setting up the projection here.
587+
// The outer projection plan will handle projecting the correct columns.
588+
let already_projected = select.already_projected();
585589

586590
let left_plan =
587591
match try_transform_to_simple_table_scan_with_filters(left_plan)? {
@@ -599,6 +603,13 @@ impl Unparser<'_> {
599603
relation,
600604
)?;
601605

606+
let left_projection: Option<Vec<ast::SelectItem>> = if !already_projected
607+
{
608+
Some(select.pop_projections())
609+
} else {
610+
None
611+
};
612+
602613
let right_plan =
603614
match try_transform_to_simple_table_scan_with_filters(right_plan)? {
604615
Some((plan, filters)) => {
@@ -657,6 +668,13 @@ impl Unparser<'_> {
657668
&mut right_relation,
658669
)?;
659670

671+
let right_projection: Option<Vec<ast::SelectItem>> = if !already_projected
672+
{
673+
Some(select.pop_projections())
674+
} else {
675+
None
676+
};
677+
660678
match join.join_type {
661679
JoinType::LeftSemi
662680
| JoinType::LeftAnti
@@ -702,6 +720,9 @@ impl Unparser<'_> {
702720
} else {
703721
select.selection(Some(exists_expr));
704722
}
723+
if let Some(projection) = left_projection {
724+
select.projection(projection);
725+
}
705726
}
706727
JoinType::Inner
707728
| JoinType::Left
@@ -719,6 +740,21 @@ impl Unparser<'_> {
719740
let mut from = select.pop_from().unwrap();
720741
from.push_join(ast_join);
721742
select.push_from(from);
743+
if !already_projected {
744+
let Some(left_projection) = left_projection else {
745+
return internal_err!("Left projection is missing");
746+
};
747+
748+
let Some(right_projection) = right_projection else {
749+
return internal_err!("Right projection is missing");
750+
};
751+
752+
let projection = left_projection
753+
.into_iter()
754+
.chain(right_projection.into_iter())
755+
.collect();
756+
select.projection(projection);
757+
}
722758
}
723759
};
724760

datafusion/sql/src/unparser/utils.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -385,7 +385,7 @@ pub(crate) fn try_transform_to_simple_table_scan_with_filters(
385385
let mut builder = LogicalPlanBuilder::scan(
386386
table_scan.table_name.clone(),
387387
Arc::clone(&table_scan.source),
388-
None,
388+
table_scan.projection.clone(),
389389
)?;
390390

391391
if let Some(alias) = table_alias.take() {

datafusion/sql/tests/cases/plan_to_sql.rs

+83
Original file line numberDiff line numberDiff line change
@@ -2342,3 +2342,86 @@ fn test_unparse_right_anti_join() -> Result<()> {
23422342
);
23432343
Ok(())
23442344
}
2345+
2346+
#[test]
2347+
fn test_unparse_cross_join_with_table_scan_projection() -> Result<()> {
2348+
let schema = Schema::new(vec![
2349+
Field::new("k", DataType::Int32, false),
2350+
Field::new("v", DataType::Int32, false),
2351+
]);
2352+
// Cross Join:
2353+
// SubqueryAlias: t1
2354+
// TableScan: test projection=[v]
2355+
// SubqueryAlias: t2
2356+
// TableScan: test projection=[v]
2357+
let table_scan1 = table_scan(Some("test"), &schema, Some(vec![1]))?.build()?;
2358+
let table_scan2 = table_scan(Some("test"), &schema, Some(vec![1]))?.build()?;
2359+
let plan = LogicalPlanBuilder::from(subquery_alias(table_scan1, "t1")?)
2360+
.cross_join(subquery_alias(table_scan2, "t2")?)?
2361+
.build()?;
2362+
let unparser = Unparser::new(&UnparserPostgreSqlDialect {});
2363+
let sql = unparser.plan_to_sql(&plan)?;
2364+
assert_snapshot!(
2365+
sql,
2366+
@r#"SELECT "t1"."v", "t2"."v" FROM "test" AS "t1" CROSS JOIN "test" AS "t2""#
2367+
);
2368+
Ok(())
2369+
}
2370+
2371+
#[test]
2372+
fn test_unparse_inner_join_with_table_scan_projection() -> Result<()> {
2373+
let schema = Schema::new(vec![
2374+
Field::new("k", DataType::Int32, false),
2375+
Field::new("v", DataType::Int32, false),
2376+
]);
2377+
// Inner Join:
2378+
// SubqueryAlias: t1
2379+
// TableScan: test projection=[v]
2380+
// SubqueryAlias: t2
2381+
// TableScan: test projection=[v]
2382+
let table_scan1 = table_scan(Some("test"), &schema, Some(vec![1]))?.build()?;
2383+
let table_scan2 = table_scan(Some("test"), &schema, Some(vec![1]))?.build()?;
2384+
let plan = LogicalPlanBuilder::from(subquery_alias(table_scan1, "t1")?)
2385+
.join_on(
2386+
subquery_alias(table_scan2, "t2")?,
2387+
datafusion_expr::JoinType::Inner,
2388+
vec![col("t1.v").eq(col("t2.v"))],
2389+
)?
2390+
.build()?;
2391+
let unparser = Unparser::new(&UnparserPostgreSqlDialect {});
2392+
let sql = unparser.plan_to_sql(&plan)?;
2393+
assert_snapshot!(
2394+
sql,
2395+
@r#"SELECT "t1"."v", "t2"."v" FROM "test" AS "t1" INNER JOIN "test" AS "t2" ON ("t1"."v" = "t2"."v")"#
2396+
);
2397+
Ok(())
2398+
}
2399+
2400+
#[test]
2401+
fn test_unparse_left_semi_join_with_table_scan_projection() -> Result<()> {
2402+
let schema = Schema::new(vec![
2403+
Field::new("k", DataType::Int32, false),
2404+
Field::new("v", DataType::Int32, false),
2405+
]);
2406+
// LeftSemi Join:
2407+
// SubqueryAlias: t1
2408+
// TableScan: test projection=[v]
2409+
// SubqueryAlias: t2
2410+
// TableScan: test projection=[v]
2411+
let table_scan1 = table_scan(Some("test"), &schema, Some(vec![1]))?.build()?;
2412+
let table_scan2 = table_scan(Some("test"), &schema, Some(vec![1]))?.build()?;
2413+
let plan = LogicalPlanBuilder::from(subquery_alias(table_scan1, "t1")?)
2414+
.join_on(
2415+
subquery_alias(table_scan2, "t2")?,
2416+
datafusion_expr::JoinType::LeftSemi,
2417+
vec![col("t1.v").eq(col("t2.v"))],
2418+
)?
2419+
.build()?;
2420+
let unparser = Unparser::new(&UnparserPostgreSqlDialect {});
2421+
let sql = unparser.plan_to_sql(&plan)?;
2422+
assert_snapshot!(
2423+
sql,
2424+
@r#"SELECT "t1"."v" FROM "test" AS "t1" WHERE EXISTS (SELECT 1 FROM "test" AS "t2" WHERE ("t1"."v" = "t2"."v"))"#
2425+
);
2426+
Ok(())
2427+
}

0 commit comments

Comments
 (0)