From 804b7f73e68a2cb8ebe80219c35d984ac52db2eb Mon Sep 17 00:00:00 2001 From: Qingping Hou Date: Fri, 29 May 2020 22:56:15 -0700 Subject: [PATCH 1/2] add nulls first/last support to order by expression --- src/ast/query.rs | 22 +++++++++++++++++++--- src/dialect/keywords.rs | 2 ++ src/parser.rs | 15 ++++++++++++++- tests/sqlparser_common.rs | 31 ++++++++++++++++++++++++++++++- 4 files changed, 65 insertions(+), 5 deletions(-) diff --git a/src/ast/query.rs b/src/ast/query.rs index a26ba2655..3cadf9898 100644 --- a/src/ast/query.rs +++ b/src/ast/query.rs @@ -379,15 +379,31 @@ pub enum JoinConstraint { pub struct OrderByExpr { pub expr: Expr, pub asc: Option, + pub nulls_first: Option, } 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", self.expr), - Some(false) => write!(f, "{} DESC", self.expr), - None => write!(f, "{}", self.expr), + 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(()) } } diff --git a/src/dialect/keywords.rs b/src/dialect/keywords.rs index b8f8817f5..a01871c6e 100644 --- a/src/dialect/keywords.rs +++ b/src/dialect/keywords.rs @@ -220,6 +220,7 @@ define_keywords!( LAG, LANGUAGE, LARGE, + LAST, LAST_VALUE, LATERAL, LEAD, @@ -262,6 +263,7 @@ define_keywords!( NTILE, NULL, NULLIF, + NULLS, NUMERIC, OBJECT, OCTET_LENGTH, diff --git a/src/parser.rs b/src/parser.rs index d235eb169..608ac4736 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -2015,7 +2015,20 @@ impl Parser { } else { None }; - Ok(OrderByExpr { expr, asc }) + + let nulls_first = if self.parse_keywords(vec!["NULLS", "FIRST"]) { + Some(true) + } else if self.parse_keywords(vec!["NULLS", "LAST"]) { + Some(false) + } else { + None + }; + + Ok(OrderByExpr { + expr, + asc, + nulls_first, + }) } /// Parse a TOP clause, MSSQL equivalent of LIMIT, diff --git a/tests/sqlparser_common.rs b/tests/sqlparser_common.rs index 7272e35ac..c87fcf3a2 100644 --- a/tests/sqlparser_common.rs +++ b/tests/sqlparser_common.rs @@ -746,14 +746,17 @@ fn parse_select_order_by() { OrderByExpr { expr: Expr::Identifier(Ident::new("lname")), asc: Some(true), + nulls_first: None, }, OrderByExpr { expr: Expr::Identifier(Ident::new("fname")), asc: Some(false), + nulls_first: None, }, OrderByExpr { expr: Expr::Identifier(Ident::new("id")), asc: None, + nulls_first: None, }, ], select.order_by @@ -775,10 +778,35 @@ fn parse_select_order_by_limit() { OrderByExpr { expr: Expr::Identifier(Ident::new("lname")), asc: Some(true), + nulls_first: None, }, OrderByExpr { expr: Expr::Identifier(Ident::new("fname")), asc: Some(false), + nulls_first: None, + }, + ], + select.order_by + ); + assert_eq!(Some(Expr::Value(number("2"))), select.limit); +} + +#[test] +fn parse_select_order_by_nulls_order() { + let sql = "SELECT id, fname, lname FROM customer WHERE id < 5 \ + ORDER BY lname ASC NULLS FIRST, fname DESC NULLS LAST LIMIT 2"; + let select = verified_query(sql); + assert_eq!( + vec![ + OrderByExpr { + expr: Expr::Identifier(Ident::new("lname")), + asc: Some(true), + nulls_first: Some(true), + }, + OrderByExpr { + expr: Expr::Identifier(Ident::new("fname")), + asc: Some(false), + nulls_first: Some(false), }, ], select.order_by @@ -1251,7 +1279,8 @@ fn parse_window_functions() { partition_by: vec![], order_by: vec![OrderByExpr { expr: Expr::Identifier(Ident::new("dt")), - asc: Some(false) + asc: Some(false), + nulls_first: None, }], window_frame: None, }), From 1e55cd5dcb97b813b9e32e77b7556b981d1864bb Mon Sep 17 00:00:00 2001 From: Nickolay Ponomarev Date: Sat, 30 May 2020 16:58:31 +0300 Subject: [PATCH 2/2] Update CHANGELOG, doc comments, formatting --- CHANGELOG.md | 6 ++++-- src/ast/query.rs | 20 +++++++------------- 2 files changed, 11 insertions(+), 15 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 81705b536..d39c76cdb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,11 +13,13 @@ Check https://github.com/andygrove/sqlparser-rs/commits/master for undocumented - Support Snowflake's `FROM (table_name)` (#155) - thanks @eyalleshem! ### Added -- Support basic forms of `CREATE INDEX` and `DROP INDEX` (#167) - thanks @mashuai! -- Support `ON { UPDATE | DELETE } { RESTRICT | CASCADE | SET NULL | NO ACTION | SET DEFAULT }` in `FOREIGN KEY` constraints (#170) - thanks @c7hm4r! - Support MSSQL `TOP () [ PERCENT ] [ WITH TIES ]` (#150) - thanks @alexkyllo! - Support MySQL `LIMIT row_count OFFSET offset` (not followed by `ROW` or `ROWS`) and remember which variant was parsed (#158) - thanks @mjibson! - Support PostgreSQL `CREATE TABLE IF NOT EXISTS table_name` (#163) - thanks @alex-dukhno! +- Support basic forms of `CREATE INDEX` and `DROP INDEX` (#167) - thanks @mashuai! +- Support `ON { UPDATE | DELETE } { RESTRICT | CASCADE | SET NULL | NO ACTION | SET DEFAULT }` in `FOREIGN KEY` constraints (#170) - thanks @c7hm4r! +- Support basic forms of `CREATE SCHEMA` and `DROP SCHEMA` (#173) - thanks @alex-dukhno! +- Support `NULLS FIRST`/`LAST` in `ORDER BY` expressions (#176) - thanks @houqp! ### Fixed - Report an error for unterminated string literals (#165) diff --git a/src/ast/query.rs b/src/ast/query.rs index 3cadf9898..a5918f1a3 100644 --- a/src/ast/query.rs +++ b/src/ast/query.rs @@ -374,11 +374,13 @@ pub enum JoinConstraint { Natural, } -/// SQL ORDER BY expression +/// An `ORDER BY` expression #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct OrderByExpr { pub expr: Expr, + /// Optional `ASC` or `DESC` pub asc: Option, + /// Optional `NULLS FIRST` or `NULLS LAST` pub nulls_first: Option, } @@ -386,21 +388,13 @@ 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")?; - } + 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")?; - } + Some(true) => write!(f, " NULLS FIRST")?, + Some(false) => write!(f, " NULLS LAST")?, None => (), } Ok(())