Skip to content

Commit bdb3d35

Browse files
authored
Handle case-sensitive identifier when decorrelating predicate subquery (#12443)
* fix replace_qualified_name to handle case-sensitive ident * add tests * fix clippy
1 parent 1d985ac commit bdb3d35

File tree

4 files changed

+90
-5
lines changed

4 files changed

+90
-5
lines changed

datafusion/optimizer/src/decorrelate_predicate_subquery.rs

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -370,8 +370,8 @@ mod tests {
370370
use super::*;
371371
use crate::test::*;
372372

373-
use arrow::datatypes::DataType;
374-
use datafusion_expr::{and, binary_expr, col, lit, not, or, out_ref_col};
373+
use arrow::datatypes::{DataType, Field, Schema};
374+
use datafusion_expr::{and, binary_expr, col, lit, not, or, out_ref_col, table_scan};
375375

376376
fn assert_optimized_plan_equal(plan: LogicalPlan, expected: &str) -> Result<()> {
377377
assert_optimized_plan_eq_display_indent(
@@ -1909,4 +1909,35 @@ mod tests {
19091909

19101910
assert_optimized_plan_equal(plan, expected)
19111911
}
1912+
1913+
#[test]
1914+
fn upper_case_ident() -> Result<()> {
1915+
let fields = vec![
1916+
Field::new("A", DataType::UInt32, false),
1917+
Field::new("B", DataType::UInt32, false),
1918+
];
1919+
1920+
let schema = Schema::new(fields);
1921+
let table_scan_a = table_scan(Some("\"TEST_A\""), &schema, None)?.build()?;
1922+
let table_scan_b = table_scan(Some("\"TEST_B\""), &schema, None)?.build()?;
1923+
1924+
let subquery = LogicalPlanBuilder::from(table_scan_b)
1925+
.filter(col("\"A\"").eq(out_ref_col(DataType::UInt32, "\"TEST_A\".\"A\"")))?
1926+
.project(vec![lit(1)])?
1927+
.build()?;
1928+
1929+
let plan = LogicalPlanBuilder::from(table_scan_a)
1930+
.filter(exists(Arc::new(subquery)))?
1931+
.project(vec![col("\"TEST_A\".\"B\"")])?
1932+
.build()?;
1933+
1934+
let expected = "Projection: TEST_A.B [B:UInt32]\
1935+
\n LeftSemi Join: Filter: __correlated_sq_1.A = TEST_A.A [A:UInt32, B:UInt32]\
1936+
\n TableScan: TEST_A [A:UInt32, B:UInt32]\
1937+
\n SubqueryAlias: __correlated_sq_1 [Int32(1):Int32, A:UInt32]\
1938+
\n Projection: Int32(1), TEST_B.A [Int32(1):Int32, A:UInt32]\
1939+
\n TableScan: TEST_B [A:UInt32, B:UInt32]";
1940+
1941+
assert_optimized_plan_equal(plan, expected)
1942+
}
19121943
}

datafusion/optimizer/src/utils.rs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -104,9 +104,7 @@ pub(crate) fn replace_qualified_name(
104104
) -> Result<Expr> {
105105
let alias_cols: Vec<Column> = cols
106106
.iter()
107-
.map(|col| {
108-
Column::from_qualified_name(format!("{}.{}", subquery_alias, col.name))
109-
})
107+
.map(|col| Column::new(Some(subquery_alias), &col.name))
110108
.collect();
111109
let replace_map: HashMap<&Column, &Column> =
112110
cols.iter().zip(alias_cols.iter()).collect();

datafusion/optimizer/tests/optimizer_integration.rs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -361,6 +361,31 @@ fn select_wildcard_with_repeated_column_but_is_aliased() {
361361
assert_eq!(expected, format!("{plan}"));
362362
}
363363

364+
#[test]
365+
fn select_correlated_predicate_subquery_with_uppercase_ident() {
366+
let sql = r#"
367+
SELECT *
368+
FROM
369+
test
370+
WHERE
371+
EXISTS (
372+
SELECT 1
373+
FROM (SELECT col_int32 as "COL_INT32", col_uint32 as "COL_UINT32" FROM test) "T1"
374+
WHERE "T1"."COL_INT32" = test.col_int32
375+
)
376+
"#;
377+
let plan = test_sql(sql).unwrap();
378+
let expected = "LeftSemi Join: test.col_int32 = __correlated_sq_1.COL_INT32\
379+
\n Filter: test.col_int32 IS NOT NULL\
380+
\n TableScan: test projection=[col_int32, col_uint32, col_utf8, col_date32, col_date64, col_ts_nano_none, col_ts_nano_utc]\
381+
\n SubqueryAlias: __correlated_sq_1\
382+
\n SubqueryAlias: T1\
383+
\n Projection: test.col_int32 AS COL_INT32\
384+
\n Filter: test.col_int32 IS NOT NULL\
385+
\n TableScan: test projection=[col_int32]";
386+
assert_eq!(expected, format!("{plan}"));
387+
}
388+
364389
fn test_sql(sql: &str) -> Result<LogicalPlan> {
365390
// parse the SQL
366391
let dialect = GenericDialect {}; // or AnsiDialect, or your own dialect ...

datafusion/sqllogictest/test_files/join.slt

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1178,3 +1178,34 @@ drop table t1;
11781178

11791179
statement ok
11801180
drop table t0;
1181+
1182+
# Test decorrelate query with the uppercase table name and column name
1183+
statement ok
1184+
create table "T1"("C1" int, "C2" int);
1185+
1186+
statement ok
1187+
create table "T2"("C1" int, "C3" int);
1188+
1189+
statement ok
1190+
select "C1" from "T1" where not exists (select 1 from "T2" where "T1"."C1" = "T2"."C1")
1191+
1192+
statement ok
1193+
create table t1(c1 int, c2 int);
1194+
1195+
statement ok
1196+
create table t2(c1 int, c3 int);
1197+
1198+
statement ok
1199+
select "C1" from (select c1 as "C1", c2 as "C2" from t1) as "T1" where not exists (select 1 from (select c1 as "C1", c3 as "C3" from t2) as "T2" where "T1"."C1" = "T2"."C1")
1200+
1201+
statement ok
1202+
drop table "T1";
1203+
1204+
statement ok
1205+
drop table "T2";
1206+
1207+
statement ok
1208+
drop table t1;
1209+
1210+
statement ok
1211+
drop table t2;

0 commit comments

Comments
 (0)