Skip to content

Commit 418b963

Browse files
authored
add nulls first/last support to order by expression (#176)
Following `<sort specification list>` from the standard https://jakewheat.github.io/sql-overview/sql-2016-foundation-grammar.html#_10_10_sort_specification_list
1 parent c918ff0 commit 418b963

File tree

5 files changed

+64
-8
lines changed

5 files changed

+64
-8
lines changed

CHANGELOG.md

+4-2
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,13 @@ Check https://github.com/andygrove/sqlparser-rs/commits/master for undocumented
1313
- Support Snowflake's `FROM (table_name)` (#155) - thanks @eyalleshem!
1414

1515
### Added
16-
- Support basic forms of `CREATE INDEX` and `DROP INDEX` (#167) - thanks @mashuai!
17-
- Support `ON { UPDATE | DELETE } { RESTRICT | CASCADE | SET NULL | NO ACTION | SET DEFAULT }` in `FOREIGN KEY` constraints (#170) - thanks @c7hm4r!
1816
- Support MSSQL `TOP (<N>) [ PERCENT ] [ WITH TIES ]` (#150) - thanks @alexkyllo!
1917
- Support MySQL `LIMIT row_count OFFSET offset` (not followed by `ROW` or `ROWS`) and remember which variant was parsed (#158) - thanks @mjibson!
2018
- Support PostgreSQL `CREATE TABLE IF NOT EXISTS table_name` (#163) - thanks @alex-dukhno!
19+
- Support basic forms of `CREATE INDEX` and `DROP INDEX` (#167) - thanks @mashuai!
20+
- Support `ON { UPDATE | DELETE } { RESTRICT | CASCADE | SET NULL | NO ACTION | SET DEFAULT }` in `FOREIGN KEY` constraints (#170) - thanks @c7hm4r!
21+
- Support basic forms of `CREATE SCHEMA` and `DROP SCHEMA` (#173) - thanks @alex-dukhno!
22+
- Support `NULLS FIRST`/`LAST` in `ORDER BY` expressions (#176) - thanks @houqp!
2123

2224
### Fixed
2325
- Report an error for unterminated string literals (#165)

src/ast/query.rs

+14-4
Original file line numberDiff line numberDiff line change
@@ -374,20 +374,30 @@ pub enum JoinConstraint {
374374
Natural,
375375
}
376376

377-
/// SQL ORDER BY expression
377+
/// An `ORDER BY` expression
378378
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
379379
pub struct OrderByExpr {
380380
pub expr: Expr,
381+
/// Optional `ASC` or `DESC`
381382
pub asc: Option<bool>,
383+
/// Optional `NULLS FIRST` or `NULLS LAST`
384+
pub nulls_first: Option<bool>,
382385
}
383386

384387
impl fmt::Display for OrderByExpr {
385388
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
389+
write!(f, "{}", self.expr)?;
386390
match self.asc {
387-
Some(true) => write!(f, "{} ASC", self.expr),
388-
Some(false) => write!(f, "{} DESC", self.expr),
389-
None => write!(f, "{}", self.expr),
391+
Some(true) => write!(f, " ASC")?,
392+
Some(false) => write!(f, " DESC")?,
393+
None => (),
390394
}
395+
match self.nulls_first {
396+
Some(true) => write!(f, " NULLS FIRST")?,
397+
Some(false) => write!(f, " NULLS LAST")?,
398+
None => (),
399+
}
400+
Ok(())
391401
}
392402
}
393403

src/dialect/keywords.rs

+2
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,7 @@ define_keywords!(
220220
LAG,
221221
LANGUAGE,
222222
LARGE,
223+
LAST,
223224
LAST_VALUE,
224225
LATERAL,
225226
LEAD,
@@ -262,6 +263,7 @@ define_keywords!(
262263
NTILE,
263264
NULL,
264265
NULLIF,
266+
NULLS,
265267
NUMERIC,
266268
OBJECT,
267269
OCTET_LENGTH,

src/parser.rs

+14-1
Original file line numberDiff line numberDiff line change
@@ -2015,7 +2015,20 @@ impl Parser {
20152015
} else {
20162016
None
20172017
};
2018-
Ok(OrderByExpr { expr, asc })
2018+
2019+
let nulls_first = if self.parse_keywords(vec!["NULLS", "FIRST"]) {
2020+
Some(true)
2021+
} else if self.parse_keywords(vec!["NULLS", "LAST"]) {
2022+
Some(false)
2023+
} else {
2024+
None
2025+
};
2026+
2027+
Ok(OrderByExpr {
2028+
expr,
2029+
asc,
2030+
nulls_first,
2031+
})
20192032
}
20202033

20212034
/// Parse a TOP clause, MSSQL equivalent of LIMIT,

tests/sqlparser_common.rs

+30-1
Original file line numberDiff line numberDiff line change
@@ -746,14 +746,17 @@ fn parse_select_order_by() {
746746
OrderByExpr {
747747
expr: Expr::Identifier(Ident::new("lname")),
748748
asc: Some(true),
749+
nulls_first: None,
749750
},
750751
OrderByExpr {
751752
expr: Expr::Identifier(Ident::new("fname")),
752753
asc: Some(false),
754+
nulls_first: None,
753755
},
754756
OrderByExpr {
755757
expr: Expr::Identifier(Ident::new("id")),
756758
asc: None,
759+
nulls_first: None,
757760
},
758761
],
759762
select.order_by
@@ -775,10 +778,35 @@ fn parse_select_order_by_limit() {
775778
OrderByExpr {
776779
expr: Expr::Identifier(Ident::new("lname")),
777780
asc: Some(true),
781+
nulls_first: None,
778782
},
779783
OrderByExpr {
780784
expr: Expr::Identifier(Ident::new("fname")),
781785
asc: Some(false),
786+
nulls_first: None,
787+
},
788+
],
789+
select.order_by
790+
);
791+
assert_eq!(Some(Expr::Value(number("2"))), select.limit);
792+
}
793+
794+
#[test]
795+
fn parse_select_order_by_nulls_order() {
796+
let sql = "SELECT id, fname, lname FROM customer WHERE id < 5 \
797+
ORDER BY lname ASC NULLS FIRST, fname DESC NULLS LAST LIMIT 2";
798+
let select = verified_query(sql);
799+
assert_eq!(
800+
vec![
801+
OrderByExpr {
802+
expr: Expr::Identifier(Ident::new("lname")),
803+
asc: Some(true),
804+
nulls_first: Some(true),
805+
},
806+
OrderByExpr {
807+
expr: Expr::Identifier(Ident::new("fname")),
808+
asc: Some(false),
809+
nulls_first: Some(false),
782810
},
783811
],
784812
select.order_by
@@ -1251,7 +1279,8 @@ fn parse_window_functions() {
12511279
partition_by: vec![],
12521280
order_by: vec![OrderByExpr {
12531281
expr: Expr::Identifier(Ident::new("dt")),
1254-
asc: Some(false)
1282+
asc: Some(false),
1283+
nulls_first: None,
12551284
}],
12561285
window_frame: None,
12571286
}),

0 commit comments

Comments
 (0)