Commit 1b9714c
feat(fsst): rewire writer/reader adapters onto the fsst module
PR 5 of #287: reduce the two FSST production files to thin wire adapters
over the standalone vortex-fsst module. Only production code touched; no
test files changed (that is PR 6's scope). The wire format is unchanged.
Deleted (writer): the ~250-line inline SymbolTable/SymbolCandidate machinery
— training loop (trainSymbolTable), stratified sampling (sampleRowIndices),
the open-addressing longest-match index, candidate counting, and per-row
compress. All of that now lives in CompressorBuilder/Compressor/Matcher.
Deleted (reader): the byte-by-byte decompressString loop. Decoding now goes
through Decompressor (the unconditional 8-byte-store Algorithm 1).
Kept (both): all wire/metadata plumbing — UTF-8 conversion, ProtoFSSTMetadata
with narrowestUnsigned ptype selection, the EncodeNode/EncodeResult two-child
tree (uncompLens/codesOff), arena allocation of every output buffer, the
readUnsigned ptype dispatch, and the VarBinArray.OffsetMode result. The
TRAINING_SAMPLE_SEED reproducibility constant is retained and now feeds
CompressorBuilder.seed().
Wire-order code remapping (the subtle part — over-explained on purpose):
The Compressor numbers its codes in gain-descending order, but the vortex.fsst
wire format lays symbols out in length order (all multi-byte length-ascending,
then length-1 last; mirrors Rust FSSTData::validate_symbol_lengths). So the
writer must express BOTH the symbol table AND the code stream in wire codes.
wireOrder = compressor.codesSortedByLength() — wireOrder[i] is the internal
(gain-descending) code that belongs at wire position i.
internalToWire[wireOrder[i]] = i — the inverse permutation, mapping every
internal code the compressor emits to its wire position.
Symbol table buffers: at wire position i write packedSymbol(wireOrder[i]) and
symbolLength(wireOrder[i]) — the symbol living at wire slot i is internal
symbol wireOrder[i]. Code stream: compressor.compress emits internal codes;
each non-escape code byte c is rewritten to internalToWire[c]. The escape byte
0xFF and the single literal byte following it are copied through untouched —
the literal is raw data, not a code, and 0xFF never collides with a code since
codes are 0..254 (numSymbols <= 255). Reader side: it reads the wire symbol
table into code-indexed arrays and a code b in the stream indexes wire slot b
directly, so encoder position i and decoder index i refer to the same symbol —
the file is self-consistent and round-trips.
Verified: (1) writer,reader unit suite green with zero test changes (1713
tests); (2) checkstyle/javadoc gate green; (3) JavaWritesRustReadsIntegration
javaWriter_jniReader_fsstUtf8Column — the real Rust reference reader decodes
this encoder's output; (4) RustJavaReaderComparison fsst.vortex fixture (our
decoder reads a Rust-written FSST file) and FileSizeComparison
highCardinalityUtf8_javaVsJni (FSST still dispatch-selected) green; (5) full
reactor build green; plus a throwaway 11-row driver proving encode→remap→wire
symbol table→Decompressor reproduces every input byte-for-byte with the wire
table in valid length order.
Also fixed the empty-table read: with 0 trained symbols the writer floors the
symbol buffers to a 1-byte placeholder, so the decoder derives the symbol count
from symbolsBuf.byteSize()/8 (floors to 0) rather than symbolLensBuf.byteSize().
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>1 parent 76e26eb commit 1b9714c
4 files changed
Lines changed: 92 additions & 353 deletions
File tree
- reader
- src/main/java/io/github/dfa1/vortex/reader/decode
- writer
- src/main/java/io/github/dfa1/vortex/writer/encode
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
20 | 20 | | |
21 | 21 | | |
22 | 22 | | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
23 | 27 | | |
24 | 28 | | |
25 | 29 | | |
| |||
Lines changed: 28 additions & 25 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
9 | 9 | | |
10 | 10 | | |
11 | 11 | | |
| 12 | + | |
12 | 13 | | |
13 | 14 | | |
14 | 15 | | |
15 | 16 | | |
16 | 17 | | |
17 | 18 | | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
18 | 24 | | |
19 | 25 | | |
20 | | - | |
21 | | - | |
22 | 26 | | |
23 | 27 | | |
24 | 28 | | |
| |||
63 | 67 | | |
64 | 68 | | |
65 | 69 | | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
66 | 84 | | |
67 | 85 | | |
68 | 86 | | |
69 | 87 | | |
70 | 88 | | |
71 | | - | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
72 | 94 | | |
73 | 95 | | |
74 | 96 | | |
75 | 97 | | |
76 | 98 | | |
77 | 99 | | |
78 | 100 | | |
79 | | - | |
80 | | - | |
| 101 | + | |
81 | 102 | | |
82 | 103 | | |
83 | 104 | | |
84 | | - | |
85 | | - | |
86 | | - | |
87 | | - | |
88 | | - | |
89 | | - | |
90 | | - | |
91 | | - | |
92 | | - | |
93 | | - | |
94 | | - | |
95 | | - | |
96 | | - | |
97 | | - | |
98 | | - | |
99 | | - | |
100 | | - | |
101 | | - | |
102 | | - | |
103 | | - | |
| 105 | + | |
| 106 | + | |
104 | 107 | | |
105 | 108 | | |
106 | 109 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
20 | 20 | | |
21 | 21 | | |
22 | 22 | | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
23 | 27 | | |
24 | 28 | | |
25 | 29 | | |
| |||
0 commit comments