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
13 changes: 12 additions & 1 deletion src/ast/query.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2860,6 +2860,8 @@ impl fmt::Display for OrderBy {
pub struct OrderByExpr {
/// The expression to order by.
pub expr: Expr,
/// Optional PostgreSQL `USING <operator>` clause.
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 include reference doc to maybe this?
https://www.postgresql.org/docs/current/sql-select.html

so folks know where to find this feature easily

Copy link
Contributor

Choose a reason for hiding this comment

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

fyi: re the comment would be nice to include the link

pub using_operator: Option<ObjectName>,
/// Ordering options such as `ASC`/`DESC` and `NULLS` behavior.
pub options: OrderByOptions,
/// Optional `WITH FILL` clause (ClickHouse extension) which specifies how to fill gaps.
Expand All @@ -2870,6 +2872,7 @@ impl From<Ident> for OrderByExpr {
fn from(ident: Ident) -> Self {
OrderByExpr {
expr: Expr::Identifier(ident),
using_operator: None,
options: OrderByOptions::default(),
with_fill: None,
}
Expand All @@ -2878,7 +2881,15 @@ 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)?;
if let Some(using_operator) = &self.using_operator {
if using_operator.0.len() > 1 {
write!(f, " USING OPERATOR({using_operator})")?;
} else {
write!(f, " USING {using_operator}")?;
}
}
write!(f, "{}", self.options)?;
if let Some(ref with_fill) = self.with_fill {
write!(f, " {with_fill}")?
}
Expand Down
1 change: 1 addition & 0 deletions src/ast/spans.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2095,6 +2095,7 @@ impl Spanned for OrderByExpr {
fn span(&self) -> Span {
let OrderByExpr {
expr,
using_operator: _,
options: _,
with_fill,
} = self;
Expand Down
8 changes: 8 additions & 0 deletions src/dialect/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1345,6 +1345,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
74 changes: 69 additions & 5 deletions src/parser/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18156,7 +18156,32 @@ impl<'a> Parser<'a> {
None
};

let options = self.parse_order_by_options()?;
let using_operator = if !with_operator_class
&& self.dialect.supports_order_by_using_operator()
&& self.parse_keyword(Keyword::USING)
{
Some(self.parse_order_by_using_operator()?)
} else {
None
};

let options = if using_operator.is_some() {
if self
.peek_one_of_keywords(&[Keyword::ASC, Keyword::DESC])
.is_some()
{
return parser_err!(
"ASC/DESC cannot be used together with USING in ORDER BY".to_string(),
self.peek_token_ref().span.start
);
}
OrderByOptions {
asc: None,
nulls_first: self.parse_order_by_nulls_first_last(),
Comment on lines +18169 to +18180
Copy link
Contributor

Choose a reason for hiding this comment

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

I think we can skip this validation

}
Comment on lines +18159 to +18181
Copy link
Contributor

Choose a reason for hiding this comment

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

would it be possible to use something like this representation instead?

pub enum OrderByExpr {
    Asc,
    Desc,
    Using(...),
    UsingOperator(...),
}

pub struct OrderByOptions {
    pub expr: Option<OrderByExpr>,
    pub nulls_first: Option<bool>,
}

} else {
self.parse_order_by_options()?
};

let with_fill = if self.dialect.supports_with_fill()
&& self.parse_keywords(&[Keyword::WITH, Keyword::FILL])
Expand All @@ -18169,23 +18194,61 @@ impl<'a> Parser<'a> {
Ok((
OrderByExpr {
expr,
using_operator,
options,
with_fill,
},
operator_class,
))
}

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> {
let dialect = self.dialect;

let nulls_first = if self.parse_keywords(&[Keyword::NULLS, Keyword::FIRST]) {
if self.parse_keyword(Keyword::OPERATOR) {
self.expect_token(&Token::LParen)?;
let operator_name = self.parse_operator_name()?;
let Some(last_part) = operator_name.0.last() else {
return self.expected_ref("an operator name", self.peek_token_ref());
};
let operator = last_part.to_string();
if operator.is_empty()
|| !operator
.chars()
.all(|ch| dialect.is_custom_operator_part(ch))
{
return self.expected_ref("an operator name", self.peek_token_ref());
}
Comment on lines +18214 to +18221
Copy link
Contributor

Choose a reason for hiding this comment

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

the to_string() cloning doesn't seem to be required here? But in general this logic iiuc is validating some property of the allowed naming? if so I think it would make sense to skip the validation here in the parser (same for the similar logic below, so that if the code is to be kept then we likely want to pull it out to a reusable function)

self.expect_token(&Token::RParen)?;
return Ok(operator_name);
}

let token = self.next_token();
let operator = token.token.to_string();
if !operator.is_empty()
&& operator
.chars()
.all(|ch| dialect.is_custom_operator_part(ch))
{
Ok(ObjectName::from(vec![Ident::new(operator)]))
} else {
self.expected_ref("an ordering operator after USING", &token)
}
}

fn parse_order_by_nulls_first_last(&mut self) -> Option<bool> {
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
fn parse_order_by_nulls_first_last(&mut self) -> Option<bool> {
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 asc = self.parse_asc_desc();
let nulls_first = self.parse_order_by_nulls_first_last();

Ok(OrderByOptions { asc, nulls_first })
}
Expand Down Expand Up @@ -20399,6 +20462,7 @@ mod tests {
asc: None,
nulls_first: None,
},
using_operator: None,
with_fill: None,
},
operator_class: None,
Expand Down
2 changes: 2 additions & 0 deletions tests/sqlparser_bigquery.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2721,6 +2721,7 @@ fn test_export_data() {
asc: None,
nulls_first: None,
},
using_operator: None,
with_fill: None,
},]),
interpolate: None,
Expand Down Expand Up @@ -2827,6 +2828,7 @@ fn test_export_data() {
asc: None,
nulls_first: None,
},
using_operator: None,
with_fill: None,
},]),
interpolate: None,
Expand Down
3 changes: 3 additions & 0 deletions tests/sqlparser_clickhouse.rs
Original file line number Diff line number Diff line change
Expand Up @@ -334,6 +334,7 @@ fn parse_alter_table_add_projection() {
asc: None,
nulls_first: None,
},
using_operator: None,
with_fill: None,
}]),
interpolate: None,
Expand Down Expand Up @@ -1162,6 +1163,7 @@ fn parse_select_order_by_with_fill_interpolate() {
asc: Some(true),
nulls_first: Some(true),
},
using_operator: None,
with_fill: Some(WithFill {
from: Some(Expr::value(number("10"))),
to: Some(Expr::value(number("20"))),
Expand All @@ -1174,6 +1176,7 @@ fn parse_select_order_by_with_fill_interpolate() {
asc: Some(false),
nulls_first: Some(false),
},
using_operator: None,
with_fill: Some(WithFill {
from: Some(Expr::value(number("30"))),
to: Some(Expr::value(number("40"))),
Expand Down
21 changes: 21 additions & 0 deletions tests/sqlparser_common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2577,6 +2577,7 @@ fn parse_select_order_by() {
asc: Some(true),
nulls_first: None,
},
using_operator: None,
with_fill: None,
},
OrderByExpr {
Expand All @@ -2585,6 +2586,7 @@ fn parse_select_order_by() {
asc: Some(false),
nulls_first: None,
},
using_operator: None,
with_fill: None,
},
OrderByExpr {
Expand All @@ -2593,6 +2595,7 @@ fn parse_select_order_by() {
asc: None,
nulls_first: None,
},
using_operator: None,
with_fill: None,
},
]),
Expand All @@ -2618,6 +2621,7 @@ fn parse_select_order_by_limit() {
asc: Some(true),
nulls_first: None,
},
using_operator: None,
with_fill: None,
},
OrderByExpr {
Expand All @@ -2626,6 +2630,7 @@ fn parse_select_order_by_limit() {
asc: Some(false),
nulls_first: None,
},
using_operator: None,
with_fill: None,
},
]),
Expand Down Expand Up @@ -2739,6 +2744,7 @@ fn parse_select_order_by_not_support_all() {
asc: None,
nulls_first: None,
},
using_operator: None,
with_fill: None,
}]),
),
Expand All @@ -2750,6 +2756,7 @@ fn parse_select_order_by_not_support_all() {
asc: Some(true),
nulls_first: Some(true),
},
using_operator: None,
with_fill: None,
}]),
),
Expand All @@ -2761,6 +2768,7 @@ fn parse_select_order_by_not_support_all() {
asc: Some(false),
nulls_first: Some(false),
},
using_operator: None,
with_fill: None,
}]),
),
Expand All @@ -2784,6 +2792,7 @@ fn parse_select_order_by_nulls_order() {
asc: Some(true),
nulls_first: Some(true),
},
using_operator: None,
with_fill: None,
},
OrderByExpr {
Expand All @@ -2792,6 +2801,7 @@ fn parse_select_order_by_nulls_order() {
asc: Some(false),
nulls_first: Some(false),
},
using_operator: None,
with_fill: None,
},
]),
Expand Down Expand Up @@ -3014,6 +3024,7 @@ fn parse_select_qualify() {
asc: None,
nulls_first: None,
},
using_operator: None,
with_fill: None,
}],
window_frame: None,
Expand Down Expand Up @@ -3459,6 +3470,7 @@ fn parse_listagg() {
asc: None,
nulls_first: None,
},
using_operator: None,
with_fill: None,
},
OrderByExpr {
Expand All @@ -3471,6 +3483,7 @@ fn parse_listagg() {
asc: None,
nulls_first: None,
},
using_operator: None,
with_fill: None,
},
]
Expand Down Expand Up @@ -5730,6 +5743,7 @@ fn parse_window_functions() {
asc: Some(false),
nulls_first: None,
},
using_operator: None,
with_fill: None,
}],
window_frame: None,
Expand Down Expand Up @@ -5956,6 +5970,7 @@ fn test_parse_named_window() {
asc: None,
nulls_first: None,
},
using_operator: None,
with_fill: None,
}],
window_frame: None,
Expand Down Expand Up @@ -9440,6 +9455,7 @@ fn parse_create_index() {
operator_class: None,
column: OrderByExpr {
expr: Expr::Identifier(Ident::new("name")),
using_operator: None,
with_fill: None,
options: OrderByOptions {
asc: None,
Expand All @@ -9451,6 +9467,7 @@ fn parse_create_index() {
operator_class: None,
column: OrderByExpr {
expr: Expr::Identifier(Ident::new("age")),
using_operator: None,
with_fill: None,
options: OrderByOptions {
asc: Some(false),
Expand Down Expand Up @@ -9486,6 +9503,7 @@ fn test_create_index_with_using_function() {
operator_class: None,
column: OrderByExpr {
expr: Expr::Identifier(Ident::new("name")),
using_operator: None,
with_fill: None,
options: OrderByOptions {
asc: None,
Expand All @@ -9497,6 +9515,7 @@ fn test_create_index_with_using_function() {
operator_class: None,
column: OrderByExpr {
expr: Expr::Identifier(Ident::new("age")),
using_operator: None,
with_fill: None,
options: OrderByOptions {
asc: Some(false),
Expand Down Expand Up @@ -9547,6 +9566,7 @@ fn test_create_index_with_with_clause() {
asc: None,
nulls_first: None,
},
using_operator: None,
with_fill: None,
},
operator_class: None,
Expand Down Expand Up @@ -13173,6 +13193,7 @@ fn test_match_recognize() {
asc: None,
nulls_first: None,
},
using_operator: None,
with_fill: None,
}],
measures: vec![
Expand Down
2 changes: 2 additions & 0 deletions tests/sqlparser_hive.rs
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,7 @@ fn create_table_with_clustered_by() {
asc: Some(true),
nulls_first: None,
},
using_operator: None,
with_fill: None,
},
OrderByExpr {
Expand All @@ -182,6 +183,7 @@ fn create_table_with_clustered_by() {
asc: Some(false),
nulls_first: None,
},
using_operator: None,
with_fill: None,
},
]),
Expand Down
Loading