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) == 0

That 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 with MaxOpenConns=1, inserts three rows, fires ExecContext with a pre-cancelled context, and asserts that a subsequent SELECT count(*) still sees the three rows. Fails on master (data lost), passes with the fix.
  • file-backed connection still discarded on interrupt: opens a temp-file DB, calls sqlite3_interrupt directly, and asserts (*conn).usable() returns false so the existing #198 fix 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_filename is always available (the C function is part of the core, no extra compile flag needed).

Merge request reports

Loading