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

79 lines
2.7 KiB
Python

import pyarrow as pa
import pytest
import ray
def test_union_basic(ray_start_10_cpus_shared):
ds1 = ray.data.from_items([{"id": 0}])
ds2 = ray.data.from_items([{"id": 1}])
ds = ds1.union(ds2)
assert sorted(row["id"] for row in ds.take_all()) == [0, 1]
def test_union_schema(ray_start_10_cpus_shared):
ds = ray.data.range(1).union(ray.data.range(1))
assert ds.schema().names == ["id"]
assert ds.schema().types == [pa.int64()]
def test_union_repr(ray_start_10_cpus_shared):
ds = ray.data.range(1).union(ray.data.range(1))
assert repr(ds) == (
"shape: (?, 1)\n"
"╭───────╮\n"
"│ id │\n"
"│ --- │\n"
"│ int64 │\n"
"╰───────╯\n"
"(Dataset isn't materialized)"
)
def test_union_with_preserve_order(ray_start_10_cpus_shared, restore_data_context):
# Test for https://github.com/ray-project/ray/issues/41524
ctx = ray.data.DataContext.get_current()
ctx.execution_options.preserve_order = True
# To reproduce the bug, you need three or more input datasets, and the first dataset
# can't finish first. To emulate this behavior, the test adds a `map` to the first
# dataset and materializes the other two.
ds1 = ray.data.from_items([{"id": 0}]).map(lambda x: x)
ds2 = ray.data.from_items([{"id": 1}]).materialize()
ds3 = ray.data.from_items([{"id": 2}]).materialize()
ds = ds1.union(ds2, ds3)
assert [row["id"] for row in ds.take_all()] == [0, 1, 2]
def test_union_with_filter(ray_start_10_cpus_shared):
"""Test that filters are pushed through union to both branches."""
from ray.data._internal.logical.optimizers import LogicalOptimizer
from ray.data.expressions import col
ds1 = ray.data.from_items([{"id": 0}, {"id": 1}, {"id": 2}])
ds2 = ray.data.from_items([{"id": 3}, {"id": 4}, {"id": 5}])
ds = ds1.union(ds2).filter(expr=col("id") > 2)
# Verify the filter was pushed through the union
optimized_plan = LogicalOptimizer().optimize(ds._logical_plan)
actual_plan_str = optimized_plan.dag.dag_str
# After optimization, filter should be pushed to both union branches
# So we should see: Filter(Read), Filter(Read) -> Union
# Not: Read, Read -> Union -> Filter
assert "Union" in actual_plan_str
assert "Filter" in actual_plan_str
# Ensure Filter is before Union (pushed down), not after
assert actual_plan_str.index("Filter") < actual_plan_str.index("Union")
# Verify correctness
result = sorted(row["id"] for row in ds.take_all())
assert result == [3, 4, 5]
if __name__ == "__main__":
import sys
sys.exit(pytest.main(["-v", __file__]))