diff --git a/go.mod b/go.mod index b2b72b3c3..bdfff73bf 100644 --- a/go.mod +++ b/go.mod @@ -45,7 +45,7 @@ require ( github.com/miekg/dns v1.1.59 github.com/minio/minio-go/v7 v7.0.70 github.com/mitchellh/mapstructure v1.5.0 - github.com/ncruces/go-sqlite3 v0.14.1-0.20240422130245-189fbc98ac32 + github.com/ncruces/go-sqlite3 v0.15.0 github.com/oklog/ulid v1.3.1 github.com/prometheus/client_golang v1.18.0 github.com/spf13/cobra v1.8.0 diff --git a/go.sum b/go.sum index 90bb4e072..d581e94b6 100644 --- a/go.sum +++ b/go.sum @@ -448,8 +448,8 @@ github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9G github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/moul/http2curl v1.0.0 h1:dRMWoAtb+ePxMlLkrCbAqh4TlPHXvoGUSQ323/9Zahs= github.com/moul/http2curl v1.0.0/go.mod h1:8UbvGypXm98wA/IqH45anm5Y2Z6ep6O31QGOAZ3H0fQ= -github.com/ncruces/go-sqlite3 v0.14.1-0.20240422130245-189fbc98ac32 h1:bXZoglQBxhicSCUi0lEhveQKR1iDy455dZ2OVaKZAGI= -github.com/ncruces/go-sqlite3 v0.14.1-0.20240422130245-189fbc98ac32/go.mod h1:kHHYmFmK4G2VFFoIovEg9BEQ8BP+D81y4ESHXnzJV/w= +github.com/ncruces/go-sqlite3 v0.15.0 h1:C+SIrcYsAIR5GUYWmCnif6x81n6BS9y75vYcQynuGNU= +github.com/ncruces/go-sqlite3 v0.15.0/go.mod h1:kHHYmFmK4G2VFFoIovEg9BEQ8BP+D81y4ESHXnzJV/w= github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4= github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls= github.com/ncruces/julianday v1.0.0 h1:fH0OKwa7NWvniGQtxdJRxAgkBMolni2BjDHaWTxqt7M= diff --git a/vendor/github.com/ncruces/go-sqlite3/README.md b/vendor/github.com/ncruces/go-sqlite3/README.md index 39534544c..3cf5b4afe 100644 --- a/vendor/github.com/ncruces/go-sqlite3/README.md +++ b/vendor/github.com/ncruces/go-sqlite3/README.md @@ -87,9 +87,10 @@ This project aims for [high test coverage](https://github.com/ncruces/go-sqlite3 It also benefits greatly from [SQLite's](https://sqlite.org/testing.html) and [wazero's](https://tetrate.io/blog/introducing-wazero-from-tetrate/#:~:text=Rock%2Dsolid%20test%20approach) thorough testing. +Every commit is [tested](.github/workflows/test.yml) on +Linux (amd64/arm64/386/riscv64), macOS (amd64/arm64), Windows, FreeBSD and illumos. The Go VFS is tested by running SQLite's -[mptest](https://github.com/sqlite/sqlite/blob/master/mptest/mptest.c) -on Linux, macOS, Windows and FreeBSD. +[mptest](https://github.com/sqlite/sqlite/blob/master/mptest/mptest.c). ### Performance @@ -106,5 +107,5 @@ The Wasm and VFS layers are also tested by running SQLite's - [`github.com/mattn/go-sqlite3`](https://pkg.go.dev/github.com/mattn/go-sqlite3) - [`github.com/zombiezen/go-sqlite`](https://pkg.go.dev/github.com/zombiezen/go-sqlite) -[^1]: anything else you find in [`go.mod`](./go.mod) is either a test dependency, +[^1]: anything else you find in `go.mod` is either a test dependency, or needed by one of the extensions. diff --git a/vendor/github.com/ncruces/go-sqlite3/conn.go b/vendor/github.com/ncruces/go-sqlite3/conn.go index bb4cae219..8ba034fee 100644 --- a/vendor/github.com/ncruces/go-sqlite3/conn.go +++ b/vendor/github.com/ncruces/go-sqlite3/conn.go @@ -2,7 +2,6 @@ package sqlite3 import ( "context" - "errors" "fmt" "math" "net/url" @@ -10,6 +9,7 @@ import ( "time" "github.com/ncruces/go-sqlite3/internal/util" + "github.com/ncruces/go-sqlite3/vfs" "github.com/tetratelabs/wazero/api" ) @@ -102,15 +102,14 @@ func (c *Conn) openDB(filename string, flags OpenFlag) (uint32, error) { pragmas.WriteString(`;`) } } - - pragmaPtr := c.arena.string(pragmas.String()) - r := c.call("sqlite3_exec", uint64(handle), uint64(pragmaPtr), 0, 0, 0) - if err := c.sqlite.error(r, handle, pragmas.String()); err != nil { - if errors.Is(err, ERROR) { + if pragmas.Len() != 0 { + pragmaPtr := c.arena.string(pragmas.String()) + r := c.call("sqlite3_exec", uint64(handle), uint64(pragmaPtr), 0, 0, 0) + if err := c.sqlite.error(r, handle, pragmas.String()); err != nil { err = fmt.Errorf("sqlite3: invalid _pragma: %w", err) + c.closeDB(handle) + return 0, err } - c.closeDB(handle) - return 0, err } } c.call("sqlite3_progress_handler_go", uint64(handle), 100) @@ -175,7 +174,7 @@ func (c *Conn) Prepare(sql string) (stmt *Stmt, tail string, err error) { // // https://sqlite.org/c3ref/prepare.html func (c *Conn) PrepareFlags(sql string, flags PrepareFlag) (stmt *Stmt, tail string, err error) { - if len(sql) > _MAX_LENGTH { + if len(sql) > _MAX_SQL_LENGTH { return nil, "", TOOBIG } @@ -216,6 +215,20 @@ func (c *Conn) DBName(n int) string { return util.ReadString(c.mod, ptr, _MAX_NAME) } +// Filename returns the filename for a database. +// +// https://sqlite.org/c3ref/db_filename.html +func (c *Conn) Filename(schema string) *vfs.Filename { + var ptr uint32 + if schema != "" { + defer c.arena.mark()() + ptr = c.arena.string(schema) + } + + r := c.call("sqlite3_db_filename", uint64(c.handle), uint64(ptr)) + return vfs.OpenFilename(c.ctx, c.mod, uint32(r), vfs.OPEN_MAIN_DB) +} + // ReadOnly determines if a database is read-only. // // https://sqlite.org/c3ref/db_readonly.html @@ -333,8 +346,8 @@ func (c *Conn) checkInterrupt() { } func progressCallback(ctx context.Context, mod api.Module, pDB uint32) (interrupt uint32) { - if c, ok := ctx.Value(connKey{}).(*Conn); ok && c.handle == pDB && c.commit != nil { - if c.interrupt != nil && c.interrupt.Err() != nil { + if c, ok := ctx.Value(connKey{}).(*Conn); ok && c.handle == pDB && c.interrupt != nil { + if c.interrupt.Err() != nil { interrupt = 1 } } diff --git a/vendor/github.com/ncruces/go-sqlite3/driver/driver.go b/vendor/github.com/ncruces/go-sqlite3/driver/driver.go index f7dc6fe2e..b496f76ec 100644 --- a/vendor/github.com/ncruces/go-sqlite3/driver/driver.go +++ b/vendor/github.com/ncruces/go-sqlite3/driver/driver.go @@ -70,8 +70,8 @@ func init() { // Open opens the SQLite database specified by dataSourceName as a [database/sql.DB]. // // The init function is called by the driver on new connections. -// The conn can be used to execute queries, register functions, etc. -// Any error return closes the conn and passes the error to [database/sql]. +// The [sqlite3.Conn] can be used to execute queries, register functions, etc. +// Any error returned closes the connection and is returned to [database/sql]. func Open(dataSourceName string, init func(*sqlite3.Conn) error) (*sql.DB, error) { c, err := (&SQLite{Init: init}).OpenConnector(dataSourceName) if err != nil { @@ -80,15 +80,15 @@ func Open(dataSourceName string, init func(*sqlite3.Conn) error) (*sql.DB, error return sql.OpenDB(c), nil } +// SQLite implements [database/sql/driver.Driver]. type SQLite struct { - - // The init function is called by the driver on new connections. - // The conn can be used to execute queries, register functions, etc. - // Any error return closes the conn and passes the error to [database/sql]. + // Init function is called by the driver on new connections. + // The [sqlite3.Conn] can be used to execute queries, register functions, etc. + // Any error returned closes the connection and is returned to [database/sql]. Init func(*sqlite3.Conn) error } -// Open: implements [database/sql.Driver]. +// Open implements [database/sql/driver.Driver]. func (d *SQLite) Open(name string) (driver.Conn, error) { c, err := d.newConnector(name) if err != nil { @@ -97,7 +97,7 @@ func (d *SQLite) Open(name string) (driver.Conn, error) { return c.Connect(context.Background()) } -// OpenConnector: implements [database/sql.DriverContext]. +// OpenConnector implements [database/sql/driver.DriverContext]. func (d *SQLite) OpenConnector(name string) (driver.Connector, error) { return d.newConnector(name) } @@ -327,7 +327,7 @@ func (c *conn) ExecContext(ctx context.Context, query string, args []driver.Name return newResult(c.Conn), nil } -func (*conn) CheckNamedValue(arg *driver.NamedValue) error { +func (c *conn) CheckNamedValue(arg *driver.NamedValue) error { return nil } diff --git a/vendor/github.com/ncruces/go-sqlite3/embed/exports.txt b/vendor/github.com/ncruces/go-sqlite3/embed/exports.txt index 3b73ddb74..da16316bb 100644 --- a/vendor/github.com/ncruces/go-sqlite3/embed/exports.txt +++ b/vendor/github.com/ncruces/go-sqlite3/embed/exports.txt @@ -53,6 +53,7 @@ sqlite3_create_module_go sqlite3_create_window_function_go sqlite3_database_file_object sqlite3_db_config +sqlite3_db_filename sqlite3_db_name sqlite3_db_readonly sqlite3_db_release_memory @@ -62,6 +63,9 @@ sqlite3_errmsg sqlite3_error_offset sqlite3_errstr sqlite3_exec +sqlite3_filename_database +sqlite3_filename_journal +sqlite3_filename_wal sqlite3_finalize sqlite3_get_autocommit sqlite3_get_auxdata diff --git a/vendor/github.com/ncruces/go-sqlite3/embed/sqlite3.wasm b/vendor/github.com/ncruces/go-sqlite3/embed/sqlite3.wasm index bb3a88853..2f36b2948 100644 Binary files a/vendor/github.com/ncruces/go-sqlite3/embed/sqlite3.wasm and b/vendor/github.com/ncruces/go-sqlite3/embed/sqlite3.wasm differ diff --git a/vendor/github.com/ncruces/go-sqlite3/internal/util/mmap.go b/vendor/github.com/ncruces/go-sqlite3/internal/util/mmap.go index 826580008..26f30beaa 100644 --- a/vendor/github.com/ncruces/go-sqlite3/internal/util/mmap.go +++ b/vendor/github.com/ncruces/go-sqlite3/internal/util/mmap.go @@ -1,4 +1,4 @@ -//go:build (darwin || linux || illumos) && (amd64 || arm64 || riscv64) && !sqlite3_flock && !sqlite3_noshm && !sqlite3_nosys +//go:build (darwin || linux) && (amd64 || arm64 || riscv64) && !(sqlite3_flock || sqlite3_noshm || sqlite3_nosys) package util @@ -12,22 +12,13 @@ import ( "golang.org/x/sys/unix" ) +func withMmappedAllocator(ctx context.Context) context.Context { + return experimental.WithMemoryAllocator(ctx, + experimental.MemoryAllocatorFunc(mmappedAllocator)) +} + type mmapState struct { regions []*MappedRegion - enabled bool -} - -func (s *mmapState) init(ctx context.Context, enabled bool) context.Context { - if s.enabled = enabled; enabled { - return experimental.WithMemoryAllocator(ctx, - experimental.MemoryAllocatorFunc(mmappedAllocator)) - } - return ctx -} - -func CanMapFiles(ctx context.Context) bool { - s := ctx.Value(moduleKey{}).(*moduleState) - return s.mmapState.enabled } func (s *mmapState) new(ctx context.Context, mod api.Module, size int32) *MappedRegion { diff --git a/vendor/github.com/ncruces/go-sqlite3/internal/util/mmap_other.go b/vendor/github.com/ncruces/go-sqlite3/internal/util/mmap_other.go index 2e89f8064..89631e093 100644 --- a/vendor/github.com/ncruces/go-sqlite3/internal/util/mmap_other.go +++ b/vendor/github.com/ncruces/go-sqlite3/internal/util/mmap_other.go @@ -1,4 +1,4 @@ -//go:build !(darwin || linux || illumos) || !(amd64 || arm64 || riscv64) || sqlite3_flock || sqlite3_noshm || sqlite3_nosys +//go:build !(darwin || linux) || !(amd64 || arm64 || riscv64) || sqlite3_flock || sqlite3_noshm || sqlite3_nosys package util @@ -6,10 +6,6 @@ import "context" type mmapState struct{} -func (s *mmapState) init(ctx context.Context, _ bool) context.Context { +func withMmappedAllocator(ctx context.Context) context.Context { return ctx } - -func CanMapFiles(ctx context.Context) bool { - return false -} diff --git a/vendor/github.com/ncruces/go-sqlite3/internal/util/module.go b/vendor/github.com/ncruces/go-sqlite3/internal/util/module.go index 0db93bbb6..20b17b209 100644 --- a/vendor/github.com/ncruces/go-sqlite3/internal/util/module.go +++ b/vendor/github.com/ncruces/go-sqlite3/internal/util/module.go @@ -8,14 +8,14 @@ import ( type moduleKey struct{} type moduleState struct { - handleState mmapState + handleState } -func NewContext(ctx context.Context, mappableMemory bool) context.Context { +func NewContext(ctx context.Context) context.Context { state := new(moduleState) - ctx = context.WithValue(ctx, moduleKey{}, state) + ctx = withMmappedAllocator(ctx) ctx = experimental.WithCloseNotifier(ctx, state) - ctx = state.mmapState.init(ctx, mappableMemory) + ctx = context.WithValue(ctx, moduleKey{}, state) return ctx } diff --git a/vendor/github.com/ncruces/go-sqlite3/sqlite.go b/vendor/github.com/ncruces/go-sqlite3/sqlite.go index a1fdd2c2b..a446ec0d1 100644 --- a/vendor/github.com/ncruces/go-sqlite3/sqlite.go +++ b/vendor/github.com/ncruces/go-sqlite3/sqlite.go @@ -85,7 +85,7 @@ func instantiateSQLite() (sqlt *sqlite, err error) { } sqlt = new(sqlite) - sqlt.ctx = util.NewContext(context.Background(), vfs.SupportsSharedMemory) + sqlt.ctx = util.NewContext(context.Background()) sqlt.mod, err = instance.runtime.InstantiateModule(sqlt.ctx, instance.compiled, wazero.NewModuleConfig().WithName("")) diff --git a/vendor/github.com/ncruces/go-sqlite3/vfs/README.md b/vendor/github.com/ncruces/go-sqlite3/vfs/README.md index a8fe2d46e..212ad6d33 100644 --- a/vendor/github.com/ncruces/go-sqlite3/vfs/README.md +++ b/vendor/github.com/ncruces/go-sqlite3/vfs/README.md @@ -15,16 +15,16 @@ The main differences are [file locking](#file-locking) and [WAL mode](write-ahea ### File Locking POSIX advisory locks, which SQLite uses on Unix, are -[broken by design](https://sqlite.org/src/artifact/2e8b12?ln=1073-1161). +[broken by design](https://github.com/sqlite/sqlite/blob/b74eb0/src/os_unix.c#L1073-L1161). -On Linux, macOS and illumos, this module uses +On Linux and macOS, this module uses [OFD locks](https://www.gnu.org/software/libc/manual/html_node/Open-File-Description-Locks.html) to synchronize access to database files. OFD locks are fully compatible with POSIX advisory locks. On BSD Unixes, this module uses [BSD locks](https://man.freebsd.org/cgi/man.cgi?query=flock&sektion=2). -On BSD Unixes, BSD locks are fully compatible with POSIX advisory locks. +On BSD, these locks are fully compatible with POSIX advisory locks. However, concurrency is reduced with BSD locks (`BEGIN IMMEDIATE` behaves the same as `BEGIN EXCLUSIVE`). @@ -34,8 +34,7 @@ like SQLite. On all other platforms, file locking is not supported, and you must use [`nolock=1`](https://sqlite.org/uri.html#urinolock) (or [`immutable=1`](https://sqlite.org/uri.html#uriimmutable)) -to open database files. - +to open database files.\ To use the [`database/sql`](https://pkg.go.dev/database/sql) driver with `nolock=1` you must disable connection pooling by calling [`db.SetMaxOpenConns(1)`](https://pkg.go.dev/database/sql#DB.SetMaxOpenConns). @@ -45,7 +44,7 @@ to check if your platform supports file locking. ### Write-Ahead Logging -On 64-bit Linux, macOS and illumos, this module uses `mmap` to implement +On 64-bit Linux and macOS, this module uses `mmap` to implement [shared-memory for the WAL-index](https://sqlite.org/wal.html#implementation_of_shared_memory_for_the_wal_index), like SQLite. @@ -53,23 +52,23 @@ To allow `mmap` to work, each connection needs to reserve up to 4GB of address s To limit the amount of address space each connection needs, use [`WithMemoryLimitPages`](../tests/testcfg/testcfg.go). -On all other platforms, [WAL](https://sqlite.org/wal.html) support is +On Windows and BSD, [WAL](https://sqlite.org/wal.html) support is [limited](https://sqlite.org/wal.html#noshm). - -To work around this limitation, SQLite is [patched](sqlite3/locking_mode.patch) -to automatically use `EXCLUSIVE` locking mode for WAL databases on such platforms. - -To use the [`database/sql`](https://pkg.go.dev/database/sql) driver -with `EXCLUSIVE` locking mode you should disable connection pooling by calling +`EXCLUSIVE` locking mode can be set to create, read, and write WAL databases.\ +To use `EXCLUSIVE` locking mode with the +[`database/sql`](https://pkg.go.dev/database/sql) driver +you must disable connection pooling by calling [`db.SetMaxOpenConns(1)`](https://pkg.go.dev/database/sql#DB.SetMaxOpenConns). +On all other platforms, where file locking is not supported, WAL mode does not work. + You can use [`vfs.SupportsSharedMemory`](https://pkg.go.dev/github.com/ncruces/go-sqlite3/vfs#SupportsSharedMemory) to check if your platform supports shared memory. ### Batch-Atomic Write On 64-bit Linux, this module supports [batch-atomic writes](https://sqlite.org/cgi/src/technote/714) -on the F2FS filesystem. +with the F2FS filesystem. ### Build tags @@ -77,4 +76,4 @@ The VFS can be customized with a few build tags: - `sqlite3_flock` forces the use of BSD locks; it can be used on macOS to test the BSD locking implementation. - `sqlite3_nosys` prevents importing [`x/sys`](https://pkg.go.dev/golang.org/x/sys); disables locking _and_ shared memory on all platforms. -- `sqlite3_noshm` disables shared memory on all platforms. \ No newline at end of file +- `sqlite3_noshm` disables shared memory on all platforms. diff --git a/vendor/github.com/ncruces/go-sqlite3/vfs/api.go b/vendor/github.com/ncruces/go-sqlite3/vfs/api.go index f8e719546..19c22ae8f 100644 --- a/vendor/github.com/ncruces/go-sqlite3/vfs/api.go +++ b/vendor/github.com/ncruces/go-sqlite3/vfs/api.go @@ -3,7 +3,7 @@ package vfs import ( "context" - "net/url" + "io" "github.com/tetratelabs/wazero/api" ) @@ -20,22 +20,13 @@ type VFS interface { FullPathname(name string) (string, error) } -// VFSParams extends VFS with the ability to handle URI parameters -// through the OpenParams method. +// VFSFilename extends VFS with the ability to use Filename +// objects for opening files. // -// https://sqlite.org/c3ref/uri_boolean.html -type VFSParams interface { +// https://sqlite.org/c3ref/filename.html +type VFSFilename interface { VFS - OpenParams(name string, flags OpenFlag, params url.Values) (File, OpenFlag, error) -} - -// VFSJournal extends VFS with the ability to open journals -// that need a reference to their corresponding database files. -// -// https://sqlite.org/c3ref/database_file_object.html -type VFSJournal interface { - VFS - OpenJournal(name string, flags OpenFlag, db File) (File, OpenFlag, error) + OpenFilename(name *Filename, flags OpenFlag) (File, OpenFlag, error) } // A File represents an open file in the OS interface layer. @@ -67,6 +58,15 @@ type FileLockState interface { LockState() LockLevel } +// FileChunkSize extends File to implement the +// SQLITE_FCNTL_CHUNK_SIZE file control opcode. +// +// https://sqlite.org/c3ref/c_fcntl_begin_atomic_write.html#sqlitefcntlchunksize +type FileChunkSize interface { + File + ChunkSize(size int) +} + // FileSizeHint extends File to implement the // SQLITE_FCNTL_SIZE_HINT file control opcode. // @@ -135,17 +135,41 @@ type FileBatchAtomicWrite interface { RollbackAtomicWrite() error } -// FileSharedMemory extends File to possibly implement shared memory. +// FilePragma extends File to implement the +// SQLITE_FCNTL_PRAGMA file control opcode. +// +// https://sqlite.org/c3ref/c_fcntl_begin_atomic_write.html#sqlitefcntlpragma +type FilePragma interface { + File + Pragma(name, value string) (string, error) +} + +// FileCheckpoint extends File to implement the +// SQLITE_FCNTL_CKPT_START and SQLITE_FCNTL_CKPT_DONE +// file control opcodes. +// +// https://sqlite.org/c3ref/c_fcntl_begin_atomic_write.html#sqlitefcntlckptstart +type FileCheckpoint interface { + File + CheckpointDone() error + CheckpointStart() error +} + +// FileSharedMemory extends File to possibly implement +// shared-memory for the WAL-index. +// The same shared-memory instance must be returned +// for the entire life of the file. // It's OK for SharedMemory to return nil. type FileSharedMemory interface { File SharedMemory() SharedMemory } -// SharedMemory is a shared memory implementation. -// This cannot be externally implemented. +// SharedMemory is a shared-memory WAL-index implementation. +// Use [NewSharedMemory] to create a shared-memory. type SharedMemory interface { shmMap(context.Context, api.Module, int32, int32, bool) (uint32, error) shmLock(int32, int32, _ShmFlag) error shmUnmap(bool) + io.Closer } diff --git a/vendor/github.com/ncruces/go-sqlite3/vfs/const.go b/vendor/github.com/ncruces/go-sqlite3/vfs/const.go index 6405d6188..7f409f35f 100644 --- a/vendor/github.com/ncruces/go-sqlite3/vfs/const.go +++ b/vendor/github.com/ncruces/go-sqlite3/vfs/const.go @@ -4,8 +4,11 @@ import "github.com/ncruces/go-sqlite3/internal/util" const ( _MAX_NAME = 1e6 // Self-imposed limit for most NUL terminated strings. + _MAX_SQL_LENGTH = 1e9 _MAX_PATHNAME = 1024 _DEFAULT_SECTOR_SIZE = 4096 + + ptrlen = 4 ) // https://sqlite.org/rescode.html @@ -17,6 +20,7 @@ func (e _ErrorCode) Error() string { const ( _OK _ErrorCode = util.OK + _ERROR _ErrorCode = util.ERROR _PERM _ErrorCode = util.PERM _BUSY _ErrorCode = util.BUSY _READONLY _ErrorCode = util.READONLY diff --git a/vendor/github.com/ncruces/go-sqlite3/vfs/file.go b/vendor/github.com/ncruces/go-sqlite3/vfs/file.go index 4f2aa39a5..ca8cf84f3 100644 --- a/vendor/github.com/ncruces/go-sqlite3/vfs/file.go +++ b/vendor/github.com/ncruces/go-sqlite3/vfs/file.go @@ -4,7 +4,6 @@ import ( "errors" "io" "io/fs" - "net/url" "os" "path/filepath" "runtime" @@ -70,10 +69,10 @@ func (vfsOS) Access(name string, flags AccessFlag) (bool, error) { } func (vfsOS) Open(name string, flags OpenFlag) (File, OpenFlag, error) { - return vfsOS{}.OpenParams(name, flags, nil) + return nil, 0, _CANTOPEN } -func (vfsOS) OpenParams(name string, flags OpenFlag, params url.Values) (File, OpenFlag, error) { +func (vfsOS) OpenFilename(name *Filename, flags OpenFlag) (File, OpenFlag, error) { var oflags int if flags&OPEN_EXCLUSIVE != 0 { oflags |= os.O_EXCL @@ -90,10 +89,10 @@ func (vfsOS) OpenParams(name string, flags OpenFlag, params url.Values) (File, O var err error var f *os.File - if name == "" { + if name == nil { f, err = os.CreateTemp("", "*.db") } else { - f, err = osutil.OpenFile(name, oflags, 0666) + f, err = osutil.OpenFile(name.String(), oflags, 0666) } if err != nil { if errors.Is(err, syscall.EISDIR) { @@ -102,7 +101,7 @@ func (vfsOS) OpenParams(name string, flags OpenFlag, params url.Values) (File, O return nil, flags, err } - if modeof := params.Get("modeof"); modeof != "" { + if modeof := name.URIParameter("modeof"); modeof != "" { if err = osSetMode(f, modeof); err != nil { f.Close() return nil, flags, _IOERR_FSTAT @@ -119,13 +118,14 @@ func (vfsOS) OpenParams(name string, flags OpenFlag, params url.Values) (File, O syncDir: runtime.GOOS != "windows" && flags&(OPEN_CREATE) != 0 && flags&(OPEN_MAIN_JOURNAL|OPEN_SUPER_JOURNAL|OPEN_WAL) != 0, + shm: NewSharedMemory(name.String()+"-shm", flags), } return &file, flags, nil } type vfsFile struct { *os.File - shm vfsShm + shm SharedMemory lock LockLevel readOnly bool keepWAL bool @@ -143,7 +143,9 @@ var ( ) func (f *vfsFile) Close() error { - f.shm.Close() + if f.shm != nil { + f.shm.Close() + } return f.File.Close() } @@ -174,7 +176,7 @@ func (f *vfsFile) Size() (int64, error) { return f.Seek(0, io.SeekEnd) } -func (*vfsFile) SectorSize() int { +func (f *vfsFile) SectorSize() int { return _DEFAULT_SECTOR_SIZE } diff --git a/vendor/github.com/ncruces/go-sqlite3/vfs/filename.go b/vendor/github.com/ncruces/go-sqlite3/vfs/filename.go new file mode 100644 index 000000000..e23575bbb --- /dev/null +++ b/vendor/github.com/ncruces/go-sqlite3/vfs/filename.go @@ -0,0 +1,174 @@ +package vfs + +import ( + "context" + "net/url" + + "github.com/ncruces/go-sqlite3/internal/util" + "github.com/tetratelabs/wazero/api" +) + +// Filename is used by SQLite to pass filenames +// to the Open method of a VFS. +// +// https://sqlite.org/c3ref/filename.html +type Filename struct { + ctx context.Context + mod api.Module + zPath uint32 + flags OpenFlag + stack [2]uint64 +} + +// OpenFilename is an internal API users should not call directly. +func OpenFilename(ctx context.Context, mod api.Module, id uint32, flags OpenFlag) *Filename { + if id == 0 { + return nil + } + return &Filename{ + ctx: ctx, + mod: mod, + zPath: id, + flags: flags, + } +} + +// String returns this filename as a string. +func (n *Filename) String() string { + if n == nil || n.zPath == 0 { + return "" + } + return util.ReadString(n.mod, n.zPath, _MAX_PATHNAME) +} + +// Database returns the name of the corresponding database file. +// +// https://sqlite.org/c3ref/filename_database.html +func (n *Filename) Database() string { + return n.path("sqlite3_filename_database") +} + +// Journal returns the name of the corresponding rollback journal file. +// +// https://sqlite.org/c3ref/filename_database.html +func (n *Filename) Journal() string { + return n.path("sqlite3_filename_journal") +} + +// Journal returns the name of the corresponding WAL file. +// +// https://sqlite.org/c3ref/filename_database.html +func (n *Filename) WAL() string { + return n.path("sqlite3_filename_wal") +} + +func (n *Filename) path(method string) string { + if n == nil || n.zPath == 0 { + return "" + } + n.stack[0] = uint64(n.zPath) + fn := n.mod.ExportedFunction(method) + if err := fn.CallWithStack(n.ctx, n.stack[:]); err != nil { + panic(err) + } + return util.ReadString(n.mod, uint32(n.stack[0]), _MAX_PATHNAME) +} + +// DatabaseFile returns the main database [File] corresponding to a journal. +// +// https://sqlite.org/c3ref/database_file_object.html +func (n *Filename) DatabaseFile() File { + if n == nil || n.zPath == 0 { + return nil + } + if n.flags&(OPEN_MAIN_DB|OPEN_MAIN_JOURNAL|OPEN_WAL) == 0 { + return nil + } + + n.stack[0] = uint64(n.zPath) + fn := n.mod.ExportedFunction("sqlite3_database_file_object") + if err := fn.CallWithStack(n.ctx, n.stack[:]); err != nil { + panic(err) + } + file, _ := vfsFileGet(n.ctx, n.mod, uint32(n.stack[0])).(File) + return file +} + +// URIParameter returns the value of a URI parameter. +// +// https://sqlite.org/c3ref/uri_boolean.html +func (n *Filename) URIParameter(key string) string { + if n == nil || n.zPath == 0 { + return "" + } + + uriKey := n.mod.ExportedFunction("sqlite3_uri_key") + n.stack[0] = uint64(n.zPath) + n.stack[1] = uint64(0) + if err := uriKey.CallWithStack(n.ctx, n.stack[:]); err != nil { + panic(err) + } + + ptr := uint32(n.stack[0]) + if ptr == 0 { + return "" + } + + // Parse the format from: + // https://github.com/sqlite/sqlite/blob/b74eb0/src/pager.c#L4797-L4840 + // This avoids having to alloc/free the key just to find a value. + for { + k := util.ReadString(n.mod, ptr, _MAX_NAME) + if k == "" { + return "" + } + ptr += uint32(len(k)) + 1 + + v := util.ReadString(n.mod, ptr, _MAX_NAME) + if k == key { + return v + } + ptr += uint32(len(v)) + 1 + } +} + +// URIParameters obtains values for URI parameters. +// +// https://sqlite.org/c3ref/uri_boolean.html +func (n *Filename) URIParameters() url.Values { + if n == nil || n.zPath == 0 { + return nil + } + + uriKey := n.mod.ExportedFunction("sqlite3_uri_key") + n.stack[0] = uint64(n.zPath) + n.stack[1] = uint64(0) + if err := uriKey.CallWithStack(n.ctx, n.stack[:]); err != nil { + panic(err) + } + + ptr := uint32(n.stack[0]) + if ptr == 0 { + return nil + } + + var params url.Values + + // Parse the format from: + // https://github.com/sqlite/sqlite/blob/b74eb0/src/pager.c#L4797-L4840 + // This is the only way to support multiple valued keys. + for { + k := util.ReadString(n.mod, ptr, _MAX_NAME) + if k == "" { + return params + } + ptr += uint32(len(k)) + 1 + + v := util.ReadString(n.mod, ptr, _MAX_NAME) + if params == nil { + params = url.Values{} + } + params.Add(k, v) + ptr += uint32(len(v)) + 1 + } +} diff --git a/vendor/github.com/ncruces/go-sqlite3/vfs/memdb/api.go b/vendor/github.com/ncruces/go-sqlite3/vfs/memdb/api.go index 74dac1d62..c32cf1af0 100644 --- a/vendor/github.com/ncruces/go-sqlite3/vfs/memdb/api.go +++ b/vendor/github.com/ncruces/go-sqlite3/vfs/memdb/api.go @@ -4,7 +4,7 @@ // among multiple database connections in the same process, // as long as the database name begins with "/". // -// Importing package memdb registers the VFS. +// Importing package memdb registers the VFS: // // import _ "github.com/ncruces/go-sqlite3/vfs/memdb" package memdb diff --git a/vendor/github.com/ncruces/go-sqlite3/vfs/memdb/memdb.go b/vendor/github.com/ncruces/go-sqlite3/vfs/memdb/memdb.go index 57d887b4c..09ffa4e98 100644 --- a/vendor/github.com/ncruces/go-sqlite3/vfs/memdb/memdb.go +++ b/vendor/github.com/ncruces/go-sqlite3/vfs/memdb/memdb.go @@ -23,12 +23,11 @@ func (memVFS) Open(name string, flags vfs.OpenFlag) (vfs.File, vfs.OpenFlag, err // This is not a problem for most SQLite file types: // - databases, which only do page aligned reads/writes; // - temp journals, as used by the sorter, which does the same: - // https://sqlite.org/src/artifact/237840?ln=409-412 + // https://github.com/sqlite/sqlite/blob/b74eb0/src/vdbesort.c#L409-L412 // // We refuse to open all other file types, // but returning OPEN_MEMORY means SQLite won't ask us to. const types = vfs.OPEN_MAIN_DB | - vfs.OPEN_TRANSIENT_DB | vfs.OPEN_TEMP_DB | vfs.OPEN_TEMP_JOURNAL if flags&types == 0 { @@ -173,7 +172,7 @@ func (m *memFile) truncate(size int64) error { return nil } -func (*memFile) Sync(flag vfs.SyncFlag) error { +func (m *memFile) Sync(flag vfs.SyncFlag) error { return nil } @@ -263,11 +262,11 @@ func (m *memFile) CheckReservedLock() (bool, error) { return m.reserved != nil, nil } -func (*memFile) SectorSize() int { +func (m *memFile) SectorSize() int { return sectorSize } -func (*memFile) DeviceCharacteristics() vfs.DeviceCharacteristic { +func (m *memFile) DeviceCharacteristics() vfs.DeviceCharacteristic { return vfs.IOCAP_ATOMIC | vfs.IOCAP_SEQUENTIAL | vfs.IOCAP_SAFE_APPEND | diff --git a/vendor/github.com/ncruces/go-sqlite3/vfs/os_bsd.go b/vendor/github.com/ncruces/go-sqlite3/vfs/os_bsd.go index f2b55277f..48ac5c9c9 100644 --- a/vendor/github.com/ncruces/go-sqlite3/vfs/os_bsd.go +++ b/vendor/github.com/ncruces/go-sqlite3/vfs/os_bsd.go @@ -1,4 +1,4 @@ -//go:build (freebsd || openbsd || netbsd || dragonfly || sqlite3_flock) && !sqlite3_nosys +//go:build (freebsd || openbsd || netbsd || dragonfly || illumos || sqlite3_flock) && !sqlite3_nosys package vfs diff --git a/vendor/github.com/ncruces/go-sqlite3/vfs/os_linux.go b/vendor/github.com/ncruces/go-sqlite3/vfs/os_linux.go index a0484eb6c..8a43f4392 100644 --- a/vendor/github.com/ncruces/go-sqlite3/vfs/os_linux.go +++ b/vendor/github.com/ncruces/go-sqlite3/vfs/os_linux.go @@ -3,7 +3,9 @@ package vfs import ( + "math/rand" "os" + "time" "golang.org/x/sys/unix" ) @@ -19,3 +21,51 @@ func osAllocate(file *os.File, size int64) error { } return unix.Fallocate(int(file.Fd()), 0, 0, size) } + +func osUnlock(file *os.File, start, len int64) _ErrorCode { + err := unix.FcntlFlock(file.Fd(), unix.F_OFD_SETLK, &unix.Flock_t{ + Type: unix.F_UNLCK, + Start: start, + Len: len, + }) + if err != nil { + return _IOERR_UNLOCK + } + return _OK +} + +func osLock(file *os.File, typ int16, start, len int64, timeout time.Duration, def _ErrorCode) _ErrorCode { + lock := unix.Flock_t{ + Type: typ, + Start: start, + Len: len, + } + var err error + switch { + case timeout == 0: + err = unix.FcntlFlock(file.Fd(), unix.F_OFD_SETLK, &lock) + case timeout < 0: + err = unix.FcntlFlock(file.Fd(), unix.F_OFD_SETLKW, &lock) + default: + before := time.Now() + for { + err = unix.FcntlFlock(file.Fd(), unix.F_OFD_SETLK, &lock) + if errno, _ := err.(unix.Errno); errno != unix.EAGAIN { + break + } + if timeout < time.Since(before) { + break + } + osSleep(time.Duration(rand.Int63n(int64(time.Millisecond)))) + } + } + return osLockErrorCode(err, def) +} + +func osReadLock(file *os.File, start, len int64, timeout time.Duration) _ErrorCode { + return osLock(file, unix.F_RDLCK, start, len, timeout, _IOERR_RDLOCK) +} + +func osWriteLock(file *os.File, start, len int64, timeout time.Duration) _ErrorCode { + return osLock(file, unix.F_WRLCK, start, len, timeout, _IOERR_LOCK) +} diff --git a/vendor/github.com/ncruces/go-sqlite3/vfs/os_ofd.go b/vendor/github.com/ncruces/go-sqlite3/vfs/os_ofd.go deleted file mode 100644 index 0fe64d805..000000000 --- a/vendor/github.com/ncruces/go-sqlite3/vfs/os_ofd.go +++ /dev/null @@ -1,59 +0,0 @@ -//go:build (linux || illumos) && !sqlite3_nosys - -package vfs - -import ( - "math/rand" - "os" - "time" - - "golang.org/x/sys/unix" -) - -func osUnlock(file *os.File, start, len int64) _ErrorCode { - err := unix.FcntlFlock(file.Fd(), unix.F_OFD_SETLK, &unix.Flock_t{ - Type: unix.F_UNLCK, - Start: start, - Len: len, - }) - if err != nil { - return _IOERR_UNLOCK - } - return _OK -} - -func osLock(file *os.File, typ int16, start, len int64, timeout time.Duration, def _ErrorCode) _ErrorCode { - lock := unix.Flock_t{ - Type: typ, - Start: start, - Len: len, - } - var err error - switch { - case timeout == 0: - err = unix.FcntlFlock(file.Fd(), unix.F_OFD_SETLK, &lock) - case timeout < 0: - err = unix.FcntlFlock(file.Fd(), unix.F_OFD_SETLKW, &lock) - default: - before := time.Now() - for { - err = unix.FcntlFlock(file.Fd(), unix.F_OFD_SETLK, &lock) - if errno, _ := err.(unix.Errno); errno != unix.EAGAIN { - break - } - if timeout < time.Since(before) { - break - } - osSleep(time.Duration(rand.Int63n(int64(time.Millisecond)))) - } - } - return osLockErrorCode(err, def) -} - -func osReadLock(file *os.File, start, len int64, timeout time.Duration) _ErrorCode { - return osLock(file, unix.F_RDLCK, start, len, timeout, _IOERR_RDLOCK) -} - -func osWriteLock(file *os.File, start, len int64, timeout time.Duration) _ErrorCode { - return osLock(file, unix.F_WRLCK, start, len, timeout, _IOERR_LOCK) -} diff --git a/vendor/github.com/ncruces/go-sqlite3/vfs/shm.go b/vendor/github.com/ncruces/go-sqlite3/vfs/shm.go index 0b3c65511..2b76dd5dc 100644 --- a/vendor/github.com/ncruces/go-sqlite3/vfs/shm.go +++ b/vendor/github.com/ncruces/go-sqlite3/vfs/shm.go @@ -1,4 +1,4 @@ -//go:build (darwin || linux || illumos) && (amd64 || arm64 || riscv64) && !sqlite3_flock && !sqlite3_noshm && !sqlite3_nosys +//go:build (darwin || linux) && (amd64 || arm64 || riscv64) && !(sqlite3_flock || sqlite3_noshm || sqlite3_nosys) package vfs @@ -12,100 +12,116 @@ import ( "golang.org/x/sys/unix" ) -// SupportsSharedMemory is true on platforms that support shared memory. -// To enable shared memory support on those platforms, -// you need to set the appropriate [wazero.RuntimeConfig]; -// otherwise, [EXCLUSIVE locking mode] is activated automatically -// to use [WAL without shared-memory]. +// SupportsSharedMemory is false on platforms that do not support shared memory. +// To use [WAL without shared-memory], you need to set [EXCLUSIVE locking mode]. // // [WAL without shared-memory]: https://sqlite.org/wal.html#noshm // [EXCLUSIVE locking mode]: https://sqlite.org/pragma.html#pragma_locking_mode const SupportsSharedMemory = true -type vfsShm struct { - *os.File - regions []*util.MappedRegion -} - const ( _SHM_NLOCK = 8 _SHM_BASE = 120 _SHM_DMS = _SHM_BASE + _SHM_NLOCK ) -func (f *vfsFile) SharedMemory() SharedMemory { return f } +func (f *vfsFile) SharedMemory() SharedMemory { return f.shm } -func (f *vfsFile) shmMap(ctx context.Context, mod api.Module, id, size int32, extend bool) (uint32, error) { +// NewSharedMemory returns a shared-memory WAL-index +// backed by a file with the given path. +// It will return nil if shared-memory is not supported, +// or not appropriate for the given flags. +// Only [OPEN_MAIN_DB] databases may need a WAL-index. +// You must ensure all concurrent accesses to a database +// use shared-memory instances created with the same path. +func NewSharedMemory(path string, flags OpenFlag) SharedMemory { + if flags&OPEN_MAIN_DB == 0 || flags&(OPEN_DELETEONCLOSE|OPEN_MEMORY) != 0 { + return nil + } + return &vfsShm{ + path: path, + readOnly: flags&OPEN_READONLY != 0, + } +} + +type vfsShm struct { + *os.File + path string + regions []*util.MappedRegion + readOnly bool +} + +func (s *vfsShm) shmMap(ctx context.Context, mod api.Module, id, size int32, extend bool) (uint32, error) { // Ensure size is a multiple of the OS page size. if int(size)&(unix.Getpagesize()-1) != 0 { return 0, _IOERR_SHMMAP } - if f.shm.File == nil { + if s.File == nil { var flag int - if f.readOnly { + if s.readOnly { flag = unix.O_RDONLY } else { flag = unix.O_RDWR } - s, err := os.OpenFile(f.Name()+"-shm", + f, err := os.OpenFile(s.path, flag|unix.O_CREAT|unix.O_NOFOLLOW, 0666) if err != nil { return 0, _CANTOPEN } - f.shm.File = s + s.File = f } // Dead man's switch. - if lock, rc := osGetLock(f.shm.File, _SHM_DMS, 1); rc != _OK { + if lock, rc := osGetLock(s.File, _SHM_DMS, 1); rc != _OK { return 0, _IOERR_LOCK } else if lock == unix.F_WRLCK { return 0, _BUSY } else if lock == unix.F_UNLCK { - if f.readOnly { + if s.readOnly { return 0, _READONLY_CANTINIT } - if rc := osWriteLock(f.shm.File, _SHM_DMS, 1, 0); rc != _OK { + if rc := osWriteLock(s.File, _SHM_DMS, 1, 0); rc != _OK { return 0, rc } - if err := f.shm.Truncate(0); err != nil { + if err := s.Truncate(0); err != nil { return 0, _IOERR_SHMOPEN } } - if rc := osReadLock(f.shm.File, _SHM_DMS, 1, 0); rc != _OK { + if rc := osReadLock(s.File, _SHM_DMS, 1, 0); rc != _OK { return 0, rc } // Check if file is big enough. - s, err := f.shm.Seek(0, io.SeekEnd) + o, err := s.Seek(0, io.SeekEnd) if err != nil { return 0, _IOERR_SHMSIZE } - if n := (int64(id) + 1) * int64(size); n > s { + if n := (int64(id) + 1) * int64(size); n > o { if !extend { return 0, nil } - err := osAllocate(f.shm.File, n) + err := osAllocate(s.File, n) if err != nil { return 0, _IOERR_SHMSIZE } } var prot int - if f.readOnly { + if s.readOnly { prot = unix.PROT_READ } else { prot = unix.PROT_READ | unix.PROT_WRITE } - r, err := util.MapRegion(ctx, mod, f.shm.File, int64(id)*int64(size), size, prot) + r, err := util.MapRegion(ctx, mod, s.File, int64(id)*int64(size), size, prot) if err != nil { return 0, err } - f.shm.regions = append(f.shm.regions, r) + s.regions = append(s.regions, r) return r.Ptr, nil } -func (f *vfsFile) shmLock(offset, n int32, flags _ShmFlag) error { +func (s *vfsShm) shmLock(offset, n int32, flags _ShmFlag) error { // Argument check. if n <= 0 || offset < 0 || offset+n > _SHM_NLOCK { panic(util.AssertErr()) @@ -126,28 +142,32 @@ func (f *vfsFile) shmLock(offset, n int32, flags _ShmFlag) error { switch { case flags&_SHM_UNLOCK != 0: - return osUnlock(f.shm.File, _SHM_BASE+int64(offset), int64(n)) + return osUnlock(s.File, _SHM_BASE+int64(offset), int64(n)) case flags&_SHM_SHARED != 0: - return osReadLock(f.shm.File, _SHM_BASE+int64(offset), int64(n), 0) + return osReadLock(s.File, _SHM_BASE+int64(offset), int64(n), 0) case flags&_SHM_EXCLUSIVE != 0: - return osWriteLock(f.shm.File, _SHM_BASE+int64(offset), int64(n), 0) + return osWriteLock(s.File, _SHM_BASE+int64(offset), int64(n), 0) default: panic(util.AssertErr()) } } -func (f *vfsFile) shmUnmap(delete bool) { +func (s *vfsShm) shmUnmap(delete bool) { + if s.File == nil { + return + } + // Unmap regions. - for _, r := range f.shm.regions { + for _, r := range s.regions { r.Unmap() } - clear(f.shm.regions) - f.shm.regions = f.shm.regions[:0] + clear(s.regions) + s.regions = s.regions[:0] // Close the file. - if delete && f.shm.File != nil { - os.Remove(f.shm.Name()) + defer s.Close() + if delete { + os.Remove(s.Name()) } - f.shm.Close() - f.shm.File = nil + s.File = nil } diff --git a/vendor/github.com/ncruces/go-sqlite3/vfs/shm_other.go b/vendor/github.com/ncruces/go-sqlite3/vfs/shm_other.go index de11ce426..21191979e 100644 --- a/vendor/github.com/ncruces/go-sqlite3/vfs/shm_other.go +++ b/vendor/github.com/ncruces/go-sqlite3/vfs/shm_other.go @@ -1,17 +1,21 @@ -//go:build !(darwin || linux || illumos) || !(amd64 || arm64 || riscv64) || sqlite3_flock || sqlite3_noshm || sqlite3_nosys +//go:build !(darwin || linux) || !(amd64 || arm64 || riscv64) || sqlite3_flock || sqlite3_noshm || sqlite3_nosys package vfs -// SupportsSharedMemory is true on platforms that support shared memory. -// To enable shared memory support on those platforms, -// you need to set the appropriate [wazero.RuntimeConfig]; -// otherwise, [EXCLUSIVE locking mode] is activated automatically -// to use [WAL without shared-memory]. +// SupportsSharedMemory is false on platforms that do not support shared memory. +// To use [WAL without shared-memory], you need to set [EXCLUSIVE locking mode]. // // [WAL without shared-memory]: https://sqlite.org/wal.html#noshm // [EXCLUSIVE locking mode]: https://sqlite.org/pragma.html#pragma_locking_mode const SupportsSharedMemory = false -type vfsShm struct{} - -func (vfsShm) Close() error { return nil } +// NewSharedMemory returns a shared-memory WAL-index +// backed by a file with the given path. +// It will return nil if shared-memory is not supported, +// or not appropriate for the given flags. +// Only [OPEN_MAIN_DB] databases may need a WAL-index. +// You must ensure all concurrent accesses to a database +// use shared-memory instances created with the same path. +func NewSharedMemory(path string, flags OpenFlag) SharedMemory { + return nil +} diff --git a/vendor/github.com/ncruces/go-sqlite3/vfs/vfs.go b/vendor/github.com/ncruces/go-sqlite3/vfs/vfs.go index 33a7ee117..1887e9f22 100644 --- a/vendor/github.com/ncruces/go-sqlite3/vfs/vfs.go +++ b/vendor/github.com/ncruces/go-sqlite3/vfs/vfs.go @@ -4,7 +4,6 @@ import ( "context" "crypto/rand" "io" - "net/url" "reflect" "sync" "time" @@ -141,42 +140,29 @@ func vfsOpen(ctx context.Context, mod api.Module, pVfs, zPath, pFile uint32, fla var file File var err error - var parsed bool - var params url.Values - if jfs, ok := vfs.(VFSJournal); ok && flags&(OPEN_WAL|OPEN_MAIN_JOURNAL) != 0 { - db := vfsDatabaseFileObject(ctx, mod, zPath) - file, flags, err = jfs.OpenJournal(path, flags, db) - } else if pfs, ok := vfs.(VFSParams); ok { - parsed = true - params = vfsURIParameters(ctx, mod, zPath, flags) - file, flags, err = pfs.OpenParams(path, flags, params) + if ffs, ok := vfs.(VFSFilename); ok { + name := OpenFilename(ctx, mod, zPath, flags) + file, flags, err = ffs.OpenFilename(name, flags) } else { file, flags, err = vfs.Open(path, flags) } - if err != nil { return vfsErrorCode(err, _CANTOPEN) } if file, ok := file.(FilePowersafeOverwrite); ok { - if !parsed { - params = vfsURIParameters(ctx, mod, zPath, flags) - } - if b, ok := util.ParseBool(params.Get("psow")); ok { + name := OpenFilename(ctx, mod, zPath, flags) + if b, ok := util.ParseBool(name.URIParameter("psow")); ok { file.SetPowersafeOverwrite(b) } } - + if file, ok := file.(FileSharedMemory); ok && + pOutVFS != 0 && file.SharedMemory() != nil { + util.WriteUint32(mod, pOutVFS, 1) + } if pOutFlags != 0 { util.WriteUint32(mod, pOutFlags, uint32(flags)) } - if pOutVFS != 0 && util.CanMapFiles(ctx) { - if f, ok := file.(FileSharedMemory); ok { - if f.SharedMemory() != nil { - util.WriteUint32(mod, pOutVFS, 1) - } - } - } vfsFileRegister(ctx, mod, pFile, file) return _OK } @@ -190,7 +176,7 @@ func vfsClose(ctx context.Context, mod api.Module, pFile uint32) _ErrorCode { } func vfsRead(ctx context.Context, mod api.Module, pFile, zBuf uint32, iAmt int32, iOfst int64) _ErrorCode { - file := vfsFileGet(ctx, mod, pFile) + file := vfsFileGet(ctx, mod, pFile).(File) buf := util.View(mod, zBuf, uint64(iAmt)) n, err := file.ReadAt(buf, iOfst) @@ -205,7 +191,7 @@ func vfsRead(ctx context.Context, mod api.Module, pFile, zBuf uint32, iAmt int32 } func vfsWrite(ctx context.Context, mod api.Module, pFile, zBuf uint32, iAmt int32, iOfst int64) _ErrorCode { - file := vfsFileGet(ctx, mod, pFile) + file := vfsFileGet(ctx, mod, pFile).(File) buf := util.View(mod, zBuf, uint64(iAmt)) _, err := file.WriteAt(buf, iOfst) @@ -216,38 +202,38 @@ func vfsWrite(ctx context.Context, mod api.Module, pFile, zBuf uint32, iAmt int3 } func vfsTruncate(ctx context.Context, mod api.Module, pFile uint32, nByte int64) _ErrorCode { - file := vfsFileGet(ctx, mod, pFile) + file := vfsFileGet(ctx, mod, pFile).(File) err := file.Truncate(nByte) return vfsErrorCode(err, _IOERR_TRUNCATE) } func vfsSync(ctx context.Context, mod api.Module, pFile uint32, flags SyncFlag) _ErrorCode { - file := vfsFileGet(ctx, mod, pFile) + file := vfsFileGet(ctx, mod, pFile).(File) err := file.Sync(flags) return vfsErrorCode(err, _IOERR_FSYNC) } func vfsFileSize(ctx context.Context, mod api.Module, pFile, pSize uint32) _ErrorCode { - file := vfsFileGet(ctx, mod, pFile) + file := vfsFileGet(ctx, mod, pFile).(File) size, err := file.Size() util.WriteUint64(mod, pSize, uint64(size)) return vfsErrorCode(err, _IOERR_SEEK) } func vfsLock(ctx context.Context, mod api.Module, pFile uint32, eLock LockLevel) _ErrorCode { - file := vfsFileGet(ctx, mod, pFile) + file := vfsFileGet(ctx, mod, pFile).(File) err := file.Lock(eLock) return vfsErrorCode(err, _IOERR_LOCK) } func vfsUnlock(ctx context.Context, mod api.Module, pFile uint32, eLock LockLevel) _ErrorCode { - file := vfsFileGet(ctx, mod, pFile) + file := vfsFileGet(ctx, mod, pFile).(File) err := file.Unlock(eLock) return vfsErrorCode(err, _IOERR_UNLOCK) } func vfsCheckReservedLock(ctx context.Context, mod api.Module, pFile, pResOut uint32) _ErrorCode { - file := vfsFileGet(ctx, mod, pFile) + file := vfsFileGet(ctx, mod, pFile).(File) locked, err := file.CheckReservedLock() var res uint32 @@ -260,7 +246,7 @@ func vfsCheckReservedLock(ctx context.Context, mod api.Module, pFile, pResOut ui } func vfsFileControl(ctx context.Context, mod api.Module, pFile uint32, op _FcntlOpcode, pArg uint32) _ErrorCode { - file := vfsFileGet(ctx, mod, pFile) + file := vfsFileGet(ctx, mod, pFile).(File) switch op { case _FCNTL_LOCKSTATE: @@ -293,6 +279,13 @@ func vfsFileControl(ctx context.Context, mod api.Module, pFile uint32, op _Fcntl return _OK } + case _FCNTL_CHUNK_SIZE: + if file, ok := file.(FileChunkSize); ok { + size := util.ReadUint32(mod, pArg) + file.ChunkSize(int(size)) + return _OK + } + case _FCNTL_SIZE_HINT: if file, ok := file.(FileSizeHint); ok { size := util.ReadUint64(mod, pArg) @@ -338,25 +331,60 @@ func vfsFileControl(ctx context.Context, mod api.Module, pFile uint32, op _Fcntl err := file.RollbackAtomicWrite() return vfsErrorCode(err, _IOERR_ROLLBACK_ATOMIC) } + + case _FCNTL_CKPT_DONE: + if file, ok := file.(FileCheckpoint); ok { + err := file.CheckpointDone() + return vfsErrorCode(err, _IOERR) + } + case _FCNTL_CKPT_START: + if file, ok := file.(FileCheckpoint); ok { + err := file.CheckpointStart() + return vfsErrorCode(err, _IOERR) + } + + case _FCNTL_PRAGMA: + if file, ok := file.(FilePragma); ok { + ptr := util.ReadUint32(mod, pArg+1*ptrlen) + name := util.ReadString(mod, ptr, _MAX_SQL_LENGTH) + var value string + if ptr := util.ReadUint32(mod, pArg+2*ptrlen); ptr != 0 { + value = util.ReadString(mod, ptr, _MAX_SQL_LENGTH) + } + + out, err := file.Pragma(name, value) + + ret := vfsErrorCode(err, _ERROR) + if ret == _ERROR { + out = err.Error() + } + if out != "" { + fn := mod.ExportedFunction("malloc") + stack := [...]uint64{uint64(len(out) + 1)} + if err := fn.CallWithStack(ctx, stack[:]); err != nil { + panic(err) + } + util.WriteUint32(mod, pArg, uint32(stack[0])) + util.WriteString(mod, uint32(stack[0]), out) + } + return ret + } } // Consider also implementing these opcodes (in use by SQLite): // _FCNTL_BUSYHANDLER - // _FCNTL_CHUNK_SIZE - // _FCNTL_CKPT_DONE - // _FCNTL_CKPT_START - // _FCNTL_PRAGMA + // _FCNTL_LAST_ERRNO // _FCNTL_SYNC return _NOTFOUND } func vfsSectorSize(ctx context.Context, mod api.Module, pFile uint32) uint32 { - file := vfsFileGet(ctx, mod, pFile) + file := vfsFileGet(ctx, mod, pFile).(File) return uint32(file.SectorSize()) } func vfsDeviceCharacteristics(ctx context.Context, mod api.Module, pFile uint32) DeviceCharacteristic { - file := vfsFileGet(ctx, mod, pFile) + file := vfsFileGet(ctx, mod, pFile).(File) return file.DeviceCharacteristics() } @@ -368,8 +396,8 @@ func vfsShmBarrier(ctx context.Context, mod api.Module, pFile uint32) { } func vfsShmMap(ctx context.Context, mod api.Module, pFile uint32, iRegion, szRegion int32, bExtend, pp uint32) _ErrorCode { - file := vfsFileGet(ctx, mod, pFile).(FileSharedMemory).SharedMemory() - p, err := file.shmMap(ctx, mod, iRegion, szRegion, bExtend != 0) + shm := vfsFileGet(ctx, mod, pFile).(FileSharedMemory).SharedMemory() + p, err := shm.shmMap(ctx, mod, iRegion, szRegion, bExtend != 0) if err != nil { return vfsErrorCode(err, _IOERR_SHMMAP) } @@ -378,67 +406,17 @@ func vfsShmMap(ctx context.Context, mod api.Module, pFile uint32, iRegion, szReg } func vfsShmLock(ctx context.Context, mod api.Module, pFile uint32, offset, n int32, flags _ShmFlag) _ErrorCode { - file := vfsFileGet(ctx, mod, pFile).(FileSharedMemory).SharedMemory() - err := file.shmLock(offset, n, flags) + shm := vfsFileGet(ctx, mod, pFile).(FileSharedMemory).SharedMemory() + err := shm.shmLock(offset, n, flags) return vfsErrorCode(err, _IOERR_SHMLOCK) } func vfsShmUnmap(ctx context.Context, mod api.Module, pFile, bDelete uint32) _ErrorCode { - file := vfsFileGet(ctx, mod, pFile).(FileSharedMemory).SharedMemory() - file.shmUnmap(bDelete != 0) + shm := vfsFileGet(ctx, mod, pFile).(FileSharedMemory).SharedMemory() + shm.shmUnmap(bDelete != 0) return _OK } -func vfsURIParameters(ctx context.Context, mod api.Module, zPath uint32, flags OpenFlag) url.Values { - switch { - case flags&(OPEN_URI|OPEN_MAIN_DB) == OPEN_URI|OPEN_MAIN_DB: - // database file with URI - case flags&(OPEN_WAL|OPEN_MAIN_JOURNAL) != 0: - // journal or WAL file - default: - return nil - } - - var stack [2]uint64 - var params url.Values - uriKey := mod.ExportedFunction("sqlite3_uri_key") - uriParam := mod.ExportedFunction("sqlite3_uri_parameter") - - for i := 0; ; i++ { - stack[1] = uint64(i) - stack[0] = uint64(zPath) - if err := uriKey.CallWithStack(ctx, stack[:]); err != nil { - panic(err) - } - if stack[0] == 0 { - return params - } - key := util.ReadString(mod, uint32(stack[0]), _MAX_NAME) - if params.Has(key) { - continue - } - - stack[1] = stack[0] - stack[0] = uint64(zPath) - if err := uriParam.CallWithStack(ctx, stack[:]); err != nil { - panic(err) - } - if params == nil { - params = url.Values{} - } - params.Set(key, util.ReadString(mod, uint32(stack[0]), _MAX_NAME)) - } -} - -func vfsDatabaseFileObject(ctx context.Context, mod api.Module, zPath uint32) File { - stack := [...]uint64{uint64(zPath)} - fn := mod.ExportedFunction("sqlite3_database_file_object") - if err := fn.CallWithStack(ctx, stack[:]); err != nil { - panic(err) - } - return vfsFileGet(ctx, mod, uint32(stack[0])) -} - func vfsGet(mod api.Module, pVfs uint32) VFS { var name string if pVfs != 0 { @@ -457,10 +435,10 @@ func vfsFileRegister(ctx context.Context, mod api.Module, pFile uint32, file Fil util.WriteUint32(mod, pFile+fileHandleOffset, id) } -func vfsFileGet(ctx context.Context, mod api.Module, pFile uint32) File { +func vfsFileGet(ctx context.Context, mod api.Module, pFile uint32) any { const fileHandleOffset = 4 id := util.ReadUint32(mod, pFile+fileHandleOffset) - return util.GetHandle(ctx, id).(File) + return util.GetHandle(ctx, id) } func vfsFileClose(ctx context.Context, mod api.Module, pFile uint32) error { diff --git a/vendor/modules.txt b/vendor/modules.txt index eac56f895..664b4f276 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -512,7 +512,7 @@ github.com/modern-go/concurrent # github.com/modern-go/reflect2 v1.0.2 ## explicit; go 1.12 github.com/modern-go/reflect2 -# github.com/ncruces/go-sqlite3 v0.14.1-0.20240422130245-189fbc98ac32 +# github.com/ncruces/go-sqlite3 v0.15.0 ## explicit; go 1.21 github.com/ncruces/go-sqlite3 github.com/ncruces/go-sqlite3/driver