dialect agnostic approach

This commit is contained in:
Elijah Duffy
2025-06-05 13:52:55 -07:00
parent b201306152
commit 4d251ffbcf
3 changed files with 63 additions and 52 deletions

View File

@@ -2,45 +2,45 @@ package migratecli
import ( import (
"context" "context"
"database/sql"
"fmt" "fmt"
"gitea.auvem.com/go-toolkit/app" "gitea.auvem.com/go-toolkit/app"
"gitea.auvem.com/go-toolkit/appcli" "gitea.auvem.com/go-toolkit/appcli"
"gitea.auvem.com/go-toolkit/dbx"
"gitea.auvem.com/go-toolkit/migrate" "gitea.auvem.com/go-toolkit/migrate"
"github.com/pressly/goose/v3" "github.com/pressly/goose/v3"
"github.com/urfave/cli/v3" "github.com/urfave/cli/v3"
) )
// MigrateCmd returns the main migrate command. // MigrateCmd returns the main migrate command.
func MigrateCmd(directDeps []*app.Module, childDeps []*app.Module) *cli.Command { func MigrateCmd(sqlo *sql.DB, directDeps []*app.Module, childDeps []*app.Module) *cli.Command {
return appcli.NewCommand(&cli.Command{ return appcli.NewCommand(&cli.Command{
Name: "migrate", Name: "migrate",
Usage: "Migrate the database", Usage: "Migrate the database",
Commands: AllSubcommands(childDeps...), Commands: AllSubcommands(sqlo, childDeps...),
}, directDeps...) }, directDeps...)
} }
// AllSubcommands returns all subcommands of the migrate command. // AllSubcommands returns all subcommands of the migrate command.
func AllSubcommands(deps ...*app.Module) []*cli.Command { func AllSubcommands(sqlo *sql.DB, deps ...*app.Module) []*cli.Command {
return []*cli.Command{ return []*cli.Command{
MigrateStatusCmd(deps...), MigrateStatusCmd(sqlo, deps...),
MigrateCreateCmd(deps...), MigrateCreateCmd(sqlo, deps...),
MigrateUpCmd(deps...), MigrateUpCmd(sqlo, deps...),
MigrateUpToCmd(deps...), MigrateUpToCmd(sqlo, deps...),
MigrateDownCmd(deps...), MigrateDownCmd(sqlo, deps...),
MigrateDownToCmd(deps...), MigrateDownToCmd(sqlo, deps...),
MigrateRedoCmd(deps...), MigrateRedoCmd(sqlo, deps...),
} }
} }
// MigrateStatusCmd returns a command to get database migration status. // MigrateStatusCmd returns a command to get database migration status.
func MigrateStatusCmd(deps ...*app.Module) *cli.Command { func MigrateStatusCmd(sqlo *sql.DB, deps ...*app.Module) *cli.Command {
return appcli.NewCommand(&cli.Command{ return appcli.NewCommand(&cli.Command{
Name: "status", Name: "status",
Usage: "Get database migration status", Usage: "Get database migration status",
Action: func(ctx context.Context, cmd *cli.Command) error { Action: func(ctx context.Context, cmd *cli.Command) error {
if err := goose.Status(dbx.SQLO(), migrate.MigrationsConfig().BasePath); err != nil { if err := goose.Status(sqlo, migrate.MigrationsConfig().BasePath); err != nil {
return fmt.Errorf("couldn't get migration status: %v", err) return fmt.Errorf("couldn't get migration status: %v", err)
} }
@@ -50,7 +50,7 @@ func MigrateStatusCmd(deps ...*app.Module) *cli.Command {
} }
// MigrateCreateCmd returns a command to create a new migration. // MigrateCreateCmd returns a command to create a new migration.
func MigrateCreateCmd(deps ...*app.Module) *cli.Command { func MigrateCreateCmd(sqlo *sql.DB, deps ...*app.Module) *cli.Command {
return appcli.NewCommand(&cli.Command{ return appcli.NewCommand(&cli.Command{
Name: "create", Name: "create",
Usage: "Create a new migration", Usage: "Create a new migration",
@@ -70,7 +70,7 @@ func MigrateCreateCmd(deps ...*app.Module) *cli.Command {
goose.SetSequential(sequential) goose.SetSequential(sequential)
} }
if err := goose.Create(dbx.SQLO(), "migrations", cmd.StringArg("name"), cmd.StringArg("type")); err != nil { if err := goose.Create(sqlo, "migrations", cmd.StringArg("name"), cmd.StringArg("type")); err != nil {
return fmt.Errorf("couldn't create migration: %v", err) return fmt.Errorf("couldn't create migration: %v", err)
} }
@@ -80,12 +80,12 @@ func MigrateCreateCmd(deps ...*app.Module) *cli.Command {
} }
// MigrateUpCmd returns a command to apply all available database migrations. // MigrateUpCmd returns a command to apply all available database migrations.
func MigrateUpCmd(deps ...*app.Module) *cli.Command { func MigrateUpCmd(sqlo *sql.DB, deps ...*app.Module) *cli.Command {
return appcli.NewCommand(&cli.Command{ return appcli.NewCommand(&cli.Command{
Name: "up", Name: "up",
Usage: "Apply all available database migrations", Usage: "Apply all available database migrations",
Action: func(ctx context.Context, cmd *cli.Command) error { Action: func(ctx context.Context, cmd *cli.Command) error {
if err := goose.Up(dbx.SQLO(), migrate.MigrationsConfig().BasePath); err != nil { if err := goose.Up(sqlo, migrate.MigrationsConfig().BasePath); err != nil {
return fmt.Errorf("couldn't apply migrations: %v", err) return fmt.Errorf("couldn't apply migrations: %v", err)
} }
return nil return nil
@@ -94,7 +94,7 @@ func MigrateUpCmd(deps ...*app.Module) *cli.Command {
} }
// MigrateUpToCmd returns a command to apply all available database migrations up to a specific version. // MigrateUpToCmd returns a command to apply all available database migrations up to a specific version.
func MigrateUpToCmd(deps ...*app.Module) *cli.Command { func MigrateUpToCmd(sqlo *sql.DB, deps ...*app.Module) *cli.Command {
return appcli.NewCommand(&cli.Command{ return appcli.NewCommand(&cli.Command{
Name: "up-to", Name: "up-to",
Usage: "Apply all available database migrations up to a specific version", Usage: "Apply all available database migrations up to a specific version",
@@ -106,7 +106,7 @@ func MigrateUpToCmd(deps ...*app.Module) *cli.Command {
}, },
Action: func(ctx context.Context, cmd *cli.Command) error { Action: func(ctx context.Context, cmd *cli.Command) error {
version := cmd.Int64("version") version := cmd.Int64("version")
if err := goose.UpTo(dbx.SQLO(), migrate.MigrationsConfig().BasePath, version); err != nil { if err := goose.UpTo(sqlo, migrate.MigrationsConfig().BasePath, version); err != nil {
return fmt.Errorf("couldn't apply migrations to target version %d: %v", version, err) return fmt.Errorf("couldn't apply migrations to target version %d: %v", version, err)
} }
return nil return nil
@@ -115,12 +115,12 @@ func MigrateUpToCmd(deps ...*app.Module) *cli.Command {
} }
// MigrateDownCmd returns a command to rollback the most recent database migration. // MigrateDownCmd returns a command to rollback the most recent database migration.
func MigrateDownCmd(deps ...*app.Module) *cli.Command { func MigrateDownCmd(sqlo *sql.DB, deps ...*app.Module) *cli.Command {
return appcli.NewCommand(&cli.Command{ return appcli.NewCommand(&cli.Command{
Name: "down", Name: "down",
Usage: "Rollback the most recent database migration", Usage: "Rollback the most recent database migration",
Action: func(ctx context.Context, cmd *cli.Command) error { Action: func(ctx context.Context, cmd *cli.Command) error {
if err := goose.Down(dbx.SQLO(), migrate.MigrationsConfig().BasePath); err != nil { if err := goose.Down(sqlo, migrate.MigrationsConfig().BasePath); err != nil {
return fmt.Errorf("couldn't rollback migration: %v", err) return fmt.Errorf("couldn't rollback migration: %v", err)
} }
return nil return nil
@@ -129,7 +129,7 @@ func MigrateDownCmd(deps ...*app.Module) *cli.Command {
} }
// MigrateDownToCmd returns a command to rollback all database migrations down to a specific version. // MigrateDownToCmd returns a command to rollback all database migrations down to a specific version.
func MigrateDownToCmd(deps ...*app.Module) *cli.Command { func MigrateDownToCmd(sqlo *sql.DB, deps ...*app.Module) *cli.Command {
return appcli.NewCommand(&cli.Command{ return appcli.NewCommand(&cli.Command{
Name: "down-to", Name: "down-to",
Usage: "Rollback all database migrations down to a specific version", Usage: "Rollback all database migrations down to a specific version",
@@ -141,7 +141,7 @@ func MigrateDownToCmd(deps ...*app.Module) *cli.Command {
}, },
Action: func(ctx context.Context, cmd *cli.Command) error { Action: func(ctx context.Context, cmd *cli.Command) error {
version := cmd.Int64("version") version := cmd.Int64("version")
if err := goose.DownTo(dbx.SQLO(), migrate.MigrationsConfig().BasePath, version); err != nil { if err := goose.DownTo(sqlo, migrate.MigrationsConfig().BasePath, version); err != nil {
return fmt.Errorf("couldn't rollback migrations to target version %d: %v", version, err) return fmt.Errorf("couldn't rollback migrations to target version %d: %v", version, err)
} }
return nil return nil
@@ -150,12 +150,12 @@ func MigrateDownToCmd(deps ...*app.Module) *cli.Command {
} }
// MigrateRedoCmd returns a command to rollback the most recent database migration and reapply it. // MigrateRedoCmd returns a command to rollback the most recent database migration and reapply it.
func MigrateRedoCmd(deps ...*app.Module) *cli.Command { func MigrateRedoCmd(sqlo *sql.DB, deps ...*app.Module) *cli.Command {
return appcli.NewCommand(&cli.Command{ return appcli.NewCommand(&cli.Command{
Name: "redo", Name: "redo",
Usage: "Rollback the most recent database migration and reapply it", Usage: "Rollback the most recent database migration and reapply it",
Action: func(ctx context.Context, cmd *cli.Command) error { Action: func(ctx context.Context, cmd *cli.Command) error {
if err := goose.Redo(dbx.SQLO(), migrate.MigrationsConfig().BasePath); err != nil { if err := goose.Redo(sqlo, migrate.MigrationsConfig().BasePath); err != nil {
return fmt.Errorf("couldn't redo migration: %v", err) return fmt.Errorf("couldn't redo migration: %v", err)
} }
return nil return nil

View File

@@ -1,17 +1,17 @@
package migratecmd package migratecmd
import ( import (
"database/sql"
"fmt" "fmt"
"strconv" "strconv"
"gitea.auvem.com/go-toolkit/dbx"
"gitea.auvem.com/go-toolkit/migrate" "gitea.auvem.com/go-toolkit/migrate"
"github.com/pressly/goose/v3" "github.com/pressly/goose/v3"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
// MigrateCmd returns the main migrate command. // MigrateCmd returns the main migrate command.
func MigrateCmd() *cobra.Command { func MigrateCmd(sqlo *sql.DB) *cobra.Command {
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "migrate", Use: "migrate",
Short: "Migrate the database", Short: "Migrate the database",
@@ -20,31 +20,31 @@ func MigrateCmd() *cobra.Command {
}, },
} }
cmd.AddCommand(AllSubcommands()...) cmd.AddCommand(AllSubcommands(sqlo)...)
return cmd return cmd
} }
// AllSubcommands returns all subcommands of the migrate command. // AllSubcommands returns all subcommands of the migrate command.
func AllSubcommands() []*cobra.Command { func AllSubcommands(sqlo *sql.DB) []*cobra.Command {
return []*cobra.Command{ return []*cobra.Command{
MigrateStatusCmd(), MigrateStatusCmd(sqlo),
MigrateCreateCmd(), MigrateCreateCmd(sqlo),
MigrateUpCmd(), MigrateUpCmd(sqlo),
MigrateUpToCmd(), MigrateUpToCmd(sqlo),
MigrateDownCmd(), MigrateDownCmd(sqlo),
MigrateDownToCmd(), MigrateDownToCmd(sqlo),
MigrateRedoCmd(), MigrateRedoCmd(sqlo),
} }
} }
// MigrateStatusCmd returns a command to get database migration status. // MigrateStatusCmd returns a command to get database migration status.
func MigrateStatusCmd() *cobra.Command { func MigrateStatusCmd(sqlo *sql.DB) *cobra.Command {
return &cobra.Command{ return &cobra.Command{
Use: "status", Use: "status",
Short: "Get database migration status", Short: "Get database migration status",
Run: func(cmd *cobra.Command, args []string) { Run: func(cmd *cobra.Command, args []string) {
if err := goose.Status(dbx.SQLO(), migrate.MigrationsConfig().BasePath); err != nil { if err := goose.Status(sqlo, migrate.MigrationsConfig().BasePath); err != nil {
fmt.Printf("Error: Couldn't get migration status: %v\n", err) fmt.Printf("Error: Couldn't get migration status: %v\n", err)
return return
} }
@@ -53,7 +53,7 @@ func MigrateStatusCmd() *cobra.Command {
} }
// MigrateCreateCmd returns a command to create a new migration. // MigrateCreateCmd returns a command to create a new migration.
func MigrateCreateCmd() *cobra.Command { func MigrateCreateCmd(sqlo *sql.DB) *cobra.Command {
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "create [NAME] [TYPE]", Use: "create [NAME] [TYPE]",
Short: "Create a new migration", Short: "Create a new migration",
@@ -69,7 +69,7 @@ func MigrateCreateCmd() *cobra.Command {
goose.SetSequential(sequential) goose.SetSequential(sequential)
} }
if err := goose.Create(dbx.SQLO(), "migrations", args[0], args[1]); err != nil { if err := goose.Create(sqlo, "migrations", args[0], args[1]); err != nil {
fmt.Printf("Error: Couldn't create migration: %v\n", err) fmt.Printf("Error: Couldn't create migration: %v\n", err)
return return
} }
@@ -81,12 +81,12 @@ func MigrateCreateCmd() *cobra.Command {
} }
// MigrateUpCmd returns a command to apply all available database migrations. // MigrateUpCmd returns a command to apply all available database migrations.
func MigrateUpCmd() *cobra.Command { func MigrateUpCmd(sqlo *sql.DB) *cobra.Command {
return &cobra.Command{ return &cobra.Command{
Use: "up", Use: "up",
Short: "Apply all available database migrations", Short: "Apply all available database migrations",
Run: func(cmd *cobra.Command, args []string) { Run: func(cmd *cobra.Command, args []string) {
if err := goose.Up(dbx.SQLO(), migrate.MigrationsConfig().BasePath); err != nil { if err := goose.Up(sqlo, migrate.MigrationsConfig().BasePath); err != nil {
fmt.Printf("Error: Couldn't apply migrations: %v\n", err) fmt.Printf("Error: Couldn't apply migrations: %v\n", err)
return return
} }
@@ -95,7 +95,7 @@ func MigrateUpCmd() *cobra.Command {
} }
// MigrateUpToCmd returns a command to apply all available database migrations up to a specific version. // MigrateUpToCmd returns a command to apply all available database migrations up to a specific version.
func MigrateUpToCmd() *cobra.Command { func MigrateUpToCmd(sqlo *sql.DB) *cobra.Command {
return &cobra.Command{ return &cobra.Command{
Use: "up-to [VERSION]", Use: "up-to [VERSION]",
Short: "Apply all available database migrations up to a specific version", Short: "Apply all available database migrations up to a specific version",
@@ -107,7 +107,7 @@ func MigrateUpToCmd() *cobra.Command {
return return
} }
if err := goose.UpTo(dbx.SQLO(), migrate.MigrationsConfig().BasePath, version); err != nil { if err := goose.UpTo(sqlo, migrate.MigrationsConfig().BasePath, version); err != nil {
fmt.Printf("Error: Couldn't apply migrations to target version %d: %v\n", version, err) fmt.Printf("Error: Couldn't apply migrations to target version %d: %v\n", version, err)
return return
} }
@@ -116,12 +116,12 @@ func MigrateUpToCmd() *cobra.Command {
} }
// MigrateDownCmd returns a command to rollback the most recent database migration. // MigrateDownCmd returns a command to rollback the most recent database migration.
func MigrateDownCmd() *cobra.Command { func MigrateDownCmd(sqlo *sql.DB) *cobra.Command {
return &cobra.Command{ return &cobra.Command{
Use: "down", Use: "down",
Short: "Rollback the most recent database migration", Short: "Rollback the most recent database migration",
Run: func(cmd *cobra.Command, args []string) { Run: func(cmd *cobra.Command, args []string) {
if err := goose.Down(dbx.SQLO(), migrate.MigrationsConfig().BasePath); err != nil { if err := goose.Down(sqlo, migrate.MigrationsConfig().BasePath); err != nil {
fmt.Printf("Error: Couldn't rollback migration: %v\n", err) fmt.Printf("Error: Couldn't rollback migration: %v\n", err)
return return
} }
@@ -130,7 +130,7 @@ func MigrateDownCmd() *cobra.Command {
} }
// MigrateDownToCmd returns a command to rollback all database migrations down to a specific version. // MigrateDownToCmd returns a command to rollback all database migrations down to a specific version.
func MigrateDownToCmd() *cobra.Command { func MigrateDownToCmd(sqlo *sql.DB) *cobra.Command {
return &cobra.Command{ return &cobra.Command{
Use: "down-to [VERSION]", Use: "down-to [VERSION]",
Short: "Rollback all database migrations down to a specific version", Short: "Rollback all database migrations down to a specific version",
@@ -142,7 +142,7 @@ func MigrateDownToCmd() *cobra.Command {
return return
} }
if err := goose.DownTo(dbx.SQLO(), migrate.MigrationsConfig().BasePath, version); err != nil { if err := goose.DownTo(sqlo, migrate.MigrationsConfig().BasePath, version); err != nil {
fmt.Printf("Error: Couldn't rollback migrations to target version %d: %v\n", version, err) fmt.Printf("Error: Couldn't rollback migrations to target version %d: %v\n", version, err)
return return
} }
@@ -151,12 +151,12 @@ func MigrateDownToCmd() *cobra.Command {
} }
// MigrateRedoCmd returns a command to rollback the most recent database migration and reapply it. // MigrateRedoCmd returns a command to rollback the most recent database migration and reapply it.
func MigrateRedoCmd() *cobra.Command { func MigrateRedoCmd(sqlo *sql.DB) *cobra.Command {
return &cobra.Command{ return &cobra.Command{
Use: "redo", Use: "redo",
Short: "Rollback the most recent database migration and reapply it", Short: "Rollback the most recent database migration and reapply it",
Run: func(cmd *cobra.Command, args []string) { Run: func(cmd *cobra.Command, args []string) {
if err := goose.Redo(dbx.SQLO(), migrate.MigrationsConfig().BasePath); err != nil { if err := goose.Redo(sqlo, migrate.MigrationsConfig().BasePath); err != nil {
fmt.Printf("Error: Couldn't redo migration: %v\n", err) fmt.Printf("Error: Couldn't redo migration: %v\n", err)
return return
} }

View File

@@ -2,6 +2,7 @@ package migrate
import ( import (
"context" "context"
"database/sql"
"io/fs" "io/fs"
"log/slog" "log/slog"
"time" "time"
@@ -13,6 +14,13 @@ import (
// MigrationsOpts define the options for the migrations module. // MigrationsOpts define the options for the migrations module.
type MigrationOpts struct { type MigrationOpts struct {
// SQLO is the SQL database handle used for migrations. REQUIRED.
SQLO *sql.DB
// Dialect is the database dialect used for migrations (e.g., "mysql", "postgres").
// REQUIRED. Must match the dialect used in dbx.
Dialect goose.Dialect
// FS is the filesystem where migration files are stored. // FS is the filesystem where migration files are stored.
FS fs.FS FS fs.FS
@@ -82,6 +90,9 @@ func ModuleMigrations(cfg MigrationOpts) *app.Module {
panic("ModuleMigrations initialized multiple times") panic("ModuleMigrations initialized multiple times")
} }
if cfg.SQLO == nil {
panic("Migration SQL handle (SQLO) must be set in the configuration")
}
if cfg.BasePath == "" { if cfg.BasePath == "" {
cfg.BasePath = "." // default base path if not set cfg.BasePath = "." // default base path if not set
} }
@@ -121,13 +132,13 @@ func ModuleAutoMigrate(enabled bool) *app.Module {
func setupMigrations(_ *app.Module) error { func setupMigrations(_ *app.Module) error {
var err error var err error
if err := goose.SetDialect("mysql"); err != nil { if err := goose.SetDialect(string(migrationsConfig.Dialect)); err != nil {
slog.Error("Couldn't set database dialect for goose", "err", err) slog.Error("Couldn't set database dialect for goose", "err", err)
return err return err
} }
// Initialize the goose migration provider // Initialize the goose migration provider
Migration, err = goose.NewProvider(goose.DialectMySQL, dbx.SQLO(), migrationsConfig.FS) Migration, err = goose.NewProvider(migrationsConfig.Dialect, migrationsConfig.SQLO, migrationsConfig.FS)
if err != nil { if err != nil {
slog.Error("Couldn't initialize goose migration provider", "err", err) slog.Error("Couldn't initialize goose migration provider", "err", err)
return err return err