Skip to content

Commit d83ec1b

Browse files
dfa1claude
andcommitted
fix(reader): guard null patchValues in all LazySparsXxxArray
SparseEncodingDecoder passes null patchValues/patchIndices when numPatches == 0. All six lazy sparse records now short-circuit on null instead of NPE-ing on patchValues.length(). Update SparseEncodingEncoderTest to use typed array accessors (getLong/getDouble) instead of ArraySegments.of() — lazy sparse arrays have no contiguous backing segment. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 68186f8 commit d83ec1b

7 files changed

Lines changed: 78 additions & 14 deletions

File tree

reader/src/main/java/io/github/dfa1/vortex/reader/array/LazySparseByteArray.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,19 +20,31 @@ public record LazySparseByteArray(
2020

2121
@Override
2222
public byte getByte(long i) {
23+
if (patchValues == null) {
24+
return fillValue;
25+
}
2326
int p = SparseArrays.findPatch(patchIndices, patchValues.length(), i + offset);
2427
return p >= 0 ? patchValues.getByte(p) : fillValue;
2528
}
2629

2730
@Override
2831
public int getInt(long i) {
32+
if (patchValues == null) {
33+
return fillInt;
34+
}
2935
int p = SparseArrays.findPatch(patchIndices, patchValues.length(), i + offset);
3036
return p >= 0 ? patchValues.getInt(p) : fillInt;
3137
}
3238

3339
@Override
3440
public long fold(long identity, LongBinaryOperator op) {
3541
long[] acc = {identity};
42+
if (patchValues == null) {
43+
for (long r = 0; r < length; r++) {
44+
acc[0] = op.applyAsLong(acc[0], fillInt);
45+
}
46+
return acc[0];
47+
}
3648
long numPatches = patchValues.length();
3749
long absStart = offset;
3850
long absEnd = offset + length;

reader/src/main/java/io/github/dfa1/vortex/reader/array/LazySparseDoubleArray.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,21 @@ public record LazySparseDoubleArray(
2020

2121
@Override
2222
public double getDouble(long i) {
23+
if (patchValues == null) {
24+
return fillValue;
25+
}
2326
int p = SparseArrays.findPatch(patchIndices, patchValues.length(), i + offset);
2427
return p >= 0 ? patchValues.getDouble(p) : fillValue;
2528
}
2629

2730
@Override
2831
public void forEachDouble(DoubleConsumer c) {
32+
if (patchValues == null) {
33+
for (long r = 0; r < length; r++) {
34+
c.accept(fillValue);
35+
}
36+
return;
37+
}
2938
long numPatches = patchValues.length();
3039
long absStart = offset;
3140
long absEnd = offset + length;

reader/src/main/java/io/github/dfa1/vortex/reader/array/LazySparseFloatArray.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,22 @@ public record LazySparseFloatArray(
1919

2020
@Override
2121
public float getFloat(long i) {
22+
if (patchValues == null) {
23+
return fillValue;
24+
}
2225
int p = SparseArrays.findPatch(patchIndices, patchValues.length(), i + offset);
2326
return p >= 0 ? patchValues.getFloat(p) : fillValue;
2427
}
2528

2629
@Override
2730
public double fold(double identity, DoubleBinaryOperator op) {
2831
double[] acc = {identity};
32+
if (patchValues == null) {
33+
for (long r = 0; r < length; r++) {
34+
acc[0] = op.applyAsDouble(acc[0], fillValue);
35+
}
36+
return acc[0];
37+
}
2938
long numPatches = patchValues.length();
3039
long absStart = offset;
3140
long absEnd = offset + length;

reader/src/main/java/io/github/dfa1/vortex/reader/array/LazySparseIntArray.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,21 @@ public record LazySparseIntArray(
2020

2121
@Override
2222
public int getInt(long i) {
23+
if (patchValues == null) {
24+
return fillValue;
25+
}
2326
int p = SparseArrays.findPatch(patchIndices, patchValues.length(), i + offset);
2427
return p >= 0 ? patchValues.getInt(p) : fillValue;
2528
}
2629

2730
@Override
2831
public void forEachInt(IntConsumer c) {
32+
if (patchValues == null) {
33+
for (long r = 0; r < length; r++) {
34+
c.accept(fillValue);
35+
}
36+
return;
37+
}
2938
long numPatches = patchValues.length();
3039
long absStart = offset;
3140
long absEnd = offset + length;

reader/src/main/java/io/github/dfa1/vortex/reader/array/LazySparseLongArray.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,12 +28,21 @@ public record LazySparseLongArray(
2828

2929
@Override
3030
public long getLong(long i) {
31+
if (patchValues == null) {
32+
return fillValue;
33+
}
3134
int p = SparseArrays.findPatch(patchIndices, patchValues.length(), i + offset);
3235
return p >= 0 ? patchValues.getLong(p) : fillValue;
3336
}
3437

3538
@Override
3639
public void forEachLong(LongConsumer c) {
40+
if (patchValues == null) {
41+
for (long r = 0; r < length; r++) {
42+
c.accept(fillValue);
43+
}
44+
return;
45+
}
3746
long numPatches = patchValues.length();
3847
long absStart = offset;
3948
long absEnd = offset + length;

reader/src/main/java/io/github/dfa1/vortex/reader/array/LazySparseShortArray.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,19 +20,31 @@ public record LazySparseShortArray(
2020

2121
@Override
2222
public short getShort(long i) {
23+
if (patchValues == null) {
24+
return fillValue;
25+
}
2326
int p = SparseArrays.findPatch(patchIndices, patchValues.length(), i + offset);
2427
return p >= 0 ? patchValues.getShort(p) : fillValue;
2528
}
2629

2730
@Override
2831
public int getInt(long i) {
32+
if (patchValues == null) {
33+
return fillInt;
34+
}
2935
int p = SparseArrays.findPatch(patchIndices, patchValues.length(), i + offset);
3036
return p >= 0 ? patchValues.getInt(p) : fillInt;
3137
}
3238

3339
@Override
3440
public long fold(long identity, LongBinaryOperator op) {
3541
long[] acc = {identity};
42+
if (patchValues == null) {
43+
for (long r = 0; r < length; r++) {
44+
acc[0] = op.applyAsLong(acc[0], fillInt);
45+
}
46+
return acc[0];
47+
}
3648
long numPatches = patchValues.length();
3749
long absStart = offset;
3850
long absEnd = offset + length;

writer/src/test/java/io/github/dfa1/vortex/writer/encode/SparseEncodingEncoderTest.java

Lines changed: 18 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
import io.github.dfa1.vortex.core.PType;
55
import io.github.dfa1.vortex.reader.array.Array;
66
import io.github.dfa1.vortex.reader.array.BoolArray;
7+
import io.github.dfa1.vortex.reader.array.DoubleArray;
8+
import io.github.dfa1.vortex.reader.array.LongArray;
79
import io.github.dfa1.vortex.reader.array.VarBinArray;
810
import io.github.dfa1.vortex.reader.decode.ArrayNode;
911
import io.github.dfa1.vortex.encoding.DTypes;
@@ -85,9 +87,9 @@ void encode_roundTrip_i64() {
8587
EncodeResult encoded = ENCODER.encode(DTypes.I64, data, EncodeTestHelper.testCtx());
8688
Array decoded = decodeResult(encoded, DTypes.I64, data.length);
8789

90+
LongArray la = (LongArray) decoded;
8891
for (int i = 0; i < data.length; i++) {
89-
assertThat(((io.github.dfa1.vortex.reader.array.LongArray) decoded).getLong(i))
90-
.as("index %d", i).isEqualTo(data[i]);
92+
assertThat(la.getLong(i)).as("index %d", i).isEqualTo(data[i]);
9193
}
9294
}
9395

@@ -97,9 +99,9 @@ void encode_roundTrip_f64() {
9799
EncodeResult encoded = ENCODER.encode(DTypes.F64, data, EncodeTestHelper.testCtx());
98100
Array decoded = decodeResult(encoded, DTypes.F64, data.length);
99101

102+
DoubleArray da = (DoubleArray) decoded;
100103
for (int i = 0; i < data.length; i++) {
101-
assertThat(((io.github.dfa1.vortex.reader.array.DoubleArray) decoded).getDouble(i))
102-
.as("index %d", i).isEqualTo(data[i]);
104+
assertThat(da.getDouble(i)).as("index %d", i).isEqualTo(data[i]);
103105
}
104106
}
105107

@@ -214,9 +216,9 @@ void decode_noPatches_returnsFillValue() {
214216
Array result = DECODER.decode(ctx);
215217

216218
assertThat(result.length()).isEqualTo(5L);
219+
LongArray la = (LongArray) result;
217220
for (int i = 0; i < 5; i++) {
218-
assertThat(((io.github.dfa1.vortex.reader.array.LongArray) result).getLong(i))
219-
.as("index %d", i).isEqualTo(fill);
221+
assertThat(la.getLong(i)).as("index %d", i).isEqualTo(fill);
220222
}
221223
}
222224

@@ -229,9 +231,9 @@ void decode_withPatches_overwritesAtIndices() {
229231
Array result = DECODER.decode(ctx);
230232

231233
long[] expected = {0, 10, 0, 0, 0, 50, 0, 0};
234+
LongArray la = (LongArray) result;
232235
for (int i = 0; i < expected.length; i++) {
233-
assertThat(((io.github.dfa1.vortex.reader.array.LongArray) result).getLong(i))
234-
.as("index %d", i).isEqualTo(expected[i]);
236+
assertThat(la.getLong(i)).as("index %d", i).isEqualTo(expected[i]);
235237
}
236238
}
237239

@@ -242,10 +244,11 @@ void decode_f64_fillAndPatches() {
242244
DecodeContext ctx = buildSparseCtxF64(DTypes.F64, 4, fillVal, new long[]{2L}, new double[]{patchVal});
243245
Array result = DECODER.decode(ctx);
244246

245-
assertThat(((io.github.dfa1.vortex.reader.array.DoubleArray) result).getDouble(0)).isNaN();
246-
assertThat(((io.github.dfa1.vortex.reader.array.DoubleArray) result).getDouble(1)).isNaN();
247-
assertThat(((io.github.dfa1.vortex.reader.array.DoubleArray) result).getDouble(2)).isEqualTo(3.14);
248-
assertThat(((io.github.dfa1.vortex.reader.array.DoubleArray) result).getDouble(3)).isNaN();
247+
DoubleArray da = (DoubleArray) result;
248+
assertThat(da.getDouble(0)).isNaN();
249+
assertThat(da.getDouble(1)).isNaN();
250+
assertThat(da.getDouble(2)).isEqualTo(3.14);
251+
assertThat(da.getDouble(3)).isNaN();
249252
}
250253

251254
@Test
@@ -255,7 +258,7 @@ void decode_offsetSubtracted() {
255258
DecodeContext ctx = buildSparseCtxWithOffset(DTypes.I64, 5, 0L, PType.U32, patchIndices, patchValues, 10L);
256259
Array result = DECODER.decode(ctx);
257260

258-
assertThat(((io.github.dfa1.vortex.reader.array.LongArray) result).getLong(2)).isEqualTo(777L);
261+
assertThat(((LongArray) result).getLong(2)).isEqualTo(777L);
259262
}
260263

261264
@Test
@@ -265,8 +268,9 @@ void decode_nullValueFill_treatedAsZero() {
265268
DecodeContext ctx = buildCtx(DTypes.I64, 4, nullFill, meta, new byte[0], new byte[0]);
266269
Array result = DECODER.decode(ctx);
267270

271+
LongArray la = (LongArray) result;
268272
for (int i = 0; i < 4; i++) {
269-
assertThat(((io.github.dfa1.vortex.reader.array.LongArray) result).getLong(i)).as("index %d", i).isZero();
273+
assertThat(la.getLong(i)).as("index %d", i).isZero();
270274
}
271275
}
272276

0 commit comments

Comments
 (0)