This is an automated email from the ASF dual-hosted git repository. github-merge-queue[bot] pushed a commit to branch gh-readonly-queue/main/pr-4009-f7dcc3d92ccd87532f6332028e68b11b09446be3 in repository https://gitbox.apache.org/repos/asf/iceberg-python.git
commit 0743814334b1caf86a54a2301e3e5b34d80aff0a Author: Rodrigo Stachlewski Palma <[email protected]> AuthorDate: Thu Sep 24 19:28:20 2026 +0000 fix(avro): advance the decoder when skipping an enum (#4009) EnumReader.skip() was a no-op, so the enum's bytes stayed in the stream and the next field was decoded from them. Reading a manifest with the status field projected out returns wrong values for every field after it. Delegate to the wrapped reader, which is what read() already does. --- pyiceberg/avro/resolver.py | 2 +- tests/avro/test_resolver.py | 36 ++++++++++++++++++++++++++++++++++-- 2 files changed, 35 insertions(+), 3 deletions(-) diff --git a/pyiceberg/avro/resolver.py b/pyiceberg/avro/resolver.py index ec76b331a..3f19ae987 100644 --- a/pyiceberg/avro/resolver.py +++ b/pyiceberg/avro/resolver.py @@ -272,7 +272,7 @@ class EnumReader(Reader): return self.enum(self.reader.read(decoder)) def skip(self, decoder: BinaryDecoder) -> None: - pass + self.reader.skip(decoder) class WriteSchemaResolver(PrimitiveWithPartnerVisitor[IcebergType, Writer]): diff --git a/tests/avro/test_resolver.py b/tests/avro/test_resolver.py index 2611f9652..106e4ad8f 100644 --- a/tests/avro/test_resolver.py +++ b/tests/avro/test_resolver.py @@ -15,11 +15,14 @@ # specific language governing permissions and limitations # under the License. +from collections.abc import Callable from tempfile import TemporaryDirectory import pytest from pydantic import Field +from pyiceberg.avro.decoder import BinaryDecoder, StreamingBinaryDecoder +from pyiceberg.avro.decoder_fast import CythonBinaryDecoder from pyiceberg.avro.file import AvroFile from pyiceberg.avro.reader import ( DecimalReader, @@ -32,7 +35,7 @@ from pyiceberg.avro.reader import ( StringReader, StructReader, ) -from pyiceberg.avro.resolver import resolve_reader, resolve_writer +from pyiceberg.avro.resolver import EnumReader, resolve_reader, resolve_writer from pyiceberg.avro.writer import ( BinaryWriter, DefaultWriter, @@ -46,7 +49,7 @@ from pyiceberg.avro.writer import ( ) from pyiceberg.exceptions import ResolveError from pyiceberg.io.pyarrow import PyArrowFileIO -from pyiceberg.manifest import MANIFEST_ENTRY_SCHEMAS +from pyiceberg.manifest import MANIFEST_ENTRY_SCHEMAS, ManifestEntryStatus from pyiceberg.schema import Schema from pyiceberg.typedef import Record from pyiceberg.types import ( @@ -64,6 +67,8 @@ from pyiceberg.types import ( StructType, ) +AVAILABLE_DECODERS = [StreamingBinaryDecoder, CythonBinaryDecoder] + def test_resolver() -> None: write_schema = Schema( @@ -418,3 +423,30 @@ def test_writer_missing_optional_in_read_schema() -> None: expected = StructWriter(field_writers=((None, OptionWriter(option=StringWriter())),)) assert actual == expected + + [email protected]("decoder_class", AVAILABLE_DECODERS) +def test_enum_reader_skip_advances_the_decoder(decoder_class: Callable[[bytes], BinaryDecoder]) -> None: + """Skipping an enum field must consume its bytes, or the next field is read from them.""" + # Two ints, zigzag encoded: the enum's ordinal 1, then the next field's value 12. + decoder = decoder_class(b"\x02\x18") + reader = EnumReader(ManifestEntryStatus, IntegerReader()) + + reader.skip(decoder) + + assert IntegerReader().read(decoder) == 12 + + [email protected]("decoder_class", AVAILABLE_DECODERS) +def test_enum_reader_skip_matches_read(decoder_class: Callable[[bytes], BinaryDecoder]) -> None: + """Reading and skipping must leave the decoder at the same position.""" + encoded = b"\x02\x18" + reader = EnumReader(ManifestEntryStatus, IntegerReader()) + + read_decoder = decoder_class(encoded) + assert reader.read(read_decoder) == ManifestEntryStatus.ADDED + + skip_decoder = decoder_class(encoded) + reader.skip(skip_decoder) + + assert IntegerReader().read(read_decoder) == IntegerReader().read(skip_decoder)
