From 71e1fab91d986424b24492b9a68285daa7953c5c Mon Sep 17 00:00:00 2001 From: Brandur Date: Tue, 30 Dec 2025 12:18:22 -0700 Subject: [PATCH] Initialize `nil` migration opts earlier The migration functions are supposed to allow you to call them with nil options, but I noticed in some cases it's not working properly for `MigrateTx` and it's possible to raise a nil pointer panic. Here, initialize a nil `opts` earlier to prevent the problem. This makes the code more obvious/conventional anyway in that it's easier to see that nil options is supposed to be supported. --- CHANGELOG.md | 4 ++++ rivermigrate/river_migrate.go | 12 ++++++++---- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 93c3bc2b..4bf697c9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Fixed + +- Fix possible nil pointer panic when using nil `opts` in `Migrator.MigrateTx`. [PR #1117](https://github.com/riverqueue/river/pull/1117). + ## [0.29.0] - 2025-12-22 ### Added diff --git a/rivermigrate/river_migrate.go b/rivermigrate/river_migrate.go index 99068256..cb411293 100644 --- a/rivermigrate/river_migrate.go +++ b/rivermigrate/river_migrate.go @@ -385,6 +385,10 @@ func (m *Migrator[TTx]) ValidateTx(ctx context.Context, tx TTx) (*ValidateResult // migrateDown runs down migrations. func (m *Migrator[TTx]) migrateDown(ctx context.Context, exec riverdriver.Executor, direction Direction, opts *MigrateOpts, inOuterTx bool) (*MigrateResult, error) { + if opts == nil { + opts = &MigrateOpts{} + } + existingMigrations, err := m.existingMigrations(ctx, exec) if err != nil { return nil, err @@ -434,6 +438,10 @@ func (m *Migrator[TTx]) migrateDown(ctx context.Context, exec riverdriver.Execut // migrateUp runs up migrations. func (m *Migrator[TTx]) migrateUp(ctx context.Context, exec riverdriver.Executor, direction Direction, opts *MigrateOpts, inOuterTx bool) (*MigrateResult, error) { + if opts == nil { + opts = &MigrateOpts{} + } + existingMigrations, err := m.existingMigrations(ctx, exec) if err != nil { return nil, err @@ -493,10 +501,6 @@ func (m *Migrator[TTx]) validate(ctx context.Context, exec riverdriver.Executor) // Common code shared between the up and down migration directions that walks // through each target migration and applies it, logging appropriately. func (m *Migrator[TTx]) applyMigrations(ctx context.Context, exec riverdriver.Executor, direction Direction, opts *MigrateOpts, inOuterTx bool, sortedTargetMigrations []Migration) (*MigrateResult, error) { - if opts == nil { - opts = &MigrateOpts{} - } - var maxSteps int switch { case opts.MaxSteps != 0: