From e8606cbffeb557ddb7dc316d37f0a8654f03eb00 Mon Sep 17 00:00:00 2001 From: Minh Vu Date: Fri, 12 Jun 2026 21:36:05 +0200 Subject: [PATCH] Reject negative positional delete positions --- .../src/arrow/caching_delete_file_loader.rs | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/crates/iceberg/src/arrow/caching_delete_file_loader.rs b/crates/iceberg/src/arrow/caching_delete_file_loader.rs index 1925964f53..d0d0ee31e6 100644 --- a/crates/iceberg/src/arrow/caching_delete_file_loader.rs +++ b/crates/iceberg/src/arrow/caching_delete_file_loader.rs @@ -361,6 +361,13 @@ impl CachingDeleteFileLoader { )); }; + if pos < 0 { + return Err(Error::new( + ErrorKind::DataInvalid, + format!("negative position in delete file: {pos}"), + )); + } + result .entry(file_path.to_string()) .or_default() @@ -768,6 +775,22 @@ mod tests { assert!(result.is_none()); // no pos dels for file 3 } + #[tokio::test] + async fn test_parse_positional_deletes_rejects_negative_positions() { + let schema = crate::arrow::delete_filter::tests::create_pos_del_schema(); + let file_path_col = Arc::new(StringArray::from_iter_values(vec!["data.parquet"])); + let pos_col = Arc::new(Int64Array::from_iter_values(vec![-1i64])); + let batch = RecordBatch::try_new(schema, vec![file_path_col, pos_col]).unwrap(); + let stream = futures::stream::iter(vec![Ok(batch)]).boxed(); + + let err = CachingDeleteFileLoader::parse_positional_deletes_record_batch_stream(stream) + .await + .unwrap_err(); + + assert_eq!(err.kind(), ErrorKind::DataInvalid); + assert!(err.message().contains("negative position")); + } + /// Verifies that evolve_schema on partial-schema equality deletes works correctly /// when only equality_ids columns are evolved, not all table columns. ///