1package goose
2
3import (
4 "errors"
5 "fmt"
6
7 "github.com/pressly/goose/v3/database"
8 "github.com/pressly/goose/v3/lock"
9)
10
11const (
12 // DefaultTablename is the default name of the database table used to track history of applied
13 // migrations.
14 DefaultTablename = "goose_db_version"
15)
16
17// ProviderOption is a configuration option for a goose goose.
18type ProviderOption interface {
19 apply(*config) error
20}
21
22// WithStore configures the provider with a custom [database.Store] implementation.
23//
24// By default, the provider uses the [database.NewStore] function to create a store backed by the
25// given dialect. However, this option allows users to provide their own implementation or call
26// [database.NewStore] with custom options, such as setting the table name.
27//
28// Example:
29//
30// // Create a store with a custom table name.
31// store, err := database.NewStore(database.DialectPostgres, "my_custom_table_name")
32// if err != nil {
33// return err
34// }
35// // Create a provider with the custom store.
36// provider, err := goose.NewProvider("", db, nil, goose.WithStore(store))
37// if err != nil {
38// return err
39// }
40func WithStore(store database.Store) ProviderOption {
41 return configFunc(func(c *config) error {
42 if c.store != nil {
43 return fmt.Errorf("store already set: %T", c.store)
44 }
45 if store == nil {
46 return errors.New("store must not be nil")
47 }
48 if store.Tablename() == "" {
49 return errors.New("store implementation must set the table name")
50 }
51 c.store = store
52 return nil
53 })
54}
55
56// WithVerbose enables verbose logging.
57func WithVerbose(b bool) ProviderOption {
58 return configFunc(func(c *config) error {
59 c.verbose = b
60 return nil
61 })
62}
63
64// WithSessionLocker enables locking using the provided SessionLocker.
65//
66// If WithSessionLocker is not called, locking is disabled.
67func WithSessionLocker(locker lock.SessionLocker) ProviderOption {
68 return configFunc(func(c *config) error {
69 if c.lockEnabled {
70 return errors.New("lock already enabled")
71 }
72 if c.sessionLocker != nil {
73 return errors.New("session locker already set")
74 }
75 if locker == nil {
76 return errors.New("session locker must not be nil")
77 }
78 c.lockEnabled = true
79 c.sessionLocker = locker
80 return nil
81 })
82}
83
84// WithExcludeNames excludes the given file name from the list of migrations. If called multiple
85// times, the list of excludes is merged.
86func WithExcludeNames(excludes []string) ProviderOption {
87 return configFunc(func(c *config) error {
88 for _, name := range excludes {
89 if _, ok := c.excludePaths[name]; ok {
90 return fmt.Errorf("duplicate exclude file name: %s", name)
91 }
92 c.excludePaths[name] = true
93 }
94 return nil
95 })
96}
97
98// WithExcludeVersions excludes the given versions from the list of migrations. If called multiple
99// times, the list of excludes is merged.
100func WithExcludeVersions(versions []int64) ProviderOption {
101 return configFunc(func(c *config) error {
102 for _, version := range versions {
103 if version < 1 {
104 return errInvalidVersion
105 }
106 if _, ok := c.excludeVersions[version]; ok {
107 return fmt.Errorf("duplicate excludes version: %d", version)
108 }
109 c.excludeVersions[version] = true
110 }
111 return nil
112 })
113}
114
115// WithGoMigrations registers Go migrations with the provider. If a Go migration with the same
116// version has already been registered, an error will be returned.
117//
118// Go migrations must be constructed using the [NewGoMigration] function.
119func WithGoMigrations(migrations ...*Migration) ProviderOption {
120 return configFunc(func(c *config) error {
121 for _, m := range migrations {
122 if _, ok := c.registered[m.Version]; ok {
123 return fmt.Errorf("go migration with version %d already registered", m.Version)
124 }
125 if err := checkGoMigration(m); err != nil {
126 return fmt.Errorf("invalid go migration: %w", err)
127 }
128 c.registered[m.Version] = m
129 }
130 return nil
131 })
132}
133
134// WithDisableGlobalRegistry prevents the provider from registering Go migrations from the global
135// registry. By default, goose will register all Go migrations including those registered globally.
136func WithDisableGlobalRegistry(b bool) ProviderOption {
137 return configFunc(func(c *config) error {
138 c.disableGlobalRegistry = b
139 return nil
140 })
141}
142
143// WithAllowOutofOrder allows the provider to apply missing (out-of-order) migrations. By default,
144// goose will raise an error if it encounters a missing migration.
145//
146// For example: migrations 1,3 are applied and then version 2,6 are introduced. If this option is
147// true, then goose will apply 2 (missing) and 6 (new) instead of raising an error. The final order
148// of applied migrations will be: 1,3,2,6. Out-of-order migrations are always applied first,
149// followed by new migrations.
150func WithAllowOutofOrder(b bool) ProviderOption {
151 return configFunc(func(c *config) error {
152 c.allowMissing = b
153 return nil
154 })
155}
156
157// WithDisableVersioning disables versioning. Disabling versioning allows applying migrations
158// without tracking the versions in the database schema table. Useful for tests, seeding a database
159// or running ad-hoc queries. By default, goose will track all versions in the database schema
160// table.
161func WithDisableVersioning(b bool) ProviderOption {
162 return configFunc(func(c *config) error {
163 c.disableVersioning = b
164 return nil
165 })
166}
167
168// WithLogger will set a custom Logger, which will override the default logger.
169func WithLogger(l Logger) ProviderOption {
170 return configFunc(func(c *config) error {
171 c.logger = l
172 return nil
173 })
174}
175
176type config struct {
177 store database.Store
178
179 verbose bool
180 excludePaths map[string]bool
181 excludeVersions map[int64]bool
182
183 // Go migrations registered by the user. These will be merged/resolved against the globally
184 // registered migrations.
185 registered map[int64]*Migration
186
187 // Locking options
188 lockEnabled bool
189 sessionLocker lock.SessionLocker
190
191 // Feature
192 disableVersioning bool
193 allowMissing bool
194 disableGlobalRegistry bool
195
196 logger Logger
197}
198
199type configFunc func(*config) error
200
201func (f configFunc) apply(cfg *config) error {
202 return f(cfg)
203}