Skip to content

Commit

Permalink
Add support for TOP before ALL/DISTINCT (#1495)
Browse files Browse the repository at this point in the history
  • Loading branch information
yoavcloud authored Nov 6, 2024
1 parent 05821cc commit a5b0092
Show file tree
Hide file tree
Showing 10 changed files with 67 additions and 7 deletions.
12 changes: 11 additions & 1 deletion src/ast/query.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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 {
Expand Down
6 changes: 6 additions & 0 deletions src/dialect/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
6 changes: 6 additions & 0 deletions src/dialect/redshift.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}
16 changes: 10 additions & 6 deletions src/parser/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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()?;

Expand Down Expand Up @@ -9342,6 +9345,7 @@ impl<'a> Parser<'a> {
Ok(Select {
distinct,
top,
top_before_distinct,
projection,
into,
from,
Expand Down
1 change: 1 addition & 0 deletions tests/sqlparser_clickhouse.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
Expand Down
18 changes: 18 additions & 0 deletions tests/sqlparser_common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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"))),
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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()
)],
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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"))),
Expand Down Expand Up @@ -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"))),
Expand Down Expand Up @@ -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");
}
2 changes: 2 additions & 0 deletions tests/sqlparser_duckdb.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down
2 changes: 2 additions & 0 deletions tests/sqlparser_mssql.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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![],
Expand Down Expand Up @@ -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(),
Expand Down
8 changes: 8 additions & 0 deletions tests/sqlparser_mysql.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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('`'),
Expand Down Expand Up @@ -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('`'),
Expand Down Expand Up @@ -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('`'),
Expand Down Expand Up @@ -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('`'),
Expand Down Expand Up @@ -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"
)))],
Expand Down Expand Up @@ -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")))
Expand Down Expand Up @@ -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(),
Expand Down Expand Up @@ -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())
Expand Down
3 changes: 3 additions & 0 deletions tests/sqlparser_postgres.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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")),
Expand Down Expand Up @@ -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![],
Expand All @@ -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![],
Expand Down

0 comments on commit a5b0092

Please sign in to comment.