Skip to content

Commit c6b65a6

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

File tree

2 files changed

+30
-4
lines changed

2 files changed

+30
-4
lines changed

pyiceberg/expressions/parser.py

Lines changed: 12 additions & 2 deletions
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,17 @@ 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+
print("BETWEEN matched:", result)
160+
return And(
161+
GreaterThanOrEqual(result.column, result[2]),
162+
LessThanOrEqual(result.column, result[4])
163+
)
164+
154165
@left_ref.set_parse_action
155166
def _(result: ParseResults) -> BooleanExpression:
156167
if result.op == "<":
@@ -258,7 +269,7 @@ def _evaluate_like_statement(result: ParseResults) -> BooleanExpression:
258269
return EqualTo(result.column, StringLiteral(literal_like.value.replace("\\%", "%")))
259270

260271

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

263274

264275
def handle_not(result: ParseResults) -> Not:
@@ -297,7 +308,6 @@ def handle_always_expression(result: ParseResults) -> BooleanExpression:
297308
.add_parse_action(handle_always_expression)
298309
)
299310

300-
301311
def parse(expr: str) -> BooleanExpression:
302312
"""Parse a boolean expression."""
303313
return boolean_expression.parse_string(expr, parse_all=True)[0]

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)