Repository navigation
Conversation
Add ColumnSelection and Scan overloads that return only the requested columns, matching Java's metadata table select(). Names resolve through Schema::Select, so nested names, case sensitivity, `*`, and unknown names behave as in TableScanBuilder::Select. Tables now implement ScanProjection(const Schema&) and build only the selected columns. SnapshotsTable uses a per-column append table from the new metadata_table_columns_internal.h helper. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
There was a problem hiding this comment.
Warning
Copilot couldn't run its full agentic review because it didn't start before the timeout. Make sure your repository has a runner available, or add a copilot-code-review.yml file specifying one with the runs-on attribute. See the docs for more details.
Copilot review overview
2 open findings
What changed in this PR
Adds column projection support to C++ Iceberg metadata table scans (including time travel), aligning behavior with Java (newScan().select(...)) and enabling engines to request only needed columns.
Changes:
- Introduces
ColumnSelectionand overloads ofMetadataTable::Scan(...)/TimeTravelMetadataTable::Scan(...)to project columns viaSchema::Select. - Refactors scanning to be implemented via
ScanProjection(const Schema&)/ScanSnapshot(..., const Schema&)instead of overridingScan(). - Adds internal helpers to map projected schema columns to per-column append functions, and updates
SnapshotsTable+ tests accordingly.
| File | Description |
|---|---|
| src/iceberg/inspect/metadata_table.h | Adds ColumnSelection and new Scan overloads; refactors scan implementation to ScanProjection / projected-schema time travel. |
| src/iceberg/inspect/metadata_table.cc | Implements column selection via Schema::Select and routes scans through projected schemas. |
| src/iceberg/inspect/metadata_table_columns_internal.h | New internal helper to select per-column append functions based on projected schema. |
| src/iceberg/inspect/snapshots_table.h | Updates SnapshotsTable to implement ScanProjection(...) rather than Scan(). |
| src/iceberg/inspect/snapshots_table.cc | Implements projected-column building for snapshots using selected append functions. |
| src/iceberg/inspect/history_table.h / .cc | Updates HistoryTable to ScanProjection(...) while keeping scan unsupported. |
| src/iceberg/test/snapshots_table_test.cc | Adds tests for column selection semantics on SnapshotsTable. |
| src/iceberg/test/metadata_table_test.cc | Adds tests for time-travel scans with column projection and HistoryTable behavior. |
🧠 Review effort: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| ICEBERG_CHECK(field.has_value(), "Metadata table column '{}' is not in the schema", | ||
| projected_field.name()); | ||
| if (*field->get().type() != *projected_field.type()) { | ||
| return NotSupported("Cannot select nested fields of metadata table column '{}'", | ||
| projected_field.name()); | ||
| } | ||
| auto column = std::ranges::find(columns, projected_field.field_id(), | ||
| &MetadataTableColumn<Row>::field_id); | ||
| ICEBERG_CHECK(column != columns.end(), | ||
| "Metadata table column '{}' has no append function", | ||
| projected_field.name()); |
| /// \brief Scan using the current snapshot on the main branch. | ||
| Result<ArrowArrayStream> Scan() final; | ||
| // Scans without a snapshot selection read the current snapshot on the main branch. | ||
| using MetadataTable::Scan; |
There was a problem hiding this comment.
This pulls in Scan(const ColumnSelection&), which makes an existing call like Scan({}) ambiguous against Scan(const SnapshotSelection&) since both are aggregates. Is that source break intended, or would a distinct name for the column-selection form avoid it?
There was a problem hiding this comment.
Renamed the column-selection forms to ScanColumns
Scan(const ColumnSelection&) made Scan({}) on a TimeTravelMetadataTable
ambiguous with Scan(const SnapshotSelection&), since both are aggregates.
Use a distinct name so existing Scan({}) calls keep resolving to the
snapshot-selection overload.
Also assert on summary entries instead of the summary map's size, and
move the selection rules explained in test comments to the
ColumnSelection docs.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
shangxinli
left a comment
There was a problem hiding this comment.
The ScanColumns rename resolves the ambiguity I raised - Scan({}) still picks the SnapshotSelection overload. LGTM.
| std::optional<std::vector<std::string>> names; | ||
|
|
||
| /// \brief Whether names are matched case-sensitively. | ||
| bool case_sensitive = true; |
There was a problem hiding this comment.
Why not use a generic scan builder to pass them individually just like the data table scan does?


What
ColumnSelection(column names plus case sensitivity),MetadataTable::ScanColumns(const ColumnSelection&), andTimeTravelMetadataTable::ScanColumns(const SnapshotSelection&, const ColumnSelection&). Names resolve throughSchema::Select, as inTableScanBuilder::Select: nested names are dot-concatenated, columns come back in schema order,*selects all columns, and unknown names are ignored (as in Java'sSchema.select). An empty list returns rows without columns, which is enough to count rows. Selecting a map's value returns the whole map; selecting only its key is an error.Scan()is no longer virtual. Tables implement the protectedScanProjection(const Schema& projected_schema)instead, andScanSnapshotalso receives the projected schema.inspect/metadata_table_columns_internal.h, which maps the projected columns to per-column append functions so a table builds only the selected columns. Selecting only some fields of a nested struct column returnsNotSupportedfor now; no current metadata table has a struct column.SnapshotsTablebuilds only the selected columns.HistoryTablescans still returnNotSupported.Why
Metadata table scans currently always return every column. Java's metadata tables support
newScan().select(...):StaticDataTaskprojects each row toscan.schema(), so an engine runningSELECT name FROM t.refsonly getsname. Without selection, a C++ caller that needs one column still pays to build all of them, for example copying every snapshot'ssummarymap.Behavior change
ScanColumnsmethods return only the selected columns.Scan()andScan(const SnapshotSelection&)return the same rows as before, andScan({})on a time-travel table still resolves toScan(const SnapshotSelection&).ScanProjection(const Schema&)(orScanSnapshot(const SnapshotSelection&, const Schema&)for time-travel tables) instead ofScan(). Open metadata table PRs (feat(inspect): add files, partitions, and manifests metadata tables #876, feat: add position_deletes metadata table #920, feat(inspect): add refs metadata table #1007) need the same change.Testing
Added
metadata_table_testcases forSnapshotsTablecovering selection order, unknown names, case sensitivity,*and no selection, an empty selection, and map columns (summary.valuereturns the whole map,summary.keyis an error). A recordingTimeTravelMetadataTablechecks the snapshot selection and projected columns passed byScan(),ScanColumns,Scan(const SnapshotSelection&), andScan({}), and another case checks thatHistoryTable::ScanColumnsreturnsNotSupported.🤖 Generated with Claude Code