Skip to content

fix(ipc): body prefix must be uncompressed length - #470

Open
kentkwu wants to merge 2 commits into
apache:mainfrom
kentkwu:fix/ipc-writer-uncompressed-length-prefix
Open

fix(ipc): body prefix must be uncompressed length#470
kentkwu wants to merge 2 commits into
apache:mainfrom
kentkwu:fix/ipc-writer-uncompressed-length-prefix

Conversation

@kentkwu

@kentkwu kentkwu commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Summary

The Arrow columnar format spec requires the eight-byte prefix on each compressed IPC body buffer to hold the uncompressed length so a reader can size the decompression destination buffer.

Spec: https://arrow.apache.org/docs/format/Columnar.html#compression
the body includes a flat sequence of compressed buffers together with the length of the uncompressed buffer as a 64-bit little-endian signed integer stored in the first 8 bytes of each buffer in the sequence. This uncompressed length can be set to -1 to indicate that that specific buffer is left uncompressed.

The writer was emitting the compressed length instead, so PyArrow rejects Arrow JS-produced ZSTD and LZ4_FRAME streams. JS-to-JS round trips missed the bug because the reader sizes buffers from the codec frame, not the prefix. The compression code path is shared between the stream and file writers, so the one-line fix covers both formats.

  • src/ipc/writer.ts: write byteBuf.length in the prefix instead of compressed.length
  • New byte-level regression test in both writer suites (two codecs × stream and file) decompresses each emitted body buffer and asserts the prefix equals the decompressed length
  • Extract registerCompressionCodecs and the new prefix inspector into test/unit/ipc/writer/compression-codecs.ts, dropping a duplicate copy from file-writer-tests.ts

Cross-language interop verified against PyArrow 25.0.1.

Test Plan

  • npm test
  • npm run build
  • npm run lint:ci
  • All four regression cases (two codecs × stream and file writers) fail against the unfixed writer and pass with the fix

Related

Per the Arrow columnar format spec, the eight-byte prefix on each
compressed IPC body buffer must hold the buffer's uncompressed length
so consumers can size the decompression destination buffer. The
writer was emitting the compressed length instead, which caused
PyArrow (and any other implementation that relies on the prefix for
allocation) to reject Arrow JS-produced ZSTD and LZ4_FRAME streams
with a "destination buffer too small" error. JS-to-JS round trips
did not surface the bug because the reader sizes buffers from the
codec frame rather than the prefix.

Add a byte-level regression test that inspects each emitted prefix
against the decompressed payload length for both allowed codecs
(LZ4_FRAME and ZSTD), and factor the shared codec-registration
helper out of stream-writer-tests.ts so the new test file does not
have to import from another test file.
@kou

kou commented Sep 3, 2026

Copy link
Copy Markdown
Member

Could you check CI failures?

The Closure externs generator crawls exported classes for their static
and prototype property names, so option-object properties (referenced
only inside constructor bodies) never get reserved. Closure ADVANCED
renames them, and external UMD callers passing
`{ compressionType: X }` to a writer receive silently-uncompressed
output — the writer's lookup targets the renamed property, gets
nothing, and sets `_compression = null`.

Declare `compressionType` as reserved so Closure preserves it.
`autoDestroy` is already preserved coincidentally (it exists as a
prototype property on `RecordBatchReader`); the identically-shaped
bug for `writeLegacyIpcFormat` is left for follow-up.
@kentkwu
kentkwu force-pushed the fix/ipc-writer-uncompressed-length-prefix branch from e110c6b to 59fde71 Compare September 3, 2026 14:27
@kou

kou commented Sep 4, 2026

Copy link
Copy Markdown
Member

@trxcllnt Do you want to review this before we merge this?

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