From c5f3d9146793b9a49790a750ff6ef086f6095a3f Mon Sep 17 00:00:00 2001 From: dyma solovei Date: Tue, 19 Nov 2024 15:00:09 +0100 Subject: [PATCH 01/17] feat: manage database schema with bundb CLI Here we introduce bundb auto migrate command to create and run migrations from the CLI. It works by importing the Models from a pre-build user plugin, similarly to how Migrator gets the list of migrations by being compiled together with the user's package. The difference here is that bundb can be distributed as a standalone binary and only requires users to set up the migrations/main.go file. --- cmd/bundb/main.go | 222 ++++++++++++++++++++++++++++++++++++++++++++++ go.mod | 33 ++++++- go.sum | 84 +++++++++++++++++- 3 files changed, 334 insertions(+), 5 deletions(-) create mode 100644 cmd/bundb/main.go diff --git a/cmd/bundb/main.go b/cmd/bundb/main.go new file mode 100644 index 000000000..9d2918183 --- /dev/null +++ b/cmd/bundb/main.go @@ -0,0 +1,222 @@ +package main + +import ( + "database/sql" + "fmt" + "log" + "os" + "plugin" + "strings" + + "github.com/uptrace/bun" + "github.com/uptrace/bun/dialect/mssqldialect" + "github.com/uptrace/bun/dialect/mysqldialect" + "github.com/uptrace/bun/dialect/oracledialect" + "github.com/uptrace/bun/dialect/pgdialect" + "github.com/uptrace/bun/dialect/sqlitedialect" + "github.com/uptrace/bun/driver/pgdriver" + "github.com/uptrace/bun/driver/sqliteshim" + "github.com/uptrace/bun/migrate" + "github.com/uptrace/bun/schema" + "github.com/urfave/cli/v2" +) + +const defaultMigrationsDirectory = "./migrations/" + +var ( + supportedDrivers = []string{"postgres", "sqlserver", "mysql", "oci8", "file"} + + // TODO: build automatically based on the migrationsPath + pluginPath string + + // Migrator options + migratorOptions []migrate.MigratorOption + migrationOptions []migrate.MigrationOption + + // AutoMigrator options + autoMigratorOptions []migrate.AutoMigratorOption +) + +var app = &cli.App{ + Name: "bundb", + Commands: cli.Commands{ + // bundb init --create-directory + // bundb create --sql --go --tx [-d | --dir] + // bundb migrate + // bundb auto create --sql --tx + // bundb auto migrate + &cli.Command{ + Name: "auto", + Usage: "manage database schema with AutoMigrator", + Subcommands: cli.Commands{ + &cli.Command{ + Name: "migrate", + Usage: "Generate SQL migrations and apply them right away", + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: "uri", + Aliases: []string{"database-uri", "dsn"}, + Required: true, + EnvVars: []string{"BUNDB_URI"}, + }, + &cli.StringFlag{ + Name: "driver", + }, + &cli.StringFlag{ + Name: "plugin", + Destination: &pluginPath, + Required: true, + }, + &cli.StringFlag{ + Name: "d", + Aliases: []string{"migrations-directory"}, + Action: func(ctx *cli.Context, dir string) error { + autoMigratorOptions = append(autoMigratorOptions, migrate.WithMigrationsDirectoryAuto(dir)) + return nil + }, + }, + &cli.StringFlag{ + Name: "t", + Aliases: []string{"migrations-table"}, + Action: func(ctx *cli.Context, migrationsTable string) error { + autoMigratorOptions = append(autoMigratorOptions, migrate.WithTableNameAuto(migrationsTable)) + return nil + }, + }, + &cli.StringFlag{ + Name: "l", + Aliases: []string{"locks", "migration-locks-table"}, + Action: func(ctx *cli.Context, locksTable string) error { + autoMigratorOptions = append(autoMigratorOptions, migrate.WithLocksTableNameAuto(locksTable)) + return nil + }, + }, + &cli.StringFlag{ + Name: "s", + Aliases: []string{"schema"}, + Action: func(ctx *cli.Context, schemaName string) error { + autoMigratorOptions = append(autoMigratorOptions, migrate.WithSchemaName(schemaName)) + return nil + }, + }, + &cli.StringSliceFlag{ + Name: "exclude", + Action: func(ctx *cli.Context, tables []string) error { + autoMigratorOptions = append(autoMigratorOptions, migrate.WithExcludeTable(tables...)) + return nil + }, + }, + }, + Action: func(ctx *cli.Context) error { + db, err := connect(ctx.String("uri"), ctx.String("driver"), !ctx.IsSet("driver")) + if err != nil { + return err + + } + + if !ctx.IsSet("migrations-directory") { + autoMigratorOptions = append(autoMigratorOptions, migrate.WithMigrationsDirectoryAuto(defaultMigrationsDirectory)) + + } + m, err := automigrator(db) + if err != nil { + return err + } + + group, err := m.Migrate(ctx.Context) + if err != nil { + return err + } + if group.IsZero() { + log.Print("ok, nothing to migrate") + } + return nil + + }, + }, + }, + }, + }, +} + +// connect to the database under the URI. A driver must be one of the supported drivers. +// If not set explicitly, the name of the driver is guessed from the URI. +// +// Example: +// +// "postgres://postgres:@localhost:5432/postgres" -> "postegres" +func connect(uri, driverName string, guessDriver bool) (*bun.DB, error) { + var sqldb *sql.DB + var dialect schema.Dialect + var err error + + if guessDriver { + driver, _, found := strings.Cut(uri, ":") + if !found { + return nil, fmt.Errorf("driver cannot be guessed from connection string; pass --driver option explicitly") + } + driverName = driver + } + + switch driverName { + case "postgres": + sqldb = sql.OpenDB(pgdriver.NewConnector(pgdriver.WithDSN(uri))) + dialect = pgdialect.New() + case "sqlserver": + sqldb, err = sql.Open(driverName, uri) + dialect = mssqldialect.New() + case "file": + sqldb, err = sql.Open(sqliteshim.ShimName, uri) + dialect = sqlitedialect.New() + case "mysql": + sqldb, err = sql.Open(driverName, uri) + dialect = mysqldialect.New() + case "oci8": + sqldb, err = sql.Open(driverName, uri) + dialect = oracledialect.New() + default: + err = fmt.Errorf("driver %q not recognized, supported drivers are %v", driverName, supportedDrivers) + } + + if err != nil { + return nil, err + } + + return bun.NewDB(sqldb, dialect), nil +} + +// automigrator creates AutoMigrator for models from user's 'migrations' package. +func automigrator(db *bun.DB) (*migrate.AutoMigrator, error) { + sym, err := lookup("Models") + if err != nil { + return nil, err + } + + models, ok := sym.(*[]interface{}) + if !ok { + return nil, fmt.Errorf("migrations plugin must export Models as []interface{}, got %T", models) + } + autoMigratorOptions = append(autoMigratorOptions, migrate.WithModel(*models...)) + + auto, err := migrate.NewAutoMigrator(db, autoMigratorOptions...) + if err != nil { + return nil, err + } + return auto, nil +} + +// lookup a symbol from user's migrations plugin. +func lookup(symbol string) (plugin.Symbol, error) { + p, err := plugin.Open(pluginPath) + if err != nil { + return nil, err + } + return p.Lookup(symbol) +} + +func main() { + log.SetPrefix("bundb: ") + if err := app.Run(os.Args); err != nil { + log.Fatal(err) + } +} diff --git a/go.mod b/go.mod index 48b197ab8..973680da8 100644 --- a/go.mod +++ b/go.mod @@ -8,17 +8,46 @@ require ( github.com/rs/zerolog v1.33.0 github.com/stretchr/testify v1.8.1 github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc + github.com/uptrace/bun/dialect/mssqldialect v1.2.6-0.20241118024642-c6b302ff06f6 + github.com/uptrace/bun/dialect/mysqldialect v1.2.6-0.20241118024642-c6b302ff06f6 + github.com/uptrace/bun/dialect/oracledialect v0.0.0-20241118024642-c6b302ff06f6 + github.com/uptrace/bun/dialect/pgdialect v1.2.6-0.20241118024642-c6b302ff06f6 + github.com/uptrace/bun/dialect/sqlitedialect v1.2.6-0.20241118024642-c6b302ff06f6 + github.com/uptrace/bun/driver/pgdriver v1.2.6-0.20241118024642-c6b302ff06f6 + github.com/uptrace/bun/driver/sqliteshim v1.2.5 + github.com/urfave/cli/v2 v2.27.5 github.com/vmihailenco/msgpack/v5 v5.4.1 ) require ( + github.com/bahlo/generic-list-go v0.2.0 // indirect + github.com/buger/jsonparser v1.1.1 // indirect + github.com/cpuguy83/go-md2man/v2 v2.0.5 // indirect github.com/davecgh/go-spew v1.1.1 // indirect + github.com/dustin/go-humanize v1.0.1 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect + github.com/mailru/easyjson v0.7.7 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect - github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect + github.com/mattn/go-sqlite3 v1.14.24 // indirect + github.com/ncruces/go-strftime v0.1.9 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect + github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect + github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // indirect + golang.org/x/crypto v0.29.0 // indirect + golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c // indirect + golang.org/x/mod v0.22.0 // indirect golang.org/x/sys v0.27.0 // indirect - gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect gopkg.in/yaml.v3 v3.0.1 // indirect + mellium.im/sasl v0.3.2 // indirect + modernc.org/gc/v3 v3.0.0-20241004144649-1aea3fae8852 // indirect + modernc.org/libc v1.61.0 // indirect + modernc.org/mathutil v1.6.0 // indirect + modernc.org/memory v1.8.0 // indirect + modernc.org/sqlite v1.33.1 // indirect + modernc.org/strutil v1.2.0 // indirect + modernc.org/token v1.1.0 // indirect ) diff --git a/go.sum b/go.sum index 0e97efd20..9de74af76 100644 --- a/go.sum +++ b/go.sum @@ -1,19 +1,35 @@ github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= +github.com/cpuguy83/go-md2man/v2 v2.0.5 h1:ZtcqGrnekaHpVLArFSe4HK5DoKx1T0rq2DwVB0alcyc= +github.com/cpuguy83/go-md2man/v2 v2.0.5/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= +github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd h1:gbpYu9NMq8jhDVbvlGkMFWCjLFlqqEZjEmObmhUy6Vo= +github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd/go.mod h1:kf6iHlnVGwgKolg33glAes7Yg/8iWP8ukqeldJSO7jw= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= +github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= -github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= -github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= +github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-sqlite3 v1.14.24 h1:tpSp2G2KyMnnQu99ngJ47EIkWVmliIizyZBfPrBWDRM= +github.com/mattn/go-sqlite3 v1.14.24/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= +github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4= +github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= @@ -21,9 +37,13 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/puzpuzpuz/xsync/v3 v3.4.0 h1:DuVBAdXuGFHv8adVXjWWZ63pJq+NRXOWVXlKDBZ+mJ4= github.com/puzpuzpuz/xsync/v3 v3.4.0/go.mod h1:VjzYrABPabuM4KyBh1Ftq6u8nhwY5tBPKP9jpmh0nnA= +github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= +github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= github.com/rs/zerolog v1.33.0 h1:1cU2KZkvPxNyfgEmhHAz/1A9Bz+llsdYzklWFzgp0r8= github.com/rs/zerolog v1.33.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss= +github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= @@ -33,18 +53,76 @@ github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKs github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc h1:9lRDQMhESg+zvGYmW5DyG0UqvY96Bu5QYsTLvCHdrgo= github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc/go.mod h1:bciPuU6GHm1iF1pBvUfxfsH0Wmnc2VbpgvbI9ZWuIRs= +github.com/uptrace/bun/dialect/mssqldialect v1.2.6-0.20241118024642-c6b302ff06f6 h1:kN03Tavir9GNmd/wONqXrXwjxMkOILcG7rKjuF6xYEk= +github.com/uptrace/bun/dialect/mssqldialect v1.2.6-0.20241118024642-c6b302ff06f6/go.mod h1:IDqR4Gnf+RpdpscDtoCGRscWvFQQN4yr7plBGRZz32U= +github.com/uptrace/bun/dialect/mysqldialect v1.2.6-0.20241118024642-c6b302ff06f6 h1:AxgysHLFx0WTZzuyhurcO8YJRM1sBzRgIJvl3nhZ9cA= +github.com/uptrace/bun/dialect/mysqldialect v1.2.6-0.20241118024642-c6b302ff06f6/go.mod h1:e/Ks/F4G9iluQsWQ3ko2mDvB3SYhC7Edzas0w0tlROE= +github.com/uptrace/bun/dialect/oracledialect v0.0.0-20241118024642-c6b302ff06f6 h1:E4fy2m5XL1DRtmO7I7VoQavPrEJD4ggh1mkvAs1BNso= +github.com/uptrace/bun/dialect/oracledialect v0.0.0-20241118024642-c6b302ff06f6/go.mod h1:M6HcWSHPQymq83DajKPbD2NIFy0UAwkA0vJVL+TUdC8= +github.com/uptrace/bun/dialect/pgdialect v1.2.6-0.20241118024642-c6b302ff06f6 h1:OhqKleS+o3kB6r445q1qaKH0J+KBL75hVopjh7c0g+s= +github.com/uptrace/bun/dialect/pgdialect v1.2.6-0.20241118024642-c6b302ff06f6/go.mod h1:T1cyHqcQJn/8wWkLPRkUYO6tpww/alyEeLmek+eMe80= +github.com/uptrace/bun/dialect/sqlitedialect v1.2.6-0.20241118024642-c6b302ff06f6 h1:d3J72lgMUMEZ4c5LsLALJmsjerxjrMELzs8PLTNPCJg= +github.com/uptrace/bun/dialect/sqlitedialect v1.2.6-0.20241118024642-c6b302ff06f6/go.mod h1:gHWpozqYO/YLozuRodoMCR4MpOvJfQZVyxN0xROxbts= +github.com/uptrace/bun/driver/pgdriver v1.2.6-0.20241118024642-c6b302ff06f6 h1:Jl4AAS3c6hsiG/jMEjI/JEFt2zR1R5jbQPWDZo8w5Is= +github.com/uptrace/bun/driver/pgdriver v1.2.6-0.20241118024642-c6b302ff06f6/go.mod h1:H5TTfArLN/X+UKEtrdBKv1mklypKjoZ7Uz/ktTOu4J8= +github.com/uptrace/bun/driver/sqliteshim v1.2.5 h1:pnGpzrsFy4MEJMAQwUPXzynncVpjFviE27Zz3RyBJUo= +github.com/uptrace/bun/driver/sqliteshim v1.2.5/go.mod h1:3C4tvcYu1As9zUa9Wlik338o1IB5GECwC+b7FJyjNco= +github.com/urfave/cli/v2 v2.27.5 h1:WoHEJLdsXr6dDWoJgMq/CboDmyY/8HMMH1fTECbih+w= +github.com/urfave/cli/v2 v2.27.5/go.mod h1:3Sevf16NykTbInEnD0yKkjDAeZDS0A6bzhBH5hrMvTQ= github.com/vmihailenco/msgpack/v5 v5.4.1 h1:cQriyiUvjTwOHg8QZaPihLWeRAAVoCpE00IUPn0Bjt8= github.com/vmihailenco/msgpack/v5 v5.4.1/go.mod h1:GaZTsDaehaPpQVyxrf5mtQlH+pc21PIudVV/E3rRQok= github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g= github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds= +github.com/wk8/go-ordered-map/v2 v2.1.9-0.20240816141633-0a40785b4f41 h1:rnB8ZLMeAr3VcqjfRkAm27qb8y6zFKNfuHvy1Gfe7KI= +github.com/wk8/go-ordered-map/v2 v2.1.9-0.20240816141633-0a40785b4f41/go.mod h1:DbzwytT4g/odXquuOCqroKvtxxldI4nb3nuesHF/Exo= +github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 h1:gEOO8jv9F4OT7lGCjxCBTO/36wtF6j2nSip77qHd4x4= +github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1/go.mod h1:Ohn+xnUBiLI6FVj/9LpzZWtj1/D6lUovWYBkxHVV3aM= +golang.org/x/crypto v0.29.0 h1:L5SG1JTTXupVV3n6sUqMTeWbjAyfPwoda2DLX8J8FrQ= +golang.org/x/crypto v0.29.0/go.mod h1:+F4F4N5hv6v38hfeYwTdx20oUvLLc+QfrE9Ax9HtgRg= +golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c h1:7dEasQXItcW1xKJ2+gg5VOiBnqWrJc+rq0DPKyvvdbY= +golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c/go.mod h1:NQtJDoLvd6faHhE7m4T/1IY708gDefGGjR/iUW8yQQ8= +golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4= +golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= +golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= +golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s= golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/tools v0.26.0 h1:v/60pFQmzmT9ExmjDv2gGIfi3OqfKoEP6I5+umXlbnQ= +golang.org/x/tools v0.26.0/go.mod h1:TPVVj70c7JJ3WCazhD8OdXcZg/og+b9+tH/KxylGwH0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +mellium.im/sasl v0.3.2 h1:PT6Xp7ccn9XaXAnJ03FcEjmAn7kK1x7aoXV6F+Vmrl0= +mellium.im/sasl v0.3.2/go.mod h1:NKXDi1zkr+BlMHLQjY3ofYuU4KSPFxknb8mfEu6SveY= +modernc.org/cc/v4 v4.21.4 h1:3Be/Rdo1fpr8GrQ7IVw9OHtplU4gWbb+wNgeoBMmGLQ= +modernc.org/cc/v4 v4.21.4/go.mod h1:HM7VJTZbUCR3rV8EYBi9wxnJ0ZBRiGE5OeGXNA0IsLQ= +modernc.org/ccgo/v4 v4.21.0 h1:kKPI3dF7RIag8YcToh5ZwDcVMIv6VGa0ED5cvh0LMW4= +modernc.org/ccgo/v4 v4.21.0/go.mod h1:h6kt6H/A2+ew/3MW/p6KEoQmrq/i3pr0J/SiwiaF/g0= +modernc.org/fileutil v1.3.0 h1:gQ5SIzK3H9kdfai/5x41oQiKValumqNTDXMvKo62HvE= +modernc.org/fileutil v1.3.0/go.mod h1:XatxS8fZi3pS8/hKG2GH/ArUogfxjpEKs3Ku3aK4JyQ= +modernc.org/gc/v2 v2.5.0 h1:bJ9ChznK1L1mUtAQtxi0wi5AtAs5jQuw4PrPHO5pb6M= +modernc.org/gc/v2 v2.5.0/go.mod h1:wzN5dK1AzVGoH6XOzc3YZ+ey/jPgYHLuVckd62P0GYU= +modernc.org/gc/v3 v3.0.0-20241004144649-1aea3fae8852 h1:IYXPPTTjjoSHvUClZIYexDiO7g+4x+XveKT4gCIAwiY= +modernc.org/gc/v3 v3.0.0-20241004144649-1aea3fae8852/go.mod h1:Qz0X07sNOR1jWYCrJMEnbW/X55x206Q7Vt4mz6/wHp4= +modernc.org/libc v1.61.0 h1:eGFcvWpqlnoGwzZeZe3PWJkkKbM/3SUGyk1DVZQ0TpE= +modernc.org/libc v1.61.0/go.mod h1:DvxVX89wtGTu+r72MLGhygpfi3aUGgZRdAYGCAVVud0= +modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4= +modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo= +modernc.org/memory v1.8.0 h1:IqGTL6eFMaDZZhEWwcREgeMXYwmW83LYW8cROZYkg+E= +modernc.org/memory v1.8.0/go.mod h1:XPZ936zp5OMKGWPqbD3JShgd/ZoQ7899TUuQqxY+peU= +modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4= +modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0= +modernc.org/sortutil v1.2.0 h1:jQiD3PfS2REGJNzNCMMaLSp/wdMNieTbKX920Cqdgqc= +modernc.org/sortutil v1.2.0/go.mod h1:TKU2s7kJMf1AE84OoiGppNHJwvB753OYfNl2WRb++Ss= +modernc.org/sqlite v1.33.1 h1:trb6Z3YYoeM9eDL1O8do81kP+0ejv+YzgyFo+Gwy0nM= +modernc.org/sqlite v1.33.1/go.mod h1:pXV2xHxhzXZsgT/RtTFAPY6JJDEvOTcTdwADQCCWD4k= +modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA= +modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0= +modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= +modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= From 224f8395bee14ceb09173f7092c5aa14cdcf7845 Mon Sep 17 00:00:00 2001 From: dyma solovei Date: Tue, 19 Nov 2024 15:35:46 +0100 Subject: [PATCH 02/17] feat: build user plugin before running command This will remove the need for users to do any extra steps before using bundb. Provide -rebuild flag to force rebuild and -cleanup to remove the dangling plugin file. --- cmd/bundb/main.go | 76 ++++++++++++++++++++++++++++++++++------------- 1 file changed, 56 insertions(+), 20 deletions(-) diff --git a/cmd/bundb/main.go b/cmd/bundb/main.go index 9d2918183..f4a9594b5 100644 --- a/cmd/bundb/main.go +++ b/cmd/bundb/main.go @@ -5,6 +5,8 @@ import ( "fmt" "log" "os" + "os/exec" + "path" "plugin" "strings" @@ -21,22 +23,25 @@ import ( "github.com/urfave/cli/v2" ) -const defaultMigrationsDirectory = "./migrations/" +const ( + defaultMigrationsDirectory = "./migrations" + pluginName = "plugin.so" +) var ( - supportedDrivers = []string{"postgres", "sqlserver", "mysql", "oci8", "file"} - - // TODO: build automatically based on the migrationsPath - pluginPath string - - // Migrator options - migratorOptions []migrate.MigratorOption - migrationOptions []migrate.MigrationOption + supportedDrivers = []string{"postgres", "sqlserver", "mysql", "oci8", "file"} + migrationsDirectory string // AutoMigrator options autoMigratorOptions []migrate.AutoMigratorOption ) +var ( + cleanup = &cli.BoolFlag{ + Name: "cleanup", + } +) + var app = &cli.App{ Name: "bundb", Commands: cli.Commands{ @@ -63,13 +68,10 @@ var app = &cli.App{ Name: "driver", }, &cli.StringFlag{ - Name: "plugin", - Destination: &pluginPath, - Required: true, - }, - &cli.StringFlag{ - Name: "d", - Aliases: []string{"migrations-directory"}, + Name: "d", + Aliases: []string{"migrations-directory"}, + Destination: &migrationsDirectory, + Value: defaultMigrationsDirectory, Action: func(ctx *cli.Context, dir string) error { autoMigratorOptions = append(autoMigratorOptions, migrate.WithMigrationsDirectoryAuto(dir)) return nil @@ -106,8 +108,20 @@ var app = &cli.App{ return nil }, }, + &cli.BoolFlag{ + Name: "rebuild", + }, + cleanup, }, Action: func(ctx *cli.Context) error { + if err := buildPlugin(ctx.Bool("rebuild")); err != nil { + return err + } + + if cleanup.Get(ctx) { + defer deletePlugin() + } + db, err := connect(ctx.String("uri"), ctx.String("driver"), !ctx.IsSet("driver")) if err != nil { return err @@ -131,7 +145,6 @@ var app = &cli.App{ log.Print("ok, nothing to migrate") } return nil - }, }, }, @@ -139,6 +152,29 @@ var app = &cli.App{ }, } +func pluginPath() string { + return path.Join(migrationsDirectory, pluginName) +} + +func buildPlugin(force bool) error { + if force { + if err := deletePlugin(); err != nil { + return err + } + } + + cmd := exec.Command("go", "build", "-C", migrationsDirectory, "-buildmode", "plugin", "-o", pluginName) + err := cmd.Run() + if err != nil { + return fmt.Errorf("build %s plugin: %w", pluginPath(), err) + } + return nil +} + +func deletePlugin() error { + return os.RemoveAll(pluginPath()) +} + // connect to the database under the URI. A driver must be one of the supported drivers. // If not set explicitly, the name of the driver is guessed from the URI. // @@ -153,7 +189,7 @@ func connect(uri, driverName string, guessDriver bool) (*bun.DB, error) { if guessDriver { driver, _, found := strings.Cut(uri, ":") if !found { - return nil, fmt.Errorf("driver cannot be guessed from connection string; pass --driver option explicitly") + return nil, fmt.Errorf("driver cannot be guessed from connection string; pass -driver option explicitly") } driverName = driver } @@ -175,7 +211,7 @@ func connect(uri, driverName string, guessDriver bool) (*bun.DB, error) { sqldb, err = sql.Open(driverName, uri) dialect = oracledialect.New() default: - err = fmt.Errorf("driver %q not recognized, supported drivers are %v", driverName, supportedDrivers) + err = fmt.Errorf("driver %q not recognized, supported drivers are %+v", driverName, supportedDrivers) } if err != nil { @@ -207,7 +243,7 @@ func automigrator(db *bun.DB) (*migrate.AutoMigrator, error) { // lookup a symbol from user's migrations plugin. func lookup(symbol string) (plugin.Symbol, error) { - p, err := plugin.Open(pluginPath) + p, err := plugin.Open(pluginPath()) if err != nil { return nil, err } From 6156a64859601f5b41265ee2d6f173153535de61 Mon Sep 17 00:00:00 2001 From: dyma solovei Date: Tue, 19 Nov 2024 22:13:57 +0100 Subject: [PATCH 03/17] feat: create SQL migration files with 'bundb auto create' Improved error handling during 'go build' step to output a more user-friendly message. --- cmd/bundb/main.go | 146 +++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 139 insertions(+), 7 deletions(-) diff --git a/cmd/bundb/main.go b/cmd/bundb/main.go index f4a9594b5..f5cfa1573 100644 --- a/cmd/bundb/main.go +++ b/cmd/bundb/main.go @@ -1,6 +1,31 @@ +/* +TODO: + +- Add a mechanism to detect potentially duplicate migration files. That is, +once we've collected migrations in a bytes.Buffer, check if 'migrations/' package +has another migration files that: + 1. is identical in content + 2. belongs to the migration that has not been applied yet + +If we find such migration, prompt the user for confirmation, unless -force flag is set. +Ideally, we should be able to ignore "transactional" for this purpose, +i.e. same_thing.up.tx.sql should overwrite same_thing.up.sql. + +- Store configured options to env variables? E.g. after 'bundb init --create-directory=db-migrations/' +set BUNDB_MIGRATIONS=db-migrations, so that subsequent commands can be run without additional parameters. +Although... this way we are moving towards a .bundb.config or something. + +- Allow defining components in the plugin, rather than passing config for them. Specifically: + 1. func DB() *bun.DB to return a database connection + Handy in avoiding having to provide options for all the dialect-specific options here + potentially + let's users re-use their existing "ConnectToDB" function. + 2. func AutoMigrator() *migrate.AutoMigrator to return a pre-configured AutoMigrator. + 3. ??? +*/ package main import ( + "bytes" "database/sql" "fmt" "log" @@ -30,10 +55,8 @@ const ( var ( supportedDrivers = []string{"postgres", "sqlserver", "mysql", "oci8", "file"} - migrationsDirectory string - - // AutoMigrator options autoMigratorOptions []migrate.AutoMigratorOption + migrationsDirectory string ) var ( @@ -43,17 +66,116 @@ var ( ) var app = &cli.App{ - Name: "bundb", + Name: "bundb", + Usage: "Database migration tool for uptrace/bun", Commands: cli.Commands{ // bundb init --create-directory // bundb create --sql --go --tx [-d | --dir] // bundb migrate - // bundb auto create --sql --tx + // bundb auto create --tx // bundb auto migrate &cli.Command{ Name: "auto", Usage: "manage database schema with AutoMigrator", Subcommands: cli.Commands{ + &cli.Command{ + Name: "create", + Usage: "Generate SQL migration files", + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: "uri", + Aliases: []string{"database-uri", "dsn"}, + Required: true, + EnvVars: []string{"BUNDB_URI"}, + }, + &cli.StringFlag{ + Name: "driver", + }, + &cli.StringFlag{ + Name: "d", + Aliases: []string{"migrations-directory"}, + Destination: &migrationsDirectory, + Value: defaultMigrationsDirectory, + Action: func(ctx *cli.Context, dir string) error { + autoMigratorOptions = append(autoMigratorOptions, migrate.WithMigrationsDirectoryAuto(dir)) + return nil + }, + }, + &cli.StringFlag{ + Name: "t", + Aliases: []string{"migrations-table"}, + Action: func(ctx *cli.Context, migrationsTable string) error { + autoMigratorOptions = append(autoMigratorOptions, migrate.WithTableNameAuto(migrationsTable)) + return nil + }, + }, + &cli.StringFlag{ + Name: "l", + Aliases: []string{"locks", "migration-locks-table"}, + Action: func(ctx *cli.Context, locksTable string) error { + autoMigratorOptions = append(autoMigratorOptions, migrate.WithLocksTableNameAuto(locksTable)) + return nil + }, + }, + &cli.StringFlag{ + Name: "s", + Aliases: []string{"schema"}, + Action: func(ctx *cli.Context, schemaName string) error { + autoMigratorOptions = append(autoMigratorOptions, migrate.WithSchemaName(schemaName)) + return nil + }, + }, + &cli.StringSliceFlag{ + Name: "exclude", + Action: func(ctx *cli.Context, tables []string) error { + autoMigratorOptions = append(autoMigratorOptions, migrate.WithExcludeTable(tables...)) + return nil + }, + }, + &cli.BoolFlag{ + Name: "rebuild", + }, + cleanup, + &cli.BoolFlag{ + Name: "tx", + Aliases: []string{"transactional"}, + }, + }, + Action: func(ctx *cli.Context) error { + if err := buildPlugin(ctx.Bool("rebuild")); err != nil { + return err + } + + if cleanup.Get(ctx) { + defer deletePlugin() + } + + db, err := connect(ctx.String("uri"), ctx.String("driver"), !ctx.IsSet("driver")) + if err != nil { + return err + + } + + if !ctx.IsSet("migrations-directory") { + autoMigratorOptions = append(autoMigratorOptions, migrate.WithMigrationsDirectoryAuto(defaultMigrationsDirectory)) + + } + m, err := automigrator(db) + if err != nil { + return err + } + + if ctx.Bool("tx") { + _, err = m.CreateTxSQLMigrations(ctx.Context) + } else { + _, err = m.CreateSQLMigrations(ctx.Context) + } + if err != nil { + return err + } + return nil + }, + }, &cli.Command{ Name: "migrate", Usage: "Generate SQL migrations and apply them right away", @@ -156,6 +278,8 @@ func pluginPath() string { return path.Join(migrationsDirectory, pluginName) } +// TODO: wrap Build and Open steps into a sync.OnceFunc, so that we could use the Plugin object in multiple places +// without having to worry if it has been compiled or not. func buildPlugin(force bool) error { if force { if err := deletePlugin(); err != nil { @@ -163,10 +287,18 @@ func buildPlugin(force bool) error { } } + // Cmd.Run returns *exec.ExitError which will only contain the exit code message in case of an error. + // Rather than logging "exit code 1" we want to output a more informative error, so we redirect the Stderr. + var errBuf bytes.Buffer + cmd := exec.Command("go", "build", "-C", migrationsDirectory, "-buildmode", "plugin", "-o", pluginName) + cmd.Stderr = &errBuf + err := cmd.Run() if err != nil { - return fmt.Errorf("build %s plugin: %w", pluginPath(), err) + // TODO: if errBuf contains "no such file or directory" add the following to the error message: + // "Create 'migrations/' directory by running: bundb init --create-directory migrations/" + return fmt.Errorf("build %s: %s", pluginPath(), &errBuf) } return nil } @@ -180,7 +312,7 @@ func deletePlugin() error { // // Example: // -// "postgres://postgres:@localhost:5432/postgres" -> "postegres" +// "postgres://root:@localhost:5432/test" -> "postgres" func connect(uri, driverName string, guessDriver bool) (*bun.DB, error) { var sqldb *sql.DB var dialect schema.Dialect From dce2d00242f4de2f4226483ab3ca9de316737f80 Mon Sep 17 00:00:00 2001 From: dyma solovei Date: Tue, 26 Nov 2024 09:27:16 +0100 Subject: [PATCH 04/17] chore: minimize changes to go.mod/go.work files --- go.mod | 33 ++--------------------- go.sum | 84 +++------------------------------------------------------- 2 files changed, 5 insertions(+), 112 deletions(-) diff --git a/go.mod b/go.mod index 973680da8..48b197ab8 100644 --- a/go.mod +++ b/go.mod @@ -8,46 +8,17 @@ require ( github.com/rs/zerolog v1.33.0 github.com/stretchr/testify v1.8.1 github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc - github.com/uptrace/bun/dialect/mssqldialect v1.2.6-0.20241118024642-c6b302ff06f6 - github.com/uptrace/bun/dialect/mysqldialect v1.2.6-0.20241118024642-c6b302ff06f6 - github.com/uptrace/bun/dialect/oracledialect v0.0.0-20241118024642-c6b302ff06f6 - github.com/uptrace/bun/dialect/pgdialect v1.2.6-0.20241118024642-c6b302ff06f6 - github.com/uptrace/bun/dialect/sqlitedialect v1.2.6-0.20241118024642-c6b302ff06f6 - github.com/uptrace/bun/driver/pgdriver v1.2.6-0.20241118024642-c6b302ff06f6 - github.com/uptrace/bun/driver/sqliteshim v1.2.5 - github.com/urfave/cli/v2 v2.27.5 github.com/vmihailenco/msgpack/v5 v5.4.1 ) require ( - github.com/bahlo/generic-list-go v0.2.0 // indirect - github.com/buger/jsonparser v1.1.1 // indirect - github.com/cpuguy83/go-md2man/v2 v2.0.5 // indirect github.com/davecgh/go-spew v1.1.1 // indirect - github.com/dustin/go-humanize v1.0.1 // indirect - github.com/google/uuid v1.6.0 // indirect - github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect - github.com/mailru/easyjson v0.7.7 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect - github.com/mattn/go-sqlite3 v1.14.24 // indirect - github.com/ncruces/go-strftime v0.1.9 // indirect + github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect - github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect - github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // indirect - golang.org/x/crypto v0.29.0 // indirect - golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c // indirect - golang.org/x/mod v0.22.0 // indirect golang.org/x/sys v0.27.0 // indirect + gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - mellium.im/sasl v0.3.2 // indirect - modernc.org/gc/v3 v3.0.0-20241004144649-1aea3fae8852 // indirect - modernc.org/libc v1.61.0 // indirect - modernc.org/mathutil v1.6.0 // indirect - modernc.org/memory v1.8.0 // indirect - modernc.org/sqlite v1.33.1 // indirect - modernc.org/strutil v1.2.0 // indirect - modernc.org/token v1.1.0 // indirect ) diff --git a/go.sum b/go.sum index 9de74af76..0e97efd20 100644 --- a/go.sum +++ b/go.sum @@ -1,35 +1,19 @@ github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= -github.com/cpuguy83/go-md2man/v2 v2.0.5 h1:ZtcqGrnekaHpVLArFSe4HK5DoKx1T0rq2DwVB0alcyc= -github.com/cpuguy83/go-md2man/v2 v2.0.5/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= -github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= -github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd h1:gbpYu9NMq8jhDVbvlGkMFWCjLFlqqEZjEmObmhUy6Vo= -github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd/go.mod h1:kf6iHlnVGwgKolg33glAes7Yg/8iWP8ukqeldJSO7jw= -github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= -github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= -github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= -github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= -github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= -github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= -github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/mattn/go-sqlite3 v1.14.24 h1:tpSp2G2KyMnnQu99ngJ47EIkWVmliIizyZBfPrBWDRM= -github.com/mattn/go-sqlite3 v1.14.24/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= -github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4= -github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= @@ -37,13 +21,9 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/puzpuzpuz/xsync/v3 v3.4.0 h1:DuVBAdXuGFHv8adVXjWWZ63pJq+NRXOWVXlKDBZ+mJ4= github.com/puzpuzpuz/xsync/v3 v3.4.0/go.mod h1:VjzYrABPabuM4KyBh1Ftq6u8nhwY5tBPKP9jpmh0nnA= -github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= -github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= github.com/rs/zerolog v1.33.0 h1:1cU2KZkvPxNyfgEmhHAz/1A9Bz+llsdYzklWFzgp0r8= github.com/rs/zerolog v1.33.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss= -github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= -github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= @@ -53,76 +33,18 @@ github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKs github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc h1:9lRDQMhESg+zvGYmW5DyG0UqvY96Bu5QYsTLvCHdrgo= github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc/go.mod h1:bciPuU6GHm1iF1pBvUfxfsH0Wmnc2VbpgvbI9ZWuIRs= -github.com/uptrace/bun/dialect/mssqldialect v1.2.6-0.20241118024642-c6b302ff06f6 h1:kN03Tavir9GNmd/wONqXrXwjxMkOILcG7rKjuF6xYEk= -github.com/uptrace/bun/dialect/mssqldialect v1.2.6-0.20241118024642-c6b302ff06f6/go.mod h1:IDqR4Gnf+RpdpscDtoCGRscWvFQQN4yr7plBGRZz32U= -github.com/uptrace/bun/dialect/mysqldialect v1.2.6-0.20241118024642-c6b302ff06f6 h1:AxgysHLFx0WTZzuyhurcO8YJRM1sBzRgIJvl3nhZ9cA= -github.com/uptrace/bun/dialect/mysqldialect v1.2.6-0.20241118024642-c6b302ff06f6/go.mod h1:e/Ks/F4G9iluQsWQ3ko2mDvB3SYhC7Edzas0w0tlROE= -github.com/uptrace/bun/dialect/oracledialect v0.0.0-20241118024642-c6b302ff06f6 h1:E4fy2m5XL1DRtmO7I7VoQavPrEJD4ggh1mkvAs1BNso= -github.com/uptrace/bun/dialect/oracledialect v0.0.0-20241118024642-c6b302ff06f6/go.mod h1:M6HcWSHPQymq83DajKPbD2NIFy0UAwkA0vJVL+TUdC8= -github.com/uptrace/bun/dialect/pgdialect v1.2.6-0.20241118024642-c6b302ff06f6 h1:OhqKleS+o3kB6r445q1qaKH0J+KBL75hVopjh7c0g+s= -github.com/uptrace/bun/dialect/pgdialect v1.2.6-0.20241118024642-c6b302ff06f6/go.mod h1:T1cyHqcQJn/8wWkLPRkUYO6tpww/alyEeLmek+eMe80= -github.com/uptrace/bun/dialect/sqlitedialect v1.2.6-0.20241118024642-c6b302ff06f6 h1:d3J72lgMUMEZ4c5LsLALJmsjerxjrMELzs8PLTNPCJg= -github.com/uptrace/bun/dialect/sqlitedialect v1.2.6-0.20241118024642-c6b302ff06f6/go.mod h1:gHWpozqYO/YLozuRodoMCR4MpOvJfQZVyxN0xROxbts= -github.com/uptrace/bun/driver/pgdriver v1.2.6-0.20241118024642-c6b302ff06f6 h1:Jl4AAS3c6hsiG/jMEjI/JEFt2zR1R5jbQPWDZo8w5Is= -github.com/uptrace/bun/driver/pgdriver v1.2.6-0.20241118024642-c6b302ff06f6/go.mod h1:H5TTfArLN/X+UKEtrdBKv1mklypKjoZ7Uz/ktTOu4J8= -github.com/uptrace/bun/driver/sqliteshim v1.2.5 h1:pnGpzrsFy4MEJMAQwUPXzynncVpjFviE27Zz3RyBJUo= -github.com/uptrace/bun/driver/sqliteshim v1.2.5/go.mod h1:3C4tvcYu1As9zUa9Wlik338o1IB5GECwC+b7FJyjNco= -github.com/urfave/cli/v2 v2.27.5 h1:WoHEJLdsXr6dDWoJgMq/CboDmyY/8HMMH1fTECbih+w= -github.com/urfave/cli/v2 v2.27.5/go.mod h1:3Sevf16NykTbInEnD0yKkjDAeZDS0A6bzhBH5hrMvTQ= github.com/vmihailenco/msgpack/v5 v5.4.1 h1:cQriyiUvjTwOHg8QZaPihLWeRAAVoCpE00IUPn0Bjt8= github.com/vmihailenco/msgpack/v5 v5.4.1/go.mod h1:GaZTsDaehaPpQVyxrf5mtQlH+pc21PIudVV/E3rRQok= github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g= github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds= -github.com/wk8/go-ordered-map/v2 v2.1.9-0.20240816141633-0a40785b4f41 h1:rnB8ZLMeAr3VcqjfRkAm27qb8y6zFKNfuHvy1Gfe7KI= -github.com/wk8/go-ordered-map/v2 v2.1.9-0.20240816141633-0a40785b4f41/go.mod h1:DbzwytT4g/odXquuOCqroKvtxxldI4nb3nuesHF/Exo= -github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 h1:gEOO8jv9F4OT7lGCjxCBTO/36wtF6j2nSip77qHd4x4= -github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1/go.mod h1:Ohn+xnUBiLI6FVj/9LpzZWtj1/D6lUovWYBkxHVV3aM= -golang.org/x/crypto v0.29.0 h1:L5SG1JTTXupVV3n6sUqMTeWbjAyfPwoda2DLX8J8FrQ= -golang.org/x/crypto v0.29.0/go.mod h1:+F4F4N5hv6v38hfeYwTdx20oUvLLc+QfrE9Ax9HtgRg= -golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c h1:7dEasQXItcW1xKJ2+gg5VOiBnqWrJc+rq0DPKyvvdbY= -golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c/go.mod h1:NQtJDoLvd6faHhE7m4T/1IY708gDefGGjR/iUW8yQQ8= -golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4= -golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= -golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= -golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s= golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/tools v0.26.0 h1:v/60pFQmzmT9ExmjDv2gGIfi3OqfKoEP6I5+umXlbnQ= -golang.org/x/tools v0.26.0/go.mod h1:TPVVj70c7JJ3WCazhD8OdXcZg/og+b9+tH/KxylGwH0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -mellium.im/sasl v0.3.2 h1:PT6Xp7ccn9XaXAnJ03FcEjmAn7kK1x7aoXV6F+Vmrl0= -mellium.im/sasl v0.3.2/go.mod h1:NKXDi1zkr+BlMHLQjY3ofYuU4KSPFxknb8mfEu6SveY= -modernc.org/cc/v4 v4.21.4 h1:3Be/Rdo1fpr8GrQ7IVw9OHtplU4gWbb+wNgeoBMmGLQ= -modernc.org/cc/v4 v4.21.4/go.mod h1:HM7VJTZbUCR3rV8EYBi9wxnJ0ZBRiGE5OeGXNA0IsLQ= -modernc.org/ccgo/v4 v4.21.0 h1:kKPI3dF7RIag8YcToh5ZwDcVMIv6VGa0ED5cvh0LMW4= -modernc.org/ccgo/v4 v4.21.0/go.mod h1:h6kt6H/A2+ew/3MW/p6KEoQmrq/i3pr0J/SiwiaF/g0= -modernc.org/fileutil v1.3.0 h1:gQ5SIzK3H9kdfai/5x41oQiKValumqNTDXMvKo62HvE= -modernc.org/fileutil v1.3.0/go.mod h1:XatxS8fZi3pS8/hKG2GH/ArUogfxjpEKs3Ku3aK4JyQ= -modernc.org/gc/v2 v2.5.0 h1:bJ9ChznK1L1mUtAQtxi0wi5AtAs5jQuw4PrPHO5pb6M= -modernc.org/gc/v2 v2.5.0/go.mod h1:wzN5dK1AzVGoH6XOzc3YZ+ey/jPgYHLuVckd62P0GYU= -modernc.org/gc/v3 v3.0.0-20241004144649-1aea3fae8852 h1:IYXPPTTjjoSHvUClZIYexDiO7g+4x+XveKT4gCIAwiY= -modernc.org/gc/v3 v3.0.0-20241004144649-1aea3fae8852/go.mod h1:Qz0X07sNOR1jWYCrJMEnbW/X55x206Q7Vt4mz6/wHp4= -modernc.org/libc v1.61.0 h1:eGFcvWpqlnoGwzZeZe3PWJkkKbM/3SUGyk1DVZQ0TpE= -modernc.org/libc v1.61.0/go.mod h1:DvxVX89wtGTu+r72MLGhygpfi3aUGgZRdAYGCAVVud0= -modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4= -modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo= -modernc.org/memory v1.8.0 h1:IqGTL6eFMaDZZhEWwcREgeMXYwmW83LYW8cROZYkg+E= -modernc.org/memory v1.8.0/go.mod h1:XPZ936zp5OMKGWPqbD3JShgd/ZoQ7899TUuQqxY+peU= -modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4= -modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0= -modernc.org/sortutil v1.2.0 h1:jQiD3PfS2REGJNzNCMMaLSp/wdMNieTbKX920Cqdgqc= -modernc.org/sortutil v1.2.0/go.mod h1:TKU2s7kJMf1AE84OoiGppNHJwvB753OYfNl2WRb++Ss= -modernc.org/sqlite v1.33.1 h1:trb6Z3YYoeM9eDL1O8do81kP+0ejv+YzgyFo+Gwy0nM= -modernc.org/sqlite v1.33.1/go.mod h1:pXV2xHxhzXZsgT/RtTFAPY6JJDEvOTcTdwADQCCWD4k= -modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA= -modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0= -modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= -modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= From 2d463f67e334ada2240900d3afa767abefec925c Mon Sep 17 00:00:00 2001 From: dyma solovei Date: Tue, 26 Nov 2024 14:47:23 +0100 Subject: [PATCH 05/17] refactor: provide CLI app in a standalone package Users can start the tool from their own entrypoints by using buncli.New/Run etc. They are also responsible for configuring the DB connection and AutoMigrator. bundb/bunctl will eventually use FromPlugin() to read config from a pre-built plugin. --- cmd/bundb/main.go | 357 +---------------------------------------- extra/buncli/auto.go | 56 +++++++ extra/buncli/buncli.go | 57 +++++++ extra/buncli/go.mod | 22 +++ extra/buncli/go.sum | 28 ++++ go.mod | 7 + go.sum | 8 + 7 files changed, 181 insertions(+), 354 deletions(-) create mode 100644 extra/buncli/auto.go create mode 100644 extra/buncli/buncli.go create mode 100644 extra/buncli/go.mod create mode 100644 extra/buncli/go.sum diff --git a/cmd/bundb/main.go b/cmd/bundb/main.go index f5cfa1573..1e5365b04 100644 --- a/cmd/bundb/main.go +++ b/cmd/bundb/main.go @@ -25,366 +25,15 @@ Although... this way we are moving towards a .bundb.config or something. package main import ( - "bytes" - "database/sql" - "fmt" "log" "os" - "os/exec" - "path" - "plugin" - "strings" - "github.com/uptrace/bun" - "github.com/uptrace/bun/dialect/mssqldialect" - "github.com/uptrace/bun/dialect/mysqldialect" - "github.com/uptrace/bun/dialect/oracledialect" - "github.com/uptrace/bun/dialect/pgdialect" - "github.com/uptrace/bun/dialect/sqlitedialect" - "github.com/uptrace/bun/driver/pgdriver" - "github.com/uptrace/bun/driver/sqliteshim" - "github.com/uptrace/bun/migrate" - "github.com/uptrace/bun/schema" - "github.com/urfave/cli/v2" + "github.com/uptrace/bun/extra/buncli" ) -const ( - defaultMigrationsDirectory = "./migrations" - pluginName = "plugin.so" -) - -var ( - supportedDrivers = []string{"postgres", "sqlserver", "mysql", "oci8", "file"} - autoMigratorOptions []migrate.AutoMigratorOption - migrationsDirectory string -) - -var ( - cleanup = &cli.BoolFlag{ - Name: "cleanup", - } -) - -var app = &cli.App{ - Name: "bundb", - Usage: "Database migration tool for uptrace/bun", - Commands: cli.Commands{ - // bundb init --create-directory - // bundb create --sql --go --tx [-d | --dir] - // bundb migrate - // bundb auto create --tx - // bundb auto migrate - &cli.Command{ - Name: "auto", - Usage: "manage database schema with AutoMigrator", - Subcommands: cli.Commands{ - &cli.Command{ - Name: "create", - Usage: "Generate SQL migration files", - Flags: []cli.Flag{ - &cli.StringFlag{ - Name: "uri", - Aliases: []string{"database-uri", "dsn"}, - Required: true, - EnvVars: []string{"BUNDB_URI"}, - }, - &cli.StringFlag{ - Name: "driver", - }, - &cli.StringFlag{ - Name: "d", - Aliases: []string{"migrations-directory"}, - Destination: &migrationsDirectory, - Value: defaultMigrationsDirectory, - Action: func(ctx *cli.Context, dir string) error { - autoMigratorOptions = append(autoMigratorOptions, migrate.WithMigrationsDirectoryAuto(dir)) - return nil - }, - }, - &cli.StringFlag{ - Name: "t", - Aliases: []string{"migrations-table"}, - Action: func(ctx *cli.Context, migrationsTable string) error { - autoMigratorOptions = append(autoMigratorOptions, migrate.WithTableNameAuto(migrationsTable)) - return nil - }, - }, - &cli.StringFlag{ - Name: "l", - Aliases: []string{"locks", "migration-locks-table"}, - Action: func(ctx *cli.Context, locksTable string) error { - autoMigratorOptions = append(autoMigratorOptions, migrate.WithLocksTableNameAuto(locksTable)) - return nil - }, - }, - &cli.StringFlag{ - Name: "s", - Aliases: []string{"schema"}, - Action: func(ctx *cli.Context, schemaName string) error { - autoMigratorOptions = append(autoMigratorOptions, migrate.WithSchemaName(schemaName)) - return nil - }, - }, - &cli.StringSliceFlag{ - Name: "exclude", - Action: func(ctx *cli.Context, tables []string) error { - autoMigratorOptions = append(autoMigratorOptions, migrate.WithExcludeTable(tables...)) - return nil - }, - }, - &cli.BoolFlag{ - Name: "rebuild", - }, - cleanup, - &cli.BoolFlag{ - Name: "tx", - Aliases: []string{"transactional"}, - }, - }, - Action: func(ctx *cli.Context) error { - if err := buildPlugin(ctx.Bool("rebuild")); err != nil { - return err - } - - if cleanup.Get(ctx) { - defer deletePlugin() - } - - db, err := connect(ctx.String("uri"), ctx.String("driver"), !ctx.IsSet("driver")) - if err != nil { - return err - - } - - if !ctx.IsSet("migrations-directory") { - autoMigratorOptions = append(autoMigratorOptions, migrate.WithMigrationsDirectoryAuto(defaultMigrationsDirectory)) - - } - m, err := automigrator(db) - if err != nil { - return err - } - - if ctx.Bool("tx") { - _, err = m.CreateTxSQLMigrations(ctx.Context) - } else { - _, err = m.CreateSQLMigrations(ctx.Context) - } - if err != nil { - return err - } - return nil - }, - }, - &cli.Command{ - Name: "migrate", - Usage: "Generate SQL migrations and apply them right away", - Flags: []cli.Flag{ - &cli.StringFlag{ - Name: "uri", - Aliases: []string{"database-uri", "dsn"}, - Required: true, - EnvVars: []string{"BUNDB_URI"}, - }, - &cli.StringFlag{ - Name: "driver", - }, - &cli.StringFlag{ - Name: "d", - Aliases: []string{"migrations-directory"}, - Destination: &migrationsDirectory, - Value: defaultMigrationsDirectory, - Action: func(ctx *cli.Context, dir string) error { - autoMigratorOptions = append(autoMigratorOptions, migrate.WithMigrationsDirectoryAuto(dir)) - return nil - }, - }, - &cli.StringFlag{ - Name: "t", - Aliases: []string{"migrations-table"}, - Action: func(ctx *cli.Context, migrationsTable string) error { - autoMigratorOptions = append(autoMigratorOptions, migrate.WithTableNameAuto(migrationsTable)) - return nil - }, - }, - &cli.StringFlag{ - Name: "l", - Aliases: []string{"locks", "migration-locks-table"}, - Action: func(ctx *cli.Context, locksTable string) error { - autoMigratorOptions = append(autoMigratorOptions, migrate.WithLocksTableNameAuto(locksTable)) - return nil - }, - }, - &cli.StringFlag{ - Name: "s", - Aliases: []string{"schema"}, - Action: func(ctx *cli.Context, schemaName string) error { - autoMigratorOptions = append(autoMigratorOptions, migrate.WithSchemaName(schemaName)) - return nil - }, - }, - &cli.StringSliceFlag{ - Name: "exclude", - Action: func(ctx *cli.Context, tables []string) error { - autoMigratorOptions = append(autoMigratorOptions, migrate.WithExcludeTable(tables...)) - return nil - }, - }, - &cli.BoolFlag{ - Name: "rebuild", - }, - cleanup, - }, - Action: func(ctx *cli.Context) error { - if err := buildPlugin(ctx.Bool("rebuild")); err != nil { - return err - } - - if cleanup.Get(ctx) { - defer deletePlugin() - } - - db, err := connect(ctx.String("uri"), ctx.String("driver"), !ctx.IsSet("driver")) - if err != nil { - return err - - } - - if !ctx.IsSet("migrations-directory") { - autoMigratorOptions = append(autoMigratorOptions, migrate.WithMigrationsDirectoryAuto(defaultMigrationsDirectory)) - - } - m, err := automigrator(db) - if err != nil { - return err - } - - group, err := m.Migrate(ctx.Context) - if err != nil { - return err - } - if group.IsZero() { - log.Print("ok, nothing to migrate") - } - return nil - }, - }, - }, - }, - }, -} - -func pluginPath() string { - return path.Join(migrationsDirectory, pluginName) -} - -// TODO: wrap Build and Open steps into a sync.OnceFunc, so that we could use the Plugin object in multiple places -// without having to worry if it has been compiled or not. -func buildPlugin(force bool) error { - if force { - if err := deletePlugin(); err != nil { - return err - } - } - - // Cmd.Run returns *exec.ExitError which will only contain the exit code message in case of an error. - // Rather than logging "exit code 1" we want to output a more informative error, so we redirect the Stderr. - var errBuf bytes.Buffer - - cmd := exec.Command("go", "build", "-C", migrationsDirectory, "-buildmode", "plugin", "-o", pluginName) - cmd.Stderr = &errBuf - - err := cmd.Run() - if err != nil { - // TODO: if errBuf contains "no such file or directory" add the following to the error message: - // "Create 'migrations/' directory by running: bundb init --create-directory migrations/" - return fmt.Errorf("build %s: %s", pluginPath(), &errBuf) - } - return nil -} - -func deletePlugin() error { - return os.RemoveAll(pluginPath()) -} - -// connect to the database under the URI. A driver must be one of the supported drivers. -// If not set explicitly, the name of the driver is guessed from the URI. -// -// Example: -// -// "postgres://root:@localhost:5432/test" -> "postgres" -func connect(uri, driverName string, guessDriver bool) (*bun.DB, error) { - var sqldb *sql.DB - var dialect schema.Dialect - var err error - - if guessDriver { - driver, _, found := strings.Cut(uri, ":") - if !found { - return nil, fmt.Errorf("driver cannot be guessed from connection string; pass -driver option explicitly") - } - driverName = driver - } - - switch driverName { - case "postgres": - sqldb = sql.OpenDB(pgdriver.NewConnector(pgdriver.WithDSN(uri))) - dialect = pgdialect.New() - case "sqlserver": - sqldb, err = sql.Open(driverName, uri) - dialect = mssqldialect.New() - case "file": - sqldb, err = sql.Open(sqliteshim.ShimName, uri) - dialect = sqlitedialect.New() - case "mysql": - sqldb, err = sql.Open(driverName, uri) - dialect = mysqldialect.New() - case "oci8": - sqldb, err = sql.Open(driverName, uri) - dialect = oracledialect.New() - default: - err = fmt.Errorf("driver %q not recognized, supported drivers are %+v", driverName, supportedDrivers) - } - - if err != nil { - return nil, err - } - - return bun.NewDB(sqldb, dialect), nil -} - -// automigrator creates AutoMigrator for models from user's 'migrations' package. -func automigrator(db *bun.DB) (*migrate.AutoMigrator, error) { - sym, err := lookup("Models") - if err != nil { - return nil, err - } - - models, ok := sym.(*[]interface{}) - if !ok { - return nil, fmt.Errorf("migrations plugin must export Models as []interface{}, got %T", models) - } - autoMigratorOptions = append(autoMigratorOptions, migrate.WithModel(*models...)) - - auto, err := migrate.NewAutoMigrator(db, autoMigratorOptions...) - if err != nil { - return nil, err - } - return auto, nil -} - -// lookup a symbol from user's migrations plugin. -func lookup(symbol string) (plugin.Symbol, error) { - p, err := plugin.Open(pluginPath()) - if err != nil { - return nil, err - } - return p.Lookup(symbol) -} - func main() { log.SetPrefix("bundb: ") - if err := app.Run(os.Args); err != nil { - log.Fatal(err) + // TODO: use buncli.New(buncli.FromPlugin()) to read config from plugin + if err := buncli.Run(os.Args, nil); err != nil { } } diff --git a/extra/buncli/auto.go b/extra/buncli/auto.go new file mode 100644 index 000000000..0d9849072 --- /dev/null +++ b/extra/buncli/auto.go @@ -0,0 +1,56 @@ +package buncli + +import ( + "github.com/urfave/cli/v2" +) + +func CmdAuto(c *Config) *cli.Command { + return &cli.Command{ + Name: "auto", + Usage: "Manage database schema with AutoMigrator", + Subcommands: cli.Commands{ + &cli.Command{ + Name: "create", + Usage: "Generate SQL migration files", + Flags: []cli.Flag{ + flagTx, + }, + Action: func(ctx *cli.Context) error { + return autoCreate(ctx, c) + }, + }, + &cli.Command{ + Name: "migrate", + Usage: "Generate SQL migrations and apply them right away", + Action: func(ctx *cli.Context) error { + return autoMigrate(ctx, c) + }, + }, + }, + } +} + +var ( + // flagTx adds --transactional flag. + flagTx = &cli.BoolFlag{ + Name: "tx", + Aliases: []string{"transactional"}, + Usage: "write migrations to .tx.(up|down).sql file, they will be marked as transactional", + Value: false, + } +) + +func autoMigrate(ctx *cli.Context, c *Config) error { + _, err := c.AutoMigrator.Migrate(ctx.Context) + return err +} + +func autoCreate(ctx *cli.Context, c *Config) error { + var err error + if flagTx.Get(ctx) { + _, err = c.AutoMigrator.CreateTxSQLMigrations(ctx.Context) + } else { + _, err = c.AutoMigrator.CreateSQLMigrations(ctx.Context) + } + return err +} diff --git a/extra/buncli/buncli.go b/extra/buncli/buncli.go new file mode 100644 index 000000000..58983666e --- /dev/null +++ b/extra/buncli/buncli.go @@ -0,0 +1,57 @@ +/* +TODO: + - Commands: + - init - Create migration+locks tables [--cmd to add cmd/ folder structure] + - migrate - Apply database migrations + - rollback - Rollback the last migration group + - create - Create template SQL migration filex [--go | --sql | --transactional] + - unlock - Unlock locks table + - provide NewCommand() *cli.Command intead of the cli.App, so that buncli could be embeded in the existing CLIs + - configure logging and verbosity + - (experimental, low prio) add FromPlugin() to read config from plugin and use from cmd/bundb. +*/ +package buncli + +import ( + "context" + + "github.com/uptrace/bun" + "github.com/uptrace/bun/migrate" + "github.com/urfave/cli/v2" +) + +// bunApp is the root-level bundb app that all other commands attach to. +var bunApp = &cli.App{ + Name: "bundb", + Usage: "Database migration tool for uptrace/bun", +} + +// New creates a new CLI application for managing bun migrations. +func New(c *Config) *App { + bunApp.Commands = cli.Commands{ + CmdAuto(c), + } + return &App{ + App: bunApp, + } +} + +type Config struct { + DB *bun.DB + AutoMigrator *migrate.AutoMigrator +} + +// Run calls cli.App.Run and returns its error. +func Run(args []string, c *Config) error { + return New(c).Run(args) +} + +// RunCtx calls cli.App.RunContexta and returns its error. +func RunContext(ctx context.Context, args []string, c *Config) error { + return New(c).RunContext(ctx, args) +} + +// App is a wrapper around cli.App that extends it with bun-specific features. +type App struct { + *cli.App +} diff --git a/extra/buncli/go.mod b/extra/buncli/go.mod new file mode 100644 index 000000000..16a966cd2 --- /dev/null +++ b/extra/buncli/go.mod @@ -0,0 +1,22 @@ +module github.com/uptrace/bun/extra/buncli + +go 1.22.0 + +require ( + github.com/uptrace/bun v1.2.7-0.20241125022320-89e9d5169f8a + github.com/urfave/cli/v2 v2.27.5 +) + +require ( + github.com/cpuguy83/go-md2man/v2 v2.0.5 // indirect + github.com/jinzhu/inflection v1.0.0 // indirect + github.com/puzpuzpuz/xsync/v3 v3.4.0 // indirect + github.com/russross/blackfriday/v2 v2.1.0 // indirect + github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc // indirect + github.com/vmihailenco/msgpack/v5 v5.4.1 // indirect + github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect + github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // indirect + golang.org/x/sys v0.27.0 // indirect +) + +replace github.com/uptrace/bun => ../.. diff --git a/extra/buncli/go.sum b/extra/buncli/go.sum new file mode 100644 index 000000000..63ffdb699 --- /dev/null +++ b/extra/buncli/go.sum @@ -0,0 +1,28 @@ +github.com/cpuguy83/go-md2man/v2 v2.0.5 h1:ZtcqGrnekaHpVLArFSe4HK5DoKx1T0rq2DwVB0alcyc= +github.com/cpuguy83/go-md2man/v2 v2.0.5/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= +github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/puzpuzpuz/xsync/v3 v3.4.0 h1:DuVBAdXuGFHv8adVXjWWZ63pJq+NRXOWVXlKDBZ+mJ4= +github.com/puzpuzpuz/xsync/v3 v3.4.0/go.mod h1:VjzYrABPabuM4KyBh1Ftq6u8nhwY5tBPKP9jpmh0nnA= +github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc h1:9lRDQMhESg+zvGYmW5DyG0UqvY96Bu5QYsTLvCHdrgo= +github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc/go.mod h1:bciPuU6GHm1iF1pBvUfxfsH0Wmnc2VbpgvbI9ZWuIRs= +github.com/urfave/cli/v2 v2.27.5 h1:WoHEJLdsXr6dDWoJgMq/CboDmyY/8HMMH1fTECbih+w= +github.com/urfave/cli/v2 v2.27.5/go.mod h1:3Sevf16NykTbInEnD0yKkjDAeZDS0A6bzhBH5hrMvTQ= +github.com/vmihailenco/msgpack/v5 v5.4.1 h1:cQriyiUvjTwOHg8QZaPihLWeRAAVoCpE00IUPn0Bjt8= +github.com/vmihailenco/msgpack/v5 v5.4.1/go.mod h1:GaZTsDaehaPpQVyxrf5mtQlH+pc21PIudVV/E3rRQok= +github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g= +github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds= +github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 h1:gEOO8jv9F4OT7lGCjxCBTO/36wtF6j2nSip77qHd4x4= +github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1/go.mod h1:Ohn+xnUBiLI6FVj/9LpzZWtj1/D6lUovWYBkxHVV3aM= +golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s= +golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/go.mod b/go.mod index 48b197ab8..dba5573ed 100644 --- a/go.mod +++ b/go.mod @@ -8,17 +8,24 @@ require ( github.com/rs/zerolog v1.33.0 github.com/stretchr/testify v1.8.1 github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc + github.com/uptrace/bun/extra/buncli v0.0.0-00010101000000-000000000000 github.com/vmihailenco/msgpack/v5 v5.4.1 ) require ( + github.com/cpuguy83/go-md2man/v2 v2.0.5 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/russross/blackfriday/v2 v2.1.0 // indirect + github.com/urfave/cli/v2 v2.27.5 // indirect github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect + github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // indirect golang.org/x/sys v0.27.0 // indirect gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) + +replace github.com/uptrace/bun/extra/buncli => ./extra/buncli diff --git a/go.sum b/go.sum index 0e97efd20..a7f2f5675 100644 --- a/go.sum +++ b/go.sum @@ -1,4 +1,6 @@ github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= +github.com/cpuguy83/go-md2man/v2 v2.0.5 h1:ZtcqGrnekaHpVLArFSe4HK5DoKx1T0rq2DwVB0alcyc= +github.com/cpuguy83/go-md2man/v2 v2.0.5/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -24,6 +26,8 @@ github.com/puzpuzpuz/xsync/v3 v3.4.0/go.mod h1:VjzYrABPabuM4KyBh1Ftq6u8nhwY5tBPK github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= github.com/rs/zerolog v1.33.0 h1:1cU2KZkvPxNyfgEmhHAz/1A9Bz+llsdYzklWFzgp0r8= github.com/rs/zerolog v1.33.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss= +github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= @@ -33,10 +37,14 @@ github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKs github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc h1:9lRDQMhESg+zvGYmW5DyG0UqvY96Bu5QYsTLvCHdrgo= github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc/go.mod h1:bciPuU6GHm1iF1pBvUfxfsH0Wmnc2VbpgvbI9ZWuIRs= +github.com/urfave/cli/v2 v2.27.5 h1:WoHEJLdsXr6dDWoJgMq/CboDmyY/8HMMH1fTECbih+w= +github.com/urfave/cli/v2 v2.27.5/go.mod h1:3Sevf16NykTbInEnD0yKkjDAeZDS0A6bzhBH5hrMvTQ= github.com/vmihailenco/msgpack/v5 v5.4.1 h1:cQriyiUvjTwOHg8QZaPihLWeRAAVoCpE00IUPn0Bjt8= github.com/vmihailenco/msgpack/v5 v5.4.1/go.mod h1:GaZTsDaehaPpQVyxrf5mtQlH+pc21PIudVV/E3rRQok= github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g= github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds= +github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 h1:gEOO8jv9F4OT7lGCjxCBTO/36wtF6j2nSip77qHd4x4= +github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1/go.mod h1:Ohn+xnUBiLI6FVj/9LpzZWtj1/D6lUovWYBkxHVV3aM= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= From 454284957e46b982b9db795fa65b51349e5a2222 Mon Sep 17 00:00:00 2001 From: dyma solovei Date: Tue, 26 Nov 2024 17:28:21 +0100 Subject: [PATCH 06/17] chore: add changes from 'go mod tidy' --- dialect/pgdialect/go.mod | 1 - dialect/pgdialect/go.sum | 1 - driver/pgdriver/go.mod | 1 - driver/pgdriver/go.sum | 4 +--- go.mod | 1 + go.sum | 2 +- 6 files changed, 3 insertions(+), 7 deletions(-) diff --git a/dialect/pgdialect/go.mod b/dialect/pgdialect/go.mod index 1b2412aed..582202736 100644 --- a/dialect/pgdialect/go.mod +++ b/dialect/pgdialect/go.mod @@ -12,7 +12,6 @@ require ( require ( github.com/davecgh/go-spew v1.1.1 // indirect github.com/jinzhu/inflection v1.0.0 // indirect - github.com/kr/text v0.2.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/puzpuzpuz/xsync/v3 v3.4.0 // indirect github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc // indirect diff --git a/dialect/pgdialect/go.sum b/dialect/pgdialect/go.sum index 207443654..4a7f9e225 100644 --- a/dialect/pgdialect/go.sum +++ b/dialect/pgdialect/go.sum @@ -1,4 +1,3 @@ -github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= diff --git a/driver/pgdriver/go.mod b/driver/pgdriver/go.mod index 1c836c726..bc3f7c806 100644 --- a/driver/pgdriver/go.mod +++ b/driver/pgdriver/go.mod @@ -13,7 +13,6 @@ require ( require ( github.com/davecgh/go-spew v1.1.1 // indirect github.com/jinzhu/inflection v1.0.0 // indirect - github.com/kr/text v0.1.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/puzpuzpuz/xsync/v3 v3.4.0 // indirect github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc // indirect diff --git a/driver/pgdriver/go.sum b/driver/pgdriver/go.sum index e545dc75d..fd935e2b4 100644 --- a/driver/pgdriver/go.sum +++ b/driver/pgdriver/go.sum @@ -3,9 +3,7 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= -github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= -github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= diff --git a/go.mod b/go.mod index dba5573ed..931c8a476 100644 --- a/go.mod +++ b/go.mod @@ -15,6 +15,7 @@ require ( require ( github.com/cpuguy83/go-md2man/v2 v2.0.5 // indirect github.com/davecgh/go-spew v1.1.1 // indirect + github.com/kr/text v0.2.0 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect diff --git a/go.sum b/go.sum index a7f2f5675..e75d90252 100644 --- a/go.sum +++ b/go.sum @@ -8,8 +8,8 @@ github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5x github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= From f20257b4026c069038882b5fe512adbe383c7b7e Mon Sep 17 00:00:00 2001 From: dyma solovei Date: Tue, 26 Nov 2024 17:33:20 +0100 Subject: [PATCH 07/17] feat: add Migrator commands - migrate - rollback - create - unlock --- cmd/bundb/main.go | 1 + extra/buncli/auto.go | 21 ++----- extra/buncli/buncli.go | 25 ++++---- extra/buncli/migrator.go | 122 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 143 insertions(+), 26 deletions(-) create mode 100644 extra/buncli/migrator.go diff --git a/cmd/bundb/main.go b/cmd/bundb/main.go index 1e5365b04..0e424d42c 100644 --- a/cmd/bundb/main.go +++ b/cmd/bundb/main.go @@ -35,5 +35,6 @@ func main() { log.SetPrefix("bundb: ") // TODO: use buncli.New(buncli.FromPlugin()) to read config from plugin if err := buncli.Run(os.Args, nil); err != nil { + log.Fatal(err) } } diff --git a/extra/buncli/auto.go b/extra/buncli/auto.go index 0d9849072..4fd4eb172 100644 --- a/extra/buncli/auto.go +++ b/extra/buncli/auto.go @@ -4,6 +4,7 @@ import ( "github.com/urfave/cli/v2" ) +// CmdAuto creates the auto command hierarchy. func CmdAuto(c *Config) *cli.Command { return &cli.Command{ Name: "auto", @@ -16,36 +17,26 @@ func CmdAuto(c *Config) *cli.Command { flagTx, }, Action: func(ctx *cli.Context) error { - return autoCreate(ctx, c) + return runAutoCreate(ctx, c) }, }, &cli.Command{ Name: "migrate", Usage: "Generate SQL migrations and apply them right away", Action: func(ctx *cli.Context) error { - return autoMigrate(ctx, c) + return runAutoMigrate(ctx, c) }, }, }, } } -var ( - // flagTx adds --transactional flag. - flagTx = &cli.BoolFlag{ - Name: "tx", - Aliases: []string{"transactional"}, - Usage: "write migrations to .tx.(up|down).sql file, they will be marked as transactional", - Value: false, - } -) - -func autoMigrate(ctx *cli.Context, c *Config) error { - _, err := c.AutoMigrator.Migrate(ctx.Context) +func runAutoMigrate(ctx *cli.Context, c *Config) error { + _, err := c.AutoMigrator.Migrate(ctx.Context, c.MigrateOptions...) return err } -func autoCreate(ctx *cli.Context, c *Config) error { +func runAutoCreate(ctx *cli.Context, c *Config) error { var err error if flagTx.Get(ctx) { _, err = c.AutoMigrator.CreateTxSQLMigrations(ctx.Context) diff --git a/extra/buncli/buncli.go b/extra/buncli/buncli.go index 58983666e..04d7ccf2f 100644 --- a/extra/buncli/buncli.go +++ b/extra/buncli/buncli.go @@ -1,14 +1,10 @@ /* TODO: - - Commands: - - init - Create migration+locks tables [--cmd to add cmd/ folder structure] - - migrate - Apply database migrations - - rollback - Rollback the last migration group - - create - Create template SQL migration filex [--go | --sql | --transactional] - - unlock - Unlock locks table - - provide NewCommand() *cli.Command intead of the cli.App, so that buncli could be embeded in the existing CLIs - - configure logging and verbosity - - (experimental, low prio) add FromPlugin() to read config from plugin and use from cmd/bundb. + - Commands: + - init - Create migration+locks tables [--no-cmd to omit cmd/ folder] + - provide NewCommand() *cli.Command intead of the cli.App, so that buncli could be embeded in the existing CLIs + - configure logging and verbosity + - (experimental, low prio) add FromPlugin() to read config from plugin and use from cmd/bundb. */ package buncli @@ -29,7 +25,11 @@ var bunApp = &cli.App{ // New creates a new CLI application for managing bun migrations. func New(c *Config) *App { bunApp.Commands = cli.Commands{ + CmdMigrate(c), + CmdRollback(c), + CmdCreate(c), CmdAuto(c), + CmdUnlock(c), } return &App{ App: bunApp, @@ -37,8 +37,11 @@ func New(c *Config) *App { } type Config struct { - DB *bun.DB - AutoMigrator *migrate.AutoMigrator + DB *bun.DB + AutoMigrator *migrate.AutoMigrator + Migrations *migrate.Migrations + MigrateOptions []migrate.MigrationOption + GoMigrationOptions []migrate.GoMigrationOption } // Run calls cli.App.Run and returns its error. diff --git a/extra/buncli/migrator.go b/extra/buncli/migrator.go new file mode 100644 index 000000000..a50dd4849 --- /dev/null +++ b/extra/buncli/migrator.go @@ -0,0 +1,122 @@ +package buncli + +import ( + "github.com/uptrace/bun/migrate" + "github.com/urfave/cli/v2" +) + +// CmdMigrate creates a migrate command. +func CmdMigrate(c *Config) *cli.Command { + return &cli.Command{ + Name: "migrate", + Usage: "Apply database migrations", + Action: func(ctx *cli.Context) error { + return runMigrate(ctx, c) + }, + } +} + +func runMigrate(ctx *cli.Context, c *Config) error { + m := migrate.NewMigrator(c.DB, c.Migrations) + _, err := m.Migrate(ctx.Context, c.MigrateOptions...) + return err +} + +// CmdRollback creates a rollback command. +func CmdRollback(c *Config) *cli.Command { + return &cli.Command{ + Name: "rollback", + Usage: "Rollback the last migration group", + Action: func(ctx *cli.Context) error { + return runRollback(ctx, c) + }, + } +} + +func runRollback(ctx *cli.Context, c *Config) error { + m := migrate.NewMigrator(c.DB, c.Migrations) + _, err := m.Rollback(ctx.Context, c.MigrateOptions...) + return err +} + +func CmdCreate(c *Config) *cli.Command { + return &cli.Command{ + Name: "create", + Usage: "Create a new migration file template", + Args: true, // TODO: add usage example and description + Flags: []cli.Flag{ + flagTx, + flagGo, + flagSQL, + }, + Action: func(ctx *cli.Context) error { + return runCreate(ctx, c) + }, + } +} + +var ( + // createGo controls the type of the template (Go/SQL) the Migrator should create. + createGo bool + + // flagTx adds --transactional flag. + flagTx = &cli.BoolFlag{ + Name: "tx", + Aliases: []string{"transactional"}, + Usage: "write migrations to .tx.(up|down).sql file, they will be marked as transactional", + Value: false, + } + + // flagGo adds --go flag. Prefer checking 'createGo' as the flagGo and flagSQL are mutually exclusive and it is how they synchronize. + flagGo = &cli.BoolFlag{ + Name: "go", + Usage: "create .go migration file", + Value: false, + Destination: &createGo, + } + + // flagSQL adds --sql flag. Prefer checking 'createGo' as the flagGo and flagSQL are mutually exclusive and it is how they synchronize. + flagSQL = &cli.BoolFlag{ + Name: "sql", + Usage: "create .sql migrations", + Value: true, + Action: func(ctx *cli.Context, b bool) error { + createGo = !b + return nil + }, + } +) + +func runCreate(ctx *cli.Context, c *Config) error { + var err error + m := migrate.NewMigrator(c.DB, c.Migrations) + name := ctx.Args().First() + + if createGo { + _, err = m.CreateGoMigration(ctx.Context, name, c.GoMigrationOptions...) + return err + } + + if flagTx.Get(ctx) { + _, err = m.CreateTxSQLMigrations(ctx.Context, name) + } else { + _, err = m.CreateSQLMigrations(ctx.Context, name) + } + return err +} + +// CmdUnlock creates an unlock command. +func CmdUnlock(c *Config) *cli.Command { + return &cli.Command{ + Name: "unlock", + Usage: "Unlock migration locks table", + Action: func(ctx *cli.Context) error { + return runUnlock(ctx, c) + }, + } +} + +func runUnlock(ctx *cli.Context, c *Config) error { + m := migrate.NewMigrator(c.DB, c.Migrations) + return m.Unlock(ctx.Context) +} From 3d4032c7c5d62a2bf0115bd702c9281c94322eee Mon Sep 17 00:00:00 2001 From: dyma solovei Date: Tue, 26 Nov 2024 19:10:58 +0100 Subject: [PATCH 08/17] chore: add missing go.sum entry --- go.sum | 2 ++ 1 file changed, 2 insertions(+) diff --git a/go.sum b/go.sum index e75d90252..804c1238d 100644 --- a/go.sum +++ b/go.sum @@ -1,6 +1,7 @@ github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/cpuguy83/go-md2man/v2 v2.0.5 h1:ZtcqGrnekaHpVLArFSe4HK5DoKx1T0rq2DwVB0alcyc= github.com/cpuguy83/go-md2man/v2 v2.0.5/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -10,6 +11,7 @@ github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkr github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= From 9da2bfca0b5a28c66bc910b0394c575c88c6fbaf Mon Sep 17 00:00:00 2001 From: dyma solovei Date: Tue, 26 Nov 2024 22:30:13 +0100 Subject: [PATCH 09/17] chore: revert all changes to go.mod/go.sum files --- dialect/pgdialect/go.mod | 1 + dialect/pgdialect/go.sum | 1 + driver/pgdriver/go.mod | 1 + driver/pgdriver/go.sum | 4 +++- 4 files changed, 6 insertions(+), 1 deletion(-) diff --git a/dialect/pgdialect/go.mod b/dialect/pgdialect/go.mod index 582202736..1b2412aed 100644 --- a/dialect/pgdialect/go.mod +++ b/dialect/pgdialect/go.mod @@ -12,6 +12,7 @@ require ( require ( github.com/davecgh/go-spew v1.1.1 // indirect github.com/jinzhu/inflection v1.0.0 // indirect + github.com/kr/text v0.2.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/puzpuzpuz/xsync/v3 v3.4.0 // indirect github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc // indirect diff --git a/dialect/pgdialect/go.sum b/dialect/pgdialect/go.sum index 4a7f9e225..207443654 100644 --- a/dialect/pgdialect/go.sum +++ b/dialect/pgdialect/go.sum @@ -1,3 +1,4 @@ +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= diff --git a/driver/pgdriver/go.mod b/driver/pgdriver/go.mod index bc3f7c806..1c836c726 100644 --- a/driver/pgdriver/go.mod +++ b/driver/pgdriver/go.mod @@ -13,6 +13,7 @@ require ( require ( github.com/davecgh/go-spew v1.1.1 // indirect github.com/jinzhu/inflection v1.0.0 // indirect + github.com/kr/text v0.1.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/puzpuzpuz/xsync/v3 v3.4.0 // indirect github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc // indirect diff --git a/driver/pgdriver/go.sum b/driver/pgdriver/go.sum index fd935e2b4..e545dc75d 100644 --- a/driver/pgdriver/go.sum +++ b/driver/pgdriver/go.sum @@ -3,7 +3,9 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= -github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= From d35e2f2bab61607081eb2efa81fcb930b5d909a4 Mon Sep 17 00:00:00 2001 From: dyma solovei Date: Tue, 26 Nov 2024 22:33:44 +0100 Subject: [PATCH 10/17] ci: revert changes to bun/go.mod and bun/go.sum --- go.mod | 1 - go.sum | 4 +--- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/go.mod b/go.mod index 931c8a476..dba5573ed 100644 --- a/go.mod +++ b/go.mod @@ -15,7 +15,6 @@ require ( require ( github.com/cpuguy83/go-md2man/v2 v2.0.5 // indirect github.com/davecgh/go-spew v1.1.1 // indirect - github.com/kr/text v0.2.0 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect diff --git a/go.sum b/go.sum index 804c1238d..a7f2f5675 100644 --- a/go.sum +++ b/go.sum @@ -1,7 +1,6 @@ github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/cpuguy83/go-md2man/v2 v2.0.5 h1:ZtcqGrnekaHpVLArFSe4HK5DoKx1T0rq2DwVB0alcyc= github.com/cpuguy83/go-md2man/v2 v2.0.5/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= -github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -9,9 +8,8 @@ github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5x github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= -github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= -github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= From 312e65ac28e46f4da93af955199323ffd3be81a0 Mon Sep 17 00:00:00 2001 From: dyma solovei Date: Wed, 27 Nov 2024 01:18:22 +0100 Subject: [PATCH 11/17] feat: bootstrap bun migrations with 'bun init' --- extra/buncli/buncli.go | 3 +- extra/buncli/go.mod | 39 ++++++ extra/buncli/go.sum | 69 ++++++++++ extra/buncli/init.go | 273 +++++++++++++++++++++++++++++++++++++++ extra/buncli/migrator.go | 5 +- go.mod | 26 +++- go.sum | 71 +++++++++- 7 files changed, 478 insertions(+), 8 deletions(-) create mode 100644 extra/buncli/init.go diff --git a/extra/buncli/buncli.go b/extra/buncli/buncli.go index 04d7ccf2f..bd67ee61b 100644 --- a/extra/buncli/buncli.go +++ b/extra/buncli/buncli.go @@ -1,7 +1,7 @@ /* TODO: - Commands: - - init - Create migration+locks tables [--no-cmd to omit cmd/ folder] + - init - Create migration+locks tables [--no-cmd to omit cmd/ folder] - provide NewCommand() *cli.Command intead of the cli.App, so that buncli could be embeded in the existing CLIs - configure logging and verbosity - (experimental, low prio) add FromPlugin() to read config from plugin and use from cmd/bundb. @@ -25,6 +25,7 @@ var bunApp = &cli.App{ // New creates a new CLI application for managing bun migrations. func New(c *Config) *App { bunApp.Commands = cli.Commands{ + CmdInit(), CmdMigrate(c), CmdRollback(c), CmdCreate(c), diff --git a/extra/buncli/go.mod b/extra/buncli/go.mod index 16a966cd2..0cc2ef1ac 100644 --- a/extra/buncli/go.mod +++ b/extra/buncli/go.mod @@ -4,19 +4,58 @@ go 1.22.0 require ( github.com/uptrace/bun v1.2.7-0.20241125022320-89e9d5169f8a + github.com/uptrace/bun/dialect/mssqldialect v1.2.7-0.20241126124946-928d0779110e + github.com/uptrace/bun/dialect/mysqldialect v1.2.7-0.20241126124946-928d0779110e + github.com/uptrace/bun/dialect/oracledialect v1.2.7-0.20241126124946-928d0779110e + github.com/uptrace/bun/dialect/pgdialect v1.2.7-0.20241126124946-928d0779110e + github.com/uptrace/bun/dialect/sqlitedialect v1.2.7-0.20241126124946-928d0779110e + github.com/uptrace/bun/driver/pgdriver v1.2.7-0.20241126124946-928d0779110e + github.com/uptrace/bun/driver/sqliteshim v1.2.7-0.20241126124946-928d0779110e github.com/urfave/cli/v2 v2.27.5 ) require ( github.com/cpuguy83/go-md2man/v2 v2.0.5 // indirect + github.com/dustin/go-humanize v1.0.1 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect github.com/jinzhu/inflection v1.0.0 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/mattn/go-sqlite3 v1.14.24 // indirect + github.com/ncruces/go-strftime v0.1.9 // indirect github.com/puzpuzpuz/xsync/v3 v3.4.0 // indirect + github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc // indirect github.com/vmihailenco/msgpack/v5 v5.4.1 // indirect github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // indirect + golang.org/x/crypto v0.29.0 // indirect + golang.org/x/exp v0.0.0-20241108190413-2d47ceb2692f // indirect + golang.org/x/mod v0.22.0 // indirect golang.org/x/sys v0.27.0 // indirect + mellium.im/sasl v0.3.2 // indirect + modernc.org/gc/v3 v3.0.0-20241004144649-1aea3fae8852 // indirect + modernc.org/libc v1.61.2 // indirect + modernc.org/mathutil v1.6.0 // indirect + modernc.org/memory v1.8.0 // indirect + modernc.org/sqlite v1.34.1 // indirect + modernc.org/strutil v1.2.0 // indirect + modernc.org/token v1.1.0 // indirect ) replace github.com/uptrace/bun => ../.. + +// replace github.com/uptrace/bun/dialect/mssqldialect => ../../dialect/mssqldialect + +// replace github.com/uptrace/bun/dialect/mysqldialect => ../../dialect/mysqldialect + +// replace github.com/uptrace/bun/dialect/oracledialect => ../../dialect/oracledialect + +// replace github.com/uptrace/bun/dialect/pgdialect => ../../dialect/pgdialect + +// replace github.com/uptrace/bun/dialect/sqlitedialect => ../../dialect/sqlitedialect + +// replace github.com/uptrace/bun/driver/pgdriver => ../../driver/pgdriver + +// replace github.com/uptrace/bun/driver/sqliteshim => ../../driver/sqliteshim diff --git a/extra/buncli/go.sum b/extra/buncli/go.sum index 63ffdb699..5f6d0b542 100644 --- a/extra/buncli/go.sum +++ b/extra/buncli/go.sum @@ -2,18 +2,48 @@ github.com/cpuguy83/go-md2man/v2 v2.0.5 h1:ZtcqGrnekaHpVLArFSe4HK5DoKx1T0rq2DwVB github.com/cpuguy83/go-md2man/v2 v2.0.5/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= +github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= +github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd h1:gbpYu9NMq8jhDVbvlGkMFWCjLFlqqEZjEmObmhUy6Vo= +github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd/go.mod h1:kf6iHlnVGwgKolg33glAes7Yg/8iWP8ukqeldJSO7jw= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= +github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-sqlite3 v1.14.24 h1:tpSp2G2KyMnnQu99ngJ47EIkWVmliIizyZBfPrBWDRM= +github.com/mattn/go-sqlite3 v1.14.24/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= +github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4= +github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/puzpuzpuz/xsync/v3 v3.4.0 h1:DuVBAdXuGFHv8adVXjWWZ63pJq+NRXOWVXlKDBZ+mJ4= github.com/puzpuzpuz/xsync/v3 v3.4.0/go.mod h1:VjzYrABPabuM4KyBh1Ftq6u8nhwY5tBPKP9jpmh0nnA= +github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= +github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc h1:9lRDQMhESg+zvGYmW5DyG0UqvY96Bu5QYsTLvCHdrgo= github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc/go.mod h1:bciPuU6GHm1iF1pBvUfxfsH0Wmnc2VbpgvbI9ZWuIRs= +github.com/uptrace/bun/dialect/mssqldialect v1.2.7-0.20241126124946-928d0779110e h1:gaD0dmqaYNFtX+iPxhX9UDelJ6u6BVajSNH4Ing5Ty4= +github.com/uptrace/bun/dialect/mssqldialect v1.2.7-0.20241126124946-928d0779110e/go.mod h1:IzBp33ztsWM76cdCqJAb1iXpwKl+djYEsA5EIPh2H6M= +github.com/uptrace/bun/dialect/mysqldialect v1.2.7-0.20241126124946-928d0779110e h1:w2VE7E0DrlYd1QdHH2MGmSO3AyTfU3D5Yn8aGuSTh9c= +github.com/uptrace/bun/dialect/mysqldialect v1.2.7-0.20241126124946-928d0779110e/go.mod h1:f3OuhVaMIwb98ZH+QhFuxlDNgkxTWcy93o3bYml78qA= +github.com/uptrace/bun/dialect/oracledialect v1.2.7-0.20241126124946-928d0779110e h1:HRWeCrgT9ggMISNuvx+QeFqgstFFh27ab8FBYgvQ5pY= +github.com/uptrace/bun/dialect/oracledialect v1.2.7-0.20241126124946-928d0779110e/go.mod h1:vCYOY37smpebuKD6FCoF7oy/TxDjz/wIZJ7jRjTxYHM= +github.com/uptrace/bun/dialect/pgdialect v1.2.7-0.20241126124946-928d0779110e h1:iMJU0gy83bPL6JtoPOAroc0GdEdDLz6B6irE83ebnas= +github.com/uptrace/bun/dialect/pgdialect v1.2.7-0.20241126124946-928d0779110e/go.mod h1:hfkgRt/bb5VYxS6CDngcjxF0An0iXOeVz8TSsWJIb/I= +github.com/uptrace/bun/dialect/sqlitedialect v1.2.7-0.20241126124946-928d0779110e h1:X2j53OxG6XgnMGWkFoyLWlZQS0hGFxNZGYXU73mUtRw= +github.com/uptrace/bun/dialect/sqlitedialect v1.2.7-0.20241126124946-928d0779110e/go.mod h1:/jVQkA37QsTWBjGesSHubm8W1PLI5+gCzFrSX1k0Kfs= +github.com/uptrace/bun/driver/pgdriver v1.2.7-0.20241126124946-928d0779110e h1:18aMpwYKpAwFcOm7Swv8zHikkq5NTW8fFEpmnDF1gH4= +github.com/uptrace/bun/driver/pgdriver v1.2.7-0.20241126124946-928d0779110e/go.mod h1:B+uzDgcf88kUubal/g7L5VB58n1DBCugKBKA16YAM6w= +github.com/uptrace/bun/driver/sqliteshim v1.2.7-0.20241126124946-928d0779110e h1:HsHf0+SS9q01zbV9jNIg9lf8U/hjSPQOvGXa+3WwzXg= +github.com/uptrace/bun/driver/sqliteshim v1.2.7-0.20241126124946-928d0779110e/go.mod h1:6NKvfZH+x8b+LEt9+zvx0LFWxJf18/tt/NFzn4FxNMM= github.com/urfave/cli/v2 v2.27.5 h1:WoHEJLdsXr6dDWoJgMq/CboDmyY/8HMMH1fTECbih+w= github.com/urfave/cli/v2 v2.27.5/go.mod h1:3Sevf16NykTbInEnD0yKkjDAeZDS0A6bzhBH5hrMvTQ= github.com/vmihailenco/msgpack/v5 v5.4.1 h1:cQriyiUvjTwOHg8QZaPihLWeRAAVoCpE00IUPn0Bjt8= @@ -22,7 +52,46 @@ github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAh github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds= github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 h1:gEOO8jv9F4OT7lGCjxCBTO/36wtF6j2nSip77qHd4x4= github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1/go.mod h1:Ohn+xnUBiLI6FVj/9LpzZWtj1/D6lUovWYBkxHVV3aM= +golang.org/x/crypto v0.29.0 h1:L5SG1JTTXupVV3n6sUqMTeWbjAyfPwoda2DLX8J8FrQ= +golang.org/x/crypto v0.29.0/go.mod h1:+F4F4N5hv6v38hfeYwTdx20oUvLLc+QfrE9Ax9HtgRg= +golang.org/x/exp v0.0.0-20241108190413-2d47ceb2692f h1:XdNn9LlyWAhLVp6P/i8QYBW+hlyhrhei9uErw2B5GJo= +golang.org/x/exp v0.0.0-20241108190413-2d47ceb2692f/go.mod h1:D5SMRVC3C2/4+F/DB1wZsLRnSNimn2Sp/NPsCrsv8ak= +golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4= +golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= +golang.org/x/sync v0.9.0 h1:fEo0HyrW1GIgZdpbhCRO0PkJajUS5H9IFUztCgEo2jQ= +golang.org/x/sync v0.9.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s= golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/tools v0.27.0 h1:qEKojBykQkQ4EynWy4S8Weg69NumxKdn40Fce3uc/8o= +golang.org/x/tools v0.27.0/go.mod h1:sUi0ZgbwW9ZPAq26Ekut+weQPR5eIM6GQLQ1Yjm1H0Q= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +mellium.im/sasl v0.3.2 h1:PT6Xp7ccn9XaXAnJ03FcEjmAn7kK1x7aoXV6F+Vmrl0= +mellium.im/sasl v0.3.2/go.mod h1:NKXDi1zkr+BlMHLQjY3ofYuU4KSPFxknb8mfEu6SveY= +modernc.org/cc/v4 v4.23.1 h1:WqJoPL3x4cUufQVHkXpXX7ThFJ1C4ik80i2eXEXbhD8= +modernc.org/cc/v4 v4.23.1/go.mod h1:HM7VJTZbUCR3rV8EYBi9wxnJ0ZBRiGE5OeGXNA0IsLQ= +modernc.org/ccgo/v4 v4.22.3 h1:C7AW89Zw3kygesTQWBzApwIn9ldM+cb/plrTIKq41Os= +modernc.org/ccgo/v4 v4.22.3/go.mod h1:Dz7n0/UkBbH3pnYaxgi1mFSfF4REqUOZNziphZASx6k= +modernc.org/fileutil v1.3.0 h1:gQ5SIzK3H9kdfai/5x41oQiKValumqNTDXMvKo62HvE= +modernc.org/fileutil v1.3.0/go.mod h1:XatxS8fZi3pS8/hKG2GH/ArUogfxjpEKs3Ku3aK4JyQ= +modernc.org/gc/v2 v2.5.0 h1:bJ9ChznK1L1mUtAQtxi0wi5AtAs5jQuw4PrPHO5pb6M= +modernc.org/gc/v2 v2.5.0/go.mod h1:wzN5dK1AzVGoH6XOzc3YZ+ey/jPgYHLuVckd62P0GYU= +modernc.org/gc/v3 v3.0.0-20241004144649-1aea3fae8852 h1:IYXPPTTjjoSHvUClZIYexDiO7g+4x+XveKT4gCIAwiY= +modernc.org/gc/v3 v3.0.0-20241004144649-1aea3fae8852/go.mod h1:Qz0X07sNOR1jWYCrJMEnbW/X55x206Q7Vt4mz6/wHp4= +modernc.org/libc v1.61.2 h1:dkO4DlowfClcJYsvf/RiK6fUwvzCQTmB34bJLt0CAGQ= +modernc.org/libc v1.61.2/go.mod h1:4QGjNyX3h+rn7V5oHpJY2yH0QN6frt1X+5BkXzwLPCo= +modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4= +modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo= +modernc.org/memory v1.8.0 h1:IqGTL6eFMaDZZhEWwcREgeMXYwmW83LYW8cROZYkg+E= +modernc.org/memory v1.8.0/go.mod h1:XPZ936zp5OMKGWPqbD3JShgd/ZoQ7899TUuQqxY+peU= +modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4= +modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0= +modernc.org/sortutil v1.2.0 h1:jQiD3PfS2REGJNzNCMMaLSp/wdMNieTbKX920Cqdgqc= +modernc.org/sortutil v1.2.0/go.mod h1:TKU2s7kJMf1AE84OoiGppNHJwvB753OYfNl2WRb++Ss= +modernc.org/sqlite v1.34.1 h1:u3Yi6M0N8t9yKRDwhXcyp1eS5/ErhPTBggxWFuR6Hfk= +modernc.org/sqlite v1.34.1/go.mod h1:pXV2xHxhzXZsgT/RtTFAPY6JJDEvOTcTdwADQCCWD4k= +modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA= +modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0= +modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= +modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= diff --git a/extra/buncli/init.go b/extra/buncli/init.go new file mode 100644 index 000000000..079647639 --- /dev/null +++ b/extra/buncli/init.go @@ -0,0 +1,273 @@ +package buncli + +import ( + "database/sql" + "fmt" + "log" + "os" + "path" + "strings" + + "github.com/uptrace/bun" + "github.com/uptrace/bun/dialect/mssqldialect" + "github.com/uptrace/bun/dialect/mysqldialect" + "github.com/uptrace/bun/dialect/oracledialect" + "github.com/uptrace/bun/dialect/pgdialect" + "github.com/uptrace/bun/dialect/sqlitedialect" + "github.com/uptrace/bun/driver/pgdriver" + "github.com/uptrace/bun/driver/sqliteshim" + "github.com/uptrace/bun/migrate" + "github.com/uptrace/bun/schema" + "github.com/urfave/cli/v2" + "golang.org/x/mod/modfile" +) + +// CmdInit creates init command. +func CmdInit() *cli.Command { + return &cli.Command{ + Name: "init", + Usage: "Create migration tables and default project layout", + Args: true, + Flags: []cli.Flag{ + flagDSN, + flagDriver, + flagNoCmd, + }, + Action: func(ctx *cli.Context) error { + c, err := fromCLI(ctx) + if err != nil { + return err + } + return runInit(ctx, c) + }, + } +} + +const ( + defaultLoc = "." + defaultBin = "bun" + defaultMigrationsDir = "migrations" +) + +var ( + supportedDrivers = []string{"postgres", "sqlserver", "mysql", "oci8", "file"} + + flagDSN = &cli.StringFlag{ + Name: "dsn", + Usage: "database connection string", + Required: true, + EnvVars: []string{"BUNDB_URI"}, + Aliases: []string{"uri"}, + } + + flagDriver = &cli.StringFlag{ + Name: "driver", + Usage: "database driver", + } + + flagNoCmd = &cli.BoolFlag{ + Name: "no-cmd", + Usage: "don't create a CLI entrypoint in cmd/ directory", + Value: false, + } +) + +func runInit(ctx *cli.Context, c *Config) error { + m := migrate.NewMigrator(c.DB, c.Migrations) + if err := m.Init(ctx.Context); err != nil { + return err + } + + loc := ctx.Args().Get(0) + migrationsDir := loc + + if loc == "" { + loc = defaultLoc + } + + if loc == defaultLoc { + migrationsDir = defaultMigrationsDir + } + + log.Print("loc-0: " + loc) + loc = path.Join(loc, "cmd", defaultBin) + log.Print("loc: " + loc) + + if withCmd := !flagNoCmd.Get(ctx); withCmd { + migrationsDir = path.Join(loc, migrationsDir) + if err := initCmd(loc, migrationsDir); err != nil { + return err + } + } + log.Print("migrationsDir: " + migrationsDir) + + if err := initMigrationsPackage(migrationsDir); err != nil { + return err + } + return nil +} + +const entrypointTemplate = `package main + +import ( + "os" + + "github.com/uptrace/bun" + "github.com/uptrace/bun/extra/buncli" + "github.com/uptrace/bun/migrate" + + %q +) + +func main() { + // TODO: connect to db + var _ /* db */ *bun.DB + + // TODO: configure AutoMigrator + var _ /* auto */ migrate.AutoMigrator + + if err := buncli.Run(os.Args, &buncli.Config{ + // DB: db, + // AutoMigrator: auto, + Migrations: migrations.Migrations, + }); err != nil { + panic(err) + } +} +` + +func initCmd(binDir string, migrationsDir string) error { + if err := os.MkdirAll(binDir, 0755); err != nil { + return err + } + + log.Print("binDir: ", binDir) + f, err := os.OpenFile(path.Join(binDir, "main.go"), os.O_CREATE|os.O_EXCL|os.O_WRONLY, 0666) + if err != nil { + if os.IsExist(err) { + // TODO: log the fact that we haven't modified an existing main.go + return nil + } + return err + } + defer f.Close() + + modPath, err := getModPath() + if err != nil { + return err + } + log.Print("go.mod path: ", modPath) + + pkgMigrations := path.Join(modPath, strings.TrimLeft(migrationsDir, ".")) + log.Print("pkgMigrations: ", pkgMigrations) + if _, err := fmt.Fprintf(f, entrypointTemplate, pkgMigrations); err != nil { + log.Print("here!") + return err + } + + return nil +} + +var migrationsTemplate = `package migrations + +import "github.com/uptrace/bun/migrate" + +var Migrations = migrate.NewMigrations() + +func init() { + if err := Migrations.DiscoverCaller(); err != nil { + panic(err) + } +} +` + +func initMigrationsPackage(dir string) error { + if err := os.MkdirAll(dir, 0755); err != nil { + return err + } + + f, err := os.OpenFile(path.Join(dir, "main.go"), os.O_CREATE|os.O_EXCL|os.O_WRONLY, 0666) + if err != nil { + if os.IsExist(err) { + // TODO: log the fact that we haven't modified an existing main.go + return nil + } + return err + } + defer f.Close() + + if _, err := fmt.Fprint(f, migrationsTemplate); err != nil { + return err + } + return nil +} + +// fromCLI creates minimal Config from command line arguments. +// It is inteded to be used exclusively by Init command, as it creates +// the default project structure and the user has no other way of configuring buncli. +// +// DB and Migrations are the only valid fields in the created config, other objects are nil. +func fromCLI(ctx *cli.Context) (*Config, error) { + db, err := newDB(ctx) + if err != nil { + return nil, err + } + return &Config{DB: db, Migrations: migrate.NewMigrations()}, nil +} + +// newDB connects to the database specified by the DSN. +// It will attempt to guess the driver from the connection string prefix, unless it is passed explicitly. +func newDB(ctx *cli.Context) (*bun.DB, error) { + var sqlDB *sql.DB + var dialect schema.Dialect + var err error + + dsn := flagDSN.Get(ctx) + driver := flagDriver.Get(ctx) + if !flagDriver.IsSet() { + guess, _, found := strings.Cut(dsn, ":") + if !found { + return nil, fmt.Errorf("driver cannot be guessed from connection string; pass --driver option explicitly") + } + driver = guess + } + + switch driver { + case "postgres": + sqlDB = sql.OpenDB(pgdriver.NewConnector(pgdriver.WithDSN(dsn))) + dialect = pgdialect.New() + case "sqlserver": + sqlDB, err = sql.Open(driver, dsn) + dialect = mssqldialect.New() + case "file": + sqlDB, err = sql.Open(sqliteshim.ShimName, dsn) + dialect = sqlitedialect.New() + case "mysql": + sqlDB, err = sql.Open(driver, dsn) + dialect = mysqldialect.New() + case "oci8": + sqlDB, err = sql.Open(driver, dsn) + dialect = oracledialect.New() + default: + err = fmt.Errorf("driver %q not recognized, supported drivers are %v", driver, supportedDrivers) + } + + if err != nil { + return nil, err + } + + return bun.NewDB(sqlDB, dialect), nil +} + +func getModPath() (string, error) { + f, err := os.ReadFile("go.mod") + if err != nil { + return "", err + } + + gomod, err := modfile.Parse("go.mod", f, nil) + if err != nil { + return "", err + } + return gomod.Module.Mod.Path, nil +} diff --git a/extra/buncli/migrator.go b/extra/buncli/migrator.go index a50dd4849..c2450e8fe 100644 --- a/extra/buncli/migrator.go +++ b/extra/buncli/migrator.go @@ -5,7 +5,7 @@ import ( "github.com/urfave/cli/v2" ) -// CmdMigrate creates a migrate command. +// CmdMigrate creates migrate command. func CmdMigrate(c *Config) *cli.Command { return &cli.Command{ Name: "migrate", @@ -22,7 +22,7 @@ func runMigrate(ctx *cli.Context, c *Config) error { return err } -// CmdRollback creates a rollback command. +// CmdRollback creates rollback command. func CmdRollback(c *Config) *cli.Command { return &cli.Command{ Name: "rollback", @@ -39,6 +39,7 @@ func runRollback(ctx *cli.Context, c *Config) error { return err } +// CmdCreate creates create command. func CmdCreate(c *Config) *cli.Command { return &cli.Command{ Name: "create", diff --git a/go.mod b/go.mod index dba5573ed..b95700e62 100644 --- a/go.mod +++ b/go.mod @@ -15,17 +15,39 @@ require ( require ( github.com/cpuguy83/go-md2man/v2 v2.0.5 // indirect github.com/davecgh/go-spew v1.1.1 // indirect + github.com/dustin/go-humanize v1.0.1 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect - github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect + github.com/mattn/go-sqlite3 v1.14.24 // indirect + github.com/ncruces/go-strftime v0.1.9 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect + github.com/uptrace/bun/dialect/mssqldialect v1.2.7-0.20241126124946-928d0779110e // indirect + github.com/uptrace/bun/dialect/mysqldialect v1.2.7-0.20241126124946-928d0779110e // indirect + github.com/uptrace/bun/dialect/oracledialect v1.2.7-0.20241126124946-928d0779110e // indirect + github.com/uptrace/bun/dialect/pgdialect v1.2.7-0.20241126124946-928d0779110e // indirect + github.com/uptrace/bun/dialect/sqlitedialect v1.2.7-0.20241126124946-928d0779110e // indirect + github.com/uptrace/bun/driver/pgdriver v1.2.7-0.20241126124946-928d0779110e // indirect + github.com/uptrace/bun/driver/sqliteshim v1.2.7-0.20241126124946-928d0779110e // indirect github.com/urfave/cli/v2 v2.27.5 // indirect github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // indirect + golang.org/x/crypto v0.29.0 // indirect + golang.org/x/exp v0.0.0-20241108190413-2d47ceb2692f // indirect + golang.org/x/mod v0.22.0 // indirect golang.org/x/sys v0.27.0 // indirect - gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect gopkg.in/yaml.v3 v3.0.1 // indirect + mellium.im/sasl v0.3.2 // indirect + modernc.org/gc/v3 v3.0.0-20241004144649-1aea3fae8852 // indirect + modernc.org/libc v1.61.2 // indirect + modernc.org/mathutil v1.6.0 // indirect + modernc.org/memory v1.8.0 // indirect + modernc.org/sqlite v1.34.1 // indirect + modernc.org/strutil v1.2.0 // indirect + modernc.org/token v1.1.0 // indirect ) replace github.com/uptrace/bun/extra/buncli => ./extra/buncli diff --git a/go.sum b/go.sum index a7f2f5675..d617bd1c9 100644 --- a/go.sum +++ b/go.sum @@ -4,18 +4,29 @@ github.com/cpuguy83/go-md2man/v2 v2.0.5/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46t github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= +github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd h1:gbpYu9NMq8jhDVbvlGkMFWCjLFlqqEZjEmObmhUy6Vo= +github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd/go.mod h1:kf6iHlnVGwgKolg33glAes7Yg/8iWP8ukqeldJSO7jw= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= +github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= -github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= -github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-sqlite3 v1.14.24 h1:tpSp2G2KyMnnQu99ngJ47EIkWVmliIizyZBfPrBWDRM= +github.com/mattn/go-sqlite3 v1.14.24/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= +github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4= +github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= @@ -23,6 +34,8 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/puzpuzpuz/xsync/v3 v3.4.0 h1:DuVBAdXuGFHv8adVXjWWZ63pJq+NRXOWVXlKDBZ+mJ4= github.com/puzpuzpuz/xsync/v3 v3.4.0/go.mod h1:VjzYrABPabuM4KyBh1Ftq6u8nhwY5tBPKP9jpmh0nnA= +github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= +github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= github.com/rs/zerolog v1.33.0 h1:1cU2KZkvPxNyfgEmhHAz/1A9Bz+llsdYzklWFzgp0r8= github.com/rs/zerolog v1.33.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss= @@ -37,6 +50,20 @@ github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKs github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc h1:9lRDQMhESg+zvGYmW5DyG0UqvY96Bu5QYsTLvCHdrgo= github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc/go.mod h1:bciPuU6GHm1iF1pBvUfxfsH0Wmnc2VbpgvbI9ZWuIRs= +github.com/uptrace/bun/dialect/mssqldialect v1.2.7-0.20241126124946-928d0779110e h1:gaD0dmqaYNFtX+iPxhX9UDelJ6u6BVajSNH4Ing5Ty4= +github.com/uptrace/bun/dialect/mssqldialect v1.2.7-0.20241126124946-928d0779110e/go.mod h1:IzBp33ztsWM76cdCqJAb1iXpwKl+djYEsA5EIPh2H6M= +github.com/uptrace/bun/dialect/mysqldialect v1.2.7-0.20241126124946-928d0779110e h1:w2VE7E0DrlYd1QdHH2MGmSO3AyTfU3D5Yn8aGuSTh9c= +github.com/uptrace/bun/dialect/mysqldialect v1.2.7-0.20241126124946-928d0779110e/go.mod h1:f3OuhVaMIwb98ZH+QhFuxlDNgkxTWcy93o3bYml78qA= +github.com/uptrace/bun/dialect/oracledialect v1.2.7-0.20241126124946-928d0779110e h1:HRWeCrgT9ggMISNuvx+QeFqgstFFh27ab8FBYgvQ5pY= +github.com/uptrace/bun/dialect/oracledialect v1.2.7-0.20241126124946-928d0779110e/go.mod h1:vCYOY37smpebuKD6FCoF7oy/TxDjz/wIZJ7jRjTxYHM= +github.com/uptrace/bun/dialect/pgdialect v1.2.7-0.20241126124946-928d0779110e h1:iMJU0gy83bPL6JtoPOAroc0GdEdDLz6B6irE83ebnas= +github.com/uptrace/bun/dialect/pgdialect v1.2.7-0.20241126124946-928d0779110e/go.mod h1:hfkgRt/bb5VYxS6CDngcjxF0An0iXOeVz8TSsWJIb/I= +github.com/uptrace/bun/dialect/sqlitedialect v1.2.7-0.20241126124946-928d0779110e h1:X2j53OxG6XgnMGWkFoyLWlZQS0hGFxNZGYXU73mUtRw= +github.com/uptrace/bun/dialect/sqlitedialect v1.2.7-0.20241126124946-928d0779110e/go.mod h1:/jVQkA37QsTWBjGesSHubm8W1PLI5+gCzFrSX1k0Kfs= +github.com/uptrace/bun/driver/pgdriver v1.2.7-0.20241126124946-928d0779110e h1:18aMpwYKpAwFcOm7Swv8zHikkq5NTW8fFEpmnDF1gH4= +github.com/uptrace/bun/driver/pgdriver v1.2.7-0.20241126124946-928d0779110e/go.mod h1:B+uzDgcf88kUubal/g7L5VB58n1DBCugKBKA16YAM6w= +github.com/uptrace/bun/driver/sqliteshim v1.2.7-0.20241126124946-928d0779110e h1:HsHf0+SS9q01zbV9jNIg9lf8U/hjSPQOvGXa+3WwzXg= +github.com/uptrace/bun/driver/sqliteshim v1.2.7-0.20241126124946-928d0779110e/go.mod h1:6NKvfZH+x8b+LEt9+zvx0LFWxJf18/tt/NFzn4FxNMM= github.com/urfave/cli/v2 v2.27.5 h1:WoHEJLdsXr6dDWoJgMq/CboDmyY/8HMMH1fTECbih+w= github.com/urfave/cli/v2 v2.27.5/go.mod h1:3Sevf16NykTbInEnD0yKkjDAeZDS0A6bzhBH5hrMvTQ= github.com/vmihailenco/msgpack/v5 v5.4.1 h1:cQriyiUvjTwOHg8QZaPihLWeRAAVoCpE00IUPn0Bjt8= @@ -45,14 +72,52 @@ github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAh github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds= github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 h1:gEOO8jv9F4OT7lGCjxCBTO/36wtF6j2nSip77qHd4x4= github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1/go.mod h1:Ohn+xnUBiLI6FVj/9LpzZWtj1/D6lUovWYBkxHVV3aM= +golang.org/x/crypto v0.29.0 h1:L5SG1JTTXupVV3n6sUqMTeWbjAyfPwoda2DLX8J8FrQ= +golang.org/x/crypto v0.29.0/go.mod h1:+F4F4N5hv6v38hfeYwTdx20oUvLLc+QfrE9Ax9HtgRg= +golang.org/x/exp v0.0.0-20241108190413-2d47ceb2692f h1:XdNn9LlyWAhLVp6P/i8QYBW+hlyhrhei9uErw2B5GJo= +golang.org/x/exp v0.0.0-20241108190413-2d47ceb2692f/go.mod h1:D5SMRVC3C2/4+F/DB1wZsLRnSNimn2Sp/NPsCrsv8ak= +golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4= +golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= +golang.org/x/sync v0.9.0 h1:fEo0HyrW1GIgZdpbhCRO0PkJajUS5H9IFUztCgEo2jQ= +golang.org/x/sync v0.9.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s= golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/tools v0.27.0 h1:qEKojBykQkQ4EynWy4S8Weg69NumxKdn40Fce3uc/8o= +golang.org/x/tools v0.27.0/go.mod h1:sUi0ZgbwW9ZPAq26Ekut+weQPR5eIM6GQLQ1Yjm1H0Q= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +mellium.im/sasl v0.3.2 h1:PT6Xp7ccn9XaXAnJ03FcEjmAn7kK1x7aoXV6F+Vmrl0= +mellium.im/sasl v0.3.2/go.mod h1:NKXDi1zkr+BlMHLQjY3ofYuU4KSPFxknb8mfEu6SveY= +modernc.org/cc/v4 v4.23.1 h1:WqJoPL3x4cUufQVHkXpXX7ThFJ1C4ik80i2eXEXbhD8= +modernc.org/cc/v4 v4.23.1/go.mod h1:HM7VJTZbUCR3rV8EYBi9wxnJ0ZBRiGE5OeGXNA0IsLQ= +modernc.org/ccgo/v4 v4.22.3 h1:C7AW89Zw3kygesTQWBzApwIn9ldM+cb/plrTIKq41Os= +modernc.org/ccgo/v4 v4.22.3/go.mod h1:Dz7n0/UkBbH3pnYaxgi1mFSfF4REqUOZNziphZASx6k= +modernc.org/fileutil v1.3.0 h1:gQ5SIzK3H9kdfai/5x41oQiKValumqNTDXMvKo62HvE= +modernc.org/fileutil v1.3.0/go.mod h1:XatxS8fZi3pS8/hKG2GH/ArUogfxjpEKs3Ku3aK4JyQ= +modernc.org/gc/v2 v2.5.0 h1:bJ9ChznK1L1mUtAQtxi0wi5AtAs5jQuw4PrPHO5pb6M= +modernc.org/gc/v2 v2.5.0/go.mod h1:wzN5dK1AzVGoH6XOzc3YZ+ey/jPgYHLuVckd62P0GYU= +modernc.org/gc/v3 v3.0.0-20241004144649-1aea3fae8852 h1:IYXPPTTjjoSHvUClZIYexDiO7g+4x+XveKT4gCIAwiY= +modernc.org/gc/v3 v3.0.0-20241004144649-1aea3fae8852/go.mod h1:Qz0X07sNOR1jWYCrJMEnbW/X55x206Q7Vt4mz6/wHp4= +modernc.org/libc v1.61.2 h1:dkO4DlowfClcJYsvf/RiK6fUwvzCQTmB34bJLt0CAGQ= +modernc.org/libc v1.61.2/go.mod h1:4QGjNyX3h+rn7V5oHpJY2yH0QN6frt1X+5BkXzwLPCo= +modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4= +modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo= +modernc.org/memory v1.8.0 h1:IqGTL6eFMaDZZhEWwcREgeMXYwmW83LYW8cROZYkg+E= +modernc.org/memory v1.8.0/go.mod h1:XPZ936zp5OMKGWPqbD3JShgd/ZoQ7899TUuQqxY+peU= +modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4= +modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0= +modernc.org/sortutil v1.2.0 h1:jQiD3PfS2REGJNzNCMMaLSp/wdMNieTbKX920Cqdgqc= +modernc.org/sortutil v1.2.0/go.mod h1:TKU2s7kJMf1AE84OoiGppNHJwvB753OYfNl2WRb++Ss= +modernc.org/sqlite v1.34.1 h1:u3Yi6M0N8t9yKRDwhXcyp1eS5/ErhPTBggxWFuR6Hfk= +modernc.org/sqlite v1.34.1/go.mod h1:pXV2xHxhzXZsgT/RtTFAPY6JJDEvOTcTdwADQCCWD4k= +modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA= +modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0= +modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= +modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= From 7852c103bdbd556b5babae95da119f3380a42dea Mon Sep 17 00:00:00 2001 From: dyma solovei Date: Wed, 27 Nov 2024 09:59:26 +0100 Subject: [PATCH 12/17] chore: go mod tidy pgdriver and pgdialect --- dialect/pgdialect/go.mod | 2 ++ dialect/pgdialect/go.sum | 2 ++ driver/pgdriver/go.mod | 4 +++- driver/pgdriver/go.sum | 2 +- 4 files changed, 8 insertions(+), 2 deletions(-) diff --git a/dialect/pgdialect/go.mod b/dialect/pgdialect/go.mod index 1b2412aed..682f739b3 100644 --- a/dialect/pgdialect/go.mod +++ b/dialect/pgdialect/go.mod @@ -13,11 +13,13 @@ require ( github.com/davecgh/go-spew v1.1.1 // indirect github.com/jinzhu/inflection v1.0.0 // indirect github.com/kr/text v0.2.0 // indirect + github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/puzpuzpuz/xsync/v3 v3.4.0 // indirect github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc // indirect github.com/vmihailenco/msgpack/v5 v5.4.1 // indirect github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect golang.org/x/sys v0.27.0 // indirect + gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/dialect/pgdialect/go.sum b/dialect/pgdialect/go.sum index 207443654..fa488033a 100644 --- a/dialect/pgdialect/go.sum +++ b/dialect/pgdialect/go.sum @@ -4,6 +4,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= diff --git a/driver/pgdriver/go.mod b/driver/pgdriver/go.mod index 1c836c726..1ba83f5ff 100644 --- a/driver/pgdriver/go.mod +++ b/driver/pgdriver/go.mod @@ -13,7 +13,8 @@ require ( require ( github.com/davecgh/go-spew v1.1.1 // indirect github.com/jinzhu/inflection v1.0.0 // indirect - github.com/kr/text v0.1.0 // indirect + github.com/kr/text v0.2.0 // indirect + github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/puzpuzpuz/xsync/v3 v3.4.0 // indirect github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc // indirect @@ -21,5 +22,6 @@ require ( github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect golang.org/x/crypto v0.29.0 // indirect golang.org/x/sys v0.27.0 // indirect + gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/driver/pgdriver/go.sum b/driver/pgdriver/go.sum index e545dc75d..9df3772e2 100644 --- a/driver/pgdriver/go.sum +++ b/driver/pgdriver/go.sum @@ -4,8 +4,8 @@ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= From ece68d48948a4cee40a3534cc49f836528b2299d Mon Sep 17 00:00:00 2001 From: dyma solovei Date: Wed, 27 Nov 2024 10:03:59 +0100 Subject: [PATCH 13/17] refactor: expose different commands from standalone / embeded CLI The bootstrapped CLI which will be compiled along with the rest of the user migration files does not need the Init command. On the other hand, the standalone bundb command will provide that and exclude all other commands to avoid confusion between them. --- cmd/bundb/main.go | 3 +-- extra/buncli/buncli.go | 20 +++++++++++++++++++- extra/buncli/go.mod | 2 +- extra/buncli/init.go | 4 +++- 4 files changed, 24 insertions(+), 5 deletions(-) diff --git a/cmd/bundb/main.go b/cmd/bundb/main.go index 0e424d42c..227c3f374 100644 --- a/cmd/bundb/main.go +++ b/cmd/bundb/main.go @@ -33,8 +33,7 @@ import ( func main() { log.SetPrefix("bundb: ") - // TODO: use buncli.New(buncli.FromPlugin()) to read config from plugin - if err := buncli.Run(os.Args, nil); err != nil { + if err := buncli.NewStandalone("bundb").Run(os.Args); err != nil { log.Fatal(err) } } diff --git a/extra/buncli/buncli.go b/extra/buncli/buncli.go index bd67ee61b..6d148516b 100644 --- a/extra/buncli/buncli.go +++ b/extra/buncli/buncli.go @@ -24,8 +24,10 @@ var bunApp = &cli.App{ // New creates a new CLI application for managing bun migrations. func New(c *Config) *App { + if c.RootName != "" { + bunApp.Name = c.RootName + } bunApp.Commands = cli.Commands{ - CmdInit(), CmdMigrate(c), CmdRollback(c), CmdCreate(c), @@ -37,7 +39,23 @@ func New(c *Config) *App { } } +// NewStandalone create a new CLI application to be distributed as a standalone binary. +// It's intended to be used in the cmb/bund and does not require any prior setup from the user: +// the app only includes the Init command and reads all its configuration from command line. +// +// Prefer using New(*Config) in your custom entrypoint. +func NewStandalone(name string) *App { + bunApp.Name = name + bunApp.Commands = cli.Commands{ + CmdInit(), + } + return &App{ + App: bunApp, + } +} + type Config struct { + RootName string DB *bun.DB AutoMigrator *migrate.AutoMigrator Migrations *migrate.Migrations diff --git a/extra/buncli/go.mod b/extra/buncli/go.mod index 0cc2ef1ac..900ae1e89 100644 --- a/extra/buncli/go.mod +++ b/extra/buncli/go.mod @@ -12,6 +12,7 @@ require ( github.com/uptrace/bun/driver/pgdriver v1.2.7-0.20241126124946-928d0779110e github.com/uptrace/bun/driver/sqliteshim v1.2.7-0.20241126124946-928d0779110e github.com/urfave/cli/v2 v2.27.5 + golang.org/x/mod v0.22.0 ) require ( @@ -32,7 +33,6 @@ require ( github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // indirect golang.org/x/crypto v0.29.0 // indirect golang.org/x/exp v0.0.0-20241108190413-2d47ceb2692f // indirect - golang.org/x/mod v0.22.0 // indirect golang.org/x/sys v0.27.0 // indirect mellium.im/sasl v0.3.2 // indirect modernc.org/gc/v3 v3.0.0-20241004144649-1aea3fae8852 // indirect diff --git a/extra/buncli/init.go b/extra/buncli/init.go index 079647639..d3c36bfce 100644 --- a/extra/buncli/init.go +++ b/extra/buncli/init.go @@ -127,6 +127,7 @@ func main() { var _ /* auto */ migrate.AutoMigrator if err := buncli.Run(os.Args, &buncli.Config{ + RootName: %q, // DB: db, // AutoMigrator: auto, Migrations: migrations.Migrations, @@ -160,7 +161,7 @@ func initCmd(binDir string, migrationsDir string) error { pkgMigrations := path.Join(modPath, strings.TrimLeft(migrationsDir, ".")) log.Print("pkgMigrations: ", pkgMigrations) - if _, err := fmt.Fprintf(f, entrypointTemplate, pkgMigrations); err != nil { + if _, err := fmt.Fprintf(f, entrypointTemplate, pkgMigrations, defaultBin); err != nil { log.Print("here!") return err } @@ -259,6 +260,7 @@ func newDB(ctx *cli.Context) (*bun.DB, error) { return bun.NewDB(sqlDB, dialect), nil } +// getModPath parses the ./go.mod file in the current directory and returns the declared module path. func getModPath() (string, error) { f, err := os.ReadFile("go.mod") if err != nil { From 98e578b417121fcffaa5fea52e72e1d958c26d2f Mon Sep 17 00:00:00 2001 From: dyma solovei Date: Wed, 27 Nov 2024 11:59:07 +0100 Subject: [PATCH 14/17] feat: bun init in plugin mode --- cmd/bundb/main.go | 7 -- extra/buncli/buncli.go | 2 +- extra/buncli/init.go | 216 +++++++++++++++++++++++++---------------- 3 files changed, 133 insertions(+), 92 deletions(-) diff --git a/cmd/bundb/main.go b/cmd/bundb/main.go index 227c3f374..07337ce0f 100644 --- a/cmd/bundb/main.go +++ b/cmd/bundb/main.go @@ -14,13 +14,6 @@ i.e. same_thing.up.tx.sql should overwrite same_thing.up.sql. - Store configured options to env variables? E.g. after 'bundb init --create-directory=db-migrations/' set BUNDB_MIGRATIONS=db-migrations, so that subsequent commands can be run without additional parameters. Although... this way we are moving towards a .bundb.config or something. - -- Allow defining components in the plugin, rather than passing config for them. Specifically: - 1. func DB() *bun.DB to return a database connection - Handy in avoiding having to provide options for all the dialect-specific options here + potentially - let's users re-use their existing "ConnectToDB" function. - 2. func AutoMigrator() *migrate.AutoMigrator to return a pre-configured AutoMigrator. - 3. ??? */ package main diff --git a/extra/buncli/buncli.go b/extra/buncli/buncli.go index 6d148516b..eb970ac84 100644 --- a/extra/buncli/buncli.go +++ b/extra/buncli/buncli.go @@ -68,7 +68,7 @@ func Run(args []string, c *Config) error { return New(c).Run(args) } -// RunCtx calls cli.App.RunContexta and returns its error. +// RunContext calls cli.App.RunContext and returns its error. func RunContext(ctx context.Context, args []string, c *Config) error { return New(c).RunContext(ctx, args) } diff --git a/extra/buncli/init.go b/extra/buncli/init.go index d3c36bfce..2874c476c 100644 --- a/extra/buncli/init.go +++ b/extra/buncli/init.go @@ -3,7 +3,6 @@ package buncli import ( "database/sql" "fmt" - "log" "os" "path" "strings" @@ -31,7 +30,9 @@ func CmdInit() *cli.Command { Flags: []cli.Flag{ flagDSN, flagDriver, - flagNoCmd, + flagBinary, + flagMigrations, + flagPluginMode, }, Action: func(ctx *cli.Context) error { c, err := fromCLI(ctx) @@ -44,9 +45,9 @@ func CmdInit() *cli.Command { } const ( - defaultLoc = "." - defaultBin = "bun" - defaultMigrationsDir = "migrations" + maingo = "main.go" + defaultBin = "bun" + defaultMigrations = "migrations" ) var ( @@ -65,10 +66,24 @@ var ( Usage: "database driver", } - flagNoCmd = &cli.BoolFlag{ - Name: "no-cmd", - Usage: "don't create a CLI entrypoint in cmd/ directory", - Value: false, + flagPluginMode = &cli.BoolFlag{ + Name: "p", + Aliases: []string{"plugin"}, + Usage: "create a 'main' package to be used as a plugin", + } + + flagBinary = &cli.StringFlag{ + Name: "b", + Aliases: []string{"binary"}, + Usage: "name of the cmd/ binary", + Value: defaultBin, + } + + flagMigrations = &cli.StringFlag{ + Name: "m", + Aliases: []string{"migrations-package"}, + Usage: "name of the migrations package", + Value: defaultMigrations, } ) @@ -79,32 +94,37 @@ func runInit(ctx *cli.Context, c *Config) error { } loc := ctx.Args().Get(0) - migrationsDir := loc + binName := flagBinary.Get(ctx) + migrationsDir := flagMigrations.Get(ctx) - if loc == "" { - loc = defaultLoc - } - - if loc == defaultLoc { - migrationsDir = defaultMigrationsDir + var b interface{ Bootstrap() error } + switch { + default: + b = &normalMode{Loc: loc, Binary: binName, Migrations: migrationsDir} + case flagPluginMode.Get(ctx): + b = &pluginMode{Loc: loc, Migrations: migrationsDir} } - log.Print("loc-0: " + loc) - loc = path.Join(loc, "cmd", defaultBin) - log.Print("loc: " + loc) + return b.Bootstrap() +} - if withCmd := !flagNoCmd.Get(ctx); withCmd { - migrationsDir = path.Join(loc, migrationsDir) - if err := initCmd(loc, migrationsDir); err != nil { - return err - } +// fromCLI creates minimal Config from command line arguments. +// It is inteded to be used exclusively by Init command, as it creates +// the default project structure and the user has no other way of configuring buncli. +// +// DB and Migrations are the only valid fields in the created config, other objects are nil. +func fromCLI(ctx *cli.Context) (*Config, error) { + db, err := newDB(ctx) + if err != nil { + return nil, err } - log.Print("migrationsDir: " + migrationsDir) + return &Config{DB: db, Migrations: migrate.NewMigrations()}, nil +} - if err := initMigrationsPackage(migrationsDir); err != nil { - return err - } - return nil +type normalMode struct { + Loc string + Binary string + Migrations string } const entrypointTemplate = `package main @@ -126,68 +146,123 @@ func main() { // TODO: configure AutoMigrator var _ /* auto */ migrate.AutoMigrator - if err := buncli.Run(os.Args, &buncli.Config{ + cfg := buncli.Config{ RootName: %q, // DB: db, // AutoMigrator: auto, Migrations: migrations.Migrations, - }); err != nil { + } + + if err := buncli.Run(os.Args, &cfg); err != nil { panic(err) } } ` -func initCmd(binDir string, migrationsDir string) error { - if err := os.MkdirAll(binDir, 0755); err != nil { - return err +var migrationsTemplate = `package migrations + +import "github.com/uptrace/bun/migrate" + +var Migrations = migrate.NewMigrations() + +func init() { + if err := Migrations.DiscoverCaller(); err != nil { + panic(err) } +} +` - log.Print("binDir: ", binDir) - f, err := os.OpenFile(path.Join(binDir, "main.go"), os.O_CREATE|os.O_EXCL|os.O_WRONLY, 0666) +func (n *normalMode) Bootstrap() error { + // Create cmd/bun/main.go entrypoint + binDir := path.Join(n.Loc, "cmd", n.Binary) + modPath, err := n.pkgMigrations(binDir) if err != nil { - if os.IsExist(err) { - // TODO: log the fact that we haven't modified an existing main.go - return nil - } return err } - defer f.Close() - - modPath, err := getModPath() - if err != nil { + if err := writef(binDir, maingo, entrypointTemplate, modPath, n.Binary); err != nil { return err } - log.Print("go.mod path: ", modPath) - pkgMigrations := path.Join(modPath, strings.TrimLeft(migrationsDir, ".")) - log.Print("pkgMigrations: ", pkgMigrations) - if _, err := fmt.Fprintf(f, entrypointTemplate, pkgMigrations, defaultBin); err != nil { - log.Print("here!") + // Create migrations/main.go template + migrationsDir := path.Join(binDir, n.Migrations) + if err := writef(migrationsDir, maingo, migrationsTemplate); err != nil { return err } - return nil } -var migrationsTemplate = `package migrations +func (n *normalMode) pkgMigrations(binDir string) (string, error) { + modPath, err := getModPath() + if err != nil { + return "", err + } + return path.Join(modPath, strings.TrimLeft(binDir, "."), n.Migrations), nil +} -import "github.com/uptrace/bun/migrate" +type pluginMode struct { + Loc string + Migrations string +} -var Migrations = migrate.NewMigrations() +var pluginTemplate = `package main + +import ( + "github.com/uptrace/bun" + "github.com/uptrace/bun/extra/buncli" + "github.com/uptrace/bun/migrate" +) + +var Config *buncli.Config func init() { - if err := Migrations.DiscoverCaller(); err != nil { + migrations := migrate.NewMigrations() + if err := migrations.DiscoverCaller(); err != nil { panic(err) } + + // TODO: connect to db + var _ /* db */ *bun.DB + + // TODO: configure AutoMigrator + var _ /* auto */ migrate.AutoMigrator + + Config = &buncli.Config{ + // DB: db, + // AutoMigrator: auto, + Migrations: migrations, + } } ` -func initMigrationsPackage(dir string) error { +func (p *pluginMode) Bootstrap() error { + binDir := path.Join(p.Loc, p.Migrations) + if err := writef(binDir, maingo, pluginTemplate); err != nil { + return err + } + return nil +} + +// getModPath parses the ./go.mod file in the current directory and returns the declared module path. +func getModPath() (string, error) { + f, err := os.ReadFile("go.mod") + if err != nil { + return "", err + } + + gomod, err := modfile.Parse("go.mod", f, nil) + if err != nil { + return "", err + } + return gomod.Module.Mod.Path, nil +} + +// TODO: document +func writef(dir string, file string, format string, args ...interface{}) error { if err := os.MkdirAll(dir, 0755); err != nil { return err } - f, err := os.OpenFile(path.Join(dir, "main.go"), os.O_CREATE|os.O_EXCL|os.O_WRONLY, 0666) + f, err := os.OpenFile(path.Join(dir, file), os.O_CREATE|os.O_EXCL|os.O_WRONLY, 0666) if err != nil { if os.IsExist(err) { // TODO: log the fact that we haven't modified an existing main.go @@ -197,25 +272,12 @@ func initMigrationsPackage(dir string) error { } defer f.Close() - if _, err := fmt.Fprint(f, migrationsTemplate); err != nil { + if _, err := fmt.Fprintf(f, format, args...); err != nil { return err } return nil } -// fromCLI creates minimal Config from command line arguments. -// It is inteded to be used exclusively by Init command, as it creates -// the default project structure and the user has no other way of configuring buncli. -// -// DB and Migrations are the only valid fields in the created config, other objects are nil. -func fromCLI(ctx *cli.Context) (*Config, error) { - db, err := newDB(ctx) - if err != nil { - return nil, err - } - return &Config{DB: db, Migrations: migrate.NewMigrations()}, nil -} - // newDB connects to the database specified by the DSN. // It will attempt to guess the driver from the connection string prefix, unless it is passed explicitly. func newDB(ctx *cli.Context) (*bun.DB, error) { @@ -259,17 +321,3 @@ func newDB(ctx *cli.Context) (*bun.DB, error) { return bun.NewDB(sqlDB, dialect), nil } - -// getModPath parses the ./go.mod file in the current directory and returns the declared module path. -func getModPath() (string, error) { - f, err := os.ReadFile("go.mod") - if err != nil { - return "", err - } - - gomod, err := modfile.Parse("go.mod", f, nil) - if err != nil { - return "", err - } - return gomod.Module.Mod.Path, nil -} From 44c44fc0c8ec37e9718b0ed5c1ff86a7b1304adb Mon Sep 17 00:00:00 2001 From: dyma solovei Date: Wed, 27 Nov 2024 14:34:30 +0100 Subject: [PATCH 15/17] fix: go mod tidy example/migrate --- example/migrate/go.mod | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/example/migrate/go.mod b/example/migrate/go.mod index d70e7fe5d..c49cdb9a0 100644 --- a/example/migrate/go.mod +++ b/example/migrate/go.mod @@ -12,8 +12,8 @@ replace github.com/uptrace/bun/driver/sqliteshim => ../../driver/sqliteshim require ( github.com/uptrace/bun v1.2.6 - github.com/uptrace/bun/dialect/sqlitedialect v1.2.6 - github.com/uptrace/bun/driver/sqliteshim v1.2.6 + github.com/uptrace/bun/dialect/sqlitedialect v1.2.7-0.20241126124946-928d0779110e + github.com/uptrace/bun/driver/sqliteshim v1.2.7-0.20241126124946-928d0779110e github.com/uptrace/bun/extra/bundebug v1.2.6 github.com/urfave/cli/v2 v2.27.5 ) From 792c96ff799e83ecbfbb079502b06d711cacfab4 Mon Sep 17 00:00:00 2001 From: dyma solovei Date: Wed, 27 Nov 2024 15:38:50 +0100 Subject: [PATCH 16/17] feat: add overriden MigratorOptions to the template file on init --- extra/buncli/buncli.go | 1 + extra/buncli/init.go | 119 +++++++++++++++++++++++++++++++-------- extra/buncli/migrator.go | 8 +-- migrate/auto.go | 4 +- migrate/migrator.go | 8 +-- 5 files changed, 105 insertions(+), 35 deletions(-) diff --git a/extra/buncli/buncli.go b/extra/buncli/buncli.go index eb970ac84..06578cde7 100644 --- a/extra/buncli/buncli.go +++ b/extra/buncli/buncli.go @@ -59,6 +59,7 @@ type Config struct { DB *bun.DB AutoMigrator *migrate.AutoMigrator Migrations *migrate.Migrations + MigratorOptions []migrate.MigratorOption MigrateOptions []migrate.MigrationOption GoMigrationOptions []migrate.GoMigrationOption } diff --git a/extra/buncli/init.go b/extra/buncli/init.go index 2874c476c..2f92415e4 100644 --- a/extra/buncli/init.go +++ b/extra/buncli/init.go @@ -30,6 +30,8 @@ func CmdInit() *cli.Command { Flags: []cli.Flag{ flagDSN, flagDriver, + flagTable, + flagLocks, flagBinary, flagMigrations, flagPluginMode, @@ -63,32 +65,45 @@ var ( flagDriver = &cli.StringFlag{ Name: "driver", - Usage: "database driver", + Usage: strings.Join(supportedDrivers, ", "), } - flagPluginMode = &cli.BoolFlag{ - Name: "p", - Aliases: []string{"plugin"}, - Usage: "create a 'main' package to be used as a plugin", + flagTable = &cli.StringFlag{ + Name: "table", + Usage: "override migrations table name", + Value: migrate.DefaultTable, + } + + flagLocks = &cli.StringFlag{ + Name: "locks", + Usage: "override locks table name", + Value: migrate.DefaultLocksTable, } flagBinary = &cli.StringFlag{ Name: "b", Aliases: []string{"binary"}, - Usage: "name of the cmd/ binary", + Usage: "override cmd/ `ENTRYPOINT` name", Value: defaultBin, } flagMigrations = &cli.StringFlag{ Name: "m", - Aliases: []string{"migrations-package"}, - Usage: "name of the migrations package", + Aliases: []string{"migrations-directory"}, + Usage: "override migrations `DIR`", Value: defaultMigrations, } + + flagPluginMode = &cli.BoolFlag{ + Name: "P", + Aliases: []string{"plugin"}, + Usage: "create a 'main' package to be used as a plugin", + DisableDefaultText: true, + } ) func runInit(ctx *cli.Context, c *Config) error { - m := migrate.NewMigrator(c.DB, c.Migrations) + m := migrate.NewMigrator(c.DB, c.Migrations, c.MigratorOptions...) if err := m.Init(ctx.Context); err != nil { return err } @@ -96,13 +111,14 @@ func runInit(ctx *cli.Context, c *Config) error { loc := ctx.Args().Get(0) binName := flagBinary.Get(ctx) migrationsDir := flagMigrations.Get(ctx) + migratorOpts := stringNonDefaultMigratorOptions(ctx) var b interface{ Bootstrap() error } switch { default: - b = &normalMode{Loc: loc, Binary: binName, Migrations: migrationsDir} + b = &normalMode{Loc: loc, Binary: binName, Migrations: migrationsDir, MigratorOptions: migratorOpts} case flagPluginMode.Get(ctx): - b = &pluginMode{Loc: loc, Migrations: migrationsDir} + b = &pluginMode{Loc: loc, Migrations: migrationsDir, MigratorOptions: migratorOpts} } return b.Bootstrap() @@ -118,13 +134,26 @@ func fromCLI(ctx *cli.Context) (*Config, error) { if err != nil { return nil, err } - return &Config{DB: db, Migrations: migrate.NewMigrations()}, nil + return &Config{ + DB: db, + MigratorOptions: addNonDefaultMigratorOptions(ctx), + Migrations: migrate.NewMigrations(), + }, nil } +// normalMode creates the default migrations directory and a cmd/ entrypoint. +// +// . +// └── cmd +// └── bun +// ├── main.go +// └── migrations +// └── main.go type normalMode struct { - Loc string - Binary string - Migrations string + Loc string + Binary string + Migrations string + MigratorOptions []string } const entrypointTemplate = `package main @@ -146,14 +175,15 @@ func main() { // TODO: configure AutoMigrator var _ /* auto */ migrate.AutoMigrator - cfg := buncli.Config{ + cfg := &buncli.Config{ RootName: %q, // DB: db, // AutoMigrator: auto, - Migrations: migrations.Migrations, + Migrations: migrations.Migrations, + MigratorOptions: []migrate.MigratorOption{%s}, } - if err := buncli.Run(os.Args, &cfg); err != nil { + if err := buncli.Run(os.Args, cfg); err != nil { panic(err) } } @@ -175,11 +205,13 @@ func init() { func (n *normalMode) Bootstrap() error { // Create cmd/bun/main.go entrypoint binDir := path.Join(n.Loc, "cmd", n.Binary) - modPath, err := n.pkgMigrations(binDir) + modPath, err := n.migrationsImportPath(binDir) if err != nil { return err } - if err := writef(binDir, maingo, entrypointTemplate, modPath, n.Binary); err != nil { + + migratorOpts := strings.Join(n.MigratorOptions, ", ") + if err := writef(binDir, maingo, entrypointTemplate, modPath, n.Binary, migratorOpts); err != nil { return err } @@ -191,7 +223,7 @@ func (n *normalMode) Bootstrap() error { return nil } -func (n *normalMode) pkgMigrations(binDir string) (string, error) { +func (n *normalMode) migrationsImportPath(binDir string) (string, error) { modPath, err := getModPath() if err != nil { return "", err @@ -199,9 +231,15 @@ func (n *normalMode) pkgMigrations(binDir string) (string, error) { return path.Join(modPath, strings.TrimLeft(binDir, "."), n.Migrations), nil } +// pluginMode creates a layout of a project that can be compiled and imported as a plugin. +// +// . +// └── migrations +// └── main.go type pluginMode struct { - Loc string - Migrations string + Loc string + Migrations string + MigratorOptions []string } var pluginTemplate = `package main @@ -229,14 +267,16 @@ func init() { Config = &buncli.Config{ // DB: db, // AutoMigrator: auto, - Migrations: migrations, + Migrations: migrations, + MigratorOptions: []migrate.MigratorOption{%s}, } } ` func (p *pluginMode) Bootstrap() error { binDir := path.Join(p.Loc, p.Migrations) - if err := writef(binDir, maingo, pluginTemplate); err != nil { + migratorOpts := strings.Join(p.MigratorOptions, ", ") + if err := writef(binDir, maingo, pluginTemplate, migratorOpts); err != nil { return err } return nil @@ -321,3 +361,32 @@ func newDB(ctx *cli.Context) (*bun.DB, error) { return bun.NewDB(sqlDB, dialect), nil } + +// addNonDefaultMigratorOptions collects migrate.MigratorOption for every value overriden by a command-line flag. +func addNonDefaultMigratorOptions(ctx *cli.Context) []migrate.MigratorOption { + var opts []migrate.MigratorOption + + if t := flagTable.Get(ctx); ctx.IsSet(flagTable.Name) { + opts = append(opts, migrate.WithTableName(t)) + } + + if l := flagLocks.Get(ctx); ctx.IsSet(flagLocks.Name) { + opts = append(opts, migrate.WithLocksTableName(l)) + } + return opts +} + +// stringNonDefaultMigratorOptions is like addNonDefaultMigratorOptions, but stringifies options so they can be written to a file. +func stringNonDefaultMigratorOptions(ctx *cli.Context) []string { + var opts []string + + if t := flagTable.Get(ctx); ctx.IsSet(flagTable.Name) { + opts = append(opts, fmt.Sprintf("migrate.WithTableName(%q)", t)) + } + + if l := flagLocks.Get(ctx); ctx.IsSet(flagLocks.Name) { + opts = append(opts, fmt.Sprintf("migrate.WithLocksTableName(%q)", l)) + } + + return opts +} diff --git a/extra/buncli/migrator.go b/extra/buncli/migrator.go index c2450e8fe..8ef0ea051 100644 --- a/extra/buncli/migrator.go +++ b/extra/buncli/migrator.go @@ -17,7 +17,7 @@ func CmdMigrate(c *Config) *cli.Command { } func runMigrate(ctx *cli.Context, c *Config) error { - m := migrate.NewMigrator(c.DB, c.Migrations) + m := migrate.NewMigrator(c.DB, c.Migrations, c.MigratorOptions...) _, err := m.Migrate(ctx.Context, c.MigrateOptions...) return err } @@ -34,7 +34,7 @@ func CmdRollback(c *Config) *cli.Command { } func runRollback(ctx *cli.Context, c *Config) error { - m := migrate.NewMigrator(c.DB, c.Migrations) + m := migrate.NewMigrator(c.DB, c.Migrations, c.MigratorOptions...) _, err := m.Rollback(ctx.Context, c.MigrateOptions...) return err } @@ -90,7 +90,7 @@ var ( func runCreate(ctx *cli.Context, c *Config) error { var err error - m := migrate.NewMigrator(c.DB, c.Migrations) + m := migrate.NewMigrator(c.DB, c.Migrations, c.MigratorOptions...) name := ctx.Args().First() if createGo { @@ -118,6 +118,6 @@ func CmdUnlock(c *Config) *cli.Command { } func runUnlock(ctx *cli.Context, c *Config) error { - m := migrate.NewMigrator(c.DB, c.Migrations) + m := migrate.NewMigrator(c.DB, c.Migrations, c.MigratorOptions...) return m.Unlock(ctx.Context) } diff --git a/migrate/auto.go b/migrate/auto.go index 16804cd99..e56ca2a64 100644 --- a/migrate/auto.go +++ b/migrate/auto.go @@ -140,8 +140,8 @@ type AutoMigrator struct { func NewAutoMigrator(db *bun.DB, opts ...AutoMigratorOption) (*AutoMigrator, error) { am := &AutoMigrator{ db: db, - table: defaultTable, - locksTable: defaultLocksTable, + table: DefaultTable, + locksTable: DefaultLocksTable, schemaName: db.Dialect().DefaultSchema(), } diff --git a/migrate/migrator.go b/migrate/migrator.go index d5a72aec0..7466fbe27 100644 --- a/migrate/migrator.go +++ b/migrate/migrator.go @@ -13,8 +13,8 @@ import ( ) const ( - defaultTable = "bun_migrations" - defaultLocksTable = "bun_migration_locks" + DefaultTable = "bun_migrations" + DefaultLocksTable = "bun_migration_locks" ) type MigratorOption func(m *Migrator) @@ -59,8 +59,8 @@ func NewMigrator(db *bun.DB, migrations *Migrations, opts ...MigratorOption) *Mi ms: migrations.ms, - table: defaultTable, - locksTable: defaultLocksTable, + table: DefaultTable, + locksTable: DefaultLocksTable, } for _, opt := range opts { opt(m) From 09ccc3411a0840a08bd9b1eb8e4b050c5e645d96 Mon Sep 17 00:00:00 2001 From: dyma solovei Date: Wed, 27 Nov 2024 19:24:44 +0100 Subject: [PATCH 17/17] feat(bundb): provide experimental commands under bundb Experimental commans are the good old [auto, migrate, rollback, etc.] but adapted to import configuration from a pre-built plugin. Plugins are known to be unstable and only sometimes work correctly, so this feature is more of a proof of concept and we should probably add the option to disable it entirely with build tags (for standalone bundb). TODO:: bun init --plugin does not initialize a Go module in the migrations/ directory. Currently one would need to manually run go mod init and go mod tidy after bootstraping the project. --- extra/buncli/auto.go | 19 +++-- extra/buncli/buncli.go | 31 ++++++- extra/buncli/init.go | 24 ++++-- extra/buncli/migrator.go | 36 ++++---- extra/buncli/plugin.go | 176 +++++++++++++++++++++++++++++++++++++++ 5 files changed, 256 insertions(+), 30 deletions(-) create mode 100644 extra/buncli/plugin.go diff --git a/extra/buncli/auto.go b/extra/buncli/auto.go index 4fd4eb172..089d43431 100644 --- a/extra/buncli/auto.go +++ b/extra/buncli/auto.go @@ -1,11 +1,12 @@ package buncli import ( + "github.com/uptrace/bun/migrate" "github.com/urfave/cli/v2" ) // CmdAuto creates the auto command hierarchy. -func CmdAuto(c *Config) *cli.Command { +func CmdAuto(c AutoConfig) *cli.Command { return &cli.Command{ Name: "auto", Usage: "Manage database schema with AutoMigrator", @@ -31,17 +32,23 @@ func CmdAuto(c *Config) *cli.Command { } } -func runAutoMigrate(ctx *cli.Context, c *Config) error { - _, err := c.AutoMigrator.Migrate(ctx.Context, c.MigrateOptions...) +// AutoConfig provides configuration for commands related to auto migration. +type AutoConfig interface { + OptionsConfig + Auto() *migrate.AutoMigrator +} + +func runAutoMigrate(ctx *cli.Context, c AutoConfig) error { + _, err := c.Auto().Migrate(ctx.Context, c.GetMigrateOptions()...) return err } -func runAutoCreate(ctx *cli.Context, c *Config) error { +func runAutoCreate(ctx *cli.Context, c AutoConfig) error { var err error if flagTx.Get(ctx) { - _, err = c.AutoMigrator.CreateTxSQLMigrations(ctx.Context) + _, err = c.Auto().CreateTxSQLMigrations(ctx.Context) } else { - _, err = c.AutoMigrator.CreateSQLMigrations(ctx.Context) + _, err = c.Auto().CreateSQLMigrations(ctx.Context) } return err } diff --git a/extra/buncli/buncli.go b/extra/buncli/buncli.go index 06578cde7..c1333e3e9 100644 --- a/extra/buncli/buncli.go +++ b/extra/buncli/buncli.go @@ -18,8 +18,9 @@ import ( // bunApp is the root-level bundb app that all other commands attach to. var bunApp = &cli.App{ - Name: "bundb", - Usage: "Database migration tool for uptrace/bun", + Name: "bundb", + Usage: "Database migration tool for uptrace/bun", + Suggest: true, } // New creates a new CLI application for managing bun migrations. @@ -40,7 +41,7 @@ func New(c *Config) *App { } // NewStandalone create a new CLI application to be distributed as a standalone binary. -// It's intended to be used in the cmb/bund and does not require any prior setup from the user: +// It's intended to be used in the cmb/bundb and does not require any prior setup from the user: // the app only includes the Init command and reads all its configuration from command line. // // Prefer using New(*Config) in your custom entrypoint. @@ -49,6 +50,9 @@ func NewStandalone(name string) *App { bunApp.Commands = cli.Commands{ CmdInit(), } + + // NOTE: use `-tags experimental` to enable/disable this feature? + addCommandGroup(bunApp, "EXPERIMENTAL", pluginCommands()...) return &App{ App: bunApp, } @@ -78,3 +82,24 @@ func RunContext(ctx context.Context, args []string, c *Config) error { type App struct { *cli.App } + +var _ OptionsConfig = (*Config)(nil) +var _ MigratorConfig = (*Config)(nil) +var _ AutoConfig = (*Config)(nil) + +func (c *Config) NewMigrator() *migrate.Migrator { + return migrate.NewMigrator(c.DB, c.Migrations, c.MigratorOptions...) +} + +func (c *Config) Auto() *migrate.AutoMigrator { return c.AutoMigrator } +func (c *Config) GetMigratorOptions() []migrate.MigratorOption { return c.MigratorOptions } +func (c *Config) GetMigrateOptions() []migrate.MigrationOption { return c.MigrateOptions } +func (c *Config) GetGoMigrationOptions() []migrate.GoMigrationOption { return c.GoMigrationOptions } + +// addCommandGroup groups commands into one category. +func addCommandGroup(app *cli.App, group string, commands ...*cli.Command) { + for _, cmd := range commands { + cmd.Category = group + } + app.Commands = append(app.Commands, commands...) +} \ No newline at end of file diff --git a/extra/buncli/init.go b/extra/buncli/init.go index 2f92415e4..0d1df1733 100644 --- a/extra/buncli/init.go +++ b/extra/buncli/init.go @@ -46,6 +46,12 @@ func CmdInit() *cli.Command { } } +type OptionsConfig interface { + GetMigratorOptions() []migrate.MigratorOption + GetMigrateOptions() []migrate.MigrationOption + GetGoMigrationOptions() []migrate.GoMigrationOption +} + const ( maingo = "main.go" defaultBin = "bun" @@ -102,8 +108,8 @@ var ( } ) -func runInit(ctx *cli.Context, c *Config) error { - m := migrate.NewMigrator(c.DB, c.Migrations, c.MigratorOptions...) +func runInit(ctx *cli.Context, c MigratorConfig) error { + m := c.NewMigrator() if err := m.Init(ctx.Context); err != nil { return err } @@ -209,17 +215,19 @@ func (n *normalMode) Bootstrap() error { if err != nil { return err } - + migratorOpts := strings.Join(n.MigratorOptions, ", ") if err := writef(binDir, maingo, entrypointTemplate, modPath, n.Binary, migratorOpts); err != nil { return err } - + // Create migrations/main.go template migrationsDir := path.Join(binDir, n.Migrations) if err := writef(migrationsDir, maingo, migrationsTemplate); err != nil { return err } + + return nil } @@ -274,11 +282,15 @@ func init() { ` func (p *pluginMode) Bootstrap() error { - binDir := path.Join(p.Loc, p.Migrations) + migrationsDir := path.Join(p.Loc, p.Migrations) migratorOpts := strings.Join(p.MigratorOptions, ", ") - if err := writef(binDir, maingo, pluginTemplate, migratorOpts); err != nil { + if err := writef(migrationsDir, maingo, pluginTemplate, migratorOpts); err != nil { return err } + + // TODO: run go mod init in migrations/ directory so that it'd be a standalone package + // TODO: run go mod init in migrations/ directory + return nil } diff --git a/extra/buncli/migrator.go b/extra/buncli/migrator.go index 8ef0ea051..fc4355a4e 100644 --- a/extra/buncli/migrator.go +++ b/extra/buncli/migrator.go @@ -6,7 +6,7 @@ import ( ) // CmdMigrate creates migrate command. -func CmdMigrate(c *Config) *cli.Command { +func CmdMigrate(c MigratorConfig) *cli.Command { return &cli.Command{ Name: "migrate", Usage: "Apply database migrations", @@ -16,14 +16,20 @@ func CmdMigrate(c *Config) *cli.Command { } } -func runMigrate(ctx *cli.Context, c *Config) error { - m := migrate.NewMigrator(c.DB, c.Migrations, c.MigratorOptions...) - _, err := m.Migrate(ctx.Context, c.MigrateOptions...) +// MigratorConfig provides configuration related to conventional migrations. +type MigratorConfig interface { + OptionsConfig + NewMigrator() *migrate.Migrator +} + +func runMigrate(ctx *cli.Context, c MigratorConfig) error { + m := c.NewMigrator() + _, err := m.Migrate(ctx.Context, c.GetMigrateOptions()...) return err } // CmdRollback creates rollback command. -func CmdRollback(c *Config) *cli.Command { +func CmdRollback(c MigratorConfig) *cli.Command { return &cli.Command{ Name: "rollback", Usage: "Rollback the last migration group", @@ -33,14 +39,14 @@ func CmdRollback(c *Config) *cli.Command { } } -func runRollback(ctx *cli.Context, c *Config) error { - m := migrate.NewMigrator(c.DB, c.Migrations, c.MigratorOptions...) - _, err := m.Rollback(ctx.Context, c.MigrateOptions...) +func runRollback(ctx *cli.Context, c MigratorConfig) error { + m := c.NewMigrator() + _, err := m.Rollback(ctx.Context, c.GetMigrateOptions()...) return err } // CmdCreate creates create command. -func CmdCreate(c *Config) *cli.Command { +func CmdCreate(c MigratorConfig) *cli.Command { return &cli.Command{ Name: "create", Usage: "Create a new migration file template", @@ -88,13 +94,13 @@ var ( } ) -func runCreate(ctx *cli.Context, c *Config) error { +func runCreate(ctx *cli.Context, c MigratorConfig) error { var err error - m := migrate.NewMigrator(c.DB, c.Migrations, c.MigratorOptions...) + m := c.NewMigrator() name := ctx.Args().First() if createGo { - _, err = m.CreateGoMigration(ctx.Context, name, c.GoMigrationOptions...) + _, err = m.CreateGoMigration(ctx.Context, name, c.GetGoMigrationOptions()...) return err } @@ -107,7 +113,7 @@ func runCreate(ctx *cli.Context, c *Config) error { } // CmdUnlock creates an unlock command. -func CmdUnlock(c *Config) *cli.Command { +func CmdUnlock(c MigratorConfig) *cli.Command { return &cli.Command{ Name: "unlock", Usage: "Unlock migration locks table", @@ -117,7 +123,7 @@ func CmdUnlock(c *Config) *cli.Command { } } -func runUnlock(ctx *cli.Context, c *Config) error { - m := migrate.NewMigrator(c.DB, c.Migrations, c.MigratorOptions...) +func runUnlock(ctx *cli.Context, c MigratorConfig) error { + m := c.NewMigrator() return m.Unlock(ctx.Context) } diff --git a/extra/buncli/plugin.go b/extra/buncli/plugin.go new file mode 100644 index 000000000..2ff370dcc --- /dev/null +++ b/extra/buncli/plugin.go @@ -0,0 +1,176 @@ +package buncli + +import ( + "bytes" + "fmt" + "os/exec" + "path" + "plugin" + "sync" + + "github.com/uptrace/bun/migrate" + "github.com/urfave/cli/v2" +) + +const ( + pluginName = "plugin.so" + configLookupName = "Config" +) + +var ( + buildOnce = sync.OnceValue(buildPlugin) + importOnce = sync.OnceValues(importConfig) + + pluginPath string + + flagPluginPath = &cli.StringFlag{ + Name: "m", + Usage: "relative `PATH` to migrations directory", + Value: "./" + defaultMigrations, + Destination: &pluginPath, + } +) + +func pluginCommands() cli.Commands { + c := fromPlugin() + + auto := CmdAuto(c) + skipAuto := func(c *cli.Command) bool { + return c == auto + } + return extendCommands(cli.Commands{ + auto, + CmdMigrate(c), + CmdRollback(c), + CmdCreate(c), + CmdUnlock(c), + }, skipAuto, withBefore(checkCanImportPlugin), withFlag(flagPluginPath)) +} + +func fromPlugin() *pluginConfig { + return &pluginConfig{ + config: func() *Config { + c, _ := importOnce() + return c + }, + } +} + +// checkCanImportPlugin returns an error if migrations/ plugin build failed or *buncli.Config could not be imported from it. +func checkCanImportPlugin(ctx *cli.Context) error { + if isHelp(ctx) { + // Do not build plugin if on -help. + return nil + } + _, err := importOnce() + return err +} + +func isHelp(ctx *cli.Context) bool { + help := cli.HelpFlag.(*cli.BoolFlag) + return ctx.Command.Name == bunApp.HelpName || help.Get(ctx) +} + +// importConfig builds migrations/ plugin and imports Config from it. +// Config must be exported and of type *buncli.Config. +func importConfig() (*Config, error) { + if err := buildOnce(); err != nil { + return nil, err + } + + p, err := plugin.Open(path.Join(pluginPath, pluginName)) + if err != nil { + return nil, err + } + sym, err := p.Lookup(configLookupName) + if err != nil { + return nil, err + } + cfg, ok := sym.(**Config) + if !ok { + return nil, fmt.Errorf("migrations plugin must export Config as *buncli.Config, got %T", sym) + } + return *cfg, nil +} + +type pluginConfig struct { + config func() *Config +} + +var _ OptionsConfig = (*pluginConfig)(nil) +var _ MigratorConfig = (*pluginConfig)(nil) +var _ AutoConfig = (*pluginConfig)(nil) + +func (p *pluginConfig) NewMigrator() *migrate.Migrator { + return p.config().NewMigrator() +} + +func (p *pluginConfig) Auto() *migrate.AutoMigrator { + return p.config().Auto() +} + +func (p *pluginConfig) GetMigratorOptions() []migrate.MigratorOption { + return p.config().GetMigratorOptions() +} + +func (p *pluginConfig) GetMigrateOptions() []migrate.MigrationOption { + return p.config().GetMigrateOptions() +} + +func (p *pluginConfig) GetGoMigrationOptions() []migrate.GoMigrationOption { + return p.config().GetGoMigrationOptions() +} + +// buildPlugin compiles migrations/ plugin with -buildmode=plugin. +func buildPlugin() error { + cmd := exec.Command("go", "build", "-C", pluginPath, "-buildmode", "plugin", "-o", pluginName) + + // Cmd.Run returns *exec.ExitError which will only contain the exit code message in case of an error. + // Rather than logging "exit code 1" we want to output a more informative error, so we redirect the Stderr. + var errBuf bytes.Buffer + cmd.Stderr = &errBuf + + err := cmd.Run() + if err != nil { + return fmt.Errorf("build %s: %s", path.Join(pluginPath, pluginName), &errBuf) + } + return nil +} + +type commandOption func(*cli.Command) + +func extendCommands(commands cli.Commands, skip func(c *cli.Command) bool, options ...commandOption) cli.Commands { + var all cli.Commands + var flatten func(cmds cli.Commands) + + flatten = func(cmds cli.Commands) { + for _, cmd := range cmds { + if skip != nil && !skip(cmd) { + all = append(all, cmd) + } + flatten(cmd.Subcommands) + } + } + flatten(commands) + + for _, cmd := range all { + for _, opt := range options { + opt(cmd) + } + } + return commands +} + +// withFlag adds a flag to command. +func withFlag(f cli.Flag) commandOption { + return func(c *cli.Command) { + c.Flags = append(c.Flags, f) + } +} + +// withBefore adds BeforeFunc to command. +func withBefore(bf cli.BeforeFunc) commandOption { + return func(c *cli.Command) { + c.Before = bf + } +}