Skip to content

Commit f4b22e4

Browse files
dfa1claude
andcommitted
refactor(core): canonical DType constants, drop per-class duplicates
Replace the no-arg non-nullable DType factories with shared immutable constants on the DType interface: - Primitives: DType.I8 … I64, U8 … U64, F16/F32/F64 - Others: DType.BOOL, UTF8, BINARY, NULL, VARIANT Removes the per-class private *_DTYPE fields in the encoders/decoders and the 130+ inline `new DType.X(false)` / factory call sites across all modules. Build a nullable column from a constant via asNullable(). Breaking (minor): the no-arg factories DType.i8()…f64(), bool_(), utf8(), binary(), null_(), variant() are removed. The decimal(..) / structBuilder() factories and the record constructors are unchanged. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent c26fd47 commit f4b22e4

127 files changed

Lines changed: 592 additions & 691 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.

CHANGELOG.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,16 @@ All notable changes to **vortex-java** are documented here.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
## [Unreleased]
9+
10+
### Added
11+
12+
- Canonical non-nullable `DType` constants: primitives `DType.I8``DType.I64`, `DType.U8``DType.U64`, `DType.F16`/`F32`/`F64`, plus `DType.BOOL`, `DType.UTF8`, `DType.BINARY`, `DType.NULL`, `DType.VARIANT`. Shared immutable instances — prefer them over `new DType.Primitive(pt, false)` / `new DType.Utf8(false)` etc.; build a nullable column with `DType.I64.asNullable()`. The encoders/decoders that each declared private `*_DTYPE` fields now reference these.
13+
14+
### Removed
15+
16+
- **Breaking (minor):** the no-arg factory methods `DType.i8()``DType.f64()`, `DType.bool_()`, `DType.utf8()`, `DType.binary()`, `DType.null_()`, `DType.variant()` are replaced by the constants above (`DType.i64()``DType.I64`, `DType.utf8()``DType.UTF8`). The `DType.decimal(..)`/`DType.structBuilder()` factories and the record constructors are unchanged.
17+
818
## [0.8.3] — 2026-06-23
919

1020
A **Sonar-driven refactoring** release: no new file-format capability, but a focused pass using SonarCloud findings to drive cleanups — dead code removed, duplication factored out, and one hot-loop micro-optimisation. Each finding was triaged (lead, not verdict) so the changes preserve behaviour and the JIT vectorisation of the hot decode loops. The interpretation framework behind this is now documented in `docs/testing.md`.

README.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -97,10 +97,10 @@ try (VortexReader vf = VortexReader.open(Path.of("data/example.vortex"));
9797

9898
```java
9999
DType.Struct schema = DType.structBuilder()
100-
.field("timestamp", DType.i64())
101-
.field("symbol", DType.utf8())
102-
.field("price", DType.f64())
103-
.field("volume", DType.i64().asNullable()) // boxed Long[] → nullable
100+
.field("timestamp", DType.I64)
101+
.field("symbol", DType.UTF8)
102+
.field("price", DType.F64)
103+
.field("volume", DType.I64.asNullable()) // boxed Long[] → nullable
104104
.build();
105105

106106
try (var ch = FileChannel.open(Path.of("data/example.vortex"),

cli/src/test/java/io/github/dfa1/vortex/cli/CliTestSupport.java

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package io.github.dfa1.vortex.cli;
22

33
import io.github.dfa1.vortex.core.DType;
4-
import io.github.dfa1.vortex.core.PType;
54
import io.github.dfa1.vortex.writer.VortexWriter;
65
import io.github.dfa1.vortex.writer.WriteOptions;
76

@@ -27,7 +26,7 @@ static Path writeSmallVortex(Path dir, String name) throws IOException {
2726
Path file = dir.resolve(name);
2827
DType.Struct schema = new DType.Struct(
2928
List.of("id"),
30-
List.of(new DType.Primitive(PType.I64, false)),
29+
List.of(DType.I64),
3130
false);
3231
try (FileChannel ch = FileChannel.open(file, StandardOpenOption.CREATE, StandardOpenOption.WRITE);
3332
VortexWriter writer = VortexWriter.create(ch, schema, WriteOptions.defaults())) {
@@ -44,10 +43,10 @@ static Path writeTypedVortex(Path dir, String name) throws IOException {
4443
DType.Struct schema = new DType.Struct(
4544
List.of("id", "qty", "price", "name"),
4645
List.of(
47-
new DType.Primitive(PType.I64, false),
48-
new DType.Primitive(PType.I32, false),
49-
new DType.Primitive(PType.F64, false),
50-
new DType.Utf8(false)),
46+
DType.I64,
47+
DType.I32,
48+
DType.F64,
49+
DType.UTF8),
5150
false);
5251
try (FileChannel ch = FileChannel.open(file, StandardOpenOption.CREATE, StandardOpenOption.WRITE);
5352
VortexWriter writer = VortexWriter.create(ch, schema, WriteOptions.defaults())) {

cli/src/test/java/io/github/dfa1/vortex/cli/SchemaCommandTest.java

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -64,17 +64,17 @@ void validFile_printsStructSchemaAndReturnsOk(@TempDir Path tmp) throws IOExcept
6464
}
6565

6666
static Stream<Arguments> dtypeCases() {
67-
DType i64 = new DType.Primitive(PType.I64, false);
67+
DType i64 = DType.I64;
6868
return Stream.of(
69-
arguments(new DType.Primitive(PType.I64, false), "I64"),
69+
arguments(DType.I64, "I64"),
7070
arguments(new DType.Primitive(PType.I32, true), "I32?"),
71-
arguments(new DType.Utf8(false), "utf8"),
71+
arguments(DType.UTF8, "utf8"),
7272
arguments(new DType.Utf8(true), "utf8?"),
73-
arguments(new DType.Binary(false), "binary"),
73+
arguments(DType.BINARY, "binary"),
7474
arguments(new DType.Binary(true), "binary?"),
75-
arguments(new DType.Bool(false), "bool"),
75+
arguments(DType.BOOL, "bool"),
7676
arguments(new DType.Bool(true), "bool?"),
77-
arguments(new DType.Null(false), "null"),
77+
arguments(DType.NULL, "null"),
7878
arguments(new DType.Decimal((byte) 10, (byte) 2, false), "decimal(10,2)"),
7979
arguments(new DType.Decimal((byte) 10, (byte) 2, true), "decimal(10,2)?"),
8080
arguments(new DType.List(i64, false), "list<I64>"),
@@ -83,9 +83,9 @@ static Stream<Arguments> dtypeCases() {
8383
arguments(new DType.FixedSizeList(i64, 4, true), "list<I64>[4]?"),
8484
arguments(new DType.Extension("vortex.uuid", i64, null, false), "ext<vortex.uuid>"),
8585
arguments(new DType.Extension("vortex.uuid", i64, null, true), "ext<vortex.uuid>?"),
86-
arguments(new DType.Variant(false), "variant"),
86+
arguments(DType.VARIANT, "variant"),
8787
arguments(new DType.Variant(true), "variant?"),
88-
arguments(new DType.Struct(List.of("a", "b"), List.of(i64, new DType.Utf8(false)), false),
88+
arguments(new DType.Struct(List.of("a", "b"), List.of(i64, DType.UTF8), false),
8989
"struct<a: I64, b: utf8>"));
9090
}
9191

cli/src/test/java/io/github/dfa1/vortex/cli/tui/ArrayFixtures.java

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -36,47 +36,47 @@ static LongArray longs(Arena arena, long... vs) {
3636
for (int i = 0; i < vs.length; i++) {
3737
seg.setAtIndex(ValueLayout.JAVA_LONG, i, vs[i]);
3838
}
39-
return new MaterializedLongArray(new DType.Primitive(PType.I64, false), vs.length, seg.asReadOnly());
39+
return new MaterializedLongArray(DType.I64, vs.length, seg.asReadOnly());
4040
}
4141

4242
static IntArray ints(Arena arena, int... vs) {
4343
MemorySegment seg = arena.allocate(vs.length * 4L, 4);
4444
for (int i = 0; i < vs.length; i++) {
4545
seg.setAtIndex(ValueLayout.JAVA_INT, i, vs[i]);
4646
}
47-
return new MaterializedIntArray(new DType.Primitive(PType.I32, false), vs.length, seg.asReadOnly());
47+
return new MaterializedIntArray(DType.I32, vs.length, seg.asReadOnly());
4848
}
4949

5050
static ShortArray shorts(Arena arena, short... vs) {
5151
MemorySegment seg = arena.allocate(vs.length * 2L, 2);
5252
for (int i = 0; i < vs.length; i++) {
5353
seg.setAtIndex(ValueLayout.JAVA_SHORT, i, vs[i]);
5454
}
55-
return new MaterializedShortArray(new DType.Primitive(PType.I16, false), vs.length, seg.asReadOnly());
55+
return new MaterializedShortArray(DType.I16, vs.length, seg.asReadOnly());
5656
}
5757

5858
static ByteArray bytes(Arena arena, byte... vs) {
5959
MemorySegment seg = arena.allocate(vs.length, 1);
6060
for (int i = 0; i < vs.length; i++) {
6161
seg.set(ValueLayout.JAVA_BYTE, i, vs[i]);
6262
}
63-
return new MaterializedByteArray(new DType.Primitive(PType.I8, false), vs.length, seg.asReadOnly());
63+
return new MaterializedByteArray(DType.I8, vs.length, seg.asReadOnly());
6464
}
6565

6666
static DoubleArray doubles(Arena arena, double... vs) {
6767
MemorySegment seg = arena.allocate(vs.length * 8L, 8);
6868
for (int i = 0; i < vs.length; i++) {
6969
seg.setAtIndex(ValueLayout.JAVA_DOUBLE, i, vs[i]);
7070
}
71-
return new MaterializedDoubleArray(new DType.Primitive(PType.F64, false), vs.length, seg.asReadOnly());
71+
return new MaterializedDoubleArray(DType.F64, vs.length, seg.asReadOnly());
7272
}
7373

7474
static FloatArray floats(Arena arena, float... vs) {
7575
MemorySegment seg = arena.allocate(vs.length * 4L, 4);
7676
for (int i = 0; i < vs.length; i++) {
7777
seg.setAtIndex(ValueLayout.JAVA_FLOAT, i, vs[i]);
7878
}
79-
return new MaterializedFloatArray(new DType.Primitive(PType.F32, false), vs.length, seg.asReadOnly());
79+
return new MaterializedFloatArray(DType.F32, vs.length, seg.asReadOnly());
8080
}
8181

8282
static BoolArray bools(Arena arena, boolean... vs) {
@@ -89,7 +89,7 @@ static BoolArray bools(Arena arena, boolean... vs) {
8989
seg.set(ValueLayout.JAVA_BYTE, byteIdx, (byte) (cur | (1 << (i & 7))));
9090
}
9191
}
92-
return new MaterializedBoolArray(new DType.Bool(false), vs.length, seg.asReadOnly());
92+
return new MaterializedBoolArray(DType.BOOL, vs.length, seg.asReadOnly());
9393
}
9494

9595
/// Builds a UTF-8 [VarBinArray] (`OffsetMode`, I64 offsets) from the given strings.
@@ -98,12 +98,12 @@ static VarBinArray utf8(Arena arena, String... vs) {
9898
for (int i = 0; i < vs.length; i++) {
9999
rows[i] = vs[i].getBytes(StandardCharsets.UTF_8);
100100
}
101-
return varbin(arena, new DType.Utf8(false), rows);
101+
return varbin(arena, DType.UTF8, rows);
102102
}
103103

104104
/// Builds a binary [VarBinArray] (`OffsetMode`, I64 offsets) from the given byte rows.
105105
static VarBinArray binary(Arena arena, byte[]... rows) {
106-
return varbin(arena, new DType.Binary(false), rows);
106+
return varbin(arena, DType.BINARY, rows);
107107
}
108108

109109
private static VarBinArray varbin(Arena arena, DType dtype, byte[]... rows) {

cli/src/test/java/io/github/dfa1/vortex/cli/tui/GridRenderTest.java

Lines changed: 11 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package io.github.dfa1.vortex.cli.tui;
22

33
import io.github.dfa1.vortex.core.DType;
4-
import io.github.dfa1.vortex.core.PType;
54
import io.github.dfa1.vortex.encoding.TimeUnit;
65
import io.github.dfa1.vortex.reader.array.Array;
76
import io.github.dfa1.vortex.reader.array.ByteArray;
@@ -27,9 +26,9 @@
2726
/// arrays without a terminal or fixture file.
2827
class GridRenderTest {
2928

30-
private static final DType I64 = new DType.Primitive(PType.I64, false);
31-
private static final DType F64 = new DType.Primitive(PType.F64, false);
32-
private static final DType BOOL = new DType.Bool(false);
29+
private static final DType I64 = DType.I64;
30+
private static final DType F64 = DType.F64;
31+
private static final DType BOOL = DType.BOOL;
3332

3433
@Test
3534
void nullArrayRendersEmpty() {
@@ -77,7 +76,7 @@ void rendersDateExtensionFromIntStorage() {
7776
try (Arena arena = Arena.ofConfined()) {
7877
// Given — vortex.date over I32 epoch-day storage: day 0 = 1970-01-01
7978
DType dateExt = new DType.Extension("vortex.date",
80-
new DType.Primitive(PType.I32, false), null, false);
79+
DType.I32, null, false);
8180
IntArray storage = ArrayFixtures.ints(arena, 0, 1);
8281

8382
// When / Then
@@ -117,7 +116,7 @@ void rendersUuidExtensionFromFixedSizeListStorage() {
117116
DType uuidExt = UuidExtensionDecoder.INSTANCE.dtype(false);
118117
ByteArray elems = ArrayFixtures.bytes(arena, new byte[16]);
119118
FixedSizeListArray storage = new FixedSizeListArray(
120-
new DType.FixedSizeList(new DType.Primitive(PType.U8, false), 16, false), 1, elems);
119+
new DType.FixedSizeList(DType.U8, 16, false), 1, elems);
121120

122121
// When / Then
123122
assertThat(GridRender.formatCell(storage, 0, uuidExt))
@@ -145,8 +144,8 @@ void rendersUtf8VarBinAsRawString() {
145144
Array sut = ArrayFixtures.utf8(arena, "héllo", "x");
146145

147146
// When / Then
148-
assertThat(GridRender.formatCell(sut, 0, new DType.Utf8(false))).isEqualTo("héllo");
149-
assertThat(GridRender.formatCell(sut, 1, new DType.Utf8(false))).isEqualTo("x");
147+
assertThat(GridRender.formatCell(sut, 0, DType.UTF8)).isEqualTo("héllo");
148+
assertThat(GridRender.formatCell(sut, 1, DType.UTF8)).isEqualTo("x");
150149
}
151150
}
152151

@@ -157,7 +156,7 @@ void rendersBinaryVarBinAsHex() {
157156
Array sut = ArrayFixtures.binary(arena, new byte[]{0x01, (byte) 0xab, 0x02});
158157

159158
// When / Then
160-
assertThat(GridRender.formatCell(sut, 0, new DType.Binary(false))).isEqualTo("0x01ab02");
159+
assertThat(GridRender.formatCell(sut, 0, DType.BINARY)).isEqualTo("0x01ab02");
161160
}
162161
}
163162

@@ -169,7 +168,7 @@ void truncatesBinaryHexBeyond16Bytes() {
169168
Array sut = ArrayFixtures.binary(arena, twenty);
170169

171170
// When
172-
String cell = GridRender.formatCell(sut, 0, new DType.Binary(false));
171+
String cell = GridRender.formatCell(sut, 0, DType.BINARY);
173172

174173
// Then — "0x" + 16*"00" + "..."
175174
assertThat(cell).isEqualTo("0x" + "00".repeat(16) + "...");
@@ -181,7 +180,7 @@ void extensionDecodeFailureRendersDiagnostic() {
181180
try (Arena arena = Arena.ofConfined()) {
182181
// Given — vortex.date declared but storage is F64, which epochInteger rejects
183182
DType dateExt = new DType.Extension("vortex.date",
184-
new DType.Primitive(PType.I32, false), null, false);
183+
DType.I32, null, false);
185184
Array badStorage = ArrayFixtures.doubles(arena, 1.5);
186185

187186
// When — the decode throws and is caught
@@ -197,7 +196,7 @@ void unknownExtensionIdFallsThroughToStorageRendering() {
197196
try (Arena arena = Arena.ofConfined()) {
198197
// Given — an extension id this reader does not know: render the raw storage value
199198
DType unknownExt = new DType.Extension("custom.thing",
200-
new DType.Primitive(PType.I32, false), null, false);
199+
DType.I32, null, false);
201200
IntArray storage = ArrayFixtures.ints(arena, 42);
202201

203202
// When / Then — no extension formatting, falls to the IntArray branch

cli/src/test/java/io/github/dfa1/vortex/cli/tui/InspectorRenderTest.java

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
/// in-memory arrays — no terminal, worker, or encoded fixture required.
2525
class InspectorRenderTest {
2626

27-
private static final DType I64 = new DType.Primitive(PType.I64, false);
27+
private static final DType I64 = DType.I64;
2828

2929
@Nested
3030
class FormatValue {
@@ -92,7 +92,7 @@ void rendersDateExtension() {
9292
try (Arena arena = Arena.ofConfined()) {
9393
// Given — vortex.date over I32 epoch-day storage
9494
DType dateExt = new DType.Extension("vortex.date",
95-
new DType.Primitive(PType.I32, false), null, false);
95+
DType.I32, null, false);
9696
IntArray storage = ArrayFixtures.ints(arena, 0);
9797

9898
// When / Then — day 0 = 1970-01-01
@@ -107,7 +107,7 @@ void rendersUtf8VarBinQuoted() {
107107
Array sut = ArrayFixtures.utf8(arena, "hi");
108108

109109
// When / Then
110-
assertThat(InspectorRender.formatValue(sut, 0, new DType.Utf8(false))).isEqualTo("\"hi\"");
110+
assertThat(InspectorRender.formatValue(sut, 0, DType.UTF8)).isEqualTo("\"hi\"");
111111
}
112112
}
113113

@@ -118,7 +118,7 @@ void rendersBinaryVarBinAsShortHex() {
118118
Array sut = ArrayFixtures.binary(arena, new byte[]{0x01, (byte) 0xab});
119119

120120
// When / Then
121-
assertThat(InspectorRender.formatValue(sut, 0, new DType.Binary(false))).isEqualTo("0x01ab");
121+
assertThat(InspectorRender.formatValue(sut, 0, DType.BINARY)).isEqualTo("0x01ab");
122122
}
123123
}
124124

@@ -127,7 +127,7 @@ void dateExtensionDecodeFailureFallsThroughToGeneric() {
127127
try (Arena arena = Arena.ofConfined()) {
128128
// Given — vortex.date declared but storage is F64; the decode throws and is swallowed
129129
DType dateExt = new DType.Extension("vortex.date",
130-
new DType.Primitive(PType.I32, false), null, false);
130+
DType.I32, null, false);
131131
Array badStorage = ArrayFixtures.doubles(arena, 1.5);
132132

133133
// When / Then — falls through to the generic DoubleArray rendering

cli/src/test/java/io/github/dfa1/vortex/cli/tui/TuiTestSupport.java

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package io.github.dfa1.vortex.cli.tui;
22

33
import io.github.dfa1.vortex.core.DType;
4-
import io.github.dfa1.vortex.core.PType;
54
import io.github.dfa1.vortex.reader.VortexHandle;
65
import io.github.dfa1.vortex.reader.VortexReader;
76
import io.github.dfa1.vortex.writer.VortexWriter;
@@ -31,9 +30,9 @@ static Path writeGridVortex(Path dir, String name, int rows) throws IOException
3130
DType.Struct schema = new DType.Struct(
3231
List.of("a", "b", "c"),
3332
List.of(
34-
new DType.Primitive(PType.I64, false),
35-
new DType.Primitive(PType.I64, false),
36-
new DType.Primitive(PType.I64, false)),
33+
DType.I64,
34+
DType.I64,
35+
DType.I64),
3736
false);
3837
long[] a = new long[rows];
3938
long[] b = new long[rows];
@@ -58,10 +57,10 @@ static Path writeMultiTypeVortex(Path dir, String name) throws IOException {
5857
DType.Struct schema = new DType.Struct(
5958
List.of("i", "d", "flag", "name"),
6059
List.of(
61-
new DType.Primitive(PType.I64, false),
62-
new DType.Primitive(PType.F64, false),
63-
new DType.Bool(false),
64-
new DType.Utf8(false)),
60+
DType.I64,
61+
DType.F64,
62+
DType.BOOL,
63+
DType.UTF8),
6564
false);
6665
try (FileChannel ch = FileChannel.open(file, StandardOpenOption.CREATE, StandardOpenOption.WRITE);
6766
VortexWriter writer = VortexWriter.create(ch, schema, WriteOptions.defaults())) {
@@ -90,7 +89,7 @@ static Path writeRichVortex(Path dir, String name, int rows) throws IOException
9089
Path file = dir.resolve(name);
9190
DType.Struct schema = new DType.Struct(
9291
List.of("label", "n"),
93-
List.of(new DType.Utf8(false), new DType.Primitive(PType.I64, false)),
92+
List.of(DType.UTF8, DType.I64),
9493
false);
9594
String[] labels = {"red", "green", "blue"};
9695
String[] label = new String[rows];

0 commit comments

Comments
 (0)