feat(go): support a read-builder limit hint for split pruning - #946
jackylee-ch wants to merge 4 commits into
Conversation
b8dc48e to
e173fde
Compare
|
Requirement fit: SUPPORTED, with a narrower performance claim. Exposing the existing split-limit hint to Go/C has real value, but [P1] Reject negative Go limits before the unsigned cast ( Verification: the PR C test |
Review follow-up (apache#946): `ReadBuilder.WithLimit(-1)` returned success and passed `uintptr(-1)` across the C boundary, so `LimitPushdownAccumulator` saw `limit as i64 == -1` and stopped planning after the first split with a known row count — a silently truncated scan. Reject a negative limit up front with `ErrNegativeLimit`. Also correct the doc claim: planning still reads the manifest entries, so the hint bounds retained splits rather than avoiding all planning I/O.
Review follow-up (apache#946): `ReadBuilder.WithLimit(-1)` returned success and passed `uintptr(-1)` across the C boundary, so `LimitPushdownAccumulator` saw `limit as i64 == -1` and stopped planning after the first split with a known row count — a silently truncated scan. Reject a negative limit up front with `ErrNegativeLimit`. Also correct the doc claim: planning still reads the manifest entries, so the hint bounds retained splits rather than avoiding all planning I/O.
3af1c47 to
facfcc7
Compare
|
Addressed both points. Negative limit. Narrower performance claim. The Multiple known-count splits. Rebased onto current main. |
|
Thanks for fixing the negative Go limit and narrowing the planning claim. The Go regression and an additional Go → C → scan → reader probe now pass, including zero, small, large, maximum nonnegative One P2 input-range issue remains in the newly exposed C API, I extended the actual C FFI test after three real writes/commits (six rows across three splits). An ordinary large limit retains all three splits; Validation: all 83 existing C tests passed; the native library was rebuilt from this head and the two Go limit tests plus the additional real reader-chain probe passed without warehouse skips. |
Go and C callers had no way to pass a row-count limit to scan planning, so previewing or sampling the first N rows of a large table planned every split and read every data file's statistics. The core ReadBuilder already stops selecting splits once the retained ones cover the limit (TableScan::apply_limit_pushdown); this exposes that through the bindings. Adds the paimon_read_builder_with_limit C FFI symbol and a Go ReadBuilder.WithLimit wrapper, threading the hint into both scan planning and new_read. Covered by a C-binding test (a zero limit prunes to no splits) and a Go integration test.
Review follow-up (apache#946): `ReadBuilder.WithLimit(-1)` returned success and passed `uintptr(-1)` across the C boundary, so `LimitPushdownAccumulator` saw `limit as i64 == -1` and stopped planning after the first split with a known row count — a silently truncated scan. Reject a negative limit up front with `ErrNegativeLimit`. Also correct the doc claim: planning still reads the manifest entries, so the hint bounds retained splits rather than avoiding all planning I/O.
…lits Extend the C-FFI limit test requested in review to plan multiple splits: a tiny `source.split.target-size` keeps each committed file as its own split, so a small positive limit is shown to retain a strict, non-empty subset rather than every split or none.
…ned cast `LimitPushdownAccumulator::push` compared `scanned_row_count >= self.limit as i64`. `limit` is a `usize`, so any value above `i64::MAX` — e.g. a C caller passing `SIZE_MAX` through `paimon_read_builder_with_limit` — turned negative, and the first counted split satisfied the limit, silently dropping the rest. Compare as `u128` instead; `scanned_row_count` is always non-negative, so a limit a real row count cannot reach never early-stops the scan. Covered by a core accumulator test for `usize::MAX` and `i64::MAX + 1`, and extended the C FFI plan test to assert both oversized limits retain every split.
facfcc7 to
af65feb
Compare
|
Addressed by fixing the root cause rather than only bounding the C input.
Coverage:
I verified non-vacuity: restoring the Rebased onto current main. The accumulator tests and |
Go and C callers can't pass a row-count limit to scan planning, so a service that only wants to preview or sample the first N rows of a large table retains every split even though the core reader can already bound them.
The core
ReadBuilderalready stops retaining splits once the kept ones cover the limit (TableScan::apply_limit_pushdown), but that bound was unreachable from Go/C. Planning still reads the manifest entries to learn each split's row count, so the hint bounds how many splits are retained rather than avoiding all planning I/O.This adds the
paimon_read_builder_with_limitC FFI symbol and a GoReadBuilder.WithLimitwrapper, threading the hint into both scan planning andnew_read. A negative Go limit is rejected withErrNegativeLimit: anintbelow zero would wrap through the unsigned C boundary to a huge value and make planning stop after the first split, silently dropping rows. It is a planning hint only — the caller still enforces the final row limit when reading.Tests: a C-binding test plans multiple known-count splits and asserts a zero limit prunes them all, a small limit retains a strict non-empty subset, and a large limit retains every split; a Go test asserts a negative limit is rejected before the unsigned cast; the existing Go integration test checks split counts with and without a limit.