diff --git a/src/dialect/databricks.rs b/src/dialect/databricks.rs index a88cc8d7d7..906332348d 100644 --- a/src/dialect/databricks.rs +++ b/src/dialect/databricks.rs @@ -44,6 +44,10 @@ impl Dialect for DatabricksDialect { false } + fn supports_comparison_in_between_low_bound(&self) -> bool { + true + } + /// See fn identifier_quote_style(&self, _identifier: &str) -> Option { Some('`') diff --git a/src/dialect/duckdb.rs b/src/dialect/duckdb.rs index 5e4d32ce40..61d28ec407 100644 --- a/src/dialect/duckdb.rs +++ b/src/dialect/duckdb.rs @@ -65,6 +65,10 @@ impl Dialect for DuckDbDialect { true } + fn supports_comparison_in_between_low_bound(&self) -> bool { + true + } + fn supports_named_fn_args_with_eq_operator(&self) -> bool { true } diff --git a/src/dialect/generic.rs b/src/dialect/generic.rs index f6a6682ec9..a53661558b 100644 --- a/src/dialect/generic.rs +++ b/src/dialect/generic.rs @@ -173,6 +173,10 @@ impl Dialect for GenericDialect { true } + fn supports_comparison_in_between_low_bound(&self) -> bool { + true + } + fn supports_comment_on(&self) -> bool { true } diff --git a/src/dialect/mod.rs b/src/dialect/mod.rs index 55d6f81a9a..e60e2080ca 100644 --- a/src/dialect/mod.rs +++ b/src/dialect/mod.rs @@ -1089,6 +1089,17 @@ pub trait Dialect: Debug + Any { self.prec_value(Precedence::Between) } + /// Returns true if the low bound of a `BETWEEN` predicate may end with one comparison, + /// as in `a BETWEEN b > 1 AND c`, while `IS`, `LIKE` and `BETWEEN` still end the bound. + /// + /// The low bound is a `b_expr` in the + /// [PostgreSQL grammar](https://github.com/postgres/postgres/blob/master/src/backend/parser/gram.y) + /// and a `valueExpression` in the + /// [Spark grammar](https://github.com/apache/spark/blob/master/sql/api/src/main/antlr4/org/apache/spark/sql/catalyst/parser/SqlBaseParser.g4). + fn supports_comparison_in_between_low_bound(&self) -> bool { + false + } + /// Returns true if this dialect requires the `TABLE` keyword after `DESCRIBE` /// /// Defaults to false. diff --git a/src/dialect/postgresql.rs b/src/dialect/postgresql.rs index c060f2a8c1..52157fda7f 100644 --- a/src/dialect/postgresql.rs +++ b/src/dialect/postgresql.rs @@ -412,6 +412,10 @@ impl Dialect for PostgreSqlDialect { true } + fn supports_comparison_in_between_low_bound(&self) -> bool { + true + } + /// see fn supports_comment_on(&self) -> bool { true diff --git a/src/dialect/spark.rs b/src/dialect/spark.rs index 1f6e47c98e..19eb6ed184 100644 --- a/src/dialect/spark.rs +++ b/src/dialect/spark.rs @@ -48,6 +48,10 @@ impl Dialect for SparkSqlDialect { false } + fn supports_comparison_in_between_low_bound(&self) -> bool { + true + } + /// See fn identifier_quote_style(&self, _identifier: &str) -> Option { Some('`') diff --git a/src/parser/mod.rs b/src/parser/mod.rs index a10f559d4b..3fb960f7dd 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -4576,7 +4576,22 @@ 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 { - let low = self.parse_subexpr(self.dialect.between_low_bound_precedence())?; + let mut low = self.parse_subexpr(self.dialect.between_low_bound_precedence())?; + if self.dialect.supports_comparison_in_between_low_bound() + && matches!( + self.peek_token_ref().token, + Token::Eq + | Token::DoubleEq + | Token::Neq + | Token::Lt + | Token::LtEq + | Token::Gt + | Token::GtEq + | Token::Spaceship + ) + { + low = self.parse_infix(low, self.dialect.prec_value(Precedence::Between))?; + } 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_common.rs b/tests/sqlparser_common.rs index 87fb61dc17..642ee33af5 100644 --- a/tests/sqlparser_common.rs +++ b/tests/sqlparser_common.rs @@ -21022,3 +21022,63 @@ fn parse_window_spec_keyword_base_window_name() { "SELECT count(*) OVER (ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) FROM t", ); } + +#[test] +fn parse_comparison_in_between_low_bound() { + let dialects = all_dialects_where(|d| d.supports_comparison_in_between_low_bound()); + for (sql, expected) in [ + ("a BETWEEN b > 1 AND c", BinaryOperator::Gt), + ("a BETWEEN b <> 1 AND c", BinaryOperator::NotEq), + ("a BETWEEN b + 1 >= 2 AND c", BinaryOperator::GtEq), + ] { + match dialects.verified_expr(sql) { + Expr::Between { low, .. } => assert!( + matches!(&*low, Expr::BinaryOp { op, .. } if *op == expected), + "{sql}: {low:?}" + ), + other => panic!("{sql}: expected BETWEEN, got {other:?}"), + } + } + for (sql, error) in [ + ( + "SELECT a BETWEEN b = 1 = true AND c", + "Expected: AND, found: =", + ), + ( + "SELECT a BETWEEN b LIKE 'x' AND c", + "Expected: AND, found: LIKE", + ), + ( + "SELECT a BETWEEN b IS NULL AND c", + "Expected: AND, found: IS", + ), + ( + "SELECT a BETWEEN b BETWEEN 1 AND 2 AND c", + "Expected: AND, found: BETWEEN", + ), + ( + "SELECT a BETWEEN (1 AND 2 FROM t", + "Expected: ), found: FROM", + ), + ( + "SELECT a BETWEEN b = (1, 2 FROM t", + "Expected: ), found: FROM", + ), + ] { + assert_eq!( + dialects.parse_sql_statements(sql), + Err(ParserError::ParserError(error.to_string())), + "{sql}" + ); + } + + assert_eq!( + all_dialects_except(|d| { + d.supports_comparison_in_between_low_bound() || d.is::() + }) + .parse_sql_statements("SELECT a BETWEEN b > 1 AND c"), + Err(ParserError::ParserError( + "Expected: AND, found: >".to_string() + )) + ); +} diff --git a/tests/sqlparser_spark.rs b/tests/sqlparser_spark.rs index 0f292f0e98..e56ecbda5e 100644 --- a/tests/sqlparser_spark.rs +++ b/tests/sqlparser_spark.rs @@ -362,3 +362,22 @@ fn test_substring() { fn test_pipe_operator() { spark().verified_stmt("SELECT * FROM t |> WHERE x > 1 |> SELECT x AS y |> ORDER BY y"); } + +// Spark is not part of all_dialects, so the between low bound comparison is pinned here. + +#[test] +fn parse_comparison_in_between_low_bound() { + match spark().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:?}"), + } +} diff --git a/tests/sqlparser_sqlite.rs b/tests/sqlparser_sqlite.rs index ffe3a87e2f..d0d1816a05 100644 --- a/tests/sqlparser_sqlite.rs +++ b/tests/sqlparser_sqlite.rs @@ -1185,21 +1185,12 @@ fn parse_between_low_bound_precedence() { 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() - )) - ); } fn sqlite() -> TestedDialects {