55import dev .vortex .jni .NativeLoader ;
66import io .github .dfa1 .vortex .core .DType ;
77import io .github .dfa1 .vortex .core .PType ;
8- import io .github .dfa1 .vortex .core .array .DoubleArray ;
98import io .github .dfa1 .vortex .core .array .LongArray ;
109import io .github .dfa1 .vortex .encoding .EncodingRegistry ;
1110import io .github .dfa1 .vortex .io .VortexReader ;
1817import org .apache .arrow .vector .BigIntVector ;
1918import org .apache .arrow .vector .DateDayVector ;
2019import org .apache .arrow .vector .Float8Vector ;
20+ import org .apache .arrow .vector .VarCharVector ;
2121import org .apache .arrow .vector .VectorSchemaRoot ;
2222import org .apache .arrow .vector .types .DateUnit ;
2323import org .apache .arrow .vector .types .FloatingPointPrecision ;
2727import org .junit .jupiter .api .Test ;
2828import org .junit .jupiter .api .io .TempDir ;
2929
30+ import java .io .BufferedWriter ;
3031import java .io .IOException ;
3132import java .nio .channels .FileChannel ;
33+ import java .nio .charset .StandardCharsets ;
3234import java .nio .file .Files ;
3335import java .nio .file .Path ;
3436import java .nio .file .StandardOpenOption ;
37+ import java .time .LocalDate ;
3538import java .util .HashMap ;
3639import java .util .List ;
3740import java .util .Map ;
3841
3942import static org .assertj .core .api .Assertions .assertThat ;
4043
41- /// Verifies that Java and JNI writers produce valid, readable files for the same numeric OHLC dataset,
42- /// and captures file sizes for regression visibility.
44+ /// File-size comparison: CSV (baseline) vs JNI/Rust writer (oracle) vs Java writer (SUT).
4345///
44- /// Schema: date(I32), open/high/low/close(F64), volume(I64) — 6 columns, no strings.
45- class WriteFileSizeIntegrationTest {
46+ /// Schema: symbol(Utf8), date(I32), open/high/low/close(F64), volume(I64) — 7 columns.
47+ /// Both Vortex writers use cascading(3) to match the Rust default compression pipeline.
48+ class FileSizeComparisonIntegrationTest {
4649
4750 private static final int TOTAL_ROWS = 100_000 ;
4851 private static final int BATCH_SIZE = 50_000 ;
4952
50- private static final ArrowType F64_TYPE = new ArrowType .FloatingPoint (FloatingPointPrecision .DOUBLE );
51- private static final Schema JNI_SCHEMA = new Schema (List .of (
52- Field .notNullable ("date" , new ArrowType .Date (DateUnit .DAY )),
53- Field .notNullable ("open" , F64_TYPE ),
54- Field .notNullable ("high" , F64_TYPE ),
55- Field .notNullable ("low" , F64_TYPE ),
56- Field .notNullable ("close" , F64_TYPE ),
57- Field .notNullable ("volume" , new ArrowType .Int (64 , true ))
58- ));
59-
6053 private static final DType .Struct JAVA_SCHEMA = new DType .Struct (
61- List .of ("date" , "open" , "high" , "low" , "close" , "volume" ),
54+ List .of ("symbol" , " date" , "open" , "high" , "low" , "close" , "volume" ),
6255 List .of (
56+ new DType .Utf8 (false ),
6357 new DType .Primitive (PType .I32 , false ),
6458 new DType .Primitive (PType .F64 , false ),
6559 new DType .Primitive (PType .F64 , false ),
@@ -70,42 +64,71 @@ class WriteFileSizeIntegrationTest {
7064 false
7165 );
7266
67+ private static final Schema JNI_SCHEMA = new Schema (List .of (
68+ Field .notNullable ("symbol" , new ArrowType .Utf8 ()),
69+ Field .notNullable ("date" , new ArrowType .Date (DateUnit .DAY )),
70+ Field .notNullable ("open" , new ArrowType .FloatingPoint (FloatingPointPrecision .DOUBLE )),
71+ Field .notNullable ("high" , new ArrowType .FloatingPoint (FloatingPointPrecision .DOUBLE )),
72+ Field .notNullable ("low" , new ArrowType .FloatingPoint (FloatingPointPrecision .DOUBLE )),
73+ Field .notNullable ("close" , new ArrowType .FloatingPoint (FloatingPointPrecision .DOUBLE )),
74+ Field .notNullable ("volume" , new ArrowType .Int (64 , true ))
75+ ));
76+
7377 private static final Session SESSION = Session .create ();
7478 private static final BufferAllocator ALLOCATOR = ArrowAllocation .rootAllocator ();
7579
7680 static {
7781 NativeLoader .loadJni ();
7882 }
7983
80-
8184 // ── Writers ───────────────────────────────────────────────────────────────
8285
83- private static void writeJava (Path file , List <OhlcGenerator .OhlcBatch > batches ) throws IOException {
86+ private static Path writeCsv (Path dir , List <OhlcGenerator .OhlcBatch > batches ) throws IOException {
87+ Path file = dir .resolve ("ohlc.csv" );
88+ try (BufferedWriter csv = Files .newBufferedWriter (file )) {
89+ csv .write ("symbol,date,open,high,low,close,volume\n " );
90+ for (OhlcGenerator .OhlcBatch b : batches ) {
91+ for (int i = 0 ; i < b .dates ().length ; i ++) {
92+ csv .write (b .symbols ()[i ] + "," + LocalDate .ofEpochDay (b .dates ()[i ]) + ","
93+ + b .open ()[i ] + "," + b .high ()[i ] + ","
94+ + b .low ()[i ] + "," + b .close ()[i ] + "," + b .volume ()[i ] + "\n " );
95+ }
96+ }
97+ }
98+ return file ;
99+ }
100+
101+ private static Path writeJava (Path dir , List <OhlcGenerator .OhlcBatch > batches ) throws IOException {
102+ Path file = dir .resolve ("ohlc-java.vtx" );
84103 try (FileChannel ch = FileChannel .open (file , StandardOpenOption .CREATE , StandardOpenOption .WRITE );
85104 VortexWriter writer = VortexWriter .create (ch , JAVA_SCHEMA , WriteOptions .cascading (3 ))) {
86105 for (OhlcGenerator .OhlcBatch b : batches ) {
87106 writer .writeChunk (Map .of (
88- "date " , b .dates (), "open " , b .open (), "high" , b . high (),
89- "low " , b .low (), "close " , b .close (), "volume" , b . volume ()
90- ));
107+ "symbol " , b .symbols (), "date " , b .dates (),
108+ "open " , b .open (), "high " , b .high (),
109+ "low" , b . low (), "close" , b . close (), "volume" , b . volume () ));
91110 }
92111 }
112+ return file ;
93113 }
94114
95- private static void writeJni (Path file , List <OhlcGenerator .OhlcBatch > batches ) throws IOException {
115+ private static Path writeJni (Path dir , List <OhlcGenerator .OhlcBatch > batches ) throws IOException {
116+ Path file = dir .resolve ("ohlc-jni.vtx" );
96117 String uri = file .toAbsolutePath ().toUri ().toString ();
97118 try (dev .vortex .api .VortexWriter writer = dev .vortex .api .VortexWriter .create (
98119 SESSION , uri , JNI_SCHEMA , new HashMap <>(), ALLOCATOR )) {
99120 for (OhlcGenerator .OhlcBatch b : batches ) {
100121 try (VectorSchemaRoot root = VectorSchemaRoot .create (JNI_SCHEMA , ALLOCATOR )) {
101- DateDayVector dateVec = (DateDayVector ) root .getVector ("date" );
102- Float8Vector openVec = (Float8Vector ) root .getVector ("open" );
103- Float8Vector highVec = (Float8Vector ) root .getVector ("high" );
104- Float8Vector lowVec = (Float8Vector ) root .getVector ("low" );
105- Float8Vector closeVec = (Float8Vector ) root .getVector ("close" );
106- BigIntVector volVec = (BigIntVector ) root .getVector ("volume" );
122+ VarCharVector symbolVec = (VarCharVector ) root .getVector ("symbol" );
123+ DateDayVector dateVec = (DateDayVector ) root .getVector ("date" );
124+ Float8Vector openVec = (Float8Vector ) root .getVector ("open" );
125+ Float8Vector highVec = (Float8Vector ) root .getVector ("high" );
126+ Float8Vector lowVec = (Float8Vector ) root .getVector ("low" );
127+ Float8Vector closeVec = (Float8Vector ) root .getVector ("close" );
128+ BigIntVector volVec = (BigIntVector ) root .getVector ("volume" );
107129
108130 int n = b .dates ().length ;
131+ symbolVec .allocateNew ();
109132 dateVec .allocateNew (n );
110133 openVec .allocateNew (n );
111134 highVec .allocateNew (n );
@@ -114,6 +137,7 @@ private static void writeJni(Path file, List<OhlcGenerator.OhlcBatch> batches) t
114137 volVec .allocateNew (n );
115138
116139 for (int i = 0 ; i < n ; i ++) {
140+ symbolVec .setSafe (i , b .symbols ()[i ].getBytes (StandardCharsets .UTF_8 ));
117141 dateVec .setSafe (i , b .dates ()[i ]);
118142 openVec .setSafe (i , b .open ()[i ]);
119143 highVec .setSafe (i , b .high ()[i ]);
@@ -131,77 +155,49 @@ private static void writeJni(Path file, List<OhlcGenerator.OhlcBatch> batches) t
131155 }
132156 }
133157 }
158+ return file ;
134159 }
135160
136- // ── Tests ─────────────────────────────────────────────────────────────────
161+ // ── Test ─ ─────────────────────────────────────────────────────────────────
137162
138163 @ Test
139- void javaFile_isReadable_withCorrectRowCount (@ TempDir Path tmp ) throws IOException {
164+ void fileSizeComparison (@ TempDir Path tmp ) throws IOException {
140165 // Given
141- Path file = tmp .resolve ("ohlc-java.vtx" );
142166 List <OhlcGenerator .OhlcBatch > batches = OhlcGenerator .generate (TOTAL_ROWS , BATCH_SIZE );
143167
144168 // When
145- writeJava (file , batches );
169+ Path csvFile = writeCsv (tmp , batches );
170+ Path jniFile = writeJni (tmp , batches );
171+ Path javaFile = writeJava (tmp , batches );
146172
147- // Then — readable and correct row count
148- long totalRows = 0L ;
149- try (VortexReader reader = VortexReader .open (file , EncodingRegistry .loadAll ())) {
150- var iter = reader .scan (io .github .dfa1 .vortex .scan .ScanOptions .columns ("volume" ));
151- while (iter .hasNext ()) {
152- totalRows += iter .next ().<LongArray >column ("volume" ).length ();
153- }
154- }
155- assertThat (totalRows ).isEqualTo (TOTAL_ROWS );
156- System .out .printf ("[WriteFileSizeIntegrationTest] Java file: %,d bytes (%.2f MB)%n" ,
157- Files .size (file ), Files .size (file ) / 1_048_576.0 );
158- }
173+ long csvSize = Files .size (csvFile );
174+ long jniSize = Files .size (jniFile );
175+ long javaSize = Files .size (javaFile );
159176
160- @ Test
161- void jniFile_isReadable_withCorrectRowCount (@ TempDir Path tmp ) throws IOException {
162- // Given
163- Path file = tmp .resolve ("ohlc-jni.vtx" );
164- List <OhlcGenerator .OhlcBatch > batches = OhlcGenerator .generate (TOTAL_ROWS , BATCH_SIZE );
177+ // Then — report
178+ System .out .printf (
179+ "[FileSizeComparison] %,d rows CSV=%,d bytes (%.1f MB) JNI=%,d bytes (%.1f MB) Java=%,d bytes (%.1f MB) Java/JNI=%.2fx CSV/Java=%.1fx%n" ,
180+ TOTAL_ROWS ,
181+ csvSize , csvSize / 1_048_576.0 ,
182+ jniSize , jniSize / 1_048_576.0 ,
183+ javaSize , javaSize / 1_048_576.0 ,
184+ (double ) javaSize / jniSize ,
185+ (double ) csvSize / javaSize );
165186
166- // When
167- writeJni ( file , batches );
187+ // Then — Java beats CSV
188+ assertThat ( javaSize ). isLessThan ( csvSize );
168189
169- // Then — readable and correct row count
190+ // Then — Java within 2x of JNI (both use cascading(3))
191+ assertThat (javaSize ).isLessThan (jniSize * 2 );
192+
193+ // Then — Java file is readable with correct row count
170194 long totalRows = 0L ;
171- try (VortexReader reader = VortexReader .open (file , EncodingRegistry .loadAll ())) {
172- var iter = reader .scan (io .github .dfa1 .vortex .scan .ScanOptions .columns ("close " ));
195+ try (VortexReader reader = VortexReader .open (javaFile , EncodingRegistry .loadAll ())) {
196+ var iter = reader .scan (io .github .dfa1 .vortex .scan .ScanOptions .columns ("volume " ));
173197 while (iter .hasNext ()) {
174- totalRows += iter .next ().<DoubleArray >column ("close " ).length ();
198+ totalRows += iter .next ().<LongArray >column ("volume " ).length ();
175199 }
176200 }
177201 assertThat (totalRows ).isEqualTo (TOTAL_ROWS );
178202 }
179-
180- @ Test
181- void fileSizeComparison (@ TempDir Path tmp ) throws IOException {
182- // Given
183- Path javaFile = tmp .resolve ("ohlc-java.vtx" );
184- Path jniFile = tmp .resolve ("ohlc-jni.vtx" );
185- List <OhlcGenerator .OhlcBatch > batches = OhlcGenerator .generate (TOTAL_ROWS , BATCH_SIZE );
186-
187- // When
188- writeJava (javaFile , batches );
189- writeJni (jniFile , batches );
190-
191- long javaSize = Files .size (javaFile );
192- long jniSize = Files .size (jniFile );
193-
194- // Then
195- System .out .printf ("[WriteFileSizeIntegrationTest] %d rows: Java=%,d bytes (%.1f MB) JNI=%,d bytes (%.1f MB) ratio=%.2fx%n" ,
196- TOTAL_ROWS ,
197- javaSize , javaSize / 1_048_576.0 ,
198- jniSize , jniSize / 1_048_576.0 ,
199- (double ) javaSize / jniSize );
200-
201- assertThat (javaSize ).isPositive ();
202- assertThat (jniSize ).isPositive ();
203- // Both writers use cascading(3): ALP+bitpacking for F64, delta+bitpacking for I64.
204- // Bound < 2× catches regressions while tolerating minor encoding strategy differences.
205- assertThat (javaSize ).isLessThan (jniSize * 2 );
206- }
207203}
0 commit comments