Skip to content

Commit

Permalink
Support EXCLUDE support for snowflake and generic dialect (#721)
Browse files Browse the repository at this point in the history
The exclude clause can be used after a possibly qualified on SELECT
  • Loading branch information
AugustoFKL committed Nov 30, 2022
1 parent 3df0e44 commit fa6bd01
Show file tree
Hide file tree
Showing 7 changed files with 170 additions and 21 deletions.
6 changes: 3 additions & 3 deletions src/ast/mod.rs
Expand Up @@ -31,9 +31,9 @@ pub use self::ddl::{
};
pub use self::operator::{BinaryOperator, UnaryOperator};
pub use self::query::{
Cte, Fetch, Join, JoinConstraint, JoinOperator, LateralView, LockType, Offset, OffsetRows,
OrderByExpr, Query, Select, SelectInto, SelectItem, SetExpr, SetOperator, SetQuantifier,
TableAlias, TableFactor, TableWithJoins, Top, Values, With,
Cte, ExcludeSelectItem, Fetch, Join, JoinConstraint, JoinOperator, LateralView, LockType,
Offset, OffsetRows, OrderByExpr, Query, Select, SelectInto, SelectItem, SetExpr, SetOperator,
SetQuantifier, TableAlias, TableFactor, TableWithJoins, Top, Values, With,
};
pub use self::value::{escape_quoted_string, DateTimeField, TrimWhereField, Value};

Expand Down
60 changes: 56 additions & 4 deletions src/ast/query.rs
Expand Up @@ -321,18 +321,70 @@ pub enum SelectItem {
/// An expression, followed by `[ AS ] alias`
ExprWithAlias { expr: Expr, alias: Ident },
/// `alias.*` or even `schema.table.*`
QualifiedWildcard(ObjectName),
QualifiedWildcard(ObjectName, Option<ExcludeSelectItem>),
/// An unqualified `*`
Wildcard,
Wildcard(Option<ExcludeSelectItem>),
}

/// Snowflake `EXCLUDE` information.
///
/// # Syntax
/// ```plaintext
/// <col_name>
/// | (<col_name>, <col_name>, ...)
/// ```
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum ExcludeSelectItem {
/// Single column name without parenthesis.
///
/// # Syntax
/// ```plaintext
/// <col_name>
/// ```
Single(Ident),
/// Multiple column names inside parenthesis.
/// # Syntax
/// ```plaintext
/// (<col_name>, <col_name>, ...)
/// ```
Multiple(Vec<Ident>),
}

impl fmt::Display for ExcludeSelectItem {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "EXCLUDE")?;
match self {
Self::Single(column) => {
write!(f, " {column}")?;
}
Self::Multiple(columns) => {
write!(f, " ({})", display_comma_separated(columns))?;
}
}
Ok(())
}
}

impl fmt::Display for SelectItem {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match &self {
SelectItem::UnnamedExpr(expr) => write!(f, "{}", expr),
SelectItem::ExprWithAlias { expr, alias } => write!(f, "{} AS {}", expr, alias),
SelectItem::QualifiedWildcard(prefix) => write!(f, "{}.*", prefix),
SelectItem::Wildcard => write!(f, "*"),
SelectItem::QualifiedWildcard(prefix, opt_exclude) => {
write!(f, "{}.*", prefix)?;
if let Some(exclude) = opt_exclude {
write!(f, " {exclude}")?;
}
Ok(())
}
SelectItem::Wildcard(opt_exclude) => {
write!(f, "*")?;
if let Some(exclude) = opt_exclude {
write!(f, " {exclude}")?;
}
Ok(())
}
}
}
}
Expand Down
1 change: 1 addition & 0 deletions src/keywords.rs
Expand Up @@ -227,6 +227,7 @@ define_keywords!(
EVENT,
EVERY,
EXCEPT,
EXCLUDE,
EXEC,
EXECUTE,
EXISTS,
Expand Down
42 changes: 40 additions & 2 deletions src/parser.rs
Expand Up @@ -5423,11 +5423,49 @@ impl<'a> Parser<'a> {
None => SelectItem::UnnamedExpr(expr),
})
}
WildcardExpr::QualifiedWildcard(prefix) => Ok(SelectItem::QualifiedWildcard(prefix)),
WildcardExpr::Wildcard => Ok(SelectItem::Wildcard),
WildcardExpr::QualifiedWildcard(prefix) => {
let opt_exclude = if dialect_of!(self is GenericDialect | SnowflakeDialect) {
self.parse_optional_select_item_exclude()?
} else {
None
};

Ok(SelectItem::QualifiedWildcard(prefix, opt_exclude))
}
WildcardExpr::Wildcard => {
let opt_exclude = if dialect_of!(self is GenericDialect | SnowflakeDialect) {
self.parse_optional_select_item_exclude()?
} else {
None
};

Ok(SelectItem::Wildcard(opt_exclude))
}
}
}

/// Parse an [`Exclude`](ExcludeSelectItem) information for wildcard select items.
///
/// If it is not possible to parse it, will return an option.
pub fn parse_optional_select_item_exclude(
&mut self,
) -> 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())?;
self.expect_token(&Token::RParen)?;
Some(ExcludeSelectItem::Multiple(columns))
} else {
let column = self.parse_identifier()?;
Some(ExcludeSelectItem::Single(column))
}
} else {
None
};

Ok(opt_exclude)
}

/// Parse an expression, optionally followed by ASC or DESC (used in ORDER BY)
pub fn parse_order_by_expr(&mut self) -> Result<OrderByExpr, ParserError> {
let expr = self.parse_expr()?;
Expand Down
14 changes: 7 additions & 7 deletions tests/sqlparser_common.rs
Expand Up @@ -578,22 +578,22 @@ fn parse_select_into() {
fn parse_select_wildcard() {
let sql = "SELECT * FROM foo";
let select = verified_only_select(sql);
assert_eq!(&SelectItem::Wildcard, only(&select.projection));
assert_eq!(&SelectItem::Wildcard(None), only(&select.projection));

let sql = "SELECT foo.* FROM foo";
let select = verified_only_select(sql);
assert_eq!(
&SelectItem::QualifiedWildcard(ObjectName(vec![Ident::new("foo")])),
&SelectItem::QualifiedWildcard(ObjectName(vec![Ident::new("foo")]), None),
only(&select.projection)
);

let sql = "SELECT myschema.mytable.* FROM myschema.mytable";
let select = verified_only_select(sql);
assert_eq!(
&SelectItem::QualifiedWildcard(ObjectName(vec![
Ident::new("myschema"),
Ident::new("mytable"),
])),
&SelectItem::QualifiedWildcard(
ObjectName(vec![Ident::new("myschema"), Ident::new("mytable"),]),
None
),
only(&select.projection)
);

Expand Down Expand Up @@ -5432,7 +5432,7 @@ fn parse_merge() {
body: Box::new(SetExpr::Select(Box::new(Select {
distinct: false,
top: None,
projection: vec![SelectItem::Wildcard],
projection: vec![SelectItem::Wildcard(None)],
into: None,
from: vec![TableWithJoins {
relation: TableFactor::Table {
Expand Down
2 changes: 1 addition & 1 deletion tests/sqlparser_postgres.rs
Expand Up @@ -1229,7 +1229,7 @@ fn parse_pg_returning() {
pg_and_generic().verified_stmt("DELETE FROM tasks WHERE status = 'DONE' RETURNING *");
match stmt {
Statement::Delete { returning, .. } => {
assert_eq!(Some(vec![SelectItem::Wildcard,]), returning);
assert_eq!(Some(vec![SelectItem::Wildcard(None),]), returning);
}
_ => unreachable!(),
};
Expand Down
66 changes: 62 additions & 4 deletions tests/sqlparser_snowflake.rs
Expand Up @@ -14,14 +14,14 @@
//! Test SQL syntax specific to Snowflake. The parser based on the
//! generic dialect is also tested (on the inputs it can handle).

#[macro_use]
mod test_utils;
use test_utils::*;

use sqlparser::ast::*;
use sqlparser::dialect::{GenericDialect, SnowflakeDialect};
use sqlparser::parser::ParserError;
use sqlparser::tokenizer::*;
use test_utils::*;

#[macro_use]
mod test_utils;

#[test]
fn test_snowflake_create_table() {
Expand Down Expand Up @@ -364,3 +364,61 @@ fn snowflake_and_generic() -> TestedDialects {
dialects: vec![Box::new(SnowflakeDialect {}), Box::new(GenericDialect {})],
}
}

#[test]
fn test_select_wildcard_with_exclude() {
match snowflake_and_generic().verified_stmt("SELECT * EXCLUDE (col_a) FROM data") {
Statement::Query(query) => match *query.body {
SetExpr::Select(select) => match &select.projection[0] {
SelectItem::Wildcard(Some(exclude)) => {
assert_eq!(
*exclude,
ExcludeSelectItem::Multiple(vec![Ident::new("col_a")])
)
}
_ => unreachable!(),
},
_ => unreachable!(),
},
_ => unreachable!(),
};

match snowflake_and_generic()
.verified_stmt("SELECT name.* EXCLUDE department_id FROM employee_table")
{
Statement::Query(query) => match *query.body {
SetExpr::Select(select) => match &select.projection[0] {
SelectItem::QualifiedWildcard(_, Some(exclude)) => {
assert_eq!(
*exclude,
ExcludeSelectItem::Single(Ident::new("department_id"))
)
}
_ => unreachable!(),
},
_ => unreachable!(),
},
_ => unreachable!(),
};

match snowflake_and_generic()
.verified_stmt("SELECT * EXCLUDE (department_id, employee_id) FROM employee_table")
{
Statement::Query(query) => match *query.body {
SetExpr::Select(select) => match &select.projection[0] {
SelectItem::Wildcard(Some(exclude)) => {
assert_eq!(
*exclude,
ExcludeSelectItem::Multiple(vec![
Ident::new("department_id"),
Ident::new("employee_id")
])
)
}
_ => unreachable!(),
},
_ => unreachable!(),
},
_ => unreachable!(),
};
}

0 comments on commit fa6bd01

Please sign in to comment.