From 892d4c98ad6a1cdf05a417311d4ed5273368eabb Mon Sep 17 00:00:00 2001 From: Minh Vu Date: Thu, 30 Jul 2026 21:39:50 +0200 Subject: [PATCH] fix(codec): reject nil data files in scan tasks Signed-off-by: Minh Vu --- codec/file_scan_task.go | 3 +++ codec/file_scan_task_test.go | 24 ++++++++++++++++++++++++ 2 files changed, 27 insertions(+) diff --git a/codec/file_scan_task.go b/codec/file_scan_task.go index 0c681a20b..6b730e96c 100644 --- a/codec/file_scan_task.go +++ b/codec/file_scan_task.go @@ -255,6 +255,9 @@ func decodeFileScanTaskResidual(data []byte, schema *iceberg.Schema) (iceberg.Bo // carrying a different spec would silently mis-map or drop partition values and // still succeed. Every file in a FileScanTask must therefore share spec.ID(). func checkDataFileSpecID(f iceberg.DataFile, spec iceberg.PartitionSpec) error { + if f == nil { + return fmt.Errorf("%w: data file is nil", iceberg.ErrInvalidArgument) + } if int(f.SpecID()) != spec.ID() { return fmt.Errorf("data file spec id %d does not match codec spec id %d (partition evolution requires per-spec grouping)", f.SpecID(), spec.ID()) } diff --git a/codec/file_scan_task_test.go b/codec/file_scan_task_test.go index ac7058fbe..c0b230c4b 100644 --- a/codec/file_scan_task_test.go +++ b/codec/file_scan_task_test.go @@ -137,6 +137,30 @@ func TestEncodeFileScanTaskRejectsMismatchedPrimaryDataFileSpec(t *testing.T) { "error must include the codec's spec id") } +func TestEncodeFileScanTaskRejectsNilDataFiles(t *testing.T) { + spec, schema, task := fullyPopulatedFileScanTask(t, 2) + tests := []struct { + name string + update func(*table.FileScanTask) + part string + }{ + {"primary file", func(task *table.FileScanTask) { task.File = nil }, "data file is nil"}, + {"delete file", func(task *table.FileScanTask) { task.DeleteFiles = []iceberg.DataFile{nil} }, "delete files: entry 0"}, + {"equality delete file", func(task *table.FileScanTask) { task.EqualityDeleteFiles = []iceberg.DataFile{nil} }, "equality delete files: entry 0"}, + {"deletion vector file", func(task *table.FileScanTask) { task.DeletionVectorFiles = []iceberg.DataFile{nil} }, "deletion vector files: entry 0"}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + task := task + tt.update(&task) + _, err := codec.EncodeFileScanTask(task, spec, schema, 2) + require.ErrorIs(t, err, iceberg.ErrInvalidArgument) + require.ErrorContains(t, err, tt.part) + }) + } +} + func TestDecodeFileScanTaskErrorCarriesMarker(t *testing.T) { schema := iceberg.NewSchema(123, iceberg.NestedField{ID: 1, Name: "id", Type: iceberg.Int64Type{}, Required: true},