diff --git a/fuzz/fuzz_seeds/sqlite_2664_between_low_bound.sql b/fuzz/fuzz_seeds/sqlite_2664_between_low_bound.sql new file mode 100644 index 0000000000..297aed4f95 --- /dev/null +++ b/fuzz/fuzz_seeds/sqlite_2664_between_low_bound.sql @@ -0,0 +1,2 @@ +SELECT a BETWEEN b > 1 AND c, a BETWEEN b LIKE 'x' AND c, a BETWEEN b IS NOT NULL AND c, a BETWEEN b NOTNULL AND c, a BETWEEN b BETWEEN 1 AND 2 AND c FROM t; +SELECT 5 BETWEEN 1 AND NULL IS NULL, 5 BETWEEN 1 AND 10 = 1 FROM t diff --git a/src/dialect/mod.rs b/src/dialect/mod.rs index 02ac35fd45..836eab1d81 100644 --- a/src/dialect/mod.rs +++ b/src/dialect/mod.rs @@ -1152,6 +1152,11 @@ pub trait Dialect: Debug + Any { 0 } + /// Precedence used to stop parsing the low bound expression of a BETWEEN predicate. + fn between_low_bound_precedence(&self) -> u8 { + self.prec_value(Precedence::Between) + } + /// Returns true if this dialect requires the `TABLE` keyword after `DESCRIBE` /// /// Defaults to false. diff --git a/src/dialect/sqlite.rs b/src/dialect/sqlite.rs index 8c9cc005e7..6a3d3edfa9 100644 --- a/src/dialect/sqlite.rs +++ b/src/dialect/sqlite.rs @@ -315,6 +315,11 @@ impl Dialect for SQLiteDialect { | Keyword::WHERE ) } + + /// Comparison and postfix operators bind within the BETWEEN low bound in SQLite (). + fn between_low_bound_precedence(&self) -> u8 { + self.prec_value(Precedence::And) + } } /// SQLite permits signed and decimal numeric type modifiers. diff --git a/src/parser/mod.rs b/src/parser/mod.rs index eafe4d621e..5f7b3338fc 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -4614,9 +4614,7 @@ impl<'a> Parser<'a> { /// Parses `BETWEEN AND `, assuming the `BETWEEN` keyword was already consumed. pub fn parse_between(&mut self, expr: Expr, negated: bool) -> Result { - // 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))?; + let low = self.parse_subexpr(self.dialect.between_low_bound_precedence())?; self.expect_keyword_is(Keyword::AND)?; let high = self.parse_subexpr(self.dialect.prec_value(Precedence::Between))?; Ok(Expr::Between { diff --git a/tests/sqlparser_sqlite.rs b/tests/sqlparser_sqlite.rs index 7a7101b26d..6ba0dee451 100644 --- a/tests/sqlparser_sqlite.rs +++ b/tests/sqlparser_sqlite.rs @@ -984,6 +984,47 @@ fn test_drop_trigger() { } } +#[test] +fn parse_between_low_bound_precedence() { + match sqlite().verified_expr("a BETWEEN b > 1 AND c") { + Expr::Between { low, .. } => assert!( + matches!( + *low, + Expr::BinaryOp { + op: BinaryOperator::Gt, + .. + } + ), + "{low:?}" + ), + other => panic!("expected BETWEEN, got {other:?}"), + } + sqlite().verified_expr("a BETWEEN b LIKE 'x' AND c"); + sqlite().verified_expr("a BETWEEN b IS NOT NULL AND c"); + sqlite().expr_parses_to("a BETWEEN b NOTNULL AND c", "a BETWEEN b IS NOT NULL AND c"); + sqlite().verified_expr("a BETWEEN b BETWEEN 1 AND 2 AND c"); + + // Operators after the high bound apply to the whole BETWEEN. + sqlite().verified_expr("5 BETWEEN 1 AND NULL IS NULL"); + sqlite().verified_expr("5 BETWEEN 1 AND 10 = 1"); + + // The inner AND belongs to the bound expression, so the missing `)` is reported at FROM. + assert_eq!( + sqlite().parse_sql_statements("SELECT a BETWEEN (1 AND 2 FROM t"), + Err(ParserError::ParserError( + "Expected: ), found: FROM".to_string() + )) + ); + + assert_eq!( + TestedDialects::new(vec![Box::new(GenericDialect {})]) + .parse_sql_statements("SELECT a BETWEEN b > 1 AND c FROM t"), + Err(ParserError::ParserError( + "Expected: AND, found: >".to_string() + )) + ); +} + #[test] fn parse_pattern_operators_bind_at_like_precedence() { fn where_operator(sql: &str) -> BinaryOperator { @@ -1517,7 +1558,6 @@ fn parse_on_conflict_target_predicate() { other => panic!("unexpected statement: {other:?}"), } } - fn sqlite() -> TestedDialects { TestedDialects::new(vec![Box::new(SQLiteDialect {})]) }