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 support for except clause on wildcards #745

Merged
merged 1 commit into from Dec 5, 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
7 changes: 4 additions & 3 deletions src/ast/mod.rs
Expand Up @@ -31,9 +31,10 @@ pub use self::ddl::{
};
pub use self::operator::{BinaryOperator, UnaryOperator};
pub use self::query::{
Cte, ExcludeSelectItem, Fetch, Join, JoinConstraint, JoinOperator, LateralView, LockType,
Offset, OffsetRows, OrderByExpr, Query, Select, SelectInto, SelectItem, SetExpr, SetOperator,
SetQuantifier, Table, TableAlias, TableFactor, TableWithJoins, Top, Values, With,
Cte, ExceptSelectItem, ExcludeSelectItem, Fetch, Join, JoinConstraint, JoinOperator,
LateralView, LockType, Offset, OffsetRows, OrderByExpr, Query, Select, SelectInto, SelectItem,
SetExpr, SetOperator, SetQuantifier, Table, TableAlias, TableFactor, TableWithJoins, Top,
Values, WildcardAdditionalOptions, With,
};
pub use self::value::{escape_quoted_string, DateTimeField, TrimWhereField, Value};

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

/// Additional options for wildcards, e.g. Snowflake `EXCLUDE` and Bigquery `EXCEPT`.
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash, Default)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct WildcardAdditionalOptions {
/// `[EXCLUDE...]`.
pub opt_exclude: Option<ExcludeSelectItem>,
/// `[EXCEPT...]`.
pub opt_except: Option<ExceptSelectItem>,
}

impl fmt::Display for WildcardAdditionalOptions {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
if let Some(exclude) = &self.opt_exclude {
write!(f, " {exclude}")?;
}
if let Some(except) = &self.opt_except {
write!(f, " {except}")?;
}
Ok(())
}
}

/// Snowflake `EXCLUDE` information.
Expand Down Expand Up @@ -392,23 +414,51 @@ impl fmt::Display for ExcludeSelectItem {
}
}

/// Bigquery `EXCEPT` information, with at least one column.
///
/// # Syntax
/// ```plaintext
/// EXCEPT (<col_name> [, ...])
/// ```
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct ExceptSelectItem {
/// First guaranteed column.
pub fist_elemnt: Ident,
/// Additional columns. This list can be empty.
pub additional_elements: Vec<Ident>,
}

impl fmt::Display for ExceptSelectItem {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "EXCEPT ")?;
if self.additional_elements.is_empty() {
write!(f, "({})", self.fist_elemnt)?;
} else {
write!(
f,
"({}, {})",
self.fist_elemnt,
display_comma_separated(&self.additional_elements)
)?;
}
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, opt_exclude) => {
SelectItem::QualifiedWildcard(prefix, additional_options) => {
write!(f, "{}.*", prefix)?;
if let Some(exclude) = opt_exclude {
write!(f, " {exclude}")?;
}
write!(f, "{additional_options}")?;
Ok(())
}
SelectItem::Wildcard(opt_exclude) => {
SelectItem::Wildcard(additional_options) => {
write!(f, "*")?;
if let Some(exclude) = opt_exclude {
write!(f, " {exclude}")?;
}
write!(f, "{additional_options}")?;
Ok(())
}
}
Expand Down
73 changes: 56 additions & 17 deletions src/parser.rs
Expand Up @@ -5643,25 +5643,37 @@ impl<'a> Parser<'a> {
None => SelectItem::UnnamedExpr(expr),
})
}
WildcardExpr::QualifiedWildcard(prefix) => {
let opt_exclude = if dialect_of!(self is GenericDialect | SnowflakeDialect) {
self.parse_optional_select_item_exclude()?
} else {
None
};
WildcardExpr::QualifiedWildcard(prefix) => Ok(SelectItem::QualifiedWildcard(
prefix,
self.parse_wildcard_additional_options()?,
)),
WildcardExpr::Wildcard => Ok(SelectItem::Wildcard(
self.parse_wildcard_additional_options()?,
)),
}
}

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
};
/// Parse an [`WildcardAdditionalOptions`](WildcardAdditionalOptions) information for wildcard select items.
///
/// If it is not possible to parse it, will return an option.
pub fn parse_wildcard_additional_options(
&mut self,
) -> Result<WildcardAdditionalOptions, ParserError> {
let opt_exclude = if dialect_of!(self is GenericDialect | SnowflakeDialect) {
self.parse_optional_select_item_exclude()?
} else {
None
};
let opt_except = if dialect_of!(self is GenericDialect | BigQueryDialect) {
self.parse_optional_select_item_except()?
} else {
None
};

Ok(SelectItem::Wildcard(opt_exclude))
}
}
Ok(WildcardAdditionalOptions {
opt_exclude,
opt_except,
})
}

/// Parse an [`Exclude`](ExcludeSelectItem) information for wildcard select items.
Expand All @@ -5686,6 +5698,33 @@ impl<'a> Parser<'a> {
Ok(opt_exclude)
}

/// Parse an [`Except`](ExceptSelectItem) information for wildcard select items.
///
/// If it is not possible to parse it, will return an option.
pub fn parse_optional_select_item_except(
&mut self,
) -> Result<Option<ExceptSelectItem>, ParserError> {
let opt_except = if self.parse_keyword(Keyword::EXCEPT) {
let idents = self.parse_parenthesized_column_list(Mandatory)?;
match &idents[..] {
[] => {
return self.expected(
"at least one column should be parsed by the expect clause",
self.peek_token(),
)?;
}
[first, idents @ ..] => Some(ExceptSelectItem {
fist_elemnt: first.clone(),
additional_elements: idents.to_vec(),
}),
}
} else {
None
};

Ok(opt_except)
}

/// 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
79 changes: 78 additions & 1 deletion tests/sqlparser_bigquery.rs
Expand Up @@ -16,7 +16,7 @@ mod test_utils;
use test_utils::*;

use sqlparser::ast::*;
use sqlparser::dialect::BigQueryDialect;
use sqlparser::dialect::{BigQueryDialect, GenericDialect};

#[test]
fn parse_literal_string() {
Expand Down Expand Up @@ -235,8 +235,85 @@ fn parse_array_agg_func() {
}
}

#[test]
fn test_select_wildcard_with_except() {
match bigquery_and_generic().verified_stmt("SELECT * EXCEPT (col_a) FROM data") {
Statement::Query(query) => match *query.body {
SetExpr::Select(select) => match &select.projection[0] {
SelectItem::Wildcard(WildcardAdditionalOptions {
opt_except: Some(except),
..
}) => {
assert_eq!(
*except,
ExceptSelectItem {
fist_elemnt: Ident::new("col_a"),
additional_elements: vec![]
}
)
}
_ => unreachable!(),
},
_ => unreachable!(),
},
_ => unreachable!(),
};

match bigquery_and_generic()
.verified_stmt("SELECT * EXCEPT (department_id, employee_id) FROM employee_table")
{
Statement::Query(query) => match *query.body {
SetExpr::Select(select) => match &select.projection[0] {
SelectItem::Wildcard(WildcardAdditionalOptions {
opt_except: Some(except),
..
}) => {
assert_eq!(
*except,
ExceptSelectItem {
fist_elemnt: Ident::new("department_id"),
additional_elements: vec![Ident::new("employee_id")]
}
)
}
_ => unreachable!(),
},
_ => unreachable!(),
},
_ => unreachable!(),
};

match bigquery_and_generic().verified_stmt("SELECT * EXCEPT (col1, col2) FROM _table") {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand what this test is checking that is not covered by "SELECT * EXCEPT (department_id, employee_id) FROM employee_table" above

Statement::Query(query) => match *query.body {
SetExpr::Select(select) => match &select.projection[0] {
SelectItem::Wildcard(WildcardAdditionalOptions {
opt_except: Some(except),
..
}) => {
assert_eq!(
*except,
ExceptSelectItem {
fist_elemnt: Ident::new("col1"),
additional_elements: vec![Ident::new("col2")]
}
)
}
_ => unreachable!(),
},
_ => unreachable!(),
},
_ => unreachable!(),
};
}

fn bigquery() -> TestedDialects {
TestedDialects {
dialects: vec![Box::new(BigQueryDialect {})],
}
}

fn bigquery_and_generic() -> TestedDialects {
TestedDialects {
dialects: vec![Box::new(BigQueryDialect {}), Box::new(GenericDialect {})],
}
}
16 changes: 12 additions & 4 deletions tests/sqlparser_common.rs
Expand Up @@ -581,12 +581,18 @@ fn parse_select_into() {
fn parse_select_wildcard() {
let sql = "SELECT * FROM foo";
let select = verified_only_select(sql);
assert_eq!(&SelectItem::Wildcard(None), only(&select.projection));
assert_eq!(
&SelectItem::Wildcard(WildcardAdditionalOptions::default()),
only(&select.projection)
);

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

Expand All @@ -595,7 +601,7 @@ fn parse_select_wildcard() {
assert_eq!(
&SelectItem::QualifiedWildcard(
ObjectName(vec![Ident::new("myschema"), Ident::new("mytable"),]),
None
WildcardAdditionalOptions::default(),
),
only(&select.projection)
);
Expand Down Expand Up @@ -5588,7 +5594,9 @@ fn parse_merge() {
body: Box::new(SetExpr::Select(Box::new(Select {
distinct: false,
top: None,
projection: vec![SelectItem::Wildcard(None)],
projection: vec![SelectItem::Wildcard(
WildcardAdditionalOptions::default()
)],
into: None,
from: vec![TableWithJoins {
relation: TableFactor::Table {
Expand Down
7 changes: 6 additions & 1 deletion tests/sqlparser_postgres.rs
Expand Up @@ -1277,7 +1277,12 @@ 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(None),]), returning);
assert_eq!(
Some(vec![SelectItem::Wildcard(
WildcardAdditionalOptions::default()
),]),
returning
);
}
_ => unreachable!(),
};
Expand Down
18 changes: 15 additions & 3 deletions tests/sqlparser_snowflake.rs
Expand Up @@ -370,7 +370,10 @@ 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)) => {
SelectItem::Wildcard(WildcardAdditionalOptions {
opt_exclude: Some(exclude),
..
}) => {
assert_eq!(
*exclude,
ExcludeSelectItem::Multiple(vec![Ident::new("col_a")])
Expand All @@ -388,7 +391,13 @@ fn test_select_wildcard_with_exclude() {
{
Statement::Query(query) => match *query.body {
SetExpr::Select(select) => match &select.projection[0] {
SelectItem::QualifiedWildcard(_, Some(exclude)) => {
SelectItem::QualifiedWildcard(
_,
WildcardAdditionalOptions {
opt_exclude: Some(exclude),
..
},
) => {
assert_eq!(
*exclude,
ExcludeSelectItem::Single(Ident::new("department_id"))
Expand All @@ -406,7 +415,10 @@ fn test_select_wildcard_with_exclude() {
{
Statement::Query(query) => match *query.body {
SetExpr::Select(select) => match &select.projection[0] {
SelectItem::Wildcard(Some(exclude)) => {
SelectItem::Wildcard(WildcardAdditionalOptions {
opt_exclude: Some(exclude),
..
}) => {
assert_eq!(
*exclude,
ExcludeSelectItem::Multiple(vec![
Expand Down