Skip to content

Commit acd1b28

Browse files
committed
Add between keyword
1 parent 640c592 commit acd1b28

File tree

2 files changed

+29
-3
lines changed

2 files changed

+29
-3
lines changed

pyiceberg/expressions/parser.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@
7979
NULL = CaselessKeyword("null")
8080
NAN = CaselessKeyword("nan")
8181
LIKE = CaselessKeyword("like")
82+
BETWEEN = CaselessKeyword("between")
8283

8384
unquoted_identifier = Word(alphas + "_", alphanums + "_$")
8485
quoted_identifier = QuotedString('"', escChar="\\", unquoteResults=True)
@@ -106,6 +107,7 @@ def _(result: ParseResults) -> Reference:
106107
string = sgl_quoted_string.set_results_name("raw_quoted_string")
107108
decimal = common.real().set_results_name("decimal")
108109
integer = common.signed_integer().set_results_name("integer")
110+
number = common.number().set_results_name("number")
109111
literal = Group(string | decimal | integer | boolean).set_results_name("literal")
110112
literal_set = Group(
111113
DelimitedList(string) | DelimitedList(decimal) | DelimitedList(integer) | DelimitedList(boolean)
@@ -149,8 +151,16 @@ def _(result: ParseResults) -> Literal[L]:
149151
left_ref = column + comparison_op + literal
150152
right_ref = literal + comparison_op + column
151153
comparison = left_ref | right_ref
154+
between = column + BETWEEN + number + AND + number
152155

153156

157+
@between.set_parse_action
158+
def _(result: ParseResults) -> BooleanExpression:
159+
return And(
160+
GreaterThanOrEqual(result.column, result[2]),
161+
LessThanOrEqual(result.column, result[4])
162+
)
163+
154164
@left_ref.set_parse_action
155165
def _(result: ParseResults) -> BooleanExpression:
156166
if result.op == "<":
@@ -258,7 +268,7 @@ def _evaluate_like_statement(result: ParseResults) -> BooleanExpression:
258268
return EqualTo(result.column, StringLiteral(literal_like.value.replace("\\%", "%")))
259269

260270

261-
predicate = (comparison | in_check | null_check | nan_check | starts_check | boolean).set_results_name("predicate")
271+
predicate = (between | comparison | in_check | null_check | nan_check | starts_check | boolean).set_results_name("predicate")
262272

263273

264274
def handle_not(result: ParseResults) -> Not:

tests/expressions/test_parser.py

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,9 +39,9 @@
3939
NotNull,
4040
NotStartsWith,
4141
Or,
42-
StartsWith,
42+
StartsWith, Reference,
4343
)
44-
from pyiceberg.expressions.literals import DecimalLiteral
44+
from pyiceberg.expressions.literals import DecimalLiteral, LongLiteral
4545

4646

4747
def test_always_true() -> None:
@@ -238,3 +238,19 @@ def test_quoted_column_with_dots() -> None:
238238

239239
def test_quoted_column_with_spaces() -> None:
240240
assert EqualTo("Foo Bar", "data") == parser.parse("\"Foo Bar\" = 'data'")
241+
242+
def test_valid_between() -> None:
243+
assert And(left=GreaterThanOrEqual(Reference(name="foo"), LongLiteral(1)), right=LessThanOrEqual(Reference(name="foo"), LongLiteral(3))) == parser.parse("foo between 1 and 3")
244+
assert And(left=GreaterThanOrEqual(Reference(name="foo"), LongLiteral(1)), right=LessThanOrEqual(Reference(name="foo"), LongLiteral(1))) == parser.parse("foo between 1 and 1")
245+
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")
246+
247+
def test_invalid_between() -> None:
248+
# boolean
249+
with pytest.raises(ParseException) as exc_info:
250+
parser.parse("foo between true and false")
251+
assert "Expected number, found 'true'" in str(exc_info)
252+
253+
# string
254+
with pytest.raises(ParseException) as exc_info:
255+
parser.parse("foo between 'a' and 'b'")
256+
assert "Expected number, found \"\'\"" in str(exc_info)

0 commit comments

Comments
 (0)