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::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};

View file

@ -321,9 +321,49 @@ 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 {
@ -331,8 +371,20 @@ impl fmt::Display for SelectItem {
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(())
}
}
}
}

View file

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

View file

@ -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()?;

View file

@ -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)
);
@ -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 {

View file

@ -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!(),
};

View file

@ -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() {
@ -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!(),
};
}