Skip to content

Commit 6a7960f

Browse files
author
c9845 (AA-Desktop)
committed
Better support for in-memory sqlite databases by allowing a db connection to remain open after deploying or updating schema. Clean up some logging.
Previously, deploying or updating a db schema closed the connection after deploy or update completed successfully. However, this didn't work for in-memory sqlite db since each connection using a new db. Therefore, when a connection was reestablished after deploy, this new connection didn't see a deployed db, it saw an empty db. To fix this, deploying or updating a db can now leave the connection open for reuse in running queries.
1 parent 9e90074 commit 6a7960f

File tree

5 files changed

+303
-179
lines changed

5 files changed

+303
-179
lines changed

changlog.txt

+14
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,17 @@
1+
v1.3.0
2+
----------
3+
- Clean up logging.
4+
- Previously logging was done with `if c.Debug{}` blocks encapsulating a `.log.Println()` call.
5+
- Now, c.debugPrintln can be used.
6+
- This removes a lot of `if` blocks to make code cleaner.
7+
- Allow keeping a database connection open after deploying or updating schema.
8+
- This was necessary for supporting SQLite when using an in-memory database.
9+
- When an deploying a db the connection was closed after deploy was completed.
10+
- However, for an in-memory db this doesn't work since each connection gets a "new" db.
11+
- When the connection was reopened to run queries none of the deployed schema existed.
12+
- To alleviate this, deploying (or updating) a db now can take an options struct that allows for keeping the connection open after deploy/update.
13+
- This allows connection to be reused and in-memory db to function as expected.
14+
115
v1.2.0
216
----------
317
- Allow choosing between mattn and modernc libraries for SQLite.

sqldb-deploySchema.go

+89-67
Original file line numberDiff line numberDiff line change
@@ -10,20 +10,32 @@ import (
1010
"github.com/jmoiron/sqlx"
1111
)
1212

13-
//DeploySchema deploys the database schema by running the list of DeployQueries defined
14-
//on a database config. This will create the database if needed. Typically this is used
15-
//to deploy an empty, or near empty, database. A database connection must not already
16-
//be established; this func will establish the connection the leave it open for further
17-
//use.
13+
//DeploySchemaOptions provides options when deploying a schema.
14+
type DeploySchemaOptions struct {
15+
SkipInsert bool
16+
CloseConnection bool
17+
}
18+
19+
//DeploySchemaWithOps deploys the database schema by running the list of DeployQueries
20+
//and DeployFuncs defined in config. This will create the database if needed. This is
21+
//typically used to deploy an empty, or near empty, database. A database connection
22+
//must not already be established; this func will establish the connection.
1823
//
19-
//Typically this func would be called when your app is passed a flag, such as --deploy-db,
20-
//so that your database is only deployed when needed, not as part of every start of
21-
//your app.
24+
//Although each DeployQuery and DeployFunc should be indempotent (ex.: using CREATE
25+
//TABLE IF NOT EXISTS), you should still not call this func each time your app starts
26+
//or otherwise. Typically you would check if the database already exists or use a
27+
//flag, such as --deploy-db, to run this func.
2228
//
23-
//The dontInsert parameter is used prevent any DeployQueries with "INSERT INTO" statements
24-
//from running. This is used to deploy a completely empty database.
25-
func (c *Config) DeploySchema(dontInsert bool) (err error) {
26-
//Make sure the connection isn't already established to prevent overwriting anything.
29+
//skipInsert is used prevent any DeployQueries with "INSERT INTO" statements from
30+
//running. This is used to deploy a completely empty database and is useful for
31+
//migrating data or backups.
32+
//
33+
//closeConnection determines if the database connection should be closed after this
34+
//func successfully completes. This was added to support SQLite in-memory databases
35+
//since each connection to an im-memory db uses a new database, so if we deploy with
36+
//a connection we need to reuse it to run queries.
37+
func (c *Config) DeploySchemaWithOps(ops DeploySchemaOptions) (err error) {
38+
//Make sure a connection isn't already established to prevent overwriting anything.
2739
//This forces users to call Close() first to prevent any incorrect db usage.
2840
if c.Connected() {
2941
return ErrConnected
@@ -55,9 +67,9 @@ func (c *Config) DeploySchema(dontInsert bool) (err error) {
5567
}
5668
defer conn.Close()
5769

58-
//Handle any database-type specific stuff.
59-
//For non-sqlite dbs, we need to create the actual database on the server.
60-
//For sqlite dbs, we need to Ping() the connection so the db file is created on disk.
70+
//Create the database.
71+
//For mariadb/mysql, we need to create the actual database on the server.
72+
//For SQLite , we need to Ping() the connection so the file is created on disk.
6173
switch c.Type {
6274
case DBTypeMySQL, DBTypeMariaDB:
6375
q := `CREATE DATABASE IF NOT EXISTS ` + c.Name
@@ -75,95 +87,105 @@ func (c *Config) DeploySchema(dontInsert bool) (err error) {
7587

7688
//Reconnect to the database since the previously used connection didn't include
7789
//the database name in the connection string. This will connect us to the specific
78-
//database, not the database server. This connects using Connect(), the same func
79-
//that would be used to connect to the db for normal usage.
80-
//
81-
//This is not necessary for SQLite since SQLite always connects to the filepath
82-
//that was provided, not a server.
83-
if !c.IsSQLite() {
84-
err = conn.Close()
85-
if err != nil {
86-
return
87-
}
90+
//database, not just the database server. This connects using Connect(), the same
91+
//func that would be used to connect to the db for normal usage.
92+
err = conn.Close()
93+
if err != nil {
94+
return
95+
}
8896

89-
//Note, no `defer Close()` since we want to leave the connection to the db
90-
//open upon successfully deploying the db so that db can be used without
91-
//calling `Connect()` after this func.
92-
err = c.Connect()
93-
if err != nil {
94-
return
95-
}
97+
err = c.Connect()
98+
if err != nil {
99+
return
100+
}
101+
102+
if ops.CloseConnection {
103+
defer c.Close()
96104
}
97105

98106
//Run each deploy query.
99-
c.log("sqldb.DeploySchema (DeployQueries)...")
107+
c.debugPrintln("sqldb.DeploySchema (DeployQueries)...")
108+
connection := c.Connection()
100109
for _, q := range c.DeployQueries {
101110
//Translate the query if needed. This will only translate queries with
102111
//CREATE TABLE in the text.
103112
q = c.translateCreateTable(q)
104113

105-
//skip queries that insert data if needed. This will skip any query with
106-
//INSERT INTO in the text.
107-
if strings.Contains(strings.ToUpper(q), "INSERT INTO") && dontInsert {
114+
//Skip queries that insert data if needed.
115+
if strings.Contains(strings.ToUpper(q), "INSERT INTO") && ops.SkipInsert {
108116
continue
109117
}
110118

111-
if c.Debug {
112-
if strings.Contains(q, "CREATE TABLE") {
113-
idx := strings.Index(q, "(")
114-
log.Println(strings.TrimSpace(q[:idx]) + "...")
115-
} else {
116-
log.Println(q)
117-
}
118-
119+
//Log out some info about the query being run for diagnostics.
120+
if strings.Contains(q, "CREATE TABLE") {
121+
idx := strings.Index(q, "(")
122+
c.debugPrintln(strings.TrimSpace(q[:idx]) + "...")
123+
} else {
124+
c.debugPrintln(q)
119125
}
120126

121-
//Execute the query.
122-
//Logging on error so users can identify query in question.
123-
connection := c.Connection()
127+
//Execute the query. Always log on error so users can identify query that has
128+
//an error.
124129
_, innerErr := connection.Exec(q)
125130
if innerErr != nil {
126131
err = innerErr
127132
log.Println("sqldb.DeploySchema() error with query", q)
133+
c.Close()
128134
return
129135
}
130136
}
131-
if c.Debug {
132-
log.Println("sqldb.DeploySchema (DeployQueries)...done")
133-
}
137+
c.debugPrintln("sqldb.DeploySchema (DeployQueries)...done")
134138

135139
//Run each deploy func.
136-
if c.Debug {
137-
log.Println("sqldb.DeploySchema (DeployFuncs)...")
138-
}
140+
c.debugPrintln("sqldb.DeploySchema (DeployFuncs)...")
139141
for _, f := range c.DeployFuncs {
140-
//get function name for diagnostics
142+
//Get function name for diagnostics.
141143
rawNameWithPath := runtime.FuncForPC(reflect.ValueOf(f).Pointer()).Name()
142144
funcName := path.Base(rawNameWithPath)
143145

144-
if c.Debug {
145-
log.Println(funcName)
146-
}
146+
//Log out some infor about the func being run for diagnostics.
147+
c.debugPrintln(funcName)
147148

149+
//Execute the func. Always log on error so users can identify query that has
150+
//an error.
148151
innerErr := f()
149152
if innerErr != nil {
150-
log.Println("Error with deploy func", funcName)
153+
log.Println("sqldb.DeploySchema() error with deploy func", funcName)
154+
c.Close()
151155
return innerErr
152156
}
153157

154158
}
155-
if c.Debug {
156-
log.Println("sqldb.DeploySchema (DeployFuncs)...done")
157-
}
159+
c.debugPrintln("sqldb.DeploySchema (DeployFuncs)...done")
158160

159-
//Not closing the connection upon success since user may want to start interacting
160-
//with the db right away and this removes the need to call Connect() right after
161-
//this func.
161+
if ops.CloseConnection {
162+
//close is handled by defer above.
163+
c.debugPrintln("Connection closed upon successful deploy.")
164+
} else {
165+
c.debugPrintln("Connection left open after successful deploy.")
166+
}
162167

163168
return
164169
}
165170

166-
//DeploySchema deploys the database for the default package level config.
167-
func DeploySchema(dontInsert bool) (err error) {
168-
return config.DeploySchema(dontInsert)
171+
//DeploySchemaWithOps deploys the database for the default package level config.
172+
func DeploySchemaWithOps(ops DeploySchemaOptions) (err error) {
173+
return config.DeploySchemaWithOps(ops)
174+
}
175+
176+
//DeploySchema runs DeploySchemaWithOps with some defaults set. This was implemented
177+
//to support legacy compatibility while expanding the feature set with deploy options.
178+
func (c *Config) DeploySchema(skipInsert bool) (err error) {
179+
ops := DeploySchemaOptions{
180+
SkipInsert: skipInsert,
181+
CloseConnection: true, //legacy
182+
}
183+
return c.DeploySchemaWithOps(ops)
184+
}
185+
186+
//DeploySchema runs DeploySchemaWithOps with some defaults set for the default package
187+
//level config. This was implemented to support legacy compatibility while expanding
188+
//the feature set with deploy options.
189+
func DeploySchema(skipInsert bool) (err error) {
190+
return config.DeploySchema(skipInsert)
169191
}

0 commit comments

Comments
 (0)