Skip to content

Commit e478a3a

Browse files
dfa1claude
andcommitted
refactor(reader): ScanOptions projections carry ColumnName
ScanOptions.columns becomes List<ColumnName>: the ergonomic columns(String...) / withColumns(String...) factories still take strings at the API boundary but validate them into ColumnName on construction, so a blank or control-character projection fails fast instead of silently matching nothing. ScanIterator's projection membership test is now ColumnName-vs-ColumnName. Completes the typed-id arc for the scan API — strings at the boundary, types inside. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent 30d72e8 commit e478a3a

5 files changed

Lines changed: 54 additions & 11 deletions

File tree

performance/src/main/java/io/github/dfa1/vortex/performance/TaxiColumnTreeDiff.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ private static void walkLayout(VortexReader reader, Layout layout, Footer footer
113113
return;
114114
}
115115
MemorySegment seg = reader.rawSegment(spec);
116-
dumpFlatRoot(seg, footer.arraySpecs(), indent + " ");
116+
dumpFlatRoot(seg, footer.arraySpecs().stream().map(EncodingId::id).toList(), indent + " ");
117117
} else {
118118
System.out.println(header);
119119
}

reader/src/main/java/io/github/dfa1/vortex/reader/ScanIterator.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -542,16 +542,16 @@ private void initialize() {
542542
Map<ColumnName, DType> columnDtypes = new LinkedHashMap<>();
543543

544544
if (rootLayout.isStruct() && rootDtype instanceof DType.Struct structDtype) {
545-
List<String> projection = options.columns();
545+
List<ColumnName> projection = options.columns();
546546
for (int i = 0; i < rootLayout.children().size(); i++) {
547547
String rawName = structDtype.fieldNames().get(i);
548548
DType colDtype = structDtype.fieldTypes().get(i);
549-
if (!projection.isEmpty() && !projection.contains(rawName)) {
550-
continue;
551-
}
552549
// File-schema names are already policy-certified by PostscriptParser, so this
553550
// ColumnName construction never throws — it just types the certified key.
554551
ColumnName colName = ColumnName.of(rawName);
552+
if (!projection.isEmpty() && !projection.contains(colName)) {
553+
continue;
554+
}
555555
Layout colTop = rootLayout.children().get(i);
556556
var flats = new ArrayList<Layout>();
557557
collectFlats(colTop, flats);
Lines changed: 45 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,51 +1,93 @@
11
package io.github.dfa1.vortex.reader;
22

3+
import io.github.dfa1.vortex.core.model.ColumnName;
4+
5+
import java.util.Arrays;
36
import java.util.List;
47

58
/// Options controlling a file scan.
69
///
710
/// Empty `columns` = read all columns.
811
/// Null `rowFilter` = no zone-map pruning.
12+
///
13+
/// Projection names are supplied as strings at the API boundary and validated into
14+
/// [ColumnName] on construction, so a blank or control-character projection name fails fast
15+
/// rather than silently matching nothing.
16+
///
17+
/// @param columns projected column names; empty means all columns
18+
/// @param rowFilter zone-map pruning filter, or `null` for none
19+
/// @param limit row limit, or [#NO_LIMIT]
920
public record ScanOptions(
10-
List<String> columns,
21+
List<ColumnName> columns,
1122
RowFilter rowFilter,
1223
long limit
1324
) {
25+
/// Sentinel limit meaning "no row limit".
1426
public static final long NO_LIMIT = Long.MAX_VALUE;
1527

28+
/// Scans every column with no filter or limit.
29+
///
30+
/// @return options selecting all columns
1631
public static ScanOptions all() {
1732
return new ScanOptions(List.of(), null, NO_LIMIT);
1833
}
1934

35+
/// Projects the named columns.
36+
///
37+
/// @param names column names to project
38+
/// @return options projecting `names`
2039
public static ScanOptions columns(String... names) {
21-
return new ScanOptions(List.of(names), null, NO_LIMIT);
40+
return new ScanOptions(toColumnNames(names), null, NO_LIMIT);
2241
}
2342

43+
/// Scans every column, capped at `limit` rows.
44+
///
45+
/// @param limit maximum rows to read
46+
/// @return options with the given row limit
2447
public static ScanOptions limit(long limit) {
2548
return new ScanOptions(List.of(), null, limit);
2649
}
2750

51+
/// Returns a copy projecting the named columns.
52+
///
53+
/// @param names column names to project
54+
/// @return a copy with `names` projected
2855
public ScanOptions withColumns(String... names) {
29-
return new ScanOptions(List.of(names), rowFilter, limit);
56+
return new ScanOptions(toColumnNames(names), rowFilter, limit);
3057
}
3158

59+
/// Returns a copy with the given row limit.
60+
///
61+
/// @param limit maximum rows to read
62+
/// @return a copy with the given limit
3263
public ScanOptions withLimit(long limit) {
3364
return new ScanOptions(columns, rowFilter, limit);
3465
}
3566

67+
/// Returns a copy with the given zone-map filter.
68+
///
69+
/// @param filter the row filter, or `null` for none
70+
/// @return a copy with the given filter
3671
public ScanOptions withFilter(RowFilter filter) {
3772
return new ScanOptions(columns, filter, limit);
3873
}
3974

75+
/// @return `true` if a column projection is set
4076
public boolean hasProjection() {
4177
return !columns.isEmpty();
4278
}
4379

80+
/// @return `true` if a zone-map filter is set
4481
public boolean hasFilter() {
4582
return rowFilter != null;
4683
}
4784

85+
/// @return `true` if a row limit is set
4886
public boolean hasLimit() {
4987
return limit != NO_LIMIT;
5088
}
89+
90+
private static List<ColumnName> toColumnNames(String... names) {
91+
return Arrays.stream(names).map(ColumnName::of).toList();
92+
}
5193
}

reader/src/main/java/io/github/dfa1/vortex/reader/VortexReader.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -220,7 +220,7 @@ public Chunk decodeChunk(int chunkIndex, List<String> columns) {
220220
validateColumns(requested);
221221
ScanOptions options = requested.isEmpty()
222222
? ScanOptions.all()
223-
: new ScanOptions(requested, null, ScanOptions.NO_LIMIT);
223+
: ScanOptions.columns(requested.toArray(new String[0]));
224224
try (ScanIterator iter = new ScanIterator(this, options)) {
225225
return iter.decodeChunkAt(chunkIndex);
226226
}

reader/src/test/java/io/github/dfa1/vortex/reader/ScanOptionsTest.java

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

3+
import io.github.dfa1.vortex.core.model.ColumnName;
34
import org.junit.jupiter.api.Test;
45

56
import static org.assertj.core.api.Assertions.assertThat;
@@ -24,7 +25,7 @@ void withColumns_setsProjection() {
2425
ScanOptions sut = ScanOptions.all().withColumns("price", "qty");
2526

2627
// Then
27-
assertThat(sut.columns()).containsExactly("price", "qty");
28+
assertThat(sut.columns()).containsExactly(ColumnName.of("price"), ColumnName.of("qty"));
2829
assertThat(sut.hasProjection()).isTrue();
2930
assertThat(sut.limit()).isEqualTo(ScanOptions.NO_LIMIT);
3031
}
@@ -51,7 +52,7 @@ void fluent_chainingPreservesAllFields() {
5152
.withFilter(RowFilter.gte("price", 50));
5253

5354
// Then
54-
assertThat(sut.columns()).containsExactly("price", "qty");
55+
assertThat(sut.columns()).containsExactly(ColumnName.of("price"), ColumnName.of("qty"));
5556
assertThat(sut.limit()).isEqualTo(10);
5657
assertThat(sut.hasFilter()).isTrue();
5758
}

0 commit comments

Comments
 (0)