You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Pluggable extension types: register custom logical types on the read/write registries #530
Rust lets users add a logical type by implementing ExtVTable (id(), validate_dtype(), metadata/scalar handling) and registering it on the session (session.dtypes().register(Vector)); see Adding a new logical type.
Current state (not supported)
ExtensionId is a closed enum (VORTEX_DATE/TIME/TIMESTAMP/UUID), so a third-party id cannot even be named.
Read side: ExtensionDecoders are singletons dispatched by a case in Chunk.as(), not registry-managed.
Write side: WriteRegistry.Builder#register(ExtensionEncoder) exists but can only re-implement the four built-ins.
CLAUDE.md says DType is pluggable only via Extension, but today that only works for wire-known ids, and docs/reference.md documents the gap.
Proposal
Make ExtensionId a sealed WellKnown/Custom type, like EncodingId/LayoutId.
Register ExtensionDecoder on ReadRegistry.Builder (built-ins via registerDefaults()), replacing the switch in Chunk.as().
Add a storage-dtype validation hook (Rust's validate_dtype) on both sides.
Unknown extension ids: decode as the storage array (Rust default), with an opt-in/out policy to decide.
Interop test: a Rust-written custom extension (e.g. a Vector over FixedSizeList<f32>) read by Java, and vice versa.
Update docs/reference.md, CLAUDE.md and the CHANGELOG in the same change.
Out of scope: compute functions on custom types (Rust vortex-tensor-style scalar functions).
WriteRegistry is mostly ready: it keeps a Map<ExtensionId, ExtensionEncoder> with register(ExtensionEncoder) / lookup(ExtensionId), so it works unchanged once ExtensionId is sealed with a Custom variant. What needs to change:
VortexWriter (~line 554) resolves the encoder with ExtensionId.parse(extDtype.extensionId()).map(defaultRegistry::lookup).orElse(null). parse only knows the four built-in ids, so an unknown id yields empty and the lookup is skipped. It must resolve unknown ids to ExtensionId.Custom.
When no encoder is found, impl is null and a Collection is passed through unconverted, which fails later with a confusing error. Fail fast with a VortexException naming the id, or accept only raw storage arrays.
Add the storage-dtype validation hook (Rust's validate_dtype) to ExtensionEncoder, checked at VortexWriter.create time.
ExtensionEncoder.extensionId(), dtype(boolean) and encodeAll are generic enough for custom types as they are.
DateTimePartsEncodingEncoder compares the timestamp id as a string and is unaffected.
Implemented in #547. Follow-ups found along the way:
Unknown ids are strict, not passthrough. Rust errors with No such DType extension ID unless the session allows unknown ids (then it reads ForeignExtDType, i.e. storage). Pluggable extension types on the read and write registries (#530) #547 matches this: ReadRegistry.Builder#allowUnknown() is required. vortex-jni 0.88.0 exposes no allow_unknown, so it cannot read a file with a custom extension id.
Built-in validate checks are not ported. Rust's date/time/timestamp validate_dtype check storage ptypes (e.g. timestamp storage must be I64); the Java built-ins accept every dtype.
No Rust-writes/Java-reads test for a custom id. vortex-jni cannot write one, so this needs a cargo harness or a checked-in fixture.
Auto-routing a Collection applies to top-level columns only. An extension nested in a list or struct is written from its storage array.
Activity
dfa1 commentedon Oct 11, 2026
Write-side details
WriteRegistryis mostly ready: it keeps aMap<ExtensionId, ExtensionEncoder>withregister(ExtensionEncoder)/lookup(ExtensionId), so it works unchanged onceExtensionIdis sealed with aCustomvariant. What needs to change:VortexWriter(~line 554) resolves the encoder withExtensionId.parse(extDtype.extensionId()).map(defaultRegistry::lookup).orElse(null).parseonly knows the four built-in ids, so an unknown id yields empty and the lookup is skipped. It must resolve unknown ids toExtensionId.Custom.implis null and aCollectionis passed through unconverted, which fails later with a confusing error. Fail fast with aVortexExceptionnaming the id, or accept only raw storage arrays.validate_dtype) toExtensionEncoder, checked atVortexWriter.createtime.ExtensionEncoder.extensionId(),dtype(boolean)andencodeAllare generic enough for custom types as they are.DateTimePartsEncodingEncodercompares the timestamp id as a string and is unaffected.dfa1 commentedon Oct 17, 2026
Implemented in #547. Follow-ups found along the way:
No such DType extension IDunless the session allows unknown ids (then it readsForeignExtDType, i.e. storage). Pluggable extension types on the read and write registries (#530) #547 matches this:ReadRegistry.Builder#allowUnknown()is required. vortex-jni 0.88.0 exposes noallow_unknown, so it cannot read a file with a custom extension id.jsonfamily is missing. Tracked in Editions: gate extension dtypes and support crate-declared families (json) #548.validatechecks are not ported. Rust's date/time/timestampvalidate_dtypecheck storage ptypes (e.g. timestamp storage must beI64); the Java built-ins accept every dtype.Collectionapplies to top-level columns only. An extension nested in a list or struct is written from its storage array.fix: create(…, WriteRegistry) honors the registry's extension encoders
feat!: ExtensionId is a sealed WellKnown/Custom type