-
Notifications
You must be signed in to change notification settings - Fork 1
/
migration.go
149 lines (131 loc) · 4.12 KB
/
migration.go
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
package internal
import (
"fmt"
"os"
"path/filepath"
"strings"
"time"
)
// A Migration is a database change with a direction name and timestamp.
// Typically, a Migration with a DirForward Direction is paired with another
// migration of DirReverse that has the same label.
type Migration interface {
Indirection() Indirection
Label() string
Version() Version
}
// mutation implements the Migration interface.
type mutation struct {
indirection Indirection
label string
version Version
}
func (m *mutation) Indirection() Indirection { return m.indirection }
func (m *mutation) Label() string { return m.label }
func (m *mutation) Version() Version { return m.version }
// ParseMigration constructs a Migration from a Filename.
func ParseMigration(name Filename) (mig Migration, err error) {
basename := filepath.Base(string(name))
indirection := parseIndirection(basename)
if indirection.Value == DirUnknown {
err = fmt.Errorf(
"%w; could not parse Direction for filename %q",
ErrDataInvalid, name,
)
return
}
// index of the start of timestamp
i := len(indirection.Label) + len(filenameDelimeter)
version, err := ParseVersion(basename)
if err != nil {
err = fmt.Errorf(
"%w, could not parse version for filename %q; %v",
ErrDataInvalid, version, err,
)
return
}
// index of the start of migration label
j := i + len(version.String()) + len(filenameDelimeter)
mig = &mutation{
indirection: indirection,
label: strings.TrimSuffix(string(basename[j:]), ".sql"),
version: version,
}
return
}
// MigrationParams collects inputs needed to generate migration files. Setting
// Reversible to true will generate a migration file for each direction.
// Otherwise, it only generates a file in the forward direction. The Directory
// field refers to the path to the directory with the migration files.
type MigrationParams struct {
Forward Migration
Reverse Migration
Reversible bool
Dirpath string
}
// NewMigrationParams constructs a MigrationParams that's ready to use.
func NewMigrationParams(name string, reversible bool, dirpath, fwdLabel, revLabel string) (out *MigrationParams, err error) {
if fwdLabel == "" {
fwdLabel = ForwardDirections[0]
}
if err = validateDirectionLabel(ForwardDirections, fwdLabel); err != nil {
return
}
if revLabel == "" {
revLabel = ReverseDirections[0]
}
if err = validateDirectionLabel(ReverseDirections, revLabel); err != nil {
return
}
now := time.Now().UTC()
version := timestamp{value: now.Unix(), label: now.Format(TimeFormat)}
out = &MigrationParams{
Reversible: reversible,
Dirpath: dirpath,
Forward: &mutation{
indirection: Indirection{Value: DirForward, Label: fwdLabel},
label: name,
version: &version,
},
Reverse: &mutation{
indirection: Indirection{Value: DirReverse, Label: revLabel},
label: name,
version: &version,
},
}
return
}
// GenerateFiles creates the migration files. If the migration is reversible it
// generates files in forward and reverse directions; otherwise it generates
// just one migration file in the forward direction. It closes each file handle
// when it's done.
func (m *MigrationParams) GenerateFiles() (err error) {
var forwardFile, reverseFile *os.File
if forwardFile, err = newMigrationFile(m.Forward, m.Dirpath); err != nil {
return
}
fmt.Fprintln(os.Stderr, "created forward file:", forwardFile.Name())
defer func() { _ = forwardFile.Close() }()
if !m.Reversible {
fmt.Fprintln(os.Stderr, "migration marked irreversible, did not create reverse file")
return
}
if reverseFile, err = newMigrationFile(m.Reverse, m.Dirpath); err != nil {
return
}
fmt.Fprintln(os.Stderr, "created reverse file:", reverseFile.Name())
defer func() { _ = reverseFile.Close() }()
return
}
func newMigrationFile(m Migration, baseDir string) (*os.File, error) {
name := filepath.Join(baseDir, MakeMigrationFilename(m))
return os.Create(filepath.Clean(name))
}
// MakeMigrationFilename converts a Migration m to a filename.
func MakeMigrationFilename(m Migration) string {
return MakeFilename(
m.Version().String(),
m.Indirection(),
m.Label(),
)
}