diff --git a/common/database/sqlite_dsn.go b/common/database/sqlite_dsn.go index 7da673ee..7f28e5bd 100644 --- a/common/database/sqlite_dsn.go +++ b/common/database/sqlite_dsn.go @@ -30,10 +30,9 @@ var sqlitePragmas = [][2]string{ } // sqliteDSN returns source with the defaults in sqlitePragmas added. -// In-memory databases are returned untouched: WAL has no meaning there, and -// every connection to one is its own database anyway. +// In-memory databases are returned untouched: WAL has no meaning there. func sqliteDSN(source string) string { - if source == "" || strings.Contains(source, ":memory:") || strings.Contains(source, "mode=memory") { + if source == "" { return source } @@ -46,6 +45,9 @@ func sqliteDSN(source string) string { // Not something to rewrite blind; the driver will report it. return source } + if isMemoryDB(path, q) { + return source + } for _, p := range sqlitePragmas { if _, set := q[p[0]]; !set { q.Set(p[0], p[1]) @@ -54,6 +56,17 @@ func sqliteDSN(source string) string { return path + "?" + q.Encode() } +// isMemoryDB reports whether the source names an in-memory database: the exact +// filename forms sqlite recognises, or a mode=memory parameter. A file that +// merely has ":memory:" or "mode=memory" somewhere in its name is a file. +func isMemoryDB(path string, q url.Values) bool { + switch path { + case ":memory:", "file::memory:", "file:": + return true + } + return q.Get("mode") == "memory" +} + // withSqliteDefaults wraps open so every connection string handed to a sqlite3 // dialector - the primary and any replicas - carries the defaults above. func withSqliteDefaults(driver string, open func(string) gorm.Dialector) func(string) gorm.Dialector { diff --git a/common/database/sqlite_dsn_test.go b/common/database/sqlite_dsn_test.go index e4da5ff4..35def066 100644 --- a/common/database/sqlite_dsn_test.go +++ b/common/database/sqlite_dsn_test.go @@ -36,6 +36,14 @@ func TestSqliteDSN(t *testing.T) { } }) + t.Run("files that only look like memory databases get the defaults", func(t *testing.T) { + for _, s := range []string{"mode=memory.db", "/tmp/:memory:.db", "data/mode=memory/app.db"} { + if got := sqliteDSN(s); got == s { + t.Errorf("sqliteDSN(%q) was left untouched", s) + } + } + }) + t.Run("in-memory and empty untouched", func(t *testing.T) { for _, s := range []string{"", ":memory:", "file::memory:?cache=shared", "file:x?mode=memory"} { if got := sqliteDSN(s); got != s {