Skip to content

ArrowReader returns nested field values for Parquet files without field ids or a name mapping, where Iceberg Java returns NULL #3436

Open

Description

@zhangfengcdt

Apache Iceberg Rust version

main @ b154603a710d730d2f43f8af4db257cc137237ef

Describe the bug

When a Parquet data file has no field ids and the table has no name mapping (schema.name-mapping.default), ArrowReader returns the values stored in the file for every nested field. Iceberg Java returns NULL for them. This covers struct fields, including structs inside lists and maps.

Iceberg Java's fallback, ParquetSchemaUtil.addFallbackIds, assigns ids by position to the top-level fields only. Its readers then match nested struct fields by id. SparkParquetReaders.ReadBuilder.struct, for example, registers a reader only for a file field that has an id. A nested field of such a file has none, so every expected field reads as NULL. The spec's column projection rules also give null for a field id the file doesn't contain when there is no name mapping.

iceberg-rust assigns the same top-level ids (add_fallback_field_ids_to_arrow_schema). Then two things differ:

So the values come back even when the file's nested field names differ from the table's.

Found through Apache DataFusion Comet, whose native Iceberg scan uses ArrowReader: apache/datafusion-comet#6794. With Spark 4.1 and Iceberg 1.11, for files added with SparkTableUtil.importSparkTable (no name mapping):

Query Spark (Iceberg Java) iceberg-rust
SELECT id, s FROM t WHERE id < 3 ORDER BY id [0,[null,null]] [1,[null,null]] [2,[null,null]] [0,[0,0]] [1,[1,-1]] [2,[2,-2]]
SELECT id, items, m FROM t WHERE id < 3 ORDER BY id [0,[],{k -> [null]}] … [0,[[0]],{k -> [0]}] …
SELECT count(*) FROM t WHERE s.a IS NULL [50] [0]

The table is (id INT, s STRUCT<a: INT, b: INT>, items ARRAY<STRUCT<x: INT>>, m MAP<STRING, STRUCT<v: INT>>). With the file's struct fields named p and q instead of a and b, Spark still returns NULL for s.a and s.b, while the iceberg-rust read returns the values by position.

To Reproduce

The existing test test_read_parquet_without_field_ids_with_struct shows it. It writes a file without field ids holding person STRUCT<name, age>, reads it with no name mapping, and asserts person.name is "Alice"/"Bob" and person.age is 30/25. It passes on main. For the same file, Iceberg Java returns a person struct whose name and age are NULL.

cargo test -p iceberg --lib test_read_parquet_without_field_ids_with_struct

Expected behavior

For a file without field ids and no name mapping, top-level columns keep their positional fallback ids, and nested fields match by id. Since such a file has no nested ids, struct fields read as NULL, as in Iceberg Java. In Java's readers a list or map is matched to its element, key and value by position, so a top-level ARRAY<INT> or MAP<STRING, INT> still reads its values. Only fields inside a struct come back NULL. A list whose element is a struct, with no nested leaf read, comes back empty in Spark.

test_read_parquet_without_field_ids_with_struct encodes the current behavior and would change with a fix.

Willingness to contribute

I would be willing to contribute a fix for this bug with guidance from the Iceberg community

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions