From 8a1218c40401a7343885afbe881de53ec0b46936 Mon Sep 17 00:00:00 2001 From: zhangwenjian Date: Wed, 7 Oct 2026 17:40:49 +0800 Subject: [PATCH] =?UTF-8?q?fix=F0=9F=90=9B:=20only=20treat=20exact=20in-me?= =?UTF-8?q?mory=20forms=20as=20in-memory=20sqlite=20sources?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A file whose name merely contained :memory: or mode=memory was classified as an in-memory database and skipped the locking defaults. Parse the query and match the exact filename forms or a mode=memory parameter. --- common/database/sqlite_dsn.go | 19 ++++++++++++++++--- common/database/sqlite_dsn_test.go | 8 ++++++++ 2 files changed, 24 insertions(+), 3 deletions(-) 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 {