Skip to content

Commit a441e33

Browse files
Feature: dictionary_columns for IncrementalAppendScan
Expose the dictionary_columns Arrow-read option (added to DataScan in #3461) on IncrementalAppendScan.to_arrow / to_arrow_batch_reader. The shared _to_arrow*_via_file_scan_tasks helpers already thread it, so this just forwards the argument. Follow-up to #3512. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent a7e3f24 commit a441e33

2 files changed

Lines changed: 40 additions & 4 deletions

File tree

‎pyiceberg/table/__init__.py‎

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2466,28 +2466,46 @@ def plan_files(self) -> Iterable[FileScanTask]:
24662466
and manifest_entry.status == ManifestEntryStatus.ADDED,
24672467
)
24682468

2469-
def to_arrow(self) -> pa.Table:
2469+
def to_arrow(self, dictionary_columns: tuple[str, ...] = ()) -> pa.Table:
24702470
"""Read an Arrow table eagerly from this IncrementalAppendScan.
24712471
24722472
All rows will be loaded into memory at once.
24732473
2474+
Args:
2475+
dictionary_columns:
2476+
A tuple of column names that PyArrow should read as
2477+
dictionary-encoded (``pa.DictionaryArray``). Dictionary
2478+
encoding can substantially reduce memory usage for columns
2479+
with low-cardinality repeated string values.
2480+
Only applies to Parquet files; silently ignored for ORC.
2481+
24742482
Returns:
24752483
pa.Table: Materialized Arrow Table from the Iceberg table's IncrementalAppendScan
24762484
"""
2477-
return _to_arrow_via_file_scan_tasks(self, self.projection(), self.plan_files())
2485+
return _to_arrow_via_file_scan_tasks(self, self.projection(), self.plan_files(), dictionary_columns=dictionary_columns)
24782486

2479-
def to_arrow_batch_reader(self) -> pa.RecordBatchReader:
2487+
def to_arrow_batch_reader(self, dictionary_columns: tuple[str, ...] = ()) -> pa.RecordBatchReader:
24802488
"""Return an Arrow RecordBatchReader from this IncrementalAppendScan.
24812489
24822490
For large results, using a RecordBatchReader requires less memory than
24832491
loading an Arrow Table for the same IncrementalAppendScan, because a
24842492
RecordBatch is read one at a time.
24852493
2494+
Args:
2495+
dictionary_columns:
2496+
A tuple of column names that PyArrow should read as
2497+
dictionary-encoded (``pa.DictionaryArray``). Dictionary
2498+
encoding can substantially reduce memory usage for columns
2499+
with low-cardinality repeated string values.
2500+
Only applies to Parquet files; silently ignored for ORC.
2501+
24862502
Returns:
24872503
pa.RecordBatchReader: Arrow RecordBatchReader from the Iceberg table's IncrementalAppendScan
24882504
which can be used to read a stream of record batches one by one.
24892505
"""
2490-
return _to_arrow_batch_reader_via_file_scan_tasks(self, self.projection(), self.plan_files())
2506+
return _to_arrow_batch_reader_via_file_scan_tasks(
2507+
self, self.projection(), self.plan_files(), dictionary_columns=dictionary_columns
2508+
)
24912509

24922510
def _validate_and_resolve_snapshots(self) -> tuple[int, int]:
24932511
if self.from_snapshot_id_exclusive is None:

‎tests/integration/test_reads.py‎

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1295,6 +1295,24 @@ def test_incremental_append_scan_append_only(catalog: Catalog) -> None:
12951295
assert len(scan.to_polars()) == 3
12961296

12971297

1298+
@pytest.mark.integration
1299+
@pytest.mark.parametrize("catalog", [lf("session_catalog_hive"), lf("session_catalog")])
1300+
def test_incremental_append_scan_dictionary_columns(catalog: Catalog) -> None:
1301+
test_table = catalog.load_table("default.test_incremental_read")
1302+
1303+
scan = (
1304+
test_table.incremental_append_scan()
1305+
.from_snapshot_exclusive(test_table.snapshots()[0].snapshot_id)
1306+
.to_snapshot_inclusive(test_table.snapshots()[2].snapshot_id)
1307+
)
1308+
result = scan.to_arrow(dictionary_columns=("letter",))
1309+
1310+
# The requested column is dictionary-encoded; others stay plain.
1311+
assert pa.types.is_dictionary(result.schema.field("letter").type)
1312+
assert not pa.types.is_dictionary(result.schema.field("number").type)
1313+
assert sorted(result["number"].to_pylist()) == [2, 3, 4]
1314+
1315+
12981316
@pytest.mark.integration
12991317
@pytest.mark.parametrize("catalog", [lf("session_catalog_hive"), lf("session_catalog")])
13001318
def test_incremental_append_scan_ignores_non_append_snapshots(catalog: Catalog) -> None:

0 commit comments

Comments
 (0)