Add support for TOP before ALL/DISTINCT (#1495)

This commit is contained in:
Yoav Cohen 2024-11-06 18:09:55 +01:00 committed by GitHub
parent 05821cc7db
commit a5b0092506
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 67 additions and 7 deletions

View file

@ -279,6 +279,8 @@ pub struct Select {
pub distinct: Option<Distinct>,
/// MSSQL syntax: `TOP (<N>) [ PERCENT ] [ WITH TIES ]`
pub top: Option<Top>,
/// Whether the top was located before `ALL`/`DISTINCT`
pub top_before_distinct: bool,
/// projection expressions
pub projection: Vec<SelectItem>,
/// INTO
@ -327,12 +329,20 @@ impl fmt::Display for Select {
write!(f, " {value_table_mode}")?;
}
if let Some(ref top) = self.top {
if self.top_before_distinct {
write!(f, " {top}")?;
}
}
if let Some(ref distinct) = self.distinct {
write!(f, " {distinct}")?;
}
if let Some(ref top) = self.top {
write!(f, " {top}")?;
if !self.top_before_distinct {
write!(f, " {top}")?;
}
}
write!(f, " {}", display_comma_separated(&self.projection))?;
if let Some(ref into) = self.into {

View file

@ -600,6 +600,12 @@ pub trait Dialect: Debug + Any {
fn supports_notify(&self) -> bool {
false
}
/// Returns true if this dialect expects the the `TOP` option
/// before the `ALL`/`DISTINCT` options in a `SELECT` statement.
fn supports_top_before_distinct(&self) -> bool {
false
}
}
/// This represents the operators for which precedence must be defined

View file

@ -68,4 +68,10 @@ impl Dialect for RedshiftSqlDialect {
fn supports_connect_by(&self) -> bool {
true
}
/// Redshift expects the `TOP` option before the `ALL/DISTINCT` option:
/// <https://docs.aws.amazon.com/redshift/latest/dg/r_SELECT_list.html#r_SELECT_list-parameters>
fn supports_top_before_distinct(&self) -> bool {
true
}
}

View file

@ -9193,13 +9193,16 @@ impl<'a> Parser<'a> {
None
};
let mut top_before_distinct = false;
let mut top = None;
if self.dialect.supports_top_before_distinct() && self.parse_keyword(Keyword::TOP) {
top = Some(self.parse_top()?);
top_before_distinct = true;
}
let distinct = self.parse_all_or_distinct()?;
let top = if self.parse_keyword(Keyword::TOP) {
Some(self.parse_top()?)
} else {
None
};
if !self.dialect.supports_top_before_distinct() && self.parse_keyword(Keyword::TOP) {
top = Some(self.parse_top()?);
}
let projection = self.parse_projection()?;
@ -9342,6 +9345,7 @@ impl<'a> Parser<'a> {
Ok(Select {
distinct,
top,
top_before_distinct,
projection,
into,
from,

View file

@ -40,6 +40,7 @@ fn parse_map_access_expr() {
Select {
distinct: None,
top: None,
top_before_distinct: false,
projection: vec![UnnamedExpr(MapAccess {
column: Box::new(Identifier(Ident {
value: "string_values".to_string(),

View file

@ -379,6 +379,7 @@ fn parse_update_set_from() {
body: Box::new(SetExpr::Select(Box::new(Select {
distinct: None,
top: None,
top_before_distinct: false,
projection: vec![
SelectItem::UnnamedExpr(Expr::Identifier(Ident::new("name"))),
SelectItem::UnnamedExpr(Expr::Identifier(Ident::new("id"))),
@ -4649,6 +4650,7 @@ fn test_parse_named_window() {
let expected = Select {
distinct: None,
top: None,
top_before_distinct: false,
projection: vec![
SelectItem::ExprWithAlias {
expr: Expr::Function(Function {
@ -5289,6 +5291,7 @@ fn parse_interval_and_or_xor() {
body: Box::new(SetExpr::Select(Box::new(Select {
distinct: None,
top: None,
top_before_distinct: false,
projection: vec![UnnamedExpr(Expr::Identifier(Ident {
value: "col".to_string(),
quote_style: None,
@ -7367,6 +7370,7 @@ fn lateral_function() {
let expected = Select {
distinct: None,
top: None,
top_before_distinct: false,
projection: vec![SelectItem::Wildcard(WildcardAdditionalOptions {
opt_ilike: None,
opt_exclude: None,
@ -8215,6 +8219,7 @@ fn parse_merge() {
body: Box::new(SetExpr::Select(Box::new(Select {
distinct: None,
top: None,
top_before_distinct: false,
projection: vec![SelectItem::Wildcard(
WildcardAdditionalOptions::default()
)],
@ -9803,6 +9808,7 @@ fn parse_unload() {
body: Box::new(SetExpr::Select(Box::new(Select {
distinct: None,
top: None,
top_before_distinct: false,
projection: vec![UnnamedExpr(Expr::Identifier(Ident::new("cola"))),],
into: None,
from: vec![TableWithJoins {
@ -9978,6 +9984,7 @@ fn parse_connect_by() {
let expect_query = Select {
distinct: None,
top: None,
top_before_distinct: false,
projection: vec![
SelectItem::UnnamedExpr(Expr::Identifier(Ident::new("employee_id"))),
SelectItem::UnnamedExpr(Expr::Identifier(Ident::new("manager_id"))),
@ -10064,6 +10071,7 @@ fn parse_connect_by() {
Select {
distinct: None,
top: None,
top_before_distinct: false,
projection: vec![
SelectItem::UnnamedExpr(Expr::Identifier(Ident::new("employee_id"))),
SelectItem::UnnamedExpr(Expr::Identifier(Ident::new("manager_id"))),
@ -11475,3 +11483,13 @@ fn parse_notify_channel() {
);
}
}
#[test]
fn test_select_top() {
let dialects = all_dialects_where(|d| d.supports_top_before_distinct());
dialects.one_statement_parses_to("SELECT ALL * FROM tbl", "SELECT * FROM tbl");
dialects.verified_stmt("SELECT TOP 3 * FROM tbl");
dialects.one_statement_parses_to("SELECT TOP 3 ALL * FROM tbl", "SELECT TOP 3 * FROM tbl");
dialects.verified_stmt("SELECT TOP 3 DISTINCT * FROM tbl");
dialects.verified_stmt("SELECT TOP 3 DISTINCT a, b, c FROM tbl");
}

View file

@ -261,6 +261,7 @@ fn test_select_union_by_name() {
left: Box::<SetExpr>::new(SetExpr::Select(Box::new(Select {
distinct: None,
top: None,
top_before_distinct: false,
projection: vec![SelectItem::Wildcard(WildcardAdditionalOptions {
opt_ilike: None,
opt_exclude: None,
@ -301,6 +302,7 @@ fn test_select_union_by_name() {
right: Box::<SetExpr>::new(SetExpr::Select(Box::new(Select {
distinct: None,
top: None,
top_before_distinct: false,
projection: vec![SelectItem::Wildcard(WildcardAdditionalOptions {
opt_ilike: None,
opt_exclude: None,

View file

@ -114,6 +114,7 @@ fn parse_create_procedure() {
body: Box::new(SetExpr::Select(Box::new(Select {
distinct: None,
top: None,
top_before_distinct: false,
projection: vec![SelectItem::UnnamedExpr(Expr::Value(number("1")))],
into: None,
from: vec![],
@ -514,6 +515,7 @@ fn parse_substring_in_select() {
body: Box::new(SetExpr::Select(Box::new(Select {
distinct: Some(Distinct::Distinct),
top: None,
top_before_distinct: false,
projection: vec![SelectItem::UnnamedExpr(Expr::Substring {
expr: Box::new(Expr::Identifier(Ident {
value: "description".to_string(),

View file

@ -957,6 +957,7 @@ fn parse_escaped_quote_identifiers_with_escape() {
body: Box::new(SetExpr::Select(Box::new(Select {
distinct: None,
top: None,
top_before_distinct: false,
projection: vec![SelectItem::UnnamedExpr(Expr::Identifier(Ident {
value: "quoted ` identifier".into(),
quote_style: Some('`'),
@ -1007,6 +1008,7 @@ fn parse_escaped_quote_identifiers_with_no_escape() {
body: Box::new(SetExpr::Select(Box::new(Select {
distinct: None,
top: None,
top_before_distinct: false,
projection: vec![SelectItem::UnnamedExpr(Expr::Identifier(Ident {
value: "quoted `` identifier".into(),
quote_style: Some('`'),
@ -1050,6 +1052,7 @@ fn parse_escaped_backticks_with_escape() {
body: Box::new(SetExpr::Select(Box::new(Select {
distinct: None,
top: None,
top_before_distinct: false,
projection: vec![SelectItem::UnnamedExpr(Expr::Identifier(Ident {
value: "`quoted identifier`".into(),
quote_style: Some('`'),
@ -1097,6 +1100,7 @@ fn parse_escaped_backticks_with_no_escape() {
body: Box::new(SetExpr::Select(Box::new(Select {
distinct: None,
top: None,
top_before_distinct: false,
projection: vec![SelectItem::UnnamedExpr(Expr::Identifier(Ident {
value: "``quoted identifier``".into(),
quote_style: Some('`'),
@ -1741,6 +1745,7 @@ fn parse_select_with_numeric_prefix_column_name() {
Box::new(SetExpr::Select(Box::new(Select {
distinct: None,
top: None,
top_before_distinct: false,
projection: vec![SelectItem::UnnamedExpr(Expr::Identifier(Ident::new(
"123col_$@123abc"
)))],
@ -1795,6 +1800,7 @@ fn parse_select_with_concatenation_of_exp_number_and_numeric_prefix_column() {
Box::new(SetExpr::Select(Box::new(Select {
distinct: None,
top: None,
top_before_distinct: false,
projection: vec![
SelectItem::UnnamedExpr(Expr::Value(number("123e4"))),
SelectItem::UnnamedExpr(Expr::Identifier(Ident::new("123col_$@123abc")))
@ -2295,6 +2301,7 @@ fn parse_substring_in_select() {
body: Box::new(SetExpr::Select(Box::new(Select {
distinct: Some(Distinct::Distinct),
top: None,
top_before_distinct: false,
projection: vec![SelectItem::UnnamedExpr(Expr::Substring {
expr: Box::new(Expr::Identifier(Ident {
value: "description".to_string(),
@ -2616,6 +2623,7 @@ fn parse_hex_string_introducer() {
body: Box::new(SetExpr::Select(Box::new(Select {
distinct: None,
top: None,
top_before_distinct: false,
projection: vec![SelectItem::UnnamedExpr(Expr::IntroducedString {
introducer: "_latin1".to_string(),
value: Value::HexStringLiteral("4D7953514C".to_string())

View file

@ -1165,6 +1165,7 @@ fn parse_copy_to() {
body: Box::new(SetExpr::Select(Box::new(Select {
distinct: None,
top: None,
top_before_distinct: false,
projection: vec![
SelectItem::ExprWithAlias {
expr: Expr::Value(number("42")),
@ -2505,6 +2506,7 @@ fn parse_array_subquery_expr() {
left: Box::new(SetExpr::Select(Box::new(Select {
distinct: None,
top: None,
top_before_distinct: false,
projection: vec![SelectItem::UnnamedExpr(Expr::Value(number("1")))],
into: None,
from: vec![],
@ -2525,6 +2527,7 @@ fn parse_array_subquery_expr() {
right: Box::new(SetExpr::Select(Box::new(Select {
distinct: None,
top: None,
top_before_distinct: false,
projection: vec![SelectItem::UnnamedExpr(Expr::Value(number("2")))],
into: None,
from: vec![],