Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add snowflake and generic exclude support #721

Merged
merged 1 commit into from Nov 30, 2022
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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 @@ -225,6 +225,7 @@ define_keywords!(
EVENT,
EVERY,
EXCEPT,
EXCLUDE,
EXEC,
EXECUTE,
EXISTS,
Expand Down
43 changes: 40 additions & 3 deletions src/parser.rs
Expand Up @@ -5339,11 +5339,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 Expand Up @@ -5841,7 +5879,6 @@ mod tests {

#[cfg(test)]
mod test_parse_data_type {

use crate::ast::{
CharLengthUnits, CharacterLength, DataType, ExactNumberInfo, ObjectName, TimezoneInfo,
};
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 @@ -5239,7 +5239,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!(),
};
}