-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathschema-deploy.go
246 lines (217 loc) · 7.66 KB
/
schema-deploy.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
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
package sqldb
import (
"path"
"reflect"
"runtime"
"strings"
"github.com/jmoiron/sqlx"
)
// DeploySchemaOptions provides options when deploying a schema.
type DeploySchemaOptions struct {
//CloseConnection determines if the database connection should be closed after
//running all the DeployQueries and DeployFuncs.//
//
//This was added to support deploying and then using a SQLite in-memory databse.
//Each connection to an in-memory database references a new database, so to run
//queries against an in-memory database that was just deployed, we need to keep
//the connection open.
CloseConnection bool //default true
}
// DeploySchema runs the DeployQueries and DeployFuncs specified in a config against
// the database noted in the config. Use this to create your tables, create indexes,
// etc. This will automatically issue a CREATE DATABASE IF NOT EXISTS query.
//
// DeployQueries will be translated via DeployQueryTranslators and any DeployQuery
// errors will be processed by DeployQueryErrorHandlers. Neither of these steps apply
// to DeployFuncs.
//
// DeploySchemaOptions is a pointer so that in cases where you do not want to provide
// any options, using the defaults, you can simply provide nil.
//
// Typically this func is run when a flag, i.e.: --deploy-db, is provided.
func (c *Config) DeploySchema(opts *DeploySchemaOptions) (err error) {
//Set default opts if none were provided.
if opts == nil {
opts = &DeploySchemaOptions{
CloseConnection: true,
}
}
//Make sure the connection isn't already established to prevent overwriting it.
//This forces users to call Close() first to prevent any errors.
if c.Connected() {
return ErrConnected
}
//Make sure the config is valid.
err = c.validate()
if err != nil {
return
}
//Build the connection string used to connect to the database.
//
//The returned string will not included the database name (for non-SQLite) since
//the database may not have been deployed yet.
c.debugLn("sqldb.DeploySchema", "Getting connection string before deploying...")
connString := c.buildConnectionString(true)
//Get the correct driver based on the database type.
//
//If using SQLite, the correct driver is chosen based on build tags.
driver := getDriver(c.Type)
//Create the database, if it doesn't already exist.
//
//For MariaDB/MySQL, we need to create the actual database on the server.
//For SQLite, we need to Ping() the connection so the file is created on disk.
conn, err := sqlx.Open(driver, connString)
if err != nil {
return
}
defer conn.Close()
switch c.Type {
case DBTypeMySQL, DBTypeMariaDB, DBTypeMSSQL:
q := `CREATE DATABASE IF NOT EXISTS ` + c.Name
_, innerErr := conn.Exec(q)
if innerErr != nil {
err = innerErr
return
}
case DBTypeSQLite:
err = conn.Ping()
if err != nil {
return
}
}
//Reconnect to the database since the previously used connection string did not
//include the database name (for non-SQLite). This will connect us to the specific
//database, not just the database server. This connects using Connect(), the same
//function that would be used to connect to the database for normal usage.
err = conn.Close()
if err != nil {
return
}
c.debugLn("sqldb.DeploySchema", "Connecting to deployed database...")
err = c.Connect()
if err != nil {
return
}
//Skip closing the connection if user wants to leave connection open after this
//function completes. Leaving the connection open is important for handling
//SQLite in-memory databases.
//
//This is only effective when an error does not occur in the below code. When an
//error occurs, Close() is always called.
if opts.CloseConnection {
defer c.Close()
}
//Get connection to use for deploying.
connection := c.Connection()
//Run each DeployQuery.
c.infoLn("sqldb.DeploySchema", "Running DeployQueries...")
for _, q := range c.DeployQueries {
//Translate.
q := c.RunDeployQueryTranslators(q)
//Log for diagnostics. Seeing queries is sometimes nice to see what is
//happening.
//
//Trim logging length just to prevent super long queries from causing long
//logging entries.
ql, _, found := strings.Cut(strings.TrimSpace(q), "\n")
if found {
c.infoLn("DeployQuery:", ql)
} else if maxLen := 70; len(q) > maxLen {
c.infoLn("DeployQuery:", q[:maxLen]+"...")
} else {
c.infoLn("DeployQuery:", q)
}
//Execute the query. If an error occurs, check if it should be ignored.
_, innerErr := connection.Exec(q)
if innerErr != nil && !c.runDeployQueryErrorHandlers(q, innerErr) {
err = innerErr
c.errorLn("sqldb.DeploySchema", "Error with query.", q, err)
c.Close()
return
}
}
c.infoLn("sqldb.DeploySchema", "Running DeployQueries...done")
//Run each DeployFunc.
c.infoLn("sqldb.DeploySchema", "Running DeployFuncs...")
for _, f := range c.DeployFuncs {
//Get function name for diagnostic logging, since for DeployQueries above we
//log out some or all of each query.
rawNameWithPath := runtime.FuncForPC(reflect.ValueOf(f).Pointer()).Name()
funcName := path.Base(rawNameWithPath)
c.infoLn("DeployFunc:", funcName)
//Execute the func.
innerErr := f(connection)
if innerErr != nil {
err = innerErr
c.errorLn("sqldb.DeploySchema", "Error with DeployFunc.", funcName, err)
c.Close()
return
}
}
c.infoLn("sqldb.DeploySchema", "Running DeployFuncs...done")
//Close the connection to the database, if needed.
if opts.CloseConnection {
c.Close()
c.debugLn("sqldb.DeploySchema", "Connection closed after successful deploy.")
} else {
c.debugLn("sqldb.DeploySchema", "Connection left open after successful deploy.")
}
return
}
// DeploySchema runs the DeployQueries and DeployFuncs specified in a config against
// the database noted in the config. Use this to create your tables, create indexes,
// etc. This will automatically issue a CREATE DATABASE IF NOT EXISTS query.
//
// DeployQueries will be translated via DeployQueryTranslators and any DeployQuery
// errors will be processed by DeployQueryErrorHandlers. Neither of these steps apply
// to DeployFuncs.
//
// DeploySchemaOptions is a pointer so that in cases where you do not want to provide
// any options, using the defaults, you can simply provide nil.
//
// Typically this func is run when a flag, i.e.: --deploy-db, is provided.
func DeploySchema(opts *DeploySchemaOptions) (err error) {
return cfg.DeploySchema(opts)
}
// RunDeployQueryTranslators runs the list of DeployQueryTranslators on the provided
// query.
//
// This func is called in DeploySchema() but can also be called manually when you want
// to translate a DeployQuery (for example, running a specific DeployQuery as part of
// UpdateSchema).
func (c *Config) RunDeployQueryTranslators(in string) (out string) {
out = in
for _, t := range c.DeployQueryTranslators {
out = t(out)
}
return out
}
// RunDeployQueryTranslators runs the list of DeployQueryTranslators on the provided
// query.
//
// This func is called in DeploySchema() but can also be called manually when you want
// to translate a DeployQuery (for example, running a specific DeployQuery as part of
// UpdateSchema).
func RunDeployQueryTranslators(in string) (out string) {
out = in
for _, t := range cfg.DeployQueryTranslators {
out = t(out)
}
return out
}
// runDeployQueryErrorHandlers runs the list of DeployQueryErrorHandlers when an error
// occured from running a DeployQuery. This is run in Deploy().
func (c *Config) runDeployQueryErrorHandlers(query string, err error) (ignoreError bool) {
//Make sure an error occured.
if err == nil {
return true
}
//Run each DeployQueryErrorHandler and see if any return true to ignore this error.
for _, eh := range c.DeployQueryErrorHandlers {
ignoreError = eh(query, err)
if ignoreError {
return
}
}
return false
}