Skip to content

v0.12.0

Latest

Choose a tag to compare

@github-actions github-actions released this 04 Jul 22:23
Immutable release. Only release title and notes can be modified.

Identity gets types: encoding ids, layout ids, and column names are now validated domain
primitives (EncodingId, LayoutId, ColumnName — sealed WellKnown/Custom shapes with a
total parse, strings only at the wire boundary), layout decode becomes pluggable through
LayoutDecoder/LayoutRegistry, and the reader gains compatibility with current Rust writers'
vortex.zoned zone-map id. Field names are strict on both sides of the file boundary — the
writer refuses what the reference toolchain cannot survive (a NUL-named column SIGABRTs its
Arrow FFI), the reader rejects duplicate/blank/control names loudly instead of corrupting
silently. Plus the dictionary code-scan lane lands in both fused compute kernels (~20×/~22×,
multi-leaf AND ~11×), and Chunk columns are one order-preserving typed map. Every wire-level
claim measured against the Rust (JNI) oracle; behavior divergences documented in
docs/compatibility.md.

Added

  • Compute.filteredSum(filterColumn, predicate, aggColumn) fuses a filter and a sum into a single scan — a row folds into the total only when the predicate selects it (a null filter row is excluded) and the aggregate value is non-null — with no intermediate selection bitmap. It matches a hand-written fused loop and is ~1.5× faster than the two-pass filter + sum. (57d2225b)
  • Compute.filteredAggregate(chunk, filter, aggColumn) fuses a whole multi-column RowFilter (an n-ary AND of column-bound predicate leaves) and folds the selected rows' SUM/MIN/MAX/non-null count over an aggregate column in a single pass — the multi-column counterpart of filteredSum, and the row-level kernel behind the Calcite boundary-chunk aggregate push-down. A null aggregate column counts selected rows only (COUNT(*)). (2ba54888)
  • core.model.ColumnName — the validated column-name domain primitive: non-blank, no control characters (the policy the writer, schema builder, and file parser all enforce — ColumnName.violation is the single source of truth). Field names are now strict on BOTH sides of the file boundary: the writer refuses blank/control names (IllegalArgumentException; NUL additionally crashes the reference toolchain's Arrow FFI), and the reader rejects files carrying them or duplicate field names (VortexException) — wire-legal is a floor, not a policy. Printable names of any shape ($$$$$, interior spaces, emoji) remain legal and round-trip against the reference implementation. (c993a355, dca815b9, d3b5b251)
  • core.model.LayoutId — typed layout identity with the same sealed shape as EncodingId (WellKnown constants plus Custom; layouts are runtime-pluggable in the reference implementation). The reader now recognizes vortex.zoned, the current canonical zone-map layout id in the Rust reference, alongside the legacy vortex.stats alias it keeps writing — files from current Rust writers scan and prune correctly. (7df3a0db)
  • Layout decode is pluggable: LayoutDecoder + LayoutRegistry (reader.layout) mirror the encoding registry — LayoutRegistry.builder().registerDefaults().register(custom).build() passed to the new VortexReader.open(path, readRegistry, layoutRegistry) / VortexHttpReader overloads dispatches every layout decode, container children included, through the registry. Programmatic registration only (no service file); unknown layouts fail loudly. Zone-map pruning and filtered scans recognize the built-in layouts only. (fc488d04, dd196f17)

Changed

  • The CLI uber-jar is now fully self-contained for vortex.zstd files: it bundles the FFM binding's native libzstd for all six platforms (osx/linux/windows × x86_64/aarch64) via zstd-platform — previously it shipped the binding's classes but relied on a system libzstd. The library modules keep zstd optional. (1983656b)

  • The zstd binding stays a two-artifact opt-in (io.github.dfa1.zstd:zstd + zstd-platform, both optional; no JNI anywhere), and touching vortex.zstd without it now fails with an actionable VortexException naming the two artifacts to add, instead of a raw NoClassDefFoundError. Default write/read paths never touch the binding (measured). (ae9f95cc)

  • The little-endian ValueLayout constants moved from PTypeIO.LE_* to VortexFormat.LE_* — endianness is a property of the wire format, not of ptypes, and VortexFormat is where format facts live. Six classes that carried private copies (including reversed-name duplicates like SHORT_LE) now share the single source; nothing outside VortexFormat defines a withOrder(LITTLE_ENDIAN) layout. (c060d34f)

  • Chunk.columns() returns an order-preserving SequencedMap<ColumnName, Chunk.Column> — one map instead of two parallel string-keyed maps, with each column's Array and DType traveling together in the Column carrier. column(String) stays as boundary sugar (plus a column(ColumnName) overload); iteration order is the schema/projection order, now guaranteed even for 1–2 column chunks. Typed names originate in ScanIterator from the file's already-certified schema. (f8ad15d1)

  • Compute.filteredSum over a dictionary-encoded filter column is ~20× faster (best runs ~30×): the predicate is resolved against the dictionary's value pool once and the raw u8 codes are scanned directly from their backing segments, instead of decoding every row through the per-element accessor — a fused SUM(measure) WHERE category = … over 100M rows drops from ~760 ms to ~38 ms. (85e251cc)

  • Compute.filteredAggregate takes the same dictionary code-scan lane (COUNT(*) included) — ~22× faster on the same workload (~980 ms → ~46 ms over 100M rows), which the Calcite WHERE-filtered aggregate push-down inherits on its boundary chunks. (145791c7, 6e6d7dd0)

  • A multi-column AND filter no longer forfeits the dictionary lane: the dict-encoded leaf drives the code scan and the remaining predicates are evaluated only on its matches — SUM(…) WHERE category = 7 AND price > 500 over 100M rows drops from ~2.3 s to ~200 ms (~11×). (12e13501)

  • core.model.EncodingId is now a sealed interface: the spec constants live in the nested WellKnown enum (re-exported, so EncodingId.VORTEX_FOO call sites compile unchanged) and Custom wraps any other wire string, which for the first time lets third-party EncodingDecoder/EncodingEncoder implementations declare ids outside the spec set. parse is total over non-blank ids — an unknown id yields a typed Custom instead of an empty Optional. (ea88a91b)

  • reader.decode.ArrayNode is a single record carrying the typed EncodingId; the KnownArrayNode/UnknownArrayNode split and the ArrayNode.of factory are gone. Decode dispatch, the allowUnknown passthrough, and error messages are unchanged. A crafted file with a blank encoding id now fails as VortexException instead of escaping as IllegalArgumentException. (21810d7e)

  • Layout and ZonedStatsSchema moved to the new reader.layout package, and Layout's misnamed String encodingId component is now LayoutId layoutId. Unknown layouts still fail loudly, now with a typed id in the error. (7df3a0db, b08ace79)

  • UnknownArray.encodingId is a typed EncodingId instead of a raw string — a Custom, or a WellKnown whose decoder is not registered. (7588aa31)