mirror of
https://github.com/ncruces/go-sqlite3.git
synced 2026-01-19 09:04:16 +00:00
Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e2a2d447ce | ||
|
|
75190a6f98 | ||
|
|
35c5619880 | ||
|
|
b51234cc82 | ||
|
|
cf7b89d3c4 | ||
|
|
ff9f27a778 | ||
|
|
f26f1a17a9 |
25
conn.go
25
conn.go
@@ -23,6 +23,7 @@ type Conn struct {
|
||||
interrupt context.Context
|
||||
pending *Stmt
|
||||
stmts []*Stmt
|
||||
timer *time.Timer
|
||||
busy func(int) bool
|
||||
log func(xErrorCode, string)
|
||||
collation func(*Conn, string)
|
||||
@@ -389,11 +390,25 @@ func timeoutCallback(ctx context.Context, mod api.Module, pDB uint32, count, tmo
|
||||
}
|
||||
|
||||
if delay = min(delay, tmout-prior); delay > 0 {
|
||||
time.Sleep(time.Duration(delay) * time.Millisecond)
|
||||
retry = 1
|
||||
delay := time.Duration(delay) * time.Millisecond
|
||||
if c.interrupt == nil || c.interrupt.Done() == nil {
|
||||
time.Sleep(delay)
|
||||
return 1
|
||||
}
|
||||
if c.timer == nil {
|
||||
c.timer = time.NewTimer(delay)
|
||||
} else {
|
||||
c.timer.Reset(delay)
|
||||
}
|
||||
select {
|
||||
case <-c.interrupt.Done():
|
||||
c.timer.Stop()
|
||||
case <-c.timer.C:
|
||||
return 1
|
||||
}
|
||||
}
|
||||
}
|
||||
return retry
|
||||
return 0
|
||||
}
|
||||
|
||||
// BusyHandler registers a callback to handle [BUSY] errors.
|
||||
@@ -492,9 +507,7 @@ func (c *Conn) stmtsIter(yield func(*Stmt) bool) {
|
||||
|
||||
// DriverConn is implemented by the SQLite [database/sql] driver connection.
|
||||
//
|
||||
// It can be used to access SQLite features like [online backup].
|
||||
//
|
||||
// [online backup]: https://sqlite.org/backup.html
|
||||
// Deprecated: use [github.com/ncruces/go-sqlite3/driver.Conn] instead.
|
||||
type DriverConn interface {
|
||||
Raw() *Conn
|
||||
}
|
||||
|
||||
@@ -130,6 +130,11 @@ type SQLite struct {
|
||||
term func(*sqlite3.Conn) error
|
||||
}
|
||||
|
||||
var (
|
||||
// Ensure these interfaces are implemented:
|
||||
_ driver.DriverContext = &SQLite{}
|
||||
)
|
||||
|
||||
// Open implements [database/sql/driver.Driver].
|
||||
func (d *SQLite) Open(name string) (driver.Conn, error) {
|
||||
c, err := d.newConnector(name)
|
||||
@@ -255,6 +260,35 @@ func (n *connector) Connect(ctx context.Context) (_ driver.Conn, err error) {
|
||||
return c, nil
|
||||
}
|
||||
|
||||
// Conn is implemented by the SQLite [database/sql] driver connections.
|
||||
//
|
||||
// It can be used to access SQLite features like [online backup]:
|
||||
//
|
||||
// db, err := driver.Open("temp.db")
|
||||
// if err != nil {
|
||||
// log.Fatal(err)
|
||||
// }
|
||||
// defer db.Close()
|
||||
//
|
||||
// conn, err := db.Conn(context.TODO())
|
||||
// if err != nil {
|
||||
// log.Fatal(err)
|
||||
// }
|
||||
//
|
||||
// err = conn.Raw(func(driverConn any) error {
|
||||
// conn := driverConn.(driver.Conn)
|
||||
// return conn.Raw().Backup("main", "backup.db")
|
||||
// })
|
||||
// if err != nil {
|
||||
// log.Fatal(err)
|
||||
// }
|
||||
//
|
||||
// [online backup]: https://sqlite.org/backup.html
|
||||
type Conn interface {
|
||||
Raw() *sqlite3.Conn
|
||||
driver.Conn
|
||||
}
|
||||
|
||||
type conn struct {
|
||||
*sqlite3.Conn
|
||||
txLock string
|
||||
@@ -266,10 +300,10 @@ type conn struct {
|
||||
|
||||
var (
|
||||
// Ensure these interfaces are implemented:
|
||||
_ Conn = &conn{}
|
||||
_ driver.ConnBeginTx = &conn{}
|
||||
_ driver.ConnPrepareContext = &conn{}
|
||||
_ driver.ExecerContext = &conn{}
|
||||
_ driver.ConnBeginTx = &conn{}
|
||||
_ sqlite3.DriverConn = &conn{}
|
||||
)
|
||||
|
||||
func (c *conn) Raw() *sqlite3.Conn {
|
||||
|
||||
Binary file not shown.
Binary file not shown.
4
go.mod
4
go.mod
@@ -12,8 +12,8 @@ require (
|
||||
github.com/tetratelabs/wazero v1.8.0
|
||||
golang.org/x/crypto v0.26.0
|
||||
golang.org/x/sync v0.8.0
|
||||
golang.org/x/sys v0.24.0
|
||||
golang.org/x/text v0.17.0
|
||||
golang.org/x/sys v0.25.0
|
||||
golang.org/x/text v0.18.0
|
||||
lukechampine.com/adiantum v1.1.1
|
||||
)
|
||||
|
||||
|
||||
8
go.sum
8
go.sum
@@ -14,9 +14,9 @@ golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw=
|
||||
golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54=
|
||||
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
|
||||
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg=
|
||||
golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc=
|
||||
golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
|
||||
golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34=
|
||||
golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224=
|
||||
golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
|
||||
lukechampine.com/adiantum v1.1.1 h1:4fp6gTxWCqpEbLy40ExiYDDED3oUNWx5cTqBCtPdZqA=
|
||||
lukechampine.com/adiantum v1.1.1/go.mod h1:LrAYVnTYLnUtE/yMp5bQr0HstAf060YUF8nM0B6+rUw=
|
||||
|
||||
@@ -5,7 +5,7 @@ go 1.21
|
||||
toolchain go1.23.0
|
||||
|
||||
require (
|
||||
github.com/ncruces/go-sqlite3 v0.18.0
|
||||
github.com/ncruces/go-sqlite3 v0.18.1
|
||||
gorm.io/gorm v1.25.11
|
||||
)
|
||||
|
||||
@@ -14,6 +14,6 @@ require (
|
||||
github.com/jinzhu/now v1.1.5 // indirect
|
||||
github.com/ncruces/julianday v1.0.0 // indirect
|
||||
github.com/tetratelabs/wazero v1.8.0 // indirect
|
||||
golang.org/x/sys v0.24.0 // indirect
|
||||
golang.org/x/text v0.17.0 // indirect
|
||||
golang.org/x/sys v0.25.0 // indirect
|
||||
golang.org/x/text v0.18.0 // indirect
|
||||
)
|
||||
|
||||
@@ -2,15 +2,15 @@ github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD
|
||||
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
|
||||
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
|
||||
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
||||
github.com/ncruces/go-sqlite3 v0.18.0 h1:aH7WGzOC0CYpUPG1LdFg7JApybiuXgYUE2itzLBwhPM=
|
||||
github.com/ncruces/go-sqlite3 v0.18.0/go.mod h1:eEOyZnW1dGTJ+zDpMuzfYamEUBtdFz5zeYhqLBtHxvM=
|
||||
github.com/ncruces/go-sqlite3 v0.18.1 h1:iN8IMZV5EMxpH88NUac9vId23eTKNFUhP7jgY0EBbNc=
|
||||
github.com/ncruces/go-sqlite3 v0.18.1/go.mod h1:eEOyZnW1dGTJ+zDpMuzfYamEUBtdFz5zeYhqLBtHxvM=
|
||||
github.com/ncruces/julianday v1.0.0 h1:fH0OKwa7NWvniGQtxdJRxAgkBMolni2BjDHaWTxqt7M=
|
||||
github.com/ncruces/julianday v1.0.0/go.mod h1:Dusn2KvZrrovOMJuOt0TNXL6tB7U2E8kvza5fFc9G7g=
|
||||
github.com/tetratelabs/wazero v1.8.0 h1:iEKu0d4c2Pd+QSRieYbnQC9yiFlMS9D+Jr0LsRmcF4g=
|
||||
github.com/tetratelabs/wazero v1.8.0/go.mod h1:yAI0XTsMBhREkM/YDAK/zNou3GoiAce1P6+rp/wQhjs=
|
||||
golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg=
|
||||
golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc=
|
||||
golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
|
||||
golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34=
|
||||
golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224=
|
||||
golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
|
||||
gorm.io/gorm v1.25.11 h1:/Wfyg1B/je1hnDx3sMkX+gAlxrlZpn6X0BXRlwXlvHg=
|
||||
gorm.io/gorm v1.25.11/go.mod h1:xh7N7RHfYlNc5EmcI/El95gXusucDrQnHXe0+CgWcLQ=
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
#define SQLITE_ENABLE_ATOMIC_WRITE
|
||||
#define SQLITE_ENABLE_BATCH_ATOMIC_WRITE
|
||||
#define SQLITE_ENABLE_COLUMN_METADATA
|
||||
#define SQLITE_ENABLE_SETLK_TIMEOUT 2
|
||||
#define SQLITE_ENABLE_STAT4 1
|
||||
|
||||
// We have our own memdb VFS.
|
||||
|
||||
@@ -131,7 +131,12 @@ func TestMultiProcess(t *testing.T) {
|
||||
"&_pragma=journal_mode(truncate)" +
|
||||
"&_pragma=synchronous(off)"
|
||||
|
||||
cmd := exec.Command(os.Args[0], append(os.Args[1:], "-test.v", "-test.run=TestChildProcess")...)
|
||||
exe, err := os.Executable()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
cmd := exec.Command(exe, append(os.Args[1:], "-test.v", "-test.run=TestChildProcess")...)
|
||||
out, err := cmd.StdoutPipe()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
|
||||
@@ -79,16 +79,15 @@ func (f *vfsFile) Lock(lock LockLevel) error {
|
||||
// A PENDING lock is needed before acquiring an EXCLUSIVE lock.
|
||||
if f.lock < LOCK_PENDING {
|
||||
// If we're already RESERVED, we can block indefinitely,
|
||||
// since only new readers may briefly hold the PENDING lock.
|
||||
// since only incoming readers may briefly hold the PENDING lock.
|
||||
if rc := osGetPendingLock(f.File, reserved /* block */); rc != _OK {
|
||||
return rc
|
||||
}
|
||||
f.lock = LOCK_PENDING
|
||||
}
|
||||
// We already have PENDING, so we're just waiting for readers to leave.
|
||||
// If we were RESERVED, we can wait for a little while, before invoking
|
||||
// the busy handler; we will only do this once.
|
||||
if rc := osGetExclusiveLock(f.File, reserved /* wait */); rc != _OK {
|
||||
// We are now PENDING, so we're just waiting for readers to leave.
|
||||
// If we were RESERVED, we can block for a bit before invoking the busy handler.
|
||||
if rc := osGetExclusiveLock(f.File, reserved /* block */); rc != _OK {
|
||||
return rc
|
||||
}
|
||||
f.lock = LOCK_EXCLUSIVE
|
||||
|
||||
@@ -53,10 +53,11 @@ func osLock(file *os.File, typ int16, start, len int64, timeout time.Duration, d
|
||||
if errno, _ := err.(unix.Errno); errno != unix.EAGAIN {
|
||||
break
|
||||
}
|
||||
if timeout < time.Since(before) {
|
||||
if time.Since(before) > timeout {
|
||||
break
|
||||
}
|
||||
time.Sleep(time.Duration(rand.Int63n(int64(time.Millisecond))))
|
||||
const sleepIncrement = 1024*1024 - 1 // power of two, ~1ms
|
||||
time.Sleep(time.Duration(rand.Int63() & sleepIncrement))
|
||||
}
|
||||
}
|
||||
return osLockErrorCode(err, def)
|
||||
|
||||
@@ -32,9 +32,9 @@ func osGetPendingLock(file *os.File, block bool) _ErrorCode {
|
||||
return osWriteLock(file, _PENDING_BYTE, 1, timeout)
|
||||
}
|
||||
|
||||
func osGetExclusiveLock(file *os.File, wait bool) _ErrorCode {
|
||||
func osGetExclusiveLock(file *os.File, block bool) _ErrorCode {
|
||||
var timeout time.Duration
|
||||
if wait {
|
||||
if block {
|
||||
timeout = time.Millisecond
|
||||
}
|
||||
// Acquire the EXCLUSIVE lock.
|
||||
|
||||
@@ -38,9 +38,9 @@ func osGetPendingLock(file *os.File, block bool) _ErrorCode {
|
||||
return osWriteLock(file, _PENDING_BYTE, 1, timeout)
|
||||
}
|
||||
|
||||
func osGetExclusiveLock(file *os.File, wait bool) _ErrorCode {
|
||||
func osGetExclusiveLock(file *os.File, block bool) _ErrorCode {
|
||||
var timeout time.Duration
|
||||
if wait {
|
||||
if block {
|
||||
timeout = time.Millisecond
|
||||
}
|
||||
|
||||
@@ -134,10 +134,11 @@ func osLock(file *os.File, flags, start, len uint32, timeout time.Duration, def
|
||||
if errno, _ := err.(windows.Errno); errno != windows.ERROR_LOCK_VIOLATION {
|
||||
break
|
||||
}
|
||||
if timeout < time.Since(before) {
|
||||
if time.Since(before) > timeout {
|
||||
break
|
||||
}
|
||||
time.Sleep(time.Duration(rand.Int63n(int64(time.Millisecond))))
|
||||
const sleepIncrement = 1024*1024 - 1 // power of two, ~1ms
|
||||
time.Sleep(time.Duration(rand.Int63() & sleepIncrement))
|
||||
}
|
||||
}
|
||||
return osLockErrorCode(err, def)
|
||||
|
||||
24
vfs/shm.go
24
vfs/shm.go
@@ -6,6 +6,7 @@ import (
|
||||
"context"
|
||||
"io"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/ncruces/go-sqlite3/internal/util"
|
||||
"github.com/tetratelabs/wazero/api"
|
||||
@@ -49,6 +50,7 @@ type vfsShm struct {
|
||||
path string
|
||||
regions []*util.MappedRegion
|
||||
readOnly bool
|
||||
blocking bool
|
||||
}
|
||||
|
||||
func (s *vfsShm) shmOpen() _ErrorCode {
|
||||
@@ -76,6 +78,13 @@ func (s *vfsShm) shmOpen() _ErrorCode {
|
||||
if s.readOnly {
|
||||
return _READONLY_CANTINIT
|
||||
}
|
||||
// Do not use a blocking lock here.
|
||||
// If the lock cannot be obtained immediately,
|
||||
// it means some other connection is truncating the file.
|
||||
// And after it has done so, it will not release its lock,
|
||||
// but only downgrade it to a shared lock.
|
||||
// So no point in blocking here.
|
||||
// The call below to obtain the shared DMS lock may use a blocking lock.
|
||||
if rc := osWriteLock(s.File, _SHM_DMS, 1, 0); rc != _OK {
|
||||
return rc
|
||||
}
|
||||
@@ -83,7 +92,7 @@ func (s *vfsShm) shmOpen() _ErrorCode {
|
||||
return _IOERR_SHMOPEN
|
||||
}
|
||||
}
|
||||
if rc := osReadLock(s.File, _SHM_DMS, 1, 0); rc != _OK {
|
||||
if rc := osReadLock(s.File, _SHM_DMS, 1, time.Millisecond); rc != _OK {
|
||||
return rc
|
||||
}
|
||||
return _OK
|
||||
@@ -150,13 +159,18 @@ func (s *vfsShm) shmLock(offset, n int32, flags _ShmFlag) _ErrorCode {
|
||||
panic(util.AssertErr())
|
||||
}
|
||||
|
||||
var timeout time.Duration
|
||||
if s.blocking {
|
||||
timeout = time.Millisecond
|
||||
}
|
||||
|
||||
switch {
|
||||
case flags&_SHM_UNLOCK != 0:
|
||||
return osUnlock(s.File, _SHM_BASE+int64(offset), int64(n))
|
||||
case flags&_SHM_SHARED != 0:
|
||||
return osReadLock(s.File, _SHM_BASE+int64(offset), int64(n), 0)
|
||||
return osReadLock(s.File, _SHM_BASE+int64(offset), int64(n), timeout)
|
||||
case flags&_SHM_EXCLUSIVE != 0:
|
||||
return osWriteLock(s.File, _SHM_BASE+int64(offset), int64(n), 0)
|
||||
return osWriteLock(s.File, _SHM_BASE+int64(offset), int64(n), timeout)
|
||||
default:
|
||||
panic(util.AssertErr())
|
||||
}
|
||||
@@ -181,3 +195,7 @@ func (s *vfsShm) shmUnmap(delete bool) {
|
||||
s.Close()
|
||||
s.File = nil
|
||||
}
|
||||
|
||||
func (s *vfsShm) shmEnableBlocking(block bool) {
|
||||
s.blocking = block
|
||||
}
|
||||
|
||||
4
vfs/tests/mptest/testdata/mptest.wasm.bz2
vendored
4
vfs/tests/mptest/testdata/mptest.wasm.bz2
vendored
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:4d58c92d45fb60dc2eea461b0e7c1d512cb1dd00deafdfaab3e24b943f714f69
|
||||
size 475960
|
||||
oid sha256:42858f7755a42e64913c5119dcd5ecb3fbceac71e13952d4e8b62a97c05311b5
|
||||
size 476253
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:90eb053e2a17bd73d8fb17f82c60e595b71018a7880c7b04d7f4b6aae187f5a5
|
||||
size 489132
|
||||
oid sha256:cf4fb9d930647e29f0da71981b6b1e204e4949c816e5f7d6aeded6a90ad9e8ab
|
||||
size 489516
|
||||
|
||||
@@ -243,6 +243,15 @@ func vfsFileControl(ctx context.Context, mod api.Module, pFile uint32, op _Fcntl
|
||||
return _OK
|
||||
}
|
||||
|
||||
case _FCNTL_LOCK_TIMEOUT:
|
||||
if file, ok := file.(FileSharedMemory); ok {
|
||||
if iface, ok := file.SharedMemory().(interface{ shmEnableBlocking(bool) }); ok {
|
||||
if i := util.ReadUint32(mod, pArg); i == 0 || i == 1 {
|
||||
iface.shmEnableBlocking(i != 0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
case _FCNTL_PERSIST_WAL:
|
||||
if file, ok := file.(FilePersistentWAL); ok {
|
||||
if i := util.ReadUint32(mod, pArg); int32(i) >= 0 {
|
||||
|
||||
Reference in New Issue
Block a user