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
19 changes: 10 additions & 9 deletions src/ast/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,15 +66,16 @@ 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,
TableIndexType, TableSample, TableSampleBucket, TableSampleKind, TableSampleMethod,
TableSampleModifier, TableSampleQuantity, TableSampleSeed, TableSampleSeedModifier,
TableSampleUnit, TableVersion, TableWithJoins, Top, TopQuantity, UpdateTableFromKind,
ValueTableMode, Values, WildcardAdditionalOptions, With, WithFill,
OrderBy, OrderByAll, OrderByExpr, OrderByExprsWithInterpolate, 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, ValueTableMode, Values,
WildcardAdditionalOptions, With, WithFill,
};

pub use self::trigger::{
Expand Down
100 changes: 87 additions & 13 deletions src/ast/query.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2205,31 +2205,50 @@ pub enum JoinConstraint {
#[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>,
/// Optional: `INTERPOLATE`
/// Supported by [ClickHouse syntax]
pub enum OrderBy {
/// ALL syntax of [DuckDB] and [ClickHouse].
///
/// [ClickHouse syntax]: <https://clickhouse.com/docs/en/sql-reference/statements/select/order-by#order-by-expr-with-fill-modifier>
pub interpolate: Option<Interpolate>,
/// [DuckDB]: <https://duckdb.org/docs/sql/query_syntax/orderby>
/// [ClickHouse]: <https://clickhouse.com/docs/en/sql-reference/statements/select/order-by>
All(OrderByAll),

/// Expressions
Expressions(OrderByExprsWithInterpolate),
}

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))?;
}
if let Some(ref interpolate) = self.interpolate {
match &interpolate.exprs {
Some(exprs) => write!(f, " INTERPOLATE ({})", display_comma_separated(exprs))?,
None => write!(f, " INTERPOLATE")?,
match self {
OrderBy::Expressions(exprs) => {
write!(f, "{}", exprs)?;
}
OrderBy::All(all) => {
write!(f, " ALL{}", all)?;
}
}

Ok(())
}
}

impl OrderBy {
pub fn get_exprs(&self) -> Option<&Vec<OrderByExpr>> {
match self {
OrderBy::Expressions(exprs_with_interpolate) => Some(&exprs_with_interpolate.exprs),
OrderBy::All(_) => None,
}
}
pub fn get_interpolate(&self) -> Option<&Interpolate> {
match self {
OrderBy::Expressions(exprs_with_interpolate) => {
exprs_with_interpolate.interpolate.as_ref()
}
OrderBy::All(_) => None,
}
}
}

/// An `ORDER BY` expression
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
Expand Down Expand Up @@ -2323,6 +2342,61 @@ impl fmt::Display for InterpolateExpr {
}
}

/// `ORDER BY` expressions with interpolate
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
pub struct OrderByExprsWithInterpolate {
pub exprs: Vec<OrderByExpr>,
/// Expressions
/// 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 OrderByExprsWithInterpolate {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
if !self.exprs.is_empty() {
write!(f, " {}", display_comma_separated(&self.exprs))?;
}
if let Some(ref interpolate) = self.interpolate {
match &interpolate.exprs {
Some(exprs) => write!(f, " INTERPOLATE ({})", display_comma_separated(exprs))?,
None => write!(f, " INTERPOLATE")?,
}
}
Ok(())
}
}

/// 'ORDER BY ALL' clause
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
pub struct OrderByAll {
/// Optional `ASC` or `DESC`
pub asc: Option<bool>,
/// Optional `NULLS FIRST` or `NULLS LAST`
pub nulls_first: Option<bool>,
}

impl fmt::Display for OrderByAll {
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
22 changes: 14 additions & 8 deletions src/ast/spans.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1096,16 +1096,22 @@ impl Spanned for ProjectionSelect {
}
}

/// # partial span
///
/// Missing spans:
/// - [OrderBy::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 {
OrderBy::All(_) => Span::empty(),
OrderBy::Expressions(expressions) => union_spans(
expressions
.exprs
.iter()
.map(|i| i.span())
.chain(expressions.interpolate.iter().map(|i| i.span())),
),
}
}
}

Expand Down
33 changes: 24 additions & 9 deletions src/parser/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9183,17 +9183,19 @@ 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()?
let order_by = if self.parse_keyword(Keyword::ALL) {
let order_by_all = self.parse_order_by_all()?;
OrderBy::All(order_by_all)
} else {
None
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::Expressions(OrderByExprsWithInterpolate { exprs, interpolate })
};

Ok(Some(OrderBy {
exprs: order_by_exprs,
interpolate,
}))
Ok(Some(order_by))
} else {
Ok(None)
}
Expand Down Expand Up @@ -13379,6 +13381,19 @@ impl<'a> Parser<'a> {
})
}

pub fn parse_order_by_all(&mut self) -> Result<OrderByAll, ParserError> {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
pub fn parse_order_by_all(&mut self) -> Result<OrderByAll, ParserError> {
fn parse_order_by_all(&mut self) -> Result<OrderByAll, ParserError> {

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we update parse_order_by_expr to use this helper? since it shares this code so that we avoid keeping both copies in sync

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are right, this change is better. I plan to first change pub fn parse_order_by_all to fn parse_order_by_options and then reuse it in parse_order_by_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
};
Ok(OrderByAll { 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
38 changes: 27 additions & 11 deletions tests/sqlparser_clickhouse.rs
Original file line number Diff line number Diff line change
Expand Up @@ -322,15 +322,15 @@ fn parse_alter_table_add_projection() {
vec![Identifier(Ident::new("a"))],
vec![]
)),
order_by: Some(OrderBy {
order_by: Some(OrderBy::Expressions(OrderByExprsWithInterpolate {
exprs: vec![OrderByExpr {
expr: Identifier(Ident::new("b")),
asc: None,
nulls_first: None,
with_fill: None,
}],
interpolate: None,
}),
})),
}
}
)
Expand Down Expand Up @@ -1134,7 +1134,7 @@ fn parse_select_order_by_with_fill_interpolate() {
LIMIT 2";
let select = clickhouse().verified_query(sql);
assert_eq!(
OrderBy {
OrderBy::Expressions(OrderByExprsWithInterpolate {
exprs: vec![
OrderByExpr {
expr: Expr::Identifier(Ident::new("fname")),
Expand Down Expand Up @@ -1167,7 +1167,7 @@ fn parse_select_order_by_with_fill_interpolate() {
}),
}])
})
},
}),
select.order_by.expect("ORDER BY expected")
);
assert_eq!(Some(Expr::Value(number("2"))), select.limit);
Expand Down Expand Up @@ -1215,7 +1215,12 @@ fn parse_with_fill() {
to: Some(Expr::Value(number("20"))),
step: Some(Expr::Value(number("2"))),
}),
select.order_by.expect("ORDER BY expected").exprs[0].with_fill
select
.order_by
.expect("ORDER BY expected")
.get_exprs()
.unwrap()[0]
.with_fill
);
}

Expand Down Expand Up @@ -1266,8 +1271,12 @@ fn parse_interpolate_body_with_columns() {
}),
},
])
}),
select.order_by.expect("ORDER BY expected").interpolate
})
.as_ref(),
select
.order_by
.expect("ORDER BY expected")
.get_interpolate()
);
}

Expand All @@ -1276,8 +1285,11 @@ fn parse_interpolate_without_body() {
let sql = "SELECT fname FROM customer ORDER BY fname WITH FILL INTERPOLATE";
let select = clickhouse().verified_query(sql);
assert_eq!(
Some(Interpolate { exprs: None }),
select.order_by.expect("ORDER BY expected").interpolate
Some(Interpolate { exprs: None }).as_ref(),
select
.order_by
.expect("ORDER BY expected")
.get_interpolate()
);
}

Expand All @@ -1288,8 +1300,12 @@ fn parse_interpolate_with_empty_body() {
assert_eq!(
Some(Interpolate {
exprs: Some(vec![])
}),
select.order_by.expect("ORDER BY expected").interpolate
})
.as_ref(),
select
.order_by
.expect("ORDER BY expected")
.get_interpolate()
);
}

Expand Down
Loading