mirror of
https://github.com/apache/datafusion-sqlparser-rs.git
synced 2025-08-22 15:04:04 +00:00
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:
parent
3df0e444c8
commit
fa6bd01b19
7 changed files with 170 additions and 21 deletions
|
@ -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};
|
||||||
|
|
||||||
|
|
|
@ -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(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -227,6 +227,7 @@ define_keywords!(
|
||||||
EVENT,
|
EVENT,
|
||||||
EVERY,
|
EVERY,
|
||||||
EXCEPT,
|
EXCEPT,
|
||||||
|
EXCLUDE,
|
||||||
EXEC,
|
EXEC,
|
||||||
EXECUTE,
|
EXECUTE,
|
||||||
EXISTS,
|
EXISTS,
|
||||||
|
|
|
@ -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()?;
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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!(),
|
||||||
};
|
};
|
||||||
|
|
|
@ -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!(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue