diff --git a/.golangci.yml b/.golangci.yml index 674605826..f7e1beab8 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -25,6 +25,7 @@ linters: - copyloopvar - usestdlibvars - misspell + - modernize - nlreturn - perfsprint - staticcheck diff --git a/catalog/rest/load_table_bench_test.go b/catalog/rest/load_table_bench_test.go index 36658e996..c24fc39ad 100644 --- a/catalog/rest/load_table_bench_test.go +++ b/catalog/rest/load_table_bench_test.go @@ -145,7 +145,7 @@ func makeTableResponseWithSnapshots(snapshotCount int64) []byte { panic(fmt.Errorf("failed to generate load table response: %w", err)) } - return []byte(fmt.Sprintf(`{ + return fmt.Appendf(nil, `{ "metadata-location": "s3://warehouse/database/table/metadata/00001-5f2f8166-244c-4eae-ac36-384ecdec81fc.gz.metadata.json", "metadata": { "format-version": 1, @@ -190,5 +190,5 @@ func makeTableResponseWithSnapshots(snapshotCount int64) []byte { } ] } - }`, snapshotTimestamp, snapshotID, snapshotID, snapshotsJson, snapshotsLogEntriesJson)) + }`, snapshotTimestamp, snapshotID, snapshotID, snapshotsJson, snapshotsLogEntriesJson) } diff --git a/catalog/sql/sql_test.go b/catalog/sql/sql_test.go index c27d95df2..58cb293db 100644 --- a/catalog/sql/sql_test.go +++ b/catalog/sql/sql_test.go @@ -1953,12 +1953,10 @@ func (s *SqliteCatalogTestSuite) TestCreateNamespaceConcurrent() { var wg sync.WaitGroup for range writers { - wg.Add(1) - go func() { - defer wg.Done() + wg.Go(func() { <-start errs <- cat.CreateNamespace(ctx, namespace, nil) - }() + }) } close(start) wg.Wait() diff --git a/data_file_codec.go b/data_file_codec.go index dcd01d952..1aeb0c83c 100644 --- a/data_file_codec.go +++ b/data_file_codec.go @@ -182,7 +182,7 @@ func newDecodeEntry(version int) (any, *dataFile) { return &manifestEntry{Data: df}, df } -var dataFileAvroFieldIndexes = avroFieldIndexes(reflect.TypeOf(dataFile{})) +var dataFileAvroFieldIndexes = avroFieldIndexes(reflect.TypeFor[dataFile]()) func avroFieldIndexes(t reflect.Type) []int { indexes := make([]int, 0, t.NumField()) diff --git a/data_file_codec_test.go b/data_file_codec_test.go index 7ca765849..6ac4d16c5 100644 --- a/data_file_codec_test.go +++ b/data_file_codec_test.go @@ -146,7 +146,7 @@ func TestMarshalAvroEntryDoesNotMutateAnyAvroField(t *testing.T) { } func TestDataFileAvroFieldIndexesCoverEveryAvroField(t *testing.T) { - typ := reflect.TypeOf(dataFile{}) + typ := reflect.TypeFor[dataFile]() want := make([]int, 0, typ.NumField()) for i := range typ.NumField() { if _, ok := typ.Field(i).Tag.Lookup("avro"); ok { diff --git a/io/gocloud/blobfs/blob.go b/io/gocloud/blobfs/blob.go index 260c02912..0bdad018b 100644 --- a/io/gocloud/blobfs/blob.go +++ b/io/gocloud/blobfs/blob.go @@ -486,10 +486,7 @@ func (bfs *FileIO) DeleteFiles(ctx context.Context, paths []string) ([]string, e } results := make([]result, len(paths)) - workers := len(paths) - if workers > deleteFilesMaxConcurrency { - workers = deleteFilesMaxConcurrency - } + workers := min(len(paths), deleteFilesMaxConcurrency) jobs := make(chan int) var wg sync.WaitGroup diff --git a/manifest_projection_test.go b/manifest_projection_test.go index d7cd76c89..fdfe138af 100644 --- a/manifest_projection_test.go +++ b/manifest_projection_test.go @@ -32,7 +32,7 @@ func TestManifestEntryProjectionWhitelistCoversDataFileSchema(t *testing.T) { schema := NewSchema(1, NestedField{ ID: 1, Name: "id", Type: PrimitiveTypes.Int64, Required: true, }) - dataFileType := reflect.TypeOf(dataFile{}) + dataFileType := reflect.TypeFor[dataFile]() dataFileFields := make(map[string]struct{}, len(dataFileAvroFieldIndexes)) for _, index := range dataFileAvroFieldIndexes { name := dataFileType.Field(index).Tag.Get("avro") diff --git a/table/deferred_snapshots_test.go b/table/deferred_snapshots_test.go index 67fb72472..87dda88d1 100644 --- a/table/deferred_snapshots_test.go +++ b/table/deferred_snapshots_test.go @@ -355,13 +355,11 @@ func TestDeferredSnapshotsConcurrentMaterialization(t *testing.T) { var wg sync.WaitGroup for range 32 { - wg.Add(1) - go func() { - defer wg.Done() + wg.Go(func() { require.NotNil(t, meta.CurrentSnapshot()) require.NotNil(t, meta.SnapshotByID(historicalID)) require.Len(t, meta.Snapshots(), 2) - }() + }) } wg.Wait() } @@ -410,11 +408,9 @@ func TestDeferredSnapshotsConcurrentSingleLookupDoesNotMaterializeHistory(t *tes var wg sync.WaitGroup for range 32 { - wg.Add(1) - go func() { - defer wg.Done() + wg.Go(func() { require.Equal(t, historicalID, meta.SnapshotByID(historicalID).SnapshotID) - }() + }) } wg.Wait() diff --git a/table/dv/roaring_bitmap.go b/table/dv/roaring_bitmap.go index 7f99fbe4b..0a72d4085 100644 --- a/table/dv/roaring_bitmap.go +++ b/table/dv/roaring_bitmap.go @@ -299,10 +299,7 @@ func (b *RoaringPositionBitmap) KeepMaskBytes(length int64) []byte { if bucketBitBase >= uint64(length) { continue } - bucketBits := uint64(length) - bucketBitBase - if bucketBits > 1<<32 { - bucketBits = 1 << 32 - } + bucketBits := min(uint64(length)-bucketBitBase, 1<<32) if bm.CardinalityInRange(0, bucketBits) < bm.DenseSize() { it := bm.Iterator() for it.HasNext() { @@ -322,10 +319,7 @@ func (b *RoaringPositionBitmap) KeepMaskBytes(length int64) []byte { continue } // Cap the bucket's bit range to what fits in `length`. - bucketBits = uint64(len(dense)) * 64 - if bucketBits > uint64(length)-bucketBitBase { - bucketBits = uint64(length) - bucketBitBase - } + bucketBits = min(uint64(len(dense))*64, uint64(length)-bucketBitBase) // bucketBitBase = key << 32 is always 8-byte-aligned, so the // BitmapWordWriter runs with offset=0 internally. The trailing-byte // loop below relies on that alignment — PutNextTrailingByte's diff --git a/table/dv_scan_planning_test.go b/table/dv_scan_planning_test.go index 02f95b198..86a4cea49 100644 --- a/table/dv_scan_planning_test.go +++ b/table/dv_scan_planning_test.go @@ -230,11 +230,9 @@ func TestManifestEntries_ConcurrentMerge(t *testing.T) { entries := newManifestEntries() var wg sync.WaitGroup for range manifestCount { - wg.Add(1) - go func() { - defer wg.Done() + wg.Go(func() { assert.NoError(t, entries.merge(batch)) - }() + }) } wg.Wait() diff --git a/table/equality_delete_reader.go b/table/equality_delete_reader.go index be778b029..17993313b 100644 --- a/table/equality_delete_reader.go +++ b/table/equality_delete_reader.go @@ -319,9 +319,9 @@ func schemaForEqualityFields(current *iceberg.Schema, schemas []*iceberg.Schema, } // Scan tasks do not retain the equality delete's sequence number, so use // the newest schema that can resolve the file's complete equality key. - for i := len(schemas) - 1; i >= 0; i-- { - if hasAllFields(schemas[i]) { - return schemas[i] + for _, v := range slices.Backward(schemas) { + if hasAllFields(v) { + return v } } diff --git a/table/inspect_internal_test.go b/table/inspect_internal_test.go index 24ad364e4..57c992cd6 100644 --- a/table/inspect_internal_test.go +++ b/table/inspect_internal_test.go @@ -2224,8 +2224,7 @@ func TestInspectFilesKeepCallerContextForManifestListRead(t *testing.T) { }, nil } - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() + ctx := t.Context() manifests, err := tbl.Inspect().Manifests(ctx) require.NoError(t, err) manifests.Release() diff --git a/table/inspect_partitions_bench_test.go b/table/inspect_partitions_bench_test.go index 38ac92999..cee48c384 100644 --- a/table/inspect_partitions_bench_test.go +++ b/table/inspect_partitions_bench_test.go @@ -133,7 +133,7 @@ func benchmarkInspectPartitionFiles( partition := make(map[int]any, benchmark.fieldCount) for field, partitionField := range partitionFields { if benchmark.binary { - partition[partitionField.FieldID] = []byte(fmt.Sprintf("partition-%d-field-%d", partitionID, field)) + partition[partitionField.FieldID] = fmt.Appendf(nil, "partition-%d-field-%d", partitionID, field) } else { partition[partitionField.FieldID] = int32(partitionID + field) } diff --git a/table/internal/utils.go b/table/internal/utils.go index 743985db8..92e4850ce 100644 --- a/table/internal/utils.go +++ b/table/internal/utils.go @@ -636,8 +636,8 @@ func TruncateUpperBoundBinary(val []byte, trunc int) []byte { result := slices.Clone(val[:trunc]) - for i := len(result) - 1; i >= 0; i-- { - if result[i] < 255 { + for i, v := range slices.Backward(result) { + if v < 255 { result[i]++ return result[:i+1] diff --git a/table/internal/variant_shredding.go b/table/internal/variant_shredding.go index c7c8670fb..ce9ae9dad 100644 --- a/table/internal/variant_shredding.go +++ b/table/internal/variant_shredding.go @@ -388,10 +388,7 @@ func decimalArrowType(info *fieldInfo) arrow.DataType { // Always Decimal128: arrow-go's pqarrow maps it to INT32/INT64/FLBA by // precision and cannot serialize Decimal32/Decimal64. intDigits := max(info.maxDecimalIntDigits, 0) - prec := min(intDigits+info.maxDecimalScale, 38) - if prec < 1 { - prec = 1 - } + prec := max(min(intDigits+info.maxDecimalScale, 38), 1) scale := info.maxDecimalScale if maxScale := 38 - intDigits; scale > maxScale { if maxScale < 0 { diff --git a/table/orphan_cleanup.go b/table/orphan_cleanup.go index dde04b57d..22d6a6019 100644 --- a/table/orphan_cleanup.go +++ b/table/orphan_cleanup.go @@ -196,7 +196,7 @@ func flattenURIEquivalences(equivalences map[string]string) map[string]string { continue } - for _, value := range strings.Split(group, ",") { + for value := range strings.SplitSeq(group, ",") { flattened[strings.TrimSpace(value)] = equivalences[group] } } diff --git a/table/positional_delete_index_bench_test.go b/table/positional_delete_index_bench_test.go index 3ababeeb9..dd5ed66c2 100644 --- a/table/positional_delete_index_bench_test.go +++ b/table/positional_delete_index_bench_test.go @@ -97,8 +97,8 @@ func positionalDeletePartitionKeyBenchmarkFiles( partition := make(map[int]any, fieldCount) for field, partitionField := range partitionFields { if binaryValue { - partition[partitionField.FieldID] = []byte(fmt.Sprintf( - "partition-%02d-%02d", i%100, field)) + partition[partitionField.FieldID] = fmt.Appendf(nil, + "partition-%02d-%02d", i%100, field) } else { partition[partitionField.FieldID] = int32(i % 100) } diff --git a/table/rolling_data_writer_test.go b/table/rolling_data_writer_test.go index b3426d463..717f6e5a2 100644 --- a/table/rolling_data_writer_test.go +++ b/table/rolling_data_writer_test.go @@ -315,16 +315,14 @@ func (s *RollingDataWriterTestSuite) TestConcurrentGetOrCreateCreatesOneWriter() var wg sync.WaitGroup for range goroutineCount { - wg.Add(1) - go func() { - defer wg.Done() + wg.Go(func() { <-start writer, err := factory.getOrCreateRollingDataWriter(s.ctx, "partition", nil, outputCh) results <- struct { writer *RollingDataWriter err error }{writer, err} - }() + }) } close(start) @@ -1129,16 +1127,14 @@ func (s *RollingDataWriterTestSuite) TestConcurrentAddDuringStreamErrorLeaksNoth var atRetain, done sync.WaitGroup atRetain.Add(senders) for range senders { - done.Add(1) - go func() { - defer done.Done() + done.Go(func() { record := s.buildRecord(arrSchema, 3) // Add retains (via gatedRetainRecord.Retain, which parks the sender // between the closed check and the enqueue) then enqueues or aborts; // the caller always drops its own reference afterward. _ = writer.Add(gatedRetainRecord{RecordBatch: record, retainGate: retainGate, atRetain: &atRetain}) record.Release() - }() + }) } atRetain.Wait() // every sender is past its closed check, parked in Retain diff --git a/table/updates_test.go b/table/updates_test.go index de500074c..60e48c72c 100644 --- a/table/updates_test.go +++ b/table/updates_test.go @@ -730,7 +730,7 @@ func TestUnmarshalUpdatesRejectsMissingRequiredFields(t *testing.T) { for _, tt := range tests { t.Run(tt.action, func(t *testing.T) { var updates Updates - data := []byte(fmt.Sprintf(`[{"action":%q}]`, tt.action)) + data := fmt.Appendf(nil, `[{"action":%q}]`, tt.action) if tt.action == UpdateSetSnapshotRef { data = snapshotRefPayload(tt.field, false) } @@ -797,7 +797,7 @@ func TestUnmarshalUpdatesRejectsNullRequiredPayload(t *testing.T) { for _, tt := range tests { t.Run(tt.action+"/"+tt.field, func(t *testing.T) { var updates Updates - data := []byte(fmt.Sprintf(`[{"action":%q,%q:null}]`, tt.action, tt.field)) + data := fmt.Appendf(nil, `[{"action":%q,%q:null}]`, tt.action, tt.field) if tt.action == UpdateSetSnapshotRef { data = snapshotRefPayload(tt.field, true) }