Skip to content

Commit 35400c3

Browse files
committed
Add support for dialects (Redshift) that expect the TOP keyword in a SELECT statement to appear before the ALL/DISTINCT option
1 parent 8e0d26a commit 35400c3

File tree

7 files changed

+63
-10
lines changed

7 files changed

+63
-10
lines changed

src/ast/query.rs

+12-1
Original file line numberDiff line numberDiff line change
@@ -327,12 +327,20 @@ impl fmt::Display for Select {
327327
write!(f, " {value_table_mode}")?;
328328
}
329329

330+
if let Some(ref top) = self.top {
331+
if top.is_before_distinct {
332+
write!(f, " {top}")?;
333+
}
334+
}
330335
if let Some(ref distinct) = self.distinct {
331336
write!(f, " {distinct}")?;
332337
}
333338
if let Some(ref top) = self.top {
334-
write!(f, " {top}")?;
339+
if !top.is_before_distinct {
340+
write!(f, " {top}")?;
341+
}
335342
}
343+
336344
write!(f, " {}", display_comma_separated(&self.projection))?;
337345

338346
if let Some(ref into) = self.into {
@@ -1996,6 +2004,9 @@ pub struct Top {
19962004
/// MSSQL only.
19972005
pub percent: bool,
19982006
pub quantity: Option<TopQuantity>,
2007+
// Whether this option was parsed before `ALL`/`DISTINCT`
2008+
// See `Dialect::expects_top_before_distinct`
2009+
pub is_before_distinct: bool,
19992010
}
20002011

20012012
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]

src/dialect/mod.rs

+6
Original file line numberDiff line numberDiff line change
@@ -590,6 +590,12 @@ pub trait Dialect: Debug + Any {
590590
fn supports_try_convert(&self) -> bool {
591591
false
592592
}
593+
594+
/// Returns true if this dialect expects the the `TOP` option
595+
/// before the `ALL`/`DISTINCT` options
596+
fn expects_top_before_distinct(&self) -> bool {
597+
false
598+
}
593599
}
594600

595601
/// 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 expects_top_before_distinct(&self) -> bool {
75+
true
76+
}
7177
}

src/keywords.rs

+1
Original file line numberDiff line numberDiff line change
@@ -254,6 +254,7 @@ define_keywords!(
254254
DISCARD,
255255
DISCONNECT,
256256
DISTINCT,
257+
DISTINCTROW,
257258
DISTRIBUTE,
258259
DIV,
259260
DO,

src/parser/mod.rs

+21-9
Original file line numberDiff line numberDiff line change
@@ -3534,7 +3534,9 @@ impl<'a> Parser<'a> {
35343534
pub fn parse_all_or_distinct(&mut self) -> Result<Option<Distinct>, ParserError> {
35353535
let loc = self.peek_token().location;
35363536
let all = self.parse_keyword(Keyword::ALL);
3537-
let distinct = self.parse_keyword(Keyword::DISTINCT);
3537+
let distinct = self
3538+
.parse_one_of_keywords(&[Keyword::DISTINCT, Keyword::DISTINCTROW])
3539+
.is_some();
35383540
if !distinct {
35393541
return Ok(None);
35403542
}
@@ -9174,12 +9176,14 @@ impl<'a> Parser<'a> {
91749176
None
91759177
};
91769178

9177-
let distinct = self.parse_all_or_distinct()?;
9178-
9179-
let top = if self.parse_keyword(Keyword::TOP) {
9180-
Some(self.parse_top()?)
9179+
let (distinct, top) = if self.dialect.expects_top_before_distinct() {
9180+
let top = self.maybe_parse_top(true)?;
9181+
let distinct = self.parse_all_or_distinct()?;
9182+
(distinct, top)
91819183
} else {
9182-
None
9184+
let distinct = self.parse_all_or_distinct()?;
9185+
let top = self.maybe_parse_top(false)?;
9186+
(distinct, top)
91839187
};
91849188

91859189
let projection = self.parse_projection()?;
@@ -11491,7 +11495,14 @@ impl<'a> Parser<'a> {
1149111495

1149211496
/// Parse a TOP clause, MSSQL equivalent of LIMIT,
1149311497
/// that follows after `SELECT [DISTINCT]`.
11494-
pub fn parse_top(&mut self) -> Result<Top, ParserError> {
11498+
pub fn maybe_parse_top(
11499+
&mut self,
11500+
is_before_distinct: bool,
11501+
) -> Result<Option<Top>, ParserError> {
11502+
if !self.parse_keyword(Keyword::TOP) {
11503+
return Ok(None);
11504+
}
11505+
1149511506
let quantity = if self.consume_token(&Token::LParen) {
1149611507
let quantity = self.parse_expr()?;
1149711508
self.expect_token(&Token::RParen)?;
@@ -11509,11 +11520,12 @@ impl<'a> Parser<'a> {
1150911520

1151011521
let with_ties = self.parse_keywords(&[Keyword::WITH, Keyword::TIES]);
1151111522

11512-
Ok(Top {
11523+
Ok(Some(Top {
1151311524
with_ties,
1151411525
percent,
1151511526
quantity,
11516-
})
11527+
is_before_distinct,
11528+
}))
1151711529
}
1151811530

1151911531
/// Parse a LIMIT clause

tests/sqlparser_mysql.rs

+8
Original file line numberDiff line numberDiff line change
@@ -2796,3 +2796,11 @@ fn test_group_concat() {
27962796
mysql_and_generic()
27972797
.verified_expr("GROUP_CONCAT(DISTINCT test_score ORDER BY test_score DESC SEPARATOR ' ')");
27982798
}
2799+
2800+
#[test]
2801+
fn parse_select_distinctrow() {
2802+
mysql().one_statement_parses_to(
2803+
"SELECT DISTINCTROW a FROM tbl",
2804+
"SELECT DISTINCT a FROM tbl"
2805+
);
2806+
}

tests/sqlparser_redshift.rs

+9
Original file line numberDiff line numberDiff line change
@@ -196,3 +196,12 @@ fn test_create_view_with_no_schema_binding() {
196196
redshift_and_generic()
197197
.verified_stmt("CREATE VIEW myevent AS SELECT eventname FROM event WITH NO SCHEMA BINDING");
198198
}
199+
200+
#[test]
201+
fn test_select_top() {
202+
redshift().one_statement_parses_to("SELECT ALL * FROM tbl", "SELECT * FROM tbl");
203+
redshift().verified_stmt("SELECT TOP 3 * FROM tbl");
204+
redshift().one_statement_parses_to("SELECT TOP 3 ALL * FROM tbl", "SELECT TOP 3 * FROM tbl");
205+
redshift().verified_stmt("SELECT TOP 3 DISTINCT * FROM tbl");
206+
redshift().verified_stmt("SELECT TOP 3 DISTINCT a, b, c FROM tbl");
207+
}

0 commit comments

Comments
 (0)