Skip to content

Commit 144c5bd

Browse files
committed
Simplify concurrent file overwrite tests
1 parent b6f88b4 commit 144c5bd

1 file changed

Lines changed: 65 additions & 59 deletions

File tree

tests/table/test_commit_retry.py

Lines changed: 65 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@
1414
# KIND, either express or implied. See the License for the
1515
# specific language governing permissions and limitations
1616
# under the License.
17-
from dataclasses import dataclass
1817
from typing import Any
1918
from unittest.mock import patch
2019

@@ -325,23 +324,13 @@ def test_concurrent_overwrite_overwrite_raises_validation_exception(catalog: Cat
325324
tbl2.overwrite(pa.table({"x": [40, 50, 60]}), overwrite_filter="x > 0")
326325

327326

328-
@dataclass(frozen=True)
329-
class _FileOverwriteScenario:
330-
identifier: str
331-
deleting_table: Table
332-
replacing_transaction: Transaction
333-
target_file: DataFile
334-
same_partition_file: DataFile
335-
different_partition_file: DataFile
327+
_FILE_OVERWRITE_TABLE = "default.concurrent_file_delete"
336328

337329

338-
def _prepare_file_overwrite_scenario(catalog: Catalog) -> _FileOverwriteScenario:
339-
"""Prepare a file replacement and candidates for concurrent deletion."""
340-
import uuid
341-
330+
def _create_file_overwrite_table(catalog: Catalog) -> DataFile:
331+
"""Create the file-overwrite test table and return the file to replace."""
342332
import pyarrow as pa
343333

344-
from pyiceberg.io.pyarrow import _dataframe_to_data_files
345334
from pyiceberg.partitioning import PartitionField, PartitionSpec
346335
from pyiceberg.transforms import IdentityTransform
347336

@@ -351,85 +340,102 @@ def _prepare_file_overwrite_scenario(catalog: Catalog) -> _FileOverwriteScenario
351340
NestedField(2, "value", LongType(), required=False),
352341
)
353342
spec = PartitionSpec(PartitionField(source_id=1, field_id=1000, transform=IdentityTransform(), name="category"))
354-
identifier = "default.concurrent_file_delete"
355-
table = catalog.create_table(identifier, schema=schema, partition_spec=spec)
343+
table = catalog.create_table(_FILE_OVERWRITE_TABLE, schema=schema, partition_spec=spec)
344+
356345
table.append(pa.table({"category": ["a", "b"], "value": [0, 1]}))
357-
original_file = next(task.file for task in table.scan().plan_files() if task.file.partition[0] == "a")
346+
file_to_replace = next(task.file for task in table.scan().plan_files() if task.file.partition[0] == "a")
347+
358348
table.append(pa.table({"category": ["a"], "value": [3]}))
349+
return file_to_replace
350+
359351

360-
deleting_table = catalog.load_table(identifier)
361-
replacing_table = catalog.load_table(identifier)
362-
active_files = [task.file for task in replacing_table.scan().plan_files()]
363-
same_partition_file = next(
364-
data_file for data_file in active_files if data_file.partition[0] == "a" and data_file != original_file
352+
def _data_file_in_partition(table: Table, partition: str, excluded_file: DataFile | None = None) -> DataFile:
353+
"""Return a data file in a partition, optionally excluding one file."""
354+
return next(
355+
task.file for task in table.scan().plan_files() if task.file.partition[0] == partition and task.file != excluded_file
365356
)
366-
different_partition_file = next(data_file for data_file in active_files if data_file.partition[0] == "b")
357+
358+
359+
def _stage_file_replacement(table: Table, file_to_replace: DataFile) -> Transaction:
360+
"""Stage replacing one data file without committing the transaction."""
361+
import uuid
362+
363+
import pyarrow as pa
364+
365+
from pyiceberg.io.pyarrow import _dataframe_to_data_files
366+
367367
replacement_files = list(
368368
_dataframe_to_data_files(
369-
table_metadata=replacing_table.metadata,
369+
table_metadata=table.metadata,
370370
df=pa.table({"category": ["a"], "value": [2]}),
371-
io=replacing_table.io,
371+
io=table.io,
372372
write_uuid=uuid.uuid4(),
373373
)
374374
)
375375

376-
replacing_transaction = replacing_table.transaction()
377-
with replacing_transaction.update_snapshot().overwrite() as overwrite:
378-
overwrite.delete_data_file(original_file)
376+
transaction = table.transaction()
377+
with transaction.update_snapshot().overwrite() as overwrite:
378+
overwrite.delete_data_file(file_to_replace)
379379
for replacement_file in replacement_files:
380380
overwrite.append_data_file(replacement_file)
381381

382-
return _FileOverwriteScenario(
383-
identifier=identifier,
384-
deleting_table=deleting_table,
385-
replacing_transaction=replacing_transaction,
386-
target_file=original_file,
387-
same_partition_file=same_partition_file,
388-
different_partition_file=different_partition_file,
389-
)
382+
return transaction
383+
384+
385+
def _delete_data_file(table: Table, data_file: DataFile) -> None:
386+
"""Commit the deletion of one data file."""
387+
with table.transaction() as transaction:
388+
with transaction.update_snapshot().overwrite() as overwrite:
389+
overwrite.delete_data_file(data_file)
390+
391+
392+
def _file_overwrite_values(catalog: Catalog) -> list[int]:
393+
"""Return the sorted values in the file-overwrite test table."""
394+
result = catalog.load_table(_FILE_OVERWRITE_TABLE).scan().to_arrow()
395+
return sorted(result["value"].to_pylist())
390396

391397

392398
def test_file_overwrite_fails_when_target_file_is_concurrently_deleted(catalog: Catalog) -> None:
393399
"""A file replacement must fail if the original file was concurrently deleted."""
394-
scenario = _prepare_file_overwrite_scenario(catalog)
400+
file_to_replace = _create_file_overwrite_table(catalog)
401+
replacing_table = catalog.load_table(_FILE_OVERWRITE_TABLE)
402+
deleting_table = catalog.load_table(_FILE_OVERWRITE_TABLE)
395403

396-
with scenario.deleting_table.transaction() as deleting_transaction:
397-
with deleting_transaction.update_snapshot().overwrite() as overwrite:
398-
overwrite.delete_data_file(scenario.target_file)
404+
replacing_transaction = _stage_file_replacement(replacing_table, file_to_replace)
405+
_delete_data_file(deleting_table, file_to_replace)
399406

400407
with pytest.raises(ValidationException, match="Data files were concurrently deleted"):
401-
scenario.replacing_transaction.commit_transaction()
408+
replacing_transaction.commit_transaction()
402409

403-
result = catalog.load_table(scenario.identifier).scan().to_arrow()
404-
assert sorted(result["value"].to_pylist()) == [1, 3]
410+
assert _file_overwrite_values(catalog) == [1, 3]
405411

406412

407413
def test_file_overwrite_allows_concurrent_delete_in_same_partition(catalog: Catalog) -> None:
408414
"""A file replacement must allow another file in its partition to be concurrently deleted."""
409-
scenario = _prepare_file_overwrite_scenario(catalog)
415+
file_to_replace = _create_file_overwrite_table(catalog)
416+
replacing_table = catalog.load_table(_FILE_OVERWRITE_TABLE)
417+
deleting_table = catalog.load_table(_FILE_OVERWRITE_TABLE)
418+
file_to_delete = _data_file_in_partition(deleting_table, "a", excluded_file=file_to_replace)
410419

411-
with scenario.deleting_table.transaction() as deleting_transaction:
412-
with deleting_transaction.update_snapshot().overwrite() as overwrite:
413-
overwrite.delete_data_file(scenario.same_partition_file)
420+
replacing_transaction = _stage_file_replacement(replacing_table, file_to_replace)
421+
_delete_data_file(deleting_table, file_to_delete)
422+
replacing_transaction.commit_transaction()
414423

415-
scenario.replacing_transaction.commit_transaction()
416-
417-
result = catalog.load_table(scenario.identifier).scan().to_arrow()
418-
assert sorted(result["value"].to_pylist()) == [1, 2]
424+
assert _file_overwrite_values(catalog) == [1, 2]
419425

420426

421427
def test_file_overwrite_allows_concurrent_delete_in_different_partition(catalog: Catalog) -> None:
422428
"""A file replacement must allow a file in another partition to be concurrently deleted."""
423-
scenario = _prepare_file_overwrite_scenario(catalog)
424-
425-
with scenario.deleting_table.transaction() as deleting_transaction:
426-
with deleting_transaction.update_snapshot().overwrite() as overwrite:
427-
overwrite.delete_data_file(scenario.different_partition_file)
429+
file_to_replace = _create_file_overwrite_table(catalog)
430+
replacing_table = catalog.load_table(_FILE_OVERWRITE_TABLE)
431+
deleting_table = catalog.load_table(_FILE_OVERWRITE_TABLE)
432+
file_to_delete = _data_file_in_partition(deleting_table, "b")
428433

429-
scenario.replacing_transaction.commit_transaction()
434+
replacing_transaction = _stage_file_replacement(replacing_table, file_to_replace)
435+
_delete_data_file(deleting_table, file_to_delete)
436+
replacing_transaction.commit_transaction()
430437

431-
result = catalog.load_table(scenario.identifier).scan().to_arrow()
432-
assert sorted(result["value"].to_pylist()) == [2, 3]
438+
assert _file_overwrite_values(catalog) == [2, 3]
433439

434440

435441
def test_concurrent_overwrite_append_retries_successfully(catalog: Catalog) -> None:

0 commit comments

Comments
 (0)