Files
2026-07-13 13:17:40 +08:00

127 lines
3.8 KiB
Python

from collections import Counter
import pyarrow as pa
import pyarrow.compute as pc
import pytest
from ray.data._internal.planner.plan_expression.expression_visitors import (
_ColumnReferenceCollector,
_StructuralFingerprintOccurrenceCollector,
_StructuralFingerprintVisitor,
)
from ray.data.datatype import DataType
from ray.data.expressions import (
AliasExpr,
BinaryExpr,
ColumnExpr,
PyArrowComputeUDFExpr,
UDFExpr,
col,
lit,
monotonically_increasing_id,
random,
udf,
uuid,
)
@udf(return_dtype=DataType.int64())
def add_one(x: pa.Array) -> pa.Array:
return pc.add(x, 1)
def _fingerprint(expr):
return _StructuralFingerprintVisitor().visit(expr)
def test_structural_fingerprint_matches_structural_equality():
expr = (add_one(col("a")) + lit(1)).alias("result")
equivalent_expr = (add_one(col("a")) + lit(1)).alias("result")
different_child_expr = (add_one(col("b")) + lit(1)).alias("result")
different_alias_expr = (add_one(col("a")) + lit(1)).alias("other")
assert expr.structurally_equals(equivalent_expr)
assert _fingerprint(expr) == _fingerprint(equivalent_expr)
assert _fingerprint(expr) != _fingerprint(different_child_expr)
assert _fingerprint(expr) != _fingerprint(different_alias_expr)
def test_structural_fingerprint_handles_pyarrow_compute_udfs():
expr = col("a").abs()
equivalent_expr = col("a").abs()
different_child_expr = col("b").abs()
assert isinstance(expr, PyArrowComputeUDFExpr)
assert expr.structurally_equals(equivalent_expr)
assert _fingerprint(expr) == _fingerprint(equivalent_expr)
assert _fingerprint(expr) != _fingerprint(different_child_expr)
def test_occurrence_collector_records_bottom_up_keys_and_depths():
expr = (add_one(col("a")) + add_one(col("a"))).alias("result")
collector = _StructuralFingerprintOccurrenceCollector()
root_key = collector.visit(expr)
occurrences = collector.get_occurrences()
assert root_key == _fingerprint(expr)
assert [type(occurrence.expr) for occurrence in occurrences] == [
ColumnExpr,
UDFExpr,
ColumnExpr,
UDFExpr,
BinaryExpr,
AliasExpr,
]
assert [occurrence.depth for occurrence in occurrences] == [3, 2, 3, 2, 1, 0]
assert all(
occurrence.key == _fingerprint(occurrence.expr) for occurrence in occurrences
)
udf_occurrences = [
occurrence for occurrence in occurrences if isinstance(occurrence.expr, UDFExpr)
]
assert len(udf_occurrences) == 2
assert udf_occurrences[0].key == udf_occurrences[1].key
assert udf_occurrences[0].expr.structurally_equals(udf_occurrences[1].expr)
@pytest.mark.parametrize(
"expr,expected",
[
# idempotent leaves and composites
(col("a"), True),
(lit(1), True),
(col("a") + lit(1), True),
(add_one(col("a")), True),
(add_one(col("a")).alias("y"), True),
# non-idempotent leaves
(random(), False),
(random(seed=42), False),
(uuid(), False),
(monotonically_increasing_id(), False),
# non-idempotency propagates through composites
(random() + lit(1), False),
((col("a") + uuid()).alias("x"), False),
(add_one(monotonically_increasing_id()), False),
],
)
def test_is_idempotent(expr, expected):
assert expr.is_idempotent() is expected
def test_column_reference_collector_counts_multiplicity():
collector = _ColumnReferenceCollector()
collector.visit(col("x") + col("x") + col("y"))
# get_counts() counts repeats within a single expression...
assert collector.get_counts() == Counter({"x": 2, "y": 1})
# ...while get_column_refs() stays ordered and de-duplicated.
assert collector.get_column_refs() == ["x", "y"]
if __name__ == "__main__":
import sys
sys.exit(pytest.main(["-v", __file__]))