diff --git a/go.mod b/go.mod index f1f06c3..fef38a0 100644 --- a/go.mod +++ b/go.mod @@ -13,7 +13,7 @@ require ( github.com/stretchr/testify v1.11.1 github.com/umputun/go-flags v1.5.1 golang.org/x/crypto v0.49.0 - modernc.org/sqlite v1.48.0 + modernc.org/sqlite v1.48.2 ) require ( diff --git a/go.sum b/go.sum index cc11941..95ca963 100644 --- a/go.sum +++ b/go.sum @@ -148,8 +148,8 @@ modernc.org/opt v0.1.4 h1:2kNGMRiUjrp4LcaPuLY2PzUfqM/w9N23quVwhKt5Qm8= modernc.org/opt v0.1.4/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns= modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w= modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE= -modernc.org/sqlite v1.48.0 h1:ElZyLop3Q2mHYk5IFPPXADejZrlHu7APbpB0sF78bq4= -modernc.org/sqlite v1.48.0/go.mod h1:hWjRO6Tj/5Ik8ieqxQybiEOUXy0NJFNp2tpvVpKlvig= +modernc.org/sqlite v1.48.2 h1:5CnW4uP8joZtA0LedVqLbZV5GD7F/0x91AXeSyjoh5c= +modernc.org/sqlite v1.48.2/go.mod h1:hWjRO6Tj/5Ik8ieqxQybiEOUXy0NJFNp2tpvVpKlvig= modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0= modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A= modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= diff --git a/vendor/modernc.org/sqlite/CHANGELOG.md b/vendor/modernc.org/sqlite/CHANGELOG.md index 4709596..6e3f736 100644 --- a/vendor/modernc.org/sqlite/CHANGELOG.md +++ b/vendor/modernc.org/sqlite/CHANGELOG.md @@ -1,5 +1,55 @@ # Changelog + - 2026-04-06 v1.48.2: + - Fix ABI mapping mismatch in the pre-update hook trampoline that caused silent truncation of large 64-bit RowIDs. + - Ensure the Go trampoline signature correctly aligns with the public `sqlite3_preupdate_hook` C API, preventing data corruption for high-entropy keys (e.g., Snowflake IDs). + - See [GitLab merge request #98](https://gitlab.com/cznic/sqlite/-/merge_requests/98), thanks Josh Bleecher Snyder! + - Fix the memory allocator used in `(*conn).Deserialize`. + - Replace `tls.Alloc` with `sqlite3_malloc64` to prevent internal allocator corruption. This ensures the buffer is safely owned by SQLite, which may resize or free it due to the `SQLITE_DESERIALIZE_RESIZEABLE` and `SQLITE_DESERIALIZE_FREEONCLOSE` flags. + - Prevent a memory leak by properly freeing the allocated buffer if fetching the main database name fails before handing ownership to SQLite. + - See [GitLab merge request #100](https://gitlab.com/cznic/sqlite/-/merge_requests/100), thanks Josh Bleecher Snyder! + - Fix `(*conn).Deserialize` to explicitly reject `nil` or empty byte slices. + - Prevent silent database disconnection and connection pool corruption caused by SQLite's default behavior when `sqlite3_deserialize` receives a 0-length buffer. + - See [GitLab merge request #101](https://gitlab.com/cznic/sqlite/-/merge_requests/101), thanks Josh Bleecher Snyder! + - Fix `commitHookTrampoline` and `rollbackHookTrampoline` signatures by removing the unused `pCsr` parameter. + - Aligns internal hook callbacks accurately with the underlying SQLite C API, cleaning up the code to prevent potential future confusion or bugs. + - See [GitLab merge request #102](https://gitlab.com/cznic/sqlite/-/merge_requests/102), thanks Josh Bleecher Snyder! + - Fix `checkptr` instrumentation failures during `go test -race` when registering and using virtual tables (`vtab`). + - Allocate `sqlite3_module` instances using the C allocator (`libc.Xcalloc`) instead of the Go heap. This ensures transpiled C code can safely perform pointer operations on the struct without tripping Go's pointer checks. + - See [GitLab merge request #103](https://gitlab.com/cznic/sqlite/-/merge_requests/103), thanks Josh Bleecher Snyder! + - Fix data race on `mutex.id` in the `mutexTry` non-recursive path. + - Ensure consistent atomic writes (`atomic.StoreInt32`) to prevent data races with atomic loads in `mutexHeld` and `mutexNotheld` during concurrent execution. + - See [GitLab merge request #104](https://gitlab.com/cznic/sqlite/-/merge_requests/104), thanks Josh Bleecher Snyder! + - Fix resource leak in `(*Backup).Commit` where the destination connection was not closed on error. + - Ensure `dstConn` is properly closed when `sqlite3_backup_finish` fails, preventing file descriptor, TLS, and memory leaks. + - See [GitLab merge request #105](https://gitlab.com/cznic/sqlite/-/merge_requests/105), thanks Josh Bleecher Snyder! + - Fix `Exec` to fully drain rows when encountering `SQLITE_ROW`, preventing silent data loss in DML statements. + - Previously, `Exec` aborted after the first row, meaning `INSERT`, `UPDATE`, or `DELETE` statements with a `RETURNING` clause would fail to process subsequent rows. The execution path now correctly loops until `SQLITE_DONE` and properly respects context cancellations during the drain loop, fully aligning with native C `sqlite3_exec` semantics. + - See [GitLab merge request #106](https://gitlab.com/cznic/sqlite/-/merge_requests/106), thanks Josh Bleecher Snyder! + - Fix "Shadowed err value (stmt.go)". + - See [GitLab issue #249](https://gitlab.com/cznic/sqlite/-/work_items/249), thanks Emrecan BATI! + - Fix silent omission of virtual table savepoint callbacks by correctly setting the sqlite3_module version. + - See [GitLab merge request #107](https://gitlab.com/cznic/sqlite/-/merge_requests/107), thanks Josh Bleecher Snyder! + - Fix `vfsRead` to properly handle partial and fragmented reads from `io.Reader`. + - Replace `f.Read` with `io.ReadFull` to ensure the buffer is fully populated, preventing premature `SQLITE_IOERR_SHORT_READ` errors on valid mid-stream partial reads. Unread tail bytes at EOF are now efficiently zero-filled using the built-in `clear` function. + - See [GitLab merge request #108](https://gitlab.com/cznic/sqlite/-/merge_requests/108), thanks Josh Bleecher Snyder! + - Refactor internal error formatting to safely handle uninitialized or closed database pointers. + - Prevent a misleading "out of memory" error message when an operation fails and the underlying SQLite database handle is `NULL` (`db == 0`). + - See [GitLab merge request #109](https://gitlab.com/cznic/sqlite/-/merge_requests/109), thanks Josh Bleecher Snyder! + - Fix error handling in database backup and restore initialization (`sqlite3_backup_init`). + - Ensure error codes and messages are accurately read from the destination database handle rather than hardcoding the source or remote handle. This prevents swallowed errors or mismatched "not an error" messages when a backup or restore operation fails to start. + - See [GitLab merge request #111](https://gitlab.com/cznic/sqlite/-/merge_requests/111), thanks Josh Bleecher Snyder! + - Fix database handle and C-heap memory leaks when `sqlite3_open_v2` fails. + - Ensure `sqlite3_close_v2` is called on the partially allocated database handle during a failed open, and explicitly close `libc.TLS` in `newConn` to prevent resource leakage. + - Prevent misleading "out of memory" error messages on failed connections by correctly extracting the exact error string from the allocated handle before it is closed. + - See [GitLab merge request #112](https://gitlab.com/cznic/sqlite/-/merge_requests/112), thanks Josh Bleecher Snyder! + + - 2026-04-03 v1.48.1: + - Fix memory leaks and double-free vulnerabilities in the multi-statement query execution path. + - Ensure bind-parameter allocations are reliably freed via strict ownership transfer if an error occurs mid-loop or if multiple statements bind parameters. + - Fix a resource leak where a subsequent statement's error could orphan a previously generated `rows` object without closing it, leaking the prepared statement handle. + - See [GitLab merge request #96](https://gitlab.com/cznic/sqlite/-/merge_requests/96), thanks Josh Bleecher Snyder! + - 2026-03-27 v1.48.0: - Add `_timezone` DSN query parameter to apply IANA timezones (e.g., "America/New_York") to both reads and writes. - Writes will convert `time.Time` values to the target timezone before formatting as a string. diff --git a/vendor/modernc.org/sqlite/Makefile b/vendor/modernc.org/sqlite/Makefile index 4955e98..929458b 100644 --- a/vendor/modernc.org/sqlite/Makefile +++ b/vendor/modernc.org/sqlite/Makefile @@ -61,7 +61,7 @@ edit: editor: go test -c -o /dev/null go build -v -o /dev/null ./... - cd vendor_libsqlite3 && go build -o /dev/null main.go + cd vendor_libs && go build -o /dev/null main.go test: go test -v -timeout 24h diff --git a/vendor/modernc.org/sqlite/backup.go b/vendor/modernc.org/sqlite/backup.go index 24c1590..cc6886a 100644 --- a/vendor/modernc.org/sqlite/backup.go +++ b/vendor/modernc.org/sqlite/backup.go @@ -60,6 +60,7 @@ func (b *Backup) Commit() (driver.Conn, error) { if rc == sqlite3.SQLITE_OK { return b.dstConn, nil } else { + b.dstConn.Close() return nil, b.srcConn.errstr(rc) } } diff --git a/vendor/modernc.org/sqlite/conn.go b/vendor/modernc.org/sqlite/conn.go index 948eca5..3a9708d 100644 --- a/vendor/modernc.org/sqlite/conn.go +++ b/vendor/modernc.org/sqlite/conn.go @@ -64,6 +64,7 @@ func newConn(dsn string) (*conn, error) { sqlite3.SQLITE_OPEN_URI, ) if err != nil { + c.tls.Close() return nil, err } @@ -310,9 +311,7 @@ func (c *conn) bind(pstmt uintptr, n int, args []driver.NamedValue) (allocs []ui return } - for _, v := range allocs { - c.free(v) - } + c.freeAllocs(allocs) allocs = nil }() @@ -619,7 +618,18 @@ func (c *conn) openV2(name, vfsName string, flags int32) (uintptr, error) { } if rc := sqlite3.Xsqlite3_open_v2(c.tls, s, p, flags, vfs); rc != sqlite3.SQLITE_OK { - return 0, c.errstr(rc) + dbh := *(*uintptr)(unsafe.Pointer(p)) + // Per SQLite docs, sqlite3_open_v2 may allocate a handle even on + // failure. The error message is stored on that handle, and it must + // be closed to avoid leaking resources. + var err error + if dbh != 0 { + err = errstrForDB(c.tls, rc, dbh) + sqlite3.Xsqlite3_close_v2(c.tls, dbh) + } else { + err = c.errstr(rc) + } + return 0, err } return *(*uintptr)(unsafe.Pointer(p)), nil @@ -639,18 +649,31 @@ func (c *conn) free(p uintptr) { } } +func (c *conn) freeAllocs(allocs []uintptr) { + for _, v := range allocs { + c.free(v) + } +} + // C documentation // // const char *sqlite3_errstr(int); func (c *conn) errstr(rc int32) error { - p := sqlite3.Xsqlite3_errstr(c.tls, rc) - str := libc.GoString(p) - p = sqlite3.Xsqlite3_errmsg(c.tls, c.db) + return errstrForDB(c.tls, rc, c.db) +} + +func errstrForDB(tls *libc.TLS, rc int32, db uintptr) error { + pStr := sqlite3.Xsqlite3_errstr(tls, rc) + str := libc.GoString(pStr) var s string if rc == sqlite3.SQLITE_BUSY { s = " (SQLITE_BUSY)" } - switch msg := libc.GoString(p); { + if db == 0 { + return &Error{msg: fmt.Sprintf("%s (%v)%s", str, rc, s), code: int(rc)} + } + pMsg := sqlite3.Xsqlite3_errmsg(tls, db) + switch msg := libc.GoString(pMsg); { case msg == str: return &Error{msg: fmt.Sprintf("%s (%v)%s", str, rc, s), code: int(rc)} default: @@ -932,15 +955,22 @@ func (c *conn) Serialize() (v []byte, err error) { return v, nil } -// Deserialize restore a database from the content returned by Serialize. +// Deserialize restores a database from the content returned by Serialize. func (c *conn) Deserialize(buf []byte) (err error) { bufLen := len(buf) - pBuf := c.tls.Alloc(bufLen) // free will be done if it fails or on close, must not be freed here + if bufLen == 0 { + return fmt.Errorf("sqlite: empty buffer passed to Deserialize") + } + pBuf := sqlite3.Xsqlite3_malloc64(c.tls, uint64(bufLen)) + if pBuf == 0 { + return fmt.Errorf("sqlite: cannot allocate %d bytes for deserialize", bufLen) + } copy((*libc.RawMem)(unsafe.Pointer(pBuf))[:bufLen:bufLen], buf) zSchema := sqlite3.Xsqlite3_db_name(c.tls, c.db, 0) if zSchema == 0 { + sqlite3.Xsqlite3_free(c.tls, pBuf) return fmt.Errorf("failed to get main db name") } @@ -997,8 +1027,12 @@ func (c *conn) backup(remoteConn *conn, restore bool) (_ *Backup, finalErr error pBackup = sqlite3.Xsqlite3_backup_init(c.tls, remoteConn.db, dstSchema, c.db, srcSchema) } if pBackup <= 0 { - rc := sqlite3.Xsqlite3_errcode(c.tls, remoteConn.db) - return nil, c.errstr(rc) + destDb := remoteConn.db + if restore { + destDb = c.db + } + rc := sqlite3.Xsqlite3_errcode(c.tls, destDb) + return nil, errstrForDB(c.tls, rc, destDb) } return &Backup{srcConn: c, dstConn: remoteConn, pBackup: pBackup}, nil diff --git a/vendor/modernc.org/sqlite/driver.go b/vendor/modernc.org/sqlite/driver.go index d7323c3..b49444a 100644 --- a/vendor/modernc.org/sqlite/driver.go +++ b/vendor/modernc.org/sqlite/driver.go @@ -12,6 +12,8 @@ import ( ) // Driver implements database/sql/driver.Driver. +// +// Registration functions and methods must be called before the first call to Open. type Driver struct { // user defined functions that are added to every new connection on Open udfs map[string]*userDefinedFunction diff --git a/vendor/modernc.org/sqlite/lib/mutex.go b/vendor/modernc.org/sqlite/lib/mutex.go index f076e2e..4c68a0d 100644 --- a/vendor/modernc.org/sqlite/lib/mutex.go +++ b/vendor/modernc.org/sqlite/lib/mutex.go @@ -253,7 +253,7 @@ func mutexTry(tls *libc.TLS, m uintptr) int32 { // Non-recursive mutex if !(*mutex)(unsafe.Pointer(m)).recursive { if (*mutex)(unsafe.Pointer(m)).TryLock() { - (*mutex)(unsafe.Pointer(m)).id = tls.ID + atomic.StoreInt32(&(*mutex)(unsafe.Pointer(m)).id, tls.ID) return SQLITE_OK } diff --git a/vendor/modernc.org/sqlite/pre_update_hook.go b/vendor/modernc.org/sqlite/pre_update_hook.go index 9a00fe5..95df285 100644 --- a/vendor/modernc.org/sqlite/pre_update_hook.go +++ b/vendor/modernc.org/sqlite/pre_update_hook.go @@ -182,7 +182,7 @@ func (d *SQLitePreUpdateData) value(ppValue uintptr, i int, new bool) (any, erro return src, nil } -func preUpdateHookTrampoline(tls *libc.TLS, handle uintptr, pCsr uintptr, op int32, zDb uintptr, pTab uintptr, iKey1 int64, iReg int32, iBlobWrite int32) { +func preUpdateHookTrampoline(tls *libc.TLS, handle uintptr, pCsr uintptr, op int32, zDb uintptr, pTab uintptr, iKey1 int64, iKey2 int64) { xPreUpdateHandlers.mu.RLock() xPreUpdateHandler := xPreUpdateHandlers.m[handle] xPreUpdateHandlers.mu.RUnlock() @@ -197,12 +197,12 @@ func preUpdateHookTrampoline(tls *libc.TLS, handle uintptr, pCsr uintptr, op int DatabaseName: libc.GoString(zDb), TableName: libc.GoString(pTab), OldRowID: iKey1, - NewRowID: int64(iReg), + NewRowID: iKey2, } xPreUpdateHandler(data) } -func commitHookTrampoline(tls *libc.TLS, handle uintptr, pCsr uintptr) int32 { +func commitHookTrampoline(tls *libc.TLS, handle uintptr) int32 { xCommitHandlers.mu.RLock() xCommitHandler := xCommitHandlers.m[handle] xCommitHandlers.mu.RUnlock() @@ -214,7 +214,7 @@ func commitHookTrampoline(tls *libc.TLS, handle uintptr, pCsr uintptr) int32 { return xCommitHandler() } -func rollbackHookTrampoline(tls *libc.TLS, handle uintptr, pCsr uintptr) { +func rollbackHookTrampoline(tls *libc.TLS, handle uintptr) { xRollbackHandlers.mu.RLock() xRollbackHandler := xRollbackHandlers.m[handle] xRollbackHandlers.mu.RUnlock() diff --git a/vendor/modernc.org/sqlite/rows.go b/vendor/modernc.org/sqlite/rows.go index a176796..8dcbae5 100644 --- a/vendor/modernc.org/sqlite/rows.go +++ b/vendor/modernc.org/sqlite/rows.go @@ -27,8 +27,13 @@ type rows struct { reuseStmt bool // If true, Close() resets instead of finalizing } -func newRows(c *conn, pstmt uintptr, allocs []uintptr, empty bool) (r *rows, err error) { - r = &rows{c: c, pstmt: pstmt, allocs: allocs, empty: empty} +func newRows(c *conn, pstmt uintptr, allocs *[]uintptr, empty bool) (r *rows, err error) { + var a []uintptr + if allocs != nil { + a = *allocs + *allocs = nil + } + r = &rows{c: c, pstmt: pstmt, allocs: a, empty: empty} defer func() { if err != nil { @@ -54,9 +59,7 @@ func newRows(c *conn, pstmt uintptr, allocs []uintptr, empty bool) (r *rows, err // Close closes the rows iterator. func (r *rows) Close() (err error) { - for _, v := range r.allocs { - r.c.free(v) - } + r.c.freeAllocs(r.allocs) r.allocs = nil if r.reuseStmt { diff --git a/vendor/modernc.org/sqlite/stmt.go b/vendor/modernc.org/sqlite/stmt.go index f585205..0d9ea9b 100644 --- a/vendor/modernc.org/sqlite/stmt.go +++ b/vendor/modernc.org/sqlite/stmt.go @@ -137,11 +137,7 @@ func (s *stmt) exec(ctx context.Context, args []driver.NamedValue) (r driver.Res } // Free allocations after step if len(allocs) != 0 { - defer func() { - for _, v := range allocs { - s.c.free(v) - } - }() + defer func() { s.c.freeAllocs(allocs) }() } } @@ -153,12 +149,29 @@ func (s *stmt) exec(ctx context.Context, args []driver.NamedValue) (r driver.Res // Handle Result switch rc & 0xff { - case sqlite3.SQLITE_DONE, sqlite3.SQLITE_ROW: + case sqlite3.SQLITE_DONE: + r, err = newResult(s.c) + case sqlite3.SQLITE_ROW: + // Step to completion, matching C sqlite3_exec() + // semantics. Required for DML RETURNING correctness; + // also drains SELECT results if passed to Exec. + for rc&0xff == sqlite3.SQLITE_ROW { + if atomic.LoadInt32(&done) != 0 { + return ctx.Err() + } + rc, err = s.c.step(s.pstmt) + if err != nil { + return err + } + } + if rc&0xff != sqlite3.SQLITE_DONE { + return s.c.errstr(int32(rc)) + } r, err = newResult(s.c) default: return s.c.errstr(int32(rc)) } - return nil + return err }() // RESET (Crucial: Do not finalize) @@ -182,7 +195,7 @@ func (s *stmt) exec(ctx context.Context, args []driver.NamedValue) (r driver.Res if pstmt == 0 { continue } - err = func() (err error) { + err = func() error { n, err := s.c.bindParameterCount(pstmt) if err != nil { return err @@ -195,11 +208,7 @@ func (s *stmt) exec(ctx context.Context, args []driver.NamedValue) (r driver.Res } if len(allocs) != 0 { - defer func() { - for _, v := range allocs { - s.c.free(v) - } - }() + defer func() { s.c.freeAllocs(allocs) }() } } @@ -209,13 +218,30 @@ func (s *stmt) exec(ctx context.Context, args []driver.NamedValue) (r driver.Res } switch rc & 0xff { - case sqlite3.SQLITE_DONE, sqlite3.SQLITE_ROW: + case sqlite3.SQLITE_DONE: + r, err = newResult(s.c) + case sqlite3.SQLITE_ROW: + // Step to completion, matching C sqlite3_exec() + // semantics. Required for DML RETURNING correctness; + // also drains SELECT results if passed to Exec. + for rc&0xff == sqlite3.SQLITE_ROW { + if atomic.LoadInt32(&done) != 0 { + return ctx.Err() + } + rc, err = s.c.step(pstmt) + if err != nil { + return err + } + } + if rc&0xff != sqlite3.SQLITE_DONE { + return s.c.errstr(int32(rc)) + } r, err = newResult(s.c) default: return s.c.errstr(int32(rc)) } - return nil + return err }() e := s.c.finalize(pstmt) @@ -270,8 +296,6 @@ func (s *stmt) query(ctx context.Context, args []driver.NamedValue) (r driver.Ro } } - var allocs []uintptr - defer func() { if ctx != nil && atomic.LoadInt32(&done) != 0 { if r != nil { @@ -279,7 +303,7 @@ func (s *stmt) query(ctx context.Context, args []driver.NamedValue) (r driver.Ro } r, err = nil, ctx.Err() } else if r == nil && err == nil { - r, err = newRows(s.c, pstmt, allocs, true) + r, err = newRows(s.c, pstmt, nil, true) } if pstmt != 0 { @@ -297,6 +321,7 @@ func (s *stmt) query(ctx context.Context, args []driver.NamedValue) (r driver.Ro // OPTIMIZED PATH: Single Cached Statement if s.pstmt != 0 { + var allocs []uintptr // Bind n, err := s.c.bindParameterCount(s.pstmt) if err != nil { @@ -312,9 +337,7 @@ func (s *stmt) query(ctx context.Context, args []driver.NamedValue) (r driver.Ro rc, err := s.c.step(s.pstmt) if err != nil { // On error, we must free allocs manually because 'newRows' won't take ownership - for _, v := range allocs { - s.c.free(v) - } + s.c.freeAllocs(allocs) s.c.reset(s.pstmt) s.c.clearBindings(s.pstmt) return nil, err @@ -324,7 +347,7 @@ func (s *stmt) query(ctx context.Context, args []driver.NamedValue) (r driver.Ro switch rc & 0xff { case sqlite3.SQLITE_ROW: // Pass reuseStmt=true - if r, err = newRows(s.c, s.pstmt, allocs, false); err != nil { + if r, err = newRows(s.c, s.pstmt, &allocs, false); err != nil { s.c.reset(s.pstmt) s.c.clearBindings(s.pstmt) return nil, err @@ -341,7 +364,7 @@ func (s *stmt) query(ctx context.Context, args []driver.NamedValue) (r driver.Ro // Actually, if we pass reuseStmt=true to an empty set, // rows.Close() will eventually reset it. - if r, err = newRows(s.c, s.pstmt, allocs, true); err != nil { + if r, err = newRows(s.c, s.pstmt, &allocs, true); err != nil { s.c.reset(s.pstmt) s.c.clearBindings(s.pstmt) return nil, err @@ -351,9 +374,7 @@ func (s *stmt) query(ctx context.Context, args []driver.NamedValue) (r driver.Ro default: // Error case - for _, v := range allocs { - s.c.free(v) - } + s.c.freeAllocs(allocs) s.c.reset(s.pstmt) s.c.clearBindings(s.pstmt) return nil, s.c.errstr(int32(rc)) @@ -363,6 +384,9 @@ func (s *stmt) query(ctx context.Context, args []driver.NamedValue) (r driver.Ro // FALLBACK PATH: Multi-statement script for psql := s.psql; *(*byte)(unsafe.Pointer(psql)) != 0 && atomic.LoadInt32(&done) == 0; { if pstmt, err = s.c.prepareV2(&psql); err != nil { + if r != nil { + r.Close() + } return nil, err } @@ -371,6 +395,9 @@ func (s *stmt) query(ctx context.Context, args []driver.NamedValue) (r driver.Ro } err = func() (err error) { + var allocs []uintptr + defer func() { s.c.freeAllocs(allocs) }() + n, err := s.c.bindParameterCount(pstmt) if err != nil { return err @@ -392,15 +419,14 @@ func (s *stmt) query(ctx context.Context, args []driver.NamedValue) (r driver.Ro if r != nil { r.Close() } - if r, err = newRows(s.c, pstmt, allocs, false); err != nil { + if r, err = newRows(s.c, pstmt, &allocs, false); err != nil { return err } - pstmt = 0 return nil case sqlite3.SQLITE_DONE: if r == nil { - if r, err = newRows(s.c, pstmt, allocs, true); err != nil { + if r, err = newRows(s.c, pstmt, &allocs, true); err != nil { return err } pstmt = 0 @@ -416,10 +442,9 @@ func (s *stmt) query(ctx context.Context, args []driver.NamedValue) (r driver.Ro if r != nil { r.Close() } - if r, err = newRows(s.c, pstmt, allocs, true); err != nil { + if r, err = newRows(s.c, pstmt, &allocs, true); err != nil { return err } - pstmt = 0 } return nil @@ -435,6 +460,9 @@ func (s *stmt) query(ctx context.Context, args []driver.NamedValue) (r driver.Ro } if err != nil { + if r != nil { + r.Close() // r is from a previous iteration; clean up since we won't return it + } return nil, err } } diff --git a/vendor/modernc.org/sqlite/vtab.go b/vendor/modernc.org/sqlite/vtab.go index 74d85bc..d77b33c 100644 --- a/vendor/modernc.org/sqlite/vtab.go +++ b/vendor/modernc.org/sqlite/vtab.go @@ -11,6 +11,7 @@ import ( "unsafe" "modernc.org/libc" + "modernc.org/libc/sys/types" sqlite3 "modernc.org/sqlite/lib" "modernc.org/sqlite/vtab" ) @@ -36,13 +37,14 @@ var ( name2id: make(map[string]uintptr), } - // nativeModules holds sqlite3_module instances for registered modules. We - // keep them in Go memory so their addresses remain stable for the C layer. + // nativeModules holds sqlite3_module instances for registered modules, + // allocated via libc so that transpiled C code can access them without + // tripping Go's checkptr instrumentation. nativeModules = struct { mu sync.RWMutex - m map[string]*sqlite3.Sqlite3_module + m map[string]uintptr }{ - m: make(map[string]*sqlite3.Sqlite3_module), + m: make(map[string]uintptr), } // vtabTables maps sqlite3_vtab* (pVtab) to the corresponding Go Table. @@ -119,13 +121,24 @@ func (c *conn) registerSingleModule(name string, m vtab.Module) error { nativeModules.mu.Lock() defer nativeModules.mu.Unlock() - var mod *sqlite3.Sqlite3_module + var modPtr uintptr if existing, exists := nativeModules.m[name]; exists { - mod = existing + modPtr = existing } else { - // Build a sqlite3_module descriptor with trampolines. - mod = &sqlite3.Sqlite3_module{} - mod.FiVersion = 1 + // Allocate with the C allocator so the transpiled SQLite code can + // access the struct without tripping Go's checkptr. + modPtr = libc.Xcalloc(c.tls, 1, types.Size_t(unsafe.Sizeof(sqlite3.Sqlite3_module{}))) + if modPtr == 0 { + if !ok { + vtabModules.mu.Lock() + delete(vtabModules.name2id, name) + delete(vtabModules.m, modID) + vtabModules.mu.Unlock() + } + return fmt.Errorf("sqlite: failed to allocate module %q", name) + } + mod := (*sqlite3.Sqlite3_module)(unsafe.Pointer(modPtr)) + mod.FiVersion = 2 mod.FxCreate = cFuncPointer(vtabCreateTrampoline) mod.FxConnect = cFuncPointer(vtabConnectTrampoline) mod.FxBestIndex = cFuncPointer(vtabBestIndexTrampoline) @@ -149,7 +162,7 @@ func (c *conn) registerSingleModule(name string, m vtab.Module) error { mod.FxRelease = cFuncPointer(vtabReleaseTrampoline) mod.FxRollbackTo = cFuncPointer(vtabRollbackToTrampoline) - nativeModules.m[name] = mod + nativeModules.m[name] = modPtr } // Prepare C string for module name. @@ -160,7 +173,7 @@ func (c *conn) registerSingleModule(name string, m vtab.Module) error { defer libc.Xfree(c.tls, zName) // Register the module with this connection. - if rc := sqlite3.Xsqlite3_create_module_v2(c.tls, c.db, zName, uintptr(unsafe.Pointer(mod)), modID, 0); rc != sqlite3.SQLITE_OK { + if rc := sqlite3.Xsqlite3_create_module_v2(c.tls, c.db, zName, modPtr, modID, 0); rc != sqlite3.SQLITE_OK { return fmt.Errorf("create_module %q: %w", name, c.errstr(rc)) } return nil @@ -739,18 +752,19 @@ func vtabUpdateTrampoline(tls *libc.TLS, pVtab uintptr, argc int32, argv uintptr } // INSERT or UPDATE: argc == N+2. argv[0]=oldRowid (NULL for insert), - // argv[1..N]=column values, argv[N+1]=newRowid (or desired rowid for insert, may be NULL). + // argv[1]=newRowid (or desired rowid for insert, may be NULL), + // argv[2..N+1]=column values. if argc < 3 { return sqlite3.SQLITE_MISUSE } nCols := argc - 2 - // Extract column values - colsPtr := argv + uintptr(1)*sqliteValPtrSize + // Extract column values starting from argv[2] + colsPtr := argv + uintptr(2)*sqliteValPtrSize cols := functionArgs(tls, nCols, colsPtr) // Determine old/new rowid oldPtr := *(*uintptr)(unsafe.Pointer(argv + uintptr(0)*sqliteValPtrSize)) - newPtr := *(*uintptr)(unsafe.Pointer(argv + uintptr(argc-1)*sqliteValPtrSize)) + newPtr := *(*uintptr)(unsafe.Pointer(argv + uintptr(1)*sqliteValPtrSize)) oldIsNull := sqlite3.Xsqlite3_value_type(tls, oldPtr) == sqlite3.SQLITE_NULL newIsNull := sqlite3.Xsqlite3_value_type(tls, newPtr) == sqlite3.SQLITE_NULL diff --git a/vendor/modules.txt b/vendor/modules.txt index d64a581..183e203 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -152,7 +152,7 @@ modernc.org/mathutil # modernc.org/memory v1.11.0 ## explicit; go 1.23.0 modernc.org/memory -# modernc.org/sqlite v1.48.0 +# modernc.org/sqlite v1.48.2 ## explicit; go 1.25.0 modernc.org/sqlite modernc.org/sqlite/lib