Skip to content

Commit 094e849

Browse files
PostgreSQL, DuckDB, Spark, Databricks: Accept a comparison in the BETWEEN lower bound
1 parent 46f4566 commit 094e849

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
@@ -65,6 +65,10 @@ impl Dialect for DuckDbDialect {
6565
true
6666
}
6767

68+
fn supports_comparison_in_between_low_bound(&self) -> bool {
69+
true
70+
}
71+
6872
fn supports_named_fn_args_with_eq_operator(&self) -> bool {
6973
true
7074
}

‎src/dialect/generic.rs‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,10 @@ impl Dialect for GenericDialect {
173173
true
174174
}
175175

176+
fn supports_comparison_in_between_low_bound(&self) -> bool {
177+
true
178+
}
179+
176180
fn supports_comment_on(&self) -> bool {
177181
true
178182
}

‎src/dialect/mod.rs‎

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

1092+
/// Returns true if the low bound of a `BETWEEN` predicate may end with one comparison,
1093+
/// as in `a BETWEEN b > 1 AND c`, while `IS`, `LIKE` and `BETWEEN` still end the bound.
1094+
///
1095+
/// The low bound is a `b_expr` in the
1096+
/// [PostgreSQL grammar](https://github.com/postgres/postgres/blob/master/src/backend/parser/gram.y)
1097+
/// and a `valueExpression` in the
1098+
/// [Spark grammar](https://github.com/apache/spark/blob/master/sql/api/src/main/antlr4/org/apache/spark/sql/catalyst/parser/SqlBaseParser.g4).
1099+
fn supports_comparison_in_between_low_bound(&self) -> bool {
1100+
false
1101+
}
1102+
10921103
/// Returns true if this dialect requires the `TABLE` keyword after `DESCRIBE`
10931104
///
10941105
/// Defaults to false.

‎src/dialect/postgresql.rs‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -412,6 +412,10 @@ impl Dialect for PostgreSqlDialect {
412412
true
413413
}
414414

415+
fn supports_comparison_in_between_low_bound(&self) -> bool {
416+
true
417+
}
418+
415419
/// see <https://www.postgresql.org/docs/current/sql-comment.html>
416420
fn supports_comment_on(&self) -> bool {
417421
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
@@ -4576,7 +4576,22 @@ impl<'a> Parser<'a> {
45764576

45774577
/// Parses `BETWEEN <low> AND <high>`, assuming the `BETWEEN` keyword was already consumed.
45784578
pub fn parse_between(&mut self, expr: Expr, negated: bool) -> Result<Expr, ParserError> {
4579-
let low = self.parse_subexpr(self.dialect.between_low_bound_precedence())?;
4579+
let mut low = self.parse_subexpr(self.dialect.between_low_bound_precedence())?;
4580+
if self.dialect.supports_comparison_in_between_low_bound()
4581+
&& matches!(
4582+
self.peek_token_ref().token,
4583+
Token::Eq
4584+
| Token::DoubleEq
4585+
| Token::Neq
4586+
| Token::Lt
4587+
| Token::LtEq
4588+
| Token::Gt
4589+
| Token::GtEq
4590+
| Token::Spaceship
4591+
)
4592+
{
4593+
low = self.parse_infix(low, self.dialect.prec_value(Precedence::Between))?;
4594+
}
45804595
self.expect_keyword_is(Keyword::AND)?;
45814596
let high = self.parse_subexpr(self.dialect.prec_value(Precedence::Between))?;
45824597
Ok(Expr::Between {

‎tests/sqlparser_common.rs‎

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21022,3 +21022,55 @@ fn parse_window_spec_keyword_base_window_name() {
2102221022
"SELECT count(*) OVER (ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) FROM t",
2102321023
);
2102421024
}
21025+
21026+
#[test]
21027+
fn parse_comparison_in_between_low_bound() {
21028+
let dialects = all_dialects_where(|d| d.supports_comparison_in_between_low_bound());
21029+
for (sql, expected) in [
21030+
("a BETWEEN b > 1 AND c", BinaryOperator::Gt),
21031+
("a BETWEEN b <> 1 AND c", BinaryOperator::NotEq),
21032+
("a BETWEEN b + 1 >= 2 AND c", BinaryOperator::GtEq),
21033+
] {
21034+
match dialects.verified_expr(sql) {
21035+
Expr::Between { low, .. } => assert!(
21036+
matches!(&*low, Expr::BinaryOp { op, .. } if *op == expected),
21037+
"{sql}: {low:?}"
21038+
),
21039+
other => panic!("{sql}: expected BETWEEN, got {other:?}"),
21040+
}
21041+
}
21042+
for (sql, error) in [
21043+
(
21044+
"SELECT a BETWEEN b = 1 = true AND c",
21045+
"Expected: AND, found: =",
21046+
),
21047+
(
21048+
"SELECT a BETWEEN b LIKE 'x' AND c",
21049+
"Expected: AND, found: LIKE",
21050+
),
21051+
(
21052+
"SELECT a BETWEEN b IS NULL AND c",
21053+
"Expected: AND, found: IS",
21054+
),
21055+
(
21056+
"SELECT a BETWEEN b BETWEEN 1 AND 2 AND c",
21057+
"Expected: AND, found: BETWEEN",
21058+
),
21059+
] {
21060+
assert_eq!(
21061+
dialects.parse_sql_statements(sql),
21062+
Err(ParserError::ParserError(error.to_string())),
21063+
"{sql}"
21064+
);
21065+
}
21066+
21067+
assert_eq!(
21068+
all_dialects_except(|d| {
21069+
d.supports_comparison_in_between_low_bound() || d.is::<SQLiteDialect>()
21070+
})
21071+
.parse_sql_statements("SELECT a BETWEEN b > 1 AND c"),
21072+
Err(ParserError::ParserError(
21073+
"Expected: AND, found: >".to_string()
21074+
))
21075+
);
21076+
}

‎tests/sqlparser_sqlite.rs‎

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

1188-
// The inner AND belongs to the bound expression, so the missing `)` is reported at FROM.
11891188
assert_eq!(
11901189
sqlite().parse_sql_statements("SELECT a BETWEEN (1 AND 2 FROM t"),
11911190
Err(ParserError::ParserError(
11921191
"Expected: ), found: FROM".to_string()
11931192
))
11941193
);
1195-
1196-
assert_eq!(
1197-
TestedDialects::new(vec![Box::new(GenericDialect {})])
1198-
.parse_sql_statements("SELECT a BETWEEN b > 1 AND c FROM t"),
1199-
Err(ParserError::ParserError(
1200-
"Expected: AND, found: >".to_string()
1201-
))
1202-
);
12031194
}
12041195

12051196
fn sqlite() -> TestedDialects {

0 commit comments

Comments
 (0)