You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@datafusion.apache.org by lu...@apache.org on 2026/10/17 16:53:11 UTC

(datafusion-sqlparser-rs) 01/01: PostgreSQL, DuckDB, Spark, Databricks: Accept a comparison in the `BETWEEN` lower bound

This is an automated email from the ASF dual-hosted git repository.

LucaCappelletti94 pushed a commit to branch between-low-bound-comparison
in repository https://gitbox.apache.org/repos/asf/datafusion-sqlparser-rs.git

commit ec5975ac52b0092370689d17cf59cf80617e0e47
Author: LucaCappelletti94 <ca...@gmail.com>
AuthorDate: Sat Oct 17 18:50:36 2026 +0200

    PostgreSQL, DuckDB, Spark, Databricks: Accept a comparison in the `BETWEEN` lower bound
---
 src/dialect/databricks.rs |  4 ++++
 src/dialect/duckdb.rs     |  4 ++++
 src/dialect/generic.rs    |  4 ++++
 src/dialect/mod.rs        |  6 ++++++
 src/dialect/postgresql.rs |  4 ++++
 src/dialect/spark.rs      |  4 ++++
 src/parser/mod.rs         | 17 +++++++++++++++-
 tests/sqlparser_common.rs | 52 +++++++++++++++++++++++++++++++++++++++++++++++
 tests/sqlparser_sqlite.rs |  8 --------
 9 files changed, 94 insertions(+), 9 deletions(-)

diff --git a/src/dialect/databricks.rs b/src/dialect/databricks.rs
index ca8610dd6..a2a6a97d5 100644
--- a/src/dialect/databricks.rs
+++ b/src/dialect/databricks.rs
@@ -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('`')
diff --git a/src/dialect/duckdb.rs b/src/dialect/duckdb.rs
index ce72f578a..6567bdd29 100644
--- a/src/dialect/duckdb.rs
+++ b/src/dialect/duckdb.rs
@@ -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
     }
diff --git a/src/dialect/generic.rs b/src/dialect/generic.rs
index 694eb8f19..9f1fbd600 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 1df53844e..09b10380c 100644
--- a/src/dialect/mod.rs
+++ b/src/dialect/mod.rs
@@ -1089,6 +1089,12 @@ 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.
+    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 65bfd46a7..c98367d07 100644
--- a/src/dialect/postgresql.rs
+++ b/src/dialect/postgresql.rs
@@ -249,6 +249,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
diff --git a/src/dialect/spark.rs b/src/dialect/spark.rs
index 024aec715..7bfe42a06 100644
--- a/src/dialect/spark.rs
+++ b/src/dialect/spark.rs
@@ -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('`')
diff --git a/src/parser/mod.rs b/src/parser/mod.rs
index 0931d02d5..8620aad81 100644
--- a/src/parser/mod.rs
+++ b/src/parser/mod.rs
@@ -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 {
diff --git a/tests/sqlparser_common.rs b/tests/sqlparser_common.rs
index 1959bcd34..a5f2765fc 100644
--- a/tests/sqlparser_common.rs
+++ b/tests/sqlparser_common.rs
@@ -20699,3 +20699,55 @@ fn parse_cte_materialization() {
         }
     }
 }
+
+#[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",
+        ),
+    ] {
+        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()
+        ))
+    );
+}
diff --git a/tests/sqlparser_sqlite.rs b/tests/sqlparser_sqlite.rs
index 431ba9b69..6c3098493 100644
--- a/tests/sqlparser_sqlite.rs
+++ b/tests/sqlparser_sqlite.rs
@@ -1140,14 +1140,6 @@ fn parse_between_low_bound_precedence() {
     // 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");
-
-    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 {


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@datafusion.apache.org
For additional commands, e-mail: commits-help@datafusion.apache.org