kamcheungting-db opened a new pull request, #1113:
URL: https://github.com/apache/iceberg-cpp/pull/1113

   ## Summary
   
   Introduce `FileScanTaskGroup` and `DataTableScan::PlanTasksStream()` (with 
`PlanTasks()` as a collecting convenience), building on the validated byte 
ranges of #976 and the range-aware Parquet reads of #1002.
   
   - Files at or below `read.split.target-size` stay whole and go straight to 
grouping without a file read. Larger Parquet files use manifest `split_offsets` 
as boundaries when they are present, strictly increasing, and in bounds — again 
with no file read. Only when the offsets are missing or unusable is the footer 
inspected once for canonical row-group starts (`RowGroupStart`, from #1002).
   - Boundary intervals coalesce toward the target size; the resulting ranges 
partition `[0, file_size)` exactly, including the bytes before the first 
boundary. A file with a single row group, or with no boundary that reaches the 
target, stays whole. Avro and other formats are not split in this version.
   - Tasks pack into groups with Java Iceberg's bin packing: bounded by 
`read.split.planning-lookback` open groups, the largest open group closed 
first, at most 1,024 tasks per group, and an oversized task as a singleton 
group. Each task is weighted like Java's `TableScanUtil.planTasks`:
     `max(range length + delete content bytes, (1 + delete file count) * 
read.split.open-file-cost)`,
     with deletion vectors counting their content size rather than their 
container file size, matching `ScanTaskUtil.contentSizeInBytes`.
   - Split settings resolve by scan option, then table property, then default 
for `read.split.target-size`, `read.split.planning-lookback`, and 
`read.split.open-file-cost`. Malformed values and nonpositive target/lookback 
or negative open-file cost fail planning with a `Result` error.
   - The core group API is format-neutral: split policies are registered per 
file format through `internal::SplitPolicyRegistry`, and the Parquet bundle 
registers its policy in `RegisterAll()`.
   
   ## Stream contract
   
   `PlanTasksStream()` wraps `PlanFilesStream()`: it is fallible, single-pass, 
and lazy, and it owns the underlying task stream and its metrics, so it can 
outlive the scan. An executor configured through `PlanWith()` is borrowed and 
must remain alive until the group stream is destroyed. Destroying the stream 
mid-consumption releases the planning resources. Split settings are resolved 
before any planning resources are created, so invalid values fail immediately.
   
   ## C++ and Java comparison
   
   - The packer is a port of Java's `BinPacking.PackingIterator` with 
`largestBinFirst=true`, so C++ and Java produce the same groups from the same 
tasks and weights.
   - When manifest offsets are unusable, Java's `BaseContentScanTask.split` 
falls back to fixed-size byte ranges; this PR instead inspects the footer once 
and uses canonical row-group starts. Boundaries need not match read offsets: 
#1002 assigns row groups by midpoint, so offsets that disagree with canonical 
starts cost balance, not correctness.
   - Coalescing happens at split time — every emitted range reaches the target 
size except the last — so a large file's splits land in singleton groups while 
many small files pack together.
   - Java's `ScanTaskUtil.contentSizeInBytes` counts a deletion vector's blob 
size; this PR does the same, so delete-heavy tasks weigh the same in both 
implementations.
   
   ## Testing
   
   * `scan_task_planning_test` (new, bundle-independent; runs with 
`ICEBERG_BUILD_BUNDLE=OFF`), fed by synthetic task streams and counting split 
policies:
     * settings defaults, scan-option-over-table-property precedence, and 
malformed/invalid values;
     * the Java weight formula, including deletion-vector content size, 
open-file cost, and cost-overflow errors;
     * split coverage and coalescing: ranges cover `[0, file size)` exactly, 
including the prefix before the first boundary; whole-file cases for a small 
file, an unsupported format, a single boundary, an empty file, and an 
already-split task; splits share the data file, delete files, and residual 
filter; policies are not consulted for files at or below the target size;
     * packing: first-fit, lookback eviction with the largest group closed 
first, oversized singleton groups, the 1,024-task cap, empty streams, 
incremental consumption, and early destruction.
   * Bundled `scan_test` additions, parameterized over manifest format versions:
     * manifest split offsets are used without a footer read (the data path 
does not exist, so planning can only succeed without one);
     * a footer is read when offsets are missing, producing ranges that cover 
the file exactly, each reaching the target except the last; a single-row-group 
file stays whole;
     * scan option beats table property, and malformed or invalid settings fail 
planning;
     * no-snapshot scans produce no groups; groups stream incrementally and 
survive early destruction; delete files are carried into every split of a file 
(format versions 2 and 3; version 1 has no delete manifests).
   
   Validation performed: `scan_task_planning_test` passes in both the core-only 
and bundled builds; the full `scan_test` passes; `pre-commit` is clean; 
`clang-tidy 22.1.8` (the version CI pins) reports no findings on changed lines.
   
   ## Related
   
   * Part-1 #976 — validated `FileScanTask` byte ranges
   * Part-2a #1002 — range-aware Parquet task reading
   * Part-2b #1062 — stable physical Arrow schema on task streams
   * Part-2c #1065 — residual filter enforcement in the task reader
   
   The bounded parallel reader and end-to-end validation follow in later parts 
of this series.
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to