Skip to content

Fix DataFile spec ID and reject unknown record fields - #3954

Merged
geruh merged 6 commits into
mainfrom
kevinjqliu-investigate-datafile-spec-id
Sep 12, 2026
Merged

Fix DataFile spec ID and reject unknown record fields#3954
geruh merged 6 commits into
mainfrom
kevinjqliu-investigate-datafile-spec-id

Conversation

@kevinjqliu

@kevinjqliu kevinjqliu commented Sep 12, 2026

Copy link
Copy Markdown
Contributor

Summary

This PR fixes two related constructor issues:

  1. DataFile.from_args now sets spec_id when it is provided. spec_id remains transient runtime context and is not included in the serialized data_file record.
  2. Record._bind now raises TypeError for fields that are not part of the selected struct instead of silently dropping them.

Motivation

DataFile.from_args(spec_id=...) previously accepted the argument, but _bind discarded it because spec_id belongs to the containing manifest rather than the serialized data-file schema. The resulting DataFile had no spec context, and accessing spec_id could raise AttributeError.

More generally, silently ignoring unknown fields is a footgun: misspelled, unsupported, or wrong-version arguments appear to work while producing an incomplete record. Validating arguments in _bind makes these mistakes fail immediately for all record types.

Tests

  • make lint
  • make test PYTEST_ARGS="-q"
  • pytest -v -x -m integration tests/integration/test_rest_manifest.py::test_write_sample_manifest

Reject unknown record fields while retaining spec_id as transient DataFile context.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot AI lite review requested due to automatic review settings September 12, 2026 01:57

This comment was marked as outdated.

@kevinjqliu
kevinjqliu marked this pull request as draft September 12, 2026 02:23
kevinjqliu and others added 3 commits September 11, 2026 19:25
Use the internal table format selector now that unknown DataFile fields are rejected.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Let the v2 manifest writer project the canonical record instead of constructing a version-specific positional layout.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Keep transient DataFile context out of the expected Avro payload.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@kevinjqliu kevinjqliu changed the title Fix DataFile spec ID binding Fix DataFile spec ID handling Sep 12, 2026
Keep the canonical DataFile name distinct from the explicit v1 record and order version selection first.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 Approval recommended

The focused implementation matches the stated behavior and is adequately tested.

Review details
  • Files reviewed: 5/5 changed files
  • Comments generated: 0 new
  • Review effort level: Balanced

Comment thread pyiceberg/manifest.py
@classmethod
def from_args(cls, _table_format_version: TableVersion = DEFAULT_READ_VERSION, **arguments: Any) -> DataFile:
def from_args(
cls, _table_format_version: TableVersion = DEFAULT_READ_VERSION, *, spec_id: int | None = None, **arguments: Any

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

explicitly add spec_id to the function.
its called in

I think this is better. Otherwise caller has to set it after constructing DataFile. For example

data_file = DataFile.from_args()
data_file.spec_id = spec_id

Comment thread pyiceberg/manifest.py
Comment on lines +472 to +474
if spec_id is not None:
data_file.spec_id = spec_id
return data_file

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is using DataFile's own setter

# Spec ID should not be stored in the file
_spec_id: int
@property
def spec_id(self) -> int:
return self._spec_id
@spec_id.setter
def spec_id(self, value: int) -> None:
self._spec_id = value

Comment thread pyiceberg/typedef.py

@classmethod
def _bind(cls, struct: StructType, **arguments: Any) -> Self:
field_names = {field.name for field in struct.fields}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

including this fix as part of the PR.

This is the footgun that was silently dropping fields. We now check for unknown fields.

This caught format_version=2, in tests/integration/test_rest_manifest.py which is invalid

Comment thread tests/avro/test_file.py
"spec_id": 3,
}
data_file_v2 = DataFile.from_args(**data_file_dict) # type: ignore
data_file = DataFile.from_args(content=DataFileContent.DATA, **common_data_file_args) # type: ignore

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

content is a V2 field.

common_data_file_args is shared between v1 and v2 in this test file

Comment thread tests/avro/test_file.py
data_file_v1 = DataFile.from_args(**data_file_dict, _table_format_version=format_version)
data_file_v1 = DataFile.from_args(
_table_format_version=format_version,
block_size_in_bytes=DEFAULT_BLOCK_SIZE,

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

block_size_in_bytes is required in V1.

@kevinjqliu kevinjqliu changed the title Fix DataFile spec ID handling Fix DataFile spec ID and reject unknown record fields Sep 12, 2026
@kevinjqliu
kevinjqliu requested review from Fokko and geruh September 12, 2026 18:55
@kevinjqliu
kevinjqliu marked this pull request as ready for review September 12, 2026 18:55
Resolve the v2 manifest fixture conflict after referenced_data_file was added to the v2 schema.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
wrapped_entry_v2.data_file = wrapped_data_file_v2_debug
wrapped_entry_v2_dict = todict(wrapped_entry_v2, [field.name for field in test_spec.fields])
for field in ("first_row_id", "content_offset", "content_size_in_bytes"):
for field in ("first_row_id", "content_offset", "content_size_in_bytes", "spec_id"):

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

spec_id is not serialized so we need to remove it for this comparison to work

test_schema = table_test_all_types.schema()
test_spec = table_test_all_types.spec()
wrapped_data_file_v2_debug = DataFile.from_args(
format_version=2,

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is not a valid field, the typedef.py change caught it 😄

Comment thread tests/avro/test_file.py
"upper_bounds": {1: b"zzzzzzzzzzzzzzzz"},
"key_metadata": b"\xde\xad\xbe\xef",
"split_offsets": [4, 133697593],
"equality_ids": [],

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

equality_ids is not a valid V1 field.

common_data_file_args is shared between v1 and v2 in this test file

@geruh geruh left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm, and aligns with java impl. The spec id is omitted wrt the manifests serialized record.

@geruh
geruh added this pull request to the merge queue Sep 12, 2026
Merged via the queue into main with commit 5133f35 Sep 12, 2026
21 checks passed
@geruh
geruh deleted the kevinjqliu-investigate-datafile-spec-id branch September 12, 2026 21:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants