You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@datafusion.apache.org by lu...@apache.org on 2026/10/02 19:55:02 UTC
(datafusion-sqlparser-rs) 01/01: Gate qualified $ placeholder field access to Snowflake
This is an automated email from the ASF dual-hosted git repository.
LucaCappelletti94 pushed a commit to branch upstream/qualified-dollar-placeholder-gate
in repository https://gitbox.apache.org/repos/asf/datafusion-sqlparser-rs.git
commit 79bb1319e134560d28d0c8d9f0cad4357debe15e
Author: LucaCappelletti94 <ca...@gmail.com>
AuthorDate: Fri Oct 2 21:51:26 2026 +0200
Gate qualified $ placeholder field access to Snowflake
---
src/dialect/mod.rs | 7 +++++++
src/dialect/snowflake.rs | 5 +++++
src/parser/mod.rs | 17 +++++++++++++----
tests/sqlparser_common.rs | 38 ++++++++++++++++++++++++++++++++++++++
4 files changed, 63 insertions(+), 4 deletions(-)
diff --git a/src/dialect/mod.rs b/src/dialect/mod.rs
index 2beb5655..3b9200d8 100644
--- a/src/dialect/mod.rs
+++ b/src/dialect/mod.rs
@@ -511,6 +511,13 @@ pub trait Dialect: Debug + Any {
false
}
+ /// Returns true if a `$1`-style placeholder directly after a period is a field
+ /// name, such as the positional column references in Snowflake staged data
+ /// queries `SELECT t.$1 FROM @stage t`.
+ fn supports_placeholder_as_field_name(&self) -> bool {
+ false
+ }
+
/// Returns true if the dialect supports numbers containing underscores, e.g. `10_000_000`
fn supports_numeric_literal_underscores(&self) -> bool {
false
diff --git a/src/dialect/snowflake.rs b/src/dialect/snowflake.rs
index 3ebc1686..b927596b 100644
--- a/src/dialect/snowflake.rs
+++ b/src/dialect/snowflake.rs
@@ -698,6 +698,11 @@ impl Dialect for SnowflakeDialect {
true
}
+ /// See <https://docs.snowflake.com/en/user-guide/querying-stage>
+ fn supports_placeholder_as_field_name(&self) -> bool {
+ true
+ }
+
/// See <https://docs.snowflake.com/en/sql-reference/sql/select#parameters>
fn supports_select_wildcard_replace(&self) -> bool {
true
diff --git a/src/parser/mod.rs b/src/parser/mod.rs
index 1a46c6e5..986a0949 100644
--- a/src/parser/mod.rs
+++ b/src/parser/mod.rs
@@ -1356,9 +1356,9 @@ impl<'a> Parser<'a> {
// SQLite has single-quoted identifiers
id_parts.push(Ident::with_quote('\'', s))
}
- Token::Placeholder(s) => {
- // Snowflake uses $1, $2, etc. for positional column references
- // in staged data queries like: SELECT t.$1 FROM @stage t
+ Token::Placeholder(s)
+ if self.dialect.supports_placeholder_as_field_name() =>
+ {
id_parts.push(Ident::new(s))
}
Token::Mul => {
@@ -2108,7 +2108,7 @@ impl<'a> Parser<'a> {
chain.push(AccessExpr::Dot(expr));
self.advance_token(); // The consumed string
}
- Token::Placeholder(s) => {
+ Token::Placeholder(s) if self.dialect.supports_placeholder_as_field_name() => {
// Snowflake uses $1, $2, etc. for positional column references
// in staged data queries like: SELECT t.$1 FROM @stage t
let expr = Expr::Identifier(Ident::with_span(next_token.span, s));
@@ -2140,6 +2140,15 @@ impl<'a> Parser<'a> {
self.maybe_parse(|parser| {
let expr = parser.parse_prefix()?;
match &expr {
+ Expr::Value(ValueWithSpan {
+ value: Value::Placeholder(_),
+ ..
+ }) if !parser.dialect.supports_placeholder_as_field_name() => {
+ parser.expected_ref(
+ "an identifier or value",
+ parser.peek_token_ref(),
+ )
+ }
Expr::CompoundFieldAccess { .. }
| Expr::CompoundIdentifier(_)
| Expr::Identifier(_)
diff --git a/tests/sqlparser_common.rs b/tests/sqlparser_common.rs
index 8e08c657..c3a719e9 100644
--- a/tests/sqlparser_common.rs
+++ b/tests/sqlparser_common.rs
@@ -20433,3 +20433,41 @@ fn parse_bang_not_renders_apart_from_operand() {
dialects.verified_stmt("SET eaac_cion = ! !o");
dialects.one_statement_parses_to("SET eaac_cion = ! ! o", "SET eaac_cion = ! !o");
}
+
+#[test]
+fn parse_placeholder_as_field_name() {
+ let supported = all_dialects_where(|d| d.supports_placeholder_as_field_name());
+ let select = supported.verified_only_select("SELECT t.$1, t.$2 FROM t");
+ assert_eq!(
+ select.projection,
+ vec![
+ SelectItem::UnnamedExpr(Expr::CompoundIdentifier(vec![
+ Ident::new("t"),
+ Ident::new("$1")
+ ])),
+ SelectItem::UnnamedExpr(Expr::CompoundIdentifier(vec![
+ Ident::new("t"),
+ Ident::new("$2")
+ ])),
+ ]
+ );
+ supported.verified_stmt("SELECT (t).$1 FROM t");
+ supported.verified_stmt("SELECT t.$name FROM t");
+
+ let unsupported = all_dialects_where(|d| {
+ !d.supports_placeholder_as_field_name() && !d.is_identifier_start('$')
+ });
+ for sql in [
+ "SELECT t.$name FROM t",
+ "SELECT t.$1 FROM t",
+ "SELECT c.$ame FROM c",
+ "SELECT (t).$1 FROM t",
+ "SELECT (t).$name FROM t",
+ ] {
+ assert!(unsupported.parse_sql_statements(sql).is_err());
+ }
+
+ // Bare placeholders are unaffected by the capability.
+ unsupported.verified_stmt("SELECT $1, $2 FROM t");
+ unsupported.verified_only_select("SELECT * FROM student WHERE id = $Id1");
+}
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@datafusion.apache.org
For additional commands, e-mail: commits-help@datafusion.apache.org