Support EXCLUDE support for snowflake and generic dialect (#721)

The exclude clause can be used after a possibly qualified on SELECT
This commit is contained in:
Augusto Fotino 2022-11-30 14:29:43 -03:00 committed by GitHub
parent 3df0e444c8
commit fa6bd01b19
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 170 additions and 21 deletions

View file

@ -31,9 +31,9 @@ pub use self::ddl::{
}; };
pub use self::operator::{BinaryOperator, UnaryOperator}; pub use self::operator::{BinaryOperator, UnaryOperator};
pub use self::query::{ pub use self::query::{
Cte, Fetch, Join, JoinConstraint, JoinOperator, LateralView, LockType, Offset, OffsetRows, Cte, ExcludeSelectItem, Fetch, Join, JoinConstraint, JoinOperator, LateralView, LockType,
OrderByExpr, Query, Select, SelectInto, SelectItem, SetExpr, SetOperator, SetQuantifier, Offset, OffsetRows, OrderByExpr, Query, Select, SelectInto, SelectItem, SetExpr, SetOperator,
TableAlias, TableFactor, TableWithJoins, Top, Values, With, SetQuantifier, TableAlias, TableFactor, TableWithJoins, Top, Values, With,
}; };
pub use self::value::{escape_quoted_string, DateTimeField, TrimWhereField, Value}; pub use self::value::{escape_quoted_string, DateTimeField, TrimWhereField, Value};

View file

@ -321,9 +321,49 @@ pub enum SelectItem {
/// An expression, followed by `[ AS ] alias` /// An expression, followed by `[ AS ] alias`
ExprWithAlias { expr: Expr, alias: Ident }, ExprWithAlias { expr: Expr, alias: Ident },
/// `alias.*` or even `schema.table.*` /// `alias.*` or even `schema.table.*`
QualifiedWildcard(ObjectName), QualifiedWildcard(ObjectName, Option<ExcludeSelectItem>),
/// An unqualified `*` /// 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 { impl fmt::Display for SelectItem {
@ -331,8 +371,20 @@ impl fmt::Display for SelectItem {
match &self { match &self {
SelectItem::UnnamedExpr(expr) => write!(f, "{}", expr), SelectItem::UnnamedExpr(expr) => write!(f, "{}", expr),
SelectItem::ExprWithAlias { expr, alias } => write!(f, "{} AS {}", expr, alias), SelectItem::ExprWithAlias { expr, alias } => write!(f, "{} AS {}", expr, alias),
SelectItem::QualifiedWildcard(prefix) => write!(f, "{}.*", prefix), SelectItem::QualifiedWildcard(prefix, opt_exclude) => {
SelectItem::Wildcard => write!(f, "*"), 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(())
}
} }
} }
} }

View file

@ -227,6 +227,7 @@ define_keywords!(
EVENT, EVENT,
EVERY, EVERY,
EXCEPT, EXCEPT,
EXCLUDE,
EXEC, EXEC,
EXECUTE, EXECUTE,
EXISTS, EXISTS,

View file

@ -5423,11 +5423,49 @@ impl<'a> Parser<'a> {
None => SelectItem::UnnamedExpr(expr), None => SelectItem::UnnamedExpr(expr),
}) })
} }
WildcardExpr::QualifiedWildcard(prefix) => Ok(SelectItem::QualifiedWildcard(prefix)), WildcardExpr::QualifiedWildcard(prefix) => {
WildcardExpr::Wildcard => Ok(SelectItem::Wildcard), 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) /// Parse an expression, optionally followed by ASC or DESC (used in ORDER BY)
pub fn parse_order_by_expr(&mut self) -> Result<OrderByExpr, ParserError> { pub fn parse_order_by_expr(&mut self) -> Result<OrderByExpr, ParserError> {
let expr = self.parse_expr()?; let expr = self.parse_expr()?;

View file

@ -578,22 +578,22 @@ fn parse_select_into() {
fn parse_select_wildcard() { fn parse_select_wildcard() {
let sql = "SELECT * FROM foo"; let sql = "SELECT * FROM foo";
let select = verified_only_select(sql); 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 sql = "SELECT foo.* FROM foo";
let select = verified_only_select(sql); let select = verified_only_select(sql);
assert_eq!( assert_eq!(
&SelectItem::QualifiedWildcard(ObjectName(vec![Ident::new("foo")])), &SelectItem::QualifiedWildcard(ObjectName(vec![Ident::new("foo")]), None),
only(&select.projection) only(&select.projection)
); );
let sql = "SELECT myschema.mytable.* FROM myschema.mytable"; let sql = "SELECT myschema.mytable.* FROM myschema.mytable";
let select = verified_only_select(sql); let select = verified_only_select(sql);
assert_eq!( assert_eq!(
&SelectItem::QualifiedWildcard(ObjectName(vec![ &SelectItem::QualifiedWildcard(
Ident::new("myschema"), ObjectName(vec![Ident::new("myschema"), Ident::new("mytable"),]),
Ident::new("mytable"), None
])), ),
only(&select.projection) only(&select.projection)
); );
@ -5432,7 +5432,7 @@ fn parse_merge() {
body: Box::new(SetExpr::Select(Box::new(Select { body: Box::new(SetExpr::Select(Box::new(Select {
distinct: false, distinct: false,
top: None, top: None,
projection: vec![SelectItem::Wildcard], projection: vec![SelectItem::Wildcard(None)],
into: None, into: None,
from: vec![TableWithJoins { from: vec![TableWithJoins {
relation: TableFactor::Table { relation: TableFactor::Table {

View file

@ -1229,7 +1229,7 @@ fn parse_pg_returning() {
pg_and_generic().verified_stmt("DELETE FROM tasks WHERE status = 'DONE' RETURNING *"); pg_and_generic().verified_stmt("DELETE FROM tasks WHERE status = 'DONE' RETURNING *");
match stmt { match stmt {
Statement::Delete { returning, .. } => { Statement::Delete { returning, .. } => {
assert_eq!(Some(vec![SelectItem::Wildcard,]), returning); assert_eq!(Some(vec![SelectItem::Wildcard(None),]), returning);
} }
_ => unreachable!(), _ => unreachable!(),
}; };

View file

@ -14,14 +14,14 @@
//! Test SQL syntax specific to Snowflake. The parser based on the //! Test SQL syntax specific to Snowflake. The parser based on the
//! generic dialect is also tested (on the inputs it can handle). //! generic dialect is also tested (on the inputs it can handle).
#[macro_use]
mod test_utils;
use test_utils::*;
use sqlparser::ast::*; use sqlparser::ast::*;
use sqlparser::dialect::{GenericDialect, SnowflakeDialect}; use sqlparser::dialect::{GenericDialect, SnowflakeDialect};
use sqlparser::parser::ParserError; use sqlparser::parser::ParserError;
use sqlparser::tokenizer::*; use sqlparser::tokenizer::*;
use test_utils::*;
#[macro_use]
mod test_utils;
#[test] #[test]
fn test_snowflake_create_table() { fn test_snowflake_create_table() {
@ -364,3 +364,61 @@ fn snowflake_and_generic() -> TestedDialects {
dialects: vec![Box::new(SnowflakeDialect {}), Box::new(GenericDialect {})], 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!(),
};
}