From a3cd5cd6fd2104b9b61d2b95f0a8653d63a1c24c Mon Sep 17 00:00:00 2001 From: dxbjavid Date: Thu, 11 Jun 2026 15:22:46 +0530 Subject: [PATCH 01/18] free leaked schema string in GetFilename --- sqlite3.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/sqlite3.go b/sqlite3.go index 19b467fd..ce0c2205 100644 --- a/sqlite3.go +++ b/sqlite3.go @@ -2069,7 +2069,9 @@ func (c *SQLiteConn) GetFilename(schemaName string) string { if schemaName == "" { schemaName = "main" } - return C.GoString(C.sqlite3_db_filename(c.db, C.CString(schemaName))) + cSchema := C.CString(schemaName) + defer C.free(unsafe.Pointer(cSchema)) + return C.GoString(C.sqlite3_db_filename(c.db, cSchema)) } // GetLimit returns the current value of a run-time limit. From 423f9605f33804999963ece923d2398333a0dc7f Mon Sep 17 00:00:00 2001 From: mattn Date: Thu, 18 Jun 2026 04:28:39 +0000 Subject: [PATCH 02/18] Make callback handle lookups lock-free --- callback.go | 43 ++++++++++++++++----- callback_bench_test.go | 85 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 119 insertions(+), 9 deletions(-) create mode 100644 callback_bench_test.go diff --git a/callback.go b/callback.go index b5cc803e..293d26db 100644 --- a/callback.go +++ b/callback.go @@ -29,6 +29,7 @@ import ( "math" "reflect" "sync" + "sync/atomic" "unsafe" ) @@ -104,24 +105,26 @@ type handleVal struct { } var handleLock sync.Mutex -var handleVals = make(map[unsafe.Pointer]handleVal) +var handleVals atomic.Value // stores map[unsafe.Pointer]handleVal func newHandle(db *SQLiteConn, v any) unsafe.Pointer { - handleLock.Lock() - defer handleLock.Unlock() val := handleVal{db: db, val: v} var p unsafe.Pointer = C.malloc(C.size_t(1)) if p == nil { panic("can't allocate 'cgo-pointer hack index pointer': ptr == nil") } - handleVals[p] = val + + handleLock.Lock() + defer handleLock.Unlock() + + next := cloneHandleVals(len(loadHandleVals()) + 1) + next[p] = val + handleVals.Store(next) return p } func lookupHandleVal(handle unsafe.Pointer) handleVal { - handleLock.Lock() - defer handleLock.Unlock() - return handleVals[handle] + return loadHandleVals()[handle] } func lookupHandle(handle unsafe.Pointer) any { @@ -131,12 +134,34 @@ func lookupHandle(handle unsafe.Pointer) any { func deleteHandles(db *SQLiteConn) { handleLock.Lock() defer handleLock.Unlock() - for handle, val := range handleVals { + + current := loadHandleVals() + if len(current) == 0 { + return + } + + next := make(map[unsafe.Pointer]handleVal, len(current)) + for handle, val := range current { if val.db == db { - delete(handleVals, handle) C.free(handle) + continue } + next[handle] = val + } + handleVals.Store(next) +} + +func loadHandleVals() map[unsafe.Pointer]handleVal { + m, _ := handleVals.Load().(map[unsafe.Pointer]handleVal) + return m +} + +func cloneHandleVals(size int) map[unsafe.Pointer]handleVal { + next := make(map[unsafe.Pointer]handleVal, size) + for handle, val := range loadHandleVals() { + next[handle] = val } + return next } // This is only here so that tests can refer to it. diff --git a/callback_bench_test.go b/callback_bench_test.go new file mode 100644 index 00000000..19e8ef5f --- /dev/null +++ b/callback_bench_test.go @@ -0,0 +1,85 @@ +// Copyright (C) 2019 Yasuhiro Matsumoto . +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file. + +//go:build cgo +// +build cgo + +package sqlite3 + +import ( + "sync" + "sync/atomic" + "testing" + "unsafe" +) + +func BenchmarkHandleLookupParallel(b *testing.B) { + d := SQLiteDriver{} + conn, err := d.Open(":memory:") + if err != nil { + b.Fatal(err) + } + defer conn.Close() + c := conn.(*SQLiteConn) + + handle := newHandle(c, func() {}) + + benchmarkHandleLookupParallel(b, func() any { + return lookupHandle(handle) + }) +} + +func BenchmarkHandleLookupBeforeAfter(b *testing.B) { + value := handleVal{val: func() {}} + handle := unsafe.Pointer(&value) + + before := mutexHandleTable{vals: map[unsafe.Pointer]handleVal{handle: value}} + after := atomicHandleTable{} + after.vals.Store(map[unsafe.Pointer]handleVal{handle: value}) + + b.Run("before_mutex", func(b *testing.B) { + benchmarkHandleLookupParallel(b, func() any { + return before.lookup(handle).val + }) + }) + b.Run("after_atomic", func(b *testing.B) { + benchmarkHandleLookupParallel(b, func() any { + return after.lookup(handle).val + }) + }) +} + +func benchmarkHandleLookupParallel(b *testing.B, lookup func() any) { + b.Helper() + b.ReportAllocs() + b.ResetTimer() + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + if lookup() == nil { + b.Fatal("lookup returned nil") + } + } + }) +} + +type mutexHandleTable struct { + mu sync.Mutex + vals map[unsafe.Pointer]handleVal +} + +func (t *mutexHandleTable) lookup(handle unsafe.Pointer) handleVal { + t.mu.Lock() + defer t.mu.Unlock() + return t.vals[handle] +} + +type atomicHandleTable struct { + vals atomic.Value +} + +func (t *atomicHandleTable) lookup(handle unsafe.Pointer) handleVal { + m, _ := t.vals.Load().(map[unsafe.Pointer]handleVal) + return m[handle] +} From e99486c6b58d5d609726a60b9759204573e0b45e Mon Sep 17 00:00:00 2001 From: Yasuhiro Matsumoto Date: Thu, 18 Jun 2026 14:15:01 +0900 Subject: [PATCH 03/18] cache column metadata for prepared and cached statements Column names and declared types are invariant for the lifetime of a prepared statement, but Columns()/declTypes() called C.sqlite3_column_name and sqlite3_column_decltype on every query, once per column. On hot QueryRow paths reusing an explicit Prepare or a _stmt_cache_size statement, this is a fixed set of cgo crossings paid on every execution. Cache the names and decltypes on the SQLiteStmt the first time they are materialized and reuse them on subsequent executions. Caching is gated by cacheMetadata(): explicit prepared statements always cache, Query-created ephemeral statements cache only when they live in the connection stmt cache. One-shot statements keep the previous per-call behavior. --- sqlite3.go | 56 ++++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 50 insertions(+), 6 deletions(-) diff --git a/sqlite3.go b/sqlite3.go index 19b467fd..e9e8f404 100644 --- a/sqlite3.go +++ b/sqlite3.go @@ -476,6 +476,12 @@ type SQLiteStmt struct { cls bool // True if the statement was created by SQLiteConn.Query namedParams map[string][3]int cacheKey string + metadata *sqliteStmtMetadata +} + +type sqliteStmtMetadata struct { + cols []string + decltype []string } // SQLiteResult implements sql.Result. @@ -2475,6 +2481,36 @@ func (rc *SQLiteRows) Close() error { return nil } +func (s *SQLiteStmt) cacheMetadata() bool { + return !s.cls || s.cacheKey != "" +} + +func (s *SQLiteStmt) columnNamesLocked(n int) []string { + if s.metadata == nil { + s.metadata = &sqliteStmtMetadata{} + } + if len(s.metadata.cols) != n { + s.metadata.cols = make([]string, n) + for i := range s.metadata.cols { + s.metadata.cols[i] = C.GoString(C.sqlite3_column_name(s.s, C.int(i))) + } + } + return s.metadata.cols +} + +func (s *SQLiteStmt) declTypesLocked(n int) []string { + if s.metadata == nil { + s.metadata = &sqliteStmtMetadata{} + } + if len(s.metadata.decltype) != n { + s.metadata.decltype = make([]string, n) + for i := range s.metadata.decltype { + s.metadata.decltype[i] = strings.ToLower(C.GoString(C.sqlite3_column_decltype(s.s, C.int(i)))) + } + } + return s.metadata.decltype +} + // Columns return column names. func (rc *SQLiteRows) Columns() []string { if rc.s == nil { @@ -2483,9 +2519,13 @@ func (rc *SQLiteRows) Columns() []string { rc.s.mu.Lock() defer rc.s.mu.Unlock() if rc.s.s != nil && int(rc.nc) != len(rc.cols) { - rc.cols = make([]string, rc.nc) - for i := range rc.cols { - rc.cols[i] = C.GoString(C.sqlite3_column_name(rc.s.s, C.int(i))) + if rc.s.cacheMetadata() { + rc.cols = rc.s.columnNamesLocked(int(rc.nc)) + } else { + rc.cols = make([]string, rc.nc) + for i := range rc.cols { + rc.cols[i] = C.GoString(C.sqlite3_column_name(rc.s.s, C.int(i))) + } } } return rc.cols @@ -2493,9 +2533,13 @@ func (rc *SQLiteRows) Columns() []string { func (rc *SQLiteRows) declTypes() []string { if rc.s.s != nil && rc.decltype == nil { - rc.decltype = make([]string, rc.nc) - for i := range rc.decltype { - rc.decltype[i] = strings.ToLower(C.GoString(C.sqlite3_column_decltype(rc.s.s, C.int(i)))) + if rc.s.cacheMetadata() { + rc.decltype = rc.s.declTypesLocked(int(rc.nc)) + } else { + rc.decltype = make([]string, rc.nc) + for i := range rc.decltype { + rc.decltype[i] = strings.ToLower(C.GoString(C.sqlite3_column_decltype(rc.s.s, C.int(i)))) + } } } return rc.decltype From a40eeff51e5eaee70c7bdc648e04fd533e946aff Mon Sep 17 00:00:00 2001 From: Yasuhiro Matsumoto Date: Mon, 22 Jun 2026 00:27:39 +0900 Subject: [PATCH 04/18] Add upgrade/check.sh to check if SQLite upgrade is available --- upgrade/check.sh | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100755 upgrade/check.sh diff --git a/upgrade/check.sh b/upgrade/check.sh new file mode 100755 index 00000000..70e55eb9 --- /dev/null +++ b/upgrade/check.sh @@ -0,0 +1,35 @@ +#!/bin/sh + +set -e + +cd "$(dirname "$0")/.." + +CURRENT_VERSION=$(grep '#define SQLITE_VERSION ' sqlite3-binding.c | grep -o '[0-9]*\.[0-9]*\.[0-9]*') + +if [ -z "$CURRENT_VERSION" ]; then + echo "Error: Could not extract current SQLite version from sqlite3-binding.c" + exit 1 +fi + +LATEST_VERSION=$(curl -fsSL https://www.sqlite.org/download.html \ + | grep -o 'sqlite-amalgamation-[0-9]*\.zip' \ + | head -n 1 \ + | sed 's/sqlite-amalgamation-\([0-9]\)\([0-9][0-9]\)\([0-9][0-9]\)[0-9][0-9]\.zip/\1.\2.\3/' \ + | sed 's/\.0*\([0-9]\)/.\1/g') + +if [ -z "$LATEST_VERSION" ]; then + echo "Error: Could not extract latest SQLite version from sqlite.org" + exit 1 +fi + +echo "Current version: $CURRENT_VERSION" +echo "Latest version: $LATEST_VERSION" + +if [ "$CURRENT_VERSION" = "$LATEST_VERSION" ]; then + echo "Already up to date." + exit 0 +fi + +echo "Upgrade available: $CURRENT_VERSION -> $LATEST_VERSION" +echo "Run upgrade/upgrade.sh to upgrade." +exit 1 From 34c9c34da42a8f5c96c554355bea69b8c36e1131 Mon Sep 17 00:00:00 2001 From: Yasuhiro Matsumoto Date: Tue, 7 Jul 2026 00:31:45 +0900 Subject: [PATCH 05/18] Fix race in SQLiteStmt.Close by holding conn lock across cache check --- sqlite3.go | 32 ++++++++++++++++++++++---------- 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/sqlite3.go b/sqlite3.go index 656d2dc6..dd849bee 100644 --- a/sqlite3.go +++ b/sqlite3.go @@ -445,12 +445,12 @@ type SQLiteDriver struct { // SQLiteConn implements driver.Conn. type SQLiteConn struct { - mu sync.Mutex - db *C.sqlite3 - loc *time.Location - txlock string - funcs []*functionInfo - aggregators []*aggInfo + mu sync.Mutex + db *C.sqlite3 + loc *time.Location + txlock string + funcs []*functionInfo + aggregators []*aggInfo // Prepared-statement cache. The slice is allocated at Open with a // fixed capacity equal to the configured cache size; cap bounds the // cache, len is the live count, and entries are ordered LRU-first @@ -1970,6 +1970,10 @@ func (c *SQLiteConn) putCachedStmt(s *SQLiteStmt) bool { c.mu.Lock() defer c.mu.Unlock() + return c.putCachedStmtLocked(s) +} + +func (c *SQLiteConn) putCachedStmtLocked(s *SQLiteStmt) bool { if c.db == nil { return false } @@ -2164,12 +2168,20 @@ func (s *SQLiteStmt) Close() error { s.c = nil return nil } - if !conn.dbConnOpen() { + if s.cacheKey != "" { + conn.mu.Lock() + if conn.db == nil { + conn.mu.Unlock() + return errors.New("sqlite statement with already closed database connection") + } + if conn.putCachedStmtLocked(s) { + conn.mu.Unlock() + return nil + } + conn.mu.Unlock() + } else if !conn.dbConnOpen() { return errors.New("sqlite statement with already closed database connection") } - if s.cacheKey != "" && conn.putCachedStmt(s) { - return nil - } s.s = nil s.c = nil rv := C.sqlite3_finalize(stmt) From 9d436de27929117d7169c469990fbf059b0479f1 Mon Sep 17 00:00:00 2001 From: Yasuhiro Matsumoto Date: Wed, 8 Jul 2026 22:28:34 +0900 Subject: [PATCH 06/18] Add CodeRabbit as a sponsor --- README.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/README.md b/README.md index 4fd39111..16acdb45 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,17 @@ go-sqlite3 [![codecov](https://codecov.io/gh/mattn/go-sqlite3/branch/master/graph/badge.svg)](https://codecov.io/gh/mattn/go-sqlite3) [![Go Report Card](https://goreportcard.com/badge/github.com/mattn/go-sqlite3)](https://goreportcard.com/report/github.com/mattn/go-sqlite3) +## Sponsors + +This project is proudly sponsored by: + + + + + CodeRabbit + + + Latest stable version is v1.14 or later, not v2. # Description From d613bbb4bccef4bc168d145aa444bfff71fba212 Mon Sep 17 00:00:00 2001 From: Yasuhiro Matsumoto Date: Wed, 8 Jul 2026 22:35:18 +0900 Subject: [PATCH 07/18] Add CodeRabbit configuration --- .coderabbit.yaml | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 .coderabbit.yaml diff --git a/.coderabbit.yaml b/.coderabbit.yaml new file mode 100644 index 00000000..2c813256 --- /dev/null +++ b/.coderabbit.yaml @@ -0,0 +1,15 @@ +# yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json +language: en-US +reviews: + # Skip the vendored SQLite amalgamation. These files are copied verbatim from + # upstream SQLite (see the License section in README.md) and are not code that + # this project authors or reviews. + path_filters: + - "!sqlite3-binding.c" + - "!sqlite3-binding.h" + - "!sqlite3ext.h" + auto_review: + enabled: true + drafts: false +chat: + auto_reply: true From 5ce75d7cbd224277ce67eee0de8598124d758398 Mon Sep 17 00:00:00 2001 From: Yasuhiro Matsumoto Date: Mon, 13 Jul 2026 10:15:49 +0900 Subject: [PATCH 08/18] Return error from vtable cursor open instead of ignoring it --- sqlite3_opt_vtable.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/sqlite3_opt_vtable.go b/sqlite3_opt_vtable.go index 7c2ae58e..9e916ea8 100644 --- a/sqlite3_opt_vtable.go +++ b/sqlite3_opt_vtable.go @@ -113,6 +113,9 @@ uintptr_t goVOpen(void *pVTab, char **pzErr); static int cXOpen(sqlite3_vtab *pVTab, sqlite3_vtab_cursor **ppCursor) { void *vTabCursor = (void *)goVOpen(((goVTab*)pVTab)->vTab, &(pVTab->zErrMsg)); + if (!vTabCursor) { + return SQLITE_ERROR; + } goVTabCursor *pCursor = (goVTabCursor *)sqlite3_malloc(sizeof(goVTabCursor)); if (!pCursor) { return SQLITE_NOMEM; From 7d6ccee48ec62186ef396adbe37ceb59bd89083e Mon Sep 17 00:00:00 2001 From: Yasuhiro Matsumoto Date: Mon, 13 Jul 2026 10:17:21 +0900 Subject: [PATCH 09/18] Check sqlite3_malloc64 result in Deserialize --- sqlite3_opt_serialize.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/sqlite3_opt_serialize.go b/sqlite3_opt_serialize.go index 51dd9c8f..60019bd5 100644 --- a/sqlite3_opt_serialize.go +++ b/sqlite3_opt_serialize.go @@ -60,6 +60,9 @@ func (c *SQLiteConn) Deserialize(b []byte, schema string) error { defer C.free(unsafe.Pointer(zSchema)) tmpBuf := (*C.uchar)(C.sqlite3_malloc64(C.sqlite3_uint64(len(b)))) + if tmpBuf == nil && len(b) > 0 { + return fmt.Errorf("deserialize failed: out of memory") + } copy(unsafe.Slice((*byte)(unsafe.Pointer(tmpBuf)), len(b)), b) rc := C.sqlite3_deserialize(c.db, zSchema, tmpBuf, C.sqlite3_int64(len(b)), From 2485463b62281164e10d9bfd30cd5f76cd06562c Mon Sep 17 00:00:00 2001 From: Yasuhiro Matsumoto Date: Mon, 13 Jul 2026 10:25:47 +0900 Subject: [PATCH 10/18] Fix panic when registered functions return named types --- callback.go | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/callback.go b/callback.go index 293d26db..cda45373 100644 --- a/callback.go +++ b/callback.go @@ -326,8 +326,7 @@ func callbackRetInteger(ctx *C.sqlite3_context, v reflect.Value) error { case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Int, reflect.Uint: v = v.Convert(reflect.TypeOf(int64(0))) case reflect.Bool: - b := v.Interface().(bool) - if b { + if v.Bool() { v = reflect.ValueOf(int64(1)) } else { v = reflect.ValueOf(int64(0)) @@ -336,7 +335,7 @@ func callbackRetInteger(ctx *C.sqlite3_context, v reflect.Value) error { return fmt.Errorf("cannot convert %s to INTEGER", v.Type()) } - C.sqlite3_result_int64(ctx, C.sqlite3_int64(v.Interface().(int64))) + C.sqlite3_result_int64(ctx, C.sqlite3_int64(v.Int())) return nil } @@ -349,7 +348,7 @@ func callbackRetFloat(ctx *C.sqlite3_context, v reflect.Value) error { return fmt.Errorf("cannot convert %s to FLOAT", v.Type()) } - C.sqlite3_result_double(ctx, C.double(v.Interface().(float64))) + C.sqlite3_result_double(ctx, C.double(v.Float())) return nil } @@ -357,11 +356,10 @@ func callbackRetBlob(ctx *C.sqlite3_context, v reflect.Value) error { if v.Type().Kind() != reflect.Slice || v.Type().Elem().Kind() != reflect.Uint8 { return fmt.Errorf("cannot convert %s to BLOB", v.Type()) } - i := v.Interface() - if i == nil || len(i.([]byte)) == 0 { + bs := v.Bytes() + if len(bs) == 0 { C.sqlite3_result_null(ctx) } else { - bs := i.([]byte) if i64 && len(bs) > math.MaxInt32 { C.sqlite3_result_error_toobig(ctx) return nil @@ -375,7 +373,7 @@ func callbackRetText(ctx *C.sqlite3_context, v reflect.Value) error { if v.Type().Kind() != reflect.String { return fmt.Errorf("cannot convert %s to TEXT", v.Type()) } - s := v.Interface().(string) + s := v.String() if i64 && len(s) > math.MaxInt32 { C.sqlite3_result_error_toobig(ctx) return nil From c703179f638001311a353191b78579de2f528c14 Mon Sep 17 00:00:00 2001 From: Yasuhiro Matsumoto Date: Mon, 13 Jul 2026 10:37:55 +0900 Subject: [PATCH 11/18] Return error instead of silently ignoring unsupported bind types --- sqlite3.go | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/sqlite3.go b/sqlite3.go index dd849bee..ef54c20a 100644 --- a/sqlite3.go +++ b/sqlite3.go @@ -2294,6 +2294,17 @@ func stmtArgs(args []driver.NamedValue, start, na int) []driver.NamedValue { return stmtArgs } +// bindError converts a non-OK return code from bindValue into an error. +// The synthetic SQLITE_MISUSE returned for unsupported Go types is never +// recorded in the database handle, so lastError may report no error; fall +// back to an explicit message instead of silently ignoring the failure. +func (s *SQLiteStmt) bindError(v driver.Value) error { + if err := s.c.lastError(); err != nil { + return err + } + return fmt.Errorf("sqlite3: unsupported bind type %T", v) +} + func (s *SQLiteStmt) bind(args []driver.NamedValue) error { rv := C._sqlite3_reset_clear(s.s) if rv != C.SQLITE_ROW && rv != C.SQLITE_OK && rv != C.SQLITE_DONE { @@ -2313,7 +2324,7 @@ func (s *SQLiteStmt) bind(args []driver.NamedValue) error { n := C.int(arg.Ordinal) rv = bindValue(s.s, n, arg.Value) if rv != C.SQLITE_OK { - return s.c.lastError() + return s.bindError(arg.Value) } } return nil @@ -2323,7 +2334,7 @@ func (s *SQLiteStmt) bind(args []driver.NamedValue) error { if arg.Name == "" { rv = bindValue(s.s, C.int(arg.Ordinal), arg.Value) if rv != C.SQLITE_OK { - return s.c.lastError() + return s.bindError(arg.Value) } continue } @@ -2334,7 +2345,7 @@ func (s *SQLiteStmt) bind(args []driver.NamedValue) error { } rv = bindValue(s.s, C.int(idx), arg.Value) if rv != C.SQLITE_OK { - return s.c.lastError() + return s.bindError(arg.Value) } } } From 0c0b48c40e424b851a71f6b69c1182b27d1d023d Mon Sep 17 00:00:00 2001 From: Yasuhiro Matsumoto Date: Mon, 13 Jul 2026 10:46:43 +0900 Subject: [PATCH 12/18] Close database on all error paths in Open --- sqlite3.go | 111 ++++++++++++++++++++++++----------------------------- 1 file changed, 51 insertions(+), 60 deletions(-) diff --git a/sqlite3.go b/sqlite3.go index dd849bee..e95c3ade 100644 --- a/sqlite3.go +++ b/sqlite3.go @@ -1595,6 +1595,20 @@ func (d *SQLiteDriver) Open(dsn string) (driver.Conn, error) { return nil, errors.New("sqlite succeeded without returning a database") } + // Create connection to SQLite + conn := &SQLiteConn{db: db, loc: loc, txlock: txlock} + if stmtCacheSize > 0 { + conn.stmtCache = make([]*SQLiteStmt, 0, stmtCacheSize) + conn.stmtCacheEnabled = true + } + + // fail closes the connection so no error path leaks the database + // handle or any callback handles registered on it. + fail := func(err error) (driver.Conn, error) { + conn.Close() + return nil, err + } + exec := func(s string) error { cs := C.CString(s) rv := C.sqlite3_exec(db, cs, nil, nil, nil) @@ -1607,8 +1621,7 @@ func (d *SQLiteDriver) Open(dsn string) (driver.Conn, error) { // Busy timeout if err := exec(fmt.Sprintf("PRAGMA busy_timeout = %d;", busyTimeout)); err != nil { - C.sqlite3_close_v2(db) - return nil, err + return fail(err) } // USER AUTHENTICATION @@ -1633,66 +1646,59 @@ func (d *SQLiteDriver) Open(dsn string) (driver.Conn, error) { // NO => Continue // - // Create connection to SQLite - conn := &SQLiteConn{db: db, loc: loc, txlock: txlock} - if stmtCacheSize > 0 { - conn.stmtCache = make([]*SQLiteStmt, 0, stmtCacheSize) - conn.stmtCacheEnabled = true - } - // Password Cipher has to be registered before authentication if len(authCrypt) > 0 { switch strings.ToUpper(authCrypt) { case "SHA1": if err := conn.RegisterFunc("sqlite_crypt", CryptEncoderSHA1, true); err != nil { - return nil, fmt.Errorf("CryptEncoderSHA1: %s", err) + return fail(fmt.Errorf("CryptEncoderSHA1: %s", err)) } case "SSHA1": if len(authSalt) == 0 { - return nil, fmt.Errorf("_auth_crypt=ssha1, requires _auth_salt") + return fail(fmt.Errorf("_auth_crypt=ssha1, requires _auth_salt")) } if err := conn.RegisterFunc("sqlite_crypt", CryptEncoderSSHA1(authSalt), true); err != nil { - return nil, fmt.Errorf("CryptEncoderSSHA1: %s", err) + return fail(fmt.Errorf("CryptEncoderSSHA1: %s", err)) } case "SHA256": if err := conn.RegisterFunc("sqlite_crypt", CryptEncoderSHA256, true); err != nil { - return nil, fmt.Errorf("CryptEncoderSHA256: %s", err) + return fail(fmt.Errorf("CryptEncoderSHA256: %s", err)) } case "SSHA256": if len(authSalt) == 0 { - return nil, fmt.Errorf("_auth_crypt=ssha256, requires _auth_salt") + return fail(fmt.Errorf("_auth_crypt=ssha256, requires _auth_salt")) } if err := conn.RegisterFunc("sqlite_crypt", CryptEncoderSSHA256(authSalt), true); err != nil { - return nil, fmt.Errorf("CryptEncoderSSHA256: %s", err) + return fail(fmt.Errorf("CryptEncoderSSHA256: %s", err)) } case "SHA384": if err := conn.RegisterFunc("sqlite_crypt", CryptEncoderSHA384, true); err != nil { - return nil, fmt.Errorf("CryptEncoderSHA384: %s", err) + return fail(fmt.Errorf("CryptEncoderSHA384: %s", err)) } case "SSHA384": if len(authSalt) == 0 { - return nil, fmt.Errorf("_auth_crypt=ssha384, requires _auth_salt") + return fail(fmt.Errorf("_auth_crypt=ssha384, requires _auth_salt")) } if err := conn.RegisterFunc("sqlite_crypt", CryptEncoderSSHA384(authSalt), true); err != nil { - return nil, fmt.Errorf("CryptEncoderSSHA384: %s", err) + return fail(fmt.Errorf("CryptEncoderSSHA384: %s", err)) } case "SHA512": if err := conn.RegisterFunc("sqlite_crypt", CryptEncoderSHA512, true); err != nil { - return nil, fmt.Errorf("CryptEncoderSHA512: %s", err) + return fail(fmt.Errorf("CryptEncoderSHA512: %s", err)) } case "SSHA512": if len(authSalt) == 0 { - return nil, fmt.Errorf("_auth_crypt=ssha512, requires _auth_salt") + return fail(fmt.Errorf("_auth_crypt=ssha512, requires _auth_salt")) } if err := conn.RegisterFunc("sqlite_crypt", CryptEncoderSSHA512(authSalt), true); err != nil { - return nil, fmt.Errorf("CryptEncoderSSHA512: %s", err) + return fail(fmt.Errorf("CryptEncoderSSHA512: %s", err)) } } } // Preform Authentication if err := conn.Authenticate(authUser, authPass); err != nil { - return nil, err + return fail(err) } // Register: authenticate @@ -1710,7 +1716,7 @@ func (d *SQLiteDriver) Open(dsn string) (driver.Conn, error) { // If the SQLITE_USER table is not present in the database file, then // this interface is a harmless no-op returnning SQLITE_OK. if err := conn.RegisterFunc("authenticate", conn.authenticate, true); err != nil { - return nil, err + return fail(err) } // // Register: auth_user_add @@ -1723,7 +1729,7 @@ func (d *SQLiteDriver) Open(dsn string) (driver.Conn, error) { // for any ATTACH-ed databases. Any call to AuthUserAdd by a // non-admin user results in an error. if err := conn.RegisterFunc("auth_user_add", conn.authUserAdd, true); err != nil { - return nil, err + return fail(err) } // // Register: auth_user_change @@ -1733,7 +1739,7 @@ func (d *SQLiteDriver) Open(dsn string) (driver.Conn, error) { // credentials or admin privilege setting. No user may change their own // admin privilege setting. if err := conn.RegisterFunc("auth_user_change", conn.authUserChange, true); err != nil { - return nil, err + return fail(err) } // // Register: auth_user_delete @@ -1743,13 +1749,13 @@ func (d *SQLiteDriver) Open(dsn string) (driver.Conn, error) { // the database cannot be converted into a no-authentication-required // database. if err := conn.RegisterFunc("auth_user_delete", conn.authUserDelete, true); err != nil { - return nil, err + return fail(err) } // Register: auth_enabled // auth_enabled can be used to check if user authentication is enabled if err := conn.RegisterFunc("auth_enabled", conn.authEnabled, true); err != nil { - return nil, err + return fail(err) } // Auto Vacuum @@ -1760,8 +1766,7 @@ func (d *SQLiteDriver) Open(dsn string) (driver.Conn, error) { // and activating user authentication creates the internal table `sqlite_user`. if autoVacuum > -1 { if err := exec(fmt.Sprintf("PRAGMA auto_vacuum = %d;", autoVacuum)); err != nil { - C.sqlite3_close_v2(db) - return nil, err + return fail(err) } } @@ -1771,17 +1776,17 @@ func (d *SQLiteDriver) Open(dsn string) (driver.Conn, error) { // has provided an username and password within the DSN. // We are not allowed to continue. if len(authUser) == 0 { - return nil, fmt.Errorf("Missing '_auth_user' while user authentication was requested with '_auth'") + return fail(fmt.Errorf("Missing '_auth_user' while user authentication was requested with '_auth'")) } if len(authPass) == 0 { - return nil, fmt.Errorf("Missing '_auth_pass' while user authentication was requested with '_auth'") + return fail(fmt.Errorf("Missing '_auth_pass' while user authentication was requested with '_auth'")) } // Check if User Authentication is Enabled authExists := conn.AuthEnabled() if !authExists { if err := conn.AuthUserAdd(authUser, authPass, true); err != nil { - return nil, err + return fail(err) } } } @@ -1789,40 +1794,35 @@ func (d *SQLiteDriver) Open(dsn string) (driver.Conn, error) { // Case Sensitive LIKE if caseSensitiveLike > -1 { if err := exec(fmt.Sprintf("PRAGMA case_sensitive_like = %d;", caseSensitiveLike)); err != nil { - C.sqlite3_close_v2(db) - return nil, err + return fail(err) } } // Defer Foreign Keys if deferForeignKeys > -1 { if err := exec(fmt.Sprintf("PRAGMA defer_foreign_keys = %d;", deferForeignKeys)); err != nil { - C.sqlite3_close_v2(db) - return nil, err + return fail(err) } } // Foreign Keys if foreignKeys > -1 { if err := exec(fmt.Sprintf("PRAGMA foreign_keys = %d;", foreignKeys)); err != nil { - C.sqlite3_close_v2(db) - return nil, err + return fail(err) } } // Ignore CHECK Constraints if ignoreCheckConstraints > -1 { if err := exec(fmt.Sprintf("PRAGMA ignore_check_constraints = %d;", ignoreCheckConstraints)); err != nil { - C.sqlite3_close_v2(db) - return nil, err + return fail(err) } } // Journal Mode if journalMode != "" { if err := exec(fmt.Sprintf("PRAGMA journal_mode = %s;", journalMode)); err != nil { - C.sqlite3_close_v2(db) - return nil, err + return fail(err) } } @@ -1830,23 +1830,20 @@ func (d *SQLiteDriver) Open(dsn string) (driver.Conn, error) { // Because the default is NORMAL and this is not changed in this package // by using the compile time SQLITE_DEFAULT_LOCKING_MODE this PRAGMA can always be executed if err := exec(fmt.Sprintf("PRAGMA locking_mode = %s;", lockingMode)); err != nil { - C.sqlite3_close_v2(db) - return nil, err + return fail(err) } // Query Only if queryOnly > -1 { if err := exec(fmt.Sprintf("PRAGMA query_only = %d;", queryOnly)); err != nil { - C.sqlite3_close_v2(db) - return nil, err + return fail(err) } } // Recursive Triggers if recursiveTriggers > -1 { if err := exec(fmt.Sprintf("PRAGMA recursive_triggers = %d;", recursiveTriggers)); err != nil { - C.sqlite3_close_v2(db) - return nil, err + return fail(err) } } @@ -1857,8 +1854,7 @@ func (d *SQLiteDriver) Open(dsn string) (driver.Conn, error) { // you can compile with secure_delete 'ON' and disable it for a specific database connection. if secureDelete != "DEFAULT" { if err := exec(fmt.Sprintf("PRAGMA secure_delete = %s;", secureDelete)); err != nil { - C.sqlite3_close_v2(db) - return nil, err + return fail(err) } } @@ -1866,37 +1862,32 @@ func (d *SQLiteDriver) Open(dsn string) (driver.Conn, error) { // // Because default is NORMAL this statement is always executed if err := exec(fmt.Sprintf("PRAGMA synchronous = %s;", synchronousMode)); err != nil { - conn.Close() - return nil, err + return fail(err) } // Writable Schema if writableSchema > -1 { if err := exec(fmt.Sprintf("PRAGMA writable_schema = %d;", writableSchema)); err != nil { - C.sqlite3_close_v2(db) - return nil, err + return fail(err) } } // Cache Size if cacheSize != nil { if err := exec(fmt.Sprintf("PRAGMA cache_size = %d;", *cacheSize)); err != nil { - C.sqlite3_close_v2(db) - return nil, err + return fail(err) } } if len(d.Extensions) > 0 { if err := conn.loadExtensions(d.Extensions); err != nil { - conn.Close() - return nil, err + return fail(err) } } if d.ConnectHook != nil { if err := d.ConnectHook(conn); err != nil { - conn.Close() - return nil, err + return fail(err) } } runtime.SetFinalizer(conn, (*SQLiteConn).Close) From 82d5507d2c59bd9a6cd2e062ae5ac5adf87c03bf Mon Sep 17 00:00:00 2001 From: Yasuhiro Matsumoto Date: Mon, 13 Jul 2026 10:48:33 +0900 Subject: [PATCH 13/18] Check preupdate value fetch result to avoid NULL dereference --- sqlite3_opt_preupdate_hook.go | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/sqlite3_opt_preupdate_hook.go b/sqlite3_opt_preupdate_hook.go index 8cce278f..37e048ff 100644 --- a/sqlite3_opt_preupdate_hook.go +++ b/sqlite3_opt_preupdate_hook.go @@ -59,13 +59,17 @@ func (d *SQLitePreUpdateData) row(dest []any, new bool) error { for i := 0; i < d.Count() && i < len(dest); i++ { var val *C.sqlite3_value var src any + var rc C.int // Initially I tried making this just a function pointer argument, but // it's absurdly complicated to pass C function pointers. if new { - C.sqlite3_preupdate_new(d.Conn.db, C.int(i), &val) + rc = C.sqlite3_preupdate_new(d.Conn.db, C.int(i), &val) } else { - C.sqlite3_preupdate_old(d.Conn.db, C.int(i), &val) + rc = C.sqlite3_preupdate_old(d.Conn.db, C.int(i), &val) + } + if rc != C.SQLITE_OK { + return Error{Code: ErrNo(rc)} } switch C.sqlite3_value_type(val) { From 16b935f7ccba6d3c15fe16e32d9fae79fb0033ba Mon Sep 17 00:00:00 2001 From: Yasuhiro Matsumoto Date: Mon, 13 Jul 2026 10:52:07 +0900 Subject: [PATCH 14/18] Use C.int in exported callbacks to match C declarations --- callback.go | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/callback.go b/callback.go index 293d26db..f42839e6 100644 --- a/callback.go +++ b/callback.go @@ -34,8 +34,8 @@ import ( ) //export callbackTrampoline -func callbackTrampoline(ctx *C.sqlite3_context, argc int, argv **C.sqlite3_value) { - args := (*[(math.MaxInt32 - 1) / unsafe.Sizeof((*C.sqlite3_value)(nil))]*C.sqlite3_value)(unsafe.Pointer(argv))[:argc:argc] +func callbackTrampoline(ctx *C.sqlite3_context, argc C.int, argv **C.sqlite3_value) { + args := (*[(math.MaxInt32 - 1) / unsafe.Sizeof((*C.sqlite3_value)(nil))]*C.sqlite3_value)(unsafe.Pointer(argv))[:int(argc):int(argc)] fi := lookupHandle(C.sqlite3_user_data(ctx)).(*functionInfo) fi.Call(ctx, args) } @@ -60,9 +60,9 @@ func compareTrampoline(handlePtr unsafe.Pointer, la C.int, a *C.char, lb C.int, } //export commitHookTrampoline -func commitHookTrampoline(handle unsafe.Pointer) int { +func commitHookTrampoline(handle unsafe.Pointer) C.int { callback := lookupHandle(handle).(func() int) - return callback() + return C.int(callback()) } //export rollbackHookTrampoline @@ -72,23 +72,23 @@ func rollbackHookTrampoline(handle unsafe.Pointer) { } //export updateHookTrampoline -func updateHookTrampoline(handle unsafe.Pointer, op int, db *C.char, table *C.char, rowid int64) { +func updateHookTrampoline(handle unsafe.Pointer, op C.int, db *C.char, table *C.char, rowid int64) { callback := lookupHandle(handle).(func(int, string, string, int64)) - callback(op, C.GoString(db), C.GoString(table), rowid) + callback(int(op), C.GoString(db), C.GoString(table), rowid) } //export authorizerTrampoline -func authorizerTrampoline(handle unsafe.Pointer, op int, arg1 *C.char, arg2 *C.char, arg3 *C.char) int { +func authorizerTrampoline(handle unsafe.Pointer, op C.int, arg1 *C.char, arg2 *C.char, arg3 *C.char) C.int { callback := lookupHandle(handle).(func(int, string, string, string) int) - return callback(op, C.GoString(arg1), C.GoString(arg2), C.GoString(arg3)) + return C.int(callback(int(op), C.GoString(arg1), C.GoString(arg2), C.GoString(arg3))) } //export preUpdateHookTrampoline -func preUpdateHookTrampoline(handle unsafe.Pointer, dbHandle uintptr, op int, db *C.char, table *C.char, oldrowid int64, newrowid int64) { +func preUpdateHookTrampoline(handle unsafe.Pointer, dbHandle uintptr, op C.int, db *C.char, table *C.char, oldrowid int64, newrowid int64) { hval := lookupHandleVal(handle) data := SQLitePreUpdateData{ Conn: hval.db, - Op: op, + Op: int(op), DatabaseName: C.GoString(db), TableName: C.GoString(table), OldRowID: oldrowid, From 8b648a05384e936e47fd7716c7fa123a3704937f Mon Sep 17 00:00:00 2001 From: Yasuhiro Matsumoto Date: Mon, 13 Jul 2026 10:55:25 +0900 Subject: [PATCH 15/18] Fix leak of extension load error message --- sqlite3_load_extension.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/sqlite3_load_extension.go b/sqlite3_load_extension.go index 03cbc8b6..fbbb6493 100644 --- a/sqlite3_load_extension.go +++ b/sqlite3_load_extension.go @@ -74,11 +74,11 @@ func (c *SQLiteConn) loadExtension(lib string, entry *string) error { } var errMsg *C.char - defer C.sqlite3_free(unsafe.Pointer(errMsg)) - rv := C.sqlite3_load_extension(c.db, clib, centry, &errMsg) if rv != C.SQLITE_OK { - return errors.New(C.GoString(errMsg)) + err := errors.New(C.GoString(errMsg)) + C.sqlite3_free(unsafe.Pointer(errMsg)) + return err } return nil From f9029e4b8b71bc9bcde8e52323490e7711ac07aa Mon Sep 17 00:00:00 2001 From: Yasuhiro Matsumoto Date: Mon, 13 Jul 2026 11:27:50 +0900 Subject: [PATCH 16/18] Convert named argument types and add regression tests --- callback.go | 20 +++++++++++---- sqlite3_test.go | 65 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 80 insertions(+), 5 deletions(-) diff --git a/callback.go b/callback.go index cda45373..702f234d 100644 --- a/callback.go +++ b/callback.go @@ -260,6 +260,16 @@ func callbackArgGeneric(v *C.sqlite3_value) (reflect.Value, error) { } } +// callbackArgConvert returns conv as-is when the parameter type is the +// canonical type conv produces, and wraps it with a cast for named types +// (e.g. time.Duration), which reflect.Call would otherwise panic on. +func callbackArgConvert(conv callbackArgConverter, typ, canonical reflect.Type) callbackArgConverter { + if typ == canonical { + return conv + } + return callbackArgCast{conv, typ}.Run +} + func callbackArg(typ reflect.Type) (callbackArgConverter, error) { switch typ.Kind() { case reflect.Interface: @@ -271,18 +281,18 @@ func callbackArg(typ reflect.Type) (callbackArgConverter, error) { if typ.Elem().Kind() != reflect.Uint8 { return nil, errors.New("the only supported slice type is []byte") } - return callbackArgBytes, nil + return callbackArgConvert(callbackArgBytes, typ, reflect.TypeOf([]byte(nil))), nil case reflect.String: - return callbackArgString, nil + return callbackArgConvert(callbackArgString, typ, reflect.TypeOf("")), nil case reflect.Bool: - return callbackArgBool, nil + return callbackArgConvert(callbackArgBool, typ, reflect.TypeOf(false)), nil case reflect.Int64: - return callbackArgInt64, nil + return callbackArgConvert(callbackArgInt64, typ, reflect.TypeOf(int64(0))), nil case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Int, reflect.Uint: c := callbackArgCast{callbackArgInt64, typ} return c.Run, nil case reflect.Float64: - return callbackArgFloat64, nil + return callbackArgConvert(callbackArgFloat64, typ, reflect.TypeOf(float64(0))), nil case reflect.Float32: c := callbackArgCast{callbackArgFloat64, typ} return c.Run, nil diff --git a/sqlite3_test.go b/sqlite3_test.go index 6d6100df..d8a535b4 100644 --- a/sqlite3_test.go +++ b/sqlite3_test.go @@ -1439,6 +1439,71 @@ func TestFunctionRegistration(t *testing.T) { } } +func TestFunctionRegistrationNamedTypes(t *testing.T) { + type NInt int64 + type NFloat float64 + type NString string + type NBlob []byte + type NBool bool + + dur := func(n int64) time.Duration { return time.Duration(n) } + nint := func(a, b NInt) NInt { return a + b } + nfloat := func(a, b NFloat) NFloat { return a + b } + nstring := func(s NString) NString { return s + "!" } + nblob := func(s string) NBlob { return NBlob(s) } + nbool := func(b NBool) NBool { return !b } + + sql.Register("sqlite3_FunctionRegistrationNamedTypes", &SQLiteDriver{ + ConnectHook: func(conn *SQLiteConn) error { + if err := conn.RegisterFunc("dur", dur, true); err != nil { + return err + } + if err := conn.RegisterFunc("nint", nint, true); err != nil { + return err + } + if err := conn.RegisterFunc("nfloat", nfloat, true); err != nil { + return err + } + if err := conn.RegisterFunc("nstring", nstring, true); err != nil { + return err + } + if err := conn.RegisterFunc("nblob", nblob, true); err != nil { + return err + } + return conn.RegisterFunc("nbool", nbool, true) + }, + }) + db, err := sql.Open("sqlite3_FunctionRegistrationNamedTypes", ":memory:") + if err != nil { + t.Fatal("Failed to open database:", err) + } + defer db.Close() + + ops := []struct { + query string + expected any + }{ + {"SELECT dur(42)", int64(42)}, + {"SELECT nint(1,2)", int64(3)}, + {"SELECT nfloat(1.5,1.5)", float64(3)}, + {`SELECT nstring('foo')`, "foo!"}, + {`SELECT nblob('xy')`, []byte("xy")}, + // An empty blob result is mapped to SQL NULL. + {`SELECT nblob('') IS NULL`, true}, + {"SELECT nbool(0)", true}, + } + + for _, op := range ops { + ret := reflect.New(reflect.TypeOf(op.expected)) + err = db.QueryRow(op.query).Scan(ret.Interface()) + if err != nil { + t.Errorf("Query %q failed: %s", op.query, err) + } else if !reflect.DeepEqual(ret.Elem().Interface(), op.expected) { + t.Errorf("Query %q returned wrong value: got %v (%T), want %v (%T)", op.query, ret.Elem().Interface(), ret.Elem().Interface(), op.expected, op.expected) + } + } +} + func TestFunctionArgStringContainingZero(t *testing.T) { sql.Register("sqlite3_FunctionArgZero", &SQLiteDriver{ ConnectHook: func(conn *SQLiteConn) error { From 08a4ce47df0089f8dc0e9f4ac6c5410336223f55 Mon Sep 17 00:00:00 2001 From: Yasuhiro Matsumoto Date: Mon, 13 Jul 2026 11:51:04 +0900 Subject: [PATCH 17/18] Add regression tests for bind error paths --- sqlite3_test.go | 44 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/sqlite3_test.go b/sqlite3_test.go index 6d6100df..e6c0e6af 100644 --- a/sqlite3_test.go +++ b/sqlite3_test.go @@ -1324,6 +1324,50 @@ func TestDateTimeNow(t *testing.T) { } } +func TestBindErrorPaths(t *testing.T) { + d := &SQLiteDriver{} + conn, err := d.Open(":memory:") + if err != nil { + t.Fatal("Failed to open database:", err) + } + defer conn.Close() + c := conn.(*SQLiteConn) + + if _, err := c.Exec("CREATE TABLE t (v)", nil); err != nil { + t.Fatal("Failed to create table:", err) + } + + // An unsupported Go type must report an explicit error instead of + // silently binding NULL: positional parameter. + _, err = c.Exec("INSERT INTO t VALUES (?)", []driver.Value{int32(1)}) + if err == nil || !strings.Contains(err.Error(), "unsupported bind type int32") { + t.Errorf("positional bind of unsupported type: got %v, want unsupported bind type error", err) + } + + // The same for a named parameter. + stmt, err := c.Prepare("INSERT INTO t VALUES (:x)") + if err != nil { + t.Fatal("Failed to prepare:", err) + } + err = stmt.(*SQLiteStmt).bind([]driver.NamedValue{{Name: "x", Ordinal: 1, Value: int32(1)}}) + if err == nil || !strings.Contains(err.Error(), "unsupported bind type int32") { + t.Errorf("named bind of unsupported type: got %v, want unsupported bind type error", err) + } + stmt.Close() + + // A genuine SQLite bind failure must preserve the recorded error. + stmt, err = c.Prepare("INSERT INTO t VALUES (?)") + if err != nil { + t.Fatal("Failed to prepare:", err) + } + err = stmt.(*SQLiteStmt).bind([]driver.NamedValue{{Ordinal: 2, Value: int64(1)}}) + var serr Error + if !errors.As(err, &serr) || serr.Code != ErrRange { + t.Errorf("out-of-range bind: got %v, want SQLITE_RANGE error", err) + } + stmt.Close() +} + func TestFunctionRegistration(t *testing.T) { addi8_16_32 := func(a int8, b int16) int32 { return int32(a) + int32(b) } addi64 := func(a, b int64) int64 { return a + b } From 603b1ba7048e1d014f540ffd21ab606ea68ce5fa Mon Sep 17 00:00:00 2001 From: Yasuhiro Matsumoto Date: Mon, 13 Jul 2026 13:07:42 +0900 Subject: [PATCH 18/18] Upgrade SQLite to version 3053003 --- sqlite3-binding.c | 781 ++++++++++++++++++++++++++++++---------------- sqlite3-binding.h | 16 +- 2 files changed, 518 insertions(+), 279 deletions(-) diff --git a/sqlite3-binding.c b/sqlite3-binding.c index 28c91060..337af9e4 100644 --- a/sqlite3-binding.c +++ b/sqlite3-binding.c @@ -1,7 +1,7 @@ #ifndef USE_LIBSQLITE3 /****************************************************************************** ** This file is an amalgamation of many separate C source files from SQLite -** version 3.53.2. By combining all the individual C code files into this +** version 3.53.3. By combining all the individual C code files into this ** single large file, the entire code can be compiled as a single translation ** unit. This allows many compilers to do optimizations that would not be ** possible if the files were compiled separately. Performance improvements @@ -19,7 +19,7 @@ ** separate file. This file contains only code for the core SQLite library. ** ** The content in this amalgamation comes from Fossil check-in -** d6e03d8c777cfa2d35e3b60d8ec3e0187f3e with changes in files: +** d4c0e51e4aeb96955b99185ab9cde75c339e with changes in files: ** ** */ @@ -468,12 +468,12 @@ extern "C" { ** [sqlite3_libversion_number()], [sqlite3_sourceid()], ** [sqlite_version()] and [sqlite_source_id()]. */ -#define SQLITE_VERSION "3.53.2" -#define SQLITE_VERSION_NUMBER 3053002 -#define SQLITE_SOURCE_ID "2026-06-03 19:12:13 d6e03d8c777cfa2d35e3b60d8ec3e0187f3e9f99d8e2ee9cac695fd6fcdf1a24" +#define SQLITE_VERSION "3.53.3" +#define SQLITE_VERSION_NUMBER 3053003 +#define SQLITE_SOURCE_ID "2026-06-26 20:14:12 d4c0e51e4aeb96955b99185ab9cde75c339e2c29c3f3f12428d364a10d782c62" #define SQLITE_SCM_BRANCH "branch-3.53" -#define SQLITE_SCM_TAGS "release version-3.53.2" -#define SQLITE_SCM_DATETIME "2026-06-03T19:12:13.350Z" +#define SQLITE_SCM_TAGS "release version-3.53.3" +#define SQLITE_SCM_DATETIME "2026-06-26T20:14:12.354Z" /* ** CAPI3REF: Run-Time Library Version Numbers @@ -4688,7 +4688,8 @@ SQLITE_API int sqlite3_limit(sqlite3*, int id, int newVal); ** or in an ORDER BY or GROUP BY clause.)^ ** ** [[SQLITE_LIMIT_EXPR_DEPTH]] ^(
SQLITE_LIMIT_EXPR_DEPTH
-**
The maximum depth of the parse tree on any expression.
)^ +**
The maximum depth of the parse tree on any expression and +** the maximum nesting depth for subqueries and VIEWs
)^ ** ** [[SQLITE_LIMIT_PARSER_DEPTH]] ^(
SQLITE_LIMIT_PARSER_DEPTH
**
The maximum depth of the LALR(1) parser stack used to analyze @@ -4719,7 +4720,8 @@ SQLITE_API int sqlite3_limit(sqlite3*, int id, int newVal); **
The maximum index number of any [parameter] in an SQL statement.)^ ** ** [[SQLITE_LIMIT_TRIGGER_DEPTH]] ^(
SQLITE_LIMIT_TRIGGER_DEPTH
-**
The maximum depth of recursion for triggers.
)^ +**
The maximum depth of recursion for triggers, and the maximum +** nesting depth for separate triggers.
)^ ** ** [[SQLITE_LIMIT_WORKER_THREADS]] ^(
SQLITE_LIMIT_WORKER_THREADS
**
The maximum number of auxiliary worker threads that a single @@ -15801,6 +15803,13 @@ SQLITE_PRIVATE void sqlite3HashClear(Hash*); # define offsetof(ST,M) ((size_t)((char*)&((ST*)0)->M - (char*)0)) #endif +/* +** sizeof64() is like sizeof(), but always returns a 64-bit value, even +** on 32-bit builds. This can help to avoid overflow by ensuring 64-bit +** arithmetic is used consistently in both 32-bit and 64-bit builds. +*/ +#define sizeof64(X) ((sqlite3_int64)sizeof(X)) + /* ** Work around C99 "flex-array" syntax for pre-C99 compilers, so as ** to avoid complaints from -fsanitize=strict-bounds. @@ -17162,7 +17171,7 @@ SQLITE_PRIVATE int sqlite3BtreeCheckpoint(Btree*, int, int *, int *); SQLITE_PRIVATE const char *sqlite3BtreeGetFilename(Btree *); SQLITE_PRIVATE const char *sqlite3BtreeGetJournalname(Btree *); -SQLITE_PRIVATE int sqlite3BtreeCopyFile(Btree *, Btree *); +SQLITE_PRIVATE int sqlite3BtreeCopyFile(Btree*, Btree*); SQLITE_PRIVATE int sqlite3BtreeIncrVacuum(Btree *); @@ -20920,6 +20929,7 @@ struct Parse { int szOpAlloc; /* Bytes of memory space allocated for Vdbe.aOp[] */ int iSelfTab; /* Table associated with an index on expr, or negative ** of the base register during check-constraint eval */ + int nNestSel; /* Number of nested SELECT statements and/or VIEWs */ int nLabel; /* The *negative* of the number of labels used */ int nLabelAlloc; /* Number of slots in aLabel */ int *aLabel; /* Space to hold the labels */ @@ -39576,16 +39586,17 @@ int kvvfsDecode(const char *a, char *aOut, int nOut){ while( 1 ){ c = kvvfsHexValue[aIn[i]]; if( c<0 ){ - int n = 0; - int mult = 1; + sqlite3_int64 n = 0; + sqlite3_int64 mult = 1; c = aIn[i]; if( c==0 ) break; while( c>='a' && c<='z' ){ n += (c - 'a')*mult; + if( n>nOut ) return -1 /* oversized/malformed input */; mult *= 26; c = aIn[++i]; } - if( j+n>nOut ) return -1; + if( j+n>nOut ) return -1 /* oversized/malformed input */; memset(&aOut[j], 0, n); j += n; if( c==0 || mult==1 ) break; /* progress stalled if mult==1 */ @@ -39621,7 +39632,7 @@ static void kvvfsDecodeJournal( i = 0; mult = 1; while( (c = zTxt[i++])>='a' && c<='z' ){ - n += (zTxt[i] - 'a')*mult; + n += (c - 'a')*mult; mult *= 26; } sqlite3_free(pFile->aJrnl); @@ -39667,9 +39678,7 @@ static int kvvfsClose(sqlite3_file *pProtoFile){ pFile->isJournal ? "journal" : "db")); sqlite3_free(pFile->aJrnl); sqlite3_free(pFile->aData); -#ifdef SQLITE_WASM memset(pFile, 0, sizeof(*pFile)); -#endif return SQLITE_OK; } @@ -39699,6 +39708,7 @@ static int kvvfsReadJrnl( aTxt, szTxt+1); if( rc>=0 ){ kvvfsDecodeJournal(pFile, aTxt, szTxt); + rc = 0; } sqlite3_free(aTxt); if( rc ) return rc; @@ -49762,10 +49772,8 @@ static struct win_syscall { #define osWaitForSingleObjectEx ((DWORD(WINAPI*)(HANDLE,DWORD, \ BOOL))aSyscall[63].pCurrent) - { "GetNativeSystemInfo", (SYSCALL)GetNativeSystemInfo, 0 }, - -#define osGetNativeSystemInfo ((VOID(WINAPI*)( \ - LPSYSTEM_INFO))aSyscall[64].pCurrent) + { "GetNativeSystemInfo", (SYSCALL)0, 0 }, + /* ^^^^^^^^^^^^^^^^^^^----------------^------- placeholder only */ #if defined(SQLITE_WIN32_HAS_ANSI) { "OutputDebugStringA", (SYSCALL)OutputDebugStringA, 0 }, @@ -53020,11 +53028,29 @@ SQLITE_API int sqlite3_win_test_unc_locking = 0; /* ** Return true if the string passed as the only argument is likely -** to be a UNC path. In other words, if it starts with "\\". +** to be a UNC path. Return false if note. +** +** Return true if: +** +** (1) The name begins with "\\" +** (2) But does not begin with "\\?\C:\" where C can be any alphabetic +** character. +** +** For testing, also return true in all cases if the global variable +** sqlite3_win_test_unc_locking is true. */ static int winIsUNCPath(const char *zFile){ if( zFile[0]=='\\' && zFile[1]=='\\' ){ - return 1; + if( zFile[2]=='?' + && zFile[3]=='\\' + && sqlite3Isalpha(zFile[4]) + && zFile[5]==':' + && winIsDirSep(zFile[6]) + ){ + return sqlite3_win_test_unc_locking; + }else{ + return 1; + } } return sqlite3_win_test_unc_locking; } @@ -56040,7 +56066,7 @@ SQLITE_API unsigned char *sqlite3_serialize( sqlite3_int64 sz; int szPage = 0; sqlite3_stmt *pStmt = 0; - unsigned char *pOut; + unsigned char *pOut = 0; char *zSql; int rc; @@ -56050,12 +56076,13 @@ SQLITE_API unsigned char *sqlite3_serialize( return 0; } #endif + sqlite3_mutex_enter(db->mutex); if( zSchema==0 ) zSchema = db->aDb[0].zDbSName; p = memdbFromDbSchema(db, zSchema); iDb = sqlite3FindDbName(db, zSchema); if( piSize ) *piSize = -1; - if( iDb<0 ) return 0; + if( iDb<0 ) goto serialize_out; if( p ){ MemStore *pStore = p->pStore; assert( pStore->pMutex==0 ); @@ -56066,19 +56093,17 @@ SQLITE_API unsigned char *sqlite3_serialize( pOut = sqlite3_malloc64( pStore->sz ); if( pOut ) memcpy(pOut, pStore->aData, pStore->sz); } - return pOut; + goto serialize_out; } pBt = db->aDb[iDb].pBt; - if( pBt==0 ) return 0; + if( pBt==0 ) goto serialize_out; szPage = sqlite3BtreeGetPageSize(pBt); zSql = sqlite3_mprintf("PRAGMA \"%w\".page_count", zSchema); rc = zSql ? sqlite3_prepare_v2(db, zSql, -1, &pStmt, 0) : SQLITE_NOMEM; sqlite3_free(zSql); - if( rc ) return 0; + if( rc ) goto serialize_out; rc = sqlite3_step(pStmt); - if( rc!=SQLITE_ROW ){ - pOut = 0; - }else{ + if( rc==SQLITE_ROW ){ sz = sqlite3_column_int64(pStmt, 0)*szPage; if( sz==0 ){ sqlite3_reset(pStmt); @@ -56112,6 +56137,9 @@ SQLITE_API unsigned char *sqlite3_serialize( } } sqlite3_finalize(pStmt); + + serialize_out: + sqlite3_mutex_leave(db->mutex); return pOut; } @@ -57965,22 +57993,24 @@ static int pcache1InitBulk(PCache1 *pCache){ if( szBulk > pCache->szAlloc*(i64)pCache->nMax ){ szBulk = pCache->szAlloc*(i64)pCache->nMax; } - zBulk = pCache->pBulk = sqlite3Malloc( szBulk ); - sqlite3EndBenignMalloc(); - if( zBulk ){ - int nBulk = sqlite3MallocSize(zBulk)/pCache->szAlloc; - do{ - PgHdr1 *pX = (PgHdr1*)&zBulk[pCache->szPage]; - pX->page.pBuf = zBulk; - pX->page.pExtra = (u8*)pX + ROUND8(sizeof(*pX)); - assert( EIGHT_BYTE_ALIGNMENT( pX->page.pExtra ) ); - pX->isBulkLocal = 1; - pX->isAnchor = 0; - pX->pNext = pCache->pFree; - pX->pLruPrev = 0; /* Initializing this saves a valgrind error */ - pCache->pFree = pX; - zBulk += pCache->szAlloc; - }while( --nBulk ); + if( szBulk>=pCache->szAlloc ){ + zBulk = pCache->pBulk = sqlite3Malloc( szBulk ); + sqlite3EndBenignMalloc(); + if( zBulk ){ + int nBulk = sqlite3MallocSize(zBulk)/pCache->szAlloc; + do{ + PgHdr1 *pX = (PgHdr1*)&zBulk[pCache->szPage]; + pX->page.pBuf = zBulk; + pX->page.pExtra = (u8*)pX + ROUND8(sizeof(*pX)); + assert( EIGHT_BYTE_ALIGNMENT( pX->page.pExtra ) ); + pX->isBulkLocal = 1; + pX->isAnchor = 0; + pX->pNext = pCache->pFree; + pX->pLruPrev = 0; /* Initializing this saves a valgrind error */ + pCache->pFree = pX; + zBulk += pCache->szAlloc; + }while( --nBulk ); + } } return pCache->pFree!=0; } @@ -60878,39 +60908,43 @@ static void checkPage(PgHdr *pPg){ #endif /* SQLITE_CHECK_PAGES */ /* -** When this is called the journal file for pager pPager must be open. -** This function attempts to read a super-journal file name from the -** end of the file and, if successful, copies it into memory supplied -** by the caller. See comments above writeSuperJournal() for the format -** used to store a super-journal file name at the end of a journal file. -** -** zSuper must point to a buffer of at least nSuper bytes allocated by -** the caller. This should be sqlite3_vfs.mxPathname+1 (to ensure there is -** enough space to write the super-journal name). If the super-journal -** name in the journal is longer than nSuper bytes (including a -** nul-terminator), then this is handled as if no super-journal name -** were present in the journal. +** Free a buffer allocated by the readSuperJournal() function. +*/ +static void freeSuperJournal(char *zSuper){ + if( zSuper ){ + sqlite3_free(&zSuper[-4]); + } +} + +/* +** Parameter pJrnl is a file-handle open on a journal file. This function +** attempts to read a super-journal file name from the end of the journal +** file. If successful, it sets output parameter (*pzSuper) to point to a +** buffer containing the super-journal name as a nul-terminated string. +** The caller is responsible for freeing the buffer using freeSuperJournal(). ** -** If a super-journal file name is present at the end of the journal -** file, then it is copied into the buffer pointed to by zSuper. A -** nul-terminator byte is appended to the buffer following the -** super-journal file name. +** Refer to comments above writeSuperJournal() for the format used to store +** a super-journal file name at the end of a journal file. ** -** If it is determined that no super-journal file name is present -** zSuper[0] is set to 0 and SQLITE_OK returned. +** Parameter nSuper is passed the maximum allowable size of the super journal +** name in bytes. If the super-journal name in the journal is longer than +** nSuper bytes (including a nul-terminator), then this is handled as if no +** super-journal name were present in the journal. ** -** If an error occurs while reading from the journal file, an SQLite -** error code is returned. +** If there is no super-journal name at the end of pJrnl, (*pzSuper) is +** set to 0 and SQLITE_OK is returned. Or, if an error occurs while reading +** the super-journal name, an SQLite error code is returned and (*pzSuper) +** is set to 0. */ -static int readSuperJournal(sqlite3_file *pJrnl, char *zSuper, u64 nSuper){ +static int readSuperJournal(sqlite3_file *pJrnl, u64 nSuper, char **pzSuper){ int rc; /* Return code */ u32 len; /* Length in bytes of super-journal name */ i64 szJ; /* Total size in bytes of journal file pJrnl */ u32 cksum; /* MJ checksum value read from journal */ - u32 u; /* Unsigned loop counter */ unsigned char aMagic[8]; /* A buffer to hold the magic header */ - zSuper[0] = '\0'; + char *zOut = 0; + *pzSuper = 0; if( SQLITE_OK!=(rc = sqlite3OsFileSize(pJrnl, &szJ)) || szJ<16 || SQLITE_OK!=(rc = read32bits(pJrnl, szJ-16, &len)) @@ -60920,27 +60954,34 @@ static int readSuperJournal(sqlite3_file *pJrnl, char *zSuper, u64 nSuper){ || SQLITE_OK!=(rc = read32bits(pJrnl, szJ-12, &cksum)) || SQLITE_OK!=(rc = sqlite3OsRead(pJrnl, aMagic, 8, szJ-8)) || memcmp(aMagic, aJournalMagic, 8) - || SQLITE_OK!=(rc = sqlite3OsRead(pJrnl, zSuper, len, szJ-16-len)) ){ return rc; } - /* See if the checksum matches the super-journal name */ - for(u=0; uzJournal */ + + /* Check if this looks like a real super-journal name. If it does not, + ** return SQLITE_OK without attempting to delete it. This is to limit + ** the degree to which a crafted journal file can be used to cause + ** SQLite to delete arbitrary files. */ + if( pagerIsSuperJrnlName(zSuper)==0 ){ + return SQLITE_OK; + } /* Allocate space for both the pJournal and pSuper file descriptors. ** If successful, open the super-journal file for reading. @@ -62170,9 +62252,8 @@ static int pager_delsuper(Pager *pPager, const char *zSuper){ */ rc = sqlite3OsFileSize(pSuper, &nSuperJournal); if( rc!=SQLITE_OK ) goto delsuper_out; - nSuperPtr = 1 + (i64)pVfs->mxPathname; - assert( nSuperJournal>=0 && nSuperPtr>0 ); - zFree = sqlite3Malloc(4 + nSuperJournal + 2 + nSuperPtr + 2); + assert( nSuperJournal>=0 ); + zFree = sqlite3Malloc(4 + nSuperJournal + 2); if( !zFree ){ rc = SQLITE_NOMEM_BKPT; goto delsuper_out; @@ -62181,7 +62262,6 @@ static int pager_delsuper(Pager *pPager, const char *zSuper){ } zFree[0] = zFree[1] = zFree[2] = zFree[3] = 0; zSuperJournal = &zFree[4]; - zSuperPtr = &zSuperJournal[nSuperJournal+2]; rc = sqlite3OsRead(pSuper, zSuperJournal, (int)nSuperJournal, 0); if( rc!=SQLITE_OK ) goto delsuper_out; zSuperJournal[nSuperJournal] = 0; @@ -62189,43 +62269,56 @@ static int pager_delsuper(Pager *pPager, const char *zSuper){ zJournal = zSuperJournal; while( (zJournal-zSuperJournal)zJournal)==0 ){ + bSeen = 1; + }else{ + int exists; + rc = sqlite3OsAccess(pVfs, zJournal, SQLITE_ACCESS_EXISTS, &exists); if( rc!=SQLITE_OK ){ goto delsuper_out; } + if( exists ){ + char *zSuperPtr = 0; - rc = readSuperJournal(pJournal, zSuperPtr, nSuperPtr); - sqlite3OsClose(pJournal); - if( rc!=SQLITE_OK ){ - goto delsuper_out; - } + /* One of the journals pointed to by the super-journal exists. + ** Open it and check if it points at the super-journal. If + ** so, return without deleting the super-journal file. + ** NB: zJournal is really a MAIN_JOURNAL. But call it a + ** SUPER_JOURNAL here so that the VFS will not send the zJournal + ** name into sqlite3_database_file_object(). + */ + int c; + int flags = (SQLITE_OPEN_READONLY|SQLITE_OPEN_SUPER_JOURNAL); + rc = sqlite3OsOpen(pVfs, zJournal, pJournal, flags, 0); + if( rc!=SQLITE_OK ){ + goto delsuper_out; + } - c = zSuperPtr[0]!=0 && strcmp(zSuperPtr, zSuper)==0; - if( c ){ - /* We have a match. Do not delete the super-journal file. */ - goto delsuper_out; + rc = readSuperJournal(pJournal, 1+(u64)pVfs->mxPathname, &zSuperPtr); + sqlite3OsClose(pJournal); + if( rc!=SQLITE_OK ){ + assert( zSuperPtr==0 ); + goto delsuper_out; + } + + c = zSuperPtr!=0 && strcmp(zSuperPtr, zSuper)==0; + freeSuperJournal(zSuperPtr); + if( c ){ + /* We have a match. Do not delete the super-journal file. */ + goto delsuper_out; + } } } zJournal += (sqlite3Strlen30(zJournal)+1); } sqlite3OsClose(pSuper); - rc = sqlite3OsDelete(pVfs, zSuper, 0); + if( bSeen ){ + /* Only delete the super-journal if bSeen is true - indicating that + ** the super-journal contained a pointer to this database's journal + ** file. */ + rc = sqlite3OsDelete(pVfs, zSuper, 0); + } delsuper_out: sqlite3_free(zFree); @@ -62430,19 +62523,11 @@ static int pager_playback(Pager *pPager, int isHot){ ** If a super-journal file name is specified, but the file is not ** present on disk, then the journal is not hot and does not need to be ** played back. - ** - ** TODO: Technically the following is an error because it assumes that - ** buffer Pager.pTmpSpace is (mxPathname+1) bytes or larger. i.e. that - ** ((pPager->pageSize+8) >= pPager->pVfs->mxPathname+1). Using os_unix.c, - ** mxPathname is 512, which is the same as the minimum allowable value - ** for pageSize, and so this assumption holds. But it might not for some - ** custom VFS. */ - zSuper = pPager->pTmpSpace; - rc = readSuperJournal(pPager->jfd, zSuper, 1+(i64)pPager->pVfs->mxPathname); - if( rc==SQLITE_OK && zSuper[0] ){ + */ + rc = readSuperJournal(pPager->jfd, 1+(i64)pPager->pVfs->mxPathname, &zSuper); + if( rc==SQLITE_OK && zSuper ){ rc = sqlite3OsAccess(pVfs, zSuper, SQLITE_ACCESS_EXISTS, &res); } - zSuper = 0; if( rc!=SQLITE_OK || !res ){ goto end_playback; } @@ -62571,30 +62656,20 @@ static int pager_playback(Pager *pPager, int isHot){ */ pPager->changeCountDone = pPager->tempFile; - if( rc==SQLITE_OK ){ - /* Leave 4 bytes of space before the super-journal filename in memory. - ** This is because it may end up being passed to sqlite3OsOpen(), in - ** which case it requires 4 0x00 bytes in memory immediately before - ** the filename. */ - zSuper = &pPager->pTmpSpace[4]; - rc = readSuperJournal(pPager->jfd, zSuper, 1+(i64)pPager->pVfs->mxPathname); - testcase( rc!=SQLITE_OK ); - } if( rc==SQLITE_OK && (pPager->eState>=PAGER_WRITER_DBMOD || pPager->eState==PAGER_OPEN) ){ rc = sqlite3PagerSync(pPager, 0); } if( rc==SQLITE_OK ){ - rc = pager_end_transaction(pPager, zSuper[0]!='\0', 0); + rc = pager_end_transaction(pPager, zSuper!=0, 0); testcase( rc!=SQLITE_OK ); } - if( rc==SQLITE_OK && zSuper[0] && res ){ + if( rc==SQLITE_OK && zSuper && res ){ /* If there was a super-journal and this routine will return success, ** see if it is possible to delete the super-journal. */ - assert( zSuper==&pPager->pTmpSpace[4] ); - memset(pPager->pTmpSpace, 0, 4); + assert( memcmp(&zSuper[-4], "\0\0\0\0", 4)==0 ); rc = pager_delsuper(pPager, zSuper); testcase( rc!=SQLITE_OK ); } @@ -62607,6 +62682,7 @@ static int pager_playback(Pager *pPager, int isHot){ ** back a journal created by a process with a different sector size ** value. Reset it to the correct value for this process. */ + freeSuperJournal(zSuper); setSectorSize(pPager); return rc; } @@ -68466,6 +68542,12 @@ static int walDecodeFrame( return 0; } + /* Need a valid page size + */ + if( !pWal->szPage ){ + return 0; + } + /* A frame is only valid if a checksum of the WAL header, ** all prior frames, the first 16 bytes of this frame-header, ** and the frame-data matches the checksum in the last 8 @@ -70320,7 +70402,7 @@ static int walBeginShmUnreliable(Wal *pWal, int *pChanged){ /* Allocate a buffer to read frames into */ assert( (pWal->szPage & (pWal->szPage-1))==0 ); - assert( pWal->szPage>=512 && pWal->szPage<=65536 ); + assert( (pWal->szPage>=512 && pWal->szPage<=65536) || pWal->szPage==0 ); szFrame = pWal->szPage + WAL_FRAME_HDRSIZE; aFrame = (u8 *)sqlite3_malloc64(szFrame); if( aFrame==0 ){ @@ -72818,6 +72900,9 @@ struct IntegrityCk { u32 *heap; /* Min-heap used for analyzing cell coverage */ sqlite3 *db; /* Database connection running the check */ i64 nRow; /* Number of rows visited in current tree */ +#ifdef SQLITE_DEBUG + u32 mxHeap; /* Maximum number of entries in the Min-heap */ +#endif }; /* @@ -75279,8 +75364,12 @@ static int btreeComputeFreeSpace(MemPage *pPage){ } next = get2byte(&data[pc]); size = get2byte(&data[pc+2]); + if( size<4 ){ + /* Minimum freeblock size is 4 */ + return SQLITE_CORRUPT_PAGE(pPage); + } nFree = nFree + size; - if( next<=pc+size+3 ) break; + if( next0 ){ @@ -79113,14 +79202,14 @@ static int indexCellCompare( /* This branch runs if the record-size field of the cell is a ** single byte varint and the record fits entirely on the main ** b-tree page. */ - testcase( pCell+nCell+1==pPage->aDataEnd ); + if( pCell + nCell >= pPage->aDataEnd ) return 99; c = xRecordCompare(nCell, (void*)&pCell[1], pIdxKey); }else if( !(pCell[1] & 0x80) && (nCell = ((nCell&0x7f)<<7) + pCell[1])<=pPage->maxLocal ){ /* The record-size field is a 2 byte varint and the record ** fits entirely on the main b-tree page. */ - testcase( pCell+nCell+2==pPage->aDataEnd ); + if( pCell + nCell >= pPage->aDataEnd ) return 99; c = xRecordCompare(nCell, (void*)&pCell[2], pIdxKey); }else{ /* If the record extends into overflow pages, do not attempt @@ -79282,14 +79371,17 @@ SQLITE_PRIVATE int sqlite3BtreeIndexMoveto( /* This branch runs if the record-size field of the cell is a ** single byte varint and the record fits entirely on the main ** b-tree page. */ - testcase( pCell+nCell+1==pPage->aDataEnd ); + if( pCell + nCell >= pPage->aDataEnd ){ + rc = SQLITE_CORRUPT_PAGE(pPage); + goto moveto_index_finish; + } c = xRecordCompare(nCell, (void*)&pCell[1], pIdxKey); }else if( !(pCell[1] & 0x80) && (nCell = ((nCell&0x7f)<<7) + pCell[1])<=pPage->maxLocal + && pCell + nCell < pPage->aDataEnd ){ /* The record-size field is a 2 byte varint and the record ** fits entirely on the main b-tree page. */ - testcase( pCell+nCell+2==pPage->aDataEnd ); c = xRecordCompare(nCell, (void*)&pCell[2], pIdxKey); }else{ /* The record flows over onto one or more overflow pages. In @@ -84154,6 +84246,7 @@ static int checkTreePage( } }else{ /* Populate the coverage-checking heap for leaf pages */ + assert( heap[0] < pCheck->mxHeap ); btreeHeapInsert(heap, (pc<<16)|(pc+info.nSize-1)); } } @@ -84173,6 +84266,7 @@ static int checkTreePage( u32 size; pc = get2byteAligned(&data[cellStart+i*2]); size = pPage->xCellSize(pPage, &data[pc]); + assert( heap[0] < pCheck->mxHeap ); btreeHeapInsert(heap, (pc<<16)|(pc+size-1)); } } @@ -84189,6 +84283,7 @@ static int checkTreePage( assert( (u32)i<=usableSize-4 ); /* Enforced by btreeComputeFreeSpace() */ size = get2byte(&data[i+2]); assert( (u32)(i+size)<=usableSize ); /* due to btreeComputeFreeSpace() */ + assert( heap[0] < pCheck->mxHeap ); btreeHeapInsert(heap, (((u32)i)<<16)|(i+size-1)); /* EVIDENCE-OF: R-58208-19414 The first 2 bytes of a freeblock are a ** big-endian integer which is the offset in the b-tree page of the next @@ -84323,6 +84418,9 @@ SQLITE_PRIVATE int sqlite3BtreeIntegrityCheck( goto integrity_ck_cleanup; } sCheck.heap = (u32*)sqlite3PageMalloc( pBt->pageSize ); +#ifdef SQLITE_DEBUG + sCheck.mxHeap = pBt->pageSize/4 - 1; +#endif if( sCheck.heap==0 ){ checkOom(&sCheck); goto integrity_ck_cleanup; @@ -84740,6 +84838,7 @@ SQLITE_PRIVATE int sqlite3BtreeConnectionCount(Btree *p){ */ struct sqlite3_backup { sqlite3* pDestDb; /* Destination database handle */ + char *zDestDb; Btree *pDest; /* Destination b-tree file */ u32 iDestSchema; /* Original schema cookie in destination */ int bDestLocked; /* True once a write-transaction is open on pDest */ @@ -84829,10 +84928,8 @@ static Btree *findBtree(sqlite3 *pErrorDb, sqlite3 *pDb, const char *zDb){ ** Attempt to set the page size of the destination to match the page size ** of the source. */ -static int setDestPgsz(sqlite3_backup *p){ - int rc; - rc = sqlite3BtreeSetPageSize(p->pDest,sqlite3BtreeGetPageSize(p->pSrc),0,0); - return rc; +static int setDestPgsz(Btree *pDest, Btree *pSrc){ + return sqlite3BtreeSetPageSize(pDest, sqlite3BtreeGetPageSize(pSrc), 0, 0); } /* @@ -84889,27 +84986,37 @@ SQLITE_API sqlite3_backup *sqlite3_backup_init( ); p = 0; }else { + int nDest = sqlite3Strlen30(zDestDb); + /* Allocate space for a new sqlite3_backup object... ** EVIDENCE-OF: R-64852-21591 The sqlite3_backup object is created by a ** call to sqlite3_backup_init() and is destroyed by a call to ** sqlite3_backup_finish(). */ - p = (sqlite3_backup *)sqlite3MallocZero(sizeof(sqlite3_backup)); + p = (sqlite3_backup*)sqlite3MallocZero(sizeof(sqlite3_backup)+nDest+1); if( !p ){ sqlite3Error(pDestDb, SQLITE_NOMEM_BKPT); + }else{ + p->zDestDb = (char*)&p[1]; + memcpy(p->zDestDb, zDestDb, nDest); } } /* If the allocation succeeded, populate the new object. */ if( p ){ + /* Do not store the pointer to the destination b-tree at this point. + ** This is because there is nothing preventing it from being detached + ** or otherwise freed before the first call to sqlite3_backup_step() + ** on this object. The source b-tree does not have this problem, as + ** incrementing Btree.nBackup (see below) effectively locks the object. */ + Btree *pDest = findBtree(pDestDb, pDestDb, zDestDb); p->pSrc = findBtree(pDestDb, pSrcDb, zSrcDb); - p->pDest = findBtree(pDestDb, pDestDb, zDestDb); p->pDestDb = pDestDb; p->pSrcDb = pSrcDb; p->iNext = 1; p->isAttached = 0; - if( 0==p->pSrc || 0==p->pDest - || checkReadTransaction(pDestDb, p->pDest)!=SQLITE_OK + if( 0==p->pSrc || 0==pDest + || checkReadTransaction(pDestDb, pDest)!=SQLITE_OK ){ /* One (or both) of the named databases did not exist or an OOM ** error was hit. Or there is a transaction open on the destination @@ -85033,7 +85140,7 @@ static void attachBackupObject(sqlite3_backup *p){ */ SQLITE_API int sqlite3_backup_step(sqlite3_backup *p, int nPage){ int rc; - int destMode; /* Destination journal mode */ + int destMode = 0; /* Destination journal mode */ int pgszSrc = 0; /* Source page size */ int pgszDest = 0; /* Destination page size */ @@ -85049,7 +85156,8 @@ SQLITE_API int sqlite3_backup_step(sqlite3_backup *p, int nPage){ rc = p->rc; if( !isFatalError(rc) ){ Pager * const pSrcPager = sqlite3BtreePager(p->pSrc); /* Source pager */ - Pager * const pDestPager = sqlite3BtreePager(p->pDest); /* Dest pager */ + Btree * pDest = 0; /* Dest btree */ + Pager * pDestPager = 0; /* Dest pager */ int ii; /* Iterator variable */ int nSrcPage = -1; /* Size of source db in pages */ int bCloseTrans = 0; /* True if src db requires unlocking */ @@ -85063,6 +85171,7 @@ SQLITE_API int sqlite3_backup_step(sqlite3_backup *p, int nPage){ rc = SQLITE_OK; } + /* If there is no open read-transaction on the source database, open ** one now. If a transaction is opened here, then it will be closed ** before this function exits. @@ -85072,34 +85181,48 @@ SQLITE_API int sqlite3_backup_step(sqlite3_backup *p, int nPage){ bCloseTrans = 1; } + /* Locate the destination btree and pager. */ + if( (pDest = p->pDest)==0 ){ + pDest = findBtree(p->pDestDb, p->pDestDb, p->zDestDb); + } + if( pDest==0 ){ + rc = SQLITE_ERROR; + }else{ + pDestPager = sqlite3BtreePager(pDest); + } + /* If the destination database has not yet been locked (i.e. if this ** is the first call to backup_step() for the current backup operation), ** try to set its page size to the same as the source database. This ** is especially important on ZipVFS systems, as in that case it is ** not possible to create a database file that uses one page size by ** writing to it with another. */ - if( p->bDestLocked==0 && rc==SQLITE_OK && setDestPgsz(p)==SQLITE_NOMEM ){ + if( p->bDestLocked==0 && rc==SQLITE_OK + && setDestPgsz(pDest, p->pSrc)==SQLITE_NOMEM + ){ rc = SQLITE_NOMEM; } /* Lock the destination database, if it is not locked already. */ if( SQLITE_OK==rc && p->bDestLocked==0 - && SQLITE_OK==(rc = sqlite3BtreeBeginTrans(p->pDest, 2, + && SQLITE_OK==(rc = sqlite3BtreeBeginTrans(pDest, 2, (int*)&p->iDestSchema)) ){ p->bDestLocked = 1; + p->pDest = pDest; } /* Do not allow backup if the destination database is in WAL mode ** and the page sizes are different between source and destination */ - pgszSrc = sqlite3BtreeGetPageSize(p->pSrc); - pgszDest = sqlite3BtreeGetPageSize(p->pDest); - destMode = sqlite3PagerGetJournalMode(sqlite3BtreePager(p->pDest)); - if( SQLITE_OK==rc - && (destMode==PAGER_JOURNALMODE_WAL || sqlite3PagerIsMemdb(pDestPager)) - && pgszSrc!=pgszDest - ){ - rc = SQLITE_READONLY; + if( rc==SQLITE_OK ){ + pgszSrc = sqlite3BtreeGetPageSize(p->pSrc); + pgszDest = sqlite3BtreeGetPageSize(p->pDest); + destMode = sqlite3PagerGetJournalMode(sqlite3BtreePager(p->pDest)); + if( (destMode==PAGER_JOURNALMODE_WAL || sqlite3PagerIsMemdb(pDestPager)) + && pgszSrc!=pgszDest + ){ + rc = SQLITE_READONLY; + } } /* Now that there is a read-lock on the source database, query the @@ -85317,7 +85440,9 @@ SQLITE_API int sqlite3_backup_finish(sqlite3_backup *p){ } /* If a transaction is still open on the Btree, roll it back. */ - sqlite3BtreeRollback(p->pDest, SQLITE_OK, 0); + if( p->pDest ){ + sqlite3BtreeRollback(p->pDest, SQLITE_OK, 0); + } /* Set the error code of the destination database handle. */ rc = (p->rc==SQLITE_DONE) ? SQLITE_OK : p->rc; @@ -93585,8 +93710,14 @@ SQLITE_PRIVATE const char *sqlite3VdbeFuncName(const sqlite3_context *pCtx){ ** added or changed. */ SQLITE_API int sqlite3_expired(sqlite3_stmt *pStmt){ - Vdbe *p = (Vdbe*)pStmt; - return p==0 || p->expired; + int iRet = 1; + if( pStmt ){ + Vdbe *p = (Vdbe*)pStmt; + sqlite3_mutex_enter(p->db->mutex); + iRet = p->expired; + sqlite3_mutex_leave(p->db->mutex); + } + return iRet; } #endif @@ -116881,7 +117012,7 @@ static void sqlite3ExprCodeIN( Expr *p = sqlite3VectorFieldSubexpr(pExpr->pLeft, i); if( pParse->nErr ) goto sqlite3ExprCodeIN_oom_error; if( sqlite3ExprCanBeNull(p) ){ - sqlite3VdbeAddOp2(v, OP_IsNull, rLhs+i, destStep2); + sqlite3VdbeAddOp2(v, OP_IsNull, rLhs+aiMap[i], destStep2); VdbeCoverage(v); } } @@ -116965,8 +117096,8 @@ static void sqlite3ExprCodeIN( ** ...)" is the collating sequence of x.". */ pColl = sqlite3ExprCollSeq(pParse, p); } - sqlite3VdbeAddOp3(v, OP_Column, iTab, i, r3); - sqlite3VdbeAddOp4(v, OP_Ne, rLhs+i, destNotNull, r3, + sqlite3VdbeAddOp3(v, OP_Column, iTab, aiMap[i], r3); + sqlite3VdbeAddOp4(v, OP_Ne, rLhs+aiMap[i], destNotNull, r3, (void*)pColl, P4_COLLSEQ); VdbeCoverage(v); sqlite3ReleaseTempReg(pParse, r3); @@ -125305,9 +125436,9 @@ static int loadStatTbl( } pIdx->nSampleCol = nIdxCol; pIdx->mxSample = nSample; - nByte = ROUND8(sizeof(IndexSample) * nSample); - nByte += sizeof(tRowcnt) * nIdxCol * 3 * nSample; - nByte += nIdxCol * sizeof(tRowcnt); /* Space for Index.aAvgEq[] */ + nByte = ROUND8(sizeof64(IndexSample) * nSample); + nByte += sizeof64(tRowcnt) * nIdxCol * 3 * nSample; + nByte += nIdxCol * sizeof64(tRowcnt); /* Space for Index.aAvgEq[] */ pIdx->aSample = sqlite3DbMallocZero(db, nByte); if( pIdx->aSample==0 ){ @@ -125315,7 +125446,7 @@ static int loadStatTbl( return SQLITE_NOMEM_BKPT; } pPtr = (u8*)pIdx->aSample; - pPtr += ROUND8(nSample*sizeof(pIdx->aSample[0])); + pPtr += ROUND8(nSample*sizeof64(pIdx->aSample[0])); pSpace = (tRowcnt*)pPtr; assert( EIGHT_BYTE_ALIGNMENT( pSpace ) ); pIdx->aAvgEq = pSpace; pSpace += nIdxCol; @@ -136787,7 +136918,7 @@ static void percentSort(double *a, unsigned int n){ i++; } }while( in/2 ){ + if( iLt>(int)(n/2) ){ if( n-iGt>=2 ) percentSort(a+iGt, n-iGt); n = iLt; }else{ @@ -151304,6 +151435,13 @@ SQLITE_PRIVATE Table *sqlite3ResultSetOfSelect(Parse *pParse, Select *pSelect, c sqlite3 *db = pParse->db; u64 savedFlags; + pParse->nNestSel++; +#if SQLITE_MAX_EXPR_DEPTH>0 + if( pParse->nNestSel >= db->aLimit[SQLITE_LIMIT_EXPR_DEPTH] ){ + sqlite3ErrorMsg(pParse, "VIEWs and/or subqueries nested too deep"); + return 0; + } +#endif savedFlags = db->flags; db->flags &= ~(u64)SQLITE_FullColNames; db->flags |= SQLITE_ShortColNames; @@ -151325,6 +151463,8 @@ SQLITE_PRIVATE Table *sqlite3ResultSetOfSelect(Parse *pParse, Select *pSelect, c sqlite3DeleteTable(db, pTab); return 0; } + pParse->nNestSel--; + assert( pParse->nNestSel>=0 ); return pTab; } @@ -159268,7 +159408,7 @@ static TriggerPrg *codeRowTrigger( Table *pTab, /* The table pTrigger is attached to */ int orconf /* ON CONFLICT policy to code trigger program with */ ){ - Parse *pTop = sqlite3ParseToplevel(pParse); + Parse *pTop; /* Top level Parse object */ sqlite3 *db = pParse->db; /* Database handle */ TriggerPrg *pPrg; /* Value to return */ Expr *pWhen = 0; /* Duplicate of trigger WHEN expression */ @@ -159277,10 +159417,24 @@ static TriggerPrg *codeRowTrigger( SubProgram *pProgram = 0; /* Sub-vdbe for trigger program */ int iEndTrigger = 0; /* Label to jump to if WHEN is false */ Parse sSubParse; /* Parse context for sub-vdbe */ + int nDepth; /* Trigger depth */ + /* Ensure that triggers are not chained too deep. This test is linear + ** in the chaining depth, but sensible code ought not be chaining + ** triggers excessively, so that shouldn't be a problem. + */ + pTop = pParse; + for(nDepth=0; pTop->pOuterParse; pTop = pTop->pOuterParse, nDepth++){} + if( nDepth>=db->aLimit[SQLITE_LIMIT_TRIGGER_DEPTH] ){ + sqlite3ErrorMsg(pParse, "triggers nested too deep"); + return 0; + } + + pTop = sqlite3ParseToplevel(pParse); assert( pTrigger->zName==0 || pTab==tableOfTrigger(pTrigger) ); assert( pTop->pVdbe ); + /* Allocate the TriggerPrg and SubProgram objects. To ensure that they ** are freed if an error occurs, link them into the Parse.pTriggerPrg ** list of the top-level Parse object sooner rather than later. */ @@ -161281,7 +161435,8 @@ SQLITE_PRIVATE void sqlite3UpsertDoUpdate( /* excluded.* columns of type REAL need to be converted to a hard real */ for(i=0; inCol; i++){ if( pTab->aCol[i].affinity==SQLITE_AFF_REAL ){ - sqlite3VdbeAddOp1(v, OP_RealAffinity, pTop->regData+i); + int iStorage = pTop->regData + sqlite3TableColumnToStorage(pTab, i); + sqlite3VdbeAddOp1(v, OP_RealAffinity, iStorage); } } sqlite3Update(pParse, pSrc, sqlite3ExprListDup(db,pUpsert->pUpsertSet,0), @@ -161869,6 +162024,7 @@ SQLITE_API int sqlite3_drop_modules(sqlite3 *db, const char** azNames){ #ifdef SQLITE_ENABLE_API_ARMOR if( !sqlite3SafetyCheckOk(db) ) return SQLITE_MISUSE_BKPT; #endif + sqlite3_mutex_enter(db->mutex); for(pThis=sqliteHashFirst(&db->aModule); pThis; pThis=pNext){ Module *pMod = (Module*)sqliteHashData(pThis); pNext = sqliteHashNext(pThis); @@ -161879,6 +162035,7 @@ SQLITE_API int sqlite3_drop_modules(sqlite3 *db, const char** azNames){ } createModule(db, pMod->zName, 0, 0, 0); } + sqlite3_mutex_leave(db->mutex); return SQLITE_OK; } @@ -167253,7 +167410,10 @@ static void transferJoinMarkings(Expr *pDerived, Expr *pBase){ static void markTermAsChild(WhereClause *pWC, int iChild, int iParent){ pWC->a[iChild].iParent = iParent; pWC->a[iChild].truthProb = pWC->a[iParent].truthProb; + assert( pWC->a[iParent].nChild < UMXV(pWC->a[0].nChild) ); pWC->a[iParent].nChild++; + testcase( pWC->a[iParent].nChild == UMXV(pWC->a[0].nChild) ); + } /* @@ -168030,6 +168190,7 @@ static void exprAnalyze( pList = pExpr->x.pList; assert( pList!=0 ); assert( pList->nExpr==2 ); + assert( pWC->a[idxTerm].nChild==0 ); for(i=0; i<2; i++){ Expr *pNewExpr; int idxNew; @@ -168240,8 +168401,11 @@ static void exprAnalyze( && pExpr->x.pSelect->pWin==0 #endif && pWC->op==TK_AND + && pExpr->x.pSelect->pEList->nExpr <= UMXV(pTerm->nChild) + /* ^-- See bug 2026-06-04T10:00:49Z */ ){ int i; + assert( pTerm->nChild==0 ); for(i=0; ipLeft); i++){ int idxNew; idxNew = whereClauseInsert(pWC, pExpr, TERM_VIRTUAL|TERM_SLICE); @@ -188310,13 +188474,17 @@ static int nocaseCollatingFunc( ** Return the ROWID of the most recent insert */ SQLITE_API sqlite_int64 sqlite3_last_insert_rowid(sqlite3 *db){ + i64 iRet; #ifdef SQLITE_ENABLE_API_ARMOR if( !sqlite3SafetyCheckOk(db) ){ (void)SQLITE_MISUSE_BKPT; return 0; } #endif - return db->lastRowid; + sqlite3_mutex_enter(db->mutex); + iRet = db->lastRowid; + sqlite3_mutex_leave(db->mutex); + return iRet; } /* @@ -188338,13 +188506,17 @@ SQLITE_API void sqlite3_set_last_insert_rowid(sqlite3 *db, sqlite3_int64 iRowid) ** Return the number of changes in the most recent call to sqlite3_exec(). */ SQLITE_API sqlite3_int64 sqlite3_changes64(sqlite3 *db){ + i64 iRet; #ifdef SQLITE_ENABLE_API_ARMOR if( !sqlite3SafetyCheckOk(db) ){ (void)SQLITE_MISUSE_BKPT; return 0; } #endif - return db->nChange; + sqlite3_mutex_enter(db->mutex); + iRet = db->nChange; + sqlite3_mutex_leave(db->mutex); + return iRet; } SQLITE_API int sqlite3_changes(sqlite3 *db){ return (int)sqlite3_changes64(db); @@ -188354,13 +188526,17 @@ SQLITE_API int sqlite3_changes(sqlite3 *db){ ** Return the number of changes since the database handle was opened. */ SQLITE_API sqlite3_int64 sqlite3_total_changes64(sqlite3 *db){ + i64 iRet; #ifdef SQLITE_ENABLE_API_ARMOR if( !sqlite3SafetyCheckOk(db) ){ (void)SQLITE_MISUSE_BKPT; return 0; } #endif - return db->nTotalChange; + sqlite3_mutex_enter(db->mutex); + iRet = db->nTotalChange; + sqlite3_mutex_leave(db->mutex); + return iRet; } SQLITE_API int sqlite3_total_changes(sqlite3 *db){ return (int)sqlite3_total_changes64(db); @@ -189043,6 +189219,7 @@ SQLITE_API int sqlite3_busy_timeout(sqlite3 *db, int ms){ #ifdef SQLITE_ENABLE_API_ARMOR if( !sqlite3SafetyCheckOk(db) ) return SQLITE_MISUSE_BKPT; #endif + sqlite3_mutex_enter(db->mutex); if( ms>0 ){ sqlite3_busy_handler(db, (int(*)(void*,int))sqliteDefaultBusyCallback, (void*)db); @@ -189053,6 +189230,7 @@ SQLITE_API int sqlite3_busy_timeout(sqlite3 *db, int ms){ }else{ sqlite3_busy_handler(db, 0, 0); } + sqlite3_mutex_leave(db->mutex); return SQLITE_OK; } @@ -189958,9 +190136,11 @@ SQLITE_API int sqlite3_set_errmsg(sqlite3 *db, int errcode, const char *zMsg){ */ SQLITE_API int sqlite3_error_offset(sqlite3 *db){ int iOffset = -1; - if( db && sqlite3SafetyCheckSickOrOk(db) && db->errCode ){ + if( db && sqlite3SafetyCheckSickOrOk(db) ){ sqlite3_mutex_enter(db->mutex); - iOffset = db->errByteOffset; + if( db->errCode ){ + iOffset = db->errByteOffset; + } sqlite3_mutex_leave(db->mutex); } return iOffset; @@ -190014,25 +190194,43 @@ SQLITE_API const void *sqlite3_errmsg16(sqlite3 *db){ ** passed to this function, we assume a malloc() failed during sqlite3_open(). */ SQLITE_API int sqlite3_errcode(sqlite3 *db){ - if( db && !sqlite3SafetyCheckSickOrOk(db) ){ + int iRet; + if( !db ) return SQLITE_NOMEM_BKPT; + if( !sqlite3SafetyCheckSickOrOk(db) ){ return SQLITE_MISUSE_BKPT; } - if( !db || db->mallocFailed ){ - return SQLITE_NOMEM_BKPT; + sqlite3_mutex_enter(db->mutex); + if( db->mallocFailed ){ + iRet = SQLITE_NOMEM_BKPT; + }else{ + iRet = db->errCode & db->errMask; } - return db->errCode & db->errMask; + sqlite3_mutex_leave(db->mutex); + return iRet; } SQLITE_API int sqlite3_extended_errcode(sqlite3 *db){ - if( db && !sqlite3SafetyCheckSickOrOk(db) ){ + int iRet; + if( !db ) return SQLITE_NOMEM_BKPT; + if( !sqlite3SafetyCheckSickOrOk(db) ){ return SQLITE_MISUSE_BKPT; } - if( !db || db->mallocFailed ){ - return SQLITE_NOMEM_BKPT; + sqlite3_mutex_enter(db->mutex); + if( db->mallocFailed ){ + iRet = SQLITE_NOMEM_BKPT; + }else{ + iRet = db->errCode; } - return db->errCode; + sqlite3_mutex_leave(db->mutex); + return iRet; } SQLITE_API int sqlite3_system_errno(sqlite3 *db){ - return db ? db->iSysErrno : 0; + int iRet = 0; + if( db ){ + sqlite3_mutex_enter(db->mutex); + iRet = db->iSysErrno; + sqlite3_mutex_leave(db->mutex); + } + return iRet; } /* @@ -190227,6 +190425,7 @@ SQLITE_API int sqlite3_limit(sqlite3 *db, int limitId, int newLimit){ if( limitId<0 || limitId>=SQLITE_N_LIMIT ){ return -1; } + sqlite3_mutex_enter(db->mutex); oldLimit = db->aLimit[limitId]; if( newLimit>=0 ){ /* IMP: R-52476-28732 */ if( newLimit>aHardLimit[limitId] ){ @@ -190236,6 +190435,7 @@ SQLITE_API int sqlite3_limit(sqlite3 *db, int limitId, int newLimit){ } db->aLimit[limitId] = newLimit; } + sqlite3_mutex_leave(db->mutex); return oldLimit; /* IMP: R-53341-35419 */ } @@ -190278,7 +190478,7 @@ SQLITE_PRIVATE int sqlite3ParseUri( const char *zVfs = zDefaultVfs; char *zFile; char c; - int nUri = sqlite3Strlen30(zUri); + i64 nUri = strlen(zUri); assert( *pzErrMsg==0 ); @@ -190288,8 +190488,8 @@ SQLITE_PRIVATE int sqlite3ParseUri( ){ char *zOpt; int eState; /* Parser state when parsing URI */ - int iIn; /* Input character index */ - int iOut = 0; /* Output character index */ + i64 iIn; /* Input character index */ + i64 iOut = 0; /* Output character index */ u64 nByte = nUri+8; /* Bytes of space to allocate */ /* Make sure the SQLITE_OPEN_URI flag is set to indicate to the VFS xOpen @@ -190323,7 +190523,7 @@ SQLITE_PRIVATE int sqlite3ParseUri( while( zUri[iIn] && zUri[iIn]!='/' ) iIn++; if( iIn!=7 && (iIn!=16 || memcmp("localhost", &zUri[7], 9)) ){ *pzErrMsg = sqlite3_mprintf("invalid uri authority: %.*s", - iIn-7, &zUri[7]); + (int)(iIn-7), &zUri[7]); rc = SQLITE_ERROR; goto parse_uri_out; } @@ -190398,11 +190598,11 @@ SQLITE_PRIVATE int sqlite3ParseUri( ** here. Options that are interpreted here include "vfs" and those that ** correspond to flags that may be passed to the sqlite3_open_v2() ** method. */ - zOpt = &zFile[sqlite3Strlen30(zFile)+1]; + zOpt = &zFile[strlen(zFile)+1]; while( zOpt[0] ){ - int nOpt = sqlite3Strlen30(zOpt); + i64 nOpt = strlen(zOpt); char *zVal = &zOpt[nOpt+1]; - int nVal = sqlite3Strlen30(zVal); + i64 nVal = strlen(zVal); if( nOpt==3 && memcmp("vfs", zOpt, 3)==0 ){ zVfs = zVal; @@ -190448,7 +190648,7 @@ SQLITE_PRIVATE int sqlite3ParseUri( int mode = 0; for(i=0; aMode[i].z; i++){ const char *z = aMode[i].z; - if( nVal==sqlite3Strlen30(z) && 0==memcmp(zVal, z, nVal) ){ + if( nVal==(i64)strlen(z) && 0==memcmp(zVal, z, nVal) ){ mode = aMode[i].mode; break; } @@ -191133,13 +191333,17 @@ SQLITE_API int sqlite3_global_recover(void){ ** by the next COMMIT or ROLLBACK. */ SQLITE_API int sqlite3_get_autocommit(sqlite3 *db){ + int iRet; #ifdef SQLITE_ENABLE_API_ARMOR if( !sqlite3SafetyCheckOk(db) ){ (void)SQLITE_MISUSE_BKPT; return 0; } #endif - return db->autoCommit; + sqlite3_mutex_enter(db->mutex); + iRet = db->autoCommit; + sqlite3_mutex_leave(db->mutex); + return iRet; } /* @@ -192164,17 +192368,19 @@ SQLITE_PRIVATE Btree *sqlite3DbNameToBtree(sqlite3 *db, const char *zDbName){ ** of range. */ SQLITE_API const char *sqlite3_db_name(sqlite3 *db, int N){ + const char *zRet = 0; #ifdef SQLITE_ENABLE_API_ARMOR if( !sqlite3SafetyCheckOk(db) ){ (void)SQLITE_MISUSE_BKPT; return 0; } #endif - if( N<0 || N>=db->nDb ){ - return 0; - }else{ - return db->aDb[N].zDbSName; + sqlite3_mutex_enter(db->mutex); + if( N>=0 && NnDb ){ + zRet = db->aDb[N].zDbSName; } + sqlite3_mutex_leave(db->mutex); + return zRet; } /* @@ -195796,8 +196002,13 @@ static void fts3PutDeltaVarint( sqlite3_int64 iVal /* Write this value to the list */ ){ assert_fts3_nc( iVal-*piPrev > 0 || (*piPrev==0 && iVal==0) ); - *pp += sqlite3Fts3PutVarint(*pp, iVal-*piPrev); - *piPrev = iVal; + if( iVal-(*piPrev)>=0 ){ + /* Refuse to write a negative delta integer. This only happens with a + ** corrupt db (see the assert above) and can cause buffer overwrites + ** in some cases. */ + *pp += sqlite3Fts3PutVarint(*pp, iVal-*piPrev); + *piPrev = iVal; + } } /* @@ -198151,6 +198362,7 @@ static int fts3EvalDeferredPhrase(Fts3Cursor *pCsr, Fts3Phrase *pPhrase){ char *p1; char *p2; char *aOut; + i64 nAlloc = (i64)nPoslist*2 + FTS3_BUFFER_PADDING; if( nMaxUndeferred>iPrev ){ p1 = aPoslist; @@ -198162,7 +198374,7 @@ static int fts3EvalDeferredPhrase(Fts3Cursor *pCsr, Fts3Phrase *pPhrase){ nDistance = iPrev - nMaxUndeferred; } - aOut = (char *)sqlite3Fts3MallocZero(((i64)nPoslist)+FTS3_BUFFER_PADDING); + aOut = (char *)sqlite3Fts3MallocZero(nAlloc); if( !aOut ){ sqlite3_free(aPoslist); return SQLITE_NOMEM; @@ -207217,6 +207429,10 @@ static void fts3ReadEndBlockField( for(/* no-op */; zText[i]>='0' && zText[i]<='9'; i++){ iVal = iVal*10 + (zText[i] - '0'); } + + /* This if() clause is just to avoid an integer overflow. The record is + ** corrupt in this case. */ + if( (i64)iVal==SMALLEST_INT64 ) iMul = 1; *pnByte = ((i64)iVal * (i64)iMul); } } @@ -208443,7 +208659,7 @@ static int fts3IncrmergeLoad( return FTS_CORRUPT_VTAB; } - pWriter->nLeafEst = (int)((iEnd - iStart) + 1)/FTS_MAX_APPENDABLE_HEIGHT; + pWriter->nLeafEst = (int)(((iEnd - iStart)+1)/FTS_MAX_APPENDABLE_HEIGHT); pWriter->iStart = iStart; pWriter->iEnd = iEnd; pWriter->iAbsLevel = iAbsLevel; @@ -218557,7 +218773,7 @@ struct RtreeCursor { sqlite3_stmt *pReadAux; /* Statement to read aux-data */ RtreeSearchPoint sPoint; /* Cached next search point */ RtreeNode *aNode[RTREE_CACHE_SZ]; /* Rtree node cache */ - u32 anQueue[RTREE_MAX_DEPTH+1]; /* Number of queued entries by iLevel */ + u32 anQueue[RTREE_MAX_DEPTH+2]; /* Number of queued entries by iLevel */ }; /* Return the Rtree of a RtreeCursor */ @@ -219012,6 +219228,9 @@ static int nodeAcquire( rc = SQLITE_CORRUPT_VTAB; RTREE_IS_CORRUPT(pRtree); } + }else if( iNode<=0 ){ + RTREE_IS_CORRUPT(pRtree); + rc = SQLITE_CORRUPT_VTAB; }else if( pRtree->iNodeSize==sqlite3_blob_bytes(pRtree->pNodeBlob) ){ pNode = (RtreeNode *)sqlite3_malloc64(sizeof(RtreeNode)+pRtree->iNodeSize); if( !pNode ){ @@ -219037,7 +219256,7 @@ static int nodeAcquire( */ if( rc==SQLITE_OK && pNode && iNode==1 ){ pRtree->iDepth = readInt16(pNode->zData); - if( pRtree->iDepth>RTREE_MAX_DEPTH ){ + if( pRtree->iDepth>=RTREE_MAX_DEPTH ){ rc = SQLITE_CORRUPT_VTAB; RTREE_IS_CORRUPT(pRtree); } @@ -226600,16 +226819,26 @@ static unsigned int rbuDeltaGetInt(const char **pz, int *pLen){ 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, -1, -1, -1, -1, 36, -1, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, -1, -1, -1, 63, -1, + + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, }; unsigned int v = 0; int c; unsigned char *z = (unsigned char*)*pz; - unsigned char *zStart = z; - while( (c = zValue[0x7f&*(z++)])>=0 ){ - v = (v<<6) + c; + unsigned char *zEnd = z + (*pLen); + while( z=0 ){ + v = (v<<6) + c; + z++; } - z--; - *pLen -= (int)(z - zStart); + + *pLen -= (int)(z - (unsigned char*)*pz); *pz = (char*)z; return v; } @@ -226685,7 +226914,7 @@ static int rbuDeltaApply( #endif limit = rbuDeltaGetInt(&zDelta, &lenDelta); - if( *zDelta!='\n' ){ + if( lenDelta<=0 || *zDelta!='\n' ){ /* ERROR: size integer not terminated by "\n" */ return -1; } @@ -226693,11 +226922,12 @@ static int rbuDeltaApply( while( *zDelta && lenDelta>0 ){ unsigned int cnt, ofst; cnt = rbuDeltaGetInt(&zDelta, &lenDelta); + if( lenDelta<=0 ) return -1; switch( zDelta[0] ){ case '@': { zDelta++; lenDelta--; ofst = rbuDeltaGetInt(&zDelta, &lenDelta); - if( lenDelta>0 && zDelta[0]!=',' ){ + if( lenDelta>0 || zDelta[0]!=',' ){ /* ERROR: copy command not terminated by ',' */ return -1; } @@ -226722,7 +226952,7 @@ static int rbuDeltaApply( /* ERROR: insert command gives an output larger than predicted */ return -1; } - if( (int)cnt>lenDelta ){ + if( (i64)cnt>(i64)lenDelta ){ /* ERROR: insert count exceeds size of delta */ return -1; } @@ -226760,7 +226990,7 @@ static int rbuDeltaApply( static int rbuDeltaOutputSize(const char *zDelta, int lenDelta){ int size; size = rbuDeltaGetInt(&zDelta, &lenDelta); - if( *zDelta!='\n' ){ + if( lenDelta<=0 || *zDelta!='\n' ){ /* ERROR: size integer not terminated by "\n" */ return -1; } @@ -226808,7 +227038,7 @@ static void rbuFossilDeltaFunc( return; } - aOut = sqlite3_malloc(nOut+1); + aOut = sqlite3_malloc64((i64)nOut+1); if( aOut==0 ){ sqlite3_result_error_nomem(context); }else{ @@ -234987,7 +235217,7 @@ static void sessionAppendStr( int *pRc ){ int nStr = sqlite3Strlen30(zStr); - if( 0==sessionBufferGrow(p, nStr+1, pRc) ){ + if( 0==sessionBufferGrow(p, (i64)nStr+1, pRc) ){ memcpy(&p->aBuf[p->nBuf], zStr, nStr); p->nBuf += nStr; p->aBuf[p->nBuf] = 0x00; @@ -240385,14 +240615,17 @@ static void sessionAppendRecordMerge( u8 *a2, int n2, /* Record 2 */ int *pRc /* IN/OUT: error code */ ){ - sessionBufferGrow(pBuf, n1+n2, pRc); + u8 *a1Eof = &a1[n1]; + u8 *a2Eof = &a2[n2]; + + sessionBufferGrow(pBuf, (i64)n1+n2, pRc); if( *pRc==SQLITE_OK ){ int i; u8 *pOut = &pBuf->aBuf[pBuf->nBuf]; for(i=0; i0 && (*a1==0 || *a1==0xFF)) ){ memcpy(pOut, a2, nn2); pOut += nn2; }else{ @@ -240434,7 +240667,7 @@ static void sessionAppendPartialUpdate( u8 *aChange, int nChange, /* Record to rebase against */ int *pRc /* IN/OUT: Return Code */ ){ - sessionBufferGrow(pBuf, 2+nRec+nChange, pRc); + sessionBufferGrow(pBuf, (i64)2+nRec+nChange, pRc); if( *pRc==SQLITE_OK ){ int bData = 0; u8 *pOut = &pBuf->aBuf[pBuf->nBuf]; @@ -244774,7 +245007,7 @@ static void fts5SnippetFunction( int rc = SQLITE_OK; /* Return code */ int iCol; /* 1st argument to snippet() */ const char *zEllips; /* 4th argument to snippet() */ - int nToken; /* 5th argument to snippet() */ + i64 nToken; /* 5th argument to snippet() */ int nInst = 0; /* Number of instance matches this row */ int i; /* Used to iterate through instances */ int nPhrase; /* Number of phrases in query */ @@ -244799,7 +245032,7 @@ static void fts5SnippetFunction( ctx.zClose = fts5ValueToText(apVal[2]); ctx.iRangeEnd = -1; zEllips = fts5ValueToText(apVal[3]); - nToken = sqlite3_value_int(apVal[4]); + nToken = (int)(MIN( MAX(sqlite3_value_int64(apVal[4]), 0), 64)); iBestCol = (iCol>=0 ? iCol : 0); nPhrase = pApi->xPhraseCount(pFts); @@ -247515,7 +247748,7 @@ static int fts5ExprNearIsMatch(int *pRc, Fts5ExprNearset *pNear){ i64 iPos = a[i].reader.iPos; Fts5PoslistWriter *pWriter = &a[i].writer; if( a[i].pOut->n==0 || iPos!=pWriter->iPrev ){ - sqlite3Fts5PoslistWriterAppend(a[i].pOut, pWriter, iPos); + sqlite3Fts5PoslistSafeAppend(a[i].pOut, &pWriter->iPrev, iPos); } } @@ -248466,10 +248699,10 @@ static int fts5ParseTokenize( memset(pSyn, 0, (size_t)nByte); pSyn->pTerm = ((char*)pSyn) + sizeof(Fts5ExprTerm) + sizeof(Fts5Buffer); pSyn->nFullTerm = pSyn->nQueryTerm = nToken; + memcpy(pSyn->pTerm, pToken, nToken); if( pCtx->pConfig->bTokendata ){ pSyn->nQueryTerm = (int)strlen(pSyn->pTerm); } - memcpy(pSyn->pTerm, pToken, nToken); pSyn->pSynonym = pPhrase->aTerm[pPhrase->nTerm-1].pSynonym; pPhrase->aTerm[pPhrase->nTerm-1].pSynonym = pSyn; } @@ -251752,7 +251985,7 @@ static int fts5StructureDecode( i += fts5GetVarint32(&pData[i], nTotal); if( nTotalnMerge ) rc = FTS5_CORRUPT; pLvl->aSeg = (Fts5StructureSegment*)sqlite3Fts5MallocZero(&rc, - nTotal * sizeof(Fts5StructureSegment) + (i64)nTotal * sizeof(Fts5StructureSegment) ); nSegment -= nTotal; } @@ -252708,7 +252941,7 @@ static void fts5SegIterReverseNewPage(Fts5Index *p, Fts5SegIter *pIter){ while( p->rc==SQLITE_OK && pIter->iLeafPgno>pIter->iTermLeafPgno ){ Fts5Data *pNew; pIter->iLeafPgno--; - pNew = fts5DataRead(p, FTS5_SEGMENT_ROWID( + pNew = fts5LeafRead(p, FTS5_SEGMENT_ROWID( pIter->pSeg->iSegid, pIter->iLeafPgno )); if( pNew ){ @@ -258576,8 +258809,8 @@ static void fts5IndexTombstoneRebuild( ){ const int MINSLOT = 32; int nSlotPerPage = MAX(MINSLOT, (p->pConfig->pgsz - 8) / szKey); - int nSlot = 0; /* Number of slots in each output page */ - int nOut = 0; + i64 nSlot = 0; /* Number of slots in each output page */ + i64 nOut = 0; /* Figure out how many output pages (nOut) and how many slots per ** page (nSlot). There are three possibilities: @@ -258602,23 +258835,26 @@ static void fts5IndexTombstoneRebuild( nSlot = MINSLOT; }else if( pSeg->nPgTombstone==1 ){ /* Case 2. */ - int nElem = (int)fts5GetU32(&pData1->p[4]); + u32 nElem = fts5GetU32(&pData1->p[4]); assert( pData1 && iPg1==0 ); - nOut = 1; - nSlot = MAX(nElem*4, MINSLOT); - if( nSlot>nSlotPerPage ) nOut = 0; + if( nElem>((u32)nSlotPerPage/4) ){ + nOut = 0; + }else{ + nOut = 1; + nSlot = MAX((i64)nElem*4, MINSLOT); + } } if( nOut==0 ){ /* Case 3. */ - nOut = (pSeg->nPgTombstone * 2 + 1); + nOut = ((i64)pSeg->nPgTombstone * 2 + 1); nSlot = nSlotPerPage; } /* Allocate the required array and output pages */ while( 1 ){ int res = 0; - int ii = 0; - int szPage = 0; + i64 ii = 0; + i64 szPage = 0; Fts5Data **apOut = 0; /* Allocate space for the new hash table */ @@ -259123,9 +259359,13 @@ static void fts5IndexIntegrityCheckSegment( FTS5_CORRUPT_ROWID(p, iRow); }else{ iOff += fts5GetVarint32(&pLeaf->p[iOff], nTerm); - res = fts5Memcmp(&pLeaf->p[iOff], zIdxTerm, MIN(nTerm, nIdxTerm)); - if( res==0 ) res = nTerm - nIdxTerm; - if( res<0 ) FTS5_CORRUPT_ROWID(p, iRow); + if( iOff+nTerm>pLeaf->szLeaf ){ + FTS5_CORRUPT_ROWID(p, iRow); + }else{ + res = fts5Memcmp(&pLeaf->p[iOff], zIdxTerm, MIN(nTerm, nIdxTerm)); + if( res==0 ) res = nTerm - nIdxTerm; + if( res<0 ) FTS5_CORRUPT_ROWID(p, iRow); + } } fts5IntegrityCheckPgidx(p, iRow, pLeaf); @@ -259156,7 +259396,7 @@ static void fts5IndexIntegrityCheckSegment( /* Check any rowid-less pages that occur before the current leaf. */ for(iPg=iPrevLeaf+1; iPgeContent==FTS5_CONTENT_NORMAL || pConfig->eContent==FTS5_CONTENT_UNINDEXED ){ - int nDefn = 32 + pConfig->nCol*10; - char *zDefn = sqlite3_malloc64(32 + (sqlite3_int64)pConfig->nCol * 20); - if( zDefn==0 ){ - rc = SQLITE_NOMEM; - }else{ - int i; - int iOff; - sqlite3_snprintf(nDefn, zDefn, "id INTEGER PRIMARY KEY"); - iOff = (int)strlen(zDefn); - for(i=0; inCol; i++){ - if( pConfig->eContent==FTS5_CONTENT_NORMAL - || pConfig->abUnindexed[i] - ){ - sqlite3_snprintf(nDefn-iOff, &zDefn[iOff], ", c%d", i); - iOff += (int)strlen(&zDefn[iOff]); - } + int i = 0; + char *zDefn = 0; + sqlite3_str *pDefn = sqlite3_str_new(pConfig->db); + + sqlite3_str_appendf(pDefn, "id INTEGER PRIMARY KEY"); + for(i=0; inCol; i++){ + if( pConfig->eContent==FTS5_CONTENT_NORMAL || pConfig->abUnindexed[i] ){ + sqlite3_str_appendf(pDefn, ", c%d", i); } - if( pConfig->bLocale ){ - for(i=0; inCol; i++){ - if( pConfig->abUnindexed[i]==0 ){ - sqlite3_snprintf(nDefn-iOff, &zDefn[iOff], ", l%d", i); - iOff += (int)strlen(&zDefn[iOff]); - } + } + if( pConfig->bLocale ){ + for(i=0; inCol; i++){ + if( pConfig->abUnindexed[i]==0 ){ + sqlite3_str_appendf(pDefn, ", l%d", i); } } + } + zDefn = sqlite3_str_finish(pDefn); + + if( zDefn ){ rc = sqlite3Fts5CreateTable(pConfig, "content", zDefn, 0, pzErr); + sqlite3_free(zDefn); + }else{ + rc = SQLITE_NOMEM; } - sqlite3_free(zDefn); } if( rc==SQLITE_OK && pConfig->bColumnsize ){ diff --git a/sqlite3-binding.h b/sqlite3-binding.h index 7ea57603..9417bce8 100644 --- a/sqlite3-binding.h +++ b/sqlite3-binding.h @@ -147,12 +147,12 @@ extern "C" { ** [sqlite3_libversion_number()], [sqlite3_sourceid()], ** [sqlite_version()] and [sqlite_source_id()]. */ -#define SQLITE_VERSION "3.53.2" -#define SQLITE_VERSION_NUMBER 3053002 -#define SQLITE_SOURCE_ID "2026-06-03 19:12:13 d6e03d8c777cfa2d35e3b60d8ec3e0187f3e9f99d8e2ee9cac695fd6fcdf1a24" +#define SQLITE_VERSION "3.53.3" +#define SQLITE_VERSION_NUMBER 3053003 +#define SQLITE_SOURCE_ID "2026-06-26 20:14:12 d4c0e51e4aeb96955b99185ab9cde75c339e2c29c3f3f12428d364a10d782c62" #define SQLITE_SCM_BRANCH "branch-3.53" -#define SQLITE_SCM_TAGS "release version-3.53.2" -#define SQLITE_SCM_DATETIME "2026-06-03T19:12:13.350Z" +#define SQLITE_SCM_TAGS "release version-3.53.3" +#define SQLITE_SCM_DATETIME "2026-06-26T20:14:12.354Z" /* ** CAPI3REF: Run-Time Library Version Numbers @@ -4367,7 +4367,8 @@ SQLITE_API int sqlite3_limit(sqlite3*, int id, int newVal); ** or in an ORDER BY or GROUP BY clause.
)^ ** ** [[SQLITE_LIMIT_EXPR_DEPTH]] ^(
SQLITE_LIMIT_EXPR_DEPTH
-**
The maximum depth of the parse tree on any expression.
)^ +**
The maximum depth of the parse tree on any expression and +** the maximum nesting depth for subqueries and VIEWs
)^ ** ** [[SQLITE_LIMIT_PARSER_DEPTH]] ^(
SQLITE_LIMIT_PARSER_DEPTH
**
The maximum depth of the LALR(1) parser stack used to analyze @@ -4398,7 +4399,8 @@ SQLITE_API int sqlite3_limit(sqlite3*, int id, int newVal); **
The maximum index number of any [parameter] in an SQL statement.)^ ** ** [[SQLITE_LIMIT_TRIGGER_DEPTH]] ^(
SQLITE_LIMIT_TRIGGER_DEPTH
-**
The maximum depth of recursion for triggers.
)^ +**
The maximum depth of recursion for triggers, and the maximum +** nesting depth for separate triggers.
)^ ** ** [[SQLITE_LIMIT_WORKER_THREADS]] ^(
SQLITE_LIMIT_WORKER_THREADS
**
The maximum number of auxiliary worker threads that a single