Apache Iceberg version
1.11.0 (latest release)
Query engine
Snowflake
Please describe the bug 🐞
Bug description
ValueWriters.FixedByteBufferWriter.write(ByteBuffer, Encoder) calls encoder.writeBytes(bytes)
instead of encoder.writeFixed(arr, 0, length). For Avro fixed[N] fields this is wrong:
writeBytes prepends a zigzag-encoded length (e.g. 0x06 for a 3-byte value) before the data,
but the Avro reader consumes exactly N bytes for a fixed field and has no length prefix to skip.
The stray prefix byte spills into the next field in the record.
Affected version: 1.11.0 (also present in prior versions)
Where the bug is
core/src/main/java/org/apache/iceberg/avro/ValueWriters.java, inner class FixedByteBufferWriter:
private static class FixedByteBufferWriter implements ValueWriter<ByteBuffer> {
private final int length;
@Override
public void write(ByteBuffer bytes, Encoder encoder) throws IOException {
Preconditions.checkArgument(bytes.remaining() == length, ...);
encoder.writeBytes(bytes); // ← BUG: should be writeFixed
}
}
The sibling class FixedWriter (which handles byte[]) already does it correctly:
private static class FixedWriter implements ValueWriter<byte[]> {
@Override
public void write(byte[] bytes, Encoder encoder) throws IOException {
Preconditions.checkArgument(bytes.length == length, ...);
encoder.writeFixed(bytes); // ← correct
}
}
Concrete corruption example
Writing a FIXED(3) partition value [AB CD EF] via FixedByteBufferWriter:
| Encoding |
Output bytes |
What the reader sees |
writeBytes (current) |
06 AB CD EF |
fixed[3] consumes 06 AB CD; EF spills into the next field |
writeFixed (correct) |
AB CD EF |
fixed[3] consumes AB CD EF ✓ |
When the spill byte EF prefixes the subsequent record_count varint 08 (zigzag for 4 rows),
the combined varint EF 08 decodes to raw value 1135, which zigzag-decodes to -568. Every
reader (Java, native, Spark) trusts this value from the manifest and sees record_count = -568
for a file that has 4 rows.
How to reproduce
- Create a partitioned Iceberg table with a
FIXED(3) identity-partition column.
- Write a data file via the Iceberg Java writer (triggers
InternalWriter → FixedByteBufferWriter).
- Read the written manifest Avro directly and inspect the
data_file.record_count field for the
partition entry — it will be a large negative number instead of the actual row count.
- Alternatively: observe that
data_file.partition.val8 in the manifest contains 06abcd instead
of abcdef for a [AB CD EF] partition value.
Proposed fix
In FixedByteBufferWriter.write(), extract the bytes from the ByteBuffer and call
encoder.writeFixed():
@Override
public void write(ByteBuffer bytes, Encoder encoder) throws IOException {
Preconditions.checkArgument(bytes.remaining() == length,
"Cannot write byte buffer of length %s as fixed[%s]", bytes.remaining(), length);
byte[] arr = new byte[length];
bytes.duplicate().get(arr);
encoder.writeFixed(arr);
}
A regression test should assert that a round-trip write/read of a manifest entry with a FIXED(N)
identity-partition column preserves the exact partition value bytes and the correct record_count.
Willingness to contribute
Apache Iceberg version
1.11.0 (latest release)
Query engine
Snowflake
Please describe the bug 🐞
Bug description
ValueWriters.FixedByteBufferWriter.write(ByteBuffer, Encoder)callsencoder.writeBytes(bytes)instead of
encoder.writeFixed(arr, 0, length). For Avrofixed[N]fields this is wrong:writeBytesprepends a zigzag-encoded length (e.g.0x06for a 3-byte value) before the data,but the Avro reader consumes exactly N bytes for a
fixedfield and has no length prefix to skip.The stray prefix byte spills into the next field in the record.
Affected version: 1.11.0 (also present in prior versions)
Where the bug is
core/src/main/java/org/apache/iceberg/avro/ValueWriters.java, inner classFixedByteBufferWriter:The sibling class
FixedWriter(which handlesbyte[]) already does it correctly:Concrete corruption example
Writing a
FIXED(3)partition value[AB CD EF]viaFixedByteBufferWriter:writeBytes(current)06 AB CD EFfixed[3]consumes06 AB CD;EFspills into the next fieldwriteFixed(correct)AB CD EFfixed[3]consumesAB CD EF✓When the spill byte
EFprefixes the subsequentrecord_countvarint08(zigzag for 4 rows),the combined varint
EF 08decodes to raw value1135, which zigzag-decodes to-568. Everyreader (Java, native, Spark) trusts this value from the manifest and sees
record_count = -568for a file that has 4 rows.
How to reproduce
FIXED(3)identity-partition column.InternalWriter→FixedByteBufferWriter).data_file.record_countfield for thepartition entry — it will be a large negative number instead of the actual row count.
data_file.partition.val8in the manifest contains06abcdinsteadof
abcdeffor a[AB CD EF]partition value.Proposed fix
In
FixedByteBufferWriter.write(), extract the bytes from theByteBufferand callencoder.writeFixed():A regression test should assert that a round-trip write/read of a manifest entry with a
FIXED(N)identity-partition column preserves the exact partition value bytes and the correct
record_count.Willingness to contribute