Skip to content

fix: allow inlined migration functions in goose validate - #1110

Open
dylanpulver wants to merge 1 commit into
pressly:mainfrom
dylanpulver:fix/validate-inline-migration-funcs
Open

fix: allow inlined migration functions in goose validate#1110
dylanpulver wants to merge 1 commit into
pressly:mainfrom
dylanpulver:fix/validate-inline-migration-funcs

Conversation

@dylanpulver

@dylanpulver dylanpulver commented Aug 20, 2026

Copy link
Copy Markdown

goose validate refuses any Go migration that registers an inlined function:

func init() {
	goose.AddMigration(func(tx *sql.Tx) error { return nil }, nil)
}
failed to parse file "00001_inline.go": failed to assert argument identifier: got *ast.Ident

parseInitFunc in internal/migrationstats/migration_go.go asserted that each argument to AddMigration and its three siblings was an *ast.Ident so it could read the function's name. A function literal parses as *ast.FuncLit, the assertion fails, and the whole command aborts. The same rejection hits a function reached through another package, such as goose.AddMigration(migrations.Up001, migrations.Down001), which parses as *ast.SelectorExpr.

The migrations themselves run fine, so the command reports a problem that does not exist.

Why dropping the identifier requirement is safe. The extracted names were never used as names. They were read in exactly two places: migrationstats.go passed them to nilAsNumber, which compared the string against "nil" and returned 0 or 1 for Stats.UpCount/DownCount — the name itself never reaches the output, since the Name column prints the file name — and parseInitFunc itself, which rejected an empty string as a sign its own parsing had gone wrong. So the name collapsed to a single bit before use, and migrationstats is under internal/, so there is no external caller either. Replacing the two string fields with two booleans set by a nil check preserves every observable output, which is what @mfridman suggested in the issue.

The removed branch also formatted %T against arg, the result of the failed type assertion, rather than against expr. arg is a nil *ast.Ident in that branch, so the message always claimed the argument was an *ast.Ident no matter what it really was — that is why #519 shows got *ast.Ident for a function literal.

TestParsingGoMigrations gains inlined literals for all four register functions, plus mixed cases and a case using qualified names from another package; the existing named-function and nil rows are converted to assert the new booleans. TestGoMigrationStatsInline runs the inlined sources through GatherStats and checks the Up and Down counts that goose validate prints. TestParsingGoMigrationsError gains a case for a call with the wrong number of arguments.

I also built the CLI on both sides of the change and ran goose validate -v against a directory holding the snippet from the issue. Before, it exits 1 with the parse error. After, it prints the row with Up 1 and Down 0.

Ran locally: go test ./internal/migrationstats/... with -race, the package tests for the rest of the module, go test ./tests/gomigrations/..., go vet ./..., and golangci-lint run ./..., all green. I did not run the Docker-backed integration tests under internal/testing/integration or pkg/dockermanage, since Docker was not available here.

Fixes #519

The Go migration parser asserted that both arguments to AddMigration and
friends were *ast.Ident, so an inlined func literal made goose validate
fail with "failed to assert argument identifier". The parser only needs
to know whether each argument is nil, so it now checks for the nil
identifier instead of requiring a named function.

Fixes pressly#519

// isNilIdent reports whether the expression is the nil identifier.
func isNilIdent(expr ast.Expr) bool {
ident, ok := expr.(*ast.Ident)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we want to unwrap parentheses here? (nil) is valid Go, but currently gets counted as a migration function.

Using ast.Unparen(expr) before the type assertion should handle.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

goose validate fails to parse inlined functions

2 participants