diff --git a/src/ast/mod.rs b/src/ast/mod.rs index 20058b83a..150497120 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -995,12 +995,16 @@ pub enum Expr { /// `true` when the `NOT` modifier is present. negated: bool, }, - /// ` [ NOT ] BETWEEN AND ` + /// ` [ NOT ] BETWEEN [ ASYMMETRIC | SYMMETRIC ] AND ` + /// + /// See [SYMMETRIC](https://www.postgresql.org/docs/current/functions-comparison.html) Between { /// Expression being compared. expr: Box, /// `true` when the `NOT` modifier is present. negated: bool, + /// `true` when the `SYMMETRIC` modifier is present. + symmetric: bool, /// Lower bound. low: Box, /// Upper bound. @@ -1819,13 +1823,15 @@ impl fmt::Display for Expr { Expr::Between { expr, negated, + symmetric, low, high, } => write!( f, - "{} {}BETWEEN {} AND {}", + "{} {}BETWEEN {}{} AND {}", expr, if *negated { "NOT " } else { "" }, + if *symmetric { "SYMMETRIC " } else { "" }, low, high ), diff --git a/src/ast/spans.rs b/src/ast/spans.rs index 7acbd7d0b..b997f01fe 100644 --- a/src/ast/spans.rs +++ b/src/ast/spans.rs @@ -1525,6 +1525,7 @@ impl Spanned for Expr { Expr::Between { expr, negated: _, + symmetric: _, low, high, } => expr.span().union(&low.span()).union(&high.span()), diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 15f135fff..ee1781e95 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -4457,8 +4457,14 @@ impl<'a> Parser<'a> { Ok(in_op) } - /// Parses `BETWEEN AND `, assuming the `BETWEEN` keyword was already consumed. + /// Parses `[ASYMMETRIC | SYMMETRIC] AND `, assuming the `BETWEEN` keyword was already consumed. pub fn parse_between(&mut self, expr: Expr, negated: bool) -> Result { + let symmetric = if self.parse_keyword(Keyword::SYMMETRIC) { + true + } else { + let _ = self.parse_keyword(Keyword::ASYMMETRIC); + false + }; // Stop parsing subexpressions for and on tokens with // precedence lower than that of `BETWEEN`, such as `AND`, `IS`, etc. let low = self.parse_subexpr(self.dialect.prec_value(Precedence::Between))?; @@ -4467,6 +4473,7 @@ impl<'a> Parser<'a> { Ok(Expr::Between { expr: Box::new(expr), negated, + symmetric, low: Box::new(low), high: Box::new(high), }) diff --git a/tests/sqlparser_common.rs b/tests/sqlparser_common.rs index 2de6062b2..8ce0d9f5a 100644 --- a/tests/sqlparser_common.rs +++ b/tests/sqlparser_common.rs @@ -2222,6 +2222,7 @@ fn parse_not_precedence() { expr: Box::new(Expr::value(number("1"))), low: Box::new(Expr::value(number("1"))), high: Box::new(Expr::value(number("2"))), + symmetric: false, negated: true, }), }, @@ -2735,6 +2736,7 @@ fn parse_between() { expr: Box::new(Expr::Identifier(Ident::new("age"))), low: Box::new(Expr::value(number("25"))), high: Box::new(Expr::value(number("32"))), + symmetric: false, negated, }, select.selection.unwrap() @@ -2762,6 +2764,7 @@ fn parse_between_with_expr() { op: Plus, right: Box::new(Expr::value(number("4"))), }), + symmetric: false, negated: false, })), select.selection.unwrap() @@ -2785,6 +2788,7 @@ fn parse_between_with_expr() { }), low: Box::new(Expr::value(number("1"))), high: Box::new(Expr::value(number("2"))), + symmetric: false, negated: false, }), }, @@ -20060,3 +20064,23 @@ fn parse_insert_by_name() { _ => unreachable!(), } } + +#[test] +fn parse_between_symmetric() { + // See https://www.postgresql.org/docs/current/functions-comparison.html + match verified_expr("1 BETWEEN SYMMETRIC 2 AND 3") { + Expr::Between { + symmetric, negated, .. + } => { + assert!(symmetric); + assert!(!negated); + } + _ => unreachable!(), + } + + // ASYMMETRIC is the default behavior and is not preserved as SQL text. + one_statement_parses_to( + "SELECT * FROM t WHERE 1 NOT BETWEEN ASYMMETRIC 2 AND 3", + "SELECT * FROM t WHERE 1 NOT BETWEEN 2 AND 3", + ); +}