This is an automated email from the ASF dual-hosted git repository.
fokko pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/iceberg-python.git
The following commit(s) were added to refs/heads/main by this push:
new f1c2ef2b Add Strict projection (#539)
f1c2ef2b is described below
commit f1c2ef2bd3f31bc7b9be60ccf1b10177f3c8dc4a
Author: Fokko Driesprong <[email protected]>
AuthorDate: Mon Mar 25 12:13:43 2024 +0100
Add Strict projection (#539)
* Add Strict projection
* Update pyiceberg/expressions/visitors.py
Co-authored-by: Honah J. <[email protected]>
* Comments, thanks Honah!
---------
Co-authored-by: Honah J. <[email protected]>
---
pyiceberg/expressions/visitors.py | 24 ++
pyiceberg/transforms.py | 140 +++++-
tests/conftest.py | 11 +
tests/test_transforms.py | 864 +++++++++++++++++++++++++++++++++++++-
4 files changed, 1029 insertions(+), 10 deletions(-)
diff --git a/pyiceberg/expressions/visitors.py
b/pyiceberg/expressions/visitors.py
index 9329b994..26698921 100644
--- a/pyiceberg/expressions/visitors.py
+++ b/pyiceberg/expressions/visitors.py
@@ -1433,6 +1433,30 @@ class _InclusiveMetricsEvaluator(_MetricsEvaluator):
return ROWS_MIGHT_MATCH
+def strict_projection(
+ schema: Schema, spec: PartitionSpec, case_sensitive: bool = True
+) -> Callable[[BooleanExpression], BooleanExpression]:
+ return StrictProjection(schema, spec, case_sensitive).project
+
+
+class StrictProjection(ProjectionEvaluator):
+ def visit_bound_predicate(self, predicate: BoundPredicate[Any]) ->
BooleanExpression:
+ parts =
self.spec.fields_by_source_id(predicate.term.ref().field.field_id)
+
+ result: BooleanExpression = AlwaysFalse()
+ for part in parts:
+ # consider (ts > 2019-01-01T01:00:00) with day(ts) and hour(ts)
+ # projections: d >= 2019-01-02 and h >= 2019-01-01-02 (note the
inclusive bounds).
+ # any timestamp where either projection predicate is true must
match the original
+ # predicate. For example, ts = 2019-01-01T03:00:00 matches the
hour projection but not
+ # the day, but does match the original predicate.
+ strict_projection = part.transform.strict_project(name=part.name,
pred=predicate)
+ if strict_projection is not None:
+ result = Or(result, strict_projection)
+
+ return result
+
+
class _StrictMetricsEvaluator(_MetricsEvaluator):
struct: StructType
expr: BooleanExpression
diff --git a/pyiceberg/transforms.py b/pyiceberg/transforms.py
index e678a77e..6dcae59e 100644
--- a/pyiceberg/transforms.py
+++ b/pyiceberg/transforms.py
@@ -35,6 +35,7 @@ from pyiceberg.expressions import (
BoundLessThan,
BoundLessThanOrEqual,
BoundLiteralPredicate,
+ BoundNotEqualTo,
BoundNotIn,
BoundNotStartsWith,
BoundPredicate,
@@ -43,8 +44,11 @@ from pyiceberg.expressions import (
BoundTerm,
BoundUnaryPredicate,
EqualTo,
+ GreaterThan,
GreaterThanOrEqual,
+ LessThan,
LessThanOrEqual,
+ NotEqualTo,
NotStartsWith,
Reference,
StartsWith,
@@ -144,6 +148,9 @@ class Transform(IcebergRootModel[str], ABC, Generic[S, T]):
@abstractmethod
def project(self, name: str, pred: BoundPredicate[L]) ->
Optional[UnboundPredicate[Any]]: ...
+ @abstractmethod
+ def strict_project(self, name: str, pred: BoundPredicate[Any]) ->
Optional[UnboundPredicate[Any]]: ...
+
@property
def preserves_order(self) -> bool:
return False
@@ -216,6 +223,21 @@ class BucketTransform(Transform[S, int]):
# For example, (x > 0) and (x < 3) can be turned into in({1, 2})
and projected.
return None
+ def strict_project(self, name: str, pred: BoundPredicate[Any]) ->
Optional[UnboundPredicate[Any]]:
+ transformer = self.transform(pred.term.ref().field.field_type)
+
+ if isinstance(pred.term, BoundTransform):
+ return _project_transform_predicate(self, name, pred)
+ elif isinstance(pred, BoundUnaryPredicate):
+ return pred.as_unbound(Reference(name))
+ elif isinstance(pred, BoundNotEqualTo):
+ return pred.as_unbound(Reference(name),
_transform_literal(transformer, pred.literal))
+ elif isinstance(pred, BoundNotIn):
+ return pred.as_unbound(Reference(name),
{_transform_literal(transformer, literal) for literal in pred.literals})
+ else:
+ # no strict projection for comparison or equality
+ return None
+
def can_transform(self, source: IcebergType) -> bool:
return isinstance(
source,
@@ -306,6 +328,19 @@ class TimeTransform(Transform[S, int], Generic[S],
Singleton):
else:
return None
+ def strict_project(self, name: str, pred: BoundPredicate[Any]) ->
Optional[UnboundPredicate[Any]]:
+ transformer = self.transform(pred.term.ref().field.field_type)
+ if isinstance(pred.term, BoundTransform):
+ return _project_transform_predicate(self, name, pred)
+ elif isinstance(pred, BoundUnaryPredicate):
+ return pred.as_unbound(Reference(name))
+ elif isinstance(pred, BoundLiteralPredicate):
+ return _truncate_number_strict(name, pred, transformer)
+ elif isinstance(pred, BoundNotIn):
+ return _set_apply_transform(name, pred, transformer)
+ else:
+ return None
+
@property
def dedup_name(self) -> str:
return "time"
@@ -516,10 +551,20 @@ class IdentityTransform(Transform[S, S]):
return pred.as_unbound(Reference(name))
elif isinstance(pred, BoundLiteralPredicate):
return pred.as_unbound(Reference(name), pred.literal)
- elif isinstance(pred, (BoundIn, BoundNotIn)):
+ elif isinstance(pred, BoundSetPredicate):
+ return pred.as_unbound(Reference(name), pred.literals)
+ else:
+ return None
+
+ def strict_project(self, name: str, pred: BoundPredicate[Any]) ->
Optional[UnboundPredicate[Any]]:
+ if isinstance(pred, BoundUnaryPredicate):
+ return pred.as_unbound(Reference(name))
+ elif isinstance(pred, BoundLiteralPredicate):
+ return pred.as_unbound(Reference(name), pred.literal)
+ elif isinstance(pred, BoundSetPredicate):
return pred.as_unbound(Reference(name), pred.literals)
else:
- raise ValueError(f"Could not project: {pred}")
+ return None
@property
def preserves_order(self) -> bool:
@@ -590,6 +635,47 @@ class TruncateTransform(Transform[S, S]):
return _truncate_array(name, pred, self.transform(field_type))
return None
+ def strict_project(self, name: str, pred: BoundPredicate[Any]) ->
Optional[UnboundPredicate[Any]]:
+ field_type = pred.term.ref().field.field_type
+
+ if isinstance(pred.term, BoundTransform):
+ return _project_transform_predicate(self, name, pred)
+
+ if isinstance(field_type, (IntegerType, LongType, DecimalType)):
+ if isinstance(pred, BoundUnaryPredicate):
+ return pred.as_unbound(Reference(name))
+ elif isinstance(pred, BoundLiteralPredicate):
+ return _truncate_number_strict(name, pred,
self.transform(field_type))
+ elif isinstance(pred, BoundNotIn):
+ return _set_apply_transform(name, pred,
self.transform(field_type))
+ else:
+ return None
+
+ if isinstance(pred, BoundLiteralPredicate):
+ if isinstance(pred, BoundStartsWith):
+ literal_width = len(pred.literal.value)
+ if literal_width < self.width:
+ return pred.as_unbound(name, pred.literal.value)
+ elif literal_width == self.width:
+ return EqualTo(name, pred.literal.value)
+ else:
+ return None
+ elif isinstance(pred, BoundNotStartsWith):
+ literal_width = len(pred.literal.value)
+ if literal_width < self.width:
+ return pred.as_unbound(name, pred.literal.value)
+ elif literal_width == self.width:
+ return NotEqualTo(name, pred.literal.value)
+ else:
+ return pred.as_unbound(name,
self.transform(field_type)(pred.literal.value))
+ else:
+ # ProjectionUtil.truncateArrayStrict(name, pred, this);
+ return _truncate_array_strict(name, pred,
self.transform(field_type))
+ elif isinstance(pred, BoundNotIn):
+ return _set_apply_transform(name, pred, self.transform(field_type))
+ else:
+ return None
+
@property
def width(self) -> int:
return self._width
@@ -714,6 +800,9 @@ class UnknownTransform(Transform[S, T]):
def project(self, name: str, pred: BoundPredicate[L]) ->
Optional[UnboundPredicate[Any]]:
return None
+ def strict_project(self, name: str, pred: BoundPredicate[Any]) ->
Optional[UnboundPredicate[Any]]:
+ return None
+
def __repr__(self) -> str:
"""Return the string representation of the UnknownTransform class."""
return f"UnknownTransform(transform={repr(self._transform)})"
@@ -736,6 +825,9 @@ class VoidTransform(Transform[S, None], Singleton):
def project(self, name: str, pred: BoundPredicate[L]) ->
Optional[UnboundPredicate[Any]]:
return None
+ def strict_project(self, name: str, pred: BoundPredicate[L]) ->
Optional[UnboundPredicate[Any]]:
+ return None
+
def to_human_string(self, _: IcebergType, value: Optional[S]) -> str:
return "null"
@@ -766,6 +858,47 @@ def _truncate_number(
return None
+def _truncate_number_strict(
+ name: str, pred: BoundLiteralPredicate[L], transform:
Callable[[Optional[L]], Optional[L]]
+) -> Optional[UnboundPredicate[Any]]:
+ boundary = pred.literal
+
+ if not isinstance(boundary, (LongLiteral, DecimalLiteral, DateLiteral,
TimestampLiteral)):
+ raise ValueError(f"Expected a numeric literal, got: {type(boundary)}")
+
+ if isinstance(pred, BoundLessThan):
+ return LessThan(Reference(name), _transform_literal(transform,
boundary))
+ elif isinstance(pred, BoundLessThanOrEqual):
+ return LessThan(Reference(name), _transform_literal(transform,
boundary.increment())) # type: ignore
+ elif isinstance(pred, BoundGreaterThan):
+ return GreaterThan(Reference(name), _transform_literal(transform,
boundary))
+ elif isinstance(pred, BoundGreaterThanOrEqual):
+ return GreaterThan(Reference(name), _transform_literal(transform,
boundary.decrement())) # type: ignore
+ elif isinstance(pred, BoundNotEqualTo):
+ return EqualTo(Reference(name), _transform_literal(transform,
boundary))
+ elif isinstance(pred, BoundEqualTo):
+ # there is no predicate that guarantees equality because adjacent
longs transform to the
+ # same value
+ return None
+ else:
+ return None
+
+
+def _truncate_array_strict(
+ name: str, pred: BoundLiteralPredicate[L], transform:
Callable[[Optional[L]], Optional[L]]
+) -> Optional[UnboundPredicate[Any]]:
+ boundary = pred.literal
+
+ if isinstance(pred, (BoundLessThan, BoundLessThanOrEqual)):
+ return LessThan(Reference(name), _transform_literal(transform,
boundary))
+ elif isinstance(pred, (BoundGreaterThan, BoundGreaterThanOrEqual)):
+ return GreaterThan(Reference(name), _transform_literal(transform,
boundary))
+ if isinstance(pred, BoundNotEqualTo):
+ return NotEqualTo(Reference(name), _transform_literal(transform,
boundary))
+ else:
+ return None
+
+
def _truncate_array(
name: str, pred: BoundLiteralPredicate[L], transform:
Callable[[Optional[L]], Optional[L]]
) -> Optional[UnboundPredicate[Any]]:
@@ -808,7 +941,8 @@ def _remove_transform(partition_name: str, pred:
BoundPredicate[L]) -> UnboundPr
def _set_apply_transform(name: str, pred: BoundSetPredicate[L], transform:
Callable[[L], L]) -> UnboundPredicate[Any]:
literals = pred.literals
if isinstance(pred, BoundSetPredicate):
- return pred.as_unbound(Reference(name), {_transform_literal(transform,
literal) for literal in literals})
+ transformed_literals = {_transform_literal(transform, literal) for
literal in literals}
+ return pred.as_unbound(Reference(name=name),
literals=transformed_literals)
else:
raise ValueError(f"Unknown BoundSetPredicate: {pred}")
diff --git a/tests/conftest.py b/tests/conftest.py
index 205cd220..48187ee6 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -79,6 +79,7 @@ from pyiceberg.types import (
NestedField,
StringType,
StructType,
+ UUIDType,
)
from pyiceberg.utils.datetime import datetime_to_millis
@@ -1928,6 +1929,16 @@ def bound_reference_str() -> BoundReference[str]:
return BoundReference(field=NestedField(1, "field", StringType(),
required=False), accessor=Accessor(position=0, inner=None))
[email protected]
+def bound_reference_binary() -> BoundReference[str]:
+ return BoundReference(field=NestedField(1, "field", BinaryType(),
required=False), accessor=Accessor(position=0, inner=None))
+
+
[email protected]
+def bound_reference_uuid() -> BoundReference[str]:
+ return BoundReference(field=NestedField(1, "field", UUIDType(),
required=False), accessor=Accessor(position=0, inner=None))
+
+
@pytest.fixture(scope="session")
def session_catalog() -> Catalog:
return load_catalog(
diff --git a/tests/test_transforms.py b/tests/test_transforms.py
index 4fea7739..4dc3d981 100644
--- a/tests/test_transforms.py
+++ b/tests/test_transforms.py
@@ -17,7 +17,7 @@
# pylint: disable=eval-used,protected-access,redefined-outer-name
from datetime import date
from decimal import Decimal
-from typing import Any, Callable
+from typing import Any, Callable, Optional
from uuid import UUID
import mmh3 as mmh3
@@ -30,32 +30,42 @@ from pydantic import (
)
from typing_extensions import Annotated
-from pyiceberg import transforms
from pyiceberg.expressions import (
BoundEqualTo,
BoundGreaterThan,
BoundGreaterThanOrEqual,
BoundIn,
+ BoundIsNull,
BoundLessThan,
BoundLessThanOrEqual,
+ BoundLiteralPredicate,
+ BoundNotEqualTo,
BoundNotIn,
BoundNotNull,
BoundNotStartsWith,
BoundReference,
BoundStartsWith,
EqualTo,
+ GreaterThan,
GreaterThanOrEqual,
In,
+ LessThan,
LessThanOrEqual,
+ LiteralPredicate,
+ NotEqualTo,
NotIn,
NotNull,
NotStartsWith,
Reference,
+ SetPredicate,
StartsWith,
+ UnaryPredicate,
+ UnboundPredicate,
)
from pyiceberg.expressions.literals import (
DateLiteral,
DecimalLiteral,
+ LongLiteral,
TimestampLiteral,
literal,
)
@@ -74,7 +84,7 @@ from pyiceberg.transforms import (
YearTransform,
parse_transform,
)
-from pyiceberg.typedef import UTF8
+from pyiceberg.typedef import UTF8, L
from pyiceberg.types import (
BinaryType,
BooleanType,
@@ -438,7 +448,7 @@ def test_truncate_method(type_var: PrimitiveType, value:
Any, expected_human_str
def test_unknown_transform() -> None:
- unknown_transform = transforms.UnknownTransform("unknown") # type: ignore
+ unknown_transform = UnknownTransform("unknown") # type: ignore
assert str(unknown_transform) == str(eval(repr(unknown_transform)))
with pytest.raises(AttributeError):
unknown_transform.transform(StringType())("test")
@@ -603,9 +613,7 @@ def bound_reference_decimal() -> BoundReference[Decimal]:
@pytest.fixture
def bound_reference_long() -> BoundReference[int]:
- return BoundReference(
- field=NestedField(1, "field", DecimalType(8, 2), required=False),
accessor=Accessor(position=0, inner=None)
- )
+ return BoundReference(field=NestedField(1, "field", LongType(),
required=False), accessor=Accessor(position=0, inner=None))
def test_projection_bucket_unary(bound_reference_str: BoundReference[str]) ->
None:
@@ -958,3 +966,845 @@ def
test_projection_truncate_string_not_starts_with(bound_reference_str: BoundRe
assert TruncateTransform(2).project(
"name", BoundNotStartsWith(term=bound_reference_str,
literal=literal("hello"))
) == NotStartsWith(term="name", literal=literal("he"))
+
+
+def _test_projection(lhs: Optional[UnboundPredicate[L]], rhs:
Optional[UnboundPredicate[L]]) -> None:
+ assert type(lhs) == type(lhs), f"Different classes: {type(lhs)} !=
{type(rhs)}"
+ if lhs is None and rhs is None:
+ # Both null
+ pass
+ elif isinstance(lhs, UnaryPredicate) and isinstance(rhs, UnaryPredicate):
+ # Nothing more to check
+ pass
+ elif isinstance(lhs, LiteralPredicate) and isinstance(rhs,
LiteralPredicate):
+ assert lhs.literal == rhs.literal, f"Different literal: {lhs.literal}
!= {rhs.literal}"
+ elif isinstance(lhs, SetPredicate) and isinstance(rhs, SetPredicate):
+ assert lhs.literals == rhs.literals, f"Different literals:
{lhs.literals} != {rhs.literals}"
+ else:
+ raise ValueError(f"Comparing unrelated: {lhs} <> {rhs}")
+
+
+def test_month_projection_strict_epoch(bound_reference_date:
BoundReference[int]) -> None:
+ date = literal("1970-01-01").to(DateType())
+ transform: Transform[Any, int] = MonthTransform()
+ _test_projection(
+ transform.strict_project(name="name",
pred=BoundLessThan(term=bound_reference_date, literal=date)),
+ LessThan(term="name", literal=DateLiteral(0)),
+ )
+ _test_projection(
+ transform.strict_project(name="name",
pred=BoundLessThanOrEqual(term=bound_reference_date, literal=date)),
+ LessThan(term="name", literal=DateLiteral(0)),
+ )
+ _test_projection(
+ transform.strict_project(name="name",
pred=BoundGreaterThan(term=bound_reference_date, literal=date)),
+ GreaterThan(term="name", literal=DateLiteral(0)),
+ )
+ _test_projection(
+ transform.strict_project(name="name",
pred=BoundGreaterThanOrEqual(term=bound_reference_date, literal=date)),
+ GreaterThan(term="name", literal=LongLiteral(-1)), # In Java this is
human string 1970-01
+ )
+ _test_projection(
+ transform.strict_project(name="name",
pred=BoundNotEqualTo(term=bound_reference_date, literal=date)),
+ NotEqualTo(term="name", literal=DateLiteral(0)),
+ )
+ _test_projection(
+ lhs=transform.strict_project(name="name",
pred=BoundEqualTo(term=bound_reference_date, literal=date)), rhs=None
+ )
+
+ another_date = literal("1969-12-31").to(DateType())
+ _test_projection(
+ transform.strict_project(name="name",
pred=BoundNotIn(term=bound_reference_date, literals={date, another_date})),
+ NotIn(term="name", literals={DateLiteral(-1), DateLiteral(0)}),
+ )
+ _test_projection(
+ lhs=transform.strict_project(name="name",
pred=BoundIn(term=bound_reference_date, literals={date, another_date})),
+ rhs=None,
+ )
+
+
+def test_month_projection_strict_lower_bound(bound_reference_date:
BoundReference[int]) -> None:
+ date = literal("2017-01-01").to(DateType()) # == 564 months since epoch
+ transform: Transform[Any, int] = MonthTransform()
+ _test_projection(
+ transform.strict_project(name="name",
pred=BoundLessThan(term=bound_reference_date, literal=date)),
+ LessThan(term="name", literal=DateLiteral(564)),
+ )
+ _test_projection(
+ transform.strict_project(name="name",
pred=BoundLessThanOrEqual(term=bound_reference_date, literal=date)),
+ LessThan(term="name", literal=DateLiteral(564)),
+ )
+ _test_projection(
+ transform.strict_project(name="name",
pred=BoundGreaterThan(term=bound_reference_date, literal=date)),
+ GreaterThan(term="name", literal=DateLiteral(564)),
+ )
+ _test_projection(
+ transform.strict_project(name="name",
pred=BoundGreaterThanOrEqual(term=bound_reference_date, literal=date)),
+ GreaterThan(term="name", literal=LongLiteral(563)),
+ )
+ _test_projection(
+ transform.strict_project(name="name",
pred=BoundNotEqualTo(term=bound_reference_date, literal=date)),
+ NotEqualTo(term="name", literal=DateLiteral(564)),
+ )
+ _test_projection(
+ lhs=transform.strict_project(name="name",
pred=BoundEqualTo(term=bound_reference_date, literal=date)), rhs=None
+ )
+
+ another_date = literal("2017-12-02").to(DateType())
+ _test_projection(
+ transform.strict_project(name="name",
pred=BoundNotIn(term=bound_reference_date, literals={date, another_date})),
+ NotIn(term="name", literals={LongLiteral(575), LongLiteral(564)}),
+ )
+ _test_projection(
+ lhs=transform.strict_project(name="name",
pred=BoundIn(term=bound_reference_date, literals={date, another_date})),
+ rhs=None,
+ )
+
+
+def test_negative_month_projection_strict_lower_bound(bound_reference_date:
BoundReference[int]) -> None:
+ date = literal("1969-01-01").to(DateType()) # == 564 months since epoch
+ transform: Transform[Any, int] = MonthTransform()
+ _test_projection(
+ transform.strict_project(name="name",
pred=BoundLessThan(term=bound_reference_date, literal=date)),
+ LessThan(term="name", literal=DateLiteral(-12)),
+ )
+ _test_projection(
+ transform.strict_project(name="name",
pred=BoundLessThanOrEqual(term=bound_reference_date, literal=date)),
+ LessThan(term="name", literal=DateLiteral(-12)),
+ )
+ _test_projection(
+ transform.strict_project(name="name",
pred=BoundGreaterThan(term=bound_reference_date, literal=date)),
+ GreaterThan(term="name", literal=LongLiteral(-12)),
+ )
+ _test_projection(
+ transform.strict_project(name="name",
pred=BoundGreaterThanOrEqual(term=bound_reference_date, literal=date)),
+ GreaterThan(term="name", literal=LongLiteral(-13)),
+ )
+ _test_projection(
+ transform.strict_project(name="name",
pred=BoundNotEqualTo(term=bound_reference_date, literal=date)),
+ NotEqualTo(term="name", literal=DateLiteral(-12)),
+ )
+ _test_projection(
+ lhs=transform.strict_project(name="name",
pred=BoundEqualTo(term=bound_reference_date, literal=date)), rhs=None
+ )
+
+ another_date = literal("1969-12-31").to(DateType())
+ _test_projection(
+ transform.strict_project(name="name",
pred=BoundNotIn(term=bound_reference_date, literals={date, another_date})),
+ NotIn(term="name", literals={LongLiteral(-1), LongLiteral(-12)}),
+ )
+ _test_projection(
+ lhs=transform.strict_project(name="name",
pred=BoundIn(term=bound_reference_date, literals={date, another_date})),
+ rhs=None,
+ )
+
+
+def test_month_projection_strict_upper_bound(bound_reference_date:
BoundReference[int]) -> None:
+ date = literal("2017-12-31").to(DateType()) # == 575 months since epoch
+ transform: Transform[Any, int] = MonthTransform()
+ _test_projection(
+ transform.strict_project(name="name",
pred=BoundLessThan(term=bound_reference_date, literal=date)),
+ LessThan(term="name", literal=DateLiteral(575)),
+ )
+ _test_projection(
+ transform.strict_project(name="name",
pred=BoundLessThanOrEqual(term=bound_reference_date, literal=date)),
+ LessThan(term="name", literal=LongLiteral(576)),
+ )
+ _test_projection(
+ transform.strict_project(name="name",
pred=BoundGreaterThan(term=bound_reference_date, literal=date)),
+ GreaterThan(term="name", literal=DateLiteral(575)),
+ )
+ _test_projection(
+ transform.strict_project(name="name",
pred=BoundGreaterThanOrEqual(term=bound_reference_date, literal=date)),
+ GreaterThan(term="name", literal=LongLiteral(575)),
+ )
+ _test_projection(
+ transform.strict_project(name="name",
pred=BoundNotEqualTo(term=bound_reference_date, literal=date)),
+ NotEqualTo(term="name", literal=DateLiteral(575)),
+ )
+ _test_projection(
+ lhs=transform.strict_project(name="name",
pred=BoundEqualTo(term=bound_reference_date, literal=date)), rhs=None
+ )
+
+ another_date = literal("2017-01-01").to(DateType())
+ _test_projection(
+ transform.strict_project(name="name",
pred=BoundNotIn(term=bound_reference_date, literals={date, another_date})),
+ NotIn(term="name", literals={LongLiteral(575), LongLiteral(564)}),
+ )
+ _test_projection(
+ lhs=transform.strict_project(name="name",
pred=BoundIn(term=bound_reference_date, literals={date, another_date})),
+ rhs=None,
+ )
+
+
+def test_negative_month_projection_strict_upper_bound(bound_reference_date:
BoundReference[int]) -> None:
+ date = literal("1969-12-31").to(DateType()) # == -1 month since epoch
+ transform: Transform[Any, int] = MonthTransform()
+ _test_projection(
+ transform.strict_project(name="name",
pred=BoundLessThan(term=bound_reference_date, literal=date)),
+ LessThan(term="name", literal=DateLiteral(-1)),
+ )
+ _test_projection(
+ transform.strict_project(name="name",
pred=BoundLessThanOrEqual(term=bound_reference_date, literal=date)),
+ LessThan(term="name", literal=LongLiteral(0)),
+ )
+ _test_projection(
+ transform.strict_project(name="name",
pred=BoundGreaterThan(term=bound_reference_date, literal=date)),
+ GreaterThan(term="name", literal=DateLiteral(-1)),
+ )
+ _test_projection(
+ transform.strict_project(name="name",
pred=BoundGreaterThanOrEqual(term=bound_reference_date, literal=date)),
+ GreaterThan(term="name", literal=LongLiteral(-1)),
+ )
+ _test_projection(
+ transform.strict_project(name="name",
pred=BoundNotEqualTo(term=bound_reference_date, literal=date)),
+ NotEqualTo(term="name", literal=DateLiteral(-1)),
+ )
+ _test_projection(
+ lhs=transform.strict_project(name="name",
pred=BoundEqualTo(term=bound_reference_date, literal=date)), rhs=None
+ )
+
+ another_date = literal("1969-11-01").to(DateType())
+ _test_projection(
+ transform.strict_project(name="name",
pred=BoundNotIn(term=bound_reference_date, literals={date, another_date})),
+ NotIn(term="name", literals={LongLiteral(-1), LongLiteral(-2)}),
+ )
+ _test_projection(
+ lhs=transform.strict_project(name="name",
pred=BoundIn(term=bound_reference_date, literals={date, another_date})),
+ rhs=None,
+ )
+
+
+def test_day_strict(bound_reference_date: BoundReference[int]) -> None:
+ date = literal("2017-01-01").to(DateType())
+ _test_projection(
+ DayTransform().strict_project(name="name",
pred=BoundLessThan(term=bound_reference_date, literal=date)),
+ LessThan(term="name", literal=DateLiteral(17167)),
+ )
+ _test_projection(
+ DayTransform().strict_project(name="name",
pred=BoundLessThanOrEqual(term=bound_reference_date, literal=date)),
+ LessThan(term="name", literal=LongLiteral(17168)),
+ )
+ _test_projection(
+ DayTransform().strict_project(name="name",
pred=BoundGreaterThan(term=bound_reference_date, literal=date)),
+ GreaterThan(term="name", literal=DateLiteral(17167)),
+ )
+ _test_projection(
+ DayTransform().strict_project(name="name",
pred=BoundGreaterThanOrEqual(term=bound_reference_date, literal=date)),
+ GreaterThan(term="name", literal=LongLiteral(17166)),
+ )
+ _test_projection(
+ DayTransform().strict_project(name="name",
pred=BoundNotEqualTo(term=bound_reference_date, literal=date)),
+ NotEqualTo(term="name", literal=DateLiteral(17167)),
+ )
+ _test_projection(
+ lhs=DayTransform().strict_project(name="name",
pred=BoundEqualTo(term=bound_reference_date, literal=date)), rhs=None
+ )
+
+ another_date = literal("2017-12-31").to(DateType())
+ _test_projection(
+ DayTransform().strict_project(name="name",
pred=BoundNotIn(term=bound_reference_date, literals={date, another_date})),
+ NotIn(term="name", literals={LongLiteral(17531), LongLiteral(17167)}),
+ )
+ _test_projection(
+ lhs=DayTransform().strict_project(name="name",
pred=BoundIn(term=bound_reference_date, literals={date, another_date})),
+ rhs=None,
+ )
+
+
+def test_day_negative_strict(bound_reference_date: BoundReference[int]) ->
None:
+ date = literal("1969-12-30").to(DateType())
+ _test_projection(
+ DayTransform().strict_project(name="name",
pred=BoundLessThan(term=bound_reference_date, literal=date)),
+ LessThan(term="name", literal=DateLiteral(-2)),
+ )
+ _test_projection(
+ DayTransform().strict_project(name="name",
pred=BoundLessThanOrEqual(term=bound_reference_date, literal=date)),
+ LessThan(term="name", literal=LongLiteral(-1)),
+ )
+ _test_projection(
+ DayTransform().strict_project(name="name",
pred=BoundGreaterThan(term=bound_reference_date, literal=date)),
+ GreaterThan(term="name", literal=DateLiteral(-2)),
+ )
+ _test_projection(
+ DayTransform().strict_project(name="name",
pred=BoundGreaterThanOrEqual(term=bound_reference_date, literal=date)),
+ GreaterThan(term="name", literal=LongLiteral(-3)),
+ )
+ _test_projection(
+ DayTransform().strict_project(name="name",
pred=BoundNotEqualTo(term=bound_reference_date, literal=date)),
+ NotEqualTo(term="name", literal=DateLiteral(-2)),
+ )
+ _test_projection(
+ lhs=DayTransform().strict_project(name="name",
pred=BoundEqualTo(term=bound_reference_date, literal=date)), rhs=None
+ )
+
+ another_date = literal("1969-12-28").to(DateType())
+ _test_projection(
+ DayTransform().strict_project(name="name",
pred=BoundNotIn(term=bound_reference_date, literals={date, another_date})),
+ NotIn(term="name", literals={LongLiteral(-2), LongLiteral(-4)}),
+ )
+ _test_projection(
+ lhs=DayTransform().strict_project(name="name",
pred=BoundIn(term=bound_reference_date, literals={date, another_date})),
+ rhs=None,
+ )
+
+
+def test_year_strict_lower_bound(bound_reference_date: BoundReference[int]) ->
None:
+ date = literal("2017-01-01").to(DateType())
+ _test_projection(
+ YearTransform().strict_project(name="name",
pred=BoundLessThan(term=bound_reference_date, literal=date)),
+ LessThan(term="name", literal=DateLiteral(47)),
+ )
+ _test_projection(
+ YearTransform().strict_project(name="name",
pred=BoundLessThanOrEqual(term=bound_reference_date, literal=date)),
+ LessThan(term="name", literal=LongLiteral(47)),
+ )
+ _test_projection(
+ YearTransform().strict_project(name="name",
pred=BoundGreaterThan(term=bound_reference_date, literal=date)),
+ GreaterThan(term="name", literal=DateLiteral(47)),
+ )
+ _test_projection(
+ YearTransform().strict_project(name="name",
pred=BoundGreaterThanOrEqual(term=bound_reference_date, literal=date)),
+ GreaterThan(term="name", literal=LongLiteral(46)),
+ )
+ _test_projection(
+ YearTransform().strict_project(name="name",
pred=BoundNotEqualTo(term=bound_reference_date, literal=date)),
+ NotEqualTo(term="name", literal=DateLiteral(47)),
+ )
+ _test_projection(
+ lhs=YearTransform().strict_project(name="name",
pred=BoundEqualTo(term=bound_reference_date, literal=date)), rhs=None
+ )
+
+ another_date = literal("2016-12-31").to(DateType())
+ _test_projection(
+ YearTransform().strict_project(name="name",
pred=BoundNotIn(term=bound_reference_date, literals={date, another_date})),
+ NotIn(term="name", literals={LongLiteral(46), LongLiteral(47)}),
+ )
+ _test_projection(
+ lhs=YearTransform().strict_project(name="name",
pred=BoundIn(term=bound_reference_date, literals={date, another_date})),
+ rhs=None,
+ )
+
+
+def test_negative_year_strict_lower_bound(bound_reference_date:
BoundReference[int]) -> None:
+ date = literal("1970-01-01").to(DateType())
+ _test_projection(
+ YearTransform().strict_project(name="name",
pred=BoundLessThan(term=bound_reference_date, literal=date)),
+ LessThan(term="name", literal=DateLiteral(0)),
+ )
+ _test_projection(
+ YearTransform().strict_project(name="name",
pred=BoundLessThanOrEqual(term=bound_reference_date, literal=date)),
+ LessThan(term="name", literal=LongLiteral(0)),
+ )
+ _test_projection(
+ YearTransform().strict_project(name="name",
pred=BoundGreaterThan(term=bound_reference_date, literal=date)),
+ GreaterThan(term="name", literal=DateLiteral(0)),
+ )
+ _test_projection(
+ YearTransform().strict_project(name="name",
pred=BoundGreaterThanOrEqual(term=bound_reference_date, literal=date)),
+ GreaterThan(term="name", literal=LongLiteral(-1)),
+ )
+ _test_projection(
+ YearTransform().strict_project(name="name",
pred=BoundNotEqualTo(term=bound_reference_date, literal=date)),
+ NotEqualTo(term="name", literal=DateLiteral(0)),
+ )
+ _test_projection(
+ lhs=YearTransform().strict_project(name="name",
pred=BoundEqualTo(term=bound_reference_date, literal=date)), rhs=None
+ )
+
+ another_date = literal("1969-12-31").to(DateType())
+ _test_projection(
+ YearTransform().strict_project(name="name",
pred=BoundNotIn(term=bound_reference_date, literals={date, another_date})),
+ NotIn(term="name", literals={LongLiteral(-1), LongLiteral(0)}),
+ )
+ _test_projection(
+ lhs=YearTransform().strict_project(name="name",
pred=BoundIn(term=bound_reference_date, literals={date, another_date})),
+ rhs=None,
+ )
+
+
+def test_year_strict_upper_bound(bound_reference_date: BoundReference[int]) ->
None:
+ date = literal("2017-12-31").to(DateType())
+ _test_projection(
+ YearTransform().strict_project(name="name",
pred=BoundLessThan(term=bound_reference_date, literal=date)),
+ LessThan(term="name", literal=DateLiteral(47)),
+ )
+ _test_projection(
+ YearTransform().strict_project(name="name",
pred=BoundLessThanOrEqual(term=bound_reference_date, literal=date)),
+ LessThan(term="name", literal=LongLiteral(48)),
+ )
+ _test_projection(
+ YearTransform().strict_project(name="name",
pred=BoundGreaterThan(term=bound_reference_date, literal=date)),
+ GreaterThan(term="name", literal=DateLiteral(47)),
+ )
+ _test_projection(
+ YearTransform().strict_project(name="name",
pred=BoundGreaterThanOrEqual(term=bound_reference_date, literal=date)),
+ GreaterThan(term="name", literal=LongLiteral(47)),
+ )
+ _test_projection(
+ YearTransform().strict_project(name="name",
pred=BoundNotEqualTo(term=bound_reference_date, literal=date)),
+ NotEqualTo(term="name", literal=DateLiteral(47)),
+ )
+ _test_projection(
+ lhs=YearTransform().strict_project(name="name",
pred=BoundEqualTo(term=bound_reference_date, literal=date)), rhs=None
+ )
+
+ another_date = literal("2016-01-01").to(DateType())
+ _test_projection(
+ YearTransform().strict_project(name="name",
pred=BoundNotIn(term=bound_reference_date, literals={date, another_date})),
+ NotIn(term="name", literals={LongLiteral(46), LongLiteral(47)}),
+ )
+ _test_projection(
+ lhs=YearTransform().strict_project(name="name",
pred=BoundIn(term=bound_reference_date, literals={date, another_date})),
+ rhs=None,
+ )
+
+
+def test_negative_year_strict_upper_bound(bound_reference_date:
BoundReference[int]) -> None:
+ date = literal("1969-12-31").to(DateType())
+ _test_projection(
+ YearTransform().strict_project(name="name",
pred=BoundLessThan(term=bound_reference_date, literal=date)),
+ LessThan(term="name", literal=DateLiteral(-1)),
+ )
+ _test_projection(
+ YearTransform().strict_project(name="name",
pred=BoundLessThanOrEqual(term=bound_reference_date, literal=date)),
+ LessThan(term="name", literal=LongLiteral(0)),
+ )
+ _test_projection(
+ YearTransform().strict_project(name="name",
pred=BoundGreaterThan(term=bound_reference_date, literal=date)),
+ GreaterThan(term="name", literal=DateLiteral(-1)),
+ )
+ _test_projection(
+ YearTransform().strict_project(name="name",
pred=BoundGreaterThanOrEqual(term=bound_reference_date, literal=date)),
+ GreaterThan(term="name", literal=LongLiteral(-1)),
+ )
+ _test_projection(
+ YearTransform().strict_project(name="name",
pred=BoundNotEqualTo(term=bound_reference_date, literal=date)),
+ NotEqualTo(term="name", literal=DateLiteral(-1)),
+ )
+ _test_projection(
+ lhs=YearTransform().strict_project(name="name",
pred=BoundEqualTo(term=bound_reference_date, literal=date)), rhs=None
+ )
+
+ another_date = literal("1970-01-01").to(DateType())
+ _test_projection(
+ YearTransform().strict_project(name="name",
pred=BoundNotIn(term=bound_reference_date, literals={date, another_date})),
+ NotIn(term="name", literals={LongLiteral(-1), LongLiteral(0)}),
+ )
+ _test_projection(
+ lhs=YearTransform().strict_project(name="name",
pred=BoundIn(term=bound_reference_date, literals={date, another_date})),
+ rhs=None,
+ )
+
+
+def test_strict_bucket_integer(bound_reference_long: BoundReference[int]) ->
None:
+ value = literal(100)
+ transform: Transform[Any, int] = BucketTransform(num_buckets=10)
+ _test_projection(
+ lhs=transform.strict_project(name="name",
pred=BoundNotEqualTo(term=bound_reference_long, literal=value)),
+ rhs=LessThan(term="name", literal=literal(6)),
+ )
+ _test_projection(
+ lhs=transform.strict_project(name="name",
pred=BoundEqualTo(term=bound_reference_long, literal=value)), rhs=None
+ )
+ _test_projection(
+ lhs=transform.strict_project(name="name",
pred=BoundLessThan(term=bound_reference_long, literal=value)), rhs=None
+ )
+ _test_projection(
+ lhs=transform.strict_project(name="name",
pred=BoundLessThanOrEqual(term=bound_reference_long, literal=value)), rhs=None
+ )
+ _test_projection(
+ lhs=transform.strict_project(name="name",
pred=BoundGreaterThan(term=bound_reference_long, literal=value)), rhs=None
+ )
+ _test_projection(
+ lhs=transform.strict_project(name="name",
pred=BoundGreaterThanOrEqual(term=bound_reference_long, literal=value)),
+ rhs=None,
+ )
+ _test_projection(
+ lhs=transform.strict_project(
+ name="name", pred=BoundNotIn(term=bound_reference_long,
literals={literal(100 - 1), value, literal(100 + 1)})
+ ),
+ rhs=NotIn(term=Reference("name"), literals={6, 7, 8}),
+ )
+
+ _test_projection(
+ lhs=transform.strict_project(
+ name="name", pred=BoundIn(term=bound_reference_long,
literals={literal(100 - 1), value, literal(100 + 1)})
+ ),
+ rhs=None,
+ )
+
+
+def test_strict_bucket_decimal(bound_reference_decimal: BoundReference[int])
-> None:
+ value = literal(Decimal("100.00"))
+ transform: Transform[Any, int] = BucketTransform(num_buckets=10)
+ _test_projection(
+ lhs=transform.strict_project(name="name",
pred=BoundNotEqualTo(term=bound_reference_decimal, literal=value)),
+ rhs=LessThan(term="name", literal=literal(2)),
+ )
+ _test_projection(
+ lhs=transform.strict_project(name="name",
pred=BoundEqualTo(term=bound_reference_decimal, literal=value)), rhs=None
+ )
+ _test_projection(
+ lhs=transform.strict_project(name="name",
pred=BoundLessThan(term=bound_reference_decimal, literal=value)), rhs=None
+ )
+ _test_projection(
+ lhs=transform.strict_project(name="name",
pred=BoundLessThanOrEqual(term=bound_reference_decimal, literal=value)),
+ rhs=None,
+ )
+ _test_projection(
+ lhs=transform.strict_project(name="name",
pred=BoundGreaterThan(term=bound_reference_decimal, literal=value)), rhs=None
+ )
+ _test_projection(
+ lhs=transform.strict_project(name="name",
pred=BoundGreaterThanOrEqual(term=bound_reference_decimal, literal=value)),
+ rhs=None,
+ )
+ _test_projection(
+ lhs=transform.strict_project(
+ name="name",
+ pred=BoundNotIn(
+ term=bound_reference_decimal,
literals={literal(Decimal("99.00")), value, literal(Decimal("101.00"))}
+ ),
+ ),
+ rhs=NotIn(term=Reference("name"), literals={2, 6}),
+ )
+ _test_projection(
+ lhs=transform.strict_project(
+ name="name",
+ pred=BoundIn(term=bound_reference_decimal,
literals={literal(Decimal("99.00")), value, literal(Decimal("101.00"))}),
+ ),
+ rhs=None,
+ )
+
+
+def test_strict_bucket_string(bound_reference_str: BoundReference[int]) ->
None:
+ value = literal("abcdefg")
+ transform: Transform[Any, int] = BucketTransform(num_buckets=10)
+ _test_projection(
+ lhs=transform.strict_project(name="name",
pred=BoundNotEqualTo(term=bound_reference_str, literal=value)),
+ rhs=LessThan(term="name", literal=literal(4)),
+ )
+ _test_projection(
+ lhs=transform.strict_project(name="name",
pred=BoundEqualTo(term=bound_reference_str, literal=value)), rhs=None
+ )
+ _test_projection(
+ lhs=transform.strict_project(name="name",
pred=BoundLessThan(term=bound_reference_str, literal=value)), rhs=None
+ )
+ _test_projection(
+ lhs=transform.strict_project(name="name",
pred=BoundLessThanOrEqual(term=bound_reference_str, literal=value)), rhs=None
+ )
+ _test_projection(
+ lhs=transform.strict_project(name="name",
pred=BoundGreaterThan(term=bound_reference_str, literal=value)), rhs=None
+ )
+ _test_projection(
+ lhs=transform.strict_project(name="name",
pred=BoundGreaterThanOrEqual(term=bound_reference_str, literal=value)), rhs=None
+ )
+ _test_projection(
+ lhs=transform.strict_project(
+ name="name", pred=BoundNotIn(term=bound_reference_str,
literals={literal("abcdefg"), literal("abcdefgabc")})
+ ),
+ rhs=NotIn(term=Reference("name"), literals={4, 9}),
+ )
+ _test_projection(
+ lhs=transform.strict_project(
+ name="name", pred=BoundIn(term=bound_reference_str,
literals={literal("abcdefg"), literal("abcdefgabc")})
+ ),
+ rhs=None,
+ )
+
+
+def test_strict_bucket_bytes(bound_reference_binary: BoundReference[int]) ->
None:
+ value = literal(str.encode("abcdefg"))
+ transform: Transform[Any, int] = BucketTransform(num_buckets=10)
+ _test_projection(
+ lhs=transform.strict_project(name="name",
pred=BoundNotEqualTo(term=bound_reference_binary, literal=value)),
+ rhs=LessThan(term="name", literal=literal(4)),
+ )
+ _test_projection(
+ lhs=transform.strict_project(name="name",
pred=BoundEqualTo(term=bound_reference_binary, literal=value)), rhs=None
+ )
+ _test_projection(
+ lhs=transform.strict_project(name="name",
pred=BoundLessThan(term=bound_reference_binary, literal=value)), rhs=None
+ )
+ _test_projection(
+ lhs=transform.strict_project(name="name",
pred=BoundLessThanOrEqual(term=bound_reference_binary, literal=value)), rhs=None
+ )
+ _test_projection(
+ lhs=transform.strict_project(name="name",
pred=BoundGreaterThan(term=bound_reference_binary, literal=value)), rhs=None
+ )
+ _test_projection(
+ lhs=transform.strict_project(name="name",
pred=BoundGreaterThanOrEqual(term=bound_reference_binary, literal=value)),
+ rhs=None,
+ )
+ _test_projection(
+ lhs=transform.strict_project(
+ name="name", pred=BoundNotIn(term=bound_reference_binary,
literals={value, literal(str.encode("abcdehij"))})
+ ),
+ rhs=NotIn(term=Reference("name"), literals={4, 6}),
+ )
+ _test_projection(
+ lhs=transform.strict_project(
+ name="name", pred=BoundIn(term=bound_reference_binary,
literals={value, literal(str.encode("abcdehij"))})
+ ),
+ rhs=None,
+ )
+
+
+def test_strict_bucket_uuid(bound_reference_uuid: BoundReference[int]) -> None:
+ value = literal(UUID('12345678123456781234567812345678'))
+ transform: Transform[Any, int] = BucketTransform(num_buckets=10)
+ _test_projection(
+ lhs=transform.strict_project(name="name",
pred=BoundNotEqualTo(term=bound_reference_uuid, literal=value)),
+ rhs=LessThan(term="name", literal=literal(1)),
+ )
+ _test_projection(
+ lhs=transform.strict_project(name="name",
pred=BoundEqualTo(term=bound_reference_uuid, literal=value)), rhs=None
+ )
+ _test_projection(
+ lhs=transform.strict_project(name="name",
pred=BoundLessThan(term=bound_reference_uuid, literal=value)), rhs=None
+ )
+ _test_projection(
+ lhs=transform.strict_project(name="name",
pred=BoundLessThanOrEqual(term=bound_reference_uuid, literal=value)), rhs=None
+ )
+ _test_projection(
+ lhs=transform.strict_project(name="name",
pred=BoundGreaterThan(term=bound_reference_uuid, literal=value)), rhs=None
+ )
+ _test_projection(
+ lhs=transform.strict_project(name="name",
pred=BoundGreaterThanOrEqual(term=bound_reference_uuid, literal=value)),
+ rhs=None,
+ )
+ _test_projection(
+ lhs=transform.strict_project(
+ name="name",
+ pred=BoundNotIn(term=bound_reference_uuid, literals={value,
literal(UUID('12345678123456781234567812345679'))}),
+ ),
+ rhs=NotIn(term=Reference("name"), literals={1, 4}),
+ )
+ _test_projection(
+ lhs=transform.strict_project(
+ name="name",
+ pred=BoundIn(term=bound_reference_uuid, literals={value,
literal(UUID('12345678123456781234567812345679'))}),
+ ),
+ rhs=None,
+ )
+
+
+def test_strict_identity_projection(bound_reference_long: BoundReference[int])
-> None:
+ transform: Transform[Any, Any] = IdentityTransform()
+ predicates = [
+ BoundNotNull(term=bound_reference_long),
+ BoundIsNull(term=bound_reference_long),
+ BoundLessThan(term=bound_reference_long, literal=literal(100)),
+ BoundLessThanOrEqual(term=bound_reference_long, literal=literal(101)),
+ BoundGreaterThan(term=bound_reference_long, literal=literal(102)),
+ BoundGreaterThanOrEqual(term=bound_reference_long,
literal=literal(103)),
+ BoundEqualTo(term=bound_reference_long, literal=literal(104)),
+ BoundNotEqualTo(term=bound_reference_long, literal=literal(105)),
+ ]
+ for predicate in predicates:
+ if isinstance(predicate, BoundLiteralPredicate):
+ _test_projection(
+ lhs=transform.strict_project(
+ name="name",
+ pred=predicate,
+ ),
+ rhs=predicate.as_unbound(term=Reference("name"),
literal=predicate.literal),
+ )
+ else:
+ _test_projection(
+ lhs=transform.strict_project(
+ name="name",
+ pred=predicate,
+ ),
+ rhs=predicate.as_unbound(term=Reference("name")),
+ )
+
+
+def test_truncate_strict_integer_lower_bound(bound_reference_long:
BoundReference[int]) -> None:
+ value = literal(100)
+ transform: Transform[Any, Any] = TruncateTransform(width=10)
+
+ _test_projection(
+ lhs=transform.strict_project(name="name",
pred=BoundLessThan(term=bound_reference_long, literal=value)),
+ rhs=LessThan(term=Reference("name"), literal=LongLiteral(100)),
+ )
+ _test_projection(
+ lhs=transform.strict_project(name="name",
pred=BoundLessThanOrEqual(term=bound_reference_long, literal=value)),
+ rhs=LessThanOrEqual(term=Reference("name"), literal=LongLiteral(100)),
+ )
+ _test_projection(
+ lhs=transform.strict_project(name="name",
pred=BoundGreaterThan(term=bound_reference_long, literal=value)),
+ rhs=GreaterThan(term=Reference("name"), literal=LongLiteral(100)),
+ )
+ _test_projection(
+ lhs=transform.strict_project(name="name",
pred=BoundGreaterThanOrEqual(term=bound_reference_long, literal=value)),
+ rhs=GreaterThan(term=Reference("name"), literal=LongLiteral(90)),
+ )
+ _test_projection(
+ lhs=transform.strict_project(
+ name="name", pred=BoundNotIn(term=bound_reference_long,
literals={literal(99), literal(100), literal(101)})
+ ),
+ rhs=NotIn(term=Reference("name"), literals={literal(90),
literal(100)}),
+ )
+ _test_projection(
+ lhs=transform.strict_project(
+ name="name", pred=BoundIn(term=bound_reference_long,
literals={literal(99), literal(100), literal(101)})
+ ),
+ rhs=None,
+ )
+
+
+def test_truncate_strict_integer_upper_bound(bound_reference_long:
BoundReference[int]) -> None:
+ value = literal(99)
+ transform: Transform[Any, Any] = TruncateTransform(width=10)
+
+ _test_projection(
+ lhs=transform.strict_project(name="name",
pred=BoundLessThan(term=bound_reference_long, literal=value)),
+ rhs=LessThan(term=Reference("name"), literal=LongLiteral(90)),
+ )
+ _test_projection(
+ lhs=transform.strict_project(name="name",
pred=BoundLessThanOrEqual(term=bound_reference_long, literal=value)),
+ rhs=LessThan(term=Reference("name"), literal=LongLiteral(100)),
+ )
+ _test_projection(
+ lhs=transform.strict_project(name="name",
pred=BoundGreaterThan(term=bound_reference_long, literal=value)),
+ rhs=GreaterThan(term=Reference("name"), literal=LongLiteral(90)),
+ )
+ _test_projection(
+ lhs=transform.strict_project(name="name",
pred=BoundGreaterThanOrEqual(term=bound_reference_long, literal=value)),
+ rhs=GreaterThan(term=Reference("name"), literal=LongLiteral(90)),
+ )
+ _test_projection(
+ lhs=transform.strict_project(
+ name="name", pred=BoundNotIn(term=bound_reference_long,
literals={literal(99), literal(100), literal(101)})
+ ),
+ rhs=NotIn(term=Reference("name"), literals={literal(90),
literal(100)}),
+ )
+ _test_projection(
+ lhs=transform.strict_project(
+ name="name", pred=BoundIn(term=bound_reference_long,
literals={literal(99), literal(100), literal(101)})
+ ),
+ rhs=None,
+ )
+
+
+def test_truncate_strict_decimal_lower_bound(bound_reference_decimal:
BoundReference[Decimal]) -> None:
+ value = literal(Decimal("100.00"))
+ transform: Transform[Any, Any] = TruncateTransform(width=10)
+
+ _test_projection(
+ lhs=transform.strict_project(name="name",
pred=BoundLessThan(term=bound_reference_decimal, literal=value)),
+ rhs=LessThan(term=Reference("name"), literal=Decimal("100.00")),
+ )
+ _test_projection(
+ lhs=transform.strict_project(name="name",
pred=BoundLessThanOrEqual(term=bound_reference_decimal, literal=value)),
+ rhs=LessThanOrEqual(term=Reference("name"), literal=Decimal("100.00")),
+ )
+ _test_projection(
+ lhs=transform.strict_project(name="name",
pred=BoundGreaterThan(term=bound_reference_decimal, literal=value)),
+ rhs=GreaterThan(term=Reference("name"), literal=Decimal("100.00")),
+ )
+ _test_projection(
+ lhs=transform.strict_project(name="name",
pred=BoundGreaterThanOrEqual(term=bound_reference_decimal, literal=value)),
+ rhs=GreaterThan(term=Reference("name"), literal=Decimal("99.90")),
+ )
+ set_of_literals = {literal(Decimal("99.00")), literal(Decimal("100.00")),
literal(Decimal("101.00"))}
+ _test_projection(
+ lhs=transform.strict_project(name="name",
pred=BoundNotIn(term=bound_reference_decimal, literals=set_of_literals)),
+ rhs=NotIn(term=Reference("name"), literals=set_of_literals),
+ )
+ _test_projection(
+ lhs=transform.strict_project(name="name",
pred=BoundIn(term=bound_reference_decimal, literals=set_of_literals)), rhs=None
+ )
+
+
+def test_truncate_strict_decimal_upper_bound(bound_reference_decimal:
BoundReference[Decimal]) -> None:
+ value = literal(Decimal("99.99"))
+ transform: Transform[Any, Any] = TruncateTransform(width=10)
+
+ _test_projection(
+ lhs=transform.strict_project(name="name",
pred=BoundLessThan(term=bound_reference_decimal, literal=value)),
+ rhs=LessThan(term=Reference("name"), literal=Decimal("99.90")),
+ )
+ _test_projection(
+ lhs=transform.strict_project(name="name",
pred=BoundLessThanOrEqual(term=bound_reference_decimal, literal=value)),
+ rhs=LessThan(term=Reference("name"), literal=Decimal("100.00")),
+ )
+ _test_projection(
+ lhs=transform.strict_project(name="name",
pred=BoundGreaterThan(term=bound_reference_decimal, literal=value)),
+ rhs=GreaterThan(term=Reference("name"), literal=Decimal("99.90")),
+ )
+ _test_projection(
+ lhs=transform.strict_project(name="name",
pred=BoundGreaterThanOrEqual(term=bound_reference_decimal, literal=value)),
+ rhs=GreaterThan(term=Reference("name"), literal=Decimal("99.90")),
+ )
+ set_of_literals = {literal(Decimal("98.99")), literal(Decimal("99.99")),
literal(Decimal("100.99"))}
+ _test_projection(
+ lhs=transform.strict_project(name="name",
pred=BoundNotIn(term=bound_reference_decimal, literals=set_of_literals)),
+ rhs=NotIn(
+ term=Reference("name"), literals={literal(Decimal("98.90")),
literal(Decimal("99.90")), literal(Decimal("100.90"))}
+ ),
+ )
+ _test_projection(
+ lhs=transform.strict_project(name="name",
pred=BoundIn(term=bound_reference_decimal, literals=set_of_literals)), rhs=None
+ )
+
+
+def test_string_strict(bound_reference_str: BoundReference[str]) -> None:
+ value = literal("abcdefg")
+ transform: Transform[Any, Any] = TruncateTransform(width=5)
+
+ _test_projection(
+ lhs=transform.strict_project(name="name",
pred=BoundLessThan(term=bound_reference_str, literal=value)),
+ rhs=LessThan(term=Reference("name"), literal=literal("abcde")),
+ )
+ _test_projection(
+ lhs=transform.strict_project(name="name",
pred=BoundLessThanOrEqual(term=bound_reference_str, literal=value)),
+ rhs=LessThan(term=Reference("name"), literal=literal("abcde")),
+ )
+ _test_projection(
+ lhs=transform.strict_project(name="name",
pred=BoundGreaterThan(term=bound_reference_str, literal=value)),
+ rhs=GreaterThan(term=Reference("name"), literal=literal("abcde")),
+ )
+ _test_projection(
+ lhs=transform.strict_project(name="name",
pred=BoundGreaterThanOrEqual(term=bound_reference_str, literal=value)),
+ rhs=GreaterThan(term=Reference("name"), literal=literal("abcde")),
+ )
+ set_of_literals = {literal("abcde"), literal("abcdefg")}
+ _test_projection(
+ lhs=transform.strict_project(name="name",
pred=BoundNotIn(term=bound_reference_str, literals=set_of_literals)),
+ rhs=NotEqualTo(term=Reference("name"), literal=literal("abcde")),
+ )
+ _test_projection(
+ lhs=transform.strict_project(name="name",
pred=BoundIn(term=bound_reference_str, literals=set_of_literals)), rhs=None
+ )
+
+
+def test_strict_binary(bound_reference_binary: BoundReference[str]) -> None:
+ value = literal(b"abcdefg")
+ transform: Transform[Any, Any] = TruncateTransform(width=5)
+ abcde = literal(b"abcde")
+
+ _test_projection(
+ lhs=transform.strict_project(name="name",
pred=BoundLessThan(term=bound_reference_binary, literal=value)),
+ rhs=LessThan(term=Reference("name"), literal=abcde),
+ )
+ _test_projection(
+ lhs=transform.strict_project(name="name",
pred=BoundLessThanOrEqual(term=bound_reference_binary, literal=value)),
+ rhs=LessThan(term=Reference("name"), literal=abcde),
+ )
+ _test_projection(
+ lhs=transform.strict_project(name="name",
pred=BoundGreaterThan(term=bound_reference_binary, literal=value)),
+ rhs=GreaterThan(term=Reference("name"), literal=abcde),
+ )
+ _test_projection(
+ lhs=transform.strict_project(name="name",
pred=BoundGreaterThanOrEqual(term=bound_reference_binary, literal=value)),
+ rhs=GreaterThan(term=Reference("name"), literal=abcde),
+ )
+ set_of_literals = {literal(b"abcde"), literal(b"abcdefg")}
+ _test_projection(
+ lhs=transform.strict_project(name="name",
pred=BoundNotIn(term=bound_reference_binary, literals=set_of_literals)),
+ rhs=NotEqualTo(term=Reference("name"), literal=abcde),
+ )
+ _test_projection(
+ lhs=transform.strict_project(name="name",
pred=BoundIn(term=bound_reference_binary, literals=set_of_literals)), rhs=None
+ )