Skip to content

Commit 0203387

Browse files
dfa1claude
andcommitted
feat(encoding): implement vortex.ext encoder
Override accepts() and encode() so the writer can encode Extension dtypes. Encode delegates the storage child to PrimitiveEncoding, then wraps in a vortex.ext node. Adds round-trip and accepts() unit tests. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 7383de4 commit 0203387

2 files changed

Lines changed: 85 additions & 6 deletions

File tree

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

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,24 +4,33 @@
44
import io.github.dfa1.vortex.core.VortexException;
55
import io.github.dfa1.vortex.core.array.Array;
66

7-
/// Decoder for {@code vortex.ext} — extension type transparent storage wrapper.
7+
/// Encoder/decoder for {@code vortex.ext} — extension type transparent storage wrapper.
88
///
9-
/// <p>No buffers, empty metadata. Child slot 0: the storage array decoded using
9+
/// <p>No buffers, empty metadata. Child slot 0: the storage array encoded/decoded using
1010
/// the extension dtype's storage dtype and same row count.
1111
///
12-
/// <p>Decode: unwraps the single child and returns it directly.
13-
/// The extension type information (name, metadata) is carried by {@link DType.Extension}
14-
/// on the parent but is not needed for decoding.
12+
/// <p>Encode: delegates to {@link PrimitiveEncoding} for the storage child, wraps in ext node.
13+
/// Decode: unwraps the single child and returns it directly.
1514
public final class ExtEncoding implements Encoding {
1615

1716
@Override
1817
public EncodingId encodingId() {
1918
return EncodingId.VORTEX_EXT;
2019
}
2120

21+
@Override
22+
public boolean accepts(DType dtype) {
23+
return dtype instanceof DType.Extension;
24+
}
25+
2226
@Override
2327
public EncodeResult encode(DType dtype, Object data) {
24-
throw new UnsupportedOperationException("encode not supported by " + encodingId());
28+
if (!(dtype instanceof DType.Extension ext)) {
29+
throw new VortexException(EncodingId.VORTEX_EXT, "expected extension dtype, got " + dtype);
30+
}
31+
EncodeResult childResult = new PrimitiveEncoding().encode(ext.storageDType(), data);
32+
EncodeNode root = new EncodeNode(EncodingId.VORTEX_EXT, null, new EncodeNode[]{childResult.rootNode()}, new int[0]);
33+
return new EncodeResult(root, childResult.buffers(), childResult.statsMin(), childResult.statsMax());
2534
}
2635

2736
@Override

core/src/test/java/io/github/dfa1/vortex/encoding/ExtEncodingTest.java

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import io.github.dfa1.vortex.core.DType;
44
import io.github.dfa1.vortex.core.PType;
55
import io.github.dfa1.vortex.core.array.LongArray;
6+
import org.junit.jupiter.api.Nested;
67
import org.junit.jupiter.api.Test;
78

89
import java.lang.foreign.Arena;
@@ -17,6 +18,74 @@ class ExtEncodingTest {
1718
private static final ValueLayout.OfLong LE_LONG =
1819
ValueLayout.JAVA_LONG_UNALIGNED.withOrder(ByteOrder.LITTLE_ENDIAN);
1920

21+
@Nested
22+
class Encode {
23+
24+
@Test
25+
void accepts_extensionDtype_returnsTrue() {
26+
// Given
27+
DType extDType = new DType.Extension("vortex.timestamp",
28+
new DType.Primitive(PType.I64, false), null, false);
29+
var sut = new ExtEncoding();
30+
31+
// When / Then
32+
assertThat(sut.accepts(extDType)).isTrue();
33+
}
34+
35+
@Test
36+
void accepts_primitiveDtype_returnsFalse() {
37+
// Given
38+
var sut = new ExtEncoding();
39+
40+
// When / Then
41+
assertThat(sut.accepts(new DType.Primitive(PType.I64, false))).isFalse();
42+
}
43+
44+
@Test
45+
void encode_extensionWrappingI64_roundTrips() {
46+
// Given
47+
long[] data = {100L, 200L, 300L, 400L};
48+
DType storageDType = new DType.Primitive(PType.I64, false);
49+
DType extDType = new DType.Extension("vortex.timestamp", storageDType, null, false);
50+
var sut = new ExtEncoding();
51+
52+
// When
53+
EncodeResult result = sut.encode(extDType, data);
54+
55+
// Then — root is ext, child is primitive
56+
assertThat(result.rootNode().encodingId()).isEqualTo(EncodingId.VORTEX_EXT);
57+
assertThat(result.rootNode().children()).hasSize(1);
58+
assertThat(result.rootNode().children()[0].encodingId()).isEqualTo(EncodingId.VORTEX_PRIMITIVE);
59+
60+
// Decode back
61+
EncodingRegistry registry = EncodingRegistry.empty();
62+
registry.register(new PrimitiveEncoding());
63+
registry.register(new ExtEncoding());
64+
ArrayNode rootNode = encodeNodeToArrayNode(result.rootNode());
65+
DecodeContext ctx = new DecodeContext(
66+
rootNode, extDType, data.length,
67+
result.buffers().toArray(MemorySegment[]::new),
68+
registry, Arena.ofAuto());
69+
var decoded = sut.decode(ctx);
70+
71+
assertThat(decoded).isInstanceOf(LongArray.class);
72+
for (int i = 0; i < data.length; i++) {
73+
assertThat(decoded.getLong(i)).isEqualTo(data[i]);
74+
}
75+
}
76+
77+
private ArrayNode encodeNodeToArrayNode(EncodeNode n) {
78+
ArrayNode[] children = new ArrayNode[n.children().length];
79+
for (int i = 0; i < children.length; i++) {
80+
children[i] = encodeNodeToArrayNode(n.children()[i]);
81+
}
82+
return new ArrayNode(n.encodingId(), n.metadata(), children, n.bufferIndices(), null);
83+
}
84+
}
85+
86+
@Nested
87+
class Decode {
88+
2089
@Test
2190
void decode_extensionWrappingI64_returnsStorageArray() {
2291
// Given
@@ -56,4 +125,5 @@ void decode_extensionWrappingI64_returnsStorageArray() {
56125
assertThat(result.getLong(i)).isEqualTo(values[i]);
57126
}
58127
}
128+
}
59129
}

0 commit comments

Comments
 (0)