keep in-memory connections valid after a context-cancelled query (#196)
Closes #196 (closed).
Background
!74 originally fixed this bug by implementing driver.Validator and driver.SessionResetter, but the later fix for #198 added an sqlite3_is_interrupted check to (*conn).usable():
return c.db != 0 && sqlite3.Xsqlite3_is_interrupted(c.tls, c.db) == 0That check is correct for file-backed databases (the data is on disk so a fresh connection can re-open it) but wrong for in-memory databases: the connection is the database, so when database/sql discards an interrupted in-memory connection it loses the entire store. Per the discussion thread you (cznic) acknowledged the regression and suggested narrowing the check to file-backed databases only.
Fix
Detect at open time whether the database is in-memory and cache the answer on conn:
zMain, _ := libc.CString("main")
defer libc.Xfree(c.tls, zMain)
c.inMemory = libc.GoString(sqlite3.Xsqlite3_db_filename(c.tls, c.db, zMain)) == ""sqlite3_db_filename returns an empty string for :memory:, file::memory:, shared-cache memory URIs, and other unbacked databases, so this single check covers all flavours.
usable() then short-circuits to true for in-memory connections:
func (c *conn) usable() bool {
if c.db == 0 {
return false
}
if c.inMemory {
return true
}
return sqlite3.Xsqlite3_is_interrupted(c.tls, c.db) == 0
}File-backed connections behave exactly as before.
Test plan
TestInMemoryDBSurvivesContextCancel in all_test.go covers both halves:
- in-memory: opens a shared-cache
file::memory:DB withMaxOpenConns=1, inserts three rows, firesExecContextwith a pre-cancelled context, and asserts that a subsequentSELECT count(*)still sees the three rows. Fails onmaster(data lost), passes with the fix. - file-backed connection still discarded on interrupt: opens a temp-file DB, calls
sqlite3_interruptdirectly, and asserts(*conn).usable()returnsfalseso the existing#198fix is preserved.
go test -short -timeout 700s . passes (ok modernc.org/sqlite 406.93s).
Notes
- The in-memory detection is done once at open time, not on every
usable()call, so there is no per-query overhead. - The cached field doesn't change the public API; it's an unexported
bool. Xsqlite3_db_filenameis always available (the C function is part of the core, no extra compile flag needed).