diff --git a/catalog/rest/scan_task_decoder.go b/catalog/rest/scan_task_decoder.go index 680351b61..70a056512 100644 --- a/catalog/rest/scan_task_decoder.go +++ b/catalog/rest/scan_task_decoder.go @@ -343,6 +343,9 @@ func decodeRESTDeleteFile( if len(wire.EqualityIDs) != 0 { return nil, errors.New("position-deletes file must not carry equality-ids") } + if format != iceberg.PuffinFile && (wire.ContentOffset != nil || wire.ContentSizeInBytes != nil) { + return nil, errors.New("content-offset and content-size-in-bytes are only valid for Puffin deletion vectors") + } if wire.ContentOffset != nil { if *wire.ContentOffset < 0 { return nil, fmt.Errorf("content-offset must be non-negative: %d", *wire.ContentOffset) diff --git a/catalog/rest/scan_task_decoder_test.go b/catalog/rest/scan_task_decoder_test.go index 0b0c82d1a..62be12841 100644 --- a/catalog/rest/scan_task_decoder_test.go +++ b/catalog/rest/scan_task_decoder_test.go @@ -390,6 +390,28 @@ func TestDecodeScanTasksRejectsMalformedPayloads(t *testing.T) { }, want: "must not carry equality-ids", }, + { + name: "non-Puffin position delete with content offset", + mutate: func(w *ScanTasks) { + w.DeleteFiles[0].ContentOffset = int64Ptr(10) + }, + want: "only valid for Puffin deletion vectors", + }, + { + name: "non-Puffin position delete with content size", + mutate: func(w *ScanTasks) { + w.DeleteFiles[0].ContentSizeInBytes = int64Ptr(20) + }, + want: "only valid for Puffin deletion vectors", + }, + { + name: "non-Puffin position delete with blob range", + mutate: func(w *ScanTasks) { + w.DeleteFiles[0].ContentOffset = int64Ptr(10) + w.DeleteFiles[0].ContentSizeInBytes = int64Ptr(20) + }, + want: "only valid for Puffin deletion vectors", + }, { name: "puffin missing blob range", mutate: func(w *ScanTasks) {