feat: adopt the Vortex editions model - #320
Merged
Merged
Conversation
Adds a frozen, additive editions catalog mirroring the ground-truth data from vortex-data/vortex#8871 (the published spec's own registry section says "Coming soon", so the catalog was built directly from the upstream Rust source), a writer-side edition guard, and reader unknown-encoding UX naming which edition an id belongs to. core.model: - EditionId, Edition, EditionFamily (a closed CORE/UNSTABLE enum, not a sealed-interface-plus-Custom like EncodingId/LayoutId: a private edition family carries no real cross-implementation guarantee, so there's no legitimate custom-family use case) - Editions catalog: the 8 frozen/draft editions, cumulativeMembers(), owningEdition() writer: - WriteOptions gains an editions field, defaulting to the latest frozen core edition (core2026.07.0) - verified safe: the default cascade candidate list never includes the two unstable-family encoders vortex-java implements (Delta, Patched) - withEdition(Edition) / withoutEditionGuard() (the escape hatch) - VortexWriter's guard has two layers: it seeds the out-of-edition encoding ids into EncodeContext's existing exclusion set so CascadingCompressor gracefully steers away from them during cost-based selection (including nested competitions like a masked column's validity-bitmap cascade - reachable via ordinary cascading(depth) writes, not just an explicit custom encoder list), plus an after-the-fact backstop check for paths that don't consult the exclusion set at all reader: - ReadRegistry's unknown-encoding VortexException now names the edition an id belongs to and its minimum Vortex version (or that it's an unstable draft with no guarantee), or says it's unknown to every edition and points at allowUnknown() No wire-format changes - editions are a write-time/read-time policy, never persisted into the file (ADR 0023, which also records the mid-implementation course correction from a simple after-the-fact check to the two-layer filter+backstop design, and why EditionFamily ended up closed rather than mirroring EncodingId's Custom pattern). Mechanical fallout: WriteOptions' 8th record component required updating 29 direct-constructor call sites (Map.of(), preserving today's no-guard behavior) plus 3 existing test files that deliberately write unstable-family encoders through the explicit encoder-list overload (added .withoutEditionGuard()). Verified: full reactor build, full unit suite, full integration suite (including the real-world file-size comparison tests that exercise cascading writes extensively), core module javadoc build. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
dfa1
commented
Jul 27, 2026
|
|
||
| | Edition | Family | Min Vortex | Adds | | ||
| |---|---|---|---| | ||
| | `core2025.05.0` | `core` | 0.36.0 | 23 baseline: `fastlanes.bitpacked`/`for`, `vortex.alp`/`alprd`/`bool`/`bytebool`/`chunked`/`constant`/`datetimeparts`/`decimal`/`decimal_byte_parts`/`dict`/`ext`/`fsst`/`list`/`null`/`primitive`/`runend`/`sparse`/`struct`/`varbin`/`varbinview`/`zigzag` | |
Owner
Author
There was a problem hiding this comment.
this "Min Vortex" refers to upstream: let's drop it as it is meangiless for us
dfa1
commented
Jul 27, 2026
| /// @param year the four-digit year the edition was cut | ||
| /// @param month the month the edition was cut (1-12) | ||
| /// @param version distinguishes editions cut in the same month; normally `0` | ||
| public record EditionId(EditionFamily family, int year, int month, int version) { |
Owner
Author
There was a problem hiding this comment.
let's use YearMonth class from JDK
dfa1
commented
Jul 27, 2026
| /// @param minVortexVersion the minimum Vortex release whose reader supports every encoding in | ||
| /// this edition, or empty if this edition is a draft (see [#isDraft()]) | ||
| /// @param added the encodings that join the family at this edition | ||
| public record Edition(EditionId id, Optional<String> minVortexVersion, Set<EncodingId> added) { |
Owner
Author
There was a problem hiding this comment.
let's drop minVortexVersion => this refers to the JNI but it is not useful for us
dfa1
commented
Jul 27, 2026
| public static final Edition CORE_2025_05_0 = new Edition( | ||
| new EditionId(EditionFamily.CORE, 2025, 5, 0), | ||
| Optional.of("0.36.0"), | ||
| idSet( |
Owner
Author
There was a problem hiding this comment.
let's use EncodingId constants + EnumSet
dfa1
commented
Jul 27, 2026
| DType.Struct schema = new DType.Struct( | ||
| List.of(ColumnName.of("v")), List.of(new DType.Primitive(PType.I64, true)), false); | ||
| WriteOptions opts = new WriteOptions(2, true, 0.90, 0, true, false, 256L * 1024 * 1024); | ||
| WriteOptions opts = new WriteOptions(2, true, 0.90, 0, true, false, 256L * 1024 * 1024, java.util.Map.of()); |
Two review comments from the PR: - EditionId(EditionFamily, int year, int month, int version) -> EditionId(EditionFamily, YearMonth cutMonth, int version). java.time.YearMonth already validates the month range (throwing DateTimeException, arguably more idiomatic than a hand-rolled IllegalArgumentException for this exact domain) and is Comparable, so isAtOrBefore simplifies to a single compareTo plus the version tie-break. Drops the separate 4-digit-year guard - belt-and-braces validation on a value only ever constructed from 8 hardcoded catalog literals, not from untrusted input. - Editions' catalog now references the real EncodingId.WellKnown constants (EncodingId.VORTEX_ALP, ...) directly instead of raw wire strings parsed at runtime via EncodingId.parse(...). Matches this project's own "strings at the boundary, types inside" principle - these ids come from a hardcoded catalog, not wire input, so there was no boundary to parse a string at; using the constants also catches a typo'd id at compile time instead of it silently becoming an unintended EncodingId.Custom. Only the handful of unstable ids with no WellKnown constant yet still use EncodingId.Custom. Applied the same fix to EditionsTest's golden-set expected values. Also reverted EditionFamily#toString() (a mid-session addition) back to the enum default: overriding toString() to return something other than the declared constant name is surprising and breaks valueOf(family.toString()). EditionId#toString() now lowercases the family name explicitly at the one call site that needs the wire form. ADR 0023 and docs/reference.md updated to match. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
dfa1
commented
Jul 27, 2026
| /// third-party or experimental ones, at the cost of the edition read-compatibility guarantee. | ||
| /// | ||
| /// @return a new `WriteOptions` with no edition guard at all | ||
| public WriteOptions withoutEditionGuard() { |
Owner
Author
There was a problem hiding this comment.
let's drop this escape hatch => this defeats the purpose
6 tasks
dfa1
added a commit
that referenced
this pull request
Jul 27, 2026
- Drop Edition.minVortexVersion: it names the Rust reference's own release train, not vortex-java's, so it was never meaningful here. - Drop WriteOptions#withoutEditionGuard(): the only escape hatch was guard-wide and untracked. Reaching an unstable-family encoding now requires enabling that family's edition explicitly via withEdition(Editions.UNSTABLE_...), which is both narrower and self-documenting. Updates the 9 test call sites that used the old escape hatch to reach fastlanes.delta/vortex.patched. - Implement ReadRegistry's edition-aware "no decoder registered" message, which was documented in the ADR/CHANGELOG/docs but never actually wired into decode()/decodeAsSegment() in the original PR. Add coverage for the core-edition, unstable-edition, and unknown-to-all-editions message branches, plus decodeAsSegment's error path (previously untested). - java.util.Map.of() -> Map.of() (redundant qualification) across 11 files. - Update ADR 0023 and CHANGELOG.md to match the final design; sweep docs/reference.md and docs/compatibility.md for stale references. Addresses the 6 review comments left on #320 (issue #301). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adopts the Vortex editions model: a frozen, additive catalog of encoding-id sets, a writer-side guard against emitting an encoding outside the declared compatibility guarantee, and reader-side unknown-encoding UX naming which edition an id belongs to.
Ground-truth data was fetched directly from the real Rust source in vortex-data/vortex#8871, since the published spec's own "Edition registry" section says "Coming soon". vortex-java already implements every
core-family encoding throughcore2026.07.0; of theunstablefamily, onlyfastlanes.delta/vortex.patchedare implemented.core.modelEditionId,Edition,EditionFamily—EditionFamilyis a closedCORE/UNSTABLEenum, deliberately not mirroringEncodingId/LayoutId's sealed-interface-plus-Customshape: a private edition family carries no real cross-implementation guarantee, so there's no legitimate custom-family use case (see ADR 0023's Alternatives section).Editionscatalog: the 8 frozen/draft editions,cumulativeMembers(),owningEdition().writerWriteOptions.editionsdefaults to the latest frozencoreedition (core2026.07.0) — verified safe: the default cascade candidate list never includes the twounstable-family encoders vortex-java implements.withEdition(Edition)/withoutEditionGuard()(the escape hatch).VortexWriter's guard is two-layered — a mid-implementation course correction documented in ADR 0023: it seeds out-of-edition encoding ids intoEncodeContext's existing exclusion set soCascadingCompressorgracefully steers away from them during cost-based selection (this matters becausefastlanes.deltais reachable through an ordinarycascading(depth)write viaMaskedEncodingEncoder's nested validity-mask competition, not just an explicit custom encoder list), plus an after-the-fact backstop check for any selection path that doesn't consult the exclusion set at all.readerReadRegistry's unknown-encoding error now names the edition an id belongs to and its minimum Vortex version (or that it's anunstabledraft with no guarantee), or says it's unknown to every edition and points atallowUnknown().No wire-format changes
Editions are a write-time/read-time policy — nothing is persisted into the file. Matches upstream's own model.
Mechanical fallout
WriteOptionsgaining an 8th record component required:Map.of()(preserves today's no-guard behavior, zero behavior change).unstable-family encoders through the explicit encoder-list overload, updated with.withoutEditionGuard().Test plan
./mvnw verify -DskipTests(full reactor compile)./mvnw test(full unit suite, all modules)./mvnw verify -pl integration -am(full integration suite, including the real-world file-size comparison tests that exercise cascading writes extensively)./mvnw javadoc:javadoc -pl core(zero errors on new types; pre-existing unrelated warnings only)EditionIdTest,EditionsTest(golden cumulative-member sets pinning the ground-truth catalog),WriterEditionGuardTest(backstop throw + escape hatch + explicit unstable opt-in),MaskedValidityCascadeEditionExclusionTest(the exact filtering mechanism, proven against a real cost-based win/exclude pair)Closes #301.
🤖 Generated with Claude Code