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.
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.