Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/ast/query.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
4 changes: 2 additions & 2 deletions src/ast/spans.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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())),
}
}
}
Expand Down
5 changes: 3 additions & 2 deletions src/parser/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
46 changes: 40 additions & 6 deletions tests/sqlparser_common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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!(),
Expand All @@ -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")),
]))
);
}
Expand All @@ -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!(),
Expand All @@ -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| {
Expand All @@ -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!(),
Expand Down Expand Up @@ -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#"
Expand Down
12 changes: 8 additions & 4 deletions tests/sqlparser_duckdb.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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]);
Expand All @@ -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()
},
);
Expand All @@ -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()
});
Expand Down
16 changes: 11 additions & 5 deletions tests/sqlparser_snowflake.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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]);
Expand All @@ -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()
},
);
Expand All @@ -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()
});
Expand Down Expand Up @@ -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"),
Expand Down