You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@iceberg.apache.org by bl...@apache.org on 2022/10/05 20:45:55 UTC
[iceberg] branch master updated: Python: Add more expression tests (#5923)
This is an automated email from the ASF dual-hosted git repository.
blue pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/iceberg.git
The following commit(s) were added to refs/heads/master by this push:
new d46734e518 Python: Add more expression tests (#5923)
d46734e518 is described below
commit d46734e51834e209e461bd26af5a77cb1a467ff7
Author: Fokko Driesprong <fo...@apache.org>
AuthorDate: Wed Oct 5 22:45:49 2022 +0200
Python: Add more expression tests (#5923)
---
python/pyiceberg/expressions/base.py | 25 +-
python/pyiceberg/schema.py | 25 +-
python/tests/expressions/test_expressions_base.py | 477 +++++++++++++++++-----
3 files changed, 412 insertions(+), 115 deletions(-)
diff --git a/python/pyiceberg/expressions/base.py b/python/pyiceberg/expressions/base.py
index ea0873ede0..5272145758 100644
--- a/python/pyiceberg/expressions/base.py
+++ b/python/pyiceberg/expressions/base.py
@@ -65,11 +65,11 @@ class BoundTerm(Term[T], Bound, ABC):
@abstractmethod
def ref(self) -> BoundReference[T]:
- ...
+ """Returns the bound reference"""
@abstractmethod
- def eval(self, struct: StructProtocol): # pylint: disable=W0613
- ... # pragma: no cover
+ def eval(self, struct: StructProtocol) -> T: # pylint: disable=W0613
+ """Returns the value at the referenced field's position in an object that abides by the StructProtocol"""
@dataclass(frozen=True)
@@ -129,13 +129,7 @@ class Reference(UnboundTerm[T]):
BoundReference: A reference bound to the specific field in the Iceberg schema
"""
field = schema.find_field(name_or_id=self.name, case_sensitive=case_sensitive)
- if not field:
- raise ValueError(f"Cannot find field '{self.name}' in schema: {schema}")
-
accessor = schema.accessor_for_field(field.field_id)
- if not accessor:
- raise ValueError(f"Cannot find accessor for field '{self.name}' in schema: {schema}")
-
return BoundReference(field=field, accessor=accessor)
@@ -233,6 +227,7 @@ class BoundPredicate(Generic[T], Bound, BooleanExpression):
term: BoundTerm[T]
def __invert__(self) -> BoundPredicate[T]:
+ """Inverts the predicate"""
raise NotImplementedError
@@ -242,9 +237,11 @@ class UnboundPredicate(Generic[T], Unbound[BooleanExpression], BooleanExpression
term: UnboundTerm[T]
def __invert__(self) -> UnboundPredicate[T]:
+ """Inverts the predicate"""
raise NotImplementedError
def bind(self, schema: Schema, case_sensitive: bool = True) -> BooleanExpression:
+ """Binds the predicate to a schema"""
raise NotImplementedError
@@ -255,12 +252,14 @@ class UnaryPredicate(UnboundPredicate[T]):
return self.as_bound(bound_term)
def __invert__(self) -> UnaryPredicate[T]:
+ """Inverts the unary predicate"""
raise NotImplementedError
@dataclass(frozen=True)
class BoundUnaryPredicate(BoundPredicate[T]):
def __invert__(self) -> BoundUnaryPredicate[T]:
+ """Inverts the unary predicate"""
raise NotImplementedError
@@ -347,6 +346,7 @@ class SetPredicate(UnboundPredicate[T]):
literals: tuple[Literal[T], ...]
def __invert__(self) -> SetPredicate[T]:
+ """Inverted expression of the SetPredicate"""
raise NotImplementedError
def bind(self, schema: Schema, case_sensitive: bool = True) -> BooleanExpression:
@@ -359,6 +359,7 @@ class BoundSetPredicate(BoundPredicate[T]):
literals: set[Literal[T]]
def __invert__(self) -> BoundSetPredicate[T]:
+ """Inverted expression of the SetPredicate"""
raise NotImplementedError
@@ -435,6 +436,7 @@ class LiteralPredicate(UnboundPredicate[T]):
return self.as_bound(bound_term, self.literal.to(bound_term.ref().field.field_type))
def __invert__(self) -> LiteralPredicate[T]:
+ """Inverts the literal predicate"""
raise NotImplementedError
@@ -443,6 +445,7 @@ class BoundLiteralPredicate(BoundPredicate[T]):
literal: Literal[T]
def __invert__(self) -> BoundLiteralPredicate[T]:
+ """Inverts the bound literal predicate"""
raise NotImplementedError
@@ -678,10 +681,10 @@ class BindVisitor(BooleanExpressionVisitor[BooleanExpression]):
def visit_or(self, left_result: BooleanExpression, right_result: BooleanExpression) -> BooleanExpression:
return Or(left=left_result, right=right_result)
- def visit_unbound_predicate(self, predicate) -> BooleanExpression:
+ def visit_unbound_predicate(self, predicate: UnboundPredicate) -> BooleanExpression:
return predicate.bind(self._schema, case_sensitive=self._case_sensitive)
- def visit_bound_predicate(self, predicate) -> BooleanExpression:
+ def visit_bound_predicate(self, predicate: BoundPredicate) -> BooleanExpression:
raise TypeError(f"Found already bound predicate: {predicate}")
diff --git a/python/pyiceberg/schema.py b/python/pyiceberg/schema.py
index b0fc41b0e1..7f2c3166ff 100644
--- a/python/pyiceberg/schema.py
+++ b/python/pyiceberg/schema.py
@@ -133,27 +133,33 @@ class Schema(IcebergBaseModel):
"""Returns the schema as a struct"""
return StructType(*self.fields)
- def find_field(self, name_or_id: Union[str, int], case_sensitive: bool = True) -> Optional[NestedField]:
+ def find_field(self, name_or_id: Union[str, int], case_sensitive: bool = True) -> NestedField:
"""Find a field using a field name or field ID
Args:
name_or_id (str | int): Either a field name or a field ID
case_sensitive (bool, optional): Whether to perform a case-sensitive lookup using a field name. Defaults to True.
+ Raises:
+ ValueError: When the value cannot be found
+
Returns:
NestedField: The matched NestedField
"""
if isinstance(name_or_id, int):
- return self._lazy_id_to_field.get(name_or_id)
+ if name_or_id not in self._lazy_id_to_field:
+ raise ValueError(f"Could not find field with id: {name_or_id}")
+ return self._lazy_id_to_field[name_or_id]
+
if case_sensitive:
field_id = self._name_to_id.get(name_or_id)
else:
field_id = self._lazy_name_to_id_lower.get(name_or_id.lower())
if field_id is None:
- raise ValueError(f"Could not find field with name or id {name_or_id}, case_sensitive={case_sensitive}")
+ raise ValueError(f"Could not find field with name {name_or_id}, case_sensitive={case_sensitive}")
- return self._lazy_id_to_field.get(field_id)
+ return self._lazy_id_to_field[field_id]
def find_type(self, name_or_id: Union[str, int], case_sensitive: bool = True) -> IcebergType:
"""Find a field type using a field name or field ID
@@ -185,16 +191,23 @@ class Schema(IcebergBaseModel):
"""
return self._lazy_id_to_name.get(column_id)
- def accessor_for_field(self, field_id: int) -> Optional["Accessor"]:
+ def accessor_for_field(self, field_id: int) -> "Accessor":
"""Find a schema position accessor given a field ID
Args:
field_id (int): The ID of the field
+ Raises:
+ ValueError: When the value cannot be found
+
Returns:
Accessor: An accessor for the given field ID
"""
- return self._lazy_id_to_accessor.get(field_id)
+
+ if field_id not in self._lazy_id_to_accessor:
+ raise ValueError(f"Could not find accessor for field with id: {field_id}")
+
+ return self._lazy_id_to_accessor[field_id]
def select(self, names: List[str], case_sensitive: bool = True) -> "Schema":
"""Return a new schema instance pruned to a subset of columns
diff --git a/python/tests/expressions/test_expressions_base.py b/python/tests/expressions/test_expressions_base.py
index 02997eaa18..30b6688c75 100644
--- a/python/tests/expressions/test_expressions_base.py
+++ b/python/tests/expressions/test_expressions_base.py
@@ -22,7 +22,7 @@ from typing import List, Set
import pytest
from pyiceberg.expressions import base
-from pyiceberg.expressions.base import rewrite_not
+from pyiceberg.expressions.base import rewrite_not, visit_bound_predicate
from pyiceberg.expressions.literals import (
Literal,
LongLiteral,
@@ -213,7 +213,7 @@ class FooBoundBooleanExpressionVisitor(base.BoundBooleanExpressionVisitor[List])
(base.Not(ExpressionA()), "Not(child=ExpressionA())"),
],
)
-def test_reprs(op, rep):
+def test_reprs(op: base.BooleanExpression, rep: str):
assert repr(op) == rep
@@ -227,6 +227,42 @@ def test_isnull_bind():
assert base.IsNull(base.Reference("a")).bind(schema) == bound
+def test_invert_is_null_bind():
+ schema = Schema(NestedField(2, "a", IntegerType(), required=False), schema_id=1)
+ assert ~base.IsNull(base.Reference("a")).bind(schema) == base.NotNull(base.Reference("a")).bind(schema)
+
+
+def test_invert_not_null_bind():
+ schema = Schema(NestedField(2, "a", IntegerType(), required=False), schema_id=1)
+ assert ~base.NotNull(base.Reference("a")).bind(schema) == base.IsNull(base.Reference("a")).bind(schema)
+
+
+def test_invert_is_nan_bind():
+ schema = Schema(NestedField(2, "a", DoubleType(), required=False), schema_id=1)
+ assert ~base.IsNaN(base.Reference("a")).bind(schema) == base.NotNaN(base.Reference("a")).bind(schema)
+
+
+def test_invert_not_nan_bind():
+ schema = Schema(NestedField(2, "a", DoubleType(), required=False), schema_id=1)
+ assert ~base.NotNaN(base.Reference("a")).bind(schema) == base.IsNaN(base.Reference("a")).bind(schema)
+
+
+def test_bind_expr_does_not_exists():
+ schema = Schema(NestedField(2, "a", IntegerType()), schema_id=1)
+ with pytest.raises(ValueError) as exc_info:
+ base.IsNull(base.Reference("b")).bind(schema)
+
+ assert str(exc_info.value) == "Could not find field with name b, case_sensitive=True"
+
+
+def test_bind_does_not_exists():
+ schema = Schema(NestedField(2, "a", IntegerType()), schema_id=1)
+ with pytest.raises(ValueError) as exc_info:
+ base.Reference("b").bind(schema)
+
+ assert str(exc_info.value) == "Could not find field with name b, case_sensitive=True"
+
+
def test_isnull_bind_required():
schema = Schema(NestedField(2, "a", IntegerType(), required=True), schema_id=1)
assert base.IsNull(base.Reference("a")).bind(schema) == base.AlwaysFalse()
@@ -301,138 +337,289 @@ def test_strs(op, string):
assert str(op) == string
-def test_ref_binding_case_sensitive(request):
- schema = request.getfixturevalue("table_schema_simple")
- ref = base.Reference("foo")
- bound = base.BoundReference(schema.find_field(1), schema.accessor_for_field(1))
- assert ref.bind(schema, case_sensitive=True) == bound
+def test_ref_binding_case_sensitive(table_schema_simple: Schema):
+ ref = base.Reference[str]("foo")
+ bound = base.BoundReference[str](table_schema_simple.find_field(1), table_schema_simple.accessor_for_field(1))
+ assert ref.bind(table_schema_simple, case_sensitive=True) == bound
-def test_ref_binding_case_sensitive_failure(request):
- schema = request.getfixturevalue("table_schema_simple")
- ref = base.Reference("Foo")
+def test_ref_binding_case_sensitive_failure(table_schema_simple: Schema):
+ ref = base.Reference[str]("Foo")
with pytest.raises(ValueError):
- ref.bind(schema, case_sensitive=True)
+ ref.bind(table_schema_simple, case_sensitive=True)
-def test_ref_binding_case_insensitive(request):
- schema = request.getfixturevalue("table_schema_simple")
- ref = base.Reference("Foo")
- bound = base.BoundReference(schema.find_field(1), schema.accessor_for_field(1))
- assert ref.bind(schema, case_sensitive=False) == bound
+def test_ref_binding_case_insensitive(table_schema_simple: Schema):
+ ref = base.Reference[str]("Foo")
+ bound = base.BoundReference[str](table_schema_simple.find_field(1), table_schema_simple.accessor_for_field(1))
+ assert ref.bind(table_schema_simple, case_sensitive=False) == bound
-def test_ref_binding_case_insensitive_failure(request):
- schema = request.getfixturevalue("table_schema_simple")
- ref = base.Reference("Foot")
+def test_ref_binding_case_insensitive_failure(table_schema_simple: Schema):
+ ref = base.Reference[str]("Foot")
with pytest.raises(ValueError):
- ref.bind(schema, case_sensitive=False)
+ ref.bind(table_schema_simple, case_sensitive=False)
def test_in_to_eq():
assert base.In(base.Reference("x"), (literal(34.56),)) == base.EqualTo(base.Reference("x"), literal(34.56))
-def test_bind_in(request):
- schema = request.getfixturevalue("table_schema_simple")
+def test_empty_bind_in(table_schema_simple: Schema):
+ bound = base.BoundIn[str](
+ base.BoundReference(table_schema_simple.find_field(1), table_schema_simple.accessor_for_field(1)), set()
+ )
+ assert bound == base.AlwaysFalse()
+
+
+def test_empty_bind_not_in(table_schema_simple: Schema):
+ bound = base.BoundNotIn(
+ base.BoundReference[str](table_schema_simple.find_field(1), table_schema_simple.accessor_for_field(1)), set()
+ )
+ assert bound == base.AlwaysTrue()
+
+
+def test_bind_not_in_equal_term(table_schema_simple: Schema):
+ bound = base.BoundNotIn(
+ base.BoundReference(table_schema_simple.find_field(1), table_schema_simple.accessor_for_field(1)), {literal("hello")}
+ )
+ assert (
+ base.BoundNotEqualTo[str](
+ term=base.BoundReference(
+ field=NestedField(field_id=1, name="foo", field_type=StringType(), required=False),
+ accessor=Accessor(position=0, inner=None),
+ ),
+ literal=StringLiteral("hello"),
+ )
+ == bound
+ )
+
+
+def test_in_empty():
+ assert base.In(base.Reference("foo"), ()) == base.AlwaysFalse()
+
+
+def test_not_in_empty():
+ assert base.NotIn(base.Reference("foo"), ()) == base.AlwaysTrue()
+
+
+def test_not_in_equal():
+ assert base.NotIn(base.Reference("foo"), (literal("hello"),)) == base.NotEqualTo(
+ term=base.Reference(name="foo"), literal=StringLiteral("hello")
+ )
+
+
+def test_bind_in(table_schema_simple: Schema):
bound = base.BoundIn(
- base.BoundReference(schema.find_field(1), schema.accessor_for_field(1)), {literal("hello"), literal("world")}
+ base.BoundReference(table_schema_simple.find_field(1), table_schema_simple.accessor_for_field(1)),
+ {literal("hello"), literal("world")},
)
- assert base.In(base.Reference("foo"), (literal("hello"), literal("world"))).bind(schema) == bound
+ assert base.In(base.Reference("foo"), (literal("hello"), literal("world"))).bind(table_schema_simple) == bound
-def test_bind_dedup(request):
- schema = request.getfixturevalue("table_schema_simple")
+def test_bind_in_invert(table_schema_simple: Schema):
bound = base.BoundIn(
- base.BoundReference(schema.find_field(1), schema.accessor_for_field(1)), {literal("hello"), literal("world")}
+ base.BoundReference(table_schema_simple.find_field(1), table_schema_simple.accessor_for_field(1)),
+ {literal("hello"), literal("world")},
+ )
+ assert ~bound == base.BoundNotIn(
+ base.BoundReference(table_schema_simple.find_field(1), table_schema_simple.accessor_for_field(1)),
+ {literal("hello"), literal("world")},
)
- assert base.In(base.Reference("foo"), (literal("hello"), literal("world"), literal("world"))).bind(schema) == bound
-def test_bind_dedup_to_eq(request):
- schema = request.getfixturevalue("table_schema_simple")
- bound = base.BoundEqualTo(base.BoundReference(schema.find_field(1), schema.accessor_for_field(1)), literal("hello"))
- assert base.In(base.Reference("foo"), (literal("hello"), literal("hello"))).bind(schema) == bound
+def test_bind_not_in_invert(table_schema_simple: Schema):
+ bound = base.BoundNotIn(
+ base.BoundReference(table_schema_simple.find_field(1), table_schema_simple.accessor_for_field(1)),
+ {literal("hello"), literal("world")},
+ )
+ assert ~bound == base.BoundIn(
+ base.BoundReference(table_schema_simple.find_field(1), table_schema_simple.accessor_for_field(1)),
+ {literal("hello"), literal("world")},
+ )
-@pytest.mark.parametrize(
- "a, schema",
- [
- (
- base.NotIn(base.Reference("foo"), (literal("hello"), literal("world"))),
- "table_schema_simple",
- ),
- (
- base.NotEqualTo(base.Reference("foo"), literal("hello")),
- "table_schema_simple",
- ),
- (
- base.EqualTo(base.Reference("foo"), literal("hello")),
- "table_schema_simple",
- ),
- (
- base.GreaterThan(base.Reference("foo"), literal("hello")),
- "table_schema_simple",
+def test_bind_dedup(table_schema_simple: Schema):
+ bound = base.BoundIn(
+ base.BoundReference(table_schema_simple.find_field(1), table_schema_simple.accessor_for_field(1)),
+ {literal("hello"), literal("world")},
+ )
+ assert (
+ base.In(base.Reference("foo"), (literal("hello"), literal("world"), literal("world"))).bind(table_schema_simple) == bound
+ )
+
+
+def test_bind_dedup_to_eq(table_schema_simple: Schema):
+ bound = base.BoundEqualTo(
+ base.BoundReference(table_schema_simple.find_field(1), table_schema_simple.accessor_for_field(1)), literal("hello")
+ )
+ assert base.In(base.Reference("foo"), (literal("hello"), literal("hello"))).bind(table_schema_simple) == bound
+
+
+def test_bound_equal_to_invert(table_schema_simple: Schema):
+ bound = base.BoundEqualTo(
+ base.BoundReference(table_schema_simple.find_field(1), table_schema_simple.accessor_for_field(1)), literal("hello")
+ )
+ assert ~bound == base.BoundNotEqualTo(
+ term=base.BoundReference[str](
+ field=NestedField(field_id=1, name="foo", field_type=StringType(), required=False),
+ accessor=Accessor(position=0, inner=None),
),
- (
- base.LessThan(base.Reference("foo"), literal("hello")),
- "table_schema_simple",
+ literal=StringLiteral("hello"),
+ )
+
+
+def test_bound_not_equal_to_invert(table_schema_simple: Schema):
+ bound = base.BoundNotEqualTo(
+ base.BoundReference(table_schema_simple.find_field(1), table_schema_simple.accessor_for_field(1)), literal("hello")
+ )
+ assert ~bound == base.BoundEqualTo(
+ term=base.BoundReference[str](
+ field=NestedField(field_id=1, name="foo", field_type=StringType(), required=False),
+ accessor=Accessor(position=0, inner=None),
),
- (
- base.GreaterThanOrEqual(base.Reference("foo"), literal("hello")),
- "table_schema_simple",
+ literal=StringLiteral("hello"),
+ )
+
+
+def test_bound_greater_than_or_equal_invert(table_schema_simple: Schema):
+ bound = base.BoundGreaterThanOrEqual(
+ base.BoundReference(table_schema_simple.find_field(1), table_schema_simple.accessor_for_field(1)), literal("hello")
+ )
+ assert ~bound == base.BoundLessThan(
+ term=base.BoundReference[str](
+ field=NestedField(field_id=1, name="foo", field_type=StringType(), required=False),
+ accessor=Accessor(position=0, inner=None),
),
- (
- base.LessThanOrEqual(base.Reference("foo"), literal("hello")),
- "table_schema_simple",
+ literal=StringLiteral("hello"),
+ )
+
+
+def test_bound_greater_than_invert(table_schema_simple: Schema):
+ bound = base.BoundGreaterThan(
+ base.BoundReference(table_schema_simple.find_field(1), table_schema_simple.accessor_for_field(1)), literal("hello")
+ )
+ assert ~bound == base.BoundLessThanOrEqual(
+ term=base.BoundReference[str](
+ field=NestedField(field_id=1, name="foo", field_type=StringType(), required=False),
+ accessor=Accessor(position=0, inner=None),
),
- ],
-)
-def test_bind(a, schema, request):
- schema = request.getfixturevalue(schema)
- assert a.bind(schema, case_sensitive=True).term.field == schema.find_field(a.term.name, case_sensitive=True)
+ literal=StringLiteral("hello"),
+ )
-@pytest.mark.parametrize(
- "a, schema",
- [
- (
- base.In(base.Reference("Bar"), (literal(5), literal(2))),
- "table_schema_simple",
+def test_bound_less_than_invert(table_schema_simple: Schema):
+ bound = base.BoundLessThan(
+ base.BoundReference(table_schema_simple.find_field(1), table_schema_simple.accessor_for_field(1)), literal("hello")
+ )
+ assert ~bound == base.BoundGreaterThanOrEqual(
+ term=base.BoundReference[str](
+ field=NestedField(field_id=1, name="foo", field_type=StringType(), required=False),
+ accessor=Accessor(position=0, inner=None),
),
- (
- base.NotIn(base.Reference("Bar"), (literal(5), literal(2))),
- "table_schema_simple",
+ literal=StringLiteral("hello"),
+ )
+
+
+def test_bound_less_than_or_equal_invert(table_schema_simple: Schema):
+ bound = base.BoundLessThanOrEqual(
+ base.BoundReference(table_schema_simple.find_field(1), table_schema_simple.accessor_for_field(1)), literal("hello")
+ )
+ assert ~bound == base.BoundGreaterThan(
+ term=base.BoundReference[str](
+ field=NestedField(field_id=1, name="foo", field_type=StringType(), required=False),
+ accessor=Accessor(position=0, inner=None),
),
- (
- base.NotEqualTo(base.Reference("Bar"), literal(5)),
- "table_schema_simple",
+ literal=StringLiteral("hello"),
+ )
+
+
+def test_not_equal_to_invert():
+ bound = base.NotEqualTo(
+ term=base.BoundReference(
+ field=NestedField(field_id=1, name="foo", field_type=StringType(), required=False),
+ accessor=Accessor(position=0, inner=None),
),
- (
- base.EqualTo(base.Reference("Bar"), literal(5)),
- "table_schema_simple",
+ literal=StringLiteral("hello"),
+ )
+ assert ~bound == base.EqualTo(
+ term=base.BoundReference(
+ field=NestedField(field_id=1, name="foo", field_type=StringType(), required=False),
+ accessor=Accessor(position=0, inner=None),
),
- (
- base.GreaterThan(base.Reference("Bar"), literal(5)),
- "table_schema_simple",
+ literal=StringLiteral("hello"),
+ )
+
+
+def test_greater_than_or_equal_invert():
+ bound = base.GreaterThanOrEqual(
+ term=base.BoundReference(
+ field=NestedField(field_id=1, name="foo", field_type=StringType(), required=False),
+ accessor=Accessor(position=0, inner=None),
),
- (
- base.LessThan(base.Reference("Bar"), literal(5)),
- "table_schema_simple",
+ literal=StringLiteral("hello"),
+ )
+ assert ~bound == base.LessThan(
+ term=base.BoundReference(
+ field=NestedField(field_id=1, name="foo", field_type=StringType(), required=False),
+ accessor=Accessor(position=0, inner=None),
),
- (
- base.GreaterThanOrEqual(base.Reference("Bar"), literal(5)),
- "table_schema_simple",
+ literal=StringLiteral("hello"),
+ )
+
+
+def test_less_than_or_equal_invert():
+ bound = base.LessThanOrEqual(
+ term=base.BoundReference(
+ field=NestedField(field_id=1, name="foo", field_type=StringType(), required=False),
+ accessor=Accessor(position=0, inner=None),
),
- (
- base.LessThanOrEqual(base.Reference("Bar"), literal(5)),
- "table_schema_simple",
+ literal=StringLiteral("hello"),
+ )
+ assert ~bound == base.GreaterThan(
+ term=base.BoundReference(
+ field=NestedField(field_id=1, name="foo", field_type=StringType(), required=False),
+ accessor=Accessor(position=0, inner=None),
),
+ literal=StringLiteral("hello"),
+ )
+
+
+@pytest.mark.parametrize(
+ "pred",
+ [
+ base.NotIn(base.Reference("foo"), (literal("hello"), literal("world"))),
+ base.NotEqualTo(base.Reference("foo"), literal("hello")),
+ base.EqualTo(base.Reference("foo"), literal("hello")),
+ base.GreaterThan(base.Reference("foo"), literal("hello")),
+ base.LessThan(base.Reference("foo"), literal("hello")),
+ base.GreaterThanOrEqual(base.Reference("foo"), literal("hello")),
+ base.LessThanOrEqual(base.Reference("foo"), literal("hello")),
],
)
-def test_bind_case_insensitive(a, schema, request):
- schema = request.getfixturevalue(schema)
- assert a.bind(schema, case_sensitive=False).term.field == schema.find_field(a.term.name, case_sensitive=False)
+def test_bind(pred, table_schema_simple: Schema):
+ assert pred.bind(table_schema_simple, case_sensitive=True).term.field == table_schema_simple.find_field(
+ pred.term.name, case_sensitive=True
+ )
+
+
+@pytest.mark.parametrize(
+ "pred",
+ [
+ base.In(base.Reference("Bar"), (literal(5), literal(2))),
+ base.NotIn(base.Reference("Bar"), (literal(5), literal(2))),
+ base.NotEqualTo(base.Reference("Bar"), literal(5)),
+ base.EqualTo(base.Reference("Bar"), literal(5)),
+ base.GreaterThan(base.Reference("Bar"), literal(5)),
+ base.LessThan(base.Reference("Bar"), literal(5)),
+ base.GreaterThanOrEqual(base.Reference("Bar"), literal(5)),
+ base.LessThanOrEqual(base.Reference("Bar"), literal(5)),
+ ],
+)
+def test_bind_case_insensitive(pred, table_schema_simple: Schema):
+ assert pred.bind(table_schema_simple, case_sensitive=False).term.field == table_schema_simple.find_field(
+ pred.term.name, case_sensitive=False
+ )
@pytest.mark.parametrize(
@@ -526,15 +713,24 @@ def test_reduce(lhs, rhs):
[
(base.And(base.AlwaysTrue(), ExpressionB()), ExpressionB()),
(base.And(base.AlwaysFalse(), ExpressionB()), base.AlwaysFalse()),
+ (base.And(ExpressionB(), base.AlwaysTrue()), ExpressionB()),
(base.Or(base.AlwaysTrue(), ExpressionB()), base.AlwaysTrue()),
(base.Or(base.AlwaysFalse(), ExpressionB()), ExpressionB()),
+ (base.Or(ExpressionA(), base.AlwaysFalse()), ExpressionA()),
(base.Not(base.Not(ExpressionA())), ExpressionA()),
+ (base.Not(base.AlwaysTrue()), base.AlwaysFalse()),
+ (base.Not(base.AlwaysFalse()), base.AlwaysTrue()),
],
)
def test_base_AlwaysTrue_base_AlwaysFalse(lhs, rhs):
assert lhs == rhs
+def test_invert_always():
+ assert ~base.AlwaysFalse() == base.AlwaysTrue()
+ assert ~base.AlwaysTrue() == base.AlwaysFalse()
+
+
def test_accessor_base_class(foo_struct):
"""Test retrieving a value at a position of a container using an accessor"""
@@ -643,28 +839,46 @@ def test_boolean_expression_visit_raise_not_implemented_error():
assert str(exc_info.value) == "Cannot visit unsupported expression: foo"
-def test_always_true_expression_binding(table_schema_simple):
+def test_bind_visitor_already_bound(table_schema_simple: Schema):
+ bound = base.BoundEqualTo(
+ base.BoundReference(table_schema_simple.find_field(1), table_schema_simple.accessor_for_field(1)), literal("hello")
+ )
+ with pytest.raises(TypeError) as exc_info:
+ base.BindVisitor(base.visit(bound, visitor=base.BindVisitor(schema=table_schema_simple))) # type: ignore
+ assert (
+ "Found already bound predicate: BoundEqualTo(term=BoundReference(field=NestedField(field_id=1, name='foo', field_type=StringType(), required=False), accessor=Accessor(position=0,inner=None)), literal=StringLiteral(hello))"
+ == str(exc_info.value)
+ )
+
+
+def test_visit_bound_visitor_unknown_predicate():
+ with pytest.raises(TypeError) as exc_info:
+ visit_bound_predicate({"something"}, FooBoundBooleanExpressionVisitor())
+ assert "Unknown predicate: {'something'}" == str(exc_info.value)
+
+
+def test_always_true_expression_binding(table_schema_simple: Schema):
"""Test that visiting an always-true expression returns always-true"""
unbound_expression = base.AlwaysTrue()
bound_expression = base.visit(unbound_expression, visitor=base.BindVisitor(schema=table_schema_simple))
assert bound_expression == base.AlwaysTrue()
-def test_always_false_expression_binding(table_schema_simple):
+def test_always_false_expression_binding(table_schema_simple: Schema):
"""Test that visiting an always-false expression returns always-false"""
unbound_expression = base.AlwaysFalse()
bound_expression = base.visit(unbound_expression, visitor=base.BindVisitor(schema=table_schema_simple))
assert bound_expression == base.AlwaysFalse()
-def test_always_false_and_always_true_expression_binding(table_schema_simple):
+def test_always_false_and_always_true_expression_binding(table_schema_simple: Schema):
"""Test that visiting both an always-true AND always-false expression returns always-false"""
unbound_expression = base.And(base.AlwaysTrue(), base.AlwaysFalse())
bound_expression = base.visit(unbound_expression, visitor=base.BindVisitor(schema=table_schema_simple))
assert bound_expression == base.AlwaysFalse()
-def test_always_false_or_always_true_expression_binding(table_schema_simple):
+def test_always_false_or_always_true_expression_binding(table_schema_simple: Schema):
"""Test that visiting always-true OR always-false expression returns always-true"""
unbound_expression = base.Or(base.AlwaysTrue(), base.AlwaysFalse())
bound_expression = base.visit(unbound_expression, visitor=base.BindVisitor(schema=table_schema_simple))
@@ -1154,6 +1368,73 @@ def test_bound_boolean_expression_visitor_raise_on_unbound_predicate():
assert "Not a bound predicate" in str(exc_info.value)
+def test_bound_predicate_invert():
+ with pytest.raises(NotImplementedError):
+ _ = ~base.BoundPredicate(
+ term=base.BoundReference(
+ field=NestedField(field_id=1, name="foo", field_type=StringType(), required=False),
+ accessor=Accessor(position=0, inner=None),
+ )
+ )
+
+
+def test_bound_unary_predicate_invert():
+ with pytest.raises(NotImplementedError):
+ _ = ~base.BoundUnaryPredicate(
+ term=base.BoundReference(
+ field=NestedField(field_id=1, name="foo", field_type=StringType(), required=False),
+ accessor=Accessor(position=0, inner=None),
+ )
+ )
+
+
+def test_bound_set_predicate_invert():
+ with pytest.raises(NotImplementedError):
+ _ = ~base.BoundSetPredicate(
+ term=base.BoundReference(
+ field=NestedField(field_id=1, name="foo", field_type=StringType(), required=False),
+ accessor=Accessor(position=0, inner=None),
+ ),
+ literals={literal("hello"), literal("world")},
+ )
+
+
+def test_bound_literal_predicate_invert():
+ with pytest.raises(NotImplementedError):
+ _ = ~base.BoundLiteralPredicate(
+ term=base.BoundReference(
+ field=NestedField(field_id=1, name="foo", field_type=StringType(), required=False),
+ accessor=Accessor(position=0, inner=None),
+ ),
+ literal=literal("world"),
+ )
+
+
+def test_unbound_predicate_invert():
+ with pytest.raises(NotImplementedError):
+ _ = ~base.UnboundPredicate(term=base.Reference("a"))
+
+
+def test_unbound_predicate_bind(table_schema_simple: Schema):
+ with pytest.raises(NotImplementedError):
+ _ = base.UnboundPredicate(term=base.Reference("a")).bind(table_schema_simple)
+
+
+def test_unbound_unary_predicate_invert():
+ with pytest.raises(NotImplementedError):
+ _ = ~base.UnaryPredicate(term=base.Reference("a"))
+
+
+def test_unbound_set_predicate_invert():
+ with pytest.raises(NotImplementedError):
+ _ = ~base.SetPredicate(term=base.Reference("a"), literals=(literal("hello"), literal("world")))
+
+
+def test_unbound_literal_predicate_invert():
+ with pytest.raises(NotImplementedError):
+ _ = ~base.LiteralPredicate(term=base.Reference("a"), literal=literal("hello"))
+
+
def test_rewrite_not_equal_to():
assert rewrite_not(base.Not(base.EqualTo(base.Reference("x"), literal(34.56)))) == base.NotEqualTo(
base.Reference("x"), literal(34.56)