1package goose
2
3import (
4 "database/sql"
5 "errors"
6 "fmt"
7 "os"
8 "path/filepath"
9 "text/template"
10 "time"
11)
12
13type tmplVars struct {
14 Version string
15 CamelName string
16}
17
18var (
19 sequential = false
20)
21
22// SetSequential set whether to use sequential versioning instead of timestamp based versioning
23func SetSequential(s bool) {
24 sequential = s
25}
26
27// Create writes a new blank migration file.
28func CreateWithTemplate(db *sql.DB, dir string, tmpl *template.Template, name, migrationType string) error {
29 version := time.Now().UTC().Format(timestampFormat)
30
31 if sequential {
32 // always use DirFS here because it's modifying operation
33 migrations, err := collectMigrationsFS(osFS{}, dir, minVersion, maxVersion, registeredGoMigrations)
34 if err != nil && !errors.Is(err, ErrNoMigrationFiles) {
35 return err
36 }
37
38 vMigrations, err := migrations.versioned()
39 if err != nil {
40 return err
41 }
42
43 if last, err := vMigrations.Last(); err == nil {
44 version = fmt.Sprintf(seqVersionTemplate, last.Version+1)
45 } else {
46 version = fmt.Sprintf(seqVersionTemplate, int64(1))
47 }
48 }
49
50 filename := fmt.Sprintf("%v_%v.%v", version, snakeCase(name), migrationType)
51
52 if tmpl == nil {
53 if migrationType == "go" {
54 tmpl = goSQLMigrationTemplate
55 } else {
56 tmpl = sqlMigrationTemplate
57 }
58 }
59
60 path := filepath.Join(dir, filename)
61 if _, err := os.Stat(path); !os.IsNotExist(err) {
62 return fmt.Errorf("failed to create migration file: %w", err)
63 }
64
65 f, err := os.Create(path)
66 if err != nil {
67 return fmt.Errorf("failed to create migration file: %w", err)
68 }
69 defer f.Close()
70
71 vars := tmplVars{
72 Version: version,
73 CamelName: camelCase(name),
74 }
75 if err := tmpl.Execute(f, vars); err != nil {
76 return fmt.Errorf("failed to execute tmpl: %w", err)
77 }
78
79 log.Printf("Created new file: %s", f.Name())
80 return nil
81}
82
83// Create writes a new blank migration file.
84func Create(db *sql.DB, dir, name, migrationType string) error {
85 return CreateWithTemplate(db, dir, nil, name, migrationType)
86}
87
88var sqlMigrationTemplate = template.Must(template.New("goose.sql-migration").Parse(`-- +goose Up
89-- +goose StatementBegin
90SELECT 'up SQL query';
91-- +goose StatementEnd
92
93-- +goose Down
94-- +goose StatementBegin
95SELECT 'down SQL query';
96-- +goose StatementEnd
97`))
98
99var goSQLMigrationTemplate = template.Must(template.New("goose.go-migration").Parse(`package migrations
100
101import (
102 "context"
103 "database/sql"
104 "github.com/pressly/goose/v3"
105)
106
107func init() {
108 goose.AddMigrationContext(up{{.CamelName}}, down{{.CamelName}})
109}
110
111func up{{.CamelName}}(ctx context.Context, tx *sql.Tx) error {
112 // This code is executed when the migration is applied.
113 return nil
114}
115
116func down{{.CamelName}}(ctx context.Context, tx *sql.Tx) error {
117 // This code is executed when the migration is rolled back.
118 return nil
119}
120`))