mirror of
https://github.com/apache/datafusion-sqlparser-rs.git
synced 2025-07-12 11:14:59 +00:00
Add support for Redshift SELECT * EXCLUDE
(#1936)
Some checks are pending
license / Release Audit Tool (RAT) (push) Waiting to run
Rust / benchmark-lint (push) Waiting to run
Rust / compile (push) Waiting to run
Rust / docs (push) Waiting to run
Rust / compile-no-std (push) Waiting to run
Rust / test (beta) (push) Waiting to run
Rust / test (nightly) (push) Waiting to run
Rust / test (stable) (push) Waiting to run
Rust / codestyle (push) Waiting to run
Rust / lint (push) Waiting to run
Some checks are pending
license / Release Audit Tool (RAT) (push) Waiting to run
Rust / benchmark-lint (push) Waiting to run
Rust / compile (push) Waiting to run
Rust / docs (push) Waiting to run
Rust / compile-no-std (push) Waiting to run
Rust / test (beta) (push) Waiting to run
Rust / test (nightly) (push) Waiting to run
Rust / test (stable) (push) Waiting to run
Rust / codestyle (push) Waiting to run
Rust / lint (push) Waiting to run
This commit is contained in:
parent
15f35e1476
commit
ee31b64f9e
15 changed files with 192 additions and 3 deletions
|
@ -321,6 +321,11 @@ pub struct Select {
|
|||
pub top_before_distinct: bool,
|
||||
/// projection expressions
|
||||
pub projection: Vec<SelectItem>,
|
||||
/// Excluded columns from the projection expression which are not specified
|
||||
/// directly after a wildcard.
|
||||
///
|
||||
/// [Redshift](https://docs.aws.amazon.com/redshift/latest/dg/r_EXCLUDE_list.html)
|
||||
pub exclude: Option<ExcludeSelectItem>,
|
||||
/// INTO
|
||||
pub into: Option<SelectInto>,
|
||||
/// FROM
|
||||
|
@ -401,6 +406,10 @@ impl fmt::Display for Select {
|
|||
indented_list(f, &self.projection)?;
|
||||
}
|
||||
|
||||
if let Some(exclude) = &self.exclude {
|
||||
write!(f, " {exclude}")?;
|
||||
}
|
||||
|
||||
if let Some(ref into) = self.into {
|
||||
f.write_str(" ")?;
|
||||
into.fmt(f)?;
|
||||
|
|
|
@ -2220,6 +2220,7 @@ impl Spanned for Select {
|
|||
distinct: _, // todo
|
||||
top: _, // todo, mysql specific
|
||||
projection,
|
||||
exclude: _,
|
||||
into,
|
||||
from,
|
||||
lateral_views,
|
||||
|
|
|
@ -94,4 +94,8 @@ impl Dialect for DuckDbDialect {
|
|||
fn supports_order_by_all(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn supports_select_wildcard_exclude(&self) -> bool {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
|
|
@ -179,4 +179,8 @@ impl Dialect for GenericDialect {
|
|||
fn supports_filter_during_aggregation(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn supports_select_wildcard_exclude(&self) -> bool {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
|
|
@ -570,6 +570,26 @@ pub trait Dialect: Debug + Any {
|
|||
false
|
||||
}
|
||||
|
||||
/// Returns true if the dialect supports an exclude option
|
||||
/// following a wildcard in the projection section. For example:
|
||||
/// `SELECT * EXCLUDE col1 FROM tbl`.
|
||||
///
|
||||
/// [Redshift](https://docs.aws.amazon.com/redshift/latest/dg/r_EXCLUDE_list.html)
|
||||
/// [Snowflake](https://docs.snowflake.com/en/sql-reference/sql/select)
|
||||
fn supports_select_wildcard_exclude(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
/// Returns true if the dialect supports an exclude option
|
||||
/// as the last item in the projection section, not necessarily
|
||||
/// after a wildcard. For example:
|
||||
/// `SELECT *, c1, c2 EXCLUDE c3 FROM tbl`
|
||||
///
|
||||
/// [Redshift](https://docs.aws.amazon.com/redshift/latest/dg/r_EXCLUDE_list.html)
|
||||
fn supports_select_exclude(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
/// Dialect-specific infix parser override
|
||||
///
|
||||
/// This method is called to parse the next infix expression.
|
||||
|
|
|
@ -131,4 +131,12 @@ impl Dialect for RedshiftSqlDialect {
|
|||
fn supports_string_literal_backslash_escape(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn supports_select_wildcard_exclude(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn supports_select_exclude(&self) -> bool {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
|
|
@ -466,6 +466,10 @@ impl Dialect for SnowflakeDialect {
|
|||
fn supports_select_expr_star(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn supports_select_wildcard_exclude(&self) -> bool {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_file_staging_command(kw: Keyword, parser: &mut Parser) -> Result<Statement, ParserError> {
|
||||
|
|
|
@ -1119,6 +1119,7 @@ pub const RESERVED_FOR_COLUMN_ALIAS: &[Keyword] = &[
|
|||
Keyword::FETCH,
|
||||
Keyword::UNION,
|
||||
Keyword::EXCEPT,
|
||||
Keyword::EXCLUDE,
|
||||
Keyword::INTERSECT,
|
||||
Keyword::MINUS,
|
||||
Keyword::CLUSTER,
|
||||
|
|
|
@ -11740,6 +11740,7 @@ impl<'a> Parser<'a> {
|
|||
top: None,
|
||||
top_before_distinct: false,
|
||||
projection: vec![],
|
||||
exclude: None,
|
||||
into: None,
|
||||
from,
|
||||
lateral_views: vec![],
|
||||
|
@ -11782,6 +11783,12 @@ impl<'a> Parser<'a> {
|
|||
self.parse_projection()?
|
||||
};
|
||||
|
||||
let exclude = if self.dialect.supports_select_exclude() {
|
||||
self.parse_optional_select_item_exclude()?
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let into = if self.parse_keyword(Keyword::INTO) {
|
||||
Some(self.parse_select_into()?)
|
||||
} else {
|
||||
|
@ -11915,6 +11922,7 @@ impl<'a> Parser<'a> {
|
|||
top,
|
||||
top_before_distinct,
|
||||
projection,
|
||||
exclude,
|
||||
into,
|
||||
from,
|
||||
lateral_views,
|
||||
|
@ -15052,8 +15060,7 @@ impl<'a> Parser<'a> {
|
|||
} else {
|
||||
None
|
||||
};
|
||||
let opt_exclude = if opt_ilike.is_none()
|
||||
&& dialect_of!(self is GenericDialect | DuckDbDialect | SnowflakeDialect)
|
||||
let opt_exclude = if opt_ilike.is_none() && self.dialect.supports_select_wildcard_exclude()
|
||||
{
|
||||
self.parse_optional_select_item_exclude()?
|
||||
} else {
|
||||
|
|
|
@ -60,6 +60,7 @@ fn parse_map_access_expr() {
|
|||
),
|
||||
})],
|
||||
})],
|
||||
exclude: None,
|
||||
into: None,
|
||||
from: vec![TableWithJoins {
|
||||
relation: table_from_name(ObjectName::from(vec![Ident::new("foos")])),
|
||||
|
|
|
@ -459,6 +459,7 @@ fn parse_update_set_from() {
|
|||
SelectItem::UnnamedExpr(Expr::Identifier(Ident::new("name"))),
|
||||
SelectItem::UnnamedExpr(Expr::Identifier(Ident::new("id"))),
|
||||
],
|
||||
exclude: None,
|
||||
into: None,
|
||||
from: vec![TableWithJoins {
|
||||
relation: table_from_name(ObjectName::from(vec![Ident::new("t1")])),
|
||||
|
@ -5695,6 +5696,7 @@ fn test_parse_named_window() {
|
|||
},
|
||||
},
|
||||
],
|
||||
exclude: None,
|
||||
into: None,
|
||||
from: vec![TableWithJoins {
|
||||
relation: table_from_name(ObjectName::from(vec![Ident {
|
||||
|
@ -6351,6 +6353,7 @@ fn parse_interval_and_or_xor() {
|
|||
quote_style: None,
|
||||
span: Span::empty(),
|
||||
}))],
|
||||
exclude: None,
|
||||
into: None,
|
||||
from: vec![TableWithJoins {
|
||||
relation: table_from_name(ObjectName::from(vec![Ident {
|
||||
|
@ -8620,6 +8623,7 @@ fn lateral_function() {
|
|||
distinct: None,
|
||||
top: None,
|
||||
projection: vec![SelectItem::Wildcard(WildcardAdditionalOptions::default())],
|
||||
exclude: None,
|
||||
top_before_distinct: false,
|
||||
into: None,
|
||||
from: vec![TableWithJoins {
|
||||
|
@ -9616,6 +9620,7 @@ fn parse_merge() {
|
|||
projection: vec![SelectItem::Wildcard(
|
||||
WildcardAdditionalOptions::default()
|
||||
)],
|
||||
exclude: None,
|
||||
into: None,
|
||||
from: vec![TableWithJoins {
|
||||
relation: table_from_name(ObjectName::from(vec![
|
||||
|
@ -11534,6 +11539,7 @@ fn parse_unload() {
|
|||
top: None,
|
||||
top_before_distinct: false,
|
||||
projection: vec![UnnamedExpr(Expr::Identifier(Ident::new("cola"))),],
|
||||
exclude: None,
|
||||
into: None,
|
||||
from: vec![TableWithJoins {
|
||||
relation: table_from_name(ObjectName::from(vec![Ident::new("tab")])),
|
||||
|
@ -11734,6 +11740,7 @@ fn parse_connect_by() {
|
|||
SelectItem::UnnamedExpr(Expr::Identifier(Ident::new("manager_id"))),
|
||||
SelectItem::UnnamedExpr(Expr::Identifier(Ident::new("title"))),
|
||||
],
|
||||
exclude: None,
|
||||
from: vec![TableWithJoins {
|
||||
relation: table_from_name(ObjectName::from(vec![Ident::new("employees")])),
|
||||
joins: vec![],
|
||||
|
@ -11815,6 +11822,7 @@ fn parse_connect_by() {
|
|||
SelectItem::UnnamedExpr(Expr::Identifier(Ident::new("manager_id"))),
|
||||
SelectItem::UnnamedExpr(Expr::Identifier(Ident::new("title"))),
|
||||
],
|
||||
exclude: None,
|
||||
from: vec![TableWithJoins {
|
||||
relation: table_from_name(ObjectName::from(vec![Ident::new("employees")])),
|
||||
joins: vec![],
|
||||
|
@ -12748,6 +12756,7 @@ fn test_extract_seconds_ok() {
|
|||
format: None,
|
||||
}),
|
||||
})],
|
||||
exclude: None,
|
||||
into: None,
|
||||
from: vec![],
|
||||
lateral_views: vec![],
|
||||
|
@ -14820,6 +14829,7 @@ fn test_select_from_first() {
|
|||
distinct: None,
|
||||
top: None,
|
||||
projection,
|
||||
exclude: None,
|
||||
top_before_distinct: false,
|
||||
into: None,
|
||||
from: vec![TableWithJoins {
|
||||
|
@ -16000,3 +16010,108 @@ fn parse_create_procedure_with_parameter_modes() {
|
|||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_select_exclude() {
|
||||
let dialects = all_dialects_where(|d| d.supports_select_wildcard_exclude());
|
||||
match &dialects
|
||||
.verified_only_select("SELECT * EXCLUDE c1 FROM test")
|
||||
.projection[0]
|
||||
{
|
||||
SelectItem::Wildcard(WildcardAdditionalOptions { opt_exclude, .. }) => {
|
||||
assert_eq!(
|
||||
*opt_exclude,
|
||||
Some(ExcludeSelectItem::Single(Ident::new("c1")))
|
||||
);
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
match &dialects
|
||||
.verified_only_select("SELECT * EXCLUDE (c1, c2) FROM test")
|
||||
.projection[0]
|
||||
{
|
||||
SelectItem::Wildcard(WildcardAdditionalOptions { opt_exclude, .. }) => {
|
||||
assert_eq!(
|
||||
*opt_exclude,
|
||||
Some(ExcludeSelectItem::Multiple(vec![
|
||||
Ident::new("c1"),
|
||||
Ident::new("c2")
|
||||
]))
|
||||
);
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
let select = dialects.verified_only_select("SELECT * EXCLUDE c1, c2 FROM test");
|
||||
match &select.projection[0] {
|
||||
SelectItem::Wildcard(WildcardAdditionalOptions { opt_exclude, .. }) => {
|
||||
assert_eq!(
|
||||
*opt_exclude,
|
||||
Some(ExcludeSelectItem::Single(Ident::new("c1")))
|
||||
);
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
match &select.projection[1] {
|
||||
SelectItem::UnnamedExpr(Expr::Identifier(ident)) => {
|
||||
assert_eq!(*ident, Ident::new("c2"));
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
|
||||
let dialects = all_dialects_where(|d| d.supports_select_exclude());
|
||||
let select = dialects.verified_only_select("SELECT *, c1 EXCLUDE c1 FROM test");
|
||||
match &select.projection[0] {
|
||||
SelectItem::Wildcard(additional_options) => {
|
||||
assert_eq!(*additional_options, WildcardAdditionalOptions::default());
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
assert_eq!(
|
||||
select.exclude,
|
||||
Some(ExcludeSelectItem::Single(Ident::new("c1")))
|
||||
);
|
||||
|
||||
let dialects = all_dialects_where(|d| {
|
||||
d.supports_select_wildcard_exclude() && !d.supports_select_exclude()
|
||||
});
|
||||
let select = dialects.verified_only_select("SELECT * EXCLUDE c1 FROM test");
|
||||
match &select.projection[0] {
|
||||
SelectItem::Wildcard(WildcardAdditionalOptions { opt_exclude, .. }) => {
|
||||
assert_eq!(
|
||||
*opt_exclude,
|
||||
Some(ExcludeSelectItem::Single(Ident::new("c1")))
|
||||
);
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
|
||||
// Dialects that only support the wildcard form and do not accept EXCLUDE as an implicity alias
|
||||
// will fail when encountered with the `c2` ident
|
||||
let dialects = all_dialects_where(|d| {
|
||||
d.supports_select_wildcard_exclude()
|
||||
&& !d.supports_select_exclude()
|
||||
&& d.is_column_alias(&Keyword::EXCLUDE, &mut Parser::new(d))
|
||||
});
|
||||
assert_eq!(
|
||||
dialects
|
||||
.parse_sql_statements("SELECT *, c1 EXCLUDE c2 FROM test")
|
||||
.err()
|
||||
.unwrap(),
|
||||
ParserError::ParserError("Expected: end of statement, found: c2".to_string())
|
||||
);
|
||||
|
||||
// Dialects that only support the wildcard form and accept EXCLUDE as an implicity alias
|
||||
// will fail when encountered with the `EXCLUDE` keyword
|
||||
let dialects = all_dialects_where(|d| {
|
||||
d.supports_select_wildcard_exclude()
|
||||
&& !d.supports_select_exclude()
|
||||
&& !d.is_column_alias(&Keyword::EXCLUDE, &mut Parser::new(d))
|
||||
});
|
||||
assert_eq!(
|
||||
dialects
|
||||
.parse_sql_statements("SELECT *, c1 EXCLUDE c2 FROM test")
|
||||
.err()
|
||||
.unwrap(),
|
||||
ParserError::ParserError("Expected: end of statement, found: EXCLUDE".to_string())
|
||||
);
|
||||
}
|
||||
|
|
|
@ -269,6 +269,7 @@ fn test_select_union_by_name() {
|
|||
distinct: None,
|
||||
top: None,
|
||||
projection: vec![SelectItem::Wildcard(WildcardAdditionalOptions::default())],
|
||||
exclude: None,
|
||||
top_before_distinct: false,
|
||||
into: None,
|
||||
from: vec![TableWithJoins {
|
||||
|
@ -299,6 +300,7 @@ fn test_select_union_by_name() {
|
|||
distinct: None,
|
||||
top: None,
|
||||
projection: vec![SelectItem::Wildcard(WildcardAdditionalOptions::default())],
|
||||
exclude: None,
|
||||
top_before_distinct: false,
|
||||
into: None,
|
||||
from: vec![TableWithJoins {
|
||||
|
|
|
@ -126,6 +126,7 @@ fn parse_create_procedure() {
|
|||
projection: vec![SelectItem::UnnamedExpr(Expr::Value(
|
||||
(number("1")).with_empty_span()
|
||||
))],
|
||||
exclude: None,
|
||||
into: None,
|
||||
from: vec![],
|
||||
lateral_views: vec![],
|
||||
|
@ -1368,6 +1369,7 @@ fn parse_substring_in_select() {
|
|||
special: true,
|
||||
shorthand: false,
|
||||
})],
|
||||
exclude: None,
|
||||
into: None,
|
||||
from: vec![TableWithJoins {
|
||||
relation: table_from_name(ObjectName::from(vec![Ident {
|
||||
|
@ -1516,6 +1518,7 @@ fn parse_mssql_declare() {
|
|||
(Value::Number("4".parse().unwrap(), false)).with_empty_span()
|
||||
)),
|
||||
})],
|
||||
exclude: None,
|
||||
into: None,
|
||||
from: vec![],
|
||||
lateral_views: vec![],
|
||||
|
|
|
@ -1403,6 +1403,7 @@ fn parse_escaped_quote_identifiers_with_escape() {
|
|||
quote_style: Some('`'),
|
||||
span: Span::empty(),
|
||||
}))],
|
||||
exclude: None,
|
||||
into: None,
|
||||
from: vec![],
|
||||
lateral_views: vec![],
|
||||
|
@ -1456,6 +1457,7 @@ fn parse_escaped_quote_identifiers_with_no_escape() {
|
|||
quote_style: Some('`'),
|
||||
span: Span::empty(),
|
||||
}))],
|
||||
exclude: None,
|
||||
into: None,
|
||||
from: vec![],
|
||||
lateral_views: vec![],
|
||||
|
@ -1503,6 +1505,7 @@ fn parse_escaped_backticks_with_escape() {
|
|||
quote_style: Some('`'),
|
||||
span: Span::empty(),
|
||||
}))],
|
||||
exclude: None,
|
||||
into: None,
|
||||
from: vec![],
|
||||
lateral_views: vec![],
|
||||
|
@ -1554,6 +1557,7 @@ fn parse_escaped_backticks_with_no_escape() {
|
|||
quote_style: Some('`'),
|
||||
span: Span::empty(),
|
||||
}))],
|
||||
exclude: None,
|
||||
into: None,
|
||||
from: vec![],
|
||||
lateral_views: vec![],
|
||||
|
@ -2225,6 +2229,7 @@ fn parse_select_with_numeric_prefix_column_name() {
|
|||
projection: vec![SelectItem::UnnamedExpr(Expr::Identifier(Ident::new(
|
||||
"123col_$@123abc"
|
||||
)))],
|
||||
exclude: None,
|
||||
into: None,
|
||||
from: vec![TableWithJoins {
|
||||
relation: table_from_name(ObjectName::from(vec![Ident::with_quote(
|
||||
|
@ -2392,7 +2397,6 @@ fn parse_select_with_concatenation_of_exp_number_and_numeric_prefix_column() {
|
|||
q.body,
|
||||
Box::new(SetExpr::Select(Box::new(Select {
|
||||
select_token: AttachedToken::empty(),
|
||||
|
||||
distinct: None,
|
||||
top: None,
|
||||
top_before_distinct: false,
|
||||
|
@ -2400,6 +2404,7 @@ fn parse_select_with_concatenation_of_exp_number_and_numeric_prefix_column() {
|
|||
SelectItem::UnnamedExpr(Expr::value(number("123e4"))),
|
||||
SelectItem::UnnamedExpr(Expr::Identifier(Ident::new("123col_$@123abc")))
|
||||
],
|
||||
exclude: None,
|
||||
into: None,
|
||||
from: vec![TableWithJoins {
|
||||
relation: table_from_name(ObjectName::from(vec![Ident::with_quote(
|
||||
|
@ -3043,6 +3048,7 @@ fn parse_substring_in_select() {
|
|||
special: true,
|
||||
shorthand: false,
|
||||
})],
|
||||
exclude: None,
|
||||
into: None,
|
||||
from: vec![TableWithJoins {
|
||||
relation: table_from_name(ObjectName::from(vec![Ident {
|
||||
|
@ -3357,6 +3363,7 @@ fn parse_hex_string_introducer() {
|
|||
)
|
||||
.into(),
|
||||
})],
|
||||
exclude: None,
|
||||
from: vec![],
|
||||
lateral_views: vec![],
|
||||
prewhere: None,
|
||||
|
|
|
@ -1305,6 +1305,7 @@ fn parse_copy_to() {
|
|||
},
|
||||
}
|
||||
],
|
||||
exclude: None,
|
||||
into: None,
|
||||
from: vec![],
|
||||
lateral_views: vec![],
|
||||
|
@ -2948,6 +2949,7 @@ fn parse_array_subquery_expr() {
|
|||
projection: vec![SelectItem::UnnamedExpr(Expr::Value(
|
||||
(number("1")).with_empty_span()
|
||||
))],
|
||||
exclude: None,
|
||||
into: None,
|
||||
from: vec![],
|
||||
lateral_views: vec![],
|
||||
|
@ -2973,6 +2975,7 @@ fn parse_array_subquery_expr() {
|
|||
projection: vec![SelectItem::UnnamedExpr(Expr::Value(
|
||||
(number("2")).with_empty_span()
|
||||
))],
|
||||
exclude: None,
|
||||
into: None,
|
||||
from: vec![],
|
||||
lateral_views: vec![],
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue