diff --git a/src/ast/query.rs b/src/ast/query.rs index dc5966e5e..6767662d5 100644 --- a/src/ast/query.rs +++ b/src/ast/query.rs @@ -279,6 +279,8 @@ pub struct Select { pub distinct: Option, /// MSSQL syntax: `TOP () [ PERCENT ] [ WITH TIES ]` pub top: Option, + /// Whether the top was located before `ALL`/`DISTINCT` + pub top_before_distinct: bool, /// projection expressions pub projection: Vec, /// 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 { diff --git a/src/dialect/mod.rs b/src/dialect/mod.rs index 5abddba38..453fee3de 100644 --- a/src/dialect/mod.rs +++ b/src/dialect/mod.rs @@ -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 diff --git a/src/dialect/redshift.rs b/src/dialect/redshift.rs index 3bfdec3b0..4d0773843 100644 --- a/src/dialect/redshift.rs +++ b/src/dialect/redshift.rs @@ -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: + /// + fn supports_top_before_distinct(&self) -> bool { + true + } } diff --git a/src/parser/mod.rs b/src/parser/mod.rs index fd7d1c578..de11ba7c9 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -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, diff --git a/tests/sqlparser_clickhouse.rs b/tests/sqlparser_clickhouse.rs index f8c349a37..a71871115 100644 --- a/tests/sqlparser_clickhouse.rs +++ b/tests/sqlparser_clickhouse.rs @@ -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(), diff --git a/tests/sqlparser_common.rs b/tests/sqlparser_common.rs index 334dae2b3..49753a1f4 100644 --- a/tests/sqlparser_common.rs +++ b/tests/sqlparser_common.rs @@ -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"); +} diff --git a/tests/sqlparser_duckdb.rs b/tests/sqlparser_duckdb.rs index a4109b0a3..d68f37713 100644 --- a/tests/sqlparser_duckdb.rs +++ b/tests/sqlparser_duckdb.rs @@ -261,6 +261,7 @@ fn test_select_union_by_name() { left: Box::::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::::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, diff --git a/tests/sqlparser_mssql.rs b/tests/sqlparser_mssql.rs index 0223e2915..c5f43b072 100644 --- a/tests/sqlparser_mssql.rs +++ b/tests/sqlparser_mssql.rs @@ -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(), diff --git a/tests/sqlparser_mysql.rs b/tests/sqlparser_mysql.rs index 4b9354e85..6cd08df18 100644 --- a/tests/sqlparser_mysql.rs +++ b/tests/sqlparser_mysql.rs @@ -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()) diff --git a/tests/sqlparser_postgres.rs b/tests/sqlparser_postgres.rs index b9b3811ba..c30603baa 100644 --- a/tests/sqlparser_postgres.rs +++ b/tests/sqlparser_postgres.rs @@ -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![],