Skip to content

Commit d9d5031

Browse files
PostgreSQL, DuckDB, Spark, Databricks: Accept a comparison in the BETWEEN lower bound
1 parent 6f6114e commit d9d5031

9 files changed

Lines changed: 99 additions & 10 deletions

File tree

‎src/dialect/databricks.rs‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,10 @@ impl Dialect for DatabricksDialect {
4444
false
4545
}
4646

47+
fn supports_comparison_in_between_low_bound(&self) -> bool {
48+
true
49+
}
50+
4751
/// See <https://docs.databricks.com/en/sql/language-manual/sql-ref-identifiers.html>
4852
fn identifier_quote_style(&self, _identifier: &str) -> Option<char> {
4953
Some('`')

‎src/dialect/duckdb.rs‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,10 @@ impl Dialect for DuckDbDialect {
7373
true
7474
}
7575

76+
fn supports_comparison_in_between_low_bound(&self) -> bool {
77+
true
78+
}
79+
7680
fn supports_named_fn_args_with_eq_operator(&self) -> bool {
7781
true
7882
}

‎src/dialect/generic.rs‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,10 @@ impl Dialect for GenericDialect {
193193
true
194194
}
195195

196+
fn supports_comparison_in_between_low_bound(&self) -> bool {
197+
true
198+
}
199+
196200
fn supports_comment_on(&self) -> bool {
197201
true
198202
}

‎src/dialect/mod.rs‎

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1105,6 +1105,17 @@ pub trait Dialect: Debug + Any {
11051105
self.prec_value(Precedence::Between)
11061106
}
11071107

1108+
/// Returns true if the low bound of a `BETWEEN` predicate may end with one comparison,
1109+
/// as in `a BETWEEN b > 1 AND c`, while `IS`, `LIKE` and `BETWEEN` still end the bound.
1110+
///
1111+
/// The low bound is a `b_expr` in the
1112+
/// [PostgreSQL grammar](https://github.com/postgres/postgres/blob/master/src/backend/parser/gram.y)
1113+
/// and a `valueExpression` in the
1114+
/// [Spark grammar](https://github.com/apache/spark/blob/master/sql/api/src/main/antlr4/org/apache/spark/sql/catalyst/parser/SqlBaseParser.g4).
1115+
fn supports_comparison_in_between_low_bound(&self) -> bool {
1116+
false
1117+
}
1118+
11081119
/// Returns true if this dialect requires the `TABLE` keyword after `DESCRIBE`
11091120
///
11101121
/// Defaults to false.

‎src/dialect/postgresql.rs‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -416,6 +416,10 @@ impl Dialect for PostgreSqlDialect {
416416
true
417417
}
418418

419+
fn supports_comparison_in_between_low_bound(&self) -> bool {
420+
true
421+
}
422+
419423
/// see <https://www.postgresql.org/docs/current/sql-comment.html>
420424
fn supports_comment_on(&self) -> bool {
421425
true

‎src/dialect/spark.rs‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,10 @@ impl Dialect for SparkSqlDialect {
4848
false
4949
}
5050

51+
fn supports_comparison_in_between_low_bound(&self) -> bool {
52+
true
53+
}
54+
5155
/// See <https://spark.apache.org/docs/latest/sql-ref-identifier.html>
5256
fn identifier_quote_style(&self, _identifier: &str) -> Option<char> {
5357
Some('`')

‎src/parser/mod.rs‎

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4591,7 +4591,22 @@ impl<'a> Parser<'a> {
45914591

45924592
/// Parses `BETWEEN <low> AND <high>`, assuming the `BETWEEN` keyword was already consumed.
45934593
pub fn parse_between(&mut self, expr: Expr, negated: bool) -> Result<Expr, ParserError> {
4594-
let low = self.parse_subexpr(self.dialect.between_low_bound_precedence())?;
4594+
let mut low = self.parse_subexpr(self.dialect.between_low_bound_precedence())?;
4595+
if self.dialect.supports_comparison_in_between_low_bound()
4596+
&& matches!(
4597+
self.peek_token_ref().token,
4598+
Token::Eq
4599+
| Token::DoubleEq
4600+
| Token::Neq
4601+
| Token::Lt
4602+
| Token::LtEq
4603+
| Token::Gt
4604+
| Token::GtEq
4605+
| Token::Spaceship
4606+
)
4607+
{
4608+
low = self.parse_infix(low, self.dialect.prec_value(Precedence::Between))?;
4609+
}
45954610
self.expect_keyword_is(Keyword::AND)?;
45964611
let high = self.parse_subexpr(self.dialect.prec_value(Precedence::Between))?;
45974612
Ok(Expr::Between {

‎tests/sqlparser_common.rs‎

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21358,3 +21358,55 @@ fn parse_qualified_literal_prefix_requires_adjacent_period() {
2135821358
dialects.tokenizes_to(sql, vec![token]);
2135921359
}
2136021360
}
21361+
21362+
#[test]
21363+
fn parse_comparison_in_between_low_bound() {
21364+
let dialects = all_dialects_where(|d| d.supports_comparison_in_between_low_bound());
21365+
for (sql, expected) in [
21366+
("a BETWEEN b > 1 AND c", BinaryOperator::Gt),
21367+
("a BETWEEN b <> 1 AND c", BinaryOperator::NotEq),
21368+
("a BETWEEN b + 1 >= 2 AND c", BinaryOperator::GtEq),
21369+
] {
21370+
match dialects.verified_expr(sql) {
21371+
Expr::Between { low, .. } => assert!(
21372+
matches!(&*low, Expr::BinaryOp { op, .. } if *op == expected),
21373+
"{sql}: {low:?}"
21374+
),
21375+
other => panic!("{sql}: expected BETWEEN, got {other:?}"),
21376+
}
21377+
}
21378+
for (sql, error) in [
21379+
(
21380+
"SELECT a BETWEEN b = 1 = true AND c",
21381+
"Expected: AND, found: =",
21382+
),
21383+
(
21384+
"SELECT a BETWEEN b LIKE 'x' AND c",
21385+
"Expected: AND, found: LIKE",
21386+
),
21387+
(
21388+
"SELECT a BETWEEN b IS NULL AND c",
21389+
"Expected: AND, found: IS",
21390+
),
21391+
(
21392+
"SELECT a BETWEEN b BETWEEN 1 AND 2 AND c",
21393+
"Expected: AND, found: BETWEEN",
21394+
),
21395+
] {
21396+
assert_eq!(
21397+
dialects.parse_sql_statements(sql),
21398+
Err(ParserError::ParserError(error.to_string())),
21399+
"{sql}"
21400+
);
21401+
}
21402+
21403+
assert_eq!(
21404+
all_dialects_except(|d| {
21405+
d.supports_comparison_in_between_low_bound() || d.is::<SQLiteDialect>()
21406+
})
21407+
.parse_sql_statements("SELECT a BETWEEN b > 1 AND c"),
21408+
Err(ParserError::ParserError(
21409+
"Expected: AND, found: >".to_string()
21410+
))
21411+
);
21412+
}

‎tests/sqlparser_sqlite.rs‎

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -979,21 +979,12 @@ fn parse_between_low_bound_precedence() {
979979
sqlite().verified_expr("5 BETWEEN 1 AND NULL IS NULL");
980980
sqlite().verified_expr("5 BETWEEN 1 AND 10 = 1");
981981

982-
// The inner AND belongs to the bound expression, so the missing `)` is reported at FROM.
983982
assert_eq!(
984983
sqlite().parse_sql_statements("SELECT a BETWEEN (1 AND 2 FROM t"),
985984
Err(ParserError::ParserError(
986985
"Expected: ), found: FROM".to_string()
987986
))
988987
);
989-
990-
assert_eq!(
991-
TestedDialects::new(vec![Box::new(GenericDialect {})])
992-
.parse_sql_statements("SELECT a BETWEEN b > 1 AND c FROM t"),
993-
Err(ParserError::ParserError(
994-
"Expected: AND, found: >".to_string()
995-
))
996-
);
997988
}
998989

999990
#[test]

0 commit comments

Comments
 (0)