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 a26ba2655..a5918f1a3 100644 --- a/src/ast/query.rs +++ b/src/ast/query.rs @@ -374,20 +374,30 @@ 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, } 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, }),