Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions src/dialect/databricks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,10 @@ impl Dialect for DatabricksDialect {
matches!(ch, '`')
}

fn supports_comparison_in_between_low_bound(&self) -> bool {
true
}

/// See <https://docs.databricks.com/en/sql/language-manual/sql-ref-identifiers.html>
fn identifier_quote_style(&self, _identifier: &str) -> Option<char> {
Some('`')
Expand Down
4 changes: 4 additions & 0 deletions src/dialect/duckdb.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,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
}
Expand Down
4 changes: 4 additions & 0 deletions src/dialect/generic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
11 changes: 11 additions & 0 deletions src/dialect/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
4 changes: 4 additions & 0 deletions src/dialect/postgresql.rs
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,10 @@ impl Dialect for PostgreSqlDialect {
true
}

fn supports_comparison_in_between_low_bound(&self) -> bool {
true
}

/// see <https://www.postgresql.org/docs/current/sql-comment.html>
fn supports_comment_on(&self) -> bool {
true
Expand Down
4 changes: 4 additions & 0 deletions src/dialect/spark.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,10 @@ impl Dialect for SparkSqlDialect {
matches!(ch, '`')
}

fn supports_comparison_in_between_low_bound(&self) -> bool {
true
}

/// See <https://spark.apache.org/docs/latest/sql-ref-identifier.html>
fn identifier_quote_style(&self, _identifier: &str) -> Option<char> {
Some('`')
Expand Down
17 changes: 16 additions & 1 deletion src/parser/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4548,7 +4548,22 @@ impl<'a> Parser<'a> {

/// Parses `BETWEEN <low> AND <high>`, assuming the `BETWEEN` keyword was already consumed.
pub fn parse_between(&mut self, expr: Expr, negated: bool) -> Result<Expr, ParserError> {
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 {
Expand Down
60 changes: 60 additions & 0 deletions tests/sqlparser_common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20803,3 +20803,63 @@ fn display_nested_unary_minus_in_linear_time() {
.expect("display should not take exponential time");
assert_eq!(printed, sql);
}

#[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::<SQLiteDialect>()
})
.parse_sql_statements("SELECT a BETWEEN b > 1 AND c"),
Err(ParserError::ParserError(
"Expected: AND, found: >".to_string()
))
);
}
19 changes: 19 additions & 0 deletions tests/sqlparser_spark.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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:?}"),
}
}
9 changes: 0 additions & 9 deletions tests/sqlparser_sqlite.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1166,21 +1166,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 {
Expand Down
Loading