@@ -25,366 +25,15 @@ Although... this way we are moving towards a .bundb.config or something.
25
25
package main
26
26
27
27
import (
28
- "bytes"
29
- "database/sql"
30
- "fmt"
31
28
"log"
32
29
"os"
33
- "os/exec"
34
- "path"
35
- "plugin"
36
- "strings"
37
30
38
- "github.com/uptrace/bun"
39
- "github.com/uptrace/bun/dialect/mssqldialect"
40
- "github.com/uptrace/bun/dialect/mysqldialect"
41
- "github.com/uptrace/bun/dialect/oracledialect"
42
- "github.com/uptrace/bun/dialect/pgdialect"
43
- "github.com/uptrace/bun/dialect/sqlitedialect"
44
- "github.com/uptrace/bun/driver/pgdriver"
45
- "github.com/uptrace/bun/driver/sqliteshim"
46
- "github.com/uptrace/bun/migrate"
47
- "github.com/uptrace/bun/schema"
48
- "github.com/urfave/cli/v2"
31
+ "github.com/uptrace/bun/extra/buncli"
49
32
)
50
33
51
- const (
52
- defaultMigrationsDirectory = "./migrations"
53
- pluginName = "plugin.so"
54
- )
55
-
56
- var (
57
- supportedDrivers = []string {"postgres" , "sqlserver" , "mysql" , "oci8" , "file" }
58
- autoMigratorOptions []migrate.AutoMigratorOption
59
- migrationsDirectory string
60
- )
61
-
62
- var (
63
- cleanup = & cli.BoolFlag {
64
- Name : "cleanup" ,
65
- }
66
- )
67
-
68
- var app = & cli.App {
69
- Name : "bundb" ,
70
- Usage : "Database migration tool for uptrace/bun" ,
71
- Commands : cli.Commands {
72
- // bundb init --create-directory
73
- // bundb create --sql --go --tx [-d | --dir]
74
- // bundb migrate
75
- // bundb auto create --tx
76
- // bundb auto migrate
77
- & cli.Command {
78
- Name : "auto" ,
79
- Usage : "manage database schema with AutoMigrator" ,
80
- Subcommands : cli.Commands {
81
- & cli.Command {
82
- Name : "create" ,
83
- Usage : "Generate SQL migration files" ,
84
- Flags : []cli.Flag {
85
- & cli.StringFlag {
86
- Name : "uri" ,
87
- Aliases : []string {"database-uri" , "dsn" },
88
- Required : true ,
89
- EnvVars : []string {"BUNDB_URI" },
90
- },
91
- & cli.StringFlag {
92
- Name : "driver" ,
93
- },
94
- & cli.StringFlag {
95
- Name : "d" ,
96
- Aliases : []string {"migrations-directory" },
97
- Destination : & migrationsDirectory ,
98
- Value : defaultMigrationsDirectory ,
99
- Action : func (ctx * cli.Context , dir string ) error {
100
- autoMigratorOptions = append (autoMigratorOptions , migrate .WithMigrationsDirectoryAuto (dir ))
101
- return nil
102
- },
103
- },
104
- & cli.StringFlag {
105
- Name : "t" ,
106
- Aliases : []string {"migrations-table" },
107
- Action : func (ctx * cli.Context , migrationsTable string ) error {
108
- autoMigratorOptions = append (autoMigratorOptions , migrate .WithTableNameAuto (migrationsTable ))
109
- return nil
110
- },
111
- },
112
- & cli.StringFlag {
113
- Name : "l" ,
114
- Aliases : []string {"locks" , "migration-locks-table" },
115
- Action : func (ctx * cli.Context , locksTable string ) error {
116
- autoMigratorOptions = append (autoMigratorOptions , migrate .WithLocksTableNameAuto (locksTable ))
117
- return nil
118
- },
119
- },
120
- & cli.StringFlag {
121
- Name : "s" ,
122
- Aliases : []string {"schema" },
123
- Action : func (ctx * cli.Context , schemaName string ) error {
124
- autoMigratorOptions = append (autoMigratorOptions , migrate .WithSchemaName (schemaName ))
125
- return nil
126
- },
127
- },
128
- & cli.StringSliceFlag {
129
- Name : "exclude" ,
130
- Action : func (ctx * cli.Context , tables []string ) error {
131
- autoMigratorOptions = append (autoMigratorOptions , migrate .WithExcludeTable (tables ... ))
132
- return nil
133
- },
134
- },
135
- & cli.BoolFlag {
136
- Name : "rebuild" ,
137
- },
138
- cleanup ,
139
- & cli.BoolFlag {
140
- Name : "tx" ,
141
- Aliases : []string {"transactional" },
142
- },
143
- },
144
- Action : func (ctx * cli.Context ) error {
145
- if err := buildPlugin (ctx .Bool ("rebuild" )); err != nil {
146
- return err
147
- }
148
-
149
- if cleanup .Get (ctx ) {
150
- defer deletePlugin ()
151
- }
152
-
153
- db , err := connect (ctx .String ("uri" ), ctx .String ("driver" ), ! ctx .IsSet ("driver" ))
154
- if err != nil {
155
- return err
156
-
157
- }
158
-
159
- if ! ctx .IsSet ("migrations-directory" ) {
160
- autoMigratorOptions = append (autoMigratorOptions , migrate .WithMigrationsDirectoryAuto (defaultMigrationsDirectory ))
161
-
162
- }
163
- m , err := automigrator (db )
164
- if err != nil {
165
- return err
166
- }
167
-
168
- if ctx .Bool ("tx" ) {
169
- _ , err = m .CreateTxSQLMigrations (ctx .Context )
170
- } else {
171
- _ , err = m .CreateSQLMigrations (ctx .Context )
172
- }
173
- if err != nil {
174
- return err
175
- }
176
- return nil
177
- },
178
- },
179
- & cli.Command {
180
- Name : "migrate" ,
181
- Usage : "Generate SQL migrations and apply them right away" ,
182
- Flags : []cli.Flag {
183
- & cli.StringFlag {
184
- Name : "uri" ,
185
- Aliases : []string {"database-uri" , "dsn" },
186
- Required : true ,
187
- EnvVars : []string {"BUNDB_URI" },
188
- },
189
- & cli.StringFlag {
190
- Name : "driver" ,
191
- },
192
- & cli.StringFlag {
193
- Name : "d" ,
194
- Aliases : []string {"migrations-directory" },
195
- Destination : & migrationsDirectory ,
196
- Value : defaultMigrationsDirectory ,
197
- Action : func (ctx * cli.Context , dir string ) error {
198
- autoMigratorOptions = append (autoMigratorOptions , migrate .WithMigrationsDirectoryAuto (dir ))
199
- return nil
200
- },
201
- },
202
- & cli.StringFlag {
203
- Name : "t" ,
204
- Aliases : []string {"migrations-table" },
205
- Action : func (ctx * cli.Context , migrationsTable string ) error {
206
- autoMigratorOptions = append (autoMigratorOptions , migrate .WithTableNameAuto (migrationsTable ))
207
- return nil
208
- },
209
- },
210
- & cli.StringFlag {
211
- Name : "l" ,
212
- Aliases : []string {"locks" , "migration-locks-table" },
213
- Action : func (ctx * cli.Context , locksTable string ) error {
214
- autoMigratorOptions = append (autoMigratorOptions , migrate .WithLocksTableNameAuto (locksTable ))
215
- return nil
216
- },
217
- },
218
- & cli.StringFlag {
219
- Name : "s" ,
220
- Aliases : []string {"schema" },
221
- Action : func (ctx * cli.Context , schemaName string ) error {
222
- autoMigratorOptions = append (autoMigratorOptions , migrate .WithSchemaName (schemaName ))
223
- return nil
224
- },
225
- },
226
- & cli.StringSliceFlag {
227
- Name : "exclude" ,
228
- Action : func (ctx * cli.Context , tables []string ) error {
229
- autoMigratorOptions = append (autoMigratorOptions , migrate .WithExcludeTable (tables ... ))
230
- return nil
231
- },
232
- },
233
- & cli.BoolFlag {
234
- Name : "rebuild" ,
235
- },
236
- cleanup ,
237
- },
238
- Action : func (ctx * cli.Context ) error {
239
- if err := buildPlugin (ctx .Bool ("rebuild" )); err != nil {
240
- return err
241
- }
242
-
243
- if cleanup .Get (ctx ) {
244
- defer deletePlugin ()
245
- }
246
-
247
- db , err := connect (ctx .String ("uri" ), ctx .String ("driver" ), ! ctx .IsSet ("driver" ))
248
- if err != nil {
249
- return err
250
-
251
- }
252
-
253
- if ! ctx .IsSet ("migrations-directory" ) {
254
- autoMigratorOptions = append (autoMigratorOptions , migrate .WithMigrationsDirectoryAuto (defaultMigrationsDirectory ))
255
-
256
- }
257
- m , err := automigrator (db )
258
- if err != nil {
259
- return err
260
- }
261
-
262
- group , err := m .Migrate (ctx .Context )
263
- if err != nil {
264
- return err
265
- }
266
- if group .IsZero () {
267
- log .Print ("ok, nothing to migrate" )
268
- }
269
- return nil
270
- },
271
- },
272
- },
273
- },
274
- },
275
- }
276
-
277
- func pluginPath () string {
278
- return path .Join (migrationsDirectory , pluginName )
279
- }
280
-
281
- // TODO: wrap Build and Open steps into a sync.OnceFunc, so that we could use the Plugin object in multiple places
282
- // without having to worry if it has been compiled or not.
283
- func buildPlugin (force bool ) error {
284
- if force {
285
- if err := deletePlugin (); err != nil {
286
- return err
287
- }
288
- }
289
-
290
- // Cmd.Run returns *exec.ExitError which will only contain the exit code message in case of an error.
291
- // Rather than logging "exit code 1" we want to output a more informative error, so we redirect the Stderr.
292
- var errBuf bytes.Buffer
293
-
294
- cmd := exec .Command ("go" , "build" , "-C" , migrationsDirectory , "-buildmode" , "plugin" , "-o" , pluginName )
295
- cmd .Stderr = & errBuf
296
-
297
- err := cmd .Run ()
298
- if err != nil {
299
- // TODO: if errBuf contains "no such file or directory" add the following to the error message:
300
- // "Create 'migrations/' directory by running: bundb init --create-directory migrations/"
301
- return fmt .Errorf ("build %s: %s" , pluginPath (), & errBuf )
302
- }
303
- return nil
304
- }
305
-
306
- func deletePlugin () error {
307
- return os .RemoveAll (pluginPath ())
308
- }
309
-
310
- // connect to the database under the URI. A driver must be one of the supported drivers.
311
- // If not set explicitly, the name of the driver is guessed from the URI.
312
- //
313
- // Example:
314
- //
315
- // "postgres://root:@localhost:5432/test" -> "postgres"
316
- func connect (uri , driverName string , guessDriver bool ) (* bun.DB , error ) {
317
- var sqldb * sql.DB
318
- var dialect schema.Dialect
319
- var err error
320
-
321
- if guessDriver {
322
- driver , _ , found := strings .Cut (uri , ":" )
323
- if ! found {
324
- return nil , fmt .Errorf ("driver cannot be guessed from connection string; pass -driver option explicitly" )
325
- }
326
- driverName = driver
327
- }
328
-
329
- switch driverName {
330
- case "postgres" :
331
- sqldb = sql .OpenDB (pgdriver .NewConnector (pgdriver .WithDSN (uri )))
332
- dialect = pgdialect .New ()
333
- case "sqlserver" :
334
- sqldb , err = sql .Open (driverName , uri )
335
- dialect = mssqldialect .New ()
336
- case "file" :
337
- sqldb , err = sql .Open (sqliteshim .ShimName , uri )
338
- dialect = sqlitedialect .New ()
339
- case "mysql" :
340
- sqldb , err = sql .Open (driverName , uri )
341
- dialect = mysqldialect .New ()
342
- case "oci8" :
343
- sqldb , err = sql .Open (driverName , uri )
344
- dialect = oracledialect .New ()
345
- default :
346
- err = fmt .Errorf ("driver %q not recognized, supported drivers are %+v" , driverName , supportedDrivers )
347
- }
348
-
349
- if err != nil {
350
- return nil , err
351
- }
352
-
353
- return bun .NewDB (sqldb , dialect ), nil
354
- }
355
-
356
- // automigrator creates AutoMigrator for models from user's 'migrations' package.
357
- func automigrator (db * bun.DB ) (* migrate.AutoMigrator , error ) {
358
- sym , err := lookup ("Models" )
359
- if err != nil {
360
- return nil , err
361
- }
362
-
363
- models , ok := sym .(* []interface {})
364
- if ! ok {
365
- return nil , fmt .Errorf ("migrations plugin must export Models as []interface{}, got %T" , models )
366
- }
367
- autoMigratorOptions = append (autoMigratorOptions , migrate .WithModel (* models ... ))
368
-
369
- auto , err := migrate .NewAutoMigrator (db , autoMigratorOptions ... )
370
- if err != nil {
371
- return nil , err
372
- }
373
- return auto , nil
374
- }
375
-
376
- // lookup a symbol from user's migrations plugin.
377
- func lookup (symbol string ) (plugin.Symbol , error ) {
378
- p , err := plugin .Open (pluginPath ())
379
- if err != nil {
380
- return nil , err
381
- }
382
- return p .Lookup (symbol )
383
- }
384
-
385
34
func main () {
386
35
log .SetPrefix ("bundb: " )
387
- if err := app . Run ( os . Args ); err != nil {
388
- log . Fatal ( err )
36
+ // TODO: use buncli.New(buncli.FromPlugin()) to read config from plugin
37
+ if err := buncli . Run ( os . Args , nil ); err != nil {
389
38
}
390
39
}
0 commit comments