You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@iceberg.apache.org by fo...@apache.org on 2025/08/18 17:10:55 UTC
(iceberg-python) branch main updated: Add `between` operator (#2335)
This is an automated email from the ASF dual-hosted git repository.
fokko pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/iceberg-python.git
The following commit(s) were added to refs/heads/main by this push:
new 8b43eb88 Add `between` operator (#2335)
8b43eb88 is described below
commit 8b43eb88fcc80b4980ce16b71852d211d7e93b13
Author: jtuglu1 <jt...@netflix.com>
AuthorDate: Mon Aug 18 10:10:49 2025 -0700
Add `between` operator (#2335)
<!--
Thanks for opening a pull request!
-->
<!-- In the case this PR will resolve an issue, please replace
${GITHUB_ISSUE_ID} below with the actual Github issue id. -->
<!-- Closes #${GITHUB_ISSUE_ID} -->
# Rationale for this change
Fixes https://github.com/apache/iceberg-python/issues/792
# Are these changes tested?
# Are there any user-facing changes?
Add `BETWEEN` operator
<!-- In the case of user-facing changes, please add the changelog label.
-->
---
mkdocs/docs/expression-dsl.md | 2 ++
mkdocs/docs/row-filter-syntax.md | 9 +++++++++
pyiceberg/expressions/parser.py | 10 +++++++++-
tests/expressions/test_parser.py | 31 ++++++++++++++++++++++++++++++-
4 files changed, 50 insertions(+), 2 deletions(-)
diff --git a/mkdocs/docs/expression-dsl.md b/mkdocs/docs/expression-dsl.md
index cf5784dd..420043bd 100644
--- a/mkdocs/docs/expression-dsl.md
+++ b/mkdocs/docs/expression-dsl.md
@@ -60,6 +60,8 @@ age_greater_than_18 = GreaterThan("age", 18)
# Greater than or equal to
age_greater_than_or_equal_18 = GreaterThanOrEqual("age", 18)
+
+
```
#### Set Predicates
diff --git a/mkdocs/docs/row-filter-syntax.md b/mkdocs/docs/row-filter-syntax.md
index 2191b9fd..ce3b46c0 100644
--- a/mkdocs/docs/row-filter-syntax.md
+++ b/mkdocs/docs/row-filter-syntax.md
@@ -100,6 +100,15 @@ column NOT LIKE 'prefix%'
!!! important
The `%` wildcard is only supported at the end of the pattern. Using it in the middle or beginning of the pattern will raise an error.
+## BETWEEN
+
+The BETWEEN operator filters a numeric value against an inclusive range, e.g. `a between 1 and 2` is equivalent to `a >= 1 and a <= 2`.
+
+```sql
+column BETWEEN 1 AND 2
+column BETWEEN 1.0 AND 2.0
+```
+
## Logical Operations
Combine multiple conditions using logical operators:
diff --git a/pyiceberg/expressions/parser.py b/pyiceberg/expressions/parser.py
index b9b6f9ab..19663638 100644
--- a/pyiceberg/expressions/parser.py
+++ b/pyiceberg/expressions/parser.py
@@ -79,6 +79,7 @@ IN = CaselessKeyword("in")
NULL = CaselessKeyword("null")
NAN = CaselessKeyword("nan")
LIKE = CaselessKeyword("like")
+BETWEEN = CaselessKeyword("between")
unquoted_identifier = Word(alphas + "_", alphanums + "_$")
quoted_identifier = QuotedString('"', escChar="\\", unquoteResults=True)
@@ -106,6 +107,7 @@ boolean = one_of(["true", "false"], caseless=True).set_results_name("boolean")
string = sgl_quoted_string.set_results_name("raw_quoted_string")
decimal = common.real().set_results_name("decimal")
integer = common.signed_integer().set_results_name("integer")
+number = common.number().set_results_name("number")
literal = Group(string | decimal | integer | boolean).set_results_name("literal")
literal_set = Group(
DelimitedList(string) | DelimitedList(decimal) | DelimitedList(integer) | DelimitedList(boolean)
@@ -149,6 +151,12 @@ comparison_op = one_of(["<", "<=", ">", ">=", "=", "==", "!=", "<>"], caseless=T
left_ref = column + comparison_op + literal
right_ref = literal + comparison_op + column
comparison = left_ref | right_ref
+between = column + BETWEEN + number + AND + number
+
+
+@between.set_parse_action
+def _(result: ParseResults) -> BooleanExpression:
+ return And(GreaterThanOrEqual(result.column, result[2]), LessThanOrEqual(result.column, result[4]))
@left_ref.set_parse_action
@@ -258,7 +266,7 @@ def _evaluate_like_statement(result: ParseResults) -> BooleanExpression:
return EqualTo(result.column, StringLiteral(literal_like.value.replace("\\%", "%")))
-predicate = (comparison | in_check | null_check | nan_check | starts_check | boolean).set_results_name("predicate")
+predicate = (between | comparison | in_check | null_check | nan_check | starts_check | boolean).set_results_name("predicate")
def handle_not(result: ParseResults) -> Not:
diff --git a/tests/expressions/test_parser.py b/tests/expressions/test_parser.py
index 064fdb8f..1988c340 100644
--- a/tests/expressions/test_parser.py
+++ b/tests/expressions/test_parser.py
@@ -39,9 +39,10 @@ from pyiceberg.expressions import (
NotNull,
NotStartsWith,
Or,
+ Reference,
StartsWith,
)
-from pyiceberg.expressions.literals import DecimalLiteral
+from pyiceberg.expressions.literals import DecimalLiteral, LongLiteral
def test_always_true() -> None:
@@ -238,3 +239,31 @@ def test_quoted_column_with_dots() -> None:
def test_quoted_column_with_spaces() -> None:
assert EqualTo("Foo Bar", "data") == parser.parse("\"Foo Bar\" = 'data'")
+
+
+def test_valid_between() -> None:
+ assert And(
+ left=GreaterThanOrEqual(Reference(name="foo"), LongLiteral(1)),
+ right=LessThanOrEqual(Reference(name="foo"), LongLiteral(3)),
+ ) == parser.parse("foo between 1 and 3")
+ assert And(
+ left=GreaterThanOrEqual(Reference(name="foo"), LongLiteral(1)),
+ right=LessThanOrEqual(Reference(name="foo"), LongLiteral(1)),
+ ) == parser.parse("foo between 1 and 1")
+ assert And(
+ left=GreaterThanOrEqual(Reference(name="foo"), DecimalLiteral(Decimal(1.0))),
+ right=LessThanOrEqual(Reference(name="foo"), DecimalLiteral(Decimal(4.0))),
+ ) == parser.parse("foo between 1.0 and 4.0")
+ assert parser.parse("foo between 1 and 3") == parser.parse("1 <= foo and foo <= 3")
+
+
+def test_invalid_between() -> None:
+ # boolean
+ with pytest.raises(ParseException) as exc_info:
+ parser.parse("foo between true and false")
+ assert "Expected number, found 'true'" in str(exc_info)
+
+ # string
+ with pytest.raises(ParseException) as exc_info:
+ parser.parse("foo between 'a' and 'b'")
+ assert 'Expected number, found "\'"' in str(exc_info)