Skip to content

Commit ab8bc55

Browse files
dfa1claude
andcommitted
fix: de-duplicate Parquet column names in oracle; use index-based row access (#256)
The Rust Vortex writer de-duplicates repeated field names when writing a Vortex file (N-th occurrence of 'col' becomes 'col [N]'). The Parquet oracle now mirrors this in the CSV header. All row access switches from name-based to index-based so duplicate-name files read the right column. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent f371d0c commit ab8bc55

3 files changed

Lines changed: 27 additions & 12 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1313

1414
### Fixed
1515

16+
- Parquet files with duplicate column names now compare correctly. The oracle header de-duplicates names with the Rust Vortex writer's algorithm (first occurrence unmodified, Nth repetition gets ` [N]` suffix) and all row access uses column index rather than name. ([#256](https://github.com/dfa1/vortex-java/issues/256))
1617
- Blank column names are now accepted on both the read and write paths. The Rust reference legitimately produces them (e.g. `uci-electricityloaddiagrams20112014` has one at field index 0). `ColumnName` now enforces a symmetric policy: non-null and free of control characters (`\n`, `\t`, U+0000, …); blank names such as `""` or `" "` pass through on both paths. ([#255](https://github.com/dfa1/vortex-java/issues/255))
1718
- `VarBinArray.DictMode` reads dict-value offsets in a ptype-uniform loop: the I32 fast path eliminates the per-row `switch(dictValOffPType)` that blocked C2 vectorization (introduced by #215). ([#243](https://github.com/dfa1/vortex-java/issues/243))
1819
- `ConstantEncodingDecoder` now returns `NullArray` for null-scalar constants (proto `null_value` tag); previously decoded as `0`/`false`, silently corrupting nullable columns that the Rust writer encodes as an all-null constant. ([#246](https://github.com/dfa1/vortex-java/issues/246))

integration/src/test/java/io/github/dfa1/vortex/integration/RaincloudConformanceIntegrationTest.java

Lines changed: 24 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,9 @@
2121
import java.io.Writer;
2222
import java.nio.file.Files;
2323
import java.nio.file.Path;
24+
import java.util.ArrayList;
2425
import java.util.HashMap;
26+
import java.util.LinkedHashMap;
2527
import java.util.List;
2628
import java.util.Map;
2729
import java.util.stream.Stream;
@@ -204,7 +206,16 @@ private static void writeOracleCsv(Path parquet, Writer out) throws IOException
204206
CsvWriter csv = CsvWriter.builder().fieldSeparator(',').build(out)) {
205207

206208
List<ColumnSchema> cols = pfr.getFileSchema().getColumns();
207-
csv.writeRecord(cols.stream().map(ColumnSchema::name).toList());
209+
// De-duplicate duplicate column names with the Rust Vortex writer's algorithm:
210+
// the Nth (N >= 1) occurrence of a base name gets a " [N]" suffix, matching
211+
// the de-duplicated names in the Vortex file (#256).
212+
Map<String, Integer> seen = new LinkedHashMap<>();
213+
List<String> header = new ArrayList<>(cols.size());
214+
for (ColumnSchema col : cols) {
215+
int count = seen.merge(col.name(), 1, Integer::sum) - 1;
216+
header.add(count == 0 ? col.name() : col.name() + " [" + count + "]");
217+
}
218+
csv.writeRecord(header);
208219

209220
String[] row = new String[cols.size()];
210221
while (rows.hasNext()) {
@@ -221,6 +232,9 @@ private static void writeOracleCsv(Path parquet, Writer out) throws IOException
221232
/// null rows export as an empty field, valid rows use the JDK canonical
222233
/// `toString` of the value.
223234
///
235+
/// Row access uses column index rather than name so that files with duplicate
236+
/// column names (#256) read the right column.
237+
///
224238
/// INT32/INT64 columns with a `UINT_32`/`UINT_64` logical-type annotation are treated
225239
/// as unsigned so their string representation matches the U32/U64 Vortex columns that
226240
/// carry the same bits (#253).
@@ -229,22 +243,22 @@ private static void writeOracleCsv(Path parquet, Writer out) throws IOException
229243
/// @param rows the row reader positioned at the current row
230244
/// @return the formatted cell string
231245
private static String oracleCell(ColumnSchema col, RowReader rows) {
232-
String name = col.name();
233-
if (col.repetitionType() == RepetitionType.OPTIONAL && rows.isNull(name)) {
246+
int idx = col.columnIndex();
247+
if (col.repetitionType() == RepetitionType.OPTIONAL && rows.isNull(idx)) {
234248
return "";
235249
}
236250
boolean unsignedInt = col.logicalType() instanceof LogicalType.IntType lt && !lt.isSigned();
237251
return switch (col.type()) {
238-
case INT32 -> unsignedInt ? Integer.toUnsignedString(rows.getInt(name)) : Integer.toString(rows.getInt(name));
239-
case INT64 -> unsignedInt ? Long.toUnsignedString(rows.getLong(name)) : Long.toString(rows.getLong(name));
240-
case FLOAT -> Float.toString(rows.getFloat(name));
241-
case DOUBLE -> Double.toString(rows.getDouble(name));
242-
case BOOLEAN -> Boolean.toString(rows.getBoolean(name));
243-
case BYTE_ARRAY -> rows.getString(name);
252+
case INT32 -> unsignedInt ? Integer.toUnsignedString(rows.getInt(idx)) : Integer.toString(rows.getInt(idx));
253+
case INT64 -> unsignedInt ? Long.toUnsignedString(rows.getLong(idx)) : Long.toString(rows.getLong(idx));
254+
case FLOAT -> Float.toString(rows.getFloat(idx));
255+
case DOUBLE -> Double.toString(rows.getDouble(idx));
256+
case BOOLEAN -> Boolean.toString(rows.getBoolean(idx));
257+
case BYTE_ARRAY -> rows.getString(idx);
244258
// aborts (not fails) the slug: the oracle can't format this physical type
245259
// yet, which is an oracle limitation rather than a vortex-java gap
246260
default -> throw new TestAbortedException(
247-
"oracle cannot format parquet type " + col.type() + " (column: " + name + ")");
261+
"oracle cannot format parquet type " + col.type() + " (column: " + col.name() + ")");
248262
};
249263
}
250264

integration/src/test/resources/raincloud/expected-status.csv

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -223,14 +223,14 @@ uci-online-retail,ok
223223
uci-online-retail-ii,ok
224224
uci-online-shoppers-purchasing-intention,ok
225225
uci-optical-recognition-of-handwritten-digits,ok
226-
uci-parkinsons,gap:256
226+
uci-parkinsons,ok
227227
uci-phishing-websites,ok
228228
uci-predict-students-dropout-and-academic-success,ok
229229
uci-real-estate-valuation-data-set,ok
230230
uci-seeds,ok
231231
uci-seoul-bike-sharing-demand,ok
232232
uci-sms-spam-collection,ok
233-
uci-spambase,gap:256
233+
uci-spambase,ok
234234
uci-statlog-german-credit-data,ok
235235
uci-student-performance,ok
236236
uci-thyroid-disease,ok

0 commit comments

Comments
 (0)