Skip to content

Commit 53a1b8f

Browse files
author
Tycho Andersen
committed
switch rqlite to using cache=shared for in-memory databases
This is a little hairy. Looking at things like [1], it's evident that golang's stdlib sql package doesn't work quite correctly with sqlite3's in-memory databases. In particular, the connection pooling causes problems, since there's no way to duplicate a connection to a particular in-memory database. So, we use cache=shared to force everything to point to the same in-memory database. However, this causes some problems (mostly that untill the last connection to this database is closed, the DB is not pruned). So we have to do a little cleanup after ourselves in this case. [1]: mattn/go-sqlite3#204 Signed-off-by: Tycho Andersen <[email protected]>
1 parent 79dcb00 commit 53a1b8f

File tree

3 files changed

+45
-9
lines changed

3 files changed

+45
-9
lines changed

db/db.go

+38-1
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ func OpenWithDSN(dbPath, dsn string) (*DB, error) {
8282

8383
// OpenInMemory opens an in-memory database.
8484
func OpenInMemory() (*DB, error) {
85-
return OpenInMemoryWithDSN("")
85+
return OpenInMemoryWithDSN("mode=memory&cache=shared")
8686
}
8787

8888
// OpenInMemoryWithDSN opens an in-memory database with a specific DSN.
@@ -92,6 +92,19 @@ func OpenInMemoryWithDSN(dsn string) (*DB, error) {
9292
return nil, err
9393
}
9494

95+
/* Here, we Ping() to activate the in-memory database. If we don't do
96+
* this, and e.g. we do an Execute() first, we'll get a raw connection
97+
* to the in memory database, insert the data, close it, which will
98+
* cause the data to be lost forever when sqlite3 purges the in-memory
99+
* database because there aren't any more connections. So, let's just
100+
* open one at the beginning.
101+
*/
102+
err = db.db.Ping()
103+
if err != nil {
104+
db.Close()
105+
return nil, err
106+
}
107+
95108
db.memory = true
96109
return db, nil
97110
}
@@ -160,6 +173,30 @@ func (db *DB) rawConn() (*sqlite3.SQLiteConn, error) {
160173

161174
// Close closes the underlying database connection.
162175
func (db *DB) Close() error {
176+
if db.memory {
177+
/* Since we use the same in memory database (i.e. cache=shared)
178+
* to avoid oddities with how the stdlib's sqlite driver works,
179+
* we need to clean up after ourselves, since things will
180+
* persist.
181+
*/
182+
rows, err := db.db.Query("SELECT name FROM sqlite_master WHERE type='table'")
183+
if err != nil {
184+
return db.db.Close()
185+
}
186+
187+
tables := []string{}
188+
for rows.Next() {
189+
n := ""
190+
rows.Scan(&n)
191+
tables = append(tables, n)
192+
}
193+
rows.Close()
194+
195+
for _, t := range tables {
196+
db.db.Exec(fmt.Sprintf("DROP TABLE %s", t))
197+
}
198+
}
199+
163200
return db.db.Close()
164201
}
165202

db/db_test.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ func Test_LoadInMemory(t *testing.T) {
6767
t.Fatalf("unexpected results for query, expected %s, got %s", exp, got)
6868
}
6969

70-
inmem, err := LoadInMemoryWithDSN(path, "")
70+
inmem, err := LoadInMemoryWithDSN(path, "mode=memory&cache=shared")
7171
if err != nil {
7272
t.Fatalf("failed to create loaded in-memory database: %s", err.Error())
7373
}

store/store_test.go

+6-7
Original file line numberDiff line numberDiff line change
@@ -216,10 +216,8 @@ func Test_SingleNodeLoad(t *testing.T) {
216216
s.WaitForLeader(10 * time.Second)
217217

218218
dump := `PRAGMA foreign_keys=OFF;
219-
BEGIN TRANSACTION;
220219
CREATE TABLE foo (id integer not null primary key, name text);
221220
INSERT INTO "foo" VALUES(1,'fiona');
222-
COMMIT;
223221
`
224222
_, err := s.Execute([]string{dump}, false, false)
225223
if err != nil {
@@ -250,7 +248,6 @@ func Test_SingleNodeSingleCommandTrigger(t *testing.T) {
250248
s.WaitForLeader(10 * time.Second)
251249

252250
dump := `PRAGMA foreign_keys=OFF;
253-
BEGIN TRANSACTION;
254251
CREATE TABLE foo (id integer primary key asc, name text);
255252
INSERT INTO "foo" VALUES(1,'bob');
256253
INSERT INTO "foo" VALUES(2,'alice');
@@ -261,7 +258,6 @@ INSERT INTO "bar" VALUES(2,46);
261258
INSERT INTO "bar" VALUES(3,8);
262259
CREATE VIEW foobar as select name as Person, Age as age from foo inner join bar on foo.id == bar.nameid;
263260
CREATE TRIGGER new_foobar instead of insert on foobar begin insert into foo (name) values (new.Person); insert into bar (nameid, age) values ((select id from foo where name == new.Person), new.Age); end;
264-
COMMIT;
265261
`
266262
_, err := s.Execute([]string{dump}, false, false)
267263
if err != nil {
@@ -289,8 +285,6 @@ func Test_SingleNodeLoadNoStatements(t *testing.T) {
289285
s.WaitForLeader(10 * time.Second)
290286

291287
dump := `PRAGMA foreign_keys=OFF;
292-
BEGIN TRANSACTION;
293-
COMMIT;
294288
`
295289
_, err := s.Execute([]string{dump}, false, false)
296290
if err != nil {
@@ -681,7 +675,12 @@ func mustNewStore(inmem bool) *Store {
681675
path := mustTempDir()
682676
defer os.RemoveAll(path)
683677

684-
cfg := NewDBConfig("", inmem)
678+
dsn := ""
679+
if inmem {
680+
dsn = "mode=memory&cache=shared"
681+
}
682+
683+
cfg := NewDBConfig(dsn, inmem)
685684
s := New(&StoreConfig{
686685
DBConf: cfg,
687686
Dir: path,

0 commit comments

Comments
 (0)