Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/ast/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
44 changes: 35 additions & 9 deletions src/ast/query.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 <operator>` and `NULLS` behavior.
pub options: OrderByOptions,
/// Optional `WITH FILL` clause (ClickHouse extension) which specifies how to fill gaps.
pub with_fill: Option<WithFill>,
Expand All @@ -2901,7 +2901,8 @@ impl From<Ident> 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}")?
}
Expand Down Expand Up @@ -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:
/// <https://www.postgresql.org/docs/current/sql-select.html>
#[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 <operator>` ordering.
///
/// See <https://www.postgresql.org/docs/current/sql-select.html>
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<bool>,
/// Optional sort order: `ASC`, `DESC`, or `USING <operator>`.
pub sort: Option<OrderBySort>,
/// Optional `NULLS FIRST` (`Some(true)`) or `NULLS LAST` (`Some(false)`).
pub nulls_first: Option<bool>,
}

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 {
Expand Down
8 changes: 8 additions & 0 deletions src/dialect/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1352,6 +1352,14 @@ pub trait Dialect: Debug + Any {
false
}

/// Returns true if the dialect supports PostgreSQL-style ordering operators:
/// `ORDER BY expr USING <operator>`.
///
/// 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 <charset_name> [COLLATE <collation_name>]`.
///
/// - [MySQL](https://dev.mysql.com/doc/refman/8.4/en/set-names.html)
Expand Down
4 changes: 4 additions & 0 deletions src/dialect/postgresql.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
51 changes: 43 additions & 8 deletions src/parser/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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<OrderBySort> {
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<OrderByExpr, ParserError> {
self.parse_order_by_expr_inner(false)
Expand Down Expand Up @@ -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])
Expand All @@ -18341,18 +18361,33 @@ impl<'a> Parser<'a> {
))
}

fn parse_order_by_options(&mut self) -> Result<OrderByOptions, ParserError> {
let asc = self.parse_asc_desc();
fn parse_order_by_using_operator(&mut self) -> Result<ObjectName, ParserError> {
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<bool> {
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<OrderByOptions, ParserError> {
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)
Expand Down Expand Up @@ -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,
Expand Down
4 changes: 2 additions & 2 deletions tests/sqlparser_bigquery.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down
6 changes: 3 additions & 3 deletions tests/sqlparser_clickhouse.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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 {
Expand All @@ -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 {
Expand Down
Loading
Loading