Skip to content

Commit 4e0161d

Browse files
authored
fix: Don't treat quoted column names as placeholder variables in SQL (#19339)
## Which issue does this PR close? Closes #19249 ## Rationale for this change SQL allows for column names to begin with `@`, so long as the identifier is quoted. For example, `SELECT "@column" FROM table`. Both PostgreSQL and MySQL allow for this at least. The existing SQL parser treats these column names as placeholder variables, because it does not check if the identifier is quoted. This change corrects that behavior. ## What changes are included in this PR? sql/src/expr/identifier.rs - fix to check if identifiers are quoted sql/tests/common/mod.rs - new test table sql/tests/sql_integration.rs - unit test ## Are these changes tested? Yes, `test_parse_quoted_column_name_with_at_sign` unit test was added to verify quoted column names beginning with `@` are treated as column names instead of placeholder variables. ## Are there any user-facing changes? It's possible users are relying on the existing behavior.
1 parent 458b491 commit 4e0161d

File tree

3 files changed

+49
-4
lines changed

3 files changed

+49
-4
lines changed

datafusion/sql/src/expr/identifier.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ impl<S: ContextProvider> SqlToRel<'_, S> {
3737
planner_context: &mut PlannerContext,
3838
) -> Result<Expr> {
3939
let id_span = id.span;
40-
if id.value.starts_with('@') {
40+
if id.value.starts_with('@') && id.quote_style.is_none() {
4141
// TODO: figure out if ScalarVariables should be insensitive.
4242
let var_names = vec![id.value];
4343
let field = self
@@ -111,7 +111,7 @@ impl<S: ContextProvider> SqlToRel<'_, S> {
111111
.filter_map(|id| Span::try_from_sqlparser_span(id.span)),
112112
);
113113

114-
if ids[0].value.starts_with('@') {
114+
if ids[0].value.starts_with('@') && ids[0].quote_style.is_none() {
115115
let var_names: Vec<_> = ids
116116
.into_iter()
117117
.map(|id| self.ident_normalizer.normalize(id))

datafusion/sql/tests/common/mod.rs

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,11 @@ impl ContextProvider for MockContextProvider {
227227
false,
228228
),
229229
])),
230+
"@quoted_identifier_names_table" => Ok(Schema::new(vec![Field::new(
231+
"@column",
232+
DataType::UInt32,
233+
false,
234+
)])),
230235
_ => plan_err!("No table named: {} found", name.table()),
231236
};
232237

@@ -244,8 +249,11 @@ impl ContextProvider for MockContextProvider {
244249
self.state.aggregate_functions.get(name).cloned()
245250
}
246251

247-
fn get_variable_type(&self, _: &[String]) -> Option<DataType> {
248-
unimplemented!()
252+
fn get_variable_type(&self, variable_names: &[String]) -> Option<DataType> {
253+
match variable_names {
254+
[var] if var == "@variable" => Some(DataType::Date32),
255+
_ => unimplemented!(),
256+
}
249257
}
250258

251259
fn get_window_meta(&self, name: &str) -> Option<Arc<WindowUDF>> {

datafusion/sql/tests/sql_integration.rs

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4522,6 +4522,43 @@ fn test_parse_escaped_string_literal_value() {
45224522
);
45234523
}
45244524

4525+
#[test]
4526+
fn test_parse_quoted_column_name_with_at_sign() {
4527+
let sql = r"SELECT `@column` FROM `@quoted_identifier_names_table`";
4528+
let plan = logical_plan(sql).unwrap();
4529+
assert_snapshot!(
4530+
plan,
4531+
@r#"
4532+
Projection: @quoted_identifier_names_table.@column
4533+
TableScan: @quoted_identifier_names_table
4534+
"#
4535+
);
4536+
4537+
let sql = r"SELECT `@quoted_identifier_names_table`.`@column` FROM `@quoted_identifier_names_table`";
4538+
let plan = logical_plan(sql).unwrap();
4539+
assert_snapshot!(
4540+
plan,
4541+
@r#"
4542+
Projection: @quoted_identifier_names_table.@column
4543+
TableScan: @quoted_identifier_names_table
4544+
"#
4545+
);
4546+
}
4547+
4548+
#[test]
4549+
fn test_variable_identifier() {
4550+
let sql = r"SELECT t_date32 FROM test WHERE t_date32 = @variable";
4551+
let plan = logical_plan(sql).unwrap();
4552+
assert_snapshot!(
4553+
plan,
4554+
@r#"
4555+
Projection: test.t_date32
4556+
Filter: test.t_date32 = @variable
4557+
TableScan: test
4558+
"#
4559+
);
4560+
}
4561+
45254562
#[test]
45264563
fn plan_create_index() {
45274564
let sql =

0 commit comments

Comments
 (0)