Skip to content

Commit f354409

Browse files
dfa1claude
andcommitted
refactor: ADR 0001 Phase 1 — move decode types to reader
DecodeContext, ArrayNode (+ KnownArrayNode/UnknownArrayNode), EncodingDecoder, and FlatSegmentDecoder move from core.encoding to reader.decode / reader. ReadRegistry replaces Registry.decode() as the canonical read dispatcher. Registry becomes pure metadata: Encoding descriptors + Extension lookup. VortexReader, VortexHttpReader, VortexHandle, ScanIterator all take ReadRegistry instead of Registry. The old decodeAsSegment() escape hatch on Registry is deleted. Test infra: - TestRegistry.ofDecoders(), TestDecodeContexts, DecodeTestHelper move to reader/test (new test-jar). Writer/test gains vortex-reader:test-jar dep. - ReadRegistryTest replaces the decode subset of RegistryTest. - EncodeTestHelper.toDecodeContext() removed from core; callers use DecodeTestHelper.toDecodeContext() from reader/test. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 947ea6c commit f354409

128 files changed

Lines changed: 1033 additions & 851 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

core/src/main/java/io/github/dfa1/vortex/encoding/EncodeNode.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import java.nio.ByteBuffer;
44

55
/// Describes the ArrayNode tree written into a flat segment's FlatBuffer.
6-
/// Mirrors [ArrayNode] for the encode path.
6+
/// Mirrors ArrayNode for the encode path.
77
///
88
/// @param encodingId encoding id for this node
99
/// @param metadata optional encoding-specific metadata bytes, or {@code null}

core/src/main/java/io/github/dfa1/vortex/encoding/Encoding.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
/// Metadata-only encoding descriptor. Carries only the wire identifier and the dtype
66
/// predicate. No encode or decode logic — those live in {@link EncodingEncoder}
7-
/// (writer module) and {@link EncodingDecoder} (reader module) respectively.
7+
/// (writer module) and EncodingDecoder (reader module) respectively.
88
///
99
/// <p>Register via {@link Registry} — implementations are discoverable via
1010
/// {@link java.util.ServiceLoader}.

core/src/main/java/io/github/dfa1/vortex/encoding/EncodingDecoder.java

Lines changed: 0 additions & 28 deletions
This file was deleted.

core/src/main/java/io/github/dfa1/vortex/encoding/EncodingId.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ public enum EncodingId {
9191
}
9292

9393
/// Parses a raw encoding id string into the matching constant.
94-
/// Used by [Registry] to discriminate [KnownArrayNode] from [UnknownArrayNode];
94+
/// Used by {@code ReadRegistry} to discriminate {@code KnownArrayNode} from {@code UnknownArrayNode};
9595
/// callers that demand a known id chain {@code .orElseThrow(...)}.
9696
///
9797
/// @param id raw encoding id string (e.g. {@code "vortex.primitive"})

core/src/main/java/io/github/dfa1/vortex/encoding/KnownArrayNode.java

Lines changed: 0 additions & 15 deletions
This file was deleted.
Lines changed: 17 additions & 127 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,33 @@
11
package io.github.dfa1.vortex.encoding;
22

33
import io.github.dfa1.vortex.core.VortexException;
4-
import io.github.dfa1.vortex.core.array.Array;
5-
import io.github.dfa1.vortex.core.array.ArraySegments;
6-
import io.github.dfa1.vortex.core.array.UnknownArray;
74
import io.github.dfa1.vortex.extension.Extension;
85
import io.github.dfa1.vortex.extension.ExtensionId;
96

10-
import java.lang.foreign.MemorySegment;
117
import java.util.HashMap;
128
import java.util.List;
139
import java.util.Map;
1410
import java.util.ServiceLoader;
1511

16-
/// Registry mapping wire identifiers to their library implementations:
17-
/// [Encoding] keyed by [EncodingId], [Extension] keyed by [ExtensionId],
18-
/// and [EncodingDecoder] keyed by [EncodingId].
12+
/// Metadata registry for {@link Encoding} descriptors and {@link Extension} implementations.
1913
///
20-
/// Instances are immutable after construction. Build one via [#builder()] or via the
21-
/// [#loadAll()], [#empty()], [#of(List)] convenience factories.
14+
/// <p>Holds only metadata-only {@link Encoding} objects and {@link Extension} impls.
15+
/// Decode dispatch lives in {@code ReadRegistry} (reader module); encode dispatch
16+
/// uses {@code Map<EncodingId, EncodingEncoder>} directly.
17+
///
18+
/// <p>Instances are immutable after construction. Build one via {@link #builder()} or via the
19+
/// {@link #loadAll()}, {@link #empty()}, {@link #of(List)} convenience factories.
2220
public final class Registry {
2321

2422
private final Map<EncodingId, Encoding> encodings;
2523
private final Map<ExtensionId, Extension> extensions;
26-
private final Map<EncodingId, EncodingDecoder> standaloneDecoders;
27-
private final boolean allowUnknown;
2824

2925
private Registry(
3026
Map<EncodingId, Encoding> encodings,
31-
Map<ExtensionId, Extension> extensions,
32-
Map<EncodingId, EncodingDecoder> standaloneDecoders,
33-
boolean allowUnknown
27+
Map<ExtensionId, Extension> extensions
3428
) {
3529
this.encodings = Map.copyOf(encodings);
3630
this.extensions = Map.copyOf(extensions);
37-
this.standaloneDecoders = Map.copyOf(standaloneDecoders);
38-
this.allowUnknown = allowUnknown;
3931
}
4032

4133
/// Returns a new {@link Builder}.
@@ -45,7 +37,7 @@ public static Builder builder() {
4537
return new Builder();
4638
}
4739

48-
/// Load all service-loaded [Encoding]s, [EncodingDecoder]s, and [Extension]s.
40+
/// Load all service-loaded {@link Encoding}s and {@link Extension}s.
4941
///
5042
/// @return an immutable {@link Registry} populated with all service-loaded entries
5143
public static Registry loadAll() {
@@ -71,45 +63,12 @@ public static Registry of(List<Encoding> encodings) {
7163
return b.build();
7264
}
7365

74-
/// Recursively wrap a node and its children as [UnknownArray]. Children of an unknown
75-
/// parent are always wrapped unknown regardless of whether their own id is recognised —
76-
/// matches Rust `decode_foreign` in `vortex-array/src/serde.rs`.
77-
private static UnknownArray decodeUnknown(DecodeContext ctx, ArrayNode node) {
78-
String rawId = switch (node) {
79-
case KnownArrayNode k -> k.encodingId().id();
80-
case UnknownArrayNode u -> u.rawEncodingId();
81-
};
82-
MemorySegment[] bufs = new MemorySegment[node.bufferIndices().length];
83-
for (int i = 0; i < bufs.length; i++) {
84-
bufs[i] = ctx.buffer(i);
85-
}
86-
Array[] children = new Array[node.children().length];
87-
for (int i = 0; i < children.length; i++) {
88-
ArrayNode childNode = node.children()[i];
89-
DecodeContext childCtx = new DecodeContext(
90-
childNode, ctx.dtype(), ctx.rowCount(),
91-
ctx.segmentBuffers(), ctx.registry(), ctx.arena());
92-
children[i] = decodeUnknown(childCtx, childNode);
93-
}
94-
return new UnknownArray(
95-
rawId, ctx.dtype(), ctx.rowCount(),
96-
node.metadata(), bufs, children);
97-
}
98-
99-
/// Returns whether passthrough decode for unknown encoding ids is enabled.
100-
///
101-
/// @return {@code true} if unknown encodings are silently wrapped as {@link io.github.dfa1.vortex.core.array.UnknownArray}
102-
public boolean isAllowUnknown() {
103-
return allowUnknown;
104-
}
105-
106-
/// Returns {@code true} if an encoding or decoder is registered for the given id.
66+
/// Returns {@code true} if a descriptor is registered for the given id.
10767
///
10868
/// @param encodingId the encoding id to query
109-
/// @return {@code true} if an {@link Encoding} or {@link EncodingDecoder} is registered
69+
/// @return {@code true} if an {@link Encoding} descriptor is registered
11070
public boolean hasEncoding(EncodingId encodingId) {
111-
return encodings.containsKey(encodingId)
112-
|| standaloneDecoders.containsKey(encodingId);
71+
return encodings.containsKey(encodingId);
11372
}
11473

11574
/// Returns the registered metadata-only encoding for {@code encodingId}, or {@code null}.
@@ -128,71 +87,17 @@ public Extension lookup(ExtensionId extensionId) {
12887
return extensions.get(extensionId);
12988
}
13089

131-
MemorySegment decodeAsSegment(DecodeContext ctx) {
132-
ArrayNode node = ctx.node();
133-
EncodingDecoder decoder = switch (node) {
134-
case KnownArrayNode k -> standaloneDecoders.get(k.encodingId());
135-
case UnknownArrayNode _ -> null;
136-
};
137-
if (decoder != null) {
138-
return ArraySegments.of(decoder.decode(ctx));
139-
}
140-
String id = switch (node) {
141-
case KnownArrayNode k -> k.encodingId().id();
142-
case UnknownArrayNode u -> u.rawEncodingId();
143-
};
144-
throw new VortexException("no encoding registered for " + id + " (or encoding has no primary segment)");
145-
}
146-
147-
/// Decodes the array described by {@code ctx}.
148-
///
149-
/// @param ctx the decode context
150-
/// @return the decoded {@link Array}
151-
public Array decode(DecodeContext ctx) {
152-
ArrayNode node = ctx.node();
153-
EncodingDecoder decoder = switch (node) {
154-
case KnownArrayNode k -> standaloneDecoders.get(k.encodingId());
155-
case UnknownArrayNode _ -> null;
156-
};
157-
if (decoder != null) {
158-
return decoder.decode(ctx);
159-
}
160-
if (allowUnknown) {
161-
return decodeUnknown(ctx, node);
162-
}
163-
String id = switch (node) {
164-
case KnownArrayNode k -> k.encodingId().id();
165-
case UnknownArrayNode u -> u.rawEncodingId();
166-
};
167-
throw new VortexException("no encoding registered for " + id);
168-
}
169-
170-
/// Builder for [Registry].
90+
/// Builder for {@link Registry}.
17191
///
172-
/// Not thread-safe. Build once, use everywhere — the produced [Registry] is immutable.
92+
/// Not thread-safe. Build once, use everywhere — the produced {@link Registry} is immutable.
17393
public static final class Builder {
17494

17595
private final Map<EncodingId, Encoding> encodings = new HashMap<>();
17696
private final Map<ExtensionId, Extension> extensions = new HashMap<>();
177-
private final Map<EncodingId, EncodingDecoder> standaloneDecoders = new HashMap<>();
178-
private boolean allowUnknown = false;
17997

18098
private Builder() {
18199
}
182100

183-
/// Registers a standalone read-only decoder.
184-
///
185-
/// @param decoder the {@link EncodingDecoder} to register
186-
/// @return this builder, for chaining
187-
/// @throws VortexException if a decoder for the same id is already registered
188-
public Builder register(EncodingDecoder decoder) {
189-
EncodingDecoder old = standaloneDecoders.put(decoder.encodingId(), decoder);
190-
if (old != null) {
191-
throw new VortexException("encoding decoder %s already registered".formatted(decoder.encodingId()));
192-
}
193-
return this;
194-
}
195-
196101
/// Registers a metadata-only encoding descriptor.
197102
///
198103
/// @param encoding the {@link Encoding} to register
@@ -219,7 +124,7 @@ public Builder register(Extension extension) {
219124
return this;
220125
}
221126

222-
/// Registers every [Encoding], [EncodingDecoder], and [Extension] discovered via
127+
/// Registers every {@link Encoding} and {@link Extension} discovered via
223128
/// {@link ServiceLoader}.
224129
///
225130
/// @return this builder, for chaining
@@ -231,29 +136,14 @@ public Builder registerServiceLoaded() {
231136
for (Extension extension : ServiceLoader.load(Extension.class)) {
232137
register(extension);
233138
}
234-
for (EncodingDecoder decoder : ServiceLoader.load(EncodingDecoder.class)) {
235-
register(decoder);
236-
}
237-
return this;
238-
}
239-
240-
/// Enable passthrough decode for unknown encoding ids.
241-
///
242-
/// Default is strict: unknown ids throw [VortexException]. When enabled, unknown nodes
243-
/// (and all their children, recursively) are wrapped as [UnknownArray] preserving raw
244-
/// metadata + buffers + stats. Mirrors Rust `VortexSession::allow_unknown()`.
245-
///
246-
/// @return this builder, for chaining
247-
public Builder allowUnknown() {
248-
this.allowUnknown = true;
249139
return this;
250140
}
251141

252-
/// Builds an immutable [Registry].
142+
/// Builds an immutable {@link Registry}.
253143
///
254144
/// @return the immutable registry
255145
public Registry build() {
256-
return new Registry(encodings, extensions, standaloneDecoders, allowUnknown);
146+
return new Registry(encodings, extensions);
257147
}
258148
}
259149
}

core/src/main/java/io/github/dfa1/vortex/encoding/SegmentBroadcast.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
import java.lang.foreign.MemorySegment;
66

7-
/// Element-offset helper for child segments returned by [DecodeContext#decodeChildSegment].
7+
/// Element-offset helper for child segments returned by DecodeContext#decodeChildSegment.
88
///
99
/// <p>{@link ConstantEncoding} deliberately stores a single element regardless of the array's
1010
/// declared {@code rowCount} (zip-bomb defense). Consumers that bulk-read child segments by
@@ -23,7 +23,7 @@ private SegmentBroadcast() {
2323
/// Returns the byte offset of logical element {@code i} in {@code seg}, broadcasting if
2424
/// {@code seg} holds fewer elements than {@code i}.
2525
///
26-
/// @param seg segment returned by [DecodeContext#decodeChildSegment]
26+
/// @param seg segment returned by DecodeContext#decodeChildSegment
2727
/// @param i logical element index in {@code [0, rowCount)}
2828
/// @param elemBytes element width in bytes (>= 1)
2929
/// @return byte offset suitable for {@link MemorySegment#get} or
@@ -43,7 +43,7 @@ public static long elementOffset(MemorySegment seg, long i, int elemBytes) {
4343
/// <p>Used by callers that want to detect the constant case explicitly — e.g. to hoist
4444
/// a single read out of a hot loop.
4545
///
46-
/// @param seg segment returned by [DecodeContext#decodeChildSegment]
46+
/// @param seg segment returned by DecodeContext#decodeChildSegment
4747
/// @param elemBytes element width in bytes (>= 1)
4848
/// @return physical element count
4949
public static long capacity(MemorySegment seg, int elemBytes) {

core/src/main/java/io/github/dfa1/vortex/encoding/UnknownArrayNode.java

Lines changed: 0 additions & 18 deletions
This file was deleted.
Lines changed: 1 addition & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,11 @@
11
package io.github.dfa1.vortex.encoding;
22

33
import java.lang.foreign.Arena;
4-
import java.lang.foreign.MemorySegment;
54
import java.util.HashMap;
6-
import java.util.List;
75
import java.util.Map;
86
import java.util.ServiceLoader;
97

10-
/// Converts an [EncodeResult] into a [DecodeContext] for roundtrip tests.
8+
/// Encode-side test utilities.
119
///
1210
/// Public so reader/ and writer/ test trees can reuse it via the core test-jar.
1311
public final class EncodeTestHelper {
@@ -27,29 +25,4 @@ public static EncodeContext testCtx() {
2725
}
2826
return EncodeContext.of(Arena.ofAuto(), Map.copyOf(encoders));
2927
}
30-
31-
/// Wraps a writer's {@link EncodeResult} into a {@link DecodeContext} for round-trip assertions.
32-
///
33-
/// @param result writer output
34-
/// @param rowCount logical row count
35-
/// @param dtype decoded dtype
36-
/// @param registry registry used for nested decode dispatch
37-
/// @return decode context ready for {@link EncodingDecoder#decode}
38-
public static DecodeContext toDecodeContext(
39-
EncodeResult result, long rowCount, io.github.dfa1.vortex.core.DType dtype,
40-
Registry registry
41-
) {
42-
List<MemorySegment> buffers = result.buffers();
43-
MemorySegment[] segments = buffers.toArray(new MemorySegment[0]);
44-
ArrayNode root = toArrayNode(result.rootNode());
45-
return new DecodeContext(root, dtype, rowCount, segments, registry, Arena.ofAuto());
46-
}
47-
48-
private static ArrayNode toArrayNode(EncodeNode enc) {
49-
ArrayNode[] children = new ArrayNode[enc.children().length];
50-
for (int i = 0; i < children.length; i++) {
51-
children[i] = toArrayNode(enc.children()[i]);
52-
}
53-
return ArrayNode.of(enc.encodingId(), enc.metadata(), children, enc.bufferIndices(), null);
54-
}
5528
}

0 commit comments

Comments
 (0)