Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
7c4c014
Fixed som typos and wrappings in sqlite3_func_crypt.go documentation.
kberov Jan 3, 2026
6f56c01
fix Exec "not enough args" error reporting wrong count
semihbkgr May 10, 2026
73d5bc7
refactor: Clean up deprecated // +build legacy build tags
Jul 21, 2026
840a3ec
Avoid per-row goroutines for query cancellation
bradengroom Aug 1, 2026
cff623d
Merge branch 'master' into fix-exec-not-enough-args-message
mattn Sep 5, 2026
4048620
Merge branch 'master' into typos
mattn Sep 5, 2026
7538105
Merge branch 'master' into refactor-build-tags
mattn Sep 5, 2026
bf851dc
Merge pull request #1396 from semihbkgr/fix-exec-not-enough-args-message
mattn Sep 5, 2026
9aa4bd3
Merge pull request #1428 from zxysilent/refactor-build-tags
mattn Sep 5, 2026
6507893
Merge pull request #1367 from kberov/typos
mattn Sep 5, 2026
c7ed68d
Flush statement cache when the schema changes
mattn Sep 5, 2026
5341cee
Gate cache on runtime version and skip probing in transactions
mattn Sep 5, 2026
2294cd9
Replace copy-on-write handle map with sync.Map
mattn Sep 5, 2026
5b285a1
Merge branch 'master' into codex/efficient-query-cancellation
mattn Sep 5, 2026
be93f7a
Merge pull request #1453 from mattn/fix-handle-map-quadratic
mattn Sep 5, 2026
6428cad
Merge pull request #1444 from bradengroom/codex/efficient-query-cance…
mattn Sep 5, 2026
d7f5da7
Merge pull request #1452 from mattn/fix-stmt-cache-schema-change
mattn Sep 5, 2026
c8212b8
Replace schema probe with eager first step for cached statements
mattn Sep 5, 2026
b0be46f
Merge pull request #1454 from mattn/fix-stmt-cache-probe-cost
mattn Sep 5, 2026
1071cc9
Cache bind parameter count at prepare time
mattn Sep 5, 2026
686bf7d
Fuse bind and first step into one CGO crossing
mattn Sep 5, 2026
fd78993
Allocate the shared row buffer under the statement lock
mattn Sep 5, 2026
55ddd5a
Merge pull request #1455 from mattn/perf-fused-query-path
mattn Sep 5, 2026
958d54c
Merge remote-tracking branch 'mattn/master' into merge-upstream
otoolep Sep 5, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion backup_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
// license that can be found in the LICENSE file.

//go:build cgo
// +build cgo

package sqlite3

Expand Down
77 changes: 21 additions & 56 deletions callback.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ import (
"math"
"reflect"
"sync"
"sync/atomic"
"unsafe"
)

Expand Down Expand Up @@ -104,27 +103,28 @@ type handleVal struct {
val any
}

var handleLock sync.Mutex
var handleVals atomic.Value // stores map[unsafe.Pointer]handleVal
// handleVals maps unsafe.Pointer handles to handleVal. A sync.Map keeps
// lookups lock-free on the hot callback path while insertion and removal
// stay O(1); the previous copy-on-write map made every registration copy
// the whole table, so opening N connections (each registering several
// functions) was quadratic in time and allocation.
var handleVals sync.Map

func newHandle(db *SQLiteConn, v any) unsafe.Pointer {
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")
}

handleLock.Lock()
defer handleLock.Unlock()

next := cloneHandleVals(len(loadHandleVals()) + 1)
next[p] = val
handleVals.Store(next)
handleVals.Store(p, handleVal{db: db, val: v})
return p
}

func lookupHandleVal(handle unsafe.Pointer) handleVal {
return loadHandleVals()[handle]
v, ok := handleVals.Load(handle)
if !ok {
return handleVal{}
}
return v.(handleVal)
}

func lookupHandle(handle unsafe.Pointer) any {
Expand All @@ -134,55 +134,20 @@ func lookupHandle(handle unsafe.Pointer) any {
// deleteHandle releases a single handle created by newHandle. It is a no-op
// if the handle is unknown (e.g. already released).
func deleteHandle(handle unsafe.Pointer) {
handleLock.Lock()
defer handleLock.Unlock()

current := loadHandleVals()
if _, ok := current[handle]; !ok {
return
if _, ok := handleVals.LoadAndDelete(handle); ok {
C.free(handle)
}
next := make(map[unsafe.Pointer]handleVal, len(current)-1)
for h, v := range current {
if h == handle {
continue
}
next[h] = v
}
handleVals.Store(next)
C.free(handle)
}

func deleteHandles(db *SQLiteConn) {
handleLock.Lock()
defer handleLock.Unlock()

current := loadHandleVals()
if len(current) == 0 {
return
}

next := make(map[unsafe.Pointer]handleVal, len(current))
for handle, val := range current {
if val.db == db {
C.free(handle)
continue
handleVals.Range(func(handle, val any) bool {
if val.(handleVal).db == db {
if _, ok := handleVals.LoadAndDelete(handle); ok {
C.free(handle.(unsafe.Pointer))
}
}
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
return true
})
}

// This is only here so that tests can refer to it.
Expand Down
21 changes: 20 additions & 1 deletion callback_bench_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
// license that can be found in the LICENSE file.

//go:build cgo
// +build cgo

package sqlite3

Expand Down Expand Up @@ -49,6 +48,14 @@ func BenchmarkHandleLookupBeforeAfter(b *testing.B) {
return after.lookup(handle).val
})
})

var syncTable syncMapHandleTable
syncTable.vals.Store(handle, value)
b.Run("sync_map", func(b *testing.B) {
benchmarkHandleLookupParallel(b, func() any {
return syncTable.lookup(handle).val
})
})
}

func benchmarkHandleLookupParallel(b *testing.B, lookup func() any) {
Expand All @@ -75,6 +82,18 @@ func (t *mutexHandleTable) lookup(handle unsafe.Pointer) handleVal {
return t.vals[handle]
}

type syncMapHandleTable struct {
vals sync.Map
}

func (t *syncMapHandleTable) lookup(handle unsafe.Pointer) handleVal {
v, ok := t.vals.Load(handle)
if !ok {
return handleVal{}
}
return v.(handleVal)
}

type atomicHandleTable struct {
vals atomic.Value
}
Expand Down
1 change: 0 additions & 1 deletion callback_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
// license that can be found in the LICENSE file.

//go:build cgo
// +build cgo

package sqlite3

Expand Down
4 changes: 2 additions & 2 deletions convert.go
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ func convertAssign(dest, src any) error {
}

dpv := reflect.ValueOf(dest)
if dpv.Kind() != reflect.Ptr {
if dpv.Kind() != reflect.Pointer {
return errors.New("destination not a pointer")
}
if dpv.IsNil() {
Expand Down Expand Up @@ -192,7 +192,7 @@ func convertAssign(dest, src any) error {
// This also allows scanning into user defined types such as "type Int int64".
// For symmetry, also check for string destination types.
switch dv.Kind() {
case reflect.Ptr:
case reflect.Pointer:
if src == nil {
dv.Set(reflect.Zero(dv.Type()))
return nil
Expand Down
1 change: 0 additions & 1 deletion error_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
// license that can be found in the LICENSE file.

//go:build cgo
// +build cgo

package sqlite3

Expand Down
Loading