forked from pressly/goose
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathprovider_options.go
More file actions
281 lines (255 loc) · 8.38 KB
/
provider_options.go
File metadata and controls
281 lines (255 loc) · 8.38 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
package goose
import (
"errors"
"fmt"
"log/slog"
"github.com/pressly/goose/v3/database"
"github.com/pressly/goose/v3/lock"
)
const (
// DefaultTablename is the default name of the database table used to track history of applied
// migrations.
DefaultTablename = "goose_db_version"
)
// ProviderOption is a configuration option for a goose goose.
type ProviderOption interface {
apply(*config) error
}
// WithStore configures the provider with a custom [database.Store], allowing users to bring their
// own implementation of the store interface. When this option is used, the dialect parameter of
// [NewProvider] must be set to [DialectCustom].
//
// This option cannot be used together with [WithTableName], since the table name is set on the
// store.
//
// By default, the provider uses the [database.NewStore] function to create a store backed by one of
// the officially supported dialects.
func WithStore(store database.Store) ProviderOption {
return configFunc(func(c *config) error {
if c.store != nil {
return fmt.Errorf("store already set: %T", c.store)
}
if store == nil {
return errors.New("store must not be nil")
}
if store.Tablename() == "" {
return errors.New("store implementation must set the table name")
}
c.store = store
return nil
})
}
// WithTableName sets the name of the database table used to track history of applied migrations.
// This option cannot be used together with [WithStore], since the table name is set on the store.
//
// Default is "goose_db_version".
func WithTableName(name string) ProviderOption {
return configFunc(func(c *config) error {
if name == "" {
return errors.New("table name must not be empty")
}
c.tableName = name
return nil
})
}
// WithVerbose enables verbose logging.
func WithVerbose(b bool) ProviderOption {
return configFunc(func(c *config) error {
c.verbose = b
return nil
})
}
// WithSessionLocker enables locking using the provided SessionLocker.
//
// If WithSessionLocker is not called, locking is disabled. Must not be used together with
// [WithLocker].
func WithSessionLocker(locker lock.SessionLocker) ProviderOption {
return configFunc(func(c *config) error {
if c.lockEnabled {
return errors.New("lock already enabled")
}
if c.sessionLocker != nil {
return errors.New("session locker already set")
}
if c.locker != nil {
return errors.New("locker already set; cannot use both SessionLocker and Locker")
}
if locker == nil {
return errors.New("session locker must not be nil")
}
c.lockEnabled = true
c.sessionLocker = locker
return nil
})
}
// WithLocker enables locking using the provided Locker.
//
// If WithLocker is not called, locking is disabled. Must not be used together with
// [WithSessionLocker].
func WithLocker(locker lock.Locker) ProviderOption {
return configFunc(func(c *config) error {
if c.lockEnabled {
return errors.New("lock already enabled")
}
if c.locker != nil {
return errors.New("locker already set")
}
if c.sessionLocker != nil {
return errors.New("session locker already set; cannot use both SessionLocker and Locker")
}
if locker == nil {
return errors.New("locker must not be nil")
}
c.lockEnabled = true
c.locker = locker
return nil
})
}
// WithExcludeNames excludes the given file name from the list of migrations. If called multiple
// times, the list of excludes is merged.
func WithExcludeNames(excludes []string) ProviderOption {
return configFunc(func(c *config) error {
for _, name := range excludes {
if _, ok := c.excludePaths[name]; ok {
return fmt.Errorf("duplicate exclude file name: %s", name)
}
c.excludePaths[name] = true
}
return nil
})
}
// WithExcludeVersions excludes the given versions from the list of migrations. If called multiple
// times, the list of excludes is merged.
func WithExcludeVersions(versions []int64) ProviderOption {
return configFunc(func(c *config) error {
for _, version := range versions {
if version < 1 {
return errInvalidVersion
}
if _, ok := c.excludeVersions[version]; ok {
return fmt.Errorf("duplicate excludes version: %d", version)
}
c.excludeVersions[version] = true
}
return nil
})
}
// WithGoMigrations registers Go migrations with the provider. If a Go migration with the same
// version has already been registered, an error will be returned.
//
// Go migrations must be constructed using the [NewGoMigration] function.
func WithGoMigrations(migrations ...*Migration) ProviderOption {
return configFunc(func(c *config) error {
for _, m := range migrations {
if _, ok := c.registered[m.Version]; ok {
return fmt.Errorf("go migration with version %d already registered", m.Version)
}
if err := checkGoMigration(m); err != nil {
return fmt.Errorf("invalid go migration: %w", err)
}
c.registered[m.Version] = m
}
return nil
})
}
// WithDisableGlobalRegistry prevents the provider from registering Go migrations from the global
// registry. By default, goose will register all Go migrations including those registered globally.
func WithDisableGlobalRegistry(b bool) ProviderOption {
return configFunc(func(c *config) error {
c.disableGlobalRegistry = b
return nil
})
}
// WithAllowOutofOrder allows the provider to apply missing (out-of-order) migrations. By default,
// goose will raise an error if it encounters a missing migration.
//
// For example: migrations 1,3 are applied and then version 2,6 are introduced. If this option is
// true, then goose will apply 2 (missing) and 6 (new) instead of raising an error. The final order
// of applied migrations will be: 1,3,2,6. Out-of-order migrations are always applied first,
// followed by new migrations.
func WithAllowOutofOrder(b bool) ProviderOption {
return configFunc(func(c *config) error {
c.allowMissing = b
return nil
})
}
// WithDisableVersioning disables versioning. Disabling versioning allows applying migrations
// without tracking the versions in the database schema table. Useful for tests, seeding a database
// or running ad-hoc queries. By default, goose will track all versions in the database schema
// table.
func WithDisableVersioning(b bool) ProviderOption {
return configFunc(func(c *config) error {
c.disableVersioning = b
return nil
})
}
// WithLogger will set a custom Logger, which will override the default logger. Cannot be used
// together with [WithSlog].
func WithLogger(l Logger) ProviderOption {
return configFunc(func(c *config) error {
if l == nil {
return errors.New("logger must not be nil")
}
if c.slogger != nil {
return errors.New("cannot use both WithLogger and WithSlog")
}
c.logger = l
return nil
})
}
// WithSlog will set a custom [*slog.Logger] for structured logging. This enables rich structured
// logging with attributes like source, direction, duration, etc. Cannot be used together with
// [WithLogger].
//
// Example:
//
// logger := slog.New(slog.NewTextHandler(os.Stdout, nil))
// p, err := goose.NewProvider("postgres", db, fs, goose.WithSlog(logger))
func WithSlog(logger *slog.Logger) ProviderOption {
return configFunc(func(c *config) error {
if logger == nil {
return errors.New("slog logger must not be nil")
}
if c.logger != nil {
return errors.New("cannot use both WithLogger and WithSlog")
}
c.slogger = logger
return nil
})
}
// WithIsolateDDL executes DDL operations separately from DML operations. This is useful for
// databases like AWS Aurora DSQL that don't support mixing DDL and DML within the same transaction.
func WithIsolateDDL(b bool) ProviderOption {
return configFunc(func(c *config) error {
c.isolateDDL = b
return nil
})
}
type config struct {
tableName string
store database.Store
dialect database.Dialect
verbose bool
excludePaths map[string]bool
excludeVersions map[int64]bool
// Go migrations registered by the user. These will be merged/resolved against the globally
// registered migrations.
registered map[int64]*Migration
// Locking options
lockEnabled bool
sessionLocker lock.SessionLocker
locker lock.Locker
// Feature
disableVersioning bool
allowMissing bool
disableGlobalRegistry bool
isolateDDL bool
// Only a single logger can be set, they are mutually exclusive. If neither is set, a default
// [Logger] will be set to maintain backward compatibility in /v3.
logger Logger
slogger *slog.Logger
}
type configFunc func(*config) error
func (f configFunc) apply(cfg *config) error {
return f(cfg)
}