diff --git a/src/ast/mod.rs b/src/ast/mod.rs index d7a3679a4..1d9812b80 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -98,7 +98,7 @@ pub use self::query::{ JsonTableNestedColumn, LateralView, LimitClause, LockClause, LockType, MatchRecognizePattern, MatchRecognizeSymbol, Measure, NamedWindowDefinition, NamedWindowExpr, NonBlock, Offset, OffsetRows, OpenJsonTableColumn, OrderBy, OrderByExpr, OrderByKind, OrderByOptions, - PipeOperator, PivotValueSource, ProjectionSelect, Query, RenameSelectItem, + OrderBySort, PipeOperator, PivotValueSource, ProjectionSelect, Query, RenameSelectItem, RepetitionQuantifier, ReplaceSelectElement, ReplaceSelectItem, RowsPerMatch, Select, SelectFlavor, SelectInto, SelectItem, SelectItemQualifiedWildcardKind, SelectModifiers, SetExpr, SetOperator, SetQuantifier, Setting, SymbolDefinition, Table, TableAlias, diff --git a/src/ast/query.rs b/src/ast/query.rs index a52d518b1..43ef64ec0 100644 --- a/src/ast/query.rs +++ b/src/ast/query.rs @@ -2883,7 +2883,7 @@ impl fmt::Display for OrderBy { pub struct OrderByExpr { /// The expression to order by. pub expr: Expr, - /// Ordering options such as `ASC`/`DESC` and `NULLS` behavior. + /// Ordering options such as `ASC`/`DESC`/`USING ` and `NULLS` behavior. pub options: OrderByOptions, /// Optional `WITH FILL` clause (ClickHouse extension) which specifies how to fill gaps. pub with_fill: Option, @@ -2901,7 +2901,8 @@ impl From for OrderByExpr { impl fmt::Display for OrderByExpr { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{}{}", self.expr, self.options)?; + write!(f, "{}", self.expr)?; + write!(f, "{}", self.options)?; if let Some(ref with_fill) = self.with_fill { write!(f, " {with_fill}")? } @@ -2976,22 +2977,47 @@ impl fmt::Display for InterpolateExpr { } } -#[derive(Default, Debug, Copy, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] +/// The sort order for an `ORDER BY` expression. +/// +/// See PostgreSQL `USING` operator: +/// +#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] -/// Options for an `ORDER BY` expression (ASC/DESC and NULLS FIRST/LAST). +pub enum OrderBySort { + /// `ASC` + Asc, + /// `DESC` + Desc, + /// PostgreSQL `USING ` ordering. + /// + /// See + Using(ObjectName), +} + +#[derive(Default, Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] +/// Options for an `ORDER BY` expression. pub struct OrderByOptions { - /// Optional `ASC` (`Some(true)`) or `DESC` (`Some(false)`). - pub asc: Option, + /// Optional sort order: `ASC`, `DESC`, or `USING `. + pub sort: Option, /// Optional `NULLS FIRST` (`Some(true)`) or `NULLS LAST` (`Some(false)`). pub nulls_first: Option, } impl fmt::Display for OrderByOptions { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self.asc { - Some(true) => write!(f, " ASC")?, - Some(false) => write!(f, " DESC")?, + match &self.sort { + Some(OrderBySort::Asc) => write!(f, " ASC")?, + Some(OrderBySort::Desc) => write!(f, " DESC")?, + Some(OrderBySort::Using(op)) => { + if op.0.len() > 1 { + write!(f, " USING OPERATOR({op})")?; + } else { + write!(f, " USING {op}")?; + } + } None => (), } match self.nulls_first { diff --git a/src/dialect/mod.rs b/src/dialect/mod.rs index fed81b60a..ac5cba3e0 100644 --- a/src/dialect/mod.rs +++ b/src/dialect/mod.rs @@ -1352,6 +1352,14 @@ pub trait Dialect: Debug + Any { false } + /// Returns true if the dialect supports PostgreSQL-style ordering operators: + /// `ORDER BY expr USING `. + /// + /// For example: `SELECT * FROM t ORDER BY a USING <`. + fn supports_order_by_using_operator(&self) -> bool { + false + } + /// Returns true if the dialect supports `SET NAMES [COLLATE ]`. /// /// - [MySQL](https://dev.mysql.com/doc/refman/8.4/en/set-names.html) diff --git a/src/dialect/postgresql.rs b/src/dialect/postgresql.rs index b99a8b5c3..3f9c58bd7 100644 --- a/src/dialect/postgresql.rs +++ b/src/dialect/postgresql.rs @@ -278,6 +278,10 @@ impl Dialect for PostgreSqlDialect { true } + fn supports_order_by_using_operator(&self) -> bool { + true + } + fn supports_set_names(&self) -> bool { true } diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 6282ed3d7..44eee58b2 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -1376,7 +1376,7 @@ impl<'a> Parser<'a> { } let alias = self.parse_optional_alias_inner(None, validator)?; let order_by = OrderByOptions { - asc: self.parse_asc_desc(), + sort: self.parse_optional_order_by_sort(), nulls_first: None, }; Ok(ExprWithAliasAndOrderBy { @@ -18285,6 +18285,15 @@ impl<'a> Parser<'a> { } } + /// Parse ASC or DESC and map to [OrderBySort]. + fn parse_optional_order_by_sort(&mut self) -> Option { + match self.parse_asc_desc() { + Some(true) => Some(OrderBySort::Asc), + Some(false) => Some(OrderBySort::Desc), + None => None, + } + } + /// Parse an [OrderByExpr] expression. pub fn parse_order_by_expr(&mut self) -> Result { self.parse_order_by_expr_inner(false) @@ -18321,7 +18330,18 @@ impl<'a> Parser<'a> { None }; - let options = self.parse_order_by_options()?; + let options = if !with_operator_class + && self.dialect.supports_order_by_using_operator() + && self.parse_keyword(Keyword::USING) + { + let op = self.parse_order_by_using_operator()?; + OrderByOptions { + sort: Some(OrderBySort::Using(op)), + nulls_first: self.parse_null_ordering_modifier(), + } + } else { + self.parse_order_by_options()? + }; let with_fill = if self.dialect.supports_with_fill() && self.parse_keywords(&[Keyword::WITH, Keyword::FILL]) @@ -18341,18 +18361,33 @@ impl<'a> Parser<'a> { )) } - fn parse_order_by_options(&mut self) -> Result { - let asc = self.parse_asc_desc(); + fn parse_order_by_using_operator(&mut self) -> Result { + if self.parse_keyword(Keyword::OPERATOR) { + self.expect_token(&Token::LParen)?; + let operator_name = self.parse_operator_name()?; + self.expect_token(&Token::RParen)?; + return Ok(operator_name); + } + + let token = self.next_token(); + Ok(ObjectName::from(vec![Ident::new(token.token.to_string())])) + } - let nulls_first = if self.parse_keywords(&[Keyword::NULLS, Keyword::FIRST]) { + fn parse_null_ordering_modifier(&mut self) -> Option { + if self.parse_keywords(&[Keyword::NULLS, Keyword::FIRST]) { Some(true) } else if self.parse_keywords(&[Keyword::NULLS, Keyword::LAST]) { Some(false) } else { None - }; + } + } + + fn parse_order_by_options(&mut self) -> Result { + let sort = self.parse_optional_order_by_sort(); + let nulls_first = self.parse_null_ordering_modifier(); - Ok(OrderByOptions { asc, nulls_first }) + Ok(OrderByOptions { sort, nulls_first }) } // Parse a WITH FILL clause (ClickHouse dialect) @@ -20620,7 +20655,7 @@ mod tests { column: OrderByExpr { expr: Expr::Identifier(name.into()), options: OrderByOptions { - asc: None, + sort: None, nulls_first: None, }, with_fill: None, diff --git a/tests/sqlparser_bigquery.rs b/tests/sqlparser_bigquery.rs index 79db34b06..3d3084404 100644 --- a/tests/sqlparser_bigquery.rs +++ b/tests/sqlparser_bigquery.rs @@ -2728,7 +2728,7 @@ fn test_export_data() { kind: OrderByKind::Expressions(vec![OrderByExpr { expr: Expr::Identifier(Ident::new("field1")), options: OrderByOptions { - asc: None, + sort: None, nulls_first: None, }, with_fill: None, @@ -2834,7 +2834,7 @@ fn test_export_data() { kind: OrderByKind::Expressions(vec![OrderByExpr { expr: Expr::Identifier(Ident::new("field1")), options: OrderByOptions { - asc: None, + sort: None, nulls_first: None, }, with_fill: None, diff --git a/tests/sqlparser_clickhouse.rs b/tests/sqlparser_clickhouse.rs index 82f79577b..2ba1f9f3a 100644 --- a/tests/sqlparser_clickhouse.rs +++ b/tests/sqlparser_clickhouse.rs @@ -331,7 +331,7 @@ fn parse_alter_table_add_projection() { kind: OrderByKind::Expressions(vec![OrderByExpr { expr: Identifier(Ident::new("b")), options: OrderByOptions { - asc: None, + sort: None, nulls_first: None, }, with_fill: None, @@ -1159,7 +1159,7 @@ fn parse_select_order_by_with_fill_interpolate() { OrderByExpr { expr: Expr::Identifier(Ident::new("fname")), options: OrderByOptions { - asc: Some(true), + sort: Some(OrderBySort::Asc), nulls_first: Some(true), }, with_fill: Some(WithFill { @@ -1171,7 +1171,7 @@ fn parse_select_order_by_with_fill_interpolate() { OrderByExpr { expr: Expr::Identifier(Ident::new("lname")), options: OrderByOptions { - asc: Some(false), + sort: Some(OrderBySort::Desc), nulls_first: Some(false), }, with_fill: Some(WithFill { diff --git a/tests/sqlparser_common.rs b/tests/sqlparser_common.rs index 17f368bbb..76b4e8f80 100644 --- a/tests/sqlparser_common.rs +++ b/tests/sqlparser_common.rs @@ -2577,7 +2577,7 @@ fn parse_select_order_by() { OrderByExpr { expr: Expr::Identifier(Ident::new("lname")), options: OrderByOptions { - asc: Some(true), + sort: Some(OrderBySort::Asc), nulls_first: None, }, with_fill: None, @@ -2585,7 +2585,7 @@ fn parse_select_order_by() { OrderByExpr { expr: Expr::Identifier(Ident::new("fname")), options: OrderByOptions { - asc: Some(false), + sort: Some(OrderBySort::Desc), nulls_first: None, }, with_fill: None, @@ -2593,7 +2593,7 @@ fn parse_select_order_by() { OrderByExpr { expr: Expr::Identifier(Ident::new("id")), options: OrderByOptions { - asc: None, + sort: None, nulls_first: None, }, with_fill: None, @@ -2618,7 +2618,7 @@ fn parse_select_order_by_limit() { OrderByExpr { expr: Expr::Identifier(Ident::new("lname")), options: OrderByOptions { - asc: Some(true), + sort: Some(OrderBySort::Asc), nulls_first: None, }, with_fill: None, @@ -2626,7 +2626,7 @@ fn parse_select_order_by_limit() { OrderByExpr { expr: Expr::Identifier(Ident::new("fname")), options: OrderByOptions { - asc: Some(false), + sort: Some(OrderBySort::Desc), nulls_first: None, }, with_fill: None, @@ -2656,63 +2656,63 @@ fn parse_select_order_by_all() { ( "SELECT id, fname, lname FROM customer WHERE id < 5 ORDER BY ALL", OrderByKind::All(OrderByOptions { - asc: None, + sort: None, nulls_first: None, }), ), ( "SELECT id, fname, lname FROM customer WHERE id < 5 ORDER BY ALL NULLS FIRST", OrderByKind::All(OrderByOptions { - asc: None, + sort: None, nulls_first: Some(true), }), ), ( "SELECT id, fname, lname FROM customer WHERE id < 5 ORDER BY ALL NULLS LAST", OrderByKind::All(OrderByOptions { - asc: None, + sort: None, nulls_first: Some(false), }), ), ( "SELECT id, fname, lname FROM customer ORDER BY ALL ASC", OrderByKind::All(OrderByOptions { - asc: Some(true), + sort: Some(OrderBySort::Asc), nulls_first: None, }), ), ( "SELECT id, fname, lname FROM customer ORDER BY ALL ASC NULLS FIRST", OrderByKind::All(OrderByOptions { - asc: Some(true), + sort: Some(OrderBySort::Asc), nulls_first: Some(true), }), ), ( "SELECT id, fname, lname FROM customer ORDER BY ALL ASC NULLS LAST", OrderByKind::All(OrderByOptions { - asc: Some(true), + sort: Some(OrderBySort::Asc), nulls_first: Some(false), }), ), ( "SELECT id, fname, lname FROM customer WHERE id < 5 ORDER BY ALL DESC", OrderByKind::All(OrderByOptions { - asc: Some(false), + sort: Some(OrderBySort::Desc), nulls_first: None, }), ), ( "SELECT id, fname, lname FROM customer WHERE id < 5 ORDER BY ALL DESC NULLS FIRST", OrderByKind::All(OrderByOptions { - asc: Some(false), + sort: Some(OrderBySort::Desc), nulls_first: Some(true), }), ), ( "SELECT id, fname, lname FROM customer WHERE id < 5 ORDER BY ALL DESC NULLS LAST", OrderByKind::All(OrderByOptions { - asc: Some(false), + sort: Some(OrderBySort::Desc), nulls_first: Some(false), }), ), @@ -2739,7 +2739,7 @@ fn parse_select_order_by_not_support_all() { OrderByKind::Expressions(vec![OrderByExpr { expr: Expr::Identifier(Ident::new("ALL")), options: OrderByOptions { - asc: None, + sort: None, nulls_first: None, }, with_fill: None, @@ -2750,7 +2750,7 @@ fn parse_select_order_by_not_support_all() { OrderByKind::Expressions(vec![OrderByExpr { expr: Expr::Identifier(Ident::new("ALL")), options: OrderByOptions { - asc: Some(true), + sort: Some(OrderBySort::Asc), nulls_first: Some(true), }, with_fill: None, @@ -2761,7 +2761,7 @@ fn parse_select_order_by_not_support_all() { OrderByKind::Expressions(vec![OrderByExpr { expr: Expr::Identifier(Ident::new("ALL")), options: OrderByOptions { - asc: Some(false), + sort: Some(OrderBySort::Desc), nulls_first: Some(false), }, with_fill: None, @@ -2784,7 +2784,7 @@ fn parse_select_order_by_nulls_order() { OrderByExpr { expr: Expr::Identifier(Ident::new("lname")), options: OrderByOptions { - asc: Some(true), + sort: Some(OrderBySort::Asc), nulls_first: Some(true), }, with_fill: None, @@ -2792,7 +2792,7 @@ fn parse_select_order_by_nulls_order() { OrderByExpr { expr: Expr::Identifier(Ident::new("fname")), options: OrderByOptions { - asc: Some(false), + sort: Some(OrderBySort::Desc), nulls_first: Some(false), }, with_fill: None, @@ -2808,6 +2808,67 @@ fn parse_select_order_by_nulls_order() { assert_eq!(Some(expected_limit_clause), select.limit_clause); } +#[test] +fn parse_aggregate_order_by_using_operator() { + let sql = "SELECT aggfns(DISTINCT a, a, c ORDER BY c USING ~<~, a) FROM t"; + let dialects = all_dialects_where(|d| d.supports_order_by_using_operator()); + let select = dialects.verified_only_select(sql); + let SelectItem::UnnamedExpr(Expr::Function(Function { + args: FunctionArguments::List(FunctionArgumentList { clauses, .. }), + .. + })) = &select.projection[0] + else { + unreachable!("expected aggregate function in projection"); + }; + + let Some(FunctionArgumentClause::OrderBy(order_by_exprs)) = clauses + .iter() + .find(|clause| matches!(clause, FunctionArgumentClause::OrderBy(_))) + else { + unreachable!("expected ORDER BY clause in aggregate function argument list"); + }; + + assert_eq!( + order_by_exprs[0].options.sort, + Some(OrderBySort::Using(ObjectName::from(vec!["~<~".into()]))) + ); + assert_eq!(order_by_exprs[1].options.sort, None); +} + +#[test] +fn parse_order_by_using_operator_syntax() { + let dialects = all_dialects_where(|d| d.supports_order_by_using_operator()); + dialects.one_statement_parses_to( + "SELECT a FROM t ORDER BY a USING OPERATOR(<)", + "SELECT a FROM t ORDER BY a USING <", + ); + + let query = dialects + .verified_query("SELECT a FROM t ORDER BY a USING OPERATOR(pg_catalog.<) NULLS LAST"); + let order_by = query.order_by.expect("expected ORDER BY clause"); + let OrderByKind::Expressions(exprs) = order_by.kind else { + unreachable!("expected ORDER BY expressions"); + }; + + assert_eq!( + exprs[0].options.sort, + Some(OrderBySort::Using(ObjectName::from(vec![ + Ident::new("pg_catalog"), + Ident::new("<"), + ]))) + ); + assert_eq!(exprs[0].options.nulls_first, Some(false)); +} + +#[test] +fn parse_order_by_using_operator_invalid_cases() { + let dialects = all_dialects_where(|d| d.supports_order_by_using_operator()); + let err = dialects + .parse_sql_statements("SELECT a FROM t ORDER BY a USING OPERATOR();") + .unwrap_err(); + assert!(matches!(err, ParserError::ParserError(_))); +} + #[test] fn parse_select_group_by() { let sql = "SELECT id, fname, lname FROM customer GROUP BY lname, fname"; @@ -3014,7 +3075,7 @@ fn parse_select_qualify() { order_by: vec![OrderByExpr { expr: Expr::Identifier(Ident::new("o")), options: OrderByOptions { - asc: None, + sort: None, nulls_first: None, }, with_fill: None, @@ -3463,7 +3524,7 @@ fn parse_listagg() { span: Span::empty(), }), options: OrderByOptions { - asc: None, + sort: None, nulls_first: None, }, with_fill: None, @@ -3475,7 +3536,7 @@ fn parse_listagg() { span: Span::empty(), }), options: OrderByOptions { - asc: None, + sort: None, nulls_first: None, }, with_fill: None, @@ -5734,7 +5795,7 @@ fn parse_window_functions() { order_by: vec![OrderByExpr { expr: Expr::Identifier(Ident::new("dt")), options: OrderByOptions { - asc: Some(false), + sort: Some(OrderBySort::Desc), nulls_first: None, }, with_fill: None, @@ -5960,7 +6021,7 @@ fn test_parse_named_window() { span: Span::empty(), }), options: OrderByOptions { - asc: None, + sort: None, nulls_first: None, }, with_fill: None, @@ -9455,7 +9516,7 @@ fn parse_create_index() { expr: Expr::Identifier(Ident::new("name")), with_fill: None, options: OrderByOptions { - asc: None, + sort: None, nulls_first: None, }, }, @@ -9466,7 +9527,7 @@ fn parse_create_index() { expr: Expr::Identifier(Ident::new("age")), with_fill: None, options: OrderByOptions { - asc: Some(false), + sort: Some(OrderBySort::Desc), nulls_first: None, }, }, @@ -9501,7 +9562,7 @@ fn test_create_index_with_using_function() { expr: Expr::Identifier(Ident::new("name")), with_fill: None, options: OrderByOptions { - asc: None, + sort: None, nulls_first: None, }, }, @@ -9512,7 +9573,7 @@ fn test_create_index_with_using_function() { expr: Expr::Identifier(Ident::new("age")), with_fill: None, options: OrderByOptions { - asc: Some(false), + sort: Some(OrderBySort::Desc), nulls_first: None, }, }, @@ -9557,7 +9618,7 @@ fn test_create_index_with_with_clause() { column: OrderByExpr { expr: Expr::Identifier(Ident::new("title")), options: OrderByOptions { - asc: None, + sort: None, nulls_first: None, }, with_fill: None, @@ -13183,7 +13244,7 @@ fn test_match_recognize() { order_by: vec![OrderByExpr { expr: Expr::Identifier(Ident::new("price_date")), options: OrderByOptions { - asc: None, + sort: None, nulls_first: None, }, with_fill: None, diff --git a/tests/sqlparser_hive.rs b/tests/sqlparser_hive.rs index 1b0948518..c0a15d5b9 100644 --- a/tests/sqlparser_hive.rs +++ b/tests/sqlparser_hive.rs @@ -23,7 +23,8 @@ use sqlparser::ast::{ ClusteredBy, CommentDef, CreateFunction, CreateFunctionBody, CreateFunctionUsing, CreateTable, Expr, Function, FunctionArgumentList, FunctionArguments, Ident, ObjectName, OrderByExpr, - OrderByOptions, SelectItem, Set, Statement, TableFactor, UnaryOperator, Use, Value, + OrderByOptions, OrderBySort, SelectItem, Set, Statement, TableFactor, UnaryOperator, Use, + Value, }; use sqlparser::dialect::{AnsiDialect, GenericDialect, HiveDialect}; use sqlparser::parser::ParserError; @@ -171,7 +172,7 @@ fn create_table_with_clustered_by() { OrderByExpr { expr: Expr::Identifier(Ident::new("a")), options: OrderByOptions { - asc: Some(true), + sort: Some(OrderBySort::Asc), nulls_first: None, }, with_fill: None, @@ -179,7 +180,7 @@ fn create_table_with_clustered_by() { OrderByExpr { expr: Expr::Identifier(Ident::new("b")), options: OrderByOptions { - asc: Some(false), + sort: Some(OrderBySort::Desc), nulls_first: None, }, with_fill: None, diff --git a/tests/sqlparser_mysql.rs b/tests/sqlparser_mysql.rs index 269787c29..928766424 100644 --- a/tests/sqlparser_mysql.rs +++ b/tests/sqlparser_mysql.rs @@ -679,7 +679,7 @@ fn table_constraint_unique_primary_ctor( column: OrderByExpr { expr: Expr::Identifier(ident), options: OrderByOptions { - asc: None, + sort: None, nulls_first: None, }, with_fill: None, @@ -2797,7 +2797,7 @@ fn parse_delete_with_order_by() { span: Span::empty(), }), options: OrderByOptions { - asc: Some(false), + sort: Some(OrderBySort::Desc), nulls_first: None, }, with_fill: None, diff --git a/tests/sqlparser_postgres.rs b/tests/sqlparser_postgres.rs index af0f2be33..855761a75 100644 --- a/tests/sqlparser_postgres.rs +++ b/tests/sqlparser_postgres.rs @@ -2778,7 +2778,7 @@ fn parse_create_indices_with_operator_classes() { within_group: vec![], }), options: OrderByOptions { - asc: None, + sort: None, nulls_first: None, }, with_fill: None, @@ -2842,7 +2842,7 @@ fn parse_create_indices_with_operator_classes() { span: Span::empty() }), options: OrderByOptions { - asc: None, + sort: None, nulls_first: None, }, with_fill: None,