Skip to content

Commit

Permalink
expose a failed migration script's contents via MigrationError
Browse files Browse the repository at this point in the history
  • Loading branch information
bokwoon95 committed Aug 7, 2023
1 parent 47c4e75 commit 94bca37
Show file tree
Hide file tree
Showing 3 changed files with 39 additions and 3 deletions.
4 changes: 4 additions & 0 deletions ddl/START_HERE.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,10 @@ There are tests that require a live database connection. They will only run if y
$ go test ./ddl -tags=fts5 -postgres $POSTGRES_URL -mysql $MYSQL_URL -sqlserver $SQLSERVER_URL # -failfast -shuffle=on -coverprofile=coverage
```

- For $POSTGRES\_URL, make sure sslmode=disable is in the query string e.g. `postgres://username:password@address:port/database?sslmode=disable`.

- For $MYSQL\_URL, make sure multiStatements=true is in the query string e.g. `username:password@tcp(address:port)/database?multiStatements=true`.

If you have docker, you can use the docker-compose.yml (run `docker-compose up`) at the root of this project's directory to spin up Postgres, MySQL and SQL Server databases that are reachable at the following URLs:

```shell
Expand Down
34 changes: 31 additions & 3 deletions ddl/migrate_cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -334,6 +334,27 @@ func (m migration) rowmapper(row *sq.Row) migration {
// https://www.reddit.com/r/golang/comments/ntyi7i/what_is_the_reason_go_chose_to_use_a_constant_as/h0w0tu7/
var rng = rand.New(rand.NewSource(int64(new(maphash.Hash).Sum64())))

// MigrationError is returned by (*MigrateCmd).Run() if any errors were
// encountered when running a migration script (SQL syntax errors, violated
// constraints, etc).
type MigrationError struct {
Err error // Migration error.
Filename string // Migration filename.
Contents string // Contents of the migration script.
StartedAt time.Time // When the migration started at.
TimeTaken time.Duration // How long the migration took.
}

// Error implements the error interface.
func (migrationErr *MigrationError) Error() string {
return migrationErr.Err.Error()
}

// Unwrap returns the underlying error when running the migration.
func (migrationErr *MigrationError) Unwrap() error {
return migrationErr.Err
}

func (cmd *MigrateCmd) runWithRetry(conn *sql.Conn, queue []migration) error {
isTx := len(queue) > 1 || (!strings.HasSuffix(queue[0].filename, ".txoff.sql") && cmd.Dialect != DialectMySQL) || strings.HasSuffix(queue[0].filename, ".tx.sql")
isStmt := false
Expand Down Expand Up @@ -472,12 +493,19 @@ func (cmd *MigrateCmd) run(conn *sql.Conn, migrations []migration) (stoppedAt in
fmt.Fprintln(cmd.Stderr, timestamp()+"[START] "+m.filename)
}
m.startedAt = sql.NullTime{Time: time.Now(), Valid: true}
_, migrationErr := db.ExecContext(cmd.Ctx, contents)
_, err = db.ExecContext(cmd.Ctx, contents)
timeTaken := time.Since(m.startedAt.Time)
m.timeTakenNs = sql.NullInt64{Int64: int64(timeTaken), Valid: true}
if migrationErr != nil {
if err != nil {
migrationErr := &MigrationError{
Err: err,
Filename: m.filename,
Contents: contents,
StartedAt: m.startedAt.Time,
TimeTaken: timeTaken,
}
if cmd.driver.AnnotateError != nil {
migrationErr = cmd.driver.AnnotateError(migrationErr, contents)
migrationErr.Err = cmd.driver.AnnotateError(migrationErr.Err, contents)
}
m.success = false
if cmd.Verbose {
Expand Down
4 changes: 4 additions & 0 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,10 @@ func main() {
if errors.Is(err, flag.ErrHelp) {
os.Exit(0)
}
var migrationErr *ddl.MigrationError
if errors.As(err, &migrationErr) {
fmt.Fprintln(os.Stderr, migrationErr.Contents)
}
fmt.Fprintln(os.Stderr, subcmd+": "+err.Error())
os.Exit(1)
}
Expand Down

0 comments on commit 94bca37

Please sign in to comment.