diff --git a/README.md b/README.md index ebda141..ddc083e 100644 --- a/README.md +++ b/README.md @@ -59,7 +59,7 @@ As a work around for other Unixes, you can use [`nolock=1`](https://www.sqlite.o - [ ] snapshots - [ ] session extension - [ ] resumable bulk update - - [ ] shared cache mode + - [ ] shared-cache mode - [ ] unlock-notify - [ ] custom SQL functions - [ ] custom VFSes diff --git a/blob.go b/blob.go index 742b833..0987612 100644 --- a/blob.go +++ b/blob.go @@ -8,7 +8,6 @@ import "io" type ZeroBlob int64 // Blob is a handle to an open BLOB. -// // It implements [io.ReadWriteSeeker] for incremental BLOB I/O. // // https://www.sqlite.org/c3ref/blob.html diff --git a/conn.go b/conn.go index 1e16d5b..361313c 100644 --- a/conn.go +++ b/conn.go @@ -6,6 +6,7 @@ import ( "fmt" "math" "net/url" + "runtime" "strings" "sync/atomic" "unsafe" @@ -30,8 +31,8 @@ type Conn struct { } // Open calls [OpenFlags] with [OPEN_READWRITE], [OPEN_CREATE] and [OPEN_URI]. -func Open(filename string) (conn *Conn, err error) { - return OpenFlags(filename, OPEN_READWRITE|OPEN_CREATE|OPEN_URI) +func Open(filename string) (*Conn, error) { + return openFlags(filename, OPEN_READWRITE|OPEN_CREATE|OPEN_URI) } // OpenFlags opens an SQLite database file as specified by the filename argument. @@ -41,7 +42,11 @@ func Open(filename string) (conn *Conn, err error) { // sqlite3.Open("file:demo.db?_pragma=busy_timeout(10000)&_pragma=locking_mode(normal)") // // https://www.sqlite.org/c3ref/open.html -func OpenFlags(filename string, flags OpenFlag) (conn *Conn, err error) { +func OpenFlags(filename string, flags OpenFlag) (*Conn, error) { + return openFlags(filename, flags) +} + +func openFlags(filename string, flags OpenFlag) (conn *Conn, err error) { ctx := context.Background() module, err := sqlite3.instantiateModule(ctx) if err != nil { @@ -50,6 +55,8 @@ func OpenFlags(filename string, flags OpenFlag) (conn *Conn, err error) { defer func() { if conn == nil { module.Close(ctx) + } else { + runtime.SetFinalizer(conn, finalizer[Conn](3)) } }() @@ -109,6 +116,7 @@ func (c *Conn) Close() error { } c.handle = 0 + runtime.SetFinalizer(c, nil) return c.mem.mod.Close(c.ctx) } @@ -133,9 +141,11 @@ func (c *Conn) MustPrepare(sql string) *Stmt { panic(err) } if s == nil { + s.Close() panic(emptyErr) } if !emptyStatement(tail) { + s.Close() panic(tailErr) } return s diff --git a/error.go b/error.go index ab5a7d6..8203f36 100644 --- a/error.go +++ b/error.go @@ -1,6 +1,7 @@ package sqlite3 import ( + "fmt" "runtime" "strconv" "strings" @@ -215,3 +216,11 @@ func assertErr() errorString { } return errorString(msg) } + +func finalizer[T any](skip int) func(*T) { + msg := fmt.Sprintf("sqlite3: %T not closed", new(T)) + if _, file, line, ok := runtime.Caller(skip + 1); ok && skip >= 0 { + msg += " (" + file + ":" + strconv.Itoa(line) + ")" + } + return func(*T) { panic(errorString(msg)) } +} diff --git a/tests/tx_test.go b/tests/tx_test.go index 9caf241..bb63531 100644 --- a/tests/tx_test.go +++ b/tests/tx_test.go @@ -29,6 +29,7 @@ func TestConn_Transaction_exec(t *testing.T) { if err != nil { t.Fatal(err) } + defer stmt.Close() if stmt.Step() { return stmt.ColumnInt(0) } @@ -117,6 +118,7 @@ func TestConn_Transaction_panic(t *testing.T) { if err != nil { t.Fatal(err) } + defer stmt.Close() if stmt.Step() { got := stmt.ColumnInt(0) if got != 1 { @@ -275,6 +277,7 @@ func TestConn_Savepoint_exec(t *testing.T) { if err != nil { t.Fatal(err) } + defer stmt.Close() if stmt.Step() { return stmt.ColumnInt(0) } @@ -361,6 +364,7 @@ func TestConn_Savepoint_panic(t *testing.T) { if err != nil { t.Fatal(err) } + defer stmt.Close() if stmt.Step() { got := stmt.ColumnInt(0) if got != 1 {