|
25 | 25 |
|
26 | 26 | import pyiceberg.manifest as manifest_module |
27 | 27 | from pyiceberg.avro.codecs import AvroCompressionCodec |
28 | | -from pyiceberg.avro.file import AvroOutputFile |
| 28 | +from pyiceberg.avro.file import AvroFile, AvroOutputFile |
29 | 29 | from pyiceberg.io import load_file_io |
30 | 30 | from pyiceberg.io.pyarrow import PyArrowFileIO |
31 | 31 | from pyiceberg.manifest import ( |
| 32 | + DATA_FILE_TYPE, |
32 | 33 | MANIFEST_ENTRY_SCHEMAS, |
33 | 34 | MANIFEST_LIST_FILE_SCHEMAS, |
34 | 35 | DataFile, |
|
50 | 51 | from pyiceberg.schema import Schema |
51 | 52 | from pyiceberg.table.snapshots import Operation, Snapshot, Summary |
52 | 53 | from pyiceberg.typedef import Record, TableVersion |
53 | | -from pyiceberg.types import IntegerType, NestedField |
| 54 | +from pyiceberg.types import IntegerType, ListType, LongType, NestedField, StructType |
54 | 55 |
|
55 | 56 |
|
56 | 57 | @pytest.fixture(autouse=True) |
@@ -294,6 +295,57 @@ def write_and_read(file_name: str, data_file: DataFile) -> DataFile: |
294 | 295 | assert delete_file.content_size_in_bytes == 46 |
295 | 296 |
|
296 | 297 |
|
| 298 | +def test_read_legacy_long_equality_ids(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None: |
| 299 | + """Manifests written by older PyIceberg versions with equality_ids as list<long> can still be read. |
| 300 | +
|
| 301 | + PyIceberg previously wrote the wrong schema, list<long>, for equality_ids; the Iceberg spec requires list<int>. |
| 302 | + The default schema now uses list<int>, so reading existing manifests relies on the fallback in the Avro resolver. |
| 303 | + See: https://github.com/apache/iceberg-python/issues/3840 |
| 304 | + See: https://iceberg.apache.org/spec/#manifests |
| 305 | + """ |
| 306 | + io = PyArrowFileIO() |
| 307 | + manifest_path = str(tmp_path / "manifest.avro") |
| 308 | + |
| 309 | + entry = ManifestEntry.from_args( |
| 310 | + status=ManifestEntryStatus.ADDED, |
| 311 | + snapshot_id=1, |
| 312 | + sequence_number=1, |
| 313 | + file_sequence_number=1, |
| 314 | + data_file=DataFile.from_args( |
| 315 | + content=DataFileContent.EQUALITY_DELETES, |
| 316 | + file_path="s3://bucket/deletes.parquet", |
| 317 | + file_format=FileFormat.PARQUET, |
| 318 | + partition=Record(), |
| 319 | + record_count=10, |
| 320 | + file_size_in_bytes=1024, |
| 321 | + equality_ids=[1, 2], |
| 322 | + ), |
| 323 | + ) |
| 324 | + |
| 325 | + # Write the manifest as older PyIceberg versions did, with equality_ids as list<long> |
| 326 | + legacy_data_file_type = StructType( |
| 327 | + *[ |
| 328 | + NestedField(135, "equality_ids", ListType(136, LongType()), required=False) if field.field_id == 135 else field |
| 329 | + for field in DATA_FILE_TYPE[2].fields |
| 330 | + ] |
| 331 | + ) |
| 332 | + with monkeypatch.context() as legacy: |
| 333 | + legacy.setitem(DATA_FILE_TYPE, 2, legacy_data_file_type) |
| 334 | + with write_manifest( |
| 335 | + format_version=2, |
| 336 | + spec=UNPARTITIONED_PARTITION_SPEC, |
| 337 | + schema=Schema(NestedField(1, "foo", IntegerType(), False)), |
| 338 | + output_file=io.new_output(manifest_path), |
| 339 | + snapshot_id=1, |
| 340 | + avro_compression="null", |
| 341 | + ) as writer: |
| 342 | + writer.add_entry(entry) |
| 343 | + with AvroFile[ManifestEntry](io.new_input(manifest_path)) as avro_file: |
| 344 | + assert avro_file.schema.find_field("data_file.equality_ids").field_type == ListType(136, LongType()) |
| 345 | + |
| 346 | + assert writer.to_manifest_file().fetch_manifest_entry(io)[0].data_file.equality_ids == [1, 2] |
| 347 | + |
| 348 | + |
297 | 349 | def test_read_manifest_list(generated_manifest_file_file_v1: str) -> None: |
298 | 350 | input_file = PyArrowFileIO().new_input(generated_manifest_file_file_v1) |
299 | 351 | manifest_list = list(read_manifest_list(input_file))[0] |
|
0 commit comments