diff --git a/src/ast/mod.rs b/src/ast/mod.rs index a025be783..eee1cfa28 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -1002,12 +1002,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. @@ -1826,13 +1830,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 cc8f71b17..9f9cd1977 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 7dbcc91e5..ed5ad6558 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, }), }, @@ -20067,6 +20071,26 @@ fn parse_insert_by_name() { } } +#[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", + ); +} + #[test] fn parse_bitwise_not_renders_apart_from_operand() { all_dialects().verified_stmt("SELECT ~ -1");