You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@iceberg.apache.org by ke...@apache.org on 2025/02/01 05:18:27 UTC

(iceberg-python) 03/10: default-value bug fixes and adding more tests

This is an automated email from the ASF dual-hosted git repository.

kevinjqliu pushed a commit to branch specPartitionIdentity
in repository https://gitbox.apache.org/repos/asf/iceberg-python.git

commit e2f42ef4c13f8eb4953b74f04dad6be5747ea6bd
Author: Gabriel Igliozzi <gi...@netflix.com>
AuthorDate: Thu Dec 19 00:18:46 2024 -0500

    default-value bug fixes and adding more tests
---
 pyiceberg/io/pyarrow.py  | 19 +++++++----------
 tests/io/test_pyarrow.py | 54 +++++++++++++++++++++++++++++++++++++++++++++++-
 2 files changed, 61 insertions(+), 12 deletions(-)

diff --git a/pyiceberg/io/pyarrow.py b/pyiceberg/io/pyarrow.py
index 93c5f868..eb570592 100644
--- a/pyiceberg/io/pyarrow.py
+++ b/pyiceberg/io/pyarrow.py
@@ -1331,20 +1331,17 @@ def _task_to_record_batches(
         if file_schema is None:
             raise ValueError(f"Missing Iceberg schema in Metadata for file: {path}")
 
-        # Apply column projection rules for missing partitions and default values (ref: https://iceberg.apache.org/spec/#column-projection)
-        projected_missing_fields = None
+        # Apply column projection rules for missing partitions and default values
+        # https://iceberg.apache.org/spec/#column-projection
+        projected_missing_fields = {}
         for field_id in projected_field_ids.difference(file_project_schema.field_ids):
             for partition_field in partition_spec.fields_by_source_id(field_id):
                 if isinstance(partition_field.transform, IdentityTransform) and task.file.partition is not None:
-                    projected_missing_fields = (partition_field.name, task.file.partition[0])
+                    projected_missing_fields[partition_field.name] = task.file.partition[0]
 
-            if (
-                nested_field := projected_schema.find_field(field_id)
-                and projected_missing_fields is None
-                and task.file.partition is None
-            ):
+            if nested_field := projected_schema.find_field(field_id):
                 if nested_field.initial_default is not None:
-                    projected_missing_fields(nested_field.name, nested_field)
+                    projected_missing_fields[nested_field.name] = nested_field.initial_default
 
         fragment_scanner = ds.Scanner.from_fragment(
             fragment=fragment,
@@ -1392,8 +1389,8 @@ def _task_to_record_batches(
                     use_large_types=use_large_types,
                 )
 
-                if projected_missing_fields is not None:
-                    name, value = projected_missing_fields
+                # Inject projected column values if available
+                for name, value in projected_missing_fields.items():
                     result_batch = result_batch.set_column(result_batch.schema.get_field_index(name), name, [value])
 
                 yield result_batch
diff --git a/tests/io/test_pyarrow.py b/tests/io/test_pyarrow.py
index 0507d9cf..b294dd19 100644
--- a/tests/io/test_pyarrow.py
+++ b/tests/io/test_pyarrow.py
@@ -1128,7 +1128,7 @@ def test_projection_concat_files(schema_int: Schema, file_int: str) -> None:
     assert repr(result_table.schema) == "id: int32"
 
 
-def test_projection_partition_inference(tmp_path: str, example_task: FileScanTask):
+def test_projection_partition_inference(tmp_path: str):
     schema = Schema(
         NestedField(1, "partition_field", IntegerType(), required=False),
         NestedField(2, "other_field", StringType(), required=False),
@@ -1180,6 +1180,58 @@ other_field: [["x"]]"""
     )
 
 
+def test_projection_initial_default_inference(tmp_path: str) -> None:
+    schema = Schema(
+        NestedField(1, "other_field", StringType(), required=False),
+        NestedField(2, "other_field_1", StringType(), required=False, initial_default="foo"),
+    )
+
+    table = TableMetadataV2(
+        location="file://a/b/c.json",
+        last_column_id=2,
+        format_version=2,
+        current_schema_id=0,
+        schemas=[schema],
+        partition_specs=[PartitionSpec()],
+        properties={TableProperties.DEFAULT_NAME_MAPPING: create_mapping_from_schema(schema).model_dump_json()},
+    )
+
+    pa_schema = pa.schema([pa.field("other_field", pa.string())])
+    pa_table = pa.table({"other_field": ["x"]}, schema=pa_schema)
+    pq.write_table(pa_table, f"{tmp_path}/datafile.parquet")
+
+    data_file = DataFile(
+        content=DataFileContent.DATA,
+        file_path=f"{tmp_path}/datafile.parquet",
+        file_format=FileFormat.PARQUET,
+        partition=Record(),
+        file_size_in_bytes=os.path.getsize(f"{tmp_path}/datafile.parquet"),
+        sort_order_id=None,
+        spec_id=0,
+        equality_ids=None,
+        key_metadata=None,
+    )
+
+    table_result_scan = ArrowScan(
+        table_metadata=table,
+        io=load_file_io(),
+        projected_schema=schema,
+        row_filter=AlwaysTrue(),
+    ).to_table(tasks=[FileScanTask(data_file=data_file)])
+
+    print(str(table_result_scan))
+
+    assert (
+        str(table_result_scan)
+        == """pyarrow.Table
+other_field: large_string
+other_field_1: string
+----
+other_field: [["x"]]
+other_field_1: [["foo"]]"""
+    )
+
+
 def test_projection_filter(schema_int: Schema, file_int: str) -> None:
     result_table = project(schema_int, [file_int], GreaterThan("id", 4))
     assert len(result_table.columns[0]) == 0