Skip to content

Commit fa6bd01

Browse files
authored
Support EXCLUDE support for snowflake and generic dialect (#721)
The exclude clause can be used after a possibly qualified on SELECT
1 parent 3df0e44 commit fa6bd01

File tree

7 files changed

+170
-21
lines changed

7 files changed

+170
-21
lines changed

src/ast/mod.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,9 @@ pub use self::ddl::{
3131
};
3232
pub use self::operator::{BinaryOperator, UnaryOperator};
3333
pub use self::query::{
34-
Cte, Fetch, Join, JoinConstraint, JoinOperator, LateralView, LockType, Offset, OffsetRows,
35-
OrderByExpr, Query, Select, SelectInto, SelectItem, SetExpr, SetOperator, SetQuantifier,
36-
TableAlias, TableFactor, TableWithJoins, Top, Values, With,
34+
Cte, ExcludeSelectItem, Fetch, Join, JoinConstraint, JoinOperator, LateralView, LockType,
35+
Offset, OffsetRows, OrderByExpr, Query, Select, SelectInto, SelectItem, SetExpr, SetOperator,
36+
SetQuantifier, TableAlias, TableFactor, TableWithJoins, Top, Values, With,
3737
};
3838
pub use self::value::{escape_quoted_string, DateTimeField, TrimWhereField, Value};
3939

src/ast/query.rs

Lines changed: 56 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -321,18 +321,70 @@ pub enum SelectItem {
321321
/// An expression, followed by `[ AS ] alias`
322322
ExprWithAlias { expr: Expr, alias: Ident },
323323
/// `alias.*` or even `schema.table.*`
324-
QualifiedWildcard(ObjectName),
324+
QualifiedWildcard(ObjectName, Option<ExcludeSelectItem>),
325325
/// An unqualified `*`
326-
Wildcard,
326+
Wildcard(Option<ExcludeSelectItem>),
327+
}
328+
329+
/// Snowflake `EXCLUDE` information.
330+
///
331+
/// # Syntax
332+
/// ```plaintext
333+
/// <col_name>
334+
/// | (<col_name>, <col_name>, ...)
335+
/// ```
336+
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
337+
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
338+
pub enum ExcludeSelectItem {
339+
/// Single column name without parenthesis.
340+
///
341+
/// # Syntax
342+
/// ```plaintext
343+
/// <col_name>
344+
/// ```
345+
Single(Ident),
346+
/// Multiple column names inside parenthesis.
347+
/// # Syntax
348+
/// ```plaintext
349+
/// (<col_name>, <col_name>, ...)
350+
/// ```
351+
Multiple(Vec<Ident>),
352+
}
353+
354+
impl fmt::Display for ExcludeSelectItem {
355+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
356+
write!(f, "EXCLUDE")?;
357+
match self {
358+
Self::Single(column) => {
359+
write!(f, " {column}")?;
360+
}
361+
Self::Multiple(columns) => {
362+
write!(f, " ({})", display_comma_separated(columns))?;
363+
}
364+
}
365+
Ok(())
366+
}
327367
}
328368

329369
impl fmt::Display for SelectItem {
330370
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
331371
match &self {
332372
SelectItem::UnnamedExpr(expr) => write!(f, "{}", expr),
333373
SelectItem::ExprWithAlias { expr, alias } => write!(f, "{} AS {}", expr, alias),
334-
SelectItem::QualifiedWildcard(prefix) => write!(f, "{}.*", prefix),
335-
SelectItem::Wildcard => write!(f, "*"),
374+
SelectItem::QualifiedWildcard(prefix, opt_exclude) => {
375+
write!(f, "{}.*", prefix)?;
376+
if let Some(exclude) = opt_exclude {
377+
write!(f, " {exclude}")?;
378+
}
379+
Ok(())
380+
}
381+
SelectItem::Wildcard(opt_exclude) => {
382+
write!(f, "*")?;
383+
if let Some(exclude) = opt_exclude {
384+
write!(f, " {exclude}")?;
385+
}
386+
Ok(())
387+
}
336388
}
337389
}
338390
}

src/keywords.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,7 @@ define_keywords!(
227227
EVENT,
228228
EVERY,
229229
EXCEPT,
230+
EXCLUDE,
230231
EXEC,
231232
EXECUTE,
232233
EXISTS,

src/parser.rs

Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5423,11 +5423,49 @@ impl<'a> Parser<'a> {
54235423
None => SelectItem::UnnamedExpr(expr),
54245424
})
54255425
}
5426-
WildcardExpr::QualifiedWildcard(prefix) => Ok(SelectItem::QualifiedWildcard(prefix)),
5427-
WildcardExpr::Wildcard => Ok(SelectItem::Wildcard),
5426+
WildcardExpr::QualifiedWildcard(prefix) => {
5427+
let opt_exclude = if dialect_of!(self is GenericDialect | SnowflakeDialect) {
5428+
self.parse_optional_select_item_exclude()?
5429+
} else {
5430+
None
5431+
};
5432+
5433+
Ok(SelectItem::QualifiedWildcard(prefix, opt_exclude))
5434+
}
5435+
WildcardExpr::Wildcard => {
5436+
let opt_exclude = if dialect_of!(self is GenericDialect | SnowflakeDialect) {
5437+
self.parse_optional_select_item_exclude()?
5438+
} else {
5439+
None
5440+
};
5441+
5442+
Ok(SelectItem::Wildcard(opt_exclude))
5443+
}
54285444
}
54295445
}
54305446

5447+
/// Parse an [`Exclude`](ExcludeSelectItem) information for wildcard select items.
5448+
///
5449+
/// If it is not possible to parse it, will return an option.
5450+
pub fn parse_optional_select_item_exclude(
5451+
&mut self,
5452+
) -> Result<Option<ExcludeSelectItem>, ParserError> {
5453+
let opt_exclude = if self.parse_keyword(Keyword::EXCLUDE) {
5454+
if self.consume_token(&Token::LParen) {
5455+
let columns = self.parse_comma_separated(|parser| parser.parse_identifier())?;
5456+
self.expect_token(&Token::RParen)?;
5457+
Some(ExcludeSelectItem::Multiple(columns))
5458+
} else {
5459+
let column = self.parse_identifier()?;
5460+
Some(ExcludeSelectItem::Single(column))
5461+
}
5462+
} else {
5463+
None
5464+
};
5465+
5466+
Ok(opt_exclude)
5467+
}
5468+
54315469
/// Parse an expression, optionally followed by ASC or DESC (used in ORDER BY)
54325470
pub fn parse_order_by_expr(&mut self) -> Result<OrderByExpr, ParserError> {
54335471
let expr = self.parse_expr()?;

tests/sqlparser_common.rs

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -578,22 +578,22 @@ fn parse_select_into() {
578578
fn parse_select_wildcard() {
579579
let sql = "SELECT * FROM foo";
580580
let select = verified_only_select(sql);
581-
assert_eq!(&SelectItem::Wildcard, only(&select.projection));
581+
assert_eq!(&SelectItem::Wildcard(None), only(&select.projection));
582582

583583
let sql = "SELECT foo.* FROM foo";
584584
let select = verified_only_select(sql);
585585
assert_eq!(
586-
&SelectItem::QualifiedWildcard(ObjectName(vec![Ident::new("foo")])),
586+
&SelectItem::QualifiedWildcard(ObjectName(vec![Ident::new("foo")]), None),
587587
only(&select.projection)
588588
);
589589

590590
let sql = "SELECT myschema.mytable.* FROM myschema.mytable";
591591
let select = verified_only_select(sql);
592592
assert_eq!(
593-
&SelectItem::QualifiedWildcard(ObjectName(vec![
594-
Ident::new("myschema"),
595-
Ident::new("mytable"),
596-
])),
593+
&SelectItem::QualifiedWildcard(
594+
ObjectName(vec![Ident::new("myschema"), Ident::new("mytable"),]),
595+
None
596+
),
597597
only(&select.projection)
598598
);
599599

@@ -5432,7 +5432,7 @@ fn parse_merge() {
54325432
body: Box::new(SetExpr::Select(Box::new(Select {
54335433
distinct: false,
54345434
top: None,
5435-
projection: vec![SelectItem::Wildcard],
5435+
projection: vec![SelectItem::Wildcard(None)],
54365436
into: None,
54375437
from: vec![TableWithJoins {
54385438
relation: TableFactor::Table {

tests/sqlparser_postgres.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1229,7 +1229,7 @@ fn parse_pg_returning() {
12291229
pg_and_generic().verified_stmt("DELETE FROM tasks WHERE status = 'DONE' RETURNING *");
12301230
match stmt {
12311231
Statement::Delete { returning, .. } => {
1232-
assert_eq!(Some(vec![SelectItem::Wildcard,]), returning);
1232+
assert_eq!(Some(vec![SelectItem::Wildcard(None),]), returning);
12331233
}
12341234
_ => unreachable!(),
12351235
};

tests/sqlparser_snowflake.rs

Lines changed: 62 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,14 @@
1414
//! Test SQL syntax specific to Snowflake. The parser based on the
1515
//! generic dialect is also tested (on the inputs it can handle).
1616
17-
#[macro_use]
18-
mod test_utils;
19-
use test_utils::*;
20-
2117
use sqlparser::ast::*;
2218
use sqlparser::dialect::{GenericDialect, SnowflakeDialect};
2319
use sqlparser::parser::ParserError;
2420
use sqlparser::tokenizer::*;
21+
use test_utils::*;
22+
23+
#[macro_use]
24+
mod test_utils;
2525

2626
#[test]
2727
fn test_snowflake_create_table() {
@@ -364,3 +364,61 @@ fn snowflake_and_generic() -> TestedDialects {
364364
dialects: vec![Box::new(SnowflakeDialect {}), Box::new(GenericDialect {})],
365365
}
366366
}
367+
368+
#[test]
369+
fn test_select_wildcard_with_exclude() {
370+
match snowflake_and_generic().verified_stmt("SELECT * EXCLUDE (col_a) FROM data") {
371+
Statement::Query(query) => match *query.body {
372+
SetExpr::Select(select) => match &select.projection[0] {
373+
SelectItem::Wildcard(Some(exclude)) => {
374+
assert_eq!(
375+
*exclude,
376+
ExcludeSelectItem::Multiple(vec![Ident::new("col_a")])
377+
)
378+
}
379+
_ => unreachable!(),
380+
},
381+
_ => unreachable!(),
382+
},
383+
_ => unreachable!(),
384+
};
385+
386+
match snowflake_and_generic()
387+
.verified_stmt("SELECT name.* EXCLUDE department_id FROM employee_table")
388+
{
389+
Statement::Query(query) => match *query.body {
390+
SetExpr::Select(select) => match &select.projection[0] {
391+
SelectItem::QualifiedWildcard(_, Some(exclude)) => {
392+
assert_eq!(
393+
*exclude,
394+
ExcludeSelectItem::Single(Ident::new("department_id"))
395+
)
396+
}
397+
_ => unreachable!(),
398+
},
399+
_ => unreachable!(),
400+
},
401+
_ => unreachable!(),
402+
};
403+
404+
match snowflake_and_generic()
405+
.verified_stmt("SELECT * EXCLUDE (department_id, employee_id) FROM employee_table")
406+
{
407+
Statement::Query(query) => match *query.body {
408+
SetExpr::Select(select) => match &select.projection[0] {
409+
SelectItem::Wildcard(Some(exclude)) => {
410+
assert_eq!(
411+
*exclude,
412+
ExcludeSelectItem::Multiple(vec![
413+
Ident::new("department_id"),
414+
Ident::new("employee_id")
415+
])
416+
)
417+
}
418+
_ => unreachable!(),
419+
},
420+
_ => unreachable!(),
421+
},
422+
_ => unreachable!(),
423+
};
424+
}

0 commit comments

Comments
 (0)