You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Unparsing of CROSS JOINs with filters is generating incorrect queries #17359
The unparsing of CROSS JOINS with filters is generating invalid SQL, when building from an optimized plan.
To Reproduce
Example:
-- originalselectt1.k, t2.vfrom t1, t2
wheret2.k=0;
-- optimized planCross Join:
TableScan: t1 projection=[k]
Projection: t2.v
Filter: t2.k= Int32(0)
TableScan: t2 projection=[k, v]
-- generated; the repeated filter is not valid since k is not projected from the subquerySELECTt1.k, t2.vFROM t1 CROSS JOIN (SELECTt2.vFROM t2 WHERE (t2.k=0)) WHERE (t2.k=0)
If we filter by a projected column (t2.v) it works correctly (although it would make more sense for the filter to be done on a WHERE instead of on the JOIN, but it works):
I'm a bit confused, I believe the logical plan should have already pushed down the filter into the subquery, removing the filter from the entire join. When I ran a test in datafusion/sql/tests/cases/plan_to_sql.rs, it passed fine:
#[test]
fn roundtrip_cross_join_right_filter_bug_dialect2() -> Result<(), DataFusionError> {
// Repros the buggy output (outer WHERE duplicated), formatted like dialect 2.
roundtrip_statement_with_dialect_helper!(
sql: "select j1.j1_id, j2.j2_string from j1, j2 where j2.j2_id = 0;",
parser_dialect: GenericDialect {},
unparser_dialect: UnparserDefaultDialect {},
expected: @"SELECT j1.j1_id, j2.j2_string FROM j1 CROSS JOIN j2 WHERE (j2.j2_id = 0)",
);
Ok(())
}
If you manually create the plan like so below the test will reproduce but this shouldn't happen in the first place from the original query but I think i'm missing something:
#[test]
fn test_unparse_cross_join() -> Result<()> {
let schema = Schema::new(vec![
Field::new("k", DataType::Int32, false),
Field::new("v", DataType::Int32, false),
]);
// Cross Join:
// TableScan: t1 projection=[k]
// Projection: t2.v
// Filter: t2.k = Int32(0)
// TableScan: t2 projection=[k, v]
let t1_scan = table_scan(Some("t1"), &schema, Some(vec![0]))?.build()?;
let t2_scan = table_scan(Some("t2"), &schema, Some(vec![0, 1]))?.build()?;
let t2_proj = LogicalPlanBuilder::from(t2_scan)
.filter(col("t2.k").eq(lit(0)))?
.project(vec![col("t2.v")])?
.build()?;
let plan = LogicalPlanBuilder::from(t1_scan)
.cross_join(t2_proj)?
.filter(col("t2.k").eq(lit(0)))?
.project(vec![col("t1.k"), col("t2.v")])?
.build()?;
let unparser = Unparser::new(&UnparserPostgreSqlDialect {});
let sql = unparser.plan_to_sql(&plan)?;
assert_snapshot!(
sql,
@r#"SELECT "t1"."k", "t2"."v" FROM "t1" CROSS JOIN (SELECT "t2"."v" FROM "t2" WHERE ("t2"."k" = 0))"#
);
Ok(())
}
However this would be incorrect as the outside filter is still kept even though it would have been pushed down. Are you able to make a specific test that I can run somewhere that reproduces this?
@chenkovsky Is it possible for me to do this one? I had forgotten to 'take', but I was about to get around to it. It is fine if that is not possible, sorry about that.
Activity
jonathanc-n commentedon Aug 31, 2025
I'm a bit confused, I believe the logical plan should have already pushed down the filter into the subquery, removing the filter from the entire join. When I ran a test in
datafusion/sql/tests/cases/plan_to_sql.rs, it passed fine:If you manually create the plan like so below the test will reproduce but this shouldn't happen in the first place from the original query but I think i'm missing something:
However this would be incorrect as the outside filter is still kept even though it would have been pushed down. Are you able to make a specific test that I can run somewhere that reproduces this?
nuno-faria commentedon Aug 31, 2025
@jonathanc-n thanks for looking into it. Here is a complete example:
The problem occurs when unparsing an optimized plan, while unparsing the original unoptimized plan works well.
Here are the plans for reference:
chenkovsky commentedon Sep 2, 2025
take
jonathanc-n commentedon Sep 2, 2025
@chenkovsky Is it possible for me to do this one? I had forgotten to 'take', but I was about to get around to it. It is fine if that is not possible, sorry about that.
chenkovsky commentedon Sep 2, 2025
untake