This is an automated email from the ASF dual-hosted git repository.
github-bot pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/datafusion.git
The following commit(s) were added to refs/heads/main by this push:
new 4e0161d99c fix: Don't treat quoted column names as placeholder
variables in SQL (#19339)
4e0161d99c is described below
commit 4e0161d99cc6558679abd1889ddf9763eed25cfc
Author: pmallex <[email protected]>
AuthorDate: Sat Jan 10 19:19:49 2026 -0800
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.
---
datafusion/sql/src/expr/identifier.rs | 4 ++--
datafusion/sql/tests/common/mod.rs | 12 +++++++++--
datafusion/sql/tests/sql_integration.rs | 37 +++++++++++++++++++++++++++++++++
3 files changed, 49 insertions(+), 4 deletions(-)
diff --git a/datafusion/sql/src/expr/identifier.rs
b/datafusion/sql/src/expr/identifier.rs
index 4c23c7a818..34fbe2edf8 100644
--- a/datafusion/sql/src/expr/identifier.rs
+++ b/datafusion/sql/src/expr/identifier.rs
@@ -37,7 +37,7 @@ impl<S: ContextProvider> SqlToRel<'_, S> {
planner_context: &mut PlannerContext,
) -> Result<Expr> {
let id_span = id.span;
- if id.value.starts_with('@') {
+ if id.value.starts_with('@') && id.quote_style.is_none() {
// TODO: figure out if ScalarVariables should be insensitive.
let var_names = vec![id.value];
let field = self
@@ -111,7 +111,7 @@ impl<S: ContextProvider> SqlToRel<'_, S> {
.filter_map(|id| Span::try_from_sqlparser_span(id.span)),
);
- if ids[0].value.starts_with('@') {
+ if ids[0].value.starts_with('@') && ids[0].quote_style.is_none() {
let var_names: Vec<_> = ids
.into_iter()
.map(|id| self.ident_normalizer.normalize(id))
diff --git a/datafusion/sql/tests/common/mod.rs
b/datafusion/sql/tests/common/mod.rs
index 44dd7cec89..9dc6b895e4 100644
--- a/datafusion/sql/tests/common/mod.rs
+++ b/datafusion/sql/tests/common/mod.rs
@@ -227,6 +227,11 @@ impl ContextProvider for MockContextProvider {
false,
),
])),
+ "@quoted_identifier_names_table" => Ok(Schema::new(vec![Field::new(
+ "@column",
+ DataType::UInt32,
+ false,
+ )])),
_ => plan_err!("No table named: {} found", name.table()),
};
@@ -244,8 +249,11 @@ impl ContextProvider for MockContextProvider {
self.state.aggregate_functions.get(name).cloned()
}
- fn get_variable_type(&self, _: &[String]) -> Option<DataType> {
- unimplemented!()
+ fn get_variable_type(&self, variable_names: &[String]) -> Option<DataType>
{
+ match variable_names {
+ [var] if var == "@variable" => Some(DataType::Date32),
+ _ => unimplemented!(),
+ }
}
fn get_window_meta(&self, name: &str) -> Option<Arc<WindowUDF>> {
diff --git a/datafusion/sql/tests/sql_integration.rs
b/datafusion/sql/tests/sql_integration.rs
index 969d56afda..491873b4af 100644
--- a/datafusion/sql/tests/sql_integration.rs
+++ b/datafusion/sql/tests/sql_integration.rs
@@ -4522,6 +4522,43 @@ fn test_parse_escaped_string_literal_value() {
);
}
+#[test]
+fn test_parse_quoted_column_name_with_at_sign() {
+ let sql = r"SELECT `@column` FROM `@quoted_identifier_names_table`";
+ let plan = logical_plan(sql).unwrap();
+ assert_snapshot!(
+ plan,
+ @r#"
+ Projection: @quoted_identifier_names_table.@column
+ TableScan: @quoted_identifier_names_table
+ "#
+ );
+
+ let sql = r"SELECT `@quoted_identifier_names_table`.`@column` FROM
`@quoted_identifier_names_table`";
+ let plan = logical_plan(sql).unwrap();
+ assert_snapshot!(
+ plan,
+ @r#"
+ Projection: @quoted_identifier_names_table.@column
+ TableScan: @quoted_identifier_names_table
+ "#
+ );
+}
+
+#[test]
+fn test_variable_identifier() {
+ let sql = r"SELECT t_date32 FROM test WHERE t_date32 = @variable";
+ let plan = logical_plan(sql).unwrap();
+ assert_snapshot!(
+ plan,
+ @r#"
+ Projection: test.t_date32
+ Filter: test.t_date32 = @variable
+ TableScan: test
+ "#
+ );
+}
+
#[test]
fn plan_create_index() {
let sql =
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]