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

Support RENAME for wildcard SELECTs #784

Merged
merged 1 commit into from Jan 2, 2023
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
8 changes: 4 additions & 4 deletions src/ast/mod.rs
Expand Up @@ -34,10 +34,10 @@ pub use self::ddl::{
};
pub use self::operator::{BinaryOperator, UnaryOperator};
pub use self::query::{
Cte, ExceptSelectItem, ExcludeSelectItem, Fetch, Join, JoinConstraint, JoinOperator,
LateralView, LockClause, LockType, NonBlock, Offset, OffsetRows, OrderByExpr, Query, Select,
SelectInto, SelectItem, SetExpr, SetOperator, SetQuantifier, Table, TableAlias, TableFactor,
TableWithJoins, Top, Values, WildcardAdditionalOptions, With,
Cte, ExceptSelectItem, ExcludeSelectItem, Fetch, IdentWithAlias, Join, JoinConstraint,
JoinOperator, LateralView, LockClause, LockType, NonBlock, Offset, OffsetRows, OrderByExpr,
Query, RenameSelectItem, Select, SelectInto, SelectItem, SetExpr, SetOperator, SetQuantifier,
Table, TableAlias, TableFactor, TableWithJoins, Top, Values, WildcardAdditionalOptions, With,
};
pub use self::value::{
escape_quoted_string, DateTimeField, DollarQuotedString, TrimWhereField, Value,
Expand Down
74 changes: 70 additions & 4 deletions src/ast/query.rs
Expand Up @@ -365,7 +365,27 @@ pub enum SelectItem {
Wildcard(WildcardAdditionalOptions),
}

/// Additional options for wildcards, e.g. Snowflake `EXCLUDE` and Bigquery `EXCEPT`.
/// Single aliased identifier
///
/// # Syntax
/// ```plaintext
/// <ident> AS <alias>
/// ```
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "visitor", derive(Visit))]
pub struct IdentWithAlias {
pub ident: Ident,
pub alias: Ident,
}

impl fmt::Display for IdentWithAlias {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{} AS {}", self.ident, self.alias)
}
}

/// Additional options for wildcards, e.g. Snowflake `EXCLUDE`/`RENAME` and Bigquery `EXCEPT`.
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash, Default)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "visitor", derive(Visit))]
Expand All @@ -374,6 +394,8 @@ pub struct WildcardAdditionalOptions {
pub opt_exclude: Option<ExcludeSelectItem>,
/// `[EXCEPT...]`.
pub opt_except: Option<ExceptSelectItem>,
/// `[RENAME ...]`.
pub opt_rename: Option<RenameSelectItem>,
}

impl fmt::Display for WildcardAdditionalOptions {
Expand All @@ -384,6 +406,9 @@ impl fmt::Display for WildcardAdditionalOptions {
if let Some(except) = &self.opt_except {
write!(f, " {except}")?;
}
if let Some(rename) = &self.opt_rename {
write!(f, " {rename}")?;
}
Ok(())
}
}
Expand Down Expand Up @@ -429,6 +454,47 @@ impl fmt::Display for ExcludeSelectItem {
}
}

/// Snowflake `RENAME` information.
///
/// # Syntax
/// ```plaintext
/// <col_name> AS <col_alias>
/// | (<col_name> AS <col_alias>, <col_name> AS <col_alias>, ...)
/// ```
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "visitor", derive(Visit))]
pub enum RenameSelectItem {
/// Single column name with alias without parenthesis.
///
/// # Syntax
/// ```plaintext
/// <col_name> AS <col_alias>
/// ```
Single(IdentWithAlias),
/// Multiple column names with aliases inside parenthesis.
/// # Syntax
/// ```plaintext
/// (<col_name> AS <col_alias>, <col_name> AS <col_alias>, ...)
/// ```
Multiple(Vec<IdentWithAlias>),
}

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

/// Bigquery `EXCEPT` information, with at least one column.
///
/// # Syntax
Expand All @@ -440,7 +506,7 @@ impl fmt::Display for ExcludeSelectItem {
#[cfg_attr(feature = "visitor", derive(Visit))]
pub struct ExceptSelectItem {
/// First guaranteed column.
pub fist_elemnt: Ident,
pub first_element: Ident,
Copy link
Collaborator

Choose a reason for hiding this comment

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

thank you for this fix

/// Additional columns. This list can be empty.
pub additional_elements: Vec<Ident>,
}
Expand All @@ -449,12 +515,12 @@ 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)?;
write!(f, "({})", self.first_element)?;
} else {
write!(
f,
"({}, {})",
self.fist_elemnt,
self.first_element,
display_comma_separated(&self.additional_elements)
)?;
}
Expand Down
39 changes: 37 additions & 2 deletions src/parser.rs
Expand Up @@ -4401,6 +4401,14 @@ impl<'a> Parser<'a> {
Ok(values)
}

/// Strictly parse `identifier AS identifier`
pub fn parse_identifier_with_alias(&mut self) -> Result<IdentWithAlias, ParserError> {
let ident = self.parse_identifier()?;
self.expect_keyword(Keyword::AS)?;
let alias = self.parse_identifier()?;
Ok(IdentWithAlias { ident, alias })
}

/// Parse `AS identifier` (or simply `identifier` if it's not a reserved keyword)
/// Some examples with aliases: `SELECT 1 foo`, `SELECT COUNT(*) AS cnt`,
/// `SELECT ... FROM t1 foo, t2 bar`, `SELECT ... FROM (...) AS bar`
Expand Down Expand Up @@ -4432,7 +4440,7 @@ impl<'a> Parser<'a> {
// ignore the <separator> and treat the multiple strings as
// a single <literal>."
Token::SingleQuotedString(s) => Ok(Some(Ident::with_quote('\'', s))),
// Support for MySql dialect double qouted string, `AS "HOUR"` for example
// Support for MySql dialect double quoted string, `AS "HOUR"` for example
Copy link
Collaborator

Choose a reason for hiding this comment

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

👍

Token::DoubleQuotedString(s) => Ok(Some(Ident::with_quote('\"', s))),
_ => {
if after_as {
Expand Down Expand Up @@ -6055,10 +6063,16 @@ impl<'a> Parser<'a> {
} else {
None
};
let opt_rename = if dialect_of!(self is GenericDialect | SnowflakeDialect) {
self.parse_optional_select_item_rename()?
} else {
None
};

Ok(WildcardAdditionalOptions {
opt_exclude,
opt_except,
opt_rename,
})
}

Expand Down Expand Up @@ -6100,7 +6114,7 @@ impl<'a> Parser<'a> {
)?;
}
[first, idents @ ..] => Some(ExceptSelectItem {
fist_elemnt: first.clone(),
first_element: first.clone(),
additional_elements: idents.to_vec(),
}),
}
Expand All @@ -6111,6 +6125,27 @@ impl<'a> Parser<'a> {
Ok(opt_except)
}

/// Parse a [`Rename`](RenameSelectItem) information for wildcard select items.
pub fn parse_optional_select_item_rename(
&mut self,
) -> Result<Option<RenameSelectItem>, ParserError> {
let opt_rename = if self.parse_keyword(Keyword::RENAME) {
if self.consume_token(&Token::LParen) {
let idents =
self.parse_comma_separated(|parser| parser.parse_identifier_with_alias())?;
self.expect_token(&Token::RParen)?;
Some(RenameSelectItem::Multiple(idents))
} else {
let ident = self.parse_identifier_with_alias()?;
Some(RenameSelectItem::Single(ident))
}
} else {
None
};

Ok(opt_rename)
}

/// 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
63 changes: 19 additions & 44 deletions tests/sqlparser_bigquery.rs
Expand Up @@ -265,51 +265,26 @@ 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!(),
};
let select = bigquery_and_generic().verified_only_select("SELECT * EXCEPT (col_a) FROM data");
let expected = SelectItem::Wildcard(WildcardAdditionalOptions {
opt_except: Some(ExceptSelectItem {
first_element: Ident::new("col_a"),
additional_elements: vec![],
}),
..Default::default()
});
assert_eq!(expected, select.projection[0]);

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!(),
};
let select = bigquery_and_generic()
.verified_only_select("SELECT * EXCEPT (department_id, employee_id) FROM employee_table");
let expected = SelectItem::Wildcard(WildcardAdditionalOptions {
opt_except: Some(ExceptSelectItem {
first_element: Ident::new("department_id"),
additional_elements: vec![Ident::new("employee_id")],
}),
..Default::default()
});
assert_eq!(expected, select.projection[0]);

assert_eq!(
bigquery_and_generic()
Expand Down