feat(writer): add PositionDeleteInput builder - #3230
Open
laskoviymishka wants to merge 5 commits into
Open
laskoviymishka wants to merge 5 commits into
laskoviymishka wants to merge 5 commits into
Conversation
Add PositionDeleteInputBuilder, a small helper that assembles a spec-conforming position delete RecordBatch from (path, pos) rows. The batch has exactly the two required position delete columns (file_path: Utf8, pos: Int64), both non-null and carrying the reserved field ids, so it passes PositionDeleteFileWriter's per-batch validation and can be handed straight to that writer. An empty builder still produces a valid 0-row batch. To keep a single source of truth for the field-id wiring, the Arrow schema is no longer hand-wired in the builder. This commit un-gates the previously test-only POSITION_DELETE_ARROW_SCHEMA static and position_delete_arrow_schema() helper in position_delete_writer.rs, making the helper pub(crate), and the builder now obtains the shared Arrow projection through it as a cheap Arc clone. extend accepts any iterator of (Into<String>, i64) rows, and build debug-asserts the paths/positions length invariant. The module docs note that rows are materialized into two heap Vecs before the batch is built (so very large inputs should use one builder per write batch), that the optional third row column is not supported yet, and that position delete files are a v2 construct which v3 forbids, so a format-version gate must be applied at the transaction/commit layer. Relates to apache#340 and apache#2218.
laskoviymishka
marked this pull request as ready for review
September 15, 2026 20:02
anoopj
reviewed
Sep 16, 2026
anoopj
reviewed
Sep 16, 2026
anoopj
reviewed
Sep 16, 2026
anoopj
reviewed
Sep 16, 2026
blackmwk
reviewed
Sep 17, 2026
…osition_delete_batch fn Per review, collapse the PositionDeleteInputBuilder surface (new/with_capacity/push/extend/len/is_empty/build) into a single free function, position_delete_batch(rows). A FooBuilder is expected to build a Foo, but the builder's build() only returned a bare RecordBatch, so a plain function reads more clearly and keeps the API as tight as possible. The function collects rows into a BTreeMap<String, RoaringTreemap> keyed by file path. This deduplicates repeated (file_path, pos) pairs and, when iterated, yields the spec-required ascending (file_path, pos) order for free, so callers no longer have to pre-sort their input. A negative pos is rejected with DataInvalid. The batch is still built from the writer's shared crate internal Arrow schema, and empty input yields a valid 0-row batch.
blackmwk
reviewed
Sep 18, 2026
…Deletes accumulator Following blackmwk's review of the redesigned position delete assembly, replace the free position_delete_batch function with a small crate-internal PositionDeletes accumulator. Callers build up deletes with insert(path, pos) and render them once via to_record_batch(). Positions are now taken as u64: a row position is non-negative by construction, so the old negative-i64 rejection is gone. The batch's Int64 pos column is filled with a checked u64-to-i64 conversion that returns DataInvalid rather than panicking on a value past i64::MAX. Rows stay keyed by a BTreeMap<String, RoaringTreemap>, so iteration yields the spec-required (file_path, pos) order with duplicate pairs removed, and the two columns are now built with Arrow StringBuilder and Int64Builder instead of intermediate Vecs. The batch is assembled against the writer's shared position_delete_arrow_schema, and an empty accumulator still yields a valid 0-row batch. Nothing outside the crate consumes this yet, so the whole surface is pub(crate) and the module carries a dead-code allowance until the write-path plumbing lands. The public API drops the position_delete_batch entry accordingly.
blackmwk
reviewed
Sep 22, 2026
|
|
||
| /// Records that row `pos` of `path` is deleted. `pos` is a row position, so it is | ||
| /// non-negative by construction. Re-inserting the same `(path, pos)` is a no-op. | ||
| pub(crate) fn insert(&mut self, path: impl Into<String>, pos: u64) { |
Contributor
There was a problem hiding this comment.
Should we fail when duplicates found?
Contributor
There was a problem hiding this comment.
After second thought, I suggest to do two checks:
- if pos could be safely converted to i64. This is a small win over current approach since we could detect the error earlier.
- I think we need to return an error when duplicates found. Position deletes are typically used to delete position in dml statement, and if duplicates found, it should be a bug.
At least, we should be these two checks using debug_assert! so that we could found bugs early in debug mode, wdyt?
Follow-up to blackmwk's review on apache#3230. - Add PositionDeletes::to_record_batches(max_rows), a lazy iterator that builds each fixed-size batch on demand instead of materializing the whole delete set at once, bounding peak memory over large delete sets. It walks the paths and positions in sorted (file_path, pos) order, splitting on max_rows boundaries; a path whose positions overflow one batch resumes in the next. A max_rows of 0 yields a single DataInvalid error rather than panicking, and the stream stops after any error. - Build the file_path column with StringBuilder::append_value_n, appending each path's run within a batch in one call rather than once per row. - Factor a shared private build_batch helper so to_record_batch (single batch) and to_record_batches share the column-building logic. - Document that duplicate (file_path, pos) inserts collapse silently, which is intentional for position deletes.
blackmwk
reviewed
Sep 23, 2026
blackmwk
left a comment
Contributor
There was a problem hiding this comment.
We are quite close, just one concern.
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What changes are included in this PR?
Add PositionDeleteInputBuilder, a small helper that assembles a spec-conforming position delete RecordBatch from (path, pos) rows.
The batch has exactly the two required position delete columns (file_path: Utf8, pos: Int64), both non-null and carrying the reserved field ids, so it passes PositionDeleteFileWriter's per-batch validation and can be handed straight to that writer. An empty builder still produces a valid 0-row batch.
To keep a single source of truth for the field-id wiring, the Arrow schema is no longer hand-wired in the builder. This commit un-gates the previously test-only POSITION_DELETE_ARROW_SCHEMA static and position_delete_arrow_schema() helper in position_delete_writer.rs, making the helper pub(crate), and the builder now obtains the shared Arrow projection through it as a cheap Arc clone.
extend accepts any iterator of (Into, i64) rows, and build debug-asserts the paths/positions length invariant. The module docs note that rows are materialized into two heap Vecs before the batch is built (so very large inputs should use one builder per write batch), that the optional third row column is not supported yet, and that position delete files are a v2 construct which v3 forbids, so a format-version gate must be applied at the transaction/commit layer.
Which issue does this PR close?
Relates to #340 and #2218.
Are these changes tested?
yes, new and existing tests