mirror of
https://github.com/apache/datafusion-sqlparser-rs.git
synced 2025-09-26 23:49:10 +00:00
feat: add support for except clause on wildcards (#745)
This commit is contained in:
parent
5b53df97c4
commit
b3688513eb
7 changed files with 232 additions and 40 deletions
|
@ -31,9 +31,10 @@ 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, ExcludeSelectItem, Fetch, Join, JoinConstraint, JoinOperator, LateralView, LockType,
|
Cte, ExceptSelectItem, ExcludeSelectItem, Fetch, Join, JoinConstraint, JoinOperator,
|
||||||
Offset, OffsetRows, OrderByExpr, Query, Select, SelectInto, SelectItem, SetExpr, SetOperator,
|
LateralView, LockType, Offset, OffsetRows, OrderByExpr, Query, Select, SelectInto, SelectItem,
|
||||||
SetQuantifier, Table, TableAlias, TableFactor, TableWithJoins, Top, Values, With,
|
SetExpr, SetOperator, SetQuantifier, Table, TableAlias, TableFactor, TableWithJoins, Top,
|
||||||
|
Values, WildcardAdditionalOptions, With,
|
||||||
};
|
};
|
||||||
pub use self::value::{escape_quoted_string, DateTimeField, TrimWhereField, Value};
|
pub use self::value::{escape_quoted_string, DateTimeField, TrimWhereField, Value};
|
||||||
|
|
||||||
|
|
|
@ -347,9 +347,31 @@ 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, Option<ExcludeSelectItem>),
|
QualifiedWildcard(ObjectName, WildcardAdditionalOptions),
|
||||||
/// An unqualified `*`
|
/// 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.
|
/// Snowflake `EXCLUDE` information.
|
||||||
|
@ -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 {
|
impl fmt::Display for SelectItem {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
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, opt_exclude) => {
|
SelectItem::QualifiedWildcard(prefix, additional_options) => {
|
||||||
write!(f, "{}.*", prefix)?;
|
write!(f, "{}.*", prefix)?;
|
||||||
if let Some(exclude) = opt_exclude {
|
write!(f, "{additional_options}")?;
|
||||||
write!(f, " {exclude}")?;
|
|
||||||
}
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
SelectItem::Wildcard(opt_exclude) => {
|
SelectItem::Wildcard(additional_options) => {
|
||||||
write!(f, "*")?;
|
write!(f, "*")?;
|
||||||
if let Some(exclude) = opt_exclude {
|
write!(f, "{additional_options}")?;
|
||||||
write!(f, " {exclude}")?;
|
|
||||||
}
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5643,25 +5643,37 @@ impl<'a> Parser<'a> {
|
||||||
None => SelectItem::UnnamedExpr(expr),
|
None => SelectItem::UnnamedExpr(expr),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
WildcardExpr::QualifiedWildcard(prefix) => {
|
WildcardExpr::QualifiedWildcard(prefix) => Ok(SelectItem::QualifiedWildcard(
|
||||||
|
prefix,
|
||||||
|
self.parse_wildcard_additional_options()?,
|
||||||
|
)),
|
||||||
|
WildcardExpr::Wildcard => Ok(SelectItem::Wildcard(
|
||||||
|
self.parse_wildcard_additional_options()?,
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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) {
|
let opt_exclude = if dialect_of!(self is GenericDialect | SnowflakeDialect) {
|
||||||
self.parse_optional_select_item_exclude()?
|
self.parse_optional_select_item_exclude()?
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
let opt_except = if dialect_of!(self is GenericDialect | BigQueryDialect) {
|
||||||
Ok(SelectItem::QualifiedWildcard(prefix, opt_exclude))
|
self.parse_optional_select_item_except()?
|
||||||
}
|
|
||||||
WildcardExpr::Wildcard => {
|
|
||||||
let opt_exclude = if dialect_of!(self is GenericDialect | SnowflakeDialect) {
|
|
||||||
self.parse_optional_select_item_exclude()?
|
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(SelectItem::Wildcard(opt_exclude))
|
Ok(WildcardAdditionalOptions {
|
||||||
}
|
opt_exclude,
|
||||||
}
|
opt_except,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Parse an [`Exclude`](ExcludeSelectItem) information for wildcard select items.
|
/// Parse an [`Exclude`](ExcludeSelectItem) information for wildcard select items.
|
||||||
|
@ -5686,6 +5698,33 @@ impl<'a> Parser<'a> {
|
||||||
Ok(opt_exclude)
|
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)
|
/// 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()?;
|
||||||
|
|
|
@ -16,7 +16,7 @@ mod test_utils;
|
||||||
use test_utils::*;
|
use test_utils::*;
|
||||||
|
|
||||||
use sqlparser::ast::*;
|
use sqlparser::ast::*;
|
||||||
use sqlparser::dialect::BigQueryDialect;
|
use sqlparser::dialect::{BigQueryDialect, GenericDialect};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn parse_literal_string() {
|
fn parse_literal_string() {
|
||||||
|
@ -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") {
|
||||||
|
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 {
|
fn bigquery() -> TestedDialects {
|
||||||
TestedDialects {
|
TestedDialects {
|
||||||
dialects: vec![Box::new(BigQueryDialect {})],
|
dialects: vec![Box::new(BigQueryDialect {})],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn bigquery_and_generic() -> TestedDialects {
|
||||||
|
TestedDialects {
|
||||||
|
dialects: vec![Box::new(BigQueryDialect {}), Box::new(GenericDialect {})],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -581,12 +581,18 @@ 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(None), only(&select.projection));
|
assert_eq!(
|
||||||
|
&SelectItem::Wildcard(WildcardAdditionalOptions::default()),
|
||||||
|
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")]), None),
|
&SelectItem::QualifiedWildcard(
|
||||||
|
ObjectName(vec![Ident::new("foo")]),
|
||||||
|
WildcardAdditionalOptions::default()
|
||||||
|
),
|
||||||
only(&select.projection)
|
only(&select.projection)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -595,7 +601,7 @@ fn parse_select_wildcard() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
&SelectItem::QualifiedWildcard(
|
&SelectItem::QualifiedWildcard(
|
||||||
ObjectName(vec![Ident::new("myschema"), Ident::new("mytable"),]),
|
ObjectName(vec![Ident::new("myschema"), Ident::new("mytable"),]),
|
||||||
None
|
WildcardAdditionalOptions::default(),
|
||||||
),
|
),
|
||||||
only(&select.projection)
|
only(&select.projection)
|
||||||
);
|
);
|
||||||
|
@ -5588,7 +5594,9 @@ 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(None)],
|
projection: vec![SelectItem::Wildcard(
|
||||||
|
WildcardAdditionalOptions::default()
|
||||||
|
)],
|
||||||
into: None,
|
into: None,
|
||||||
from: vec![TableWithJoins {
|
from: vec![TableWithJoins {
|
||||||
relation: TableFactor::Table {
|
relation: TableFactor::Table {
|
||||||
|
|
|
@ -1277,7 +1277,12 @@ 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(None),]), returning);
|
assert_eq!(
|
||||||
|
Some(vec![SelectItem::Wildcard(
|
||||||
|
WildcardAdditionalOptions::default()
|
||||||
|
),]),
|
||||||
|
returning
|
||||||
|
);
|
||||||
}
|
}
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
};
|
};
|
||||||
|
|
|
@ -370,7 +370,10 @@ fn test_select_wildcard_with_exclude() {
|
||||||
match snowflake_and_generic().verified_stmt("SELECT * EXCLUDE (col_a) FROM data") {
|
match snowflake_and_generic().verified_stmt("SELECT * EXCLUDE (col_a) FROM data") {
|
||||||
Statement::Query(query) => match *query.body {
|
Statement::Query(query) => match *query.body {
|
||||||
SetExpr::Select(select) => match &select.projection[0] {
|
SetExpr::Select(select) => match &select.projection[0] {
|
||||||
SelectItem::Wildcard(Some(exclude)) => {
|
SelectItem::Wildcard(WildcardAdditionalOptions {
|
||||||
|
opt_exclude: Some(exclude),
|
||||||
|
..
|
||||||
|
}) => {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
*exclude,
|
*exclude,
|
||||||
ExcludeSelectItem::Multiple(vec![Ident::new("col_a")])
|
ExcludeSelectItem::Multiple(vec![Ident::new("col_a")])
|
||||||
|
@ -388,7 +391,13 @@ fn test_select_wildcard_with_exclude() {
|
||||||
{
|
{
|
||||||
Statement::Query(query) => match *query.body {
|
Statement::Query(query) => match *query.body {
|
||||||
SetExpr::Select(select) => match &select.projection[0] {
|
SetExpr::Select(select) => match &select.projection[0] {
|
||||||
SelectItem::QualifiedWildcard(_, Some(exclude)) => {
|
SelectItem::QualifiedWildcard(
|
||||||
|
_,
|
||||||
|
WildcardAdditionalOptions {
|
||||||
|
opt_exclude: Some(exclude),
|
||||||
|
..
|
||||||
|
},
|
||||||
|
) => {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
*exclude,
|
*exclude,
|
||||||
ExcludeSelectItem::Single(Ident::new("department_id"))
|
ExcludeSelectItem::Single(Ident::new("department_id"))
|
||||||
|
@ -406,7 +415,10 @@ fn test_select_wildcard_with_exclude() {
|
||||||
{
|
{
|
||||||
Statement::Query(query) => match *query.body {
|
Statement::Query(query) => match *query.body {
|
||||||
SetExpr::Select(select) => match &select.projection[0] {
|
SetExpr::Select(select) => match &select.projection[0] {
|
||||||
SelectItem::Wildcard(Some(exclude)) => {
|
SelectItem::Wildcard(WildcardAdditionalOptions {
|
||||||
|
opt_exclude: Some(exclude),
|
||||||
|
..
|
||||||
|
}) => {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
*exclude,
|
*exclude,
|
||||||
ExcludeSelectItem::Multiple(vec![
|
ExcludeSelectItem::Multiple(vec![
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue