fix(scan): validate deletion vector delete tasks - #3145
Conversation
|
CI follow-up pushed in 8ff9d12: removed the internal design note that failed the Apache license-header check, made the helper private, and updated the validated builder public API baseline. Verified locally with 15 targeted scan tests, rustfmt, clippy -D warnings, LICENSE/NOTICE checks, and cargo-public-api 0.51.0 output comparison. |
|
Addressed the review in 0228cd4: all |
| )); | ||
| } | ||
|
|
||
| match self.content_offset { |
There was a problem hiding this comment.
- I think this check should also apply to non deletion vector?
- Why not make offset usize?
| Some(_) => {} | ||
| } | ||
|
|
||
| match self.content_size_in_bytes { |
| Some(_) => {} | ||
| } | ||
|
|
||
| if self.record_count.is_none() { |
There was a problem hiding this comment.
I think this should also apply to non deletion vector? Also why not remove the Option
mbutrovich
left a comment
There was a problem hiding this comment.
I noticed this PR after opening #3180. I'll leave mine open for now as a reference, but we can try to get #3145 over the finish line first.
The two converge on the same mechanism (build_method(into = Result<..>) + validate() + is_deletion_vector()), so the differences are small. Two things that might be worth pulling across either way:
- The #3035 naming nit (r3919759548), renaming
pos_deletes_by_pathtopos_deletes_by_referenced_data_file, isn't covered here. It's independent of #3135 and could go separately. - Making the fields private is a bigger breaking change for downstream than the validation itself. Would it be worth calling out explicitly in the description, so it shows up in the 0.11 release notes?
| let path = data_file | ||
| .referenced_data_file() | ||
| .expect("validated deletion vector must have referenced_data_file"); |
There was a problem hiding this comment.
I may be misreading the ordering here, but is the "validated" premise true at this point? PopulatedDeleteFileIndex::new consumes DeleteFileContext { manifest_entry, .. } fed straight from manifest scanning, and the conversion to FileScanTaskDeleteFile (and so build()) happens later, in get_deletes_for_data_file. If that's right, a manifest with a Puffin position-delete entry missing referenced_data_file would panic here, where it previously returned DataInvalid.
Relatedly, test_deletion_vector_missing_referenced_data_file_is_rejected is removed in this PR. Was that deliberate, or fallout from the check moving?
One option: keep this one check returning DataInvalid, since the index needs the value as its map key and so can't defer it, and move only content_offset / content_size_in_bytes to the builder. Curious whether you considered that and preferred the expect.
| let content_offset = task | ||
| .content_offset() | ||
| .expect("validated deletion vector must have content_offset"); |
There was a problem hiding this comment.
Same question about the guarantee, for a different reason: FileScanTaskDeleteFile derives plain Deserialize with no #[serde(try_from = ..)], unlike FileScanTask, which has one. So a task rebuilt from a serialized scan plan, the planner to worker path the key_metadata doc comment describes, never passes through build(). Does that leave these expects reachable on a malformed or hand-written plan?
Since the function already returns Result, keeping ok_or_else(..) here would cost little and degrade to DataInvalid instead of a panic. Alternatively, adding #[serde(try_from = ..)] to route deserialization through the same validation would make the expects sound, which seems like the stronger fix if it's not too much scope for this PR.
|
Addressed the latest review in
I left the independent Verification: 63 focused tests passed; workspace fmt, all-target/all-feature Clippy with |
Summary
FileScanTaskDeleteFileis builtDataInvaliderrors for malformed manifests and deserialized scan plans instead of panickingBreaking change
FileScanTaskDeleteFilefields are now private and exposed through read-only getters; downstream direct field access must migrate to the corresponding getter. This should be called out in the 0.11 release notes.Testing
cargo test -p iceberg scan::task::tests --lib(16 passed)cargo test -p iceberg delete_file_index::tests --lib(23 passed)cargo test -p iceberg arrow::caching_delete_file_loader::tests --lib(24 passed)cargo clippy -p iceberg --all-targets --all-features -- -D warningscargo fmt --all -- --checkgit diff --checkCloses #3135.