Skip to content

Commit

Permalink
switch rqlite to using cache=shared for in-memory databases
Browse files Browse the repository at this point in the history
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]>
  • Loading branch information
Tycho Andersen committed Jan 27, 2017
1 parent 79dcb00 commit 53a1b8f
Show file tree
Hide file tree
Showing 3 changed files with 45 additions and 9 deletions.
39 changes: 38 additions & 1 deletion db/db.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ func OpenWithDSN(dbPath, dsn string) (*DB, error) {

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

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

/* Here, we Ping() to activate the in-memory database. If we don't do
* this, and e.g. we do an Execute() first, we'll get a raw connection
* to the in memory database, insert the data, close it, which will
* cause the data to be lost forever when sqlite3 purges the in-memory
* database because there aren't any more connections. So, let's just
* open one at the beginning.
*/
err = db.db.Ping()
if err != nil {
db.Close()
return nil, err
}

db.memory = true
return db, nil
}
Expand Down Expand Up @@ -160,6 +173,30 @@ func (db *DB) rawConn() (*sqlite3.SQLiteConn, error) {

// Close closes the underlying database connection.
func (db *DB) Close() error {
if db.memory {
/* Since we use the same in memory database (i.e. cache=shared)
* to avoid oddities with how the stdlib's sqlite driver works,
* we need to clean up after ourselves, since things will
* persist.
*/
rows, err := db.db.Query("SELECT name FROM sqlite_master WHERE type='table'")
if err != nil {
return db.db.Close()
}

tables := []string{}
for rows.Next() {
n := ""
rows.Scan(&n)
tables = append(tables, n)
}
rows.Close()

for _, t := range tables {
db.db.Exec(fmt.Sprintf("DROP TABLE %s", t))
}
}

return db.db.Close()
}

Expand Down
2 changes: 1 addition & 1 deletion db/db_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ func Test_LoadInMemory(t *testing.T) {
t.Fatalf("unexpected results for query, expected %s, got %s", exp, got)
}

inmem, err := LoadInMemoryWithDSN(path, "")
inmem, err := LoadInMemoryWithDSN(path, "mode=memory&cache=shared")
if err != nil {
t.Fatalf("failed to create loaded in-memory database: %s", err.Error())
}
Expand Down
13 changes: 6 additions & 7 deletions store/store_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -216,10 +216,8 @@ func Test_SingleNodeLoad(t *testing.T) {
s.WaitForLeader(10 * time.Second)

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

dump := `PRAGMA foreign_keys=OFF;
BEGIN TRANSACTION;
CREATE TABLE foo (id integer primary key asc, name text);
INSERT INTO "foo" VALUES(1,'bob');
INSERT INTO "foo" VALUES(2,'alice');
Expand All @@ -261,7 +258,6 @@ INSERT INTO "bar" VALUES(2,46);
INSERT INTO "bar" VALUES(3,8);
CREATE VIEW foobar as select name as Person, Age as age from foo inner join bar on foo.id == bar.nameid;
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;
COMMIT;
`
_, err := s.Execute([]string{dump}, false, false)
if err != nil {
Expand Down Expand Up @@ -289,8 +285,6 @@ func Test_SingleNodeLoadNoStatements(t *testing.T) {
s.WaitForLeader(10 * time.Second)

dump := `PRAGMA foreign_keys=OFF;
BEGIN TRANSACTION;
COMMIT;
`
_, err := s.Execute([]string{dump}, false, false)
if err != nil {
Expand Down Expand Up @@ -681,7 +675,12 @@ func mustNewStore(inmem bool) *Store {
path := mustTempDir()
defer os.RemoveAll(path)

cfg := NewDBConfig("", inmem)
dsn := ""
if inmem {
dsn = "mode=memory&cache=shared"
}

cfg := NewDBConfig(dsn, inmem)
s := New(&StoreConfig{
DBConf: cfg,
Dir: path,
Expand Down

0 comments on commit 53a1b8f

Please sign in to comment.