Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions mkdocs/docs/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -1031,6 +1031,7 @@ Expert Iceberg users may choose to commit existing parquet files to the Iceberg
### Example

Add files to Iceberg table:

```python
# Given that these parquet files have schema consistent with the Iceberg table
Expand All @@ -1047,6 +1048,7 @@ tbl.add_files(file_paths=file_paths)
```

Add files to Iceberg table with custom snapshot properties:

```python
# Assume an existing Iceberg table object `tbl`

Expand Down
10 changes: 9 additions & 1 deletion pyiceberg/expressions/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@
NULL = CaselessKeyword("null")
NAN = CaselessKeyword("nan")
LIKE = CaselessKeyword("like")
BETWEEN = CaselessKeyword("between")

unquoted_identifier = Word(alphas + "_", alphanums + "_$")
quoted_identifier = QuotedString('"', escChar="\\", unquoteResults=True)
Expand Down Expand Up @@ -106,6 +107,7 @@ def _(result: ParseResults) -> Reference:
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)
Expand Down Expand Up @@ -149,6 +151,12 @@ def _(result: ParseResults) -> Literal[L]:
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
Expand Down Expand Up @@ -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:
Expand Down
31 changes: 30 additions & 1 deletion tests/expressions/test_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,10 @@
NotNull,
NotStartsWith,
Or,
Reference,
StartsWith,
)
from pyiceberg.expressions.literals import DecimalLiteral
from pyiceberg.expressions.literals import DecimalLiteral, LongLiteral


def test_always_true() -> None:
Expand Down Expand Up @@ -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)