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]

Reply via email to