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-sqlparser-rs.git
The following commit(s) were added to refs/heads/main by this push:
new 49bdb5ca Snowflake: parse EXCLUDE column list as ObjectName to support
qualified names (#2244)
49bdb5ca is described below
commit 49bdb5ca2be7eb9b74b8471f63344d8fa296b337
Author: Yoabot <[email protected]>
AuthorDate: Fri Feb 27 14:58:52 2026 +0100
Snowflake: parse EXCLUDE column list as ObjectName to support qualified
names (#2244)
Co-authored-by: Yoav Cohen <[email protected]>
---
src/ast/query.rs | 4 ++--
src/ast/spans.rs | 4 ++--
src/parser/mod.rs | 5 +++--
tests/sqlparser_common.rs | 46 ++++++++++++++++++++++++++++++++++++++------
tests/sqlparser_duckdb.rs | 12 ++++++++----
tests/sqlparser_snowflake.rs | 16 ++++++++++-----
6 files changed, 66 insertions(+), 21 deletions(-)
diff --git a/src/ast/query.rs b/src/ast/query.rs
index 159f02a6..440928ed 100644
--- a/src/ast/query.rs
+++ b/src/ast/query.rs
@@ -1018,13 +1018,13 @@ pub enum ExcludeSelectItem {
/// ```plaintext
/// <col_name>
/// ```
- Single(Ident),
+ Single(ObjectName),
/// Multiple column names inside parenthesis.
/// # Syntax
/// ```plaintext
/// (<col_name>, <col_name>, ...)
/// ```
- Multiple(Vec<Ident>),
+ Multiple(Vec<ObjectName>),
}
impl fmt::Display for ExcludeSelectItem {
diff --git a/src/ast/spans.rs b/src/ast/spans.rs
index 0b95c3ed..43005cfb 100644
--- a/src/ast/spans.rs
+++ b/src/ast/spans.rs
@@ -1849,8 +1849,8 @@ impl Spanned for IlikeSelectItem {
impl Spanned for ExcludeSelectItem {
fn span(&self) -> Span {
match self {
- ExcludeSelectItem::Single(ident) => ident.span,
- ExcludeSelectItem::Multiple(vec) => union_spans(vec.iter().map(|i|
i.span)),
+ ExcludeSelectItem::Single(name) => name.span(),
+ ExcludeSelectItem::Multiple(vec) => union_spans(vec.iter().map(|i|
i.span())),
}
}
}
diff --git a/src/parser/mod.rs b/src/parser/mod.rs
index 8d8b55a3..bc91213f 100644
--- a/src/parser/mod.rs
+++ b/src/parser/mod.rs
@@ -17951,11 +17951,12 @@ impl<'a> Parser<'a> {
) -> Result<Option<ExcludeSelectItem>, ParserError> {
let opt_exclude = if self.parse_keyword(Keyword::EXCLUDE) {
if self.consume_token(&Token::LParen) {
- let columns = self.parse_comma_separated(|parser|
parser.parse_identifier())?;
+ let columns =
+ self.parse_comma_separated(|parser|
parser.parse_object_name(false))?;
self.expect_token(&Token::RParen)?;
Some(ExcludeSelectItem::Multiple(columns))
} else {
- let column = self.parse_identifier()?;
+ let column = self.parse_object_name(false)?;
Some(ExcludeSelectItem::Single(column))
}
} else {
diff --git a/tests/sqlparser_common.rs b/tests/sqlparser_common.rs
index 8de460d7..ad7697a7 100644
--- a/tests/sqlparser_common.rs
+++ b/tests/sqlparser_common.rs
@@ -17343,7 +17343,9 @@ fn test_select_exclude() {
SelectItem::Wildcard(WildcardAdditionalOptions { opt_exclude, .. }) =>
{
assert_eq!(
*opt_exclude,
- Some(ExcludeSelectItem::Single(Ident::new("c1")))
+ Some(ExcludeSelectItem::Single(ObjectName::from(Ident::new(
+ "c1"
+ ))))
);
}
_ => unreachable!(),
@@ -17356,8 +17358,8 @@ fn test_select_exclude() {
assert_eq!(
*opt_exclude,
Some(ExcludeSelectItem::Multiple(vec![
- Ident::new("c1"),
- Ident::new("c2")
+ ObjectName::from(Ident::new("c1")),
+ ObjectName::from(Ident::new("c2")),
]))
);
}
@@ -17368,7 +17370,9 @@ fn test_select_exclude() {
SelectItem::Wildcard(WildcardAdditionalOptions { opt_exclude, .. }) =>
{
assert_eq!(
*opt_exclude,
- Some(ExcludeSelectItem::Single(Ident::new("c1")))
+ Some(ExcludeSelectItem::Single(ObjectName::from(Ident::new(
+ "c1"
+ ))))
);
}
_ => unreachable!(),
@@ -17390,7 +17394,9 @@ fn test_select_exclude() {
}
assert_eq!(
select.exclude,
- Some(ExcludeSelectItem::Single(Ident::new("c1")))
+ Some(ExcludeSelectItem::Single(ObjectName::from(Ident::new(
+ "c1"
+ ))))
);
let dialects = all_dialects_where(|d| {
@@ -17401,7 +17407,9 @@ fn test_select_exclude() {
SelectItem::Wildcard(WildcardAdditionalOptions { opt_exclude, .. }) =>
{
assert_eq!(
*opt_exclude,
- Some(ExcludeSelectItem::Single(Ident::new("c1")))
+ Some(ExcludeSelectItem::Single(ObjectName::from(Ident::new(
+ "c1"
+ ))))
);
}
_ => unreachable!(),
@@ -17438,6 +17446,32 @@ fn test_select_exclude() {
);
}
+#[test]
+fn test_select_exclude_qualified_names() {
+ // EXCLUDE should accept qualified names like `f.col` parsed as ObjectName.
+ let dialects = all_dialects_where(|d|
d.supports_select_wildcard_exclude());
+
+ // Qualified name in multi-column EXCLUDE list: f.* EXCLUDE (f.col1,
f.col2)
+ let select = dialects
+ .verified_only_select("SELECT f.* EXCLUDE (f.account_canonical_id,
f.amount) FROM t AS f");
+ match &select.projection[0] {
+ SelectItem::QualifiedWildcard(_, WildcardAdditionalOptions {
opt_exclude, .. }) => {
+ assert_eq!(
+ *opt_exclude,
+ Some(ExcludeSelectItem::Multiple(vec![
+ ObjectName::from(vec![Ident::new("f"),
Ident::new("account_canonical_id")]),
+ ObjectName::from(vec![Ident::new("f"),
Ident::new("amount")]),
+ ]))
+ );
+ }
+ _ => unreachable!(),
+ }
+
+ // Plain identifiers must still parse successfully.
+ dialects.verified_only_select("SELECT f.* EXCLUDE (account_canonical_id)
FROM t AS f");
+ dialects.verified_only_select("SELECT f.* EXCLUDE (col1, col2) FROM t AS
f");
+}
+
#[test]
fn test_no_semicolon_required_between_statements() {
let sql = r#"
diff --git a/tests/sqlparser_duckdb.rs b/tests/sqlparser_duckdb.rs
index e0e3f143..a061876d 100644
--- a/tests/sqlparser_duckdb.rs
+++ b/tests/sqlparser_duckdb.rs
@@ -156,7 +156,9 @@ fn column_defs(statement: Statement) -> Vec<ColumnDef> {
fn test_select_wildcard_with_exclude() {
let select = duckdb().verified_only_select("SELECT * EXCLUDE (col_a) FROM
data");
let expected = SelectItem::Wildcard(WildcardAdditionalOptions {
- opt_exclude:
Some(ExcludeSelectItem::Multiple(vec![Ident::new("col_a")])),
+ opt_exclude: Some(ExcludeSelectItem::Multiple(vec![ObjectName::from(
+ Ident::new("col_a"),
+ )])),
..Default::default()
});
assert_eq!(expected, select.projection[0]);
@@ -166,7 +168,9 @@ fn test_select_wildcard_with_exclude() {
let expected = SelectItem::QualifiedWildcard(
SelectItemQualifiedWildcardKind::ObjectName(ObjectName::from(vec![Ident::new("name")])),
WildcardAdditionalOptions {
- opt_exclude:
Some(ExcludeSelectItem::Single(Ident::new("department_id"))),
+ opt_exclude:
Some(ExcludeSelectItem::Single(ObjectName::from(Ident::new(
+ "department_id",
+ )))),
..Default::default()
},
);
@@ -176,8 +180,8 @@ fn test_select_wildcard_with_exclude() {
.verified_only_select("SELECT * EXCLUDE (department_id, employee_id)
FROM employee_table");
let expected = SelectItem::Wildcard(WildcardAdditionalOptions {
opt_exclude: Some(ExcludeSelectItem::Multiple(vec![
- Ident::new("department_id"),
- Ident::new("employee_id"),
+ ObjectName::from(Ident::new("department_id")),
+ ObjectName::from(Ident::new("employee_id")),
])),
..Default::default()
});
diff --git a/tests/sqlparser_snowflake.rs b/tests/sqlparser_snowflake.rs
index 43444016..c51cf3bd 100644
--- a/tests/sqlparser_snowflake.rs
+++ b/tests/sqlparser_snowflake.rs
@@ -1474,7 +1474,9 @@ fn snowflake_and_generic() -> TestedDialects {
fn test_select_wildcard_with_exclude() {
let select = snowflake_and_generic().verified_only_select("SELECT *
EXCLUDE (col_a) FROM data");
let expected = SelectItem::Wildcard(WildcardAdditionalOptions {
- opt_exclude:
Some(ExcludeSelectItem::Multiple(vec![Ident::new("col_a")])),
+ opt_exclude: Some(ExcludeSelectItem::Multiple(vec![ObjectName::from(
+ Ident::new("col_a"),
+ )])),
..Default::default()
});
assert_eq!(expected, select.projection[0]);
@@ -1484,7 +1486,9 @@ fn test_select_wildcard_with_exclude() {
let expected = SelectItem::QualifiedWildcard(
SelectItemQualifiedWildcardKind::ObjectName(ObjectName::from(vec![Ident::new("name")])),
WildcardAdditionalOptions {
- opt_exclude:
Some(ExcludeSelectItem::Single(Ident::new("department_id"))),
+ opt_exclude:
Some(ExcludeSelectItem::Single(ObjectName::from(Ident::new(
+ "department_id",
+ )))),
..Default::default()
},
);
@@ -1494,8 +1498,8 @@ fn test_select_wildcard_with_exclude() {
.verified_only_select("SELECT * EXCLUDE (department_id, employee_id)
FROM employee_table");
let expected = SelectItem::Wildcard(WildcardAdditionalOptions {
opt_exclude: Some(ExcludeSelectItem::Multiple(vec![
- Ident::new("department_id"),
- Ident::new("employee_id"),
+ ObjectName::from(Ident::new("department_id")),
+ ObjectName::from(Ident::new("employee_id")),
])),
..Default::default()
});
@@ -1580,7 +1584,9 @@ fn test_select_wildcard_with_exclude_and_rename() {
let select = snowflake_and_generic()
.verified_only_select("SELECT * EXCLUDE col_z RENAME col_a AS col_b
FROM data");
let expected = SelectItem::Wildcard(WildcardAdditionalOptions {
- opt_exclude: Some(ExcludeSelectItem::Single(Ident::new("col_z"))),
+ opt_exclude:
Some(ExcludeSelectItem::Single(ObjectName::from(Ident::new(
+ "col_z",
+ )))),
opt_rename: Some(RenameSelectItem::Single(IdentWithAlias {
ident: Ident::new("col_a"),
alias: Ident::new("col_b"),
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]