chore: import upstream snapshot with attribution

This commit is contained in:
wehub-resource-sync
2026-07-13 13:17:40 +08:00
commit f1825c8ceb
10096 changed files with 2364182 additions and 0 deletions
@@ -0,0 +1,657 @@
import sys
from typing import Any, Dict, List
import pandas as pd
import pytest
import ray
from ray.data import Dataset
from ray.data._internal.logical.interfaces import LogicalOperator, Plan
from ray.data._internal.logical.operators import Download, Limit
from ray.data._internal.logical.rules.limit_pushdown import LimitPushdownRule
from ray.data._internal.util import rows_same
from ray.data.block import BlockMetadata
from ray.data.datasource import Datasource
from ray.data.datasource.datasource import ReadTask
from ray.data.tests.conftest import * # noqa
from ray.tests.conftest import * # noqa
def _check_valid_plan_and_result(
ds: Dataset,
expected_plan: Plan,
expected_result: List[Dict[str, Any]],
expected_physical_plan_ops=None,
check_ordering=True,
):
actual_result = ds.take_all()
if check_ordering:
assert actual_result == expected_result
else:
assert rows_same(pd.DataFrame(actual_result), pd.DataFrame(expected_result))
assert ds._logical_plan.dag.dag_str == expected_plan
expected_physical_plan_ops = expected_physical_plan_ops or []
for op in expected_physical_plan_ops:
assert op in ds.stats(), f"Operator {op} not found: {ds.stats()}"
class _DummyLogicalOperator(LogicalOperator):
def __init__(self, input_dependencies, name=None):
object.__setattr__(self, "_input_dependencies", input_dependencies)
if name is not None:
object.__setattr__(self, "_name", name)
def test_limit_pushdown_recreates_frozen_download():
input_op = _DummyLogicalOperator(input_dependencies=[], name="DummyInput")
download_op = Download(
uri_column_names=["uri"],
output_bytes_column_names=["bytes"],
input_dependencies=[input_op],
)
limit_op = Limit(1, input_dependencies=[download_op])
result = LimitPushdownRule()._push_limit_down(limit_op)
assert isinstance(result, Download)
assert isinstance(result.input_dependencies[0], Limit)
assert result.input_dependencies[0].limit == 1
assert result.input_dependencies[0].input_dependencies[0] is input_op
def test_limit_pushdown_basic_limit_fusion(ray_start_regular_shared_2_cpus):
"""Test basic Limit -> Limit fusion."""
# Use override_num_blocks=1 for deterministic row ordering.
ds = ray.data.range(100, override_num_blocks=1).limit(5).limit(100)
_check_valid_plan_and_result(
ds,
"Read[ReadRange] -> Limit[limit=5]",
[{"id": i} for i in range(5)],
check_ordering=False,
)
def test_limit_pushdown_limit_fusion_reversed(ray_start_regular_shared_2_cpus):
"""Test Limit fusion with reversed order."""
# Use override_num_blocks=1 for deterministic row ordering.
ds = ray.data.range(100, override_num_blocks=1).limit(100).limit(5)
_check_valid_plan_and_result(
ds,
"Read[ReadRange] -> Limit[limit=5]",
[{"id": i} for i in range(5)],
check_ordering=False,
)
def test_limit_pushdown_multiple_limit_fusion(ray_start_regular_shared_2_cpus):
"""Test multiple Limit operations fusion."""
# Use override_num_blocks=1 for deterministic row ordering.
ds = (
ray.data.range(100, override_num_blocks=1)
.limit(50)
.limit(80)
.limit(5)
.limit(20)
)
_check_valid_plan_and_result(
ds,
"Read[ReadRange] -> Limit[limit=5]",
[{"id": i} for i in range(5)],
check_ordering=False,
)
def test_limit_pushdown_through_maprows(ray_start_regular_shared_2_cpus):
"""Test that Limit pushes through MapRows operations."""
def f1(x):
return x
ds = ray.data.range(100, override_num_blocks=100).map(f1).limit(1)
_check_valid_plan_and_result(
ds,
"Read[ReadRange] -> Limit[limit=1] -> MapRows[Map(f1)]",
[{"id": 0}],
check_ordering=False,
)
def test_limit_pushdown_through_mapbatches(ray_start_regular_shared_2_cpus):
"""Test that Limit pushes through MapBatches operations."""
def f2(x):
return x
ds = (
ray.data.range(100, override_num_blocks=100)
.map_batches(f2, udf_modifying_row_count=False)
.limit(1)
)
_check_valid_plan_and_result(
ds,
"Read[ReadRange] -> Limit[limit=1] -> MapBatches[MapBatches(f2)]",
[{"id": 0}],
check_ordering=False,
)
def test_limit_pushdown_stops_at_filter(ray_start_regular_shared_2_cpus):
"""Test that Limit does NOT push through Filter operations (conservative)."""
ds = (
ray.data.range(100, override_num_blocks=100)
.filter(lambda x: x["id"] < 50)
.limit(1)
)
_check_valid_plan_and_result(
ds,
"Read[ReadRange] -> Filter[Filter(<lambda>)] -> Limit[limit=1]",
[{"id": 0}],
check_ordering=False,
)
def test_limit_pushdown_through_project(ray_start_regular_shared_2_cpus):
"""Test that Limit pushes through Project operations."""
ds = ray.data.range(100, override_num_blocks=100).select_columns(["id"]).limit(5)
_check_valid_plan_and_result(
ds,
"Read[ReadRange] -> Limit[limit=5] -> Project[Project]",
[{"id": i} for i in range(5)],
check_ordering=False,
)
def test_limit_pushdown_stops_at_sort(ray_start_regular_shared_2_cpus):
"""Test that Limit stops at Sort operations (AllToAll)."""
ds = ray.data.range(100).sort("id").limit(5)
_check_valid_plan_and_result(
ds,
"Read[ReadRange] -> Sort[Sort] -> Limit[limit=5]",
[{"id": i} for i in range(5)],
)
def test_limit_pushdown_complex_interweaved_operations(ray_start_regular_shared_2_cpus):
"""Test Limit pushdown with complex interweaved operations."""
def f1(x):
return x
def f2(x):
return x
ds = ray.data.range(100).sort("id").map(f1).limit(20).sort("id").map(f2).limit(5)
_check_valid_plan_and_result(
ds,
"Read[ReadRange] -> Sort[Sort] -> Limit[limit=20] -> MapRows[Map(f1)] -> "
"Sort[Sort] -> Limit[limit=5] -> MapRows[Map(f2)]",
[{"id": i} for i in range(5)],
)
def test_limit_pushdown_between_two_map_operators(ray_start_regular_shared_2_cpus):
"""Test Limit pushdown between two Map operators."""
def f1(x):
return x
def f2(x):
return x
ds = ray.data.range(100, override_num_blocks=100).map(f1).limit(1).map(f2)
_check_valid_plan_and_result(
ds,
"Read[ReadRange] -> Limit[limit=1] -> MapRows[Map(f1)] -> MapRows[Map(f2)]",
[{"id": 0}],
check_ordering=False,
)
def test_limit_pushdown_correctness(ray_start_regular_shared_2_cpus):
"""Test that limit pushdown produces correct results in various scenarios."""
# Test 1: Simple project + limit
ds = ray.data.range(100).select_columns(["id"]).limit(10)
result = ds.take_all()
expected = [{"id": i} for i in range(10)]
assert result == expected
# Test 2: Multiple operations + limit (with MapRows pushdown)
ds = (
ray.data.range(100)
.map(lambda x: {"id": x["id"], "squared": x["id"] ** 2})
.select_columns(["id"])
.limit(5)
)
result = ds.take_all()
expected = [{"id": i} for i in range(5)]
assert result == expected
# Test 3: MapRows operations should get limit pushed (safe)
ds = ray.data.range(100).map(lambda x: {"id": x["id"] * 2}).limit(5)
result = ds.take_all()
expected = [{"id": i * 2} for i in range(5)]
assert result == expected
# Test 4: MapBatches operations should not get limit pushed
ds = ray.data.range(100).map_batches(lambda batch: {"id": batch["id"] * 2}).limit(5)
result = ds.take_all()
expected = [{"id": i * 2} for i in range(5)]
assert result == expected
# Test 5: Filter operations should not get limit pushed (conservative)
ds = ray.data.range(100).filter(lambda x: x["id"] % 2 == 0).limit(3)
result = ds.take_all()
expected = [{"id": i} for i in [0, 2, 4]]
assert result == expected
# Test 6: Complex chain with both safe operations (should all get limit pushed)
ds = (
ray.data.range(100)
.select_columns(["id"]) # Project - could be safe if it was the immediate input
.map(lambda x: {"id": x["id"] + 1}) # MapRows - NOT safe, stops pushdown
.limit(3)
)
result = ds.take_all()
expected = [{"id": i + 1} for i in range(3)]
assert result == expected
# The plan should show all operations after the limit
plan_str = ds._logical_plan.dag.dag_str
assert (
"Read[ReadRange] -> Limit[limit=3] -> Project[Project] -> MapRows[Map(<lambda>)]"
== plan_str
)
def test_limit_pushdown_scan_efficiency(ray_start_regular_shared_2_cpus):
"""Test that limit pushdown scans fewer rows from the data source."""
@ray.remote
class Counter:
def __init__(self):
self.value = 0
def increment(self, amount=1):
self.value += amount
return self.value
def get(self):
return self.value
def reset(self):
self.value = 0
# Create a custom datasource that tracks how many rows it produces
class CountingDatasource(Datasource):
def __init__(self):
self.counter = Counter.remote()
def prepare_read(self, parallelism, n_per_block=10):
def read_fn(block_idx):
# Each block produces n_per_block rows
ray.get(self.counter.increment.remote(n_per_block))
return [
pd.DataFrame(
{
"id": range(
block_idx * n_per_block, (block_idx + 1) * n_per_block
)
}
)
]
return [
ReadTask(
lambda i=i: read_fn(i),
BlockMetadata(
num_rows=n_per_block,
size_bytes=n_per_block * 8, # rough estimate
input_files=None,
exec_stats=None,
),
)
for i in range(parallelism)
]
def get_rows_produced(self):
return ray.get(self.counter.get.remote())
# Test 1: Project + Limit should scan fewer rows due to pushdown
source = CountingDatasource()
ds = ray.data.read_datasource(source, override_num_blocks=20, n_per_block=10)
ds = ds.select_columns(["id"]).limit(5)
result = ds.take_all()
# Should get correct results
assert len(result) == 5
assert result == [{"id": i} for i in range(5)]
# Should have scanned significantly fewer than all 200 rows (20 blocks * 10 rows)
# Due to pushdown, we should scan much less
rows_produced_1 = source.get_rows_produced()
assert rows_produced_1 < 200 # Should be much less than total
# Test 2: MapRows + Limit should also scan fewer rows due to pushdown
source2 = CountingDatasource()
ds2 = ray.data.read_datasource(source2, override_num_blocks=20, n_per_block=10)
ds2 = ds2.map(lambda x: x).limit(5)
result2 = ds2.take_all()
# Should get correct results
assert len(result2) == 5
assert result2 == [{"id": i} for i in range(5)]
# Should also scan fewer than total due to pushdown
rows_produced_2 = source2.get_rows_produced()
assert rows_produced_2 < 200
# Both should be efficient with pushdown
assert rows_produced_1 < 100 # Should be much less than total
assert rows_produced_2 < 100 # Should be much less than total
# Test 3: Filter + Limit should scan fewer due to early termination, but not pushdown
source3 = CountingDatasource()
ds3 = ray.data.read_datasource(source3, override_num_blocks=20, n_per_block=10)
ds3 = ds3.filter(lambda x: x["id"] % 2 == 0).limit(3)
result3 = ds3.take_all()
# Should get correct results
assert len(result3) == 3
assert result3 == [{"id": i} for i in [0, 2, 4]]
# Should still scan fewer than total due to early termination
rows_produced_3 = source3.get_rows_produced()
assert rows_produced_3 < 200
def test_limit_pushdown_union(ray_start_regular_shared_2_cpus):
"""Test limit pushdown behavior with Union operations."""
# Create two datasets and union with limit
ds1 = ray.data.range(100, override_num_blocks=10)
ds2 = ray.data.range(200, override_num_blocks=10)
ds = ds1.union(ds2).limit(5)
expected_plan = "Read[ReadRange] -> Limit[limit=5], Read[ReadRange] -> Limit[limit=5] -> Union[Union] -> Limit[limit=5]"
_check_valid_plan_and_result(
ds, expected_plan, [{"id": i} for i in range(5)], check_ordering=False
)
def test_limit_pushdown_union_with_maprows(ray_start_regular_shared_2_cpus):
"""Limit after Union + MapRows: limit should be pushed before the MapRows
and inside each Union branch."""
ds1 = ray.data.range(100, override_num_blocks=10)
ds2 = ray.data.range(200, override_num_blocks=10)
ds = ds1.union(ds2).map(lambda x: x).limit(5)
expected_plan = (
"Read[ReadRange] -> Limit[limit=5], "
"Read[ReadRange] -> Limit[limit=5] -> Union[Union] -> "
"Limit[limit=5] -> MapRows[Map(<lambda>)]"
)
_check_valid_plan_and_result(
ds, expected_plan, [{"id": i} for i in range(5)], check_ordering=False
)
def test_limit_pushdown_union_with_sort(ray_start_regular_shared_2_cpus):
"""Limit after Union + Sort: limit must NOT push through the Sort."""
ds1 = ray.data.range(100, override_num_blocks=4)
ds2 = ray.data.range(50, override_num_blocks=4).map(
lambda x: {"id": x["id"] + 1000}
)
ds = ds1.union(ds2).sort("id").limit(5)
expected_plan = (
"Read[ReadRange], "
"Read[ReadRange] -> MapRows[Map(<lambda>)] -> "
"Union[Union] -> Sort[Sort] -> Limit[limit=5]"
)
_check_valid_plan_and_result(ds, expected_plan, [{"id": i} for i in range(5)])
def test_limit_pushdown_multiple_unions(ray_start_regular_shared_2_cpus):
"""Outer limit over nested unions should create a branch-local limit
for every leaf plus the global one."""
ds = (
ray.data.range(100)
.union(ray.data.range(100, override_num_blocks=5))
.union(ray.data.range(50))
.limit(5)
)
expected_plan = (
"Read[ReadRange] -> Limit[limit=5], "
"Read[ReadRange] -> Limit[limit=5] -> Union[Union] -> Limit[limit=5], "
"Read[ReadRange] -> Limit[limit=5] -> Union[Union] -> Limit[limit=5]"
)
_check_valid_plan_and_result(
ds, expected_plan, [{"id": i} for i in range(5)], check_ordering=False
)
def test_limit_pushdown_union_with_groupby(ray_start_regular_shared_2_cpus):
"""Limit after Union + Aggregate: limit should stay after Aggregate."""
ds1 = ray.data.range(100)
ds2 = ray.data.range(100).map(lambda x: {"id": x["id"] + 1000})
ds = ds1.union(ds2).groupby("id").count().limit(5)
# Result should contain 5 distinct ids with count == 1.
res = ds.take_all()
# Plan suffix check (no branch limits past Aggregate).
assert ds._logical_plan.dag.dag_str.endswith(
"Union[Union] -> Aggregate[Aggregate] -> Limit[limit=5]"
)
assert len(res) == 5 and all(r["count()"] == 1 for r in res)
def test_limit_pushdown_complex_chain(ray_start_regular_shared_2_cpus):
"""
Complex end-to-end case:
1. Two branches each with a branch-local Limit pushed to Read.
• left : Project
• right : MapRows
2. Union of the two branches.
3. Global Aggregate (groupby/count).
4. Sort (descending id) pushes stop here.
5. Final Limit.
Verifies both plan rewrite and result correctness.
"""
# ── left branch ────────────────────────────────────────────────
left = ray.data.range(50).select_columns(["id"]).limit(10)
# ── right branch ───────────────────────────────────────────────
right = ray.data.range(50).map(lambda x: {"id": x["id"] + 1000}).limit(10)
# ── union → aggregate → sort → limit ──────────────────────────
ds = left.union(right).groupby("id").count().sort("id", descending=True).limit(3)
# Expected logical-plan string.
expected_plan = (
"Read[ReadRange] -> Limit[limit=10] -> Project[Project], "
"Read[ReadRange] -> Limit[limit=10] -> MapRows[Map(<lambda>)] "
"-> Union[Union] -> Aggregate[Aggregate] -> Sort[Sort] -> Limit[limit=3]"
)
# Top-3 ids are the three largest (1009, 1008, 1007) with count()==1.
expected_result = [
{"id": 1009, "count()": 1},
{"id": 1008, "count()": 1},
{"id": 1007, "count()": 1},
]
_check_valid_plan_and_result(ds, expected_plan, expected_result)
def test_limit_pushdown_union_maps_projects(ray_start_regular_shared_2_cpus):
r"""
Read -> MapBatches -> MapRows -> Project
\ /
-------- Union ------------- → Limit
The limit should be pushed in front of each branch
(past MapRows, Project) while the original
global Limit is preserved after the Union.
"""
# Left branch.
left = (
ray.data.range(30)
.map_batches(lambda b: b, udf_modifying_row_count=False)
.map(lambda r: {"id": r["id"]})
.select_columns(["id"])
)
# Right branch with shifted ids.
right = (
ray.data.range(30)
.map_batches(lambda b: b, udf_modifying_row_count=False)
.map(lambda r: {"id": r["id"] + 100})
.select_columns(["id"])
)
ds = left.union(right).limit(3)
expected_plan = (
"Read[ReadRange] -> "
"Limit[limit=3] -> MapBatches[MapBatches(<lambda>)] -> MapRows[Map(<lambda>)] -> "
"Project[Project], "
"Read[ReadRange] -> "
"Limit[limit=3] -> MapBatches[MapBatches(<lambda>)] -> MapRows[Map(<lambda>)] -> "
"Project[Project] -> Union[Union] -> Limit[limit=3]"
)
expected_result = [{"id": i} for i in range(3)] # First 3 rows from left branch.
_check_valid_plan_and_result(
ds, expected_plan, expected_result, check_ordering=False
)
def test_limit_pushdown_map_per_block_limit_applied(ray_start_regular_shared_2_cpus):
"""Test that per-block limits are actually applied during map execution."""
# Create a global counter using Ray
@ray.remote
class Counter:
def __init__(self):
self.value = 0
def increment(self):
self.value += 1
return self.value
def get(self):
return self.value
counter = Counter.remote()
def track_processing(row):
# Record that this row was processed
ray.get(counter.increment.remote())
return row
# Create dataset with limit pushed through map
ds = ray.data.range(1000, override_num_blocks=10).map(track_processing).limit(50)
# Execute and get results
result = ds.take_all()
# Verify correct results
expected = [{"id": i} for i in range(50)]
assert result == expected
# Check how many rows were actually processed
processed_count = ray.get(counter.get.remote())
# With per-block limits, we should process fewer rows than the total dataset
# but at least the number we need for the final result
assert (
processed_count >= 50
), f"Expected at least 50 rows processed, got {processed_count}"
assert (
processed_count < 1000
), f"Expected fewer than 1000 rows processed, got {processed_count}"
print(f"Processed {processed_count} rows to get {len(result)} results")
def test_limit_pushdown_preserves_map_behavior(ray_start_regular_shared_2_cpus):
"""Test that adding per-block limits doesn't change the logical result."""
def add_one(row):
row["id"] += 1
return row
# Compare with and without limit pushdown
ds_with_limit = ray.data.range(100).map(add_one).limit(10)
ds_without_limit = ray.data.range(100).limit(10).map(add_one)
result_with = ds_with_limit.take_all()
result_without = ds_without_limit.take_all()
# Results should be identical
assert result_with == result_without
# Both should have the expected transformation applied
expected = [{"id": i + 1} for i in range(10)]
assert result_with == expected
@pytest.mark.parametrize(
"udf_modifying_row_count,expected_plan",
[
(
False,
"Read[ReadRange] -> Limit[limit=10] -> MapBatches[MapBatches(<lambda>)]",
),
(
True,
"Read[ReadRange] -> MapBatches[MapBatches(<lambda>)] -> Limit[limit=10]",
),
],
)
def test_limit_pushdown_udf_modifying_row_count_with_map_batches(
ray_start_regular_shared_2_cpus,
udf_modifying_row_count,
expected_plan,
):
"""Test that limit pushdown preserves the row count with map batches."""
ds = (
ray.data.range(100)
.map_batches(lambda x: x, udf_modifying_row_count=udf_modifying_row_count)
.limit(10)
)
_check_valid_plan_and_result(
ds,
expected_plan,
[{"id": i} for i in range(10)],
)
def test_does_not_pushdown_limit_past_map_batches_by_default(
ray_start_regular_shared_2_cpus,
):
def duplicate_id(batch):
yield {"data": list(batch["id"]) * 2}
# If the optimizer incorrectly pushes the limit past the map operator, then the
# returned count is 2.
num_rows = ray.data.range(1).map_batches(duplicate_id).limit(1).count()
assert num_rows == 1, num_rows
def test_does_not_pushdown_limit_past_map_groups_by_default(
ray_start_regular_shared_2_cpus,
):
def duplicate_id(batch):
yield {"data": list(batch["id"]) * 2}
# If the optimizer incorrectly pushes the limit past the map operator, then the
# returned count is 2.
num_rows = ray.data.range(1).groupby("id").map_groups(duplicate_id).limit(1).count()
assert num_rows == 1, num_rows
if __name__ == "__main__":
sys.exit(pytest.main(["-v", __file__]))