Skip to content

Add support for ORDER BY ALL #1724

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 10 commits into from
Feb 22, 2025
10 changes: 5 additions & 5 deletions src/ast/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,11 +66,11 @@ pub use self::query::{
JsonTableColumn, JsonTableColumnErrorHandling, JsonTableNamedColumn, JsonTableNestedColumn,
LateralView, LockClause, LockType, MatchRecognizePattern, MatchRecognizeSymbol, Measure,
NamedWindowDefinition, NamedWindowExpr, NonBlock, Offset, OffsetRows, OpenJsonTableColumn,
OrderBy, OrderByExpr, PivotValueSource, ProjectionSelect, Query, RenameSelectItem,
RepetitionQuantifier, ReplaceSelectElement, ReplaceSelectItem, RowsPerMatch, Select,
SelectFlavor, SelectInto, SelectItem, SelectItemQualifiedWildcardKind, SetExpr, SetOperator,
SetQuantifier, Setting, SymbolDefinition, Table, TableAlias, TableAliasColumnDef, TableFactor,
TableFunctionArgs, TableIndexHintForClause, TableIndexHintType, TableIndexHints,
OrderBy, OrderByExpr, OrderByKind, OrderByOptions, PivotValueSource, ProjectionSelect, Query,
RenameSelectItem, RepetitionQuantifier, ReplaceSelectElement, ReplaceSelectItem, RowsPerMatch,
Select, SelectFlavor, SelectInto, SelectItem, SelectItemQualifiedWildcardKind, SetExpr,
SetOperator, SetQuantifier, Setting, SymbolDefinition, Table, TableAlias, TableAliasColumnDef,
TableFactor, TableFunctionArgs, TableIndexHintForClause, TableIndexHintType, TableIndexHints,
TableIndexType, TableSample, TableSampleBucket, TableSampleKind, TableSampleMethod,
TableSampleModifier, TableSampleQuantity, TableSampleSeed, TableSampleSeedModifier,
TableSampleUnit, TableVersion, TableWithJoins, Top, TopQuantity, UpdateTableFromKind,
Expand Down
73 changes: 53 additions & 20 deletions src/ast/query.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2218,30 +2218,50 @@ pub enum JoinConstraint {
None,
}

#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
pub enum OrderByKind {
/// ALL syntax of [DuckDB] and [ClickHouse].
///
/// [DuckDB]: <https://duckdb.org/docs/sql/query_syntax/orderby>
/// [ClickHouse]: <https://clickhouse.com/docs/en/sql-reference/statements/select/order-by>
All(OrderByOptions),

/// Expressions
Expressions(Vec<OrderByExpr>),
}

#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
pub struct OrderBy {
pub exprs: Vec<OrderByExpr>,
pub kind: OrderByKind,

/// Optional: `INTERPOLATE`
/// Supported by [ClickHouse syntax]
///
/// [ClickHouse syntax]: <https://clickhouse.com/docs/en/sql-reference/statements/select/order-by#order-by-expr-with-fill-modifier>
pub interpolate: Option<Interpolate>,
}

impl fmt::Display for OrderBy {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "ORDER BY")?;
if !self.exprs.is_empty() {
write!(f, " {}", display_comma_separated(&self.exprs))?;
match &self.kind {
OrderByKind::Expressions(exprs) => {
write!(f, " {}", display_comma_separated(exprs))?;
}
OrderByKind::All(all) => {
write!(f, " ALL{}", all)?;
}
}

if let Some(ref interpolate) = self.interpolate {
match &interpolate.exprs {
Some(exprs) => write!(f, " INTERPOLATE ({})", display_comma_separated(exprs))?,
None => write!(f, " INTERPOLATE")?,
}
}

Ok(())
}
}
Expand All @@ -2252,28 +2272,15 @@ impl fmt::Display for OrderBy {
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
pub struct OrderByExpr {
pub expr: Expr,
/// Optional `ASC` or `DESC`
pub asc: Option<bool>,
/// Optional `NULLS FIRST` or `NULLS LAST`
pub nulls_first: Option<bool>,
pub options: OrderByOptions,
/// Optional: `WITH FILL`
/// Supported by [ClickHouse syntax]: <https://clickhouse.com/docs/en/sql-reference/statements/select/order-by#order-by-expr-with-fill-modifier>
pub with_fill: Option<WithFill>,
}

impl fmt::Display for OrderByExpr {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.expr)?;
match self.asc {
Some(true) => write!(f, " ASC")?,
Some(false) => write!(f, " DESC")?,
None => (),
}
match self.nulls_first {
Some(true) => write!(f, " NULLS FIRST")?,
Some(false) => write!(f, " NULLS LAST")?,
None => (),
}
write!(f, "{}{}", self.expr, self.options)?;
if let Some(ref with_fill) = self.with_fill {
write!(f, " {}", with_fill)?
}
Expand Down Expand Up @@ -2339,6 +2346,32 @@ impl fmt::Display for InterpolateExpr {
}
}

#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
pub struct OrderByOptions {
/// Optional `ASC` or `DESC`
pub asc: Option<bool>,
/// Optional `NULLS FIRST` or `NULLS LAST`
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")?,
None => (),
}
match self.nulls_first {
Some(true) => write!(f, " NULLS FIRST")?,
Some(false) => write!(f, " NULLS LAST")?,
None => (),
}
Ok(())
}
}

#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
Expand Down
26 changes: 15 additions & 11 deletions src/ast/spans.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ use super::{
GroupByExpr, HavingBound, IlikeSelectItem, Insert, Interpolate, InterpolateExpr, Join,
JoinConstraint, JoinOperator, JsonPath, JsonPathElem, LateralView, MatchRecognizePattern,
Measure, NamedWindowDefinition, ObjectName, ObjectNamePart, Offset, OnConflict,
OnConflictAction, OnInsert, OrderBy, OrderByExpr, Partition, PivotValueSource,
OnConflictAction, OnInsert, OrderBy, OrderByExpr, OrderByKind, Partition, PivotValueSource,
ProjectionSelect, Query, ReferentialAction, RenameSelectItem, ReplaceSelectElement,
ReplaceSelectItem, Select, SelectInto, SelectItem, SetExpr, SqlOption, Statement, Subscript,
SymbolDefinition, TableAlias, TableAliasColumnDef, TableConstraint, TableFactor, TableObject,
Expand Down Expand Up @@ -1096,16 +1096,21 @@ impl Spanned for ProjectionSelect {
}
}

/// # partial span
///
/// Missing spans:
/// - [OrderByKind::All]
impl Spanned for OrderBy {
fn span(&self) -> Span {
let OrderBy { exprs, interpolate } = self;

union_spans(
exprs
.iter()
.map(|i| i.span())
.chain(interpolate.iter().map(|i| i.span())),
)
match &self.kind {
OrderByKind::All(_) => Span::empty(),
OrderByKind::Expressions(exprs) => union_spans(
exprs
.iter()
.map(|i| i.span())
.chain(self.interpolate.iter().map(|i| i.span())),
),
}
}
}

Expand Down Expand Up @@ -1905,8 +1910,7 @@ impl Spanned for OrderByExpr {
fn span(&self) -> Span {
let OrderByExpr {
expr,
asc: _, // bool
nulls_first: _, // bool
options: _,
with_fill,
} = self;

Expand Down
5 changes: 5 additions & 0 deletions src/dialect/clickhouse.rs
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,11 @@ impl Dialect for ClickHouseDialect {
true
}

/// See <https://clickhouse.com/docs/en/sql-reference/statements/select/order-by>
fn supports_order_by_all(&self) -> bool {
true
}

// See <https://clickhouse.com/docs/en/sql-reference/aggregate-functions/grouping_function#grouping-sets>
fn supports_group_by_expr(&self) -> bool {
true
Expand Down
5 changes: 5 additions & 0 deletions src/dialect/duckdb.rs
Original file line number Diff line number Diff line change
Expand Up @@ -89,4 +89,9 @@ impl Dialect for DuckDbDialect {
fn supports_from_first_select(&self) -> bool {
true
}

/// See DuckDB <https://duckdb.org/docs/sql/query_syntax/orderby.html#order-by-all-examples>
fn supports_order_by_all(&self) -> bool {
true
}
}
8 changes: 8 additions & 0 deletions src/dialect/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -918,6 +918,14 @@ pub trait Dialect: Debug + Any {
fn supports_array_typedef_size(&self) -> bool {
false
}

/// Returns true if the dialect supports `ORDER BY ALL`.
/// `ALL` which means all columns of the SELECT clause.
///
/// For example: ```SELECT * FROM addresses ORDER BY ALL;```.
fn supports_order_by_all(&self) -> bool {
false
}
}

/// This represents the operators for which precedence must be defined
Expand Down
58 changes: 36 additions & 22 deletions src/parser/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9191,17 +9191,26 @@ impl<'a> Parser<'a> {

pub fn parse_optional_order_by(&mut self) -> Result<Option<OrderBy>, ParserError> {
if self.parse_keywords(&[Keyword::ORDER, Keyword::BY]) {
let order_by_exprs = self.parse_comma_separated(Parser::parse_order_by_expr)?;
let interpolate = if dialect_of!(self is ClickHouseDialect | GenericDialect) {
self.parse_interpolations()?
} else {
None
};

Ok(Some(OrderBy {
exprs: order_by_exprs,
interpolate,
}))
let order_by =
if self.dialect.supports_order_by_all() && self.parse_keyword(Keyword::ALL) {
let order_by_options = self.parse_order_by_options()?;
OrderBy {
kind: OrderByKind::All(order_by_options),
interpolate: None,
}
} else {
let exprs = self.parse_comma_separated(Parser::parse_order_by_expr)?;
let interpolate = if dialect_of!(self is ClickHouseDialect | GenericDialect) {
self.parse_interpolations()?
} else {
None
};
OrderBy {
kind: OrderByKind::Expressions(exprs),
interpolate,
}
};
Ok(Some(order_by))
} else {
Ok(None)
}
Expand Down Expand Up @@ -13379,15 +13388,7 @@ impl<'a> Parser<'a> {
pub fn parse_order_by_expr(&mut self) -> Result<OrderByExpr, ParserError> {
let expr = self.parse_expr()?;

let asc = self.parse_asc_desc();

let nulls_first = if self.parse_keywords(&[Keyword::NULLS, Keyword::FIRST]) {
Some(true)
} else if self.parse_keywords(&[Keyword::NULLS, Keyword::LAST]) {
Some(false)
} else {
None
};
let options = self.parse_order_by_options()?;

let with_fill = if dialect_of!(self is ClickHouseDialect | GenericDialect)
&& self.parse_keywords(&[Keyword::WITH, Keyword::FILL])
Expand All @@ -13399,12 +13400,25 @@ impl<'a> Parser<'a> {

Ok(OrderByExpr {
expr,
asc,
nulls_first,
options,
with_fill,
})
}

fn parse_order_by_options(&mut self) -> Result<OrderByOptions, ParserError> {
let asc = self.parse_asc_desc();

let nulls_first = if self.parse_keywords(&[Keyword::NULLS, Keyword::FIRST]) {
Some(true)
} else if self.parse_keywords(&[Keyword::NULLS, Keyword::LAST]) {
Some(false)
} else {
None
};

Ok(OrderByOptions { asc, nulls_first })
}

// Parse a WITH FILL clause (ClickHouse dialect)
// that follow the WITH FILL keywords in a ORDER BY clause
pub fn parse_with_fill(&mut self) -> Result<WithFill, ParserError> {
Expand Down
Loading