LuciferYang opened a new issue, #856:
URL: https://github.com/apache/iceberg-cpp/issues/856

   **Summary**
   
   `internal::CanContainEqDeletesForFile` (`src/iceberg/delete_file_index.cc`) 
throws `std::bad_optional_access` and crashes the process when an equality 
delete file carries lower/upper bounds for some of its equality fields but not 
others. The throw is an uncaught exception escaping the `Result`-based 
(non-throwing) call chain, so it terminates the caller rather than surfacing as 
an error.
   
   Two reachable entry points, both consuming files written by arbitrary 
engines:
   
   - Scan planning: `ManifestGroup` → `DeleteFileIndex::ForEntry` 
(`manifest_group.cc:214`).
   - Commit validation: 
`MergingSnapshotUpdate::ValidateNoNewDeletesForDataFiles` → 
`DeleteFileIndex::ForDataFile` (`merging_snapshot_update.cc:1196`/`:1256`), 
reached from `RowDelta`/`OverwriteFiles`/`RewriteFiles`.
   
   Equality delete files with bounds for only some fields are produced 
legitimately by per-column metrics modes (`counts`/`none`/`truncate`) and by 
cross-engine writers; `ContentFileUtil::DropUnselectedStats` drops non-equality 
column stats without back-filling missing bounds, so the asymmetry is preserved 
on read. The crash is reachable in normal reader use, not just a synthetic 
input.
   
   **Root Cause**
   
   `EqualityDeleteFile::LowerBound`/`UpperBound` return 
`Result<std::optional<std::reference_wrapper<const Literal>>>`, i.e. 
`std::expected<std::optional<...>, Error>`. A field that has no bound yields an 
*engaged* `expected` wrapping a *disengaged* `optional`. The guard checks the 
wrong layer:
   
   ```cpp
   auto delete_lower = delete_file.LowerBound(field_id);   // 
expected<optional<...>>
   auto delete_upper = delete_file.UpperBound(field_id);
   if (!delete_lower.has_value() || !delete_upper.has_value()) {  // 
expected::has_value(): only the error state
     continue;  // Missing bounds, assume may match
   }
   ...
   RangesOverlap(..., delete_lower->value().get(), 
delete_upper->value().get());  // optional::value() on nullopt -> throws
   ```
   
   `has_value()` here calls `std::expected::has_value()`, which only reports 
whether an error is present; it does not detect the empty inner `optional`. So 
a missing bound passes the guard and `->value()` throws. The data-side guard a 
few lines above (`data_file.lower_bounds.find(...) == cend() || 
...->second.empty()`) checks field-level presence correctly, so the two sides 
are asymmetric. Java's `DeleteFileIndex.canContainEqDeletesForFile` (checked 
against apache/iceberg `main`) guards all four bounds uniformly (`dataLower == 
null || dataUpper == null || deleteLower == null || deleteUpper == null` → 
`continue`, "assume the delete file may match"), and only prunes when all four 
are present and the ranges do not overlap.
   
   **Reproduction**
   
   An equality delete over fields `{1, 2}` whose `lower_bounds`/`upper_bounds` 
contain only field 1, evaluated against a data file that has bounds for both 
fields (field 1's range overlapping so evaluation proceeds to field 2), throws 
`std::bad_optional_access` out of `EqualityDeletes::Filter`.
   
   **Proposed Fix**
   
   Unwrap the `expected` first with `ICEBERG_ASSIGN_OR_RAISE` so `has_value()` 
tests the inner `optional`, matching the data-side guard and Java:
   
   ```cpp
   ICEBERG_ASSIGN_OR_RAISE(auto delete_lower, delete_file.LowerBound(field_id));
   ICEBERG_ASSIGN_OR_RAISE(auto delete_upper, delete_file.UpperBound(field_id));
   if (!delete_lower.has_value() || !delete_upper.has_value()) {
     continue;
   }
   ...
   RangesOverlap(..., delete_lower->get(), delete_upper->get());
   ```
   
   This also propagates a genuine bound-deserialization error instead of 
silently swallowing it as "may match", consistent with the data-side handling 
in the same function.
   
   I have a fix and a regression test ready and will open a PR.
   


-- 
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