This is an automated email from the ASF dual-hosted git repository. github-merge-queue[bot] pushed a commit to branch gh-readonly-queue/main/pr-4144-9345369e8e7411e9351313bda2501df33b99c311 in repository https://gitbox.apache.org/repos/asf/iceberg-python.git
commit 0a2e7caa65502be3d5090bc053292439c2253cf6 Author: Shahzeb Ali <[email protected]> AuthorDate: Tue Oct 27 23:04:53 2026 +0000 fix: raise when binding starts-with with an out-of-range literal (#4144) LiteralPredicate.bind folded AboveMax/BelowMin literals to AlwaysTrue or AlwaysFalse for the comparison operations, but starts-with and not-starts-with fell through and built a bound predicate wrapping the sentinel itself, which then crashed evaluators. Raise a ValueError at bind time instead, mirroring the Java implementation, which throws for operations that have no defined fold. Signed-off-by: Shahzeb Ali <[email protected]> --- pyiceberg/expressions/__init__.py | 4 ++++ tests/expressions/test_expressions.py | 14 ++++++++++++++ 2 files changed, 18 insertions(+) diff --git a/pyiceberg/expressions/__init__.py b/pyiceberg/expressions/__init__.py index 1fb9810ff..ad916171c 100644 --- a/pyiceberg/expressions/__init__.py +++ b/pyiceberg/expressions/__init__.py @@ -916,11 +916,15 @@ class LiteralPredicate(UnboundPredicate, ABC): return AlwaysTrue() elif isinstance(self, (GreaterThan, GreaterThanOrEqual, EqualTo)): return AlwaysFalse() + else: + raise ValueError(f"Cannot bind {type(self).__name__} with AboveMax literal") elif isinstance(lit, BelowMin): if isinstance(self, (GreaterThan, GreaterThanOrEqual, NotEqualTo)): return AlwaysTrue() elif isinstance(self, (LessThan, LessThanOrEqual, EqualTo)): return AlwaysFalse() + else: + raise ValueError(f"Cannot bind {type(self).__name__} with BelowMin literal") return self.as_bound(bound_term, lit) # type: ignore diff --git a/tests/expressions/test_expressions.py b/tests/expressions/test_expressions.py index 8ce48a689..5a33db254 100644 --- a/tests/expressions/test_expressions.py +++ b/tests/expressions/test_expressions.py @@ -295,6 +295,20 @@ def test_empty_bind_not_in(table_schema_simple: Schema) -> None: assert bound == AlwaysTrue() [email protected]( + "expression, match", + [ + (StartsWith("bar", "2147483648"), "AboveMax"), + (NotStartsWith("bar", "2147483648"), "AboveMax"), + (StartsWith("bar", "-2147483649"), "BelowMin"), + (NotStartsWith("bar", "-2147483649"), "BelowMin"), + ], +) +def test_bind_starts_with_out_of_range_literal(table_schema_simple: Schema, expression: BooleanExpression, match: str) -> None: + with pytest.raises(ValueError, match=match): + expression.bind(table_schema_simple) + + def test_bind_not_in_equal_term(table_schema_simple: Schema) -> None: bound = BoundNotIn( BoundReference(table_schema_simple.find_field(1), table_schema_simple.accessor_for_field(1)), {literal("hello")}
