Skip to content

Commit 7742ecd

Browse files
dfa1claude
andcommitted
test(reader): cover DeltaEncodingDecoder null-meta + broadcast paths
The encoder property test drives the dense ptype/size matrix but always emits metadata and full-length children. Adds the meta==null empty-array fallback (every integer ptype) and the readLongs broadcast path where bases/deltas children hold a single constant value (capacity < count). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 38a22e3 commit 7742ecd

1 file changed

Lines changed: 103 additions & 0 deletions

File tree

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
package io.github.dfa1.vortex.reader.decode;
2+
3+
import io.github.dfa1.vortex.core.DType;
4+
import io.github.dfa1.vortex.core.PType;
5+
import io.github.dfa1.vortex.encoding.EncodingId;
6+
import io.github.dfa1.vortex.encoding.PTypeIO;
7+
import io.github.dfa1.vortex.encoding.TestSegments;
8+
import io.github.dfa1.vortex.proto.DeltaMetadata;
9+
import io.github.dfa1.vortex.reader.ReadRegistry;
10+
import io.github.dfa1.vortex.reader.array.Array;
11+
import io.github.dfa1.vortex.reader.array.LongArray;
12+
import org.junit.jupiter.api.Test;
13+
import org.junit.jupiter.params.ParameterizedTest;
14+
import org.junit.jupiter.params.provider.EnumSource;
15+
16+
import java.lang.foreign.Arena;
17+
import java.lang.foreign.MemorySegment;
18+
import java.nio.ByteBuffer;
19+
20+
import static org.assertj.core.api.Assertions.assertThat;
21+
22+
class DeltaEncodingDecoderTest {
23+
24+
private static final DeltaEncodingDecoder SUT = new DeltaEncodingDecoder();
25+
private static final ReadRegistry REGISTRY = TestRegistry.ofDecoders(SUT, new PrimitiveEncodingDecoder());
26+
27+
private static final int FL_CHUNK_SIZE = 1024;
28+
29+
@Test
30+
void encodingId_isFastlanesDelta() {
31+
// Given / When / Then
32+
assertThat(SUT.encodingId()).isEqualTo(EncodingId.FASTLANES_DELTA);
33+
}
34+
35+
@ParameterizedTest
36+
@EnumSource(value = PType.class, names = {"I8", "I16", "I32", "I64", "U8", "U16", "U32", "U64"})
37+
void decode_nullMetadata_returnsEmptyArray(PType ptype) {
38+
// Given no metadata — the decoder defaults to deltas_len=0 and short-circuits
39+
// to an empty array of the right ptype (a path the encoder never emits, since it
40+
// always writes metadata)
41+
ArrayNode node = ArrayNode.of(EncodingId.FASTLANES_DELTA, null, new ArrayNode[0], new int[0]);
42+
DecodeContext ctx = new DecodeContext(node, new DType.Primitive(ptype, false), 0,
43+
new MemorySegment[0], REGISTRY, Arena.global());
44+
45+
// When
46+
Array result = SUT.decode(ctx);
47+
48+
// Then
49+
assertThat(result.length()).isZero();
50+
}
51+
52+
@Test
53+
void decode_constantChildren_broadcastsAcrossChunk() {
54+
// Given a single delta chunk (1024 rows) whose bases and deltas children each hold
55+
// a single element, as a ConstantEncoding child would. readLongs must broadcast the
56+
// lone value across the whole chunk (capacity < count). Zero bases + zero deltas
57+
// means every decoded row is zero.
58+
PType ptype = PType.I64;
59+
long deltasLen = FL_CHUNK_SIZE;
60+
ByteBuffer meta = ByteBuffer.wrap(new DeltaMetadata(deltasLen, 0).encode());
61+
62+
ArrayNode bases = ArrayNode.of(EncodingId.VORTEX_PRIMITIVE, null, new ArrayNode[0], new int[]{0});
63+
ArrayNode deltas = ArrayNode.of(EncodingId.VORTEX_PRIMITIVE, null, new ArrayNode[0], new int[]{1});
64+
ArrayNode node = ArrayNode.of(EncodingId.FASTLANES_DELTA, meta, new ArrayNode[]{bases, deltas}, new int[0]);
65+
66+
// one element each → broadcast
67+
MemorySegment[] segs = {TestSegments.leLongs(0L), TestSegments.leLongs(0L)};
68+
DecodeContext ctx = new DecodeContext(node, new DType.Primitive(ptype, false), 4, segs, REGISTRY, Arena.global());
69+
70+
// When
71+
LongArray result = (LongArray) SUT.decode(ctx);
72+
73+
// Then
74+
assertThat(result.length()).isEqualTo(4);
75+
for (int i = 0; i < 4; i++) {
76+
assertThat(result.getLong(i)).as("index %d", i).isZero();
77+
}
78+
}
79+
80+
@Test
81+
void decode_constantBases_nonZeroOffsetAndBase() {
82+
// Given a constant base of 5 broadcast across all 16 lanes with zero deltas:
83+
// every row decodes to the base value 5. Reads from an offset into the chunk.
84+
PType ptype = PType.I64;
85+
ByteBuffer meta = ByteBuffer.wrap(new DeltaMetadata(FL_CHUNK_SIZE, 0).encode());
86+
87+
ArrayNode bases = ArrayNode.of(EncodingId.VORTEX_PRIMITIVE, null, new ArrayNode[0], new int[]{0});
88+
ArrayNode deltas = ArrayNode.of(EncodingId.VORTEX_PRIMITIVE, null, new ArrayNode[0], new int[]{1});
89+
ArrayNode node = ArrayNode.of(EncodingId.FASTLANES_DELTA, meta, new ArrayNode[]{bases, deltas}, new int[0]);
90+
91+
MemorySegment[] segs = {TestSegments.leLongs(5L), TestSegments.leLongs(0L)};
92+
DecodeContext ctx = new DecodeContext(node, new DType.Primitive(ptype, false), 3, segs, REGISTRY, Arena.global());
93+
94+
// When
95+
LongArray result = (LongArray) SUT.decode(ctx);
96+
97+
// Then prefix-sum of zero deltas over base 5 stays 5 on lane 0
98+
assertThat(result.getLong(0)).isEqualTo(5L);
99+
// sanity: materialized bytes are little-endian
100+
MemorySegment seg = result.materialize(Arena.global());
101+
assertThat(seg.get(PTypeIO.LE_LONG, 0)).isEqualTo(5L);
102+
}
103+
}

0 commit comments

Comments
 (0)