Skip to content

Commit a5b0092

Browse files
authored
Add support for TOP before ALL/DISTINCT (#1495)
1 parent 05821cc commit a5b0092

10 files changed

+67
-7
lines changed

src/ast/query.rs

+11-1
Original file line numberDiff line numberDiff line change
@@ -279,6 +279,8 @@ pub struct Select {
279279
pub distinct: Option<Distinct>,
280280
/// MSSQL syntax: `TOP (<N>) [ PERCENT ] [ WITH TIES ]`
281281
pub top: Option<Top>,
282+
/// Whether the top was located before `ALL`/`DISTINCT`
283+
pub top_before_distinct: bool,
282284
/// projection expressions
283285
pub projection: Vec<SelectItem>,
284286
/// INTO
@@ -327,12 +329,20 @@ impl fmt::Display for Select {
327329
write!(f, " {value_table_mode}")?;
328330
}
329331

332+
if let Some(ref top) = self.top {
333+
if self.top_before_distinct {
334+
write!(f, " {top}")?;
335+
}
336+
}
330337
if let Some(ref distinct) = self.distinct {
331338
write!(f, " {distinct}")?;
332339
}
333340
if let Some(ref top) = self.top {
334-
write!(f, " {top}")?;
341+
if !self.top_before_distinct {
342+
write!(f, " {top}")?;
343+
}
335344
}
345+
336346
write!(f, " {}", display_comma_separated(&self.projection))?;
337347

338348
if let Some(ref into) = self.into {

src/dialect/mod.rs

+6
Original file line numberDiff line numberDiff line change
@@ -600,6 +600,12 @@ pub trait Dialect: Debug + Any {
600600
fn supports_notify(&self) -> bool {
601601
false
602602
}
603+
604+
/// Returns true if this dialect expects the the `TOP` option
605+
/// before the `ALL`/`DISTINCT` options in a `SELECT` statement.
606+
fn supports_top_before_distinct(&self) -> bool {
607+
false
608+
}
603609
}
604610

605611
/// This represents the operators for which precedence must be defined

src/dialect/redshift.rs

+6
Original file line numberDiff line numberDiff line change
@@ -68,4 +68,10 @@ impl Dialect for RedshiftSqlDialect {
6868
fn supports_connect_by(&self) -> bool {
6969
true
7070
}
71+
72+
/// Redshift expects the `TOP` option before the `ALL/DISTINCT` option:
73+
/// <https://docs.aws.amazon.com/redshift/latest/dg/r_SELECT_list.html#r_SELECT_list-parameters>
74+
fn supports_top_before_distinct(&self) -> bool {
75+
true
76+
}
7177
}

src/parser/mod.rs

+10-6
Original file line numberDiff line numberDiff line change
@@ -9193,13 +9193,16 @@ impl<'a> Parser<'a> {
91939193
None
91949194
};
91959195

9196+
let mut top_before_distinct = false;
9197+
let mut top = None;
9198+
if self.dialect.supports_top_before_distinct() && self.parse_keyword(Keyword::TOP) {
9199+
top = Some(self.parse_top()?);
9200+
top_before_distinct = true;
9201+
}
91969202
let distinct = self.parse_all_or_distinct()?;
9197-
9198-
let top = if self.parse_keyword(Keyword::TOP) {
9199-
Some(self.parse_top()?)
9200-
} else {
9201-
None
9202-
};
9203+
if !self.dialect.supports_top_before_distinct() && self.parse_keyword(Keyword::TOP) {
9204+
top = Some(self.parse_top()?);
9205+
}
92039206

92049207
let projection = self.parse_projection()?;
92059208

@@ -9342,6 +9345,7 @@ impl<'a> Parser<'a> {
93429345
Ok(Select {
93439346
distinct,
93449347
top,
9348+
top_before_distinct,
93459349
projection,
93469350
into,
93479351
from,

tests/sqlparser_clickhouse.rs

+1
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ fn parse_map_access_expr() {
4040
Select {
4141
distinct: None,
4242
top: None,
43+
top_before_distinct: false,
4344
projection: vec![UnnamedExpr(MapAccess {
4445
column: Box::new(Identifier(Ident {
4546
value: "string_values".to_string(),

tests/sqlparser_common.rs

+18
Original file line numberDiff line numberDiff line change
@@ -379,6 +379,7 @@ fn parse_update_set_from() {
379379
body: Box::new(SetExpr::Select(Box::new(Select {
380380
distinct: None,
381381
top: None,
382+
top_before_distinct: false,
382383
projection: vec![
383384
SelectItem::UnnamedExpr(Expr::Identifier(Ident::new("name"))),
384385
SelectItem::UnnamedExpr(Expr::Identifier(Ident::new("id"))),
@@ -4649,6 +4650,7 @@ fn test_parse_named_window() {
46494650
let expected = Select {
46504651
distinct: None,
46514652
top: None,
4653+
top_before_distinct: false,
46524654
projection: vec![
46534655
SelectItem::ExprWithAlias {
46544656
expr: Expr::Function(Function {
@@ -5289,6 +5291,7 @@ fn parse_interval_and_or_xor() {
52895291
body: Box::new(SetExpr::Select(Box::new(Select {
52905292
distinct: None,
52915293
top: None,
5294+
top_before_distinct: false,
52925295
projection: vec![UnnamedExpr(Expr::Identifier(Ident {
52935296
value: "col".to_string(),
52945297
quote_style: None,
@@ -7367,6 +7370,7 @@ fn lateral_function() {
73677370
let expected = Select {
73687371
distinct: None,
73697372
top: None,
7373+
top_before_distinct: false,
73707374
projection: vec![SelectItem::Wildcard(WildcardAdditionalOptions {
73717375
opt_ilike: None,
73727376
opt_exclude: None,
@@ -8215,6 +8219,7 @@ fn parse_merge() {
82158219
body: Box::new(SetExpr::Select(Box::new(Select {
82168220
distinct: None,
82178221
top: None,
8222+
top_before_distinct: false,
82188223
projection: vec![SelectItem::Wildcard(
82198224
WildcardAdditionalOptions::default()
82208225
)],
@@ -9803,6 +9808,7 @@ fn parse_unload() {
98039808
body: Box::new(SetExpr::Select(Box::new(Select {
98049809
distinct: None,
98059810
top: None,
9811+
top_before_distinct: false,
98069812
projection: vec![UnnamedExpr(Expr::Identifier(Ident::new("cola"))),],
98079813
into: None,
98089814
from: vec![TableWithJoins {
@@ -9978,6 +9984,7 @@ fn parse_connect_by() {
99789984
let expect_query = Select {
99799985
distinct: None,
99809986
top: None,
9987+
top_before_distinct: false,
99819988
projection: vec![
99829989
SelectItem::UnnamedExpr(Expr::Identifier(Ident::new("employee_id"))),
99839990
SelectItem::UnnamedExpr(Expr::Identifier(Ident::new("manager_id"))),
@@ -10064,6 +10071,7 @@ fn parse_connect_by() {
1006410071
Select {
1006510072
distinct: None,
1006610073
top: None,
10074+
top_before_distinct: false,
1006710075
projection: vec![
1006810076
SelectItem::UnnamedExpr(Expr::Identifier(Ident::new("employee_id"))),
1006910077
SelectItem::UnnamedExpr(Expr::Identifier(Ident::new("manager_id"))),
@@ -11475,3 +11483,13 @@ fn parse_notify_channel() {
1147511483
);
1147611484
}
1147711485
}
11486+
11487+
#[test]
11488+
fn test_select_top() {
11489+
let dialects = all_dialects_where(|d| d.supports_top_before_distinct());
11490+
dialects.one_statement_parses_to("SELECT ALL * FROM tbl", "SELECT * FROM tbl");
11491+
dialects.verified_stmt("SELECT TOP 3 * FROM tbl");
11492+
dialects.one_statement_parses_to("SELECT TOP 3 ALL * FROM tbl", "SELECT TOP 3 * FROM tbl");
11493+
dialects.verified_stmt("SELECT TOP 3 DISTINCT * FROM tbl");
11494+
dialects.verified_stmt("SELECT TOP 3 DISTINCT a, b, c FROM tbl");
11495+
}

tests/sqlparser_duckdb.rs

+2
Original file line numberDiff line numberDiff line change
@@ -261,6 +261,7 @@ fn test_select_union_by_name() {
261261
left: Box::<SetExpr>::new(SetExpr::Select(Box::new(Select {
262262
distinct: None,
263263
top: None,
264+
top_before_distinct: false,
264265
projection: vec![SelectItem::Wildcard(WildcardAdditionalOptions {
265266
opt_ilike: None,
266267
opt_exclude: None,
@@ -301,6 +302,7 @@ fn test_select_union_by_name() {
301302
right: Box::<SetExpr>::new(SetExpr::Select(Box::new(Select {
302303
distinct: None,
303304
top: None,
305+
top_before_distinct: false,
304306
projection: vec![SelectItem::Wildcard(WildcardAdditionalOptions {
305307
opt_ilike: None,
306308
opt_exclude: None,

tests/sqlparser_mssql.rs

+2
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,7 @@ fn parse_create_procedure() {
114114
body: Box::new(SetExpr::Select(Box::new(Select {
115115
distinct: None,
116116
top: None,
117+
top_before_distinct: false,
117118
projection: vec![SelectItem::UnnamedExpr(Expr::Value(number("1")))],
118119
into: None,
119120
from: vec![],
@@ -514,6 +515,7 @@ fn parse_substring_in_select() {
514515
body: Box::new(SetExpr::Select(Box::new(Select {
515516
distinct: Some(Distinct::Distinct),
516517
top: None,
518+
top_before_distinct: false,
517519
projection: vec![SelectItem::UnnamedExpr(Expr::Substring {
518520
expr: Box::new(Expr::Identifier(Ident {
519521
value: "description".to_string(),

tests/sqlparser_mysql.rs

+8
Original file line numberDiff line numberDiff line change
@@ -957,6 +957,7 @@ fn parse_escaped_quote_identifiers_with_escape() {
957957
body: Box::new(SetExpr::Select(Box::new(Select {
958958
distinct: None,
959959
top: None,
960+
top_before_distinct: false,
960961
projection: vec![SelectItem::UnnamedExpr(Expr::Identifier(Ident {
961962
value: "quoted ` identifier".into(),
962963
quote_style: Some('`'),
@@ -1007,6 +1008,7 @@ fn parse_escaped_quote_identifiers_with_no_escape() {
10071008
body: Box::new(SetExpr::Select(Box::new(Select {
10081009
distinct: None,
10091010
top: None,
1011+
top_before_distinct: false,
10101012
projection: vec![SelectItem::UnnamedExpr(Expr::Identifier(Ident {
10111013
value: "quoted `` identifier".into(),
10121014
quote_style: Some('`'),
@@ -1050,6 +1052,7 @@ fn parse_escaped_backticks_with_escape() {
10501052
body: Box::new(SetExpr::Select(Box::new(Select {
10511053
distinct: None,
10521054
top: None,
1055+
top_before_distinct: false,
10531056
projection: vec![SelectItem::UnnamedExpr(Expr::Identifier(Ident {
10541057
value: "`quoted identifier`".into(),
10551058
quote_style: Some('`'),
@@ -1097,6 +1100,7 @@ fn parse_escaped_backticks_with_no_escape() {
10971100
body: Box::new(SetExpr::Select(Box::new(Select {
10981101
distinct: None,
10991102
top: None,
1103+
top_before_distinct: false,
11001104
projection: vec![SelectItem::UnnamedExpr(Expr::Identifier(Ident {
11011105
value: "``quoted identifier``".into(),
11021106
quote_style: Some('`'),
@@ -1741,6 +1745,7 @@ fn parse_select_with_numeric_prefix_column_name() {
17411745
Box::new(SetExpr::Select(Box::new(Select {
17421746
distinct: None,
17431747
top: None,
1748+
top_before_distinct: false,
17441749
projection: vec![SelectItem::UnnamedExpr(Expr::Identifier(Ident::new(
17451750
"123col_$@123abc"
17461751
)))],
@@ -1795,6 +1800,7 @@ fn parse_select_with_concatenation_of_exp_number_and_numeric_prefix_column() {
17951800
Box::new(SetExpr::Select(Box::new(Select {
17961801
distinct: None,
17971802
top: None,
1803+
top_before_distinct: false,
17981804
projection: vec![
17991805
SelectItem::UnnamedExpr(Expr::Value(number("123e4"))),
18001806
SelectItem::UnnamedExpr(Expr::Identifier(Ident::new("123col_$@123abc")))
@@ -2295,6 +2301,7 @@ fn parse_substring_in_select() {
22952301
body: Box::new(SetExpr::Select(Box::new(Select {
22962302
distinct: Some(Distinct::Distinct),
22972303
top: None,
2304+
top_before_distinct: false,
22982305
projection: vec![SelectItem::UnnamedExpr(Expr::Substring {
22992306
expr: Box::new(Expr::Identifier(Ident {
23002307
value: "description".to_string(),
@@ -2616,6 +2623,7 @@ fn parse_hex_string_introducer() {
26162623
body: Box::new(SetExpr::Select(Box::new(Select {
26172624
distinct: None,
26182625
top: None,
2626+
top_before_distinct: false,
26192627
projection: vec![SelectItem::UnnamedExpr(Expr::IntroducedString {
26202628
introducer: "_latin1".to_string(),
26212629
value: Value::HexStringLiteral("4D7953514C".to_string())

tests/sqlparser_postgres.rs

+3
Original file line numberDiff line numberDiff line change
@@ -1165,6 +1165,7 @@ fn parse_copy_to() {
11651165
body: Box::new(SetExpr::Select(Box::new(Select {
11661166
distinct: None,
11671167
top: None,
1168+
top_before_distinct: false,
11681169
projection: vec![
11691170
SelectItem::ExprWithAlias {
11701171
expr: Expr::Value(number("42")),
@@ -2505,6 +2506,7 @@ fn parse_array_subquery_expr() {
25052506
left: Box::new(SetExpr::Select(Box::new(Select {
25062507
distinct: None,
25072508
top: None,
2509+
top_before_distinct: false,
25082510
projection: vec![SelectItem::UnnamedExpr(Expr::Value(number("1")))],
25092511
into: None,
25102512
from: vec![],
@@ -2525,6 +2527,7 @@ fn parse_array_subquery_expr() {
25252527
right: Box::new(SetExpr::Select(Box::new(Select {
25262528
distinct: None,
25272529
top: None,
2530+
top_before_distinct: false,
25282531
projection: vec![SelectItem::UnnamedExpr(Expr::Value(number("2")))],
25292532
into: None,
25302533
from: vec![],

0 commit comments

Comments
 (0)