diff options
Diffstat (limited to 'vendor/github.com/ncruces/go-sqlite3')
78 files changed, 9594 insertions, 0 deletions
diff --git a/vendor/github.com/ncruces/go-sqlite3/.gitignore b/vendor/github.com/ncruces/go-sqlite3/.gitignore new file mode 100644 index 000000000..c8b2376cd --- /dev/null +++ b/vendor/github.com/ncruces/go-sqlite3/.gitignore @@ -0,0 +1,16 @@ +# Binaries for programs and plugins +*.exe +*.exe~ +*.dll +*.so +*.dylib + +# Test binary, built with `go test -c` +*.test + +# Output of the go coverage tool, specifically when used with LiteIDE +*.out + +# Dependency directories (remove the comment below to include it) +# vendor/ +tools
\ No newline at end of file diff --git a/vendor/github.com/ncruces/go-sqlite3/LICENSE b/vendor/github.com/ncruces/go-sqlite3/LICENSE new file mode 100644 index 000000000..9bdc1df48 --- /dev/null +++ b/vendor/github.com/ncruces/go-sqlite3/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2023 Nuno Cruces + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vendor/github.com/ncruces/go-sqlite3/README.md b/vendor/github.com/ncruces/go-sqlite3/README.md new file mode 100644 index 000000000..c31414724 --- /dev/null +++ b/vendor/github.com/ncruces/go-sqlite3/README.md @@ -0,0 +1,113 @@ +# Go bindings to SQLite using Wazero + +[](https://pkg.go.dev/github.com/ncruces/go-sqlite3) +[](https://goreportcard.com/report/github.com/ncruces/go-sqlite3) +[](https://github.com/ncruces/go-sqlite3/wiki/Test-coverage-report) + +Go module `github.com/ncruces/go-sqlite3` is a `cgo`-free [SQLite](https://sqlite.org/) wrapper.\ +It provides a [`database/sql`](https://pkg.go.dev/database/sql) compatible driver, +as well as direct access to most of the [C SQLite API](https://sqlite.org/cintro.html). + +It wraps a [Wasm](https://webassembly.org/) [build](embed/) of SQLite, +and uses [wazero](https://wazero.io/) as the runtime.\ +Go, wazero and [`x/sys`](https://pkg.go.dev/golang.org/x/sys) are the _only_ runtime dependencies [^1]. + +### Packages + +- [`github.com/ncruces/go-sqlite3`](https://pkg.go.dev/github.com/ncruces/go-sqlite3) + wraps the [C SQLite API](https://sqlite.org/cintro.html) + ([example usage](https://pkg.go.dev/github.com/ncruces/go-sqlite3#example-package)). +- [`github.com/ncruces/go-sqlite3/driver`](https://pkg.go.dev/github.com/ncruces/go-sqlite3/driver) + provides a [`database/sql`](https://pkg.go.dev/database/sql) driver + ([example usage](https://pkg.go.dev/github.com/ncruces/go-sqlite3/driver#example-package)). +- [`github.com/ncruces/go-sqlite3/embed`](https://pkg.go.dev/github.com/ncruces/go-sqlite3/embed) + embeds a build of SQLite into your application. +- [`github.com/ncruces/go-sqlite3/vfs`](https://pkg.go.dev/github.com/ncruces/go-sqlite3/vfs) + wraps the [C SQLite VFS API](https://sqlite.org/vfs.html) and provides a pure Go implementation. +- [`github.com/ncruces/go-sqlite3/gormlite`](https://pkg.go.dev/github.com/ncruces/go-sqlite3/gormlite) + provides a [GORM](https://gorm.io) driver. + +### Extensions + +- [`github.com/ncruces/go-sqlite3/ext/array`](https://pkg.go.dev/github.com/ncruces/go-sqlite3/ext/array) + provides the [`array`](https://sqlite.org/carray.html) table-valued function. +- [`github.com/ncruces/go-sqlite3/ext/blobio`](https://pkg.go.dev/github.com/ncruces/go-sqlite3/ext/blobio) + simplifies [incremental BLOB I/O](https://sqlite.org/c3ref/blob_open.html). +- [`github.com/ncruces/go-sqlite3/ext/csv`](https://pkg.go.dev/github.com/ncruces/go-sqlite3/ext/csv) + reads [comma-separated values](https://sqlite.org/csv.html). +- [`github.com/ncruces/go-sqlite3/ext/fileio`](https://pkg.go.dev/github.com/ncruces/go-sqlite3/ext/fileio) + reads, writes and lists files. +- [`github.com/ncruces/go-sqlite3/ext/hash`](https://pkg.go.dev/github.com/ncruces/go-sqlite3/ext/hash) + provides cryptographic hash functions. +- [`github.com/ncruces/go-sqlite3/ext/lines`](https://pkg.go.dev/github.com/ncruces/go-sqlite3/ext/lines) + reads data [line-by-line](https://github.com/asg017/sqlite-lines). +- [`github.com/ncruces/go-sqlite3/ext/pivot`](https://pkg.go.dev/github.com/ncruces/go-sqlite3/ext/pivot) + creates [pivot tables](https://github.com/jakethaw/pivot_vtab). +- [`github.com/ncruces/go-sqlite3/ext/statement`](https://pkg.go.dev/github.com/ncruces/go-sqlite3/ext/statement) + creates [parameterized views](https://github.com/0x09/sqlite-statement-vtab). +- [`github.com/ncruces/go-sqlite3/ext/stats`](https://pkg.go.dev/github.com/ncruces/go-sqlite3/ext/stats) + provides [statistics](https://www.oreilly.com/library/view/sql-in-a/9780596155322/ch04s02.html) functions. +- [`github.com/ncruces/go-sqlite3/ext/unicode`](https://pkg.go.dev/github.com/ncruces/go-sqlite3/ext/unicode) + provides [Unicode aware](https://sqlite.org/src/dir/ext/icu) functions. +- [`github.com/ncruces/go-sqlite3/ext/zorder`](https://pkg.go.dev/github.com/ncruces/go-sqlite3/ext/zorder) + maps multidimensional data to one dimension. +- [`github.com/ncruces/go-sqlite3/vfs/memdb`](https://pkg.go.dev/github.com/ncruces/go-sqlite3/vfs/memdb) + implements an in-memory VFS. +- [`github.com/ncruces/go-sqlite3/vfs/readervfs`](https://pkg.go.dev/github.com/ncruces/go-sqlite3/vfs/readervfs) + implements a VFS for immutable databases. +- [`github.com/ncruces/go-sqlite3/vfs/adiantum`](https://pkg.go.dev/github.com/ncruces/go-sqlite3/vfs/adiantum) + wraps a VFS to offer encryption at rest. + +### Advanced features + +- [incremental BLOB I/O](https://sqlite.org/c3ref/blob_open.html) +- [nested transactions](https://sqlite.org/lang_savepoint.html) +- [custom functions](https://sqlite.org/c3ref/create_function.html) +- [virtual tables](https://sqlite.org/vtab.html) +- [custom VFSes](https://sqlite.org/vfs.html) +- [online backup](https://sqlite.org/backup.html) +- [JSON support](https://sqlite.org/json1.html) +- [math functions](https://sqlite.org/lang_mathfunc.html) +- [full-text search](https://sqlite.org/fts5.html) +- [geospatial search](https://sqlite.org/geopoly.html) +- [encryption at rest](vfs/adiantum/README.md) +- [and moreā¦](embed/README.md) + +### Caveats + +This module replaces the SQLite [OS Interface](https://sqlite.org/vfs.html) +(aka VFS) with a [pure Go](vfs/) implementation, +which has advantages and disadvantages. + +Read more about the Go VFS design [here](vfs/README.md). + +### Testing + +This project aims for [high test coverage](https://github.com/ncruces/go-sqlite3/wiki/Test-coverage-report). +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/s390x), macOS (amd64/arm64), +Windows (amd64), FreeBSD (amd64), illumos (amd64), and Solaris (amd64). + +The Go VFS is tested by running SQLite's +[mptest](https://github.com/sqlite/sqlite/blob/master/mptest/mptest.c). + +### Performance + +Perfomance of the [`database/sql`](https://pkg.go.dev/database/sql) driver is +[competitive](https://github.com/cvilsmeier/go-sqlite-bench) with alternatives. + +The Wasm and VFS layers are also tested by running SQLite's +[speedtest1](https://github.com/sqlite/sqlite/blob/master/test/speedtest1.c). + +### Alternatives + +- [`modernc.org/sqlite`](https://pkg.go.dev/modernc.org/sqlite) +- [`crawshaw.io/sqlite`](https://pkg.go.dev/crawshaw.io/sqlite) +- [`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` is either a test dependency, + or needed by one of the extensions. diff --git a/vendor/github.com/ncruces/go-sqlite3/backup.go b/vendor/github.com/ncruces/go-sqlite3/backup.go new file mode 100644 index 000000000..b16c7511e --- /dev/null +++ b/vendor/github.com/ncruces/go-sqlite3/backup.go @@ -0,0 +1,134 @@ +package sqlite3 + +// Backup is an handle to an ongoing online backup operation. +// +// https://sqlite.org/c3ref/backup.html +type Backup struct { + c *Conn + handle uint32 + otherc uint32 +} + +// Backup backs up srcDB on the src connection to the "main" database in dstURI. +// +// Backup opens the SQLite database file dstURI, +// and blocks until the entire backup is complete. +// Use [Conn.BackupInit] for incremental backup. +// +// https://sqlite.org/backup.html +func (src *Conn) Backup(srcDB, dstURI string) error { + b, err := src.BackupInit(srcDB, dstURI) + if err != nil { + return err + } + defer b.Close() + _, err = b.Step(-1) + return err +} + +// Restore restores dstDB on the dst connection from the "main" database in srcURI. +// +// Restore opens the SQLite database file srcURI, +// and blocks until the entire restore is complete. +// +// https://sqlite.org/backup.html +func (dst *Conn) Restore(dstDB, srcURI string) error { + src, err := dst.openDB(srcURI, OPEN_READONLY|OPEN_URI) + if err != nil { + return err + } + b, err := dst.backupInit(dst.handle, dstDB, src, "main") + if err != nil { + return err + } + defer b.Close() + _, err = b.Step(-1) + return err +} + +// BackupInit initializes a backup operation to copy the content of one database into another. +// +// BackupInit opens the SQLite database file dstURI, +// then initializes a backup that copies the contents of srcDB on the src connection +// to the "main" database in dstURI. +// +// https://sqlite.org/c3ref/backup_finish.html#sqlite3backupinit +func (src *Conn) BackupInit(srcDB, dstURI string) (*Backup, error) { + dst, err := src.openDB(dstURI, OPEN_READWRITE|OPEN_CREATE|OPEN_URI) + if err != nil { + return nil, err + } + return src.backupInit(dst, "main", src.handle, srcDB) +} + +func (c *Conn) backupInit(dst uint32, dstName string, src uint32, srcName string) (*Backup, error) { + defer c.arena.mark()() + dstPtr := c.arena.string(dstName) + srcPtr := c.arena.string(srcName) + + other := dst + if c.handle == dst { + other = src + } + + r := c.call("sqlite3_backup_init", + uint64(dst), uint64(dstPtr), + uint64(src), uint64(srcPtr)) + if r == 0 { + defer c.closeDB(other) + r = c.call("sqlite3_errcode", uint64(dst)) + return nil, c.sqlite.error(r, dst) + } + + return &Backup{ + c: c, + otherc: other, + handle: uint32(r), + }, nil +} + +// Close finishes a backup operation. +// +// It is safe to close a nil, zero or closed Backup. +// +// https://sqlite.org/c3ref/backup_finish.html#sqlite3backupfinish +func (b *Backup) Close() error { + if b == nil || b.handle == 0 { + return nil + } + + r := b.c.call("sqlite3_backup_finish", uint64(b.handle)) + b.c.closeDB(b.otherc) + b.handle = 0 + return b.c.error(r) +} + +// Step copies up to nPage pages between the source and destination databases. +// If nPage is negative, all remaining source pages are copied. +// +// https://sqlite.org/c3ref/backup_finish.html#sqlite3backupstep +func (b *Backup) Step(nPage int) (done bool, err error) { + r := b.c.call("sqlite3_backup_step", uint64(b.handle), uint64(nPage)) + if r == _DONE { + return true, nil + } + return false, b.c.error(r) +} + +// Remaining returns the number of pages still to be backed up +// at the conclusion of the most recent [Backup.Step]. +// +// https://sqlite.org/c3ref/backup_finish.html#sqlite3backupremaining +func (b *Backup) Remaining() int { + r := b.c.call("sqlite3_backup_remaining", uint64(b.handle)) + return int(int32(r)) +} + +// PageCount returns the total number of pages in the source database +// at the conclusion of the most recent [Backup.Step]. +// +// https://sqlite.org/c3ref/backup_finish.html#sqlite3backuppagecount +func (b *Backup) PageCount() int { + r := b.c.call("sqlite3_backup_pagecount", uint64(b.handle)) + return int(int32(r)) +} diff --git a/vendor/github.com/ncruces/go-sqlite3/blob.go b/vendor/github.com/ncruces/go-sqlite3/blob.go new file mode 100644 index 000000000..bb10c5fa2 --- /dev/null +++ b/vendor/github.com/ncruces/go-sqlite3/blob.go @@ -0,0 +1,250 @@ +package sqlite3 + +import ( + "io" + + "github.com/ncruces/go-sqlite3/internal/util" +) + +// ZeroBlob represents a zero-filled, length n BLOB +// that can be used as an argument to +// [database/sql.DB.Exec] and similar methods. +type ZeroBlob int64 + +// Blob is an handle to an open BLOB. +// +// It implements [io.ReadWriteSeeker] for incremental BLOB I/O. +// +// https://sqlite.org/c3ref/blob.html +type Blob struct { + c *Conn + bytes int64 + offset int64 + handle uint32 +} + +var _ io.ReadWriteSeeker = &Blob{} + +// OpenBlob opens a BLOB for incremental I/O. +// +// https://sqlite.org/c3ref/blob_open.html +func (c *Conn) OpenBlob(db, table, column string, row int64, write bool) (*Blob, error) { + c.checkInterrupt() + defer c.arena.mark()() + blobPtr := c.arena.new(ptrlen) + dbPtr := c.arena.string(db) + tablePtr := c.arena.string(table) + columnPtr := c.arena.string(column) + + var flags uint64 + if write { + flags = 1 + } + + r := c.call("sqlite3_blob_open", uint64(c.handle), + uint64(dbPtr), uint64(tablePtr), uint64(columnPtr), + uint64(row), flags, uint64(blobPtr)) + + if err := c.error(r); err != nil { + return nil, err + } + + blob := Blob{c: c} + blob.handle = util.ReadUint32(c.mod, blobPtr) + blob.bytes = int64(c.call("sqlite3_blob_bytes", uint64(blob.handle))) + return &blob, nil +} + +// Close closes a BLOB handle. +// +// It is safe to close a nil, zero or closed Blob. +// +// https://sqlite.org/c3ref/blob_close.html +func (b *Blob) Close() error { + if b == nil || b.handle == 0 { + return nil + } + + r := b.c.call("sqlite3_blob_close", uint64(b.handle)) + + b.handle = 0 + return b.c.error(r) +} + +// Size returns the size of the BLOB in bytes. +// +// https://sqlite.org/c3ref/blob_bytes.html +func (b *Blob) Size() int64 { + return b.bytes +} + +// Read implements the [io.Reader] interface. +// +// https://sqlite.org/c3ref/blob_read.html +func (b *Blob) Read(p []byte) (n int, err error) { + if b.offset >= b.bytes { + return 0, io.EOF + } + + avail := b.bytes - b.offset + want := int64(len(p)) + if want > avail { + want = avail + } + + defer b.c.arena.mark()() + ptr := b.c.arena.new(uint64(want)) + + r := b.c.call("sqlite3_blob_read", uint64(b.handle), + uint64(ptr), uint64(want), uint64(b.offset)) + err = b.c.error(r) + if err != nil { + return 0, err + } + b.offset += want + if b.offset >= b.bytes { + err = io.EOF + } + + copy(p, util.View(b.c.mod, ptr, uint64(want))) + return int(want), err +} + +// WriteTo implements the [io.WriterTo] interface. +// +// https://sqlite.org/c3ref/blob_read.html +func (b *Blob) WriteTo(w io.Writer) (n int64, err error) { + if b.offset >= b.bytes { + return 0, nil + } + + want := int64(1024 * 1024) + avail := b.bytes - b.offset + if want > avail { + want = avail + } + + defer b.c.arena.mark()() + ptr := b.c.arena.new(uint64(want)) + + for want > 0 { + r := b.c.call("sqlite3_blob_read", uint64(b.handle), + uint64(ptr), uint64(want), uint64(b.offset)) + err = b.c.error(r) + if err != nil { + return n, err + } + + mem := util.View(b.c.mod, ptr, uint64(want)) + m, err := w.Write(mem[:want]) + b.offset += int64(m) + n += int64(m) + if err != nil { + return n, err + } + if int64(m) != want { + return n, io.ErrShortWrite + } + + avail = b.bytes - b.offset + if want > avail { + want = avail + } + } + return n, nil +} + +// Write implements the [io.Writer] interface. +// +// https://sqlite.org/c3ref/blob_write.html +func (b *Blob) Write(p []byte) (n int, err error) { + defer b.c.arena.mark()() + ptr := b.c.arena.bytes(p) + + r := b.c.call("sqlite3_blob_write", uint64(b.handle), + uint64(ptr), uint64(len(p)), uint64(b.offset)) + err = b.c.error(r) + if err != nil { + return 0, err + } + b.offset += int64(len(p)) + return len(p), nil +} + +// ReadFrom implements the [io.ReaderFrom] interface. +// +// https://sqlite.org/c3ref/blob_write.html +func (b *Blob) ReadFrom(r io.Reader) (n int64, err error) { + want := int64(1024 * 1024) + avail := b.bytes - b.offset + if l, ok := r.(*io.LimitedReader); ok && want > l.N { + want = l.N + } + if want > avail { + want = avail + } + if want < 1 { + want = 1 + } + + defer b.c.arena.mark()() + ptr := b.c.arena.new(uint64(want)) + + for { + mem := util.View(b.c.mod, ptr, uint64(want)) + m, err := r.Read(mem[:want]) + if m > 0 { + r := b.c.call("sqlite3_blob_write", uint64(b.handle), + uint64(ptr), uint64(m), uint64(b.offset)) + err := b.c.error(r) + if err != nil { + return n, err + } + b.offset += int64(m) + n += int64(m) + } + if err == io.EOF { + return n, nil + } + if err != nil { + return n, err + } + + avail = b.bytes - b.offset + if want > avail { + want = avail + } + if want < 1 { + want = 1 + } + } +} + +// Seek implements the [io.Seeker] interface. +func (b *Blob) Seek(offset int64, whence int) (int64, error) { + switch whence { + default: + return 0, util.WhenceErr + case io.SeekStart: + break + case io.SeekCurrent: + offset += b.offset + case io.SeekEnd: + offset += b.bytes + } + if offset < 0 { + return 0, util.OffsetErr + } + b.offset = offset + return offset, nil +} + +// Reopen moves a BLOB handle to a new row of the same database table. +// +// https://sqlite.org/c3ref/blob_reopen.html +func (b *Blob) Reopen(row int64) error { + err := b.c.error(b.c.call("sqlite3_blob_reopen", uint64(b.handle), uint64(row))) + b.bytes = int64(b.c.call("sqlite3_blob_bytes", uint64(b.handle))) + b.offset = 0 + return err +} diff --git a/vendor/github.com/ncruces/go-sqlite3/config.go b/vendor/github.com/ncruces/go-sqlite3/config.go new file mode 100644 index 000000000..0342be7fb --- /dev/null +++ b/vendor/github.com/ncruces/go-sqlite3/config.go @@ -0,0 +1,164 @@ +package sqlite3 + +import ( + "context" + + "github.com/ncruces/go-sqlite3/internal/util" + "github.com/tetratelabs/wazero/api" +) + +// Config makes configuration changes to a database connection. +// Only boolean configuration options are supported. +// Called with no arg reads the current configuration value, +// called with one arg sets and returns the new value. +// +// https://sqlite.org/c3ref/db_config.html +func (c *Conn) Config(op DBConfig, arg ...bool) (bool, error) { + defer c.arena.mark()() + argsPtr := c.arena.new(2 * ptrlen) + + var flag int + switch { + case len(arg) == 0: + flag = -1 + case arg[0]: + flag = 1 + } + + util.WriteUint32(c.mod, argsPtr+0*ptrlen, uint32(flag)) + util.WriteUint32(c.mod, argsPtr+1*ptrlen, argsPtr) + + r := c.call("sqlite3_db_config", uint64(c.handle), + uint64(op), uint64(argsPtr)) + return util.ReadUint32(c.mod, argsPtr) != 0, c.error(r) +} + +// ConfigLog sets up the error logging callback for the connection. +// +// https://sqlite.org/errlog.html +func (c *Conn) ConfigLog(cb func(code ExtendedErrorCode, msg string)) error { + var enable uint64 + if cb != nil { + enable = 1 + } + r := c.call("sqlite3_config_log_go", enable) + if err := c.error(r); err != nil { + return err + } + c.log = cb + return nil +} + +func logCallback(ctx context.Context, mod api.Module, _, iCode, zMsg uint32) { + if c, ok := ctx.Value(connKey{}).(*Conn); ok && c.log != nil { + msg := util.ReadString(mod, zMsg, _MAX_LENGTH) + c.log(xErrorCode(iCode), msg) + } +} + +// Limit allows the size of various constructs to be +// limited on a connection by connection basis. +// +// https://sqlite.org/c3ref/limit.html +func (c *Conn) Limit(id LimitCategory, value int) int { + r := c.call("sqlite3_limit", uint64(c.handle), uint64(id), uint64(value)) + return int(int32(r)) +} + +// SetAuthorizer registers an authorizer callback with the database connection. +// +// https://sqlite.org/c3ref/set_authorizer.html +func (c *Conn) SetAuthorizer(cb func(action AuthorizerActionCode, name3rd, name4th, schema, nameInner string) AuthorizerReturnCode) error { + var enable uint64 + if cb != nil { + enable = 1 + } + r := c.call("sqlite3_set_authorizer_go", uint64(c.handle), enable) + if err := c.error(r); err != nil { + return err + } + c.authorizer = cb + return nil + +} + +func authorizerCallback(ctx context.Context, mod api.Module, pDB uint32, action AuthorizerActionCode, zName3rd, zName4th, zSchema, zNameInner uint32) (rc AuthorizerReturnCode) { + if c, ok := ctx.Value(connKey{}).(*Conn); ok && c.handle == pDB && c.authorizer != nil { + var name3rd, name4th, schema, nameInner string + if zName3rd != 0 { + name3rd = util.ReadString(mod, zName3rd, _MAX_NAME) + } + if zName4th != 0 { + name4th = util.ReadString(mod, zName4th, _MAX_NAME) + } + if zSchema != 0 { + schema = util.ReadString(mod, zSchema, _MAX_NAME) + } + if zNameInner != 0 { + nameInner = util.ReadString(mod, zNameInner, _MAX_NAME) + } + rc = c.authorizer(action, name3rd, name4th, schema, nameInner) + } + return rc +} + +// WalCheckpoint checkpoints a WAL database. +// +// https://sqlite.org/c3ref/wal_checkpoint_v2.html +func (c *Conn) WalCheckpoint(schema string, mode CheckpointMode) (nLog, nCkpt int, err error) { + defer c.arena.mark()() + nLogPtr := c.arena.new(ptrlen) + nCkptPtr := c.arena.new(ptrlen) + schemaPtr := c.arena.string(schema) + r := c.call("sqlite3_wal_checkpoint_v2", + uint64(c.handle), uint64(schemaPtr), uint64(mode), + uint64(nLogPtr), uint64(nCkptPtr)) + nLog = int(int32(util.ReadUint32(c.mod, nLogPtr))) + nCkpt = int(int32(util.ReadUint32(c.mod, nCkptPtr))) + return nLog, nCkpt, c.error(r) +} + +// WalAutoCheckpoint configures WAL auto-checkpoints. +// +// https://sqlite.org/c3ref/wal_autocheckpoint.html +func (c *Conn) WalAutoCheckpoint(pages int) error { + r := c.call("sqlite3_wal_autocheckpoint", uint64(c.handle), uint64(pages)) + return c.error(r) +} + +// WalHook registers a callback function to be invoked +// each time data is committed to a database in WAL mode. +// +// https://sqlite.org/c3ref/wal_hook.html +func (c *Conn) WalHook(cb func(db *Conn, schema string, pages int) error) { + var enable uint64 + if cb != nil { + enable = 1 + } + c.call("sqlite3_wal_hook_go", uint64(c.handle), enable) + c.wal = cb +} + +func walCallback(ctx context.Context, mod api.Module, _, pDB, zSchema uint32, pages int32) (rc uint32) { + if c, ok := ctx.Value(connKey{}).(*Conn); ok && c.handle == pDB && c.wal != nil { + schema := util.ReadString(mod, zSchema, _MAX_NAME) + err := c.wal(c, schema, int(pages)) + _, rc = errorCode(err, ERROR) + } + return rc +} + +// AutoVacuumPages registers a autovacuum compaction amount callback. +// +// https://sqlite.org/c3ref/autovacuum_pages.html +func (c *Conn) AutoVacuumPages(cb func(schema string, dbPages, freePages, bytesPerPage uint) uint) error { + funcPtr := util.AddHandle(c.ctx, cb) + r := c.call("sqlite3_autovacuum_pages_go", uint64(c.handle), uint64(funcPtr)) + return c.error(r) +} + +func autoVacuumCallback(ctx context.Context, mod api.Module, pApp, zSchema, nDbPage, nFreePage, nBytePerPage uint32) uint32 { + fn := util.GetHandle(ctx, pApp).(func(schema string, dbPages, freePages, bytesPerPage uint) uint) + schema := util.ReadString(mod, zSchema, _MAX_NAME) + return uint32(fn(schema, uint(nDbPage), uint(nFreePage), uint(nBytePerPage))) +} diff --git a/vendor/github.com/ncruces/go-sqlite3/conn.go b/vendor/github.com/ncruces/go-sqlite3/conn.go new file mode 100644 index 000000000..f170ccf57 --- /dev/null +++ b/vendor/github.com/ncruces/go-sqlite3/conn.go @@ -0,0 +1,426 @@ +package sqlite3 + +import ( + "context" + "fmt" + "math" + "net/url" + "strings" + "time" + + "github.com/ncruces/go-sqlite3/internal/util" + "github.com/ncruces/go-sqlite3/vfs" + "github.com/tetratelabs/wazero/api" +) + +// Conn is a database connection handle. +// A Conn is not safe for concurrent use by multiple goroutines. +// +// https://sqlite.org/c3ref/sqlite3.html +type Conn struct { + *sqlite + + interrupt context.Context + pending *Stmt + busy func(int) bool + log func(xErrorCode, string) + collation func(*Conn, string) + authorizer func(AuthorizerActionCode, string, string, string, string) AuthorizerReturnCode + update func(AuthorizerActionCode, string, string, int64) + commit func() bool + rollback func() + wal func(*Conn, string, int) error + arena arena + + handle uint32 +} + +// Open calls [OpenFlags] with [OPEN_READWRITE], [OPEN_CREATE], [OPEN_URI] and [OPEN_NOFOLLOW]. +func Open(filename string) (*Conn, error) { + return newConn(filename, OPEN_READWRITE|OPEN_CREATE|OPEN_URI|OPEN_NOFOLLOW) +} + +// OpenFlags opens an SQLite database file as specified by the filename argument. +// +// If none of the required flags is used, a combination of [OPEN_READWRITE] and [OPEN_CREATE] is used. +// If a URI filename is used, PRAGMA statements to execute can be specified using "_pragma": +// +// sqlite3.Open("file:demo.db?_pragma=busy_timeout(10000)") +// +// https://sqlite.org/c3ref/open.html +func OpenFlags(filename string, flags OpenFlag) (*Conn, error) { + if flags&(OPEN_READONLY|OPEN_READWRITE|OPEN_CREATE) == 0 { + flags |= OPEN_READWRITE | OPEN_CREATE + } + return newConn(filename, flags) +} + +type connKey struct{} + +func newConn(filename string, flags OpenFlag) (conn *Conn, err error) { + sqlite, err := instantiateSQLite() + if err != nil { + return nil, err + } + defer func() { + if conn == nil { + sqlite.close() + } + }() + + c := &Conn{sqlite: sqlite} + c.arena = c.newArena(1024) + c.ctx = context.WithValue(c.ctx, connKey{}, c) + c.handle, err = c.openDB(filename, flags) + if err != nil { + return nil, err + } + return c, nil +} + +func (c *Conn) openDB(filename string, flags OpenFlag) (uint32, error) { + defer c.arena.mark()() + connPtr := c.arena.new(ptrlen) + namePtr := c.arena.string(filename) + + flags |= OPEN_EXRESCODE + r := c.call("sqlite3_open_v2", uint64(namePtr), uint64(connPtr), uint64(flags), 0) + + handle := util.ReadUint32(c.mod, connPtr) + if err := c.sqlite.error(r, handle); err != nil { + c.closeDB(handle) + return 0, err + } + + if flags|OPEN_URI != 0 && strings.HasPrefix(filename, "file:") { + var pragmas strings.Builder + if _, after, ok := strings.Cut(filename, "?"); ok { + query, _ := url.ParseQuery(after) + for _, p := range query["_pragma"] { + pragmas.WriteString(`PRAGMA `) + pragmas.WriteString(p) + pragmas.WriteString(`;`) + } + } + 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.call("sqlite3_progress_handler_go", uint64(handle), 100) + return handle, nil +} + +func (c *Conn) closeDB(handle uint32) { + r := c.call("sqlite3_close_v2", uint64(handle)) + if err := c.sqlite.error(r, handle); err != nil { + panic(err) + } +} + +// Close closes the database connection. +// +// If the database connection is associated with unfinalized prepared statements, +// open blob handles, and/or unfinished backup objects, +// Close will leave the database connection open and return [BUSY]. +// +// It is safe to close a nil, zero or closed Conn. +// +// https://sqlite.org/c3ref/close.html +func (c *Conn) Close() error { + if c == nil || c.handle == 0 { + return nil + } + + c.pending.Close() + c.pending = nil + + r := c.call("sqlite3_close", uint64(c.handle)) + if err := c.error(r); err != nil { + return err + } + + c.handle = 0 + return c.close() +} + +// Exec is a convenience function that allows an application to run +// multiple statements of SQL without having to use a lot of code. +// +// https://sqlite.org/c3ref/exec.html +func (c *Conn) Exec(sql string) error { + c.checkInterrupt() + defer c.arena.mark()() + sqlPtr := c.arena.string(sql) + + r := c.call("sqlite3_exec", uint64(c.handle), uint64(sqlPtr), 0, 0, 0) + return c.error(r, sql) +} + +// Prepare calls [Conn.PrepareFlags] with no flags. +func (c *Conn) Prepare(sql string) (stmt *Stmt, tail string, err error) { + return c.PrepareFlags(sql, 0) +} + +// PrepareFlags compiles the first SQL statement in sql; +// tail is left pointing to what remains uncompiled. +// If the input text contains no SQL (if the input is an empty string or a comment), +// both stmt and err will be nil. +// +// https://sqlite.org/c3ref/prepare.html +func (c *Conn) PrepareFlags(sql string, flags PrepareFlag) (stmt *Stmt, tail string, err error) { + if len(sql) > _MAX_SQL_LENGTH { + return nil, "", TOOBIG + } + + defer c.arena.mark()() + stmtPtr := c.arena.new(ptrlen) + tailPtr := c.arena.new(ptrlen) + sqlPtr := c.arena.string(sql) + + r := c.call("sqlite3_prepare_v3", uint64(c.handle), + uint64(sqlPtr), uint64(len(sql)+1), uint64(flags), + uint64(stmtPtr), uint64(tailPtr)) + + stmt = &Stmt{c: c} + stmt.handle = util.ReadUint32(c.mod, stmtPtr) + if sql := sql[util.ReadUint32(c.mod, tailPtr)-sqlPtr:]; sql != "" { + tail = sql + } + + if err := c.error(r, sql); err != nil { + return nil, "", err + } + if stmt.handle == 0 { + return nil, "", nil + } + return stmt, tail, nil +} + +// DBName returns the schema name for n-th database on the database connection. +// +// https://sqlite.org/c3ref/db_name.html +func (c *Conn) DBName(n int) string { + r := c.call("sqlite3_db_name", uint64(c.handle), uint64(n)) + + ptr := uint32(r) + if ptr == 0 { + return "" + } + 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 +func (c *Conn) ReadOnly(schema string) (ro bool, ok bool) { + var ptr uint32 + if schema != "" { + defer c.arena.mark()() + ptr = c.arena.string(schema) + } + r := c.call("sqlite3_db_readonly", uint64(c.handle), uint64(ptr)) + return int32(r) > 0, int32(r) < 0 +} + +// GetAutocommit tests the connection for auto-commit mode. +// +// https://sqlite.org/c3ref/get_autocommit.html +func (c *Conn) GetAutocommit() bool { + r := c.call("sqlite3_get_autocommit", uint64(c.handle)) + return r != 0 +} + +// LastInsertRowID returns the rowid of the most recent successful INSERT +// on the database connection. +// +// https://sqlite.org/c3ref/last_insert_rowid.html +func (c *Conn) LastInsertRowID() int64 { + r := c.call("sqlite3_last_insert_rowid", uint64(c.handle)) + return int64(r) +} + +// SetLastInsertRowID allows the application to set the value returned by +// [Conn.LastInsertRowID]. +// +// https://sqlite.org/c3ref/set_last_insert_rowid.html +func (c *Conn) SetLastInsertRowID(id int64) { + c.call("sqlite3_set_last_insert_rowid", uint64(c.handle), uint64(id)) +} + +// Changes returns the number of rows modified, inserted or deleted +// by the most recently completed INSERT, UPDATE or DELETE statement +// on the database connection. +// +// https://sqlite.org/c3ref/changes.html +func (c *Conn) Changes() int64 { + r := c.call("sqlite3_changes64", uint64(c.handle)) + return int64(r) +} + +// TotalChanges returns the number of rows modified, inserted or deleted +// by all INSERT, UPDATE or DELETE statements completed +// since the database connection was opened. +// +// https://sqlite.org/c3ref/total_changes.html +func (c *Conn) TotalChanges() int64 { + r := c.call("sqlite3_total_changes64", uint64(c.handle)) + return int64(r) +} + +// ReleaseMemory frees memory used by a database connection. +// +// https://sqlite.org/c3ref/db_release_memory.html +func (c *Conn) ReleaseMemory() error { + r := c.call("sqlite3_db_release_memory", uint64(c.handle)) + return c.error(r) +} + +// GetInterrupt gets the context set with [Conn.SetInterrupt], +// or nil if none was set. +func (c *Conn) GetInterrupt() context.Context { + return c.interrupt +} + +// SetInterrupt interrupts a long-running query when a context is done. +// +// Subsequent uses of the connection will return [INTERRUPT] +// until the context is reset by another call to SetInterrupt. +// +// To associate a timeout with a connection: +// +// ctx, cancel := context.WithTimeout(context.TODO(), 100*time.Millisecond) +// conn.SetInterrupt(ctx) +// defer cancel() +// +// SetInterrupt returns the old context assigned to the connection. +// +// https://sqlite.org/c3ref/interrupt.html +func (c *Conn) SetInterrupt(ctx context.Context) (old context.Context) { + // Is it the same context? + if ctx == c.interrupt { + return ctx + } + + // A busy SQL statement prevents SQLite from ignoring an interrupt + // that comes before any other statements are started. + if c.pending == nil { + c.pending, _, _ = c.Prepare(`WITH RECURSIVE c(x) AS (VALUES(0) UNION ALL SELECT x FROM c) SELECT x FROM c`) + } + + old = c.interrupt + c.interrupt = ctx + + if old != nil && old.Done() != nil && (ctx == nil || ctx.Err() == nil) { + c.pending.Reset() + } + if ctx != nil && ctx.Done() != nil { + c.pending.Step() + } + return old +} + +func (c *Conn) checkInterrupt() { + if c.interrupt != nil && c.interrupt.Err() != nil { + c.call("sqlite3_interrupt", uint64(c.handle)) + } +} + +func progressCallback(ctx context.Context, mod api.Module, pDB uint32) (interrupt uint32) { + if c, ok := ctx.Value(connKey{}).(*Conn); ok && c.handle == pDB && + c.interrupt != nil && c.interrupt.Err() != nil { + interrupt = 1 + } + return interrupt +} + +// BusyTimeout sets a busy timeout. +// +// https://sqlite.org/c3ref/busy_timeout.html +func (c *Conn) BusyTimeout(timeout time.Duration) error { + ms := min((timeout+time.Millisecond-1)/time.Millisecond, math.MaxInt32) + r := c.call("sqlite3_busy_timeout", uint64(c.handle), uint64(ms)) + return c.error(r) +} + +func timeoutCallback(ctx context.Context, mod api.Module, pDB uint32, count, tmout int32) (retry uint32) { + if c, ok := ctx.Value(connKey{}).(*Conn); ok && + (c.interrupt == nil || c.interrupt.Err() == nil) { + const delays = "\x01\x02\x05\x0a\x0f\x14\x19\x19\x19\x32\x32\x64" + const totals = "\x00\x01\x03\x08\x12\x21\x35\x4e\x67\x80\xb2\xe4" + const ndelay = int32(len(delays) - 1) + + var delay, prior int32 + if count <= ndelay { + delay = int32(delays[count]) + prior = int32(totals[count]) + } else { + delay = int32(delays[ndelay]) + prior = int32(totals[ndelay]) + delay*(count-ndelay) + } + + if delay = min(delay, tmout-prior); delay > 0 { + time.Sleep(time.Duration(delay) * time.Millisecond) + retry = 1 + } + } + return retry +} + +// BusyHandler registers a callback to handle [BUSY] errors. +// +// https://sqlite.org/c3ref/busy_handler.html +func (c *Conn) BusyHandler(cb func(count int) (retry bool)) error { + var enable uint64 + if cb != nil { + enable = 1 + } + r := c.call("sqlite3_busy_handler_go", uint64(c.handle), enable) + if err := c.error(r); err != nil { + return err + } + c.busy = cb + return nil +} + +func busyCallback(ctx context.Context, mod api.Module, pDB uint32, count int32) (retry uint32) { + if c, ok := ctx.Value(connKey{}).(*Conn); ok && c.handle == pDB && c.busy != nil && + (c.interrupt == nil || c.interrupt.Err() == nil) { + if c.busy(int(count)) { + retry = 1 + } + } + return retry +} + +func (c *Conn) error(rc uint64, sql ...string) error { + return c.sqlite.error(rc, c.handle, sql...) +} + +// 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 +type DriverConn interface { + Raw() *Conn +} diff --git a/vendor/github.com/ncruces/go-sqlite3/const.go b/vendor/github.com/ncruces/go-sqlite3/const.go new file mode 100644 index 000000000..2bb53656f --- /dev/null +++ b/vendor/github.com/ncruces/go-sqlite3/const.go @@ -0,0 +1,360 @@ +package sqlite3 + +import "strconv" + +const ( + _OK = 0 /* Successful result */ + _ROW = 100 /* sqlite3_step() has another row ready */ + _DONE = 101 /* sqlite3_step() has finished executing */ + + _UTF8 = 1 + + _MAX_NAME = 1e6 // Self-imposed limit for most NUL terminated strings. + _MAX_LENGTH = 1e9 + _MAX_SQL_LENGTH = 1e9 + _MAX_ALLOCATION_SIZE = 0x7ffffeff + _MAX_FUNCTION_ARG = 100 + + ptrlen = 4 +) + +// ErrorCode is a result code that [Error.Code] might return. +// +// https://sqlite.org/rescode.html +type ErrorCode uint8 + +const ( + ERROR ErrorCode = 1 /* Generic error */ + INTERNAL ErrorCode = 2 /* Internal logic error in SQLite */ + PERM ErrorCode = 3 /* Access permission denied */ + ABORT ErrorCode = 4 /* Callback routine requested an abort */ + BUSY ErrorCode = 5 /* The database file is locked */ + LOCKED ErrorCode = 6 /* A table in the database is locked */ + NOMEM ErrorCode = 7 /* A malloc() failed */ + READONLY ErrorCode = 8 /* Attempt to write a readonly database */ + INTERRUPT ErrorCode = 9 /* Operation terminated by sqlite3_interrupt() */ + IOERR ErrorCode = 10 /* Some kind of disk I/O error occurred */ + CORRUPT ErrorCode = 11 /* The database disk image is malformed */ + NOTFOUND ErrorCode = 12 /* Unknown opcode in sqlite3_file_control() */ + FULL ErrorCode = 13 /* Insertion failed because database is full */ + CANTOPEN ErrorCode = 14 /* Unable to open the database file */ + PROTOCOL ErrorCode = 15 /* Database lock protocol error */ + EMPTY ErrorCode = 16 /* Internal use only */ + SCHEMA ErrorCode = 17 /* The database schema changed */ + TOOBIG ErrorCode = 18 /* String or BLOB exceeds size limit */ + CONSTRAINT ErrorCode = 19 /* Abort due to constraint violation */ + MISMATCH ErrorCode = 20 /* Data type mismatch */ + MISUSE ErrorCode = 21 /* Library used incorrectly */ + NOLFS ErrorCode = 22 /* Uses OS features not supported on host */ + AUTH ErrorCode = 23 /* Authorization denied */ + FORMAT ErrorCode = 24 /* Not used */ + RANGE ErrorCode = 25 /* 2nd parameter to sqlite3_bind out of range */ + NOTADB ErrorCode = 26 /* File opened that is not a database file */ + NOTICE ErrorCode = 27 /* Notifications from sqlite3_log() */ + WARNING ErrorCode = 28 /* Warnings from sqlite3_log() */ +) + +// ExtendedErrorCode is a result code that [Error.ExtendedCode] might return. +// +// https://sqlite.org/rescode.html +type ( + ExtendedErrorCode uint16 + xErrorCode = ExtendedErrorCode +) + +const ( + ERROR_MISSING_COLLSEQ ExtendedErrorCode = xErrorCode(ERROR) | (1 << 8) + ERROR_RETRY ExtendedErrorCode = xErrorCode(ERROR) | (2 << 8) + ERROR_SNAPSHOT ExtendedErrorCode = xErrorCode(ERROR) | (3 << 8) + IOERR_READ ExtendedErrorCode = xErrorCode(IOERR) | (1 << 8) + IOERR_SHORT_READ ExtendedErrorCode = xErrorCode(IOERR) | (2 << 8) + IOERR_WRITE ExtendedErrorCode = xErrorCode(IOERR) | (3 << 8) + IOERR_FSYNC ExtendedErrorCode = xErrorCode(IOERR) | (4 << 8) + IOERR_DIR_FSYNC ExtendedErrorCode = xErrorCode(IOERR) | (5 << 8) + IOERR_TRUNCATE ExtendedErrorCode = xErrorCode(IOERR) | (6 << 8) + IOERR_FSTAT ExtendedErrorCode = xErrorCode(IOERR) | (7 << 8) + IOERR_UNLOCK ExtendedErrorCode = xErrorCode(IOERR) | (8 << 8) + IOERR_RDLOCK ExtendedErrorCode = xErrorCode(IOERR) | (9 << 8) + IOERR_DELETE ExtendedErrorCode = xErrorCode(IOERR) | (10 << 8) + IOERR_BLOCKED ExtendedErrorCode = xErrorCode(IOERR) | (11 << 8) + IOERR_NOMEM ExtendedErrorCode = xErrorCode(IOERR) | (12 << 8) + IOERR_ACCESS ExtendedErrorCode = xErrorCode(IOERR) | (13 << 8) + IOERR_CHECKRESERVEDLOCK ExtendedErrorCode = xErrorCode(IOERR) | (14 << 8) + IOERR_LOCK ExtendedErrorCode = xErrorCode(IOERR) | (15 << 8) + IOERR_CLOSE ExtendedErrorCode = xErrorCode(IOERR) | (16 << 8) + IOERR_DIR_CLOSE ExtendedErrorCode = xErrorCode(IOERR) | (17 << 8) + IOERR_SHMOPEN ExtendedErrorCode = xErrorCode(IOERR) | (18 << 8) + IOERR_SHMSIZE ExtendedErrorCode = xErrorCode(IOERR) | (19 << 8) + IOERR_SHMLOCK ExtendedErrorCode = xErrorCode(IOERR) | (20 << 8) + IOERR_SHMMAP ExtendedErrorCode = xErrorCode(IOERR) | (21 << 8) + IOERR_SEEK ExtendedErrorCode = xErrorCode(IOERR) | (22 << 8) + IOERR_DELETE_NOENT ExtendedErrorCode = xErrorCode(IOERR) | (23 << 8) + IOERR_MMAP ExtendedErrorCode = xErrorCode(IOERR) | (24 << 8) + IOERR_GETTEMPPATH ExtendedErrorCode = xErrorCode(IOERR) | (25 << 8) + IOERR_CONVPATH ExtendedErrorCode = xErrorCode(IOERR) | (26 << 8) + IOERR_VNODE ExtendedErrorCode = xErrorCode(IOERR) | (27 << 8) + IOERR_AUTH ExtendedErrorCode = xErrorCode(IOERR) | (28 << 8) + IOERR_BEGIN_ATOMIC ExtendedErrorCode = xErrorCode(IOERR) | (29 << 8) + IOERR_COMMIT_ATOMIC ExtendedErrorCode = xErrorCode(IOERR) | (30 << 8) + IOERR_ROLLBACK_ATOMIC ExtendedErrorCode = xErrorCode(IOERR) | (31 << 8) + IOERR_DATA ExtendedErrorCode = xErrorCode(IOERR) | (32 << 8) + IOERR_CORRUPTFS ExtendedErrorCode = xErrorCode(IOERR) | (33 << 8) + IOERR_IN_PAGE ExtendedErrorCode = xErrorCode(IOERR) | (34 << 8) + LOCKED_SHAREDCACHE ExtendedErrorCode = xErrorCode(LOCKED) | (1 << 8) + LOCKED_VTAB ExtendedErrorCode = xErrorCode(LOCKED) | (2 << 8) + BUSY_RECOVERY ExtendedErrorCode = xErrorCode(BUSY) | (1 << 8) + BUSY_SNAPSHOT ExtendedErrorCode = xErrorCode(BUSY) | (2 << 8) + BUSY_TIMEOUT ExtendedErrorCode = xErrorCode(BUSY) | (3 << 8) + CANTOPEN_NOTEMPDIR ExtendedErrorCode = xErrorCode(CANTOPEN) | (1 << 8) + CANTOPEN_ISDIR ExtendedErrorCode = xErrorCode(CANTOPEN) | (2 << 8) + CANTOPEN_FULLPATH ExtendedErrorCode = xErrorCode(CANTOPEN) | (3 << 8) + CANTOPEN_CONVPATH ExtendedErrorCode = xErrorCode(CANTOPEN) | (4 << 8) + CANTOPEN_DIRTYWAL ExtendedErrorCode = xErrorCode(CANTOPEN) | (5 << 8) /* Not Used */ + CANTOPEN_SYMLINK ExtendedErrorCode = xErrorCode(CANTOPEN) | (6 << 8) + CORRUPT_VTAB ExtendedErrorCode = xErrorCode(CORRUPT) | (1 << 8) + CORRUPT_SEQUENCE ExtendedErrorCode = xErrorCode(CORRUPT) | (2 << 8) + CORRUPT_INDEX ExtendedErrorCode = xErrorCode(CORRUPT) | (3 << 8) + READONLY_RECOVERY ExtendedErrorCode = xErrorCode(READONLY) | (1 << 8) + READONLY_CANTLOCK ExtendedErrorCode = xErrorCode(READONLY) | (2 << 8) + READONLY_ROLLBACK ExtendedErrorCode = xErrorCode(READONLY) | (3 << 8) + READONLY_DBMOVED ExtendedErrorCode = xErrorCode(READONLY) | (4 << 8) + READONLY_CANTINIT ExtendedErrorCode = xErrorCode(READONLY) | (5 << 8) + READONLY_DIRECTORY ExtendedErrorCode = xErrorCode(READONLY) | (6 << 8) + ABORT_ROLLBACK ExtendedErrorCode = xErrorCode(ABORT) | (2 << 8) + CONSTRAINT_CHECK ExtendedErrorCode = xErrorCode(CONSTRAINT) | (1 << 8) + CONSTRAINT_COMMITHOOK ExtendedErrorCode = xErrorCode(CONSTRAINT) | (2 << 8) + CONSTRAINT_FOREIGNKEY ExtendedErrorCode = xErrorCode(CONSTRAINT) | (3 << 8) + CONSTRAINT_FUNCTION ExtendedErrorCode = xErrorCode(CONSTRAINT) | (4 << 8) + CONSTRAINT_NOTNULL ExtendedErrorCode = xErrorCode(CONSTRAINT) | (5 << 8) + CONSTRAINT_PRIMARYKEY ExtendedErrorCode = xErrorCode(CONSTRAINT) | (6 << 8) + CONSTRAINT_TRIGGER ExtendedErrorCode = xErrorCode(CONSTRAINT) | (7 << 8) + CONSTRAINT_UNIQUE ExtendedErrorCode = xErrorCode(CONSTRAINT) | (8 << 8) + CONSTRAINT_VTAB ExtendedErrorCode = xErrorCode(CONSTRAINT) | (9 << 8) + CONSTRAINT_ROWID ExtendedErrorCode = xErrorCode(CONSTRAINT) | (10 << 8) + CONSTRAINT_PINNED ExtendedErrorCode = xErrorCode(CONSTRAINT) | (11 << 8) + CONSTRAINT_DATATYPE ExtendedErrorCode = xErrorCode(CONSTRAINT) | (12 << 8) + NOTICE_RECOVER_WAL ExtendedErrorCode = xErrorCode(NOTICE) | (1 << 8) + NOTICE_RECOVER_ROLLBACK ExtendedErrorCode = xErrorCode(NOTICE) | (2 << 8) + NOTICE_RBU ExtendedErrorCode = xErrorCode(NOTICE) | (3 << 8) + WARNING_AUTOINDEX ExtendedErrorCode = xErrorCode(WARNING) | (1 << 8) + AUTH_USER ExtendedErrorCode = xErrorCode(AUTH) | (1 << 8) +) + +// OpenFlag is a flag for the [OpenFlags] function. +// +// https://sqlite.org/c3ref/c_open_autoproxy.html +type OpenFlag uint32 + +const ( + OPEN_READONLY OpenFlag = 0x00000001 /* Ok for sqlite3_open_v2() */ + OPEN_READWRITE OpenFlag = 0x00000002 /* Ok for sqlite3_open_v2() */ + OPEN_CREATE OpenFlag = 0x00000004 /* Ok for sqlite3_open_v2() */ + OPEN_URI OpenFlag = 0x00000040 /* Ok for sqlite3_open_v2() */ + OPEN_MEMORY OpenFlag = 0x00000080 /* Ok for sqlite3_open_v2() */ + OPEN_NOMUTEX OpenFlag = 0x00008000 /* Ok for sqlite3_open_v2() */ + OPEN_FULLMUTEX OpenFlag = 0x00010000 /* Ok for sqlite3_open_v2() */ + OPEN_SHAREDCACHE OpenFlag = 0x00020000 /* Ok for sqlite3_open_v2() */ + OPEN_PRIVATECACHE OpenFlag = 0x00040000 /* Ok for sqlite3_open_v2() */ + OPEN_NOFOLLOW OpenFlag = 0x01000000 /* Ok for sqlite3_open_v2() */ + OPEN_EXRESCODE OpenFlag = 0x02000000 /* Extended result codes */ +) + +// PrepareFlag is a flag that can be passed to [Conn.PrepareFlags]. +// +// https://sqlite.org/c3ref/c_prepare_normalize.html +type PrepareFlag uint32 + +const ( + PREPARE_PERSISTENT PrepareFlag = 0x01 + PREPARE_NORMALIZE PrepareFlag = 0x02 + PREPARE_NO_VTAB PrepareFlag = 0x04 +) + +// FunctionFlag is a flag that can be passed to +// [Conn.CreateFunction] and [Conn.CreateWindowFunction]. +// +// https://sqlite.org/c3ref/c_deterministic.html +type FunctionFlag uint32 + +const ( + DETERMINISTIC FunctionFlag = 0x000000800 + DIRECTONLY FunctionFlag = 0x000080000 + SUBTYPE FunctionFlag = 0x000100000 + INNOCUOUS FunctionFlag = 0x000200000 + RESULT_SUBTYPE FunctionFlag = 0x001000000 +) + +// StmtStatus name counter values associated with the [Stmt.Status] method. +// +// https://sqlite.org/c3ref/c_stmtstatus_counter.html +type StmtStatus uint32 + +const ( + STMTSTATUS_FULLSCAN_STEP StmtStatus = 1 + STMTSTATUS_SORT StmtStatus = 2 + STMTSTATUS_AUTOINDEX StmtStatus = 3 + STMTSTATUS_VM_STEP StmtStatus = 4 + STMTSTATUS_REPREPARE StmtStatus = 5 + STMTSTATUS_RUN StmtStatus = 6 + STMTSTATUS_FILTER_MISS StmtStatus = 7 + STMTSTATUS_FILTER_HIT StmtStatus = 8 + STMTSTATUS_MEMUSED StmtStatus = 99 +) + +// DBConfig are the available database connection configuration options. +// +// https://sqlite.org/c3ref/c_dbconfig_defensive.html +type DBConfig uint32 + +const ( + // DBCONFIG_MAINDBNAME DBConfig = 1000 + // DBCONFIG_LOOKASIDE DBConfig = 1001 + DBCONFIG_ENABLE_FKEY DBConfig = 1002 + DBCONFIG_ENABLE_TRIGGER DBConfig = 1003 + DBCONFIG_ENABLE_FTS3_TOKENIZER DBConfig = 1004 + DBCONFIG_ENABLE_LOAD_EXTENSION DBConfig = 1005 + DBCONFIG_NO_CKPT_ON_CLOSE DBConfig = 1006 + DBCONFIG_ENABLE_QPSG DBConfig = 1007 + DBCONFIG_TRIGGER_EQP DBConfig = 1008 + DBCONFIG_RESET_DATABASE DBConfig = 1009 + DBCONFIG_DEFENSIVE DBConfig = 1010 + DBCONFIG_WRITABLE_SCHEMA DBConfig = 1011 + DBCONFIG_LEGACY_ALTER_TABLE DBConfig = 1012 + DBCONFIG_DQS_DML DBConfig = 1013 + DBCONFIG_DQS_DDL DBConfig = 1014 + DBCONFIG_ENABLE_VIEW DBConfig = 1015 + DBCONFIG_LEGACY_FILE_FORMAT DBConfig = 1016 + DBCONFIG_TRUSTED_SCHEMA DBConfig = 1017 + DBCONFIG_STMT_SCANSTATUS DBConfig = 1018 + DBCONFIG_REVERSE_SCANORDER DBConfig = 1019 +) + +// LimitCategory are the available run-time limit categories. +// +// https://sqlite.org/c3ref/c_limit_attached.html +type LimitCategory uint32 + +const ( + LIMIT_LENGTH LimitCategory = 0 + LIMIT_SQL_LENGTH LimitCategory = 1 + LIMIT_COLUMN LimitCategory = 2 + LIMIT_EXPR_DEPTH LimitCategory = 3 + LIMIT_COMPOUND_SELECT LimitCategory = 4 + LIMIT_VDBE_OP LimitCategory = 5 + LIMIT_FUNCTION_ARG LimitCategory = 6 + LIMIT_ATTACHED LimitCategory = 7 + LIMIT_LIKE_PATTERN_LENGTH LimitCategory = 8 + LIMIT_VARIABLE_NUMBER LimitCategory = 9 + LIMIT_TRIGGER_DEPTH LimitCategory = 10 + LIMIT_WORKER_THREADS LimitCategory = 11 +) + +// AuthorizerActionCode are the integer action codes +// that the authorizer callback may be passed. +// +// https://sqlite.org/c3ref/c_alter_table.html +type AuthorizerActionCode uint32 + +const ( + /***************************************************** 3rd ************ 4th ***********/ + AUTH_CREATE_INDEX AuthorizerActionCode = 1 /* Index Name Table Name */ + AUTH_CREATE_TABLE AuthorizerActionCode = 2 /* Table Name NULL */ + AUTH_CREATE_TEMP_INDEX AuthorizerActionCode = 3 /* Index Name Table Name */ + AUTH_CREATE_TEMP_TABLE AuthorizerActionCode = 4 /* Table Name NULL */ + AUTH_CREATE_TEMP_TRIGGER AuthorizerActionCode = 5 /* Trigger Name Table Name */ + AUTH_CREATE_TEMP_VIEW AuthorizerActionCode = 6 /* View Name NULL */ + AUTH_CREATE_TRIGGER AuthorizerActionCode = 7 /* Trigger Name Table Name */ + AUTH_CREATE_VIEW AuthorizerActionCode = 8 /* View Name NULL */ + AUTH_DELETE AuthorizerActionCode = 9 /* Table Name NULL */ + AUTH_DROP_INDEX AuthorizerActionCode = 10 /* Index Name Table Name */ + AUTH_DROP_TABLE AuthorizerActionCode = 11 /* Table Name NULL */ + AUTH_DROP_TEMP_INDEX AuthorizerActionCode = 12 /* Index Name Table Name */ + AUTH_DROP_TEMP_TABLE AuthorizerActionCode = 13 /* Table Name NULL */ + AUTH_DROP_TEMP_TRIGGER AuthorizerActionCode = 14 /* Trigger Name Table Name */ + AUTH_DROP_TEMP_VIEW AuthorizerActionCode = 15 /* View Name NULL */ + AUTH_DROP_TRIGGER AuthorizerActionCode = 16 /* Trigger Name Table Name */ + AUTH_DROP_VIEW AuthorizerActionCode = 17 /* View Name NULL */ + AUTH_INSERT AuthorizerActionCode = 18 /* Table Name NULL */ + AUTH_PRAGMA AuthorizerActionCode = 19 /* Pragma Name 1st arg or NULL */ + AUTH_READ AuthorizerActionCode = 20 /* Table Name Column Name */ + AUTH_SELECT AuthorizerActionCode = 21 /* NULL NULL */ + AUTH_TRANSACTION AuthorizerActionCode = 22 /* Operation NULL */ + AUTH_UPDATE AuthorizerActionCode = 23 /* Table Name Column Name */ + AUTH_ATTACH AuthorizerActionCode = 24 /* Filename NULL */ + AUTH_DETACH AuthorizerActionCode = 25 /* Database Name NULL */ + AUTH_ALTER_TABLE AuthorizerActionCode = 26 /* Database Name Table Name */ + AUTH_REINDEX AuthorizerActionCode = 27 /* Index Name NULL */ + AUTH_ANALYZE AuthorizerActionCode = 28 /* Table Name NULL */ + AUTH_CREATE_VTABLE AuthorizerActionCode = 29 /* Table Name Module Name */ + AUTH_DROP_VTABLE AuthorizerActionCode = 30 /* Table Name Module Name */ + AUTH_FUNCTION AuthorizerActionCode = 31 /* NULL Function Name */ + AUTH_SAVEPOINT AuthorizerActionCode = 32 /* Operation Savepoint Name */ + AUTH_COPY AuthorizerActionCode = 0 /* No longer used */ + AUTH_RECURSIVE AuthorizerActionCode = 33 /* NULL NULL */ +) + +// AuthorizerReturnCode are the integer codes +// that the authorizer callback may return. +// +// https://sqlite.org/c3ref/c_deny.html +type AuthorizerReturnCode uint32 + +const ( + AUTH_OK AuthorizerReturnCode = 0 + AUTH_DENY AuthorizerReturnCode = 1 /* Abort the SQL statement with an error */ + AUTH_IGNORE AuthorizerReturnCode = 2 /* Don't allow access, but don't generate an error */ +) + +// CheckpointMode are all the checkpoint mode values. +// +// https://sqlite.org/c3ref/c_checkpoint_full.html +type CheckpointMode uint32 + +const ( + CHECKPOINT_PASSIVE CheckpointMode = 0 /* Do as much as possible w/o blocking */ + CHECKPOINT_FULL CheckpointMode = 1 /* Wait for writers, then checkpoint */ + CHECKPOINT_RESTART CheckpointMode = 2 /* Like FULL but wait for readers */ + CHECKPOINT_TRUNCATE CheckpointMode = 3 /* Like RESTART but also truncate WAL */ +) + +// TxnState are the allowed return values from [Conn.TxnState]. +// +// https://sqlite.org/c3ref/c_txn_none.html +type TxnState uint32 + +const ( + TXN_NONE TxnState = 0 + TXN_READ TxnState = 1 + TXN_WRITE TxnState = 2 +) + +// Datatype is a fundamental datatype of SQLite. +// +// https://sqlite.org/c3ref/c_blob.html +type Datatype uint32 + +const ( + INTEGER Datatype = 1 + FLOAT Datatype = 2 + TEXT Datatype = 3 + BLOB Datatype = 4 + NULL Datatype = 5 +) + +// String implements the [fmt.Stringer] interface. +func (t Datatype) String() string { + const name = "INTEGERFLOATEXTBLOBNULL" + switch t { + case INTEGER: + return name[0:7] + case FLOAT: + return name[7:12] + case TEXT: + return name[11:15] + case BLOB: + return name[15:19] + case NULL: + return name[19:23] + } + return strconv.FormatUint(uint64(t), 10) +} diff --git a/vendor/github.com/ncruces/go-sqlite3/context.go b/vendor/github.com/ncruces/go-sqlite3/context.go new file mode 100644 index 000000000..8d7604c66 --- /dev/null +++ b/vendor/github.com/ncruces/go-sqlite3/context.go @@ -0,0 +1,229 @@ +package sqlite3 + +import ( + "encoding/json" + "errors" + "math" + "time" + + "github.com/ncruces/go-sqlite3/internal/util" +) + +// Context is the context in which an SQL function executes. +// An SQLite [Context] is in no way related to a Go [context.Context]. +// +// https://sqlite.org/c3ref/context.html +type Context struct { + c *Conn + handle uint32 +} + +// Conn returns the database connection of the +// [Conn.CreateFunction] or [Conn.CreateWindowFunction] +// routines that originally registered the application defined function. +// +// https://sqlite.org/c3ref/context_db_handle.html +func (ctx Context) Conn() *Conn { + return ctx.c +} + +// SetAuxData saves metadata for argument n of the function. +// +// https://sqlite.org/c3ref/get_auxdata.html +func (ctx Context) SetAuxData(n int, data any) { + ptr := util.AddHandle(ctx.c.ctx, data) + ctx.c.call("sqlite3_set_auxdata_go", uint64(ctx.handle), uint64(n), uint64(ptr)) +} + +// GetAuxData returns metadata for argument n of the function. +// +// https://sqlite.org/c3ref/get_auxdata.html +func (ctx Context) GetAuxData(n int) any { + ptr := uint32(ctx.c.call("sqlite3_get_auxdata", uint64(ctx.handle), uint64(n))) + return util.GetHandle(ctx.c.ctx, ptr) +} + +// ResultBool sets the result of the function to a bool. +// SQLite does not have a separate boolean storage class. +// Instead, boolean values are stored as integers 0 (false) and 1 (true). +// +// https://sqlite.org/c3ref/result_blob.html +func (ctx Context) ResultBool(value bool) { + var i int64 + if value { + i = 1 + } + ctx.ResultInt64(i) +} + +// ResultInt sets the result of the function to an int. +// +// https://sqlite.org/c3ref/result_blob.html +func (ctx Context) ResultInt(value int) { + ctx.ResultInt64(int64(value)) +} + +// ResultInt64 sets the result of the function to an int64. +// +// https://sqlite.org/c3ref/result_blob.html +func (ctx Context) ResultInt64(value int64) { + ctx.c.call("sqlite3_result_int64", + uint64(ctx.handle), uint64(value)) +} + +// ResultFloat sets the result of the function to a float64. +// +// https://sqlite.org/c3ref/result_blob.html +func (ctx Context) ResultFloat(value float64) { + ctx.c.call("sqlite3_result_double", + uint64(ctx.handle), math.Float64bits(value)) +} + +// ResultText sets the result of the function to a string. +// +// https://sqlite.org/c3ref/result_blob.html +func (ctx Context) ResultText(value string) { + ptr := ctx.c.newString(value) + ctx.c.call("sqlite3_result_text64", + uint64(ctx.handle), uint64(ptr), uint64(len(value)), + uint64(ctx.c.freer), _UTF8) +} + +// ResultRawText sets the text result of the function to a []byte. +// +// https://sqlite.org/c3ref/result_blob.html +func (ctx Context) ResultRawText(value []byte) { + ptr := ctx.c.newBytes(value) + ctx.c.call("sqlite3_result_text64", + uint64(ctx.handle), uint64(ptr), uint64(len(value)), + uint64(ctx.c.freer), _UTF8) +} + +// ResultBlob sets the result of the function to a []byte. +// Returning a nil slice is the same as calling [Context.ResultNull]. +// +// https://sqlite.org/c3ref/result_blob.html +func (ctx Context) ResultBlob(value []byte) { + ptr := ctx.c.newBytes(value) + ctx.c.call("sqlite3_result_blob64", + uint64(ctx.handle), uint64(ptr), uint64(len(value)), + uint64(ctx.c.freer)) +} + +// ResultZeroBlob sets the result of the function to a zero-filled, length n BLOB. +// +// https://sqlite.org/c3ref/result_blob.html +func (ctx Context) ResultZeroBlob(n int64) { + ctx.c.call("sqlite3_result_zeroblob64", + uint64(ctx.handle), uint64(n)) +} + +// ResultNull sets the result of the function to NULL. +// +// https://sqlite.org/c3ref/result_blob.html +func (ctx Context) ResultNull() { + ctx.c.call("sqlite3_result_null", + uint64(ctx.handle)) +} + +// ResultTime sets the result of the function to a [time.Time]. +// +// https://sqlite.org/c3ref/result_blob.html +func (ctx Context) ResultTime(value time.Time, format TimeFormat) { + if format == TimeFormatDefault { + ctx.resultRFC3339Nano(value) + return + } + switch v := format.Encode(value).(type) { + case string: + ctx.ResultText(v) + case int64: + ctx.ResultInt64(v) + case float64: + ctx.ResultFloat(v) + default: + panic(util.AssertErr()) + } +} + +func (ctx Context) resultRFC3339Nano(value time.Time) { + const maxlen = uint64(len(time.RFC3339Nano)) + 5 + + ptr := ctx.c.new(maxlen) + buf := util.View(ctx.c.mod, ptr, maxlen) + buf = value.AppendFormat(buf[:0], time.RFC3339Nano) + + ctx.c.call("sqlite3_result_text64", + uint64(ctx.handle), uint64(ptr), uint64(len(buf)), + uint64(ctx.c.freer), _UTF8) +} + +// ResultPointer sets the result of the function to NULL, just like [Context.ResultNull], +// except that it also associates ptr with that NULL value such that it can be retrieved +// within an application-defined SQL function using [Value.Pointer]. +// +// https://sqlite.org/c3ref/result_blob.html +func (ctx Context) ResultPointer(ptr any) { + valPtr := util.AddHandle(ctx.c.ctx, ptr) + ctx.c.call("sqlite3_result_pointer_go", uint64(valPtr)) +} + +// ResultJSON sets the result of the function to the JSON encoding of value. +// +// https://sqlite.org/c3ref/result_blob.html +func (ctx Context) ResultJSON(value any) { + data, err := json.Marshal(value) + if err != nil { + ctx.ResultError(err) + return + } + ctx.ResultRawText(data) +} + +// ResultValue sets the result of the function to a copy of [Value]. +// +// https://sqlite.org/c3ref/result_blob.html +func (ctx Context) ResultValue(value Value) { + if value.c != ctx.c { + ctx.ResultError(MISUSE) + return + } + ctx.c.call("sqlite3_result_value", + uint64(ctx.handle), uint64(value.handle)) +} + +// ResultError sets the result of the function an error. +// +// https://sqlite.org/c3ref/result_blob.html +func (ctx Context) ResultError(err error) { + if errors.Is(err, NOMEM) { + ctx.c.call("sqlite3_result_error_nomem", uint64(ctx.handle)) + return + } + + if errors.Is(err, TOOBIG) { + ctx.c.call("sqlite3_result_error_toobig", uint64(ctx.handle)) + return + } + + msg, code := errorCode(err, _OK) + if msg != "" { + defer ctx.c.arena.mark()() + ptr := ctx.c.arena.string(msg) + ctx.c.call("sqlite3_result_error", + uint64(ctx.handle), uint64(ptr), uint64(len(msg))) + } + if code != _OK { + ctx.c.call("sqlite3_result_error_code", + uint64(ctx.handle), uint64(code)) + } +} + +// VTabNoChange may return true if a column is being fetched as part +// of an update during which the column value will not change. +// +// https://sqlite.org/c3ref/vtab_nochange.html +func (ctx Context) VTabNoChange() bool { + r := ctx.c.call("sqlite3_vtab_nochange", uint64(ctx.handle)) + return r != 0 +} diff --git a/vendor/github.com/ncruces/go-sqlite3/driver/driver.go b/vendor/github.com/ncruces/go-sqlite3/driver/driver.go new file mode 100644 index 000000000..b496f76ec --- /dev/null +++ b/vendor/github.com/ncruces/go-sqlite3/driver/driver.go @@ -0,0 +1,579 @@ +// Package driver provides a database/sql driver for SQLite. +// +// Importing package driver registers a [database/sql] driver named "sqlite3". +// You may also need to import package embed. +// +// import _ "github.com/ncruces/go-sqlite3/driver" +// import _ "github.com/ncruces/go-sqlite3/embed" +// +// The data source name for "sqlite3" databases can be a filename or a "file:" [URI]. +// +// The [TRANSACTION] mode can be specified using "_txlock": +// +// sql.Open("sqlite3", "file:demo.db?_txlock=immediate") +// +// Possible values are: "deferred", "immediate", "exclusive". +// A [read-only] transaction is always "deferred", regardless of "_txlock". +// +// The time encoding/decoding format can be specified using "_timefmt": +// +// sql.Open("sqlite3", "file:demo.db?_timefmt=sqlite") +// +// Possible values are: "auto" (the default), "sqlite", "rfc3339"; +// "auto" encodes as RFC 3339 and decodes any [format] supported by SQLite; +// "sqlite" encodes as SQLite and decodes any [format] supported by SQLite; +// "rfc3339" encodes and decodes RFC 3339 only. +// +// [PRAGMA] statements can be specified using "_pragma": +// +// sql.Open("sqlite3", "file:demo.db?_pragma=busy_timeout(10000)") +// +// If no PRAGMAs are specified, a busy timeout of 1 minute is set. +// +// Order matters: +// busy timeout and locking mode should be the first PRAGMAs set, in that order. +// +// [URI]: https://sqlite.org/uri.html +// [PRAGMA]: https://sqlite.org/pragma.html +// [format]: https://sqlite.org/lang_datefunc.html#time_values +// [TRANSACTION]: https://sqlite.org/lang_transaction.html#deferred_immediate_and_exclusive_transactions +// [read-only]: https://pkg.go.dev/database/sql#TxOptions +package driver + +import ( + "context" + "database/sql" + "database/sql/driver" + "errors" + "fmt" + "io" + "net/url" + "strings" + "time" + "unsafe" + + "github.com/ncruces/go-sqlite3" + "github.com/ncruces/go-sqlite3/internal/util" +) + +// This variable can be replaced with -ldflags: +// +// go build -ldflags="-X github.com/ncruces/go-sqlite3/driver.driverName=sqlite" +var driverName = "sqlite3" + +func init() { + if driverName != "" { + sql.Register(driverName, &SQLite{}) + } +} + +// 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 [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 { + return nil, err + } + return sql.OpenDB(c), nil +} + +// SQLite implements [database/sql/driver.Driver]. +type SQLite struct { + // 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.Driver]. +func (d *SQLite) Open(name string) (driver.Conn, error) { + c, err := d.newConnector(name) + if err != nil { + return nil, err + } + return c.Connect(context.Background()) +} + +// OpenConnector implements [database/sql/driver.DriverContext]. +func (d *SQLite) OpenConnector(name string) (driver.Connector, error) { + return d.newConnector(name) +} + +func (d *SQLite) newConnector(name string) (*connector, error) { + c := connector{driver: d, name: name} + + var txlock, timefmt string + if strings.HasPrefix(name, "file:") { + if _, after, ok := strings.Cut(name, "?"); ok { + query, err := url.ParseQuery(after) + if err != nil { + return nil, err + } + txlock = query.Get("_txlock") + timefmt = query.Get("_timefmt") + c.pragmas = query.Has("_pragma") + } + } + + switch txlock { + case "": + c.txBegin = "BEGIN" + case "deferred", "immediate", "exclusive": + c.txBegin = "BEGIN " + txlock + default: + return nil, fmt.Errorf("sqlite3: invalid _txlock: %s", txlock) + } + + switch timefmt { + case "": + c.tmRead = sqlite3.TimeFormatAuto + c.tmWrite = sqlite3.TimeFormatDefault + case "sqlite": + c.tmRead = sqlite3.TimeFormatAuto + c.tmWrite = sqlite3.TimeFormat3 + case "rfc3339": + c.tmRead = sqlite3.TimeFormatDefault + c.tmWrite = sqlite3.TimeFormatDefault + default: + c.tmRead = sqlite3.TimeFormat(timefmt) + c.tmWrite = sqlite3.TimeFormat(timefmt) + } + return &c, nil +} + +type connector struct { + driver *SQLite + name string + txBegin string + tmRead sqlite3.TimeFormat + tmWrite sqlite3.TimeFormat + pragmas bool +} + +func (n *connector) Driver() driver.Driver { + return n.driver +} + +func (n *connector) Connect(ctx context.Context) (_ driver.Conn, err error) { + c := &conn{ + txBegin: n.txBegin, + tmRead: n.tmRead, + tmWrite: n.tmWrite, + } + + c.Conn, err = sqlite3.Open(n.name) + if err != nil { + return nil, err + } + defer func() { + if err != nil { + c.Close() + } + }() + + old := c.Conn.SetInterrupt(ctx) + defer c.Conn.SetInterrupt(old) + + if !n.pragmas { + err = c.Conn.BusyTimeout(60 * time.Second) + if err != nil { + return nil, err + } + } + if n.driver.Init != nil { + err = n.driver.Init(c.Conn) + if err != nil { + return nil, err + } + } + if n.pragmas || n.driver.Init != nil { + s, _, err := c.Conn.Prepare(`PRAGMA query_only`) + if err != nil { + return nil, err + } + if s.Step() && s.ColumnBool(0) { + c.readOnly = '1' + } else { + c.readOnly = '0' + } + err = s.Close() + if err != nil { + return nil, err + } + } + return c, nil +} + +type conn struct { + *sqlite3.Conn + txBegin string + txCommit string + txRollback string + tmRead sqlite3.TimeFormat + tmWrite sqlite3.TimeFormat + readOnly byte +} + +var ( + // Ensure these interfaces are implemented: + _ driver.ConnPrepareContext = &conn{} + _ driver.ExecerContext = &conn{} + _ driver.ConnBeginTx = &conn{} + _ sqlite3.DriverConn = &conn{} +) + +func (c *conn) Raw() *sqlite3.Conn { + return c.Conn +} + +func (c *conn) Begin() (driver.Tx, error) { + return c.BeginTx(context.Background(), driver.TxOptions{}) +} + +func (c *conn) BeginTx(ctx context.Context, opts driver.TxOptions) (driver.Tx, error) { + txBegin := c.txBegin + c.txCommit = `COMMIT` + c.txRollback = `ROLLBACK` + + if opts.ReadOnly { + txBegin = ` + BEGIN deferred; + PRAGMA query_only=on` + c.txRollback = ` + ROLLBACK; + PRAGMA query_only=` + string(c.readOnly) + c.txCommit = c.txRollback + } + + switch opts.Isolation { + default: + return nil, util.IsolationErr + case + driver.IsolationLevel(sql.LevelDefault), + driver.IsolationLevel(sql.LevelSerializable): + break + } + + old := c.Conn.SetInterrupt(ctx) + defer c.Conn.SetInterrupt(old) + + err := c.Conn.Exec(txBegin) + if err != nil { + return nil, err + } + return c, nil +} + +func (c *conn) Commit() error { + err := c.Conn.Exec(c.txCommit) + if err != nil && !c.Conn.GetAutocommit() { + c.Rollback() + } + return err +} + +func (c *conn) Rollback() error { + err := c.Conn.Exec(c.txRollback) + if errors.Is(err, sqlite3.INTERRUPT) { + old := c.Conn.SetInterrupt(context.Background()) + defer c.Conn.SetInterrupt(old) + err = c.Conn.Exec(c.txRollback) + } + return err +} + +func (c *conn) Prepare(query string) (driver.Stmt, error) { + return c.PrepareContext(context.Background(), query) +} + +func (c *conn) PrepareContext(ctx context.Context, query string) (driver.Stmt, error) { + old := c.Conn.SetInterrupt(ctx) + defer c.Conn.SetInterrupt(old) + + s, tail, err := c.Conn.Prepare(query) + if err != nil { + return nil, err + } + if tail != "" { + s.Close() + return nil, util.TailErr + } + return &stmt{Stmt: s, tmRead: c.tmRead, tmWrite: c.tmWrite}, nil +} + +func (c *conn) ExecContext(ctx context.Context, query string, args []driver.NamedValue) (driver.Result, error) { + if len(args) != 0 { + // Slow path. + return nil, driver.ErrSkip + } + + if savept, ok := ctx.(*saveptCtx); ok { + // Called from driver.Savepoint. + savept.Savepoint = c.Conn.Savepoint() + return resultRowsAffected(0), nil + } + + old := c.Conn.SetInterrupt(ctx) + defer c.Conn.SetInterrupt(old) + + err := c.Conn.Exec(query) + if err != nil { + return nil, err + } + + return newResult(c.Conn), nil +} + +func (c *conn) CheckNamedValue(arg *driver.NamedValue) error { + return nil +} + +type stmt struct { + *sqlite3.Stmt + tmWrite sqlite3.TimeFormat + tmRead sqlite3.TimeFormat +} + +var ( + // Ensure these interfaces are implemented: + _ driver.StmtExecContext = &stmt{} + _ driver.StmtQueryContext = &stmt{} + _ driver.NamedValueChecker = &stmt{} +) + +func (s *stmt) NumInput() int { + n := s.Stmt.BindCount() + for i := 1; i <= n; i++ { + if s.Stmt.BindName(i) != "" { + return -1 + } + } + return n +} + +// Deprecated: use ExecContext instead. +func (s *stmt) Exec(args []driver.Value) (driver.Result, error) { + return s.ExecContext(context.Background(), namedValues(args)) +} + +// Deprecated: use QueryContext instead. +func (s *stmt) Query(args []driver.Value) (driver.Rows, error) { + return s.QueryContext(context.Background(), namedValues(args)) +} + +func (s *stmt) ExecContext(ctx context.Context, args []driver.NamedValue) (driver.Result, error) { + err := s.setupBindings(args) + if err != nil { + return nil, err + } + + old := s.Stmt.Conn().SetInterrupt(ctx) + defer s.Stmt.Conn().SetInterrupt(old) + + err = s.Stmt.Exec() + if err != nil { + return nil, err + } + + return newResult(s.Stmt.Conn()), nil +} + +func (s *stmt) QueryContext(ctx context.Context, args []driver.NamedValue) (driver.Rows, error) { + err := s.setupBindings(args) + if err != nil { + return nil, err + } + return &rows{ctx: ctx, stmt: s}, nil +} + +func (s *stmt) setupBindings(args []driver.NamedValue) error { + err := s.Stmt.ClearBindings() + if err != nil { + return err + } + + var ids [3]int + for _, arg := range args { + ids := ids[:0] + if arg.Name == "" { + ids = append(ids, arg.Ordinal) + } else { + for _, prefix := range []string{":", "@", "$"} { + if id := s.Stmt.BindIndex(prefix + arg.Name); id != 0 { + ids = append(ids, id) + } + } + } + + for _, id := range ids { + switch a := arg.Value.(type) { + case bool: + err = s.Stmt.BindBool(id, a) + case int: + err = s.Stmt.BindInt(id, a) + case int64: + err = s.Stmt.BindInt64(id, a) + case float64: + err = s.Stmt.BindFloat(id, a) + case string: + err = s.Stmt.BindText(id, a) + case []byte: + err = s.Stmt.BindBlob(id, a) + case sqlite3.ZeroBlob: + err = s.Stmt.BindZeroBlob(id, int64(a)) + case time.Time: + err = s.Stmt.BindTime(id, a, s.tmWrite) + case util.JSON: + err = s.Stmt.BindJSON(id, a.Value) + case util.PointerUnwrap: + err = s.Stmt.BindPointer(id, util.UnwrapPointer(a)) + case nil: + err = s.Stmt.BindNull(id) + default: + panic(util.AssertErr()) + } + } + if err != nil { + return err + } + } + return nil +} + +func (s *stmt) CheckNamedValue(arg *driver.NamedValue) error { + switch arg.Value.(type) { + case bool, int, int64, float64, string, []byte, + time.Time, sqlite3.ZeroBlob, + util.JSON, util.PointerUnwrap, + nil: + return nil + default: + return driver.ErrSkip + } +} + +func newResult(c *sqlite3.Conn) driver.Result { + rows := c.Changes() + if rows != 0 { + id := c.LastInsertRowID() + if id != 0 { + return result{id, rows} + } + } + return resultRowsAffected(rows) +} + +type result struct{ lastInsertId, rowsAffected int64 } + +func (r result) LastInsertId() (int64, error) { + return r.lastInsertId, nil +} + +func (r result) RowsAffected() (int64, error) { + return r.rowsAffected, nil +} + +type resultRowsAffected int64 + +func (r resultRowsAffected) LastInsertId() (int64, error) { + return 0, nil +} + +func (r resultRowsAffected) RowsAffected() (int64, error) { + return int64(r), nil +} + +type rows struct { + ctx context.Context + *stmt + names []string + types []string +} + +func (r *rows) Close() error { + r.Stmt.ClearBindings() + return r.Stmt.Reset() +} + +func (r *rows) Columns() []string { + if r.names == nil { + count := r.Stmt.ColumnCount() + r.names = make([]string, count) + for i := range r.names { + r.names[i] = r.Stmt.ColumnName(i) + } + } + return r.names +} + +func (r *rows) declType(index int) string { + if r.types == nil { + count := r.Stmt.ColumnCount() + r.types = make([]string, count) + for i := range r.types { + r.types[i] = strings.ToUpper(r.Stmt.ColumnDeclType(i)) + } + } + return r.types[index] +} + +func (r *rows) ColumnTypeDatabaseTypeName(index int) string { + decltype := r.declType(index) + if len := len(decltype); len > 0 && decltype[len-1] == ')' { + if i := strings.LastIndexByte(decltype, '('); i >= 0 { + decltype = decltype[:i] + } + } + return strings.TrimSpace(decltype) +} + +func (r *rows) Next(dest []driver.Value) error { + old := r.Stmt.Conn().SetInterrupt(r.ctx) + defer r.Stmt.Conn().SetInterrupt(old) + + if !r.Stmt.Step() { + if err := r.Stmt.Err(); err != nil { + return err + } + return io.EOF + } + + data := unsafe.Slice((*any)(unsafe.SliceData(dest)), len(dest)) + err := r.Stmt.Columns(data) + for i := range dest { + if t, ok := r.decodeTime(i, dest[i]); ok { + dest[i] = t + continue + } + if s, ok := dest[i].(string); ok { + t, ok := maybeTime(s) + if ok { + dest[i] = t + } + } + } + return err +} + +func (r *rows) decodeTime(i int, v any) (_ time.Time, _ bool) { + if r.tmRead == sqlite3.TimeFormatDefault { + return + } + switch r.declType(i) { + case "DATE", "TIME", "DATETIME", "TIMESTAMP": + // maybe + default: + return + } + switch v.(type) { + case int64, float64, string: + // maybe + default: + return + } + t, err := r.tmRead.Decode(v) + return t, err == nil +} diff --git a/vendor/github.com/ncruces/go-sqlite3/driver/savepoint.go b/vendor/github.com/ncruces/go-sqlite3/driver/savepoint.go new file mode 100644 index 000000000..60aa6b991 --- /dev/null +++ b/vendor/github.com/ncruces/go-sqlite3/driver/savepoint.go @@ -0,0 +1,27 @@ +package driver + +import ( + "database/sql" + "time" + + "github.com/ncruces/go-sqlite3" +) + +// Savepoint establishes a new transaction savepoint. +// +// https://sqlite.org/lang_savepoint.html +func Savepoint(tx *sql.Tx) sqlite3.Savepoint { + var ctx saveptCtx + tx.ExecContext(&ctx, "") + return ctx.Savepoint +} + +type saveptCtx struct{ sqlite3.Savepoint } + +func (*saveptCtx) Deadline() (deadline time.Time, ok bool) { return } + +func (*saveptCtx) Done() <-chan struct{} { return nil } + +func (*saveptCtx) Err() error { return nil } + +func (*saveptCtx) Value(key any) any { return nil } diff --git a/vendor/github.com/ncruces/go-sqlite3/driver/time.go b/vendor/github.com/ncruces/go-sqlite3/driver/time.go new file mode 100644 index 000000000..630a5b10b --- /dev/null +++ b/vendor/github.com/ncruces/go-sqlite3/driver/time.go @@ -0,0 +1,31 @@ +package driver + +import ( + "time" +) + +// Convert a string in [time.RFC3339Nano] format into a [time.Time] +// if it roundtrips back to the same string. +// This way times can be persisted to, and recovered from, the database, +// but if a string is needed, [database/sql] will recover the same string. +func maybeTime(text string) (_ time.Time, _ bool) { + // Weed out (some) values that can't possibly be + // [time.RFC3339Nano] timestamps. + if len(text) < len("2006-01-02T15:04:05Z") { + return + } + if len(text) > len(time.RFC3339Nano) { + return + } + if text[4] != '-' || text[10] != 'T' || text[16] != ':' { + return + } + + // Slow path. + var buf [len(time.RFC3339Nano)]byte + date, err := time.Parse(time.RFC3339Nano, text) + if err == nil && text == string(date.AppendFormat(buf[:0], time.RFC3339Nano)) { + return date, true + } + return +} diff --git a/vendor/github.com/ncruces/go-sqlite3/driver/util.go b/vendor/github.com/ncruces/go-sqlite3/driver/util.go new file mode 100644 index 000000000..033841157 --- /dev/null +++ b/vendor/github.com/ncruces/go-sqlite3/driver/util.go @@ -0,0 +1,14 @@ +package driver + +import "database/sql/driver" + +func namedValues(args []driver.Value) []driver.NamedValue { + named := make([]driver.NamedValue, len(args)) + for i, v := range args { + named[i] = driver.NamedValue{ + Ordinal: i + 1, + Value: v, + } + } + return named +} diff --git a/vendor/github.com/ncruces/go-sqlite3/embed/README.md b/vendor/github.com/ncruces/go-sqlite3/embed/README.md new file mode 100644 index 000000000..400fe870a --- /dev/null +++ b/vendor/github.com/ncruces/go-sqlite3/embed/README.md @@ -0,0 +1,27 @@ +# Embeddable Wasm build of SQLite + +This folder includes an embeddable Wasm build of SQLite 3.46.0 for use with +[`github.com/ncruces/go-sqlite3`](https://pkg.go.dev/github.com/ncruces/go-sqlite3). + +The following optional features are compiled in: +- [math functions](https://sqlite.org/lang_mathfunc.html) +- [FTS5](https://sqlite.org/fts5.html) +- [JSON](https://sqlite.org/json1.html) +- [R*Tree](https://sqlite.org/rtree.html) +- [GeoPoly](https://sqlite.org/geopoly.html) +- [soundex](https://sqlite.org/lang_corefunc.html#soundex) +- [stat4](https://sqlite.org/compile.html#enable_stat4) +- [base64](https://github.com/sqlite/sqlite/blob/master/ext/misc/base64.c) +- [decimal](https://github.com/sqlite/sqlite/blob/master/ext/misc/decimal.c) +- [ieee754](https://github.com/sqlite/sqlite/blob/master/ext/misc/ieee754.c) +- [regexp](https://github.com/sqlite/sqlite/blob/master/ext/misc/regexp.c) +- [series](https://github.com/sqlite/sqlite/blob/master/ext/misc/series.c) +- [uint](https://github.com/sqlite/sqlite/blob/master/ext/misc/uint.c) +- [uuid](https://github.com/sqlite/sqlite/blob/master/ext/misc/uuid.c) +- [time](../sqlite3/time.c) + +See the [configuration options](../sqlite3/sqlite_cfg.h), +and [patches](../sqlite3) applied. + +Built using [`wasi-sdk`](https://github.com/WebAssembly/wasi-sdk), +and [`binaryen`](https://github.com/WebAssembly/binaryen).
\ No newline at end of file diff --git a/vendor/github.com/ncruces/go-sqlite3/embed/build.sh b/vendor/github.com/ncruces/go-sqlite3/embed/build.sh new file mode 100644 index 000000000..abe5e60c4 --- /dev/null +++ b/vendor/github.com/ncruces/go-sqlite3/embed/build.sh @@ -0,0 +1,32 @@ +#!/usr/bin/env bash +set -euo pipefail + +cd -P -- "$(dirname -- "$0")" + +ROOT=../ +BINARYEN="$ROOT/tools/binaryen-version_117/bin" +WASI_SDK="$ROOT/tools/wasi-sdk-22.0/bin" + +"$WASI_SDK/clang" --target=wasm32-wasi -std=c17 -flto -g0 -O2 \ + -Wall -Wextra -Wno-unused-parameter -Wno-unused-function \ + -o sqlite3.wasm "$ROOT/sqlite3/main.c" \ + -I"$ROOT/sqlite3" \ + -mexec-model=reactor \ + -msimd128 -mmutable-globals \ + -mbulk-memory -mreference-types \ + -mnontrapping-fptoint -msign-ext \ + -fno-stack-protector -fno-stack-clash-protection \ + -Wl,--initial-memory=327680 \ + -Wl,--stack-first \ + -Wl,--import-undefined \ + -D_HAVE_SQLITE_CONFIG_H \ + -DSQLITE_CUSTOM_INCLUDE=sqlite_opt.h \ + $(awk '{print "-Wl,--export="$0}' exports.txt) + +trap 'rm -f sqlite3.tmp' EXIT +"$BINARYEN/wasm-ctor-eval" -g -c _initialize sqlite3.wasm -o sqlite3.tmp +"$BINARYEN/wasm-opt" -g --strip --strip-producers -c -O3 \ + sqlite3.tmp -o sqlite3.wasm \ + --enable-simd --enable-mutable-globals --enable-multivalue \ + --enable-bulk-memory --enable-reference-types \ + --enable-nontrapping-float-to-int --enable-sign-ext
\ No newline at end of file diff --git a/vendor/github.com/ncruces/go-sqlite3/embed/exports.txt b/vendor/github.com/ncruces/go-sqlite3/embed/exports.txt new file mode 100644 index 000000000..b3cb1581c --- /dev/null +++ b/vendor/github.com/ncruces/go-sqlite3/embed/exports.txt @@ -0,0 +1,130 @@ +aligned_alloc +free +malloc +malloc_destructor +sqlite3_anycollseq_init +sqlite3_autovacuum_pages_go +sqlite3_backup_finish +sqlite3_backup_init +sqlite3_backup_pagecount +sqlite3_backup_remaining +sqlite3_backup_step +sqlite3_bind_blob64 +sqlite3_bind_double +sqlite3_bind_int64 +sqlite3_bind_null +sqlite3_bind_parameter_count +sqlite3_bind_parameter_index +sqlite3_bind_parameter_name +sqlite3_bind_pointer_go +sqlite3_bind_text64 +sqlite3_bind_value +sqlite3_bind_zeroblob64 +sqlite3_blob_bytes +sqlite3_blob_close +sqlite3_blob_open +sqlite3_blob_read +sqlite3_blob_reopen +sqlite3_blob_write +sqlite3_busy_handler_go +sqlite3_busy_timeout +sqlite3_changes64 +sqlite3_clear_bindings +sqlite3_close +sqlite3_close_v2 +sqlite3_collation_needed_go +sqlite3_column_blob +sqlite3_column_bytes +sqlite3_column_count +sqlite3_column_database_name +sqlite3_column_decltype +sqlite3_column_double +sqlite3_column_int64 +sqlite3_column_name +sqlite3_column_origin_name +sqlite3_column_table_name +sqlite3_column_text +sqlite3_column_type +sqlite3_column_value +sqlite3_columns_go +sqlite3_commit_hook_go +sqlite3_config_log_go +sqlite3_create_aggregate_function_go +sqlite3_create_collation_go +sqlite3_create_function_go +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 +sqlite3_declare_vtab +sqlite3_errcode +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 +sqlite3_interrupt +sqlite3_last_insert_rowid +sqlite3_limit +sqlite3_open_v2 +sqlite3_overload_function +sqlite3_prepare_v3 +sqlite3_progress_handler_go +sqlite3_reset +sqlite3_result_blob64 +sqlite3_result_double +sqlite3_result_error +sqlite3_result_error_code +sqlite3_result_error_nomem +sqlite3_result_error_toobig +sqlite3_result_int64 +sqlite3_result_null +sqlite3_result_pointer_go +sqlite3_result_text64 +sqlite3_result_value +sqlite3_result_zeroblob64 +sqlite3_rollback_hook_go +sqlite3_set_authorizer_go +sqlite3_set_auxdata_go +sqlite3_set_last_insert_rowid +sqlite3_step +sqlite3_stmt_busy +sqlite3_stmt_readonly +sqlite3_stmt_status +sqlite3_total_changes64 +sqlite3_txn_state +sqlite3_update_hook_go +sqlite3_uri_key +sqlite3_uri_parameter +sqlite3_value_blob +sqlite3_value_bytes +sqlite3_value_double +sqlite3_value_dup +sqlite3_value_free +sqlite3_value_int64 +sqlite3_value_nochange +sqlite3_value_numeric_type +sqlite3_value_pointer_go +sqlite3_value_text +sqlite3_value_type +sqlite3_vtab_collation +sqlite3_vtab_config_go +sqlite3_vtab_distinct +sqlite3_vtab_in +sqlite3_vtab_in_first +sqlite3_vtab_in_next +sqlite3_vtab_nochange +sqlite3_vtab_on_conflict +sqlite3_vtab_rhs_value +sqlite3_wal_autocheckpoint +sqlite3_wal_checkpoint_v2 +sqlite3_wal_hook_go
\ No newline at end of file diff --git a/vendor/github.com/ncruces/go-sqlite3/embed/init.go b/vendor/github.com/ncruces/go-sqlite3/embed/init.go new file mode 100644 index 000000000..da527abd0 --- /dev/null +++ b/vendor/github.com/ncruces/go-sqlite3/embed/init.go @@ -0,0 +1,20 @@ +// Package embed embeds SQLite into your application. +// +// Importing package embed initializes the [sqlite3.Binary] variable +// with an appropriate build of SQLite: +// +// import _ "github.com/ncruces/go-sqlite3/embed" +package embed + +import ( + _ "embed" + + "github.com/ncruces/go-sqlite3" +) + +//go:embed sqlite3.wasm +var binary []byte + +func init() { + sqlite3.Binary = binary +} diff --git a/vendor/github.com/ncruces/go-sqlite3/embed/sqlite3.wasm b/vendor/github.com/ncruces/go-sqlite3/embed/sqlite3.wasm Binary files differnew file mode 100644 index 000000000..2689f773a --- /dev/null +++ b/vendor/github.com/ncruces/go-sqlite3/embed/sqlite3.wasm diff --git a/vendor/github.com/ncruces/go-sqlite3/error.go b/vendor/github.com/ncruces/go-sqlite3/error.go new file mode 100644 index 000000000..71238ef12 --- /dev/null +++ b/vendor/github.com/ncruces/go-sqlite3/error.go @@ -0,0 +1,162 @@ +package sqlite3 + +import ( + "errors" + "strconv" + "strings" + + "github.com/ncruces/go-sqlite3/internal/util" +) + +// Error wraps an SQLite Error Code. +// +// https://sqlite.org/c3ref/errcode.html +type Error struct { + str string + msg string + sql string + code uint64 +} + +// Code returns the primary error code for this error. +// +// https://sqlite.org/rescode.html +func (e *Error) Code() ErrorCode { + return ErrorCode(e.code) +} + +// ExtendedCode returns the extended error code for this error. +// +// https://sqlite.org/rescode.html +func (e *Error) ExtendedCode() ExtendedErrorCode { + return ExtendedErrorCode(e.code) +} + +// Error implements the error interface. +func (e *Error) Error() string { + var b strings.Builder + b.WriteString("sqlite3: ") + + if e.str != "" { + b.WriteString(e.str) + } else { + b.WriteString(strconv.Itoa(int(e.code))) + } + + if e.msg != "" { + b.WriteString(": ") + b.WriteString(e.msg) + } + + return b.String() +} + +// Is tests whether this error matches a given [ErrorCode] or [ExtendedErrorCode]. +// +// It makes it possible to do: +// +// if errors.Is(err, sqlite3.BUSY) { +// // ... handle BUSY +// } +func (e *Error) Is(err error) bool { + switch c := err.(type) { + case ErrorCode: + return c == e.Code() + case ExtendedErrorCode: + return c == e.ExtendedCode() + } + return false +} + +// As converts this error to an [ErrorCode] or [ExtendedErrorCode]. +func (e *Error) As(err any) bool { + switch c := err.(type) { + case *ErrorCode: + *c = e.Code() + return true + case *ExtendedErrorCode: + *c = e.ExtendedCode() + return true + } + return false +} + +// Temporary returns true for [BUSY] errors. +func (e *Error) Temporary() bool { + return e.Code() == BUSY +} + +// Timeout returns true for [BUSY_TIMEOUT] errors. +func (e *Error) Timeout() bool { + return e.ExtendedCode() == BUSY_TIMEOUT +} + +// SQL returns the SQL starting at the token that triggered a syntax error. +func (e *Error) SQL() string { + return e.sql +} + +// Error implements the error interface. +func (e ErrorCode) Error() string { + return util.ErrorCodeString(uint32(e)) +} + +// Temporary returns true for [BUSY] errors. +func (e ErrorCode) Temporary() bool { + return e == BUSY +} + +// Error implements the error interface. +func (e ExtendedErrorCode) Error() string { + return util.ErrorCodeString(uint32(e)) +} + +// Is tests whether this error matches a given [ErrorCode]. +func (e ExtendedErrorCode) Is(err error) bool { + c, ok := err.(ErrorCode) + return ok && c == ErrorCode(e) +} + +// As converts this error to an [ErrorCode]. +func (e ExtendedErrorCode) As(err any) bool { + c, ok := err.(*ErrorCode) + if ok { + *c = ErrorCode(e) + } + return ok +} + +// Temporary returns true for [BUSY] errors. +func (e ExtendedErrorCode) Temporary() bool { + return ErrorCode(e) == BUSY +} + +// Timeout returns true for [BUSY_TIMEOUT] errors. +func (e ExtendedErrorCode) Timeout() bool { + return e == BUSY_TIMEOUT +} + +func errorCode(err error, def ErrorCode) (msg string, code uint32) { + switch code := err.(type) { + case nil: + return "", _OK + case ErrorCode: + return "", uint32(code) + case xErrorCode: + return "", uint32(code) + case *Error: + return code.msg, uint32(code.code) + } + + var ecode ErrorCode + var xcode xErrorCode + switch { + case errors.As(err, &xcode): + code = uint32(xcode) + case errors.As(err, &ecode): + code = uint32(ecode) + default: + code = uint32(def) + } + return err.Error(), code +} diff --git a/vendor/github.com/ncruces/go-sqlite3/func.go b/vendor/github.com/ncruces/go-sqlite3/func.go new file mode 100644 index 000000000..255584a43 --- /dev/null +++ b/vendor/github.com/ncruces/go-sqlite3/func.go @@ -0,0 +1,214 @@ +package sqlite3 + +import ( + "context" + "sync" + + "github.com/ncruces/go-sqlite3/internal/util" + "github.com/tetratelabs/wazero/api" +) + +// CollationNeeded registers a callback to be invoked +// whenever an unknown collation sequence is required. +// +// https://sqlite.org/c3ref/collation_needed.html +func (c *Conn) CollationNeeded(cb func(db *Conn, name string)) error { + var enable uint64 + if cb != nil { + enable = 1 + } + r := c.call("sqlite3_collation_needed_go", uint64(c.handle), enable) + if err := c.error(r); err != nil { + return err + } + c.collation = cb + return nil +} + +// AnyCollationNeeded uses [Conn.CollationNeeded] to register +// a fake collating function for any unknown collating sequence. +// The fake collating function works like BINARY. +// +// This can be used to load schemas that contain +// one or more unknown collating sequences. +func (c *Conn) AnyCollationNeeded() { + c.call("sqlite3_anycollseq_init", uint64(c.handle), 0, 0) +} + +// CreateCollation defines a new collating sequence. +// +// https://sqlite.org/c3ref/create_collation.html +func (c *Conn) CreateCollation(name string, fn func(a, b []byte) int) error { + defer c.arena.mark()() + namePtr := c.arena.string(name) + funcPtr := util.AddHandle(c.ctx, fn) + r := c.call("sqlite3_create_collation_go", + uint64(c.handle), uint64(namePtr), uint64(funcPtr)) + return c.error(r) +} + +// CreateFunction defines a new scalar SQL function. +// +// https://sqlite.org/c3ref/create_function.html +func (c *Conn) CreateFunction(name string, nArg int, flag FunctionFlag, fn ScalarFunction) error { + defer c.arena.mark()() + namePtr := c.arena.string(name) + funcPtr := util.AddHandle(c.ctx, fn) + r := c.call("sqlite3_create_function_go", + uint64(c.handle), uint64(namePtr), uint64(nArg), + uint64(flag), uint64(funcPtr)) + return c.error(r) +} + +// ScalarFunction is the type of a scalar SQL function. +// Implementations must not retain arg. +type ScalarFunction func(ctx Context, arg ...Value) + +// CreateWindowFunction defines a new aggregate or aggregate window SQL function. +// If fn returns a [WindowFunction], then an aggregate window function is created. +// If fn returns an [io.Closer], it will be called to free resources. +// +// https://sqlite.org/c3ref/create_function.html +func (c *Conn) CreateWindowFunction(name string, nArg int, flag FunctionFlag, fn func() AggregateFunction) error { + defer c.arena.mark()() + call := "sqlite3_create_aggregate_function_go" + namePtr := c.arena.string(name) + funcPtr := util.AddHandle(c.ctx, fn) + if _, ok := fn().(WindowFunction); ok { + call = "sqlite3_create_window_function_go" + } + r := c.call(call, + uint64(c.handle), uint64(namePtr), uint64(nArg), + uint64(flag), uint64(funcPtr)) + return c.error(r) +} + +// AggregateFunction is the interface an aggregate function should implement. +// +// https://sqlite.org/appfunc.html +type AggregateFunction interface { + // Step is invoked to add a row to the current window. + // The function arguments, if any, corresponding to the row being added, are passed to Step. + // Implementations must not retain arg. + Step(ctx Context, arg ...Value) + + // Value is invoked to return the current (or final) value of the aggregate. + Value(ctx Context) +} + +// WindowFunction is the interface an aggregate window function should implement. +// +// https://sqlite.org/windowfunctions.html +type WindowFunction interface { + AggregateFunction + + // Inverse is invoked to remove the oldest presently aggregated result of Step from the current window. + // The function arguments, if any, are those passed to Step for the row being removed. + // Implementations must not retain arg. + Inverse(ctx Context, arg ...Value) +} + +// OverloadFunction overloads a function for a virtual table. +// +// https://sqlite.org/c3ref/overload_function.html +func (c *Conn) OverloadFunction(name string, nArg int) error { + defer c.arena.mark()() + namePtr := c.arena.string(name) + r := c.call("sqlite3_overload_function", + uint64(c.handle), uint64(namePtr), uint64(nArg)) + return c.error(r) +} + +func destroyCallback(ctx context.Context, mod api.Module, pApp uint32) { + util.DelHandle(ctx, pApp) +} + +func collationCallback(ctx context.Context, mod api.Module, pArg, pDB, eTextRep, zName uint32) { + if c, ok := ctx.Value(connKey{}).(*Conn); ok && c.handle == pDB && c.collation != nil { + name := util.ReadString(mod, zName, _MAX_NAME) + c.collation(c, name) + } +} + +func compareCallback(ctx context.Context, mod api.Module, pApp, nKey1, pKey1, nKey2, pKey2 uint32) uint32 { + fn := util.GetHandle(ctx, pApp).(func(a, b []byte) int) + return uint32(fn(util.View(mod, pKey1, uint64(nKey1)), util.View(mod, pKey2, uint64(nKey2)))) +} + +func funcCallback(ctx context.Context, mod api.Module, pCtx, pApp, nArg, pArg uint32) { + args := getFuncArgs() + defer putFuncArgs(args) + db := ctx.Value(connKey{}).(*Conn) + fn := util.GetHandle(db.ctx, pApp).(ScalarFunction) + callbackArgs(db, args[:nArg], pArg) + fn(Context{db, pCtx}, args[:nArg]...) +} + +func stepCallback(ctx context.Context, mod api.Module, pCtx, pAgg, pApp, nArg, pArg uint32) { + args := getFuncArgs() + defer putFuncArgs(args) + db := ctx.Value(connKey{}).(*Conn) + callbackArgs(db, args[:nArg], pArg) + fn, _ := callbackAggregate(db, pAgg, pApp) + fn.Step(Context{db, pCtx}, args[:nArg]...) +} + +func finalCallback(ctx context.Context, mod api.Module, pCtx, pAgg, pApp uint32) { + db := ctx.Value(connKey{}).(*Conn) + fn, handle := callbackAggregate(db, pAgg, pApp) + fn.Value(Context{db, pCtx}) + util.DelHandle(ctx, handle) +} + +func valueCallback(ctx context.Context, mod api.Module, pCtx, pAgg uint32) { + db := ctx.Value(connKey{}).(*Conn) + fn := util.GetHandle(db.ctx, pAgg).(AggregateFunction) + fn.Value(Context{db, pCtx}) +} + +func inverseCallback(ctx context.Context, mod api.Module, pCtx, pAgg, nArg, pArg uint32) { + args := getFuncArgs() + defer putFuncArgs(args) + db := ctx.Value(connKey{}).(*Conn) + callbackArgs(db, args[:nArg], pArg) + fn := util.GetHandle(db.ctx, pAgg).(WindowFunction) + fn.Inverse(Context{db, pCtx}, args[:nArg]...) +} + +func callbackAggregate(db *Conn, pAgg, pApp uint32) (AggregateFunction, uint32) { + if pApp == 0 { + handle := util.ReadUint32(db.mod, pAgg) + return util.GetHandle(db.ctx, handle).(AggregateFunction), handle + } + + // We need to create the aggregate. + fn := util.GetHandle(db.ctx, pApp).(func() AggregateFunction)() + handle := util.AddHandle(db.ctx, fn) + if pAgg != 0 { + util.WriteUint32(db.mod, pAgg, handle) + } + return fn, handle +} + +func callbackArgs(db *Conn, arg []Value, pArg uint32) { + for i := range arg { + arg[i] = Value{ + c: db, + handle: util.ReadUint32(db.mod, pArg+ptrlen*uint32(i)), + } + } +} + +var funcArgsPool sync.Pool + +func putFuncArgs(p *[_MAX_FUNCTION_ARG]Value) { + funcArgsPool.Put(p) +} + +func getFuncArgs() *[_MAX_FUNCTION_ARG]Value { + if p := funcArgsPool.Get(); p == nil { + return new([_MAX_FUNCTION_ARG]Value) + } else { + return p.(*[_MAX_FUNCTION_ARG]Value) + } +} diff --git a/vendor/github.com/ncruces/go-sqlite3/go.work b/vendor/github.com/ncruces/go-sqlite3/go.work new file mode 100644 index 000000000..18e378592 --- /dev/null +++ b/vendor/github.com/ncruces/go-sqlite3/go.work @@ -0,0 +1,6 @@ +go 1.21 + +use ( + . + ./gormlite +) diff --git a/vendor/github.com/ncruces/go-sqlite3/go.work.sum b/vendor/github.com/ncruces/go-sqlite3/go.work.sum new file mode 100644 index 000000000..4deb7b7f3 --- /dev/null +++ b/vendor/github.com/ncruces/go-sqlite3/go.work.sum @@ -0,0 +1,9 @@ +golang.org/x/mod v0.8.0 h1:LUYupSeNrTNCGzR/hVBk2NHZO4hXcVaW1k4Qx7rjPx8= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4= +golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= +golang.org/x/term v0.19.0 h1:+ThwsDv+tYfnJFhF4L8jITxu1tdTWRTZpdsWgEgjL6Q= +golang.org/x/term v0.19.0/go.mod h1:2CuTdWZ7KHSQwUzKva0cbMg6q2DMI3Mmxp+gKJbskEk= +golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= +golang.org/x/tools v0.6.0 h1:BOw41kyTf3PuCW1pVQf8+Cyg8pMlkYB1oo9iJ6D/lKM= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= diff --git a/vendor/github.com/ncruces/go-sqlite3/internal/util/alloc_other.go b/vendor/github.com/ncruces/go-sqlite3/internal/util/alloc_other.go new file mode 100644 index 000000000..ba16efc02 --- /dev/null +++ b/vendor/github.com/ncruces/go-sqlite3/internal/util/alloc_other.go @@ -0,0 +1,9 @@ +//go:build !(unix || windows) || sqlite3_nosys + +package util + +import "github.com/tetratelabs/wazero/experimental" + +func virtualAlloc(cap, max uint64) experimental.LinearMemory { + return sliceAlloc(cap, max) +} diff --git a/vendor/github.com/ncruces/go-sqlite3/internal/util/alloc_slice.go b/vendor/github.com/ncruces/go-sqlite3/internal/util/alloc_slice.go new file mode 100644 index 000000000..b8cc1453c --- /dev/null +++ b/vendor/github.com/ncruces/go-sqlite3/internal/util/alloc_slice.go @@ -0,0 +1,25 @@ +//go:build !(darwin || linux) || !(amd64 || arm64 || riscv64) || sqlite3_noshm || sqlite3_nosys + +package util + +import "github.com/tetratelabs/wazero/experimental" + +func sliceAlloc(cap, max uint64) experimental.LinearMemory { + return &sliceBuffer{make([]byte, cap), max} +} + +type sliceBuffer struct { + buf []byte + max uint64 +} + +func (b *sliceBuffer) Free() {} + +func (b *sliceBuffer) Reallocate(size uint64) []byte { + if cap := uint64(cap(b.buf)); size > cap { + b.buf = append(b.buf[:cap], make([]byte, size-cap)...) + } else { + b.buf = b.buf[:size] + } + return b.buf +} diff --git a/vendor/github.com/ncruces/go-sqlite3/internal/util/alloc_unix.go b/vendor/github.com/ncruces/go-sqlite3/internal/util/alloc_unix.go new file mode 100644 index 000000000..2b1d3916b --- /dev/null +++ b/vendor/github.com/ncruces/go-sqlite3/internal/util/alloc_unix.go @@ -0,0 +1,67 @@ +//go:build unix && !sqlite3_nosys + +package util + +import ( + "math" + + "github.com/tetratelabs/wazero/experimental" + "golang.org/x/sys/unix" +) + +func virtualAlloc(cap, max uint64) experimental.LinearMemory { + // Round up to the page size. + rnd := uint64(unix.Getpagesize() - 1) + max = (max + rnd) &^ rnd + + if max > math.MaxInt { + // This ensures int(max) overflows to a negative value, + // and unix.Mmap returns EINVAL. + max = math.MaxUint64 + } + + // Reserve max bytes of address space, to ensure we won't need to move it. + // A protected, private, anonymous mapping should not commit memory. + b, err := unix.Mmap(-1, 0, int(max), unix.PROT_NONE, unix.MAP_PRIVATE|unix.MAP_ANON) + if err != nil { + panic(err) + } + return &mmappedMemory{buf: b[:0]} +} + +// The slice covers the entire mmapped memory: +// - len(buf) is the already committed memory, +// - cap(buf) is the reserved address space. +type mmappedMemory struct { + buf []byte +} + +func (m *mmappedMemory) Reallocate(size uint64) []byte { + com := uint64(len(m.buf)) + res := uint64(cap(m.buf)) + if com < size && size < res { + // Round up to the page size. + rnd := uint64(unix.Getpagesize() - 1) + new := (size + rnd) &^ rnd + + // Commit additional memory up to new bytes. + err := unix.Mprotect(m.buf[com:new], unix.PROT_READ|unix.PROT_WRITE) + if err != nil { + panic(err) + } + + // Update committed memory. + m.buf = m.buf[:new] + } + // Limit returned capacity because bytes beyond + // len(m.buf) have not yet been committed. + return m.buf[:size:len(m.buf)] +} + +func (m *mmappedMemory) Free() { + err := unix.Munmap(m.buf[:cap(m.buf)]) + if err != nil { + panic(err) + } + m.buf = nil +} diff --git a/vendor/github.com/ncruces/go-sqlite3/internal/util/alloc_windows.go b/vendor/github.com/ncruces/go-sqlite3/internal/util/alloc_windows.go new file mode 100644 index 000000000..8936173b4 --- /dev/null +++ b/vendor/github.com/ncruces/go-sqlite3/internal/util/alloc_windows.go @@ -0,0 +1,76 @@ +//go:build !sqlite3_nosys + +package util + +import ( + "math" + "reflect" + "unsafe" + + "github.com/tetratelabs/wazero/experimental" + "golang.org/x/sys/windows" +) + +func virtualAlloc(cap, max uint64) experimental.LinearMemory { + // Round up to the page size. + rnd := uint64(windows.Getpagesize() - 1) + max = (max + rnd) &^ rnd + + if max > math.MaxInt { + // This ensures uintptr(max) overflows to a large value, + // and windows.VirtualAlloc returns an error. + max = math.MaxUint64 + } + + // Reserve max bytes of address space, to ensure we won't need to move it. + // This does not commit memory. + r, err := windows.VirtualAlloc(0, uintptr(max), windows.MEM_RESERVE, windows.PAGE_READWRITE) + if err != nil { + panic(err) + } + + mem := virtualMemory{addr: r} + // SliceHeader, although deprecated, avoids a go vet warning. + sh := (*reflect.SliceHeader)(unsafe.Pointer(&mem.buf)) + sh.Cap = int(max) // Not a bug. + sh.Data = r + return &mem +} + +// The slice covers the entire mmapped memory: +// - len(buf) is the already committed memory, +// - cap(buf) is the reserved address space. +type virtualMemory struct { + buf []byte + addr uintptr +} + +func (m *virtualMemory) Reallocate(size uint64) []byte { + com := uint64(len(m.buf)) + res := uint64(cap(m.buf)) + if com < size && size < res { + // Round up to the page size. + rnd := uint64(windows.Getpagesize() - 1) + new := (size + rnd) &^ rnd + + // Commit additional memory up to new bytes. + _, err := windows.VirtualAlloc(m.addr, uintptr(new), windows.MEM_COMMIT, windows.PAGE_READWRITE) + if err != nil { + panic(err) + } + + // Update committed memory. + m.buf = m.buf[:new] + } + // Limit returned capacity because bytes beyond + // len(m.buf) have not yet been committed. + return m.buf[:size:len(m.buf)] +} + +func (m *virtualMemory) Free() { + err := windows.VirtualFree(m.addr, 0, windows.MEM_RELEASE) + if err != nil { + panic(err) + } + m.addr = 0 +} diff --git a/vendor/github.com/ncruces/go-sqlite3/internal/util/bool.go b/vendor/github.com/ncruces/go-sqlite3/internal/util/bool.go new file mode 100644 index 000000000..8427f3085 --- /dev/null +++ b/vendor/github.com/ncruces/go-sqlite3/internal/util/bool.go @@ -0,0 +1,22 @@ +package util + +import "strings" + +func ParseBool(s string) (b, ok bool) { + if len(s) == 0 { + return false, false + } + if s[0] == '0' { + return false, true + } + if '1' <= s[0] && s[0] <= '9' { + return true, true + } + switch strings.ToLower(s) { + case "true", "yes", "on": + return true, true + case "false", "no", "off": + return false, true + } + return false, false +} diff --git a/vendor/github.com/ncruces/go-sqlite3/internal/util/const.go b/vendor/github.com/ncruces/go-sqlite3/internal/util/const.go new file mode 100644 index 000000000..86bb9749d --- /dev/null +++ b/vendor/github.com/ncruces/go-sqlite3/internal/util/const.go @@ -0,0 +1,117 @@ +package util + +// https://sqlite.com/matrix/rescode.html +const ( + OK = 0 /* Successful result */ + + ERROR = 1 /* Generic error */ + INTERNAL = 2 /* Internal logic error in SQLite */ + PERM = 3 /* Access permission denied */ + ABORT = 4 /* Callback routine requested an abort */ + BUSY = 5 /* The database file is locked */ + LOCKED = 6 /* A table in the database is locked */ + NOMEM = 7 /* A malloc() failed */ + READONLY = 8 /* Attempt to write a readonly database */ + INTERRUPT = 9 /* Operation terminated by sqlite3_interrupt() */ + IOERR = 10 /* Some kind of disk I/O error occurred */ + CORRUPT = 11 /* The database disk image is malformed */ + NOTFOUND = 12 /* Unknown opcode in sqlite3_file_control() */ + FULL = 13 /* Insertion failed because database is full */ + CANTOPEN = 14 /* Unable to open the database file */ + PROTOCOL = 15 /* Database lock protocol error */ + EMPTY = 16 /* Internal use only */ + SCHEMA = 17 /* The database schema changed */ + TOOBIG = 18 /* String or BLOB exceeds size limit */ + CONSTRAINT = 19 /* Abort due to constraint violation */ + MISMATCH = 20 /* Data type mismatch */ + MISUSE = 21 /* Library used incorrectly */ + NOLFS = 22 /* Uses OS features not supported on host */ + AUTH = 23 /* Authorization denied */ + FORMAT = 24 /* Not used */ + RANGE = 25 /* 2nd parameter to sqlite3_bind out of range */ + NOTADB = 26 /* File opened that is not a database file */ + NOTICE = 27 /* Notifications from sqlite3_log() */ + WARNING = 28 /* Warnings from sqlite3_log() */ + + ROW = 100 /* sqlite3_step() has another row ready */ + DONE = 101 /* sqlite3_step() has finished executing */ + + ERROR_MISSING_COLLSEQ = ERROR | (1 << 8) + ERROR_RETRY = ERROR | (2 << 8) + ERROR_SNAPSHOT = ERROR | (3 << 8) + IOERR_READ = IOERR | (1 << 8) + IOERR_SHORT_READ = IOERR | (2 << 8) + IOERR_WRITE = IOERR | (3 << 8) + IOERR_FSYNC = IOERR | (4 << 8) + IOERR_DIR_FSYNC = IOERR | (5 << 8) + IOERR_TRUNCATE = IOERR | (6 << 8) + IOERR_FSTAT = IOERR | (7 << 8) + IOERR_UNLOCK = IOERR | (8 << 8) + IOERR_RDLOCK = IOERR | (9 << 8) + IOERR_DELETE = IOERR | (10 << 8) + IOERR_BLOCKED = IOERR | (11 << 8) + IOERR_NOMEM = IOERR | (12 << 8) + IOERR_ACCESS = IOERR | (13 << 8) + IOERR_CHECKRESERVEDLOCK = IOERR | (14 << 8) + IOERR_LOCK = IOERR | (15 << 8) + IOERR_CLOSE = IOERR | (16 << 8) + IOERR_DIR_CLOSE = IOERR | (17 << 8) + IOERR_SHMOPEN = IOERR | (18 << 8) + IOERR_SHMSIZE = IOERR | (19 << 8) + IOERR_SHMLOCK = IOERR | (20 << 8) + IOERR_SHMMAP = IOERR | (21 << 8) + IOERR_SEEK = IOERR | (22 << 8) + IOERR_DELETE_NOENT = IOERR | (23 << 8) + IOERR_MMAP = IOERR | (24 << 8) + IOERR_GETTEMPPATH = IOERR | (25 << 8) + IOERR_CONVPATH = IOERR | (26 << 8) + IOERR_VNODE = IOERR | (27 << 8) + IOERR_AUTH = IOERR | (28 << 8) + IOERR_BEGIN_ATOMIC = IOERR | (29 << 8) + IOERR_COMMIT_ATOMIC = IOERR | (30 << 8) + IOERR_ROLLBACK_ATOMIC = IOERR | (31 << 8) + IOERR_DATA = IOERR | (32 << 8) + IOERR_CORRUPTFS = IOERR | (33 << 8) + IOERR_IN_PAGE = IOERR | (34 << 8) + LOCKED_SHAREDCACHE = LOCKED | (1 << 8) + LOCKED_VTAB = LOCKED | (2 << 8) + BUSY_RECOVERY = BUSY | (1 << 8) + BUSY_SNAPSHOT = BUSY | (2 << 8) + BUSY_TIMEOUT = BUSY | (3 << 8) + CANTOPEN_NOTEMPDIR = CANTOPEN | (1 << 8) + CANTOPEN_ISDIR = CANTOPEN | (2 << 8) + CANTOPEN_FULLPATH = CANTOPEN | (3 << 8) + CANTOPEN_CONVPATH = CANTOPEN | (4 << 8) + CANTOPEN_DIRTYWAL = CANTOPEN | (5 << 8) /* Not Used */ + CANTOPEN_SYMLINK = CANTOPEN | (6 << 8) + CORRUPT_VTAB = CORRUPT | (1 << 8) + CORRUPT_SEQUENCE = CORRUPT | (2 << 8) + CORRUPT_INDEX = CORRUPT | (3 << 8) + READONLY_RECOVERY = READONLY | (1 << 8) + READONLY_CANTLOCK = READONLY | (2 << 8) + READONLY_ROLLBACK = READONLY | (3 << 8) + READONLY_DBMOVED = READONLY | (4 << 8) + READONLY_CANTINIT = READONLY | (5 << 8) + READONLY_DIRECTORY = READONLY | (6 << 8) + ABORT_ROLLBACK = ABORT | (2 << 8) + CONSTRAINT_CHECK = CONSTRAINT | (1 << 8) + CONSTRAINT_COMMITHOOK = CONSTRAINT | (2 << 8) + CONSTRAINT_FOREIGNKEY = CONSTRAINT | (3 << 8) + CONSTRAINT_FUNCTION = CONSTRAINT | (4 << 8) + CONSTRAINT_NOTNULL = CONSTRAINT | (5 << 8) + CONSTRAINT_PRIMARYKEY = CONSTRAINT | (6 << 8) + CONSTRAINT_TRIGGER = CONSTRAINT | (7 << 8) + CONSTRAINT_UNIQUE = CONSTRAINT | (8 << 8) + CONSTRAINT_VTAB = CONSTRAINT | (9 << 8) + CONSTRAINT_ROWID = CONSTRAINT | (10 << 8) + CONSTRAINT_PINNED = CONSTRAINT | (11 << 8) + CONSTRAINT_DATATYPE = CONSTRAINT | (12 << 8) + NOTICE_RECOVER_WAL = NOTICE | (1 << 8) + NOTICE_RECOVER_ROLLBACK = NOTICE | (2 << 8) + NOTICE_RBU = NOTICE | (3 << 8) + WARNING_AUTOINDEX = WARNING | (1 << 8) + AUTH_USER = AUTH | (1 << 8) + + OK_LOAD_PERMANENTLY = OK | (1 << 8) + OK_SYMLINK = OK | (2 << 8) /* internal use only */ +) diff --git a/vendor/github.com/ncruces/go-sqlite3/internal/util/error.go b/vendor/github.com/ncruces/go-sqlite3/internal/util/error.go new file mode 100644 index 000000000..1f5555fd3 --- /dev/null +++ b/vendor/github.com/ncruces/go-sqlite3/internal/util/error.go @@ -0,0 +1,106 @@ +package util + +import ( + "runtime" + "strconv" +) + +type ErrorString string + +func (e ErrorString) Error() string { return string(e) } + +const ( + NilErr = ErrorString("sqlite3: invalid memory address or null pointer dereference") + OOMErr = ErrorString("sqlite3: out of memory") + RangeErr = ErrorString("sqlite3: index out of range") + NoNulErr = ErrorString("sqlite3: missing NUL terminator") + NoBinaryErr = ErrorString("sqlite3: no SQLite binary embed/set/loaded") + BadBinaryErr = ErrorString("sqlite3: invalid SQLite binary embed/set/loaded") + TimeErr = ErrorString("sqlite3: invalid time value") + WhenceErr = ErrorString("sqlite3: invalid whence") + OffsetErr = ErrorString("sqlite3: invalid offset") + TailErr = ErrorString("sqlite3: multiple statements") + IsolationErr = ErrorString("sqlite3: unsupported isolation level") + ValueErr = ErrorString("sqlite3: unsupported value") + NoVFSErr = ErrorString("sqlite3: no such vfs: ") +) + +func AssertErr() ErrorString { + msg := "sqlite3: assertion failed" + if _, file, line, ok := runtime.Caller(1); ok { + msg += " (" + file + ":" + strconv.Itoa(line) + ")" + } + return ErrorString(msg) +} + +func ErrorCodeString(rc uint32) string { + switch rc { + case ABORT_ROLLBACK: + return "sqlite3: abort due to ROLLBACK" + case ROW: + return "sqlite3: another row available" + case DONE: + return "sqlite3: no more rows available" + } + switch rc & 0xff { + case OK: + return "sqlite3: not an error" + case ERROR: + return "sqlite3: SQL logic error" + case INTERNAL: + break + case PERM: + return "sqlite3: access permission denied" + case ABORT: + return "sqlite3: query aborted" + case BUSY: + return "sqlite3: database is locked" + case LOCKED: + return "sqlite3: database table is locked" + case NOMEM: + return "sqlite3: out of memory" + case READONLY: + return "sqlite3: attempt to write a readonly database" + case INTERRUPT: + return "sqlite3: interrupted" + case IOERR: + return "sqlite3: disk I/O error" + case CORRUPT: + return "sqlite3: database disk image is malformed" + case NOTFOUND: + return "sqlite3: unknown operation" + case FULL: + return "sqlite3: database or disk is full" + case CANTOPEN: + return "sqlite3: unable to open database file" + case PROTOCOL: + return "sqlite3: locking protocol" + case FORMAT: + break + case SCHEMA: + return "sqlite3: database schema has changed" + case TOOBIG: + return "sqlite3: string or blob too big" + case CONSTRAINT: + return "sqlite3: constraint failed" + case MISMATCH: + return "sqlite3: datatype mismatch" + case MISUSE: + return "sqlite3: bad parameter or other API misuse" + case NOLFS: + break + case AUTH: + return "sqlite3: authorization denied" + case EMPTY: + break + case RANGE: + return "sqlite3: column index out of range" + case NOTADB: + return "sqlite3: file is not a database" + case NOTICE: + return "sqlite3: notification message" + case WARNING: + return "sqlite3: warning message" + } + return "sqlite3: unknown error" +} diff --git a/vendor/github.com/ncruces/go-sqlite3/internal/util/func.go b/vendor/github.com/ncruces/go-sqlite3/internal/util/func.go new file mode 100644 index 000000000..be7a47c2f --- /dev/null +++ b/vendor/github.com/ncruces/go-sqlite3/internal/util/func.go @@ -0,0 +1,193 @@ +package util + +import ( + "context" + + "github.com/tetratelabs/wazero" + "github.com/tetratelabs/wazero/api" +) + +type i32 interface{ ~int32 | ~uint32 } +type i64 interface{ ~int64 | ~uint64 } + +type funcVI[T0 i32] func(context.Context, api.Module, T0) + +func (fn funcVI[T0]) Call(ctx context.Context, mod api.Module, stack []uint64) { + fn(ctx, mod, T0(stack[0])) +} + +func ExportFuncVI[T0 i32](mod wazero.HostModuleBuilder, name string, fn func(context.Context, api.Module, T0)) { + mod.NewFunctionBuilder(). + WithGoModuleFunction(funcVI[T0](fn), + []api.ValueType{api.ValueTypeI32}, nil). + Export(name) +} + +type funcVII[T0, T1 i32] func(context.Context, api.Module, T0, T1) + +func (fn funcVII[T0, T1]) Call(ctx context.Context, mod api.Module, stack []uint64) { + fn(ctx, mod, T0(stack[0]), T1(stack[1])) +} + +func ExportFuncVII[T0, T1 i32](mod wazero.HostModuleBuilder, name string, fn func(context.Context, api.Module, T0, T1)) { + mod.NewFunctionBuilder(). + WithGoModuleFunction(funcVII[T0, T1](fn), + []api.ValueType{api.ValueTypeI32, api.ValueTypeI32}, nil). + Export(name) +} + +type funcVIII[T0, T1, T2 i32] func(context.Context, api.Module, T0, T1, T2) + +func (fn funcVIII[T0, T1, T2]) Call(ctx context.Context, mod api.Module, stack []uint64) { + fn(ctx, mod, T0(stack[0]), T1(stack[1]), T2(stack[2])) +} + +func ExportFuncVIII[T0, T1, T2 i32](mod wazero.HostModuleBuilder, name string, fn func(context.Context, api.Module, T0, T1, T2)) { + mod.NewFunctionBuilder(). + WithGoModuleFunction(funcVIII[T0, T1, T2](fn), + []api.ValueType{api.ValueTypeI32, api.ValueTypeI32, api.ValueTypeI32}, nil). + Export(name) +} + +type funcVIIII[T0, T1, T2, T3 i32] func(context.Context, api.Module, T0, T1, T2, T3) + +func (fn funcVIIII[T0, T1, T2, T3]) Call(ctx context.Context, mod api.Module, stack []uint64) { + fn(ctx, mod, T0(stack[0]), T1(stack[1]), T2(stack[2]), T3(stack[3])) +} + +func ExportFuncVIIII[T0, T1, T2, T3 i32](mod wazero.HostModuleBuilder, name string, fn func(context.Context, api.Module, T0, T1, T2, T3)) { + mod.NewFunctionBuilder(). + WithGoModuleFunction(funcVIIII[T0, T1, T2, T3](fn), + []api.ValueType{api.ValueTypeI32, api.ValueTypeI32, api.ValueTypeI32, api.ValueTypeI32}, nil). + Export(name) +} + +type funcVIIIII[T0, T1, T2, T3, T4 i32] func(context.Context, api.Module, T0, T1, T2, T3, T4) + +func (fn funcVIIIII[T0, T1, T2, T3, T4]) Call(ctx context.Context, mod api.Module, stack []uint64) { + fn(ctx, mod, T0(stack[0]), T1(stack[1]), T2(stack[2]), T3(stack[3]), T4(stack[4])) +} + +func ExportFuncVIIIII[T0, T1, T2, T3, T4 i32](mod wazero.HostModuleBuilder, name string, fn func(context.Context, api.Module, T0, T1, T2, T3, T4)) { + mod.NewFunctionBuilder(). + WithGoModuleFunction(funcVIIIII[T0, T1, T2, T3, T4](fn), + []api.ValueType{api.ValueTypeI32, api.ValueTypeI32, api.ValueTypeI32, api.ValueTypeI32, api.ValueTypeI32}, nil). + Export(name) +} + +type funcVIIIIJ[T0, T1, T2, T3 i32, T4 i64] func(context.Context, api.Module, T0, T1, T2, T3, T4) + +func (fn funcVIIIIJ[T0, T1, T2, T3, T4]) Call(ctx context.Context, mod api.Module, stack []uint64) { + fn(ctx, mod, T0(stack[0]), T1(stack[1]), T2(stack[2]), T3(stack[3]), T4(stack[4])) +} + +func ExportFuncVIIIIJ[T0, T1, T2, T3 i32, T4 i64](mod wazero.HostModuleBuilder, name string, fn func(context.Context, api.Module, T0, T1, T2, T3, T4)) { + mod.NewFunctionBuilder(). + WithGoModuleFunction(funcVIIIIJ[T0, T1, T2, T3, T4](fn), + []api.ValueType{api.ValueTypeI32, api.ValueTypeI32, api.ValueTypeI32, api.ValueTypeI32, api.ValueTypeI64}, nil). + Export(name) +} + +type funcII[TR, T0 i32] func(context.Context, api.Module, T0) TR + +func (fn funcII[TR, T0]) Call(ctx context.Context, mod api.Module, stack []uint64) { + stack[0] = uint64(fn(ctx, mod, T0(stack[0]))) +} + +func ExportFuncII[TR, T0 i32](mod wazero.HostModuleBuilder, name string, fn func(context.Context, api.Module, T0) TR) { + mod.NewFunctionBuilder(). + WithGoModuleFunction(funcII[TR, T0](fn), + []api.ValueType{api.ValueTypeI32}, []api.ValueType{api.ValueTypeI32}). + Export(name) +} + +type funcIII[TR, T0, T1 i32] func(context.Context, api.Module, T0, T1) TR + +func (fn funcIII[TR, T0, T1]) Call(ctx context.Context, mod api.Module, stack []uint64) { + stack[0] = uint64(fn(ctx, mod, T0(stack[0]), T1(stack[1]))) +} + +func ExportFuncIII[TR, T0, T1 i32](mod wazero.HostModuleBuilder, name string, fn func(context.Context, api.Module, T0, T1) TR) { + mod.NewFunctionBuilder(). + WithGoModuleFunction(funcIII[TR, T0, T1](fn), + []api.ValueType{api.ValueTypeI32, api.ValueTypeI32}, []api.ValueType{api.ValueTypeI32}). + Export(name) +} + +type funcIIII[TR, T0, T1, T2 i32] func(context.Context, api.Module, T0, T1, T2) TR + +func (fn funcIIII[TR, T0, T1, T2]) Call(ctx context.Context, mod api.Module, stack []uint64) { + stack[0] = uint64(fn(ctx, mod, T0(stack[0]), T1(stack[1]), T2(stack[2]))) +} + +func ExportFuncIIII[TR, T0, T1, T2 i32](mod wazero.HostModuleBuilder, name string, fn func(context.Context, api.Module, T0, T1, T2) TR) { + mod.NewFunctionBuilder(). + WithGoModuleFunction(funcIIII[TR, T0, T1, T2](fn), + []api.ValueType{api.ValueTypeI32, api.ValueTypeI32, api.ValueTypeI32}, []api.ValueType{api.ValueTypeI32}). + Export(name) +} + +type funcIIIII[TR, T0, T1, T2, T3 i32] func(context.Context, api.Module, T0, T1, T2, T3) TR + +func (fn funcIIIII[TR, T0, T1, T2, T3]) Call(ctx context.Context, mod api.Module, stack []uint64) { + stack[0] = uint64(fn(ctx, mod, T0(stack[0]), T1(stack[1]), T2(stack[2]), T3(stack[3]))) +} + +func ExportFuncIIIII[TR, T0, T1, T2, T3 i32](mod wazero.HostModuleBuilder, name string, fn func(context.Context, api.Module, T0, T1, T2, T3) TR) { + mod.NewFunctionBuilder(). + WithGoModuleFunction(funcIIIII[TR, T0, T1, T2, T3](fn), + []api.ValueType{api.ValueTypeI32, api.ValueTypeI32, api.ValueTypeI32, api.ValueTypeI32}, []api.ValueType{api.ValueTypeI32}). + Export(name) +} + +type funcIIIIII[TR, T0, T1, T2, T3, T4 i32] func(context.Context, api.Module, T0, T1, T2, T3, T4) TR + +func (fn funcIIIIII[TR, T0, T1, T2, T3, T4]) Call(ctx context.Context, mod api.Module, stack []uint64) { + stack[0] = uint64(fn(ctx, mod, T0(stack[0]), T1(stack[1]), T2(stack[2]), T3(stack[3]), T4(stack[4]))) +} + +func ExportFuncIIIIII[TR, T0, T1, T2, T3, T4 i32](mod wazero.HostModuleBuilder, name string, fn func(context.Context, api.Module, T0, T1, T2, T3, T4) TR) { + mod.NewFunctionBuilder(). + WithGoModuleFunction(funcIIIIII[TR, T0, T1, T2, T3, T4](fn), + []api.ValueType{api.ValueTypeI32, api.ValueTypeI32, api.ValueTypeI32, api.ValueTypeI32, api.ValueTypeI32}, []api.ValueType{api.ValueTypeI32}). + Export(name) +} + +type funcIIIIIII[TR, T0, T1, T2, T3, T4, T5 i32] func(context.Context, api.Module, T0, T1, T2, T3, T4, T5) TR + +func (fn funcIIIIIII[TR, T0, T1, T2, T3, T4, T5]) Call(ctx context.Context, mod api.Module, stack []uint64) { + stack[0] = uint64(fn(ctx, mod, T0(stack[0]), T1(stack[1]), T2(stack[2]), T3(stack[3]), T4(stack[4]), T5(stack[5]))) +} + +func ExportFuncIIIIIII[TR, T0, T1, T2, T3, T4, T5 i32](mod wazero.HostModuleBuilder, name string, fn func(context.Context, api.Module, T0, T1, T2, T3, T4, T5) TR) { + mod.NewFunctionBuilder(). + WithGoModuleFunction(funcIIIIIII[TR, T0, T1, T2, T3, T4, T5](fn), + []api.ValueType{api.ValueTypeI32, api.ValueTypeI32, api.ValueTypeI32, api.ValueTypeI32, api.ValueTypeI32, api.ValueTypeI32}, []api.ValueType{api.ValueTypeI32}). + Export(name) +} + +type funcIIIIJ[TR, T0, T1, T2 i32, T3 i64] func(context.Context, api.Module, T0, T1, T2, T3) TR + +func (fn funcIIIIJ[TR, T0, T1, T2, T3]) Call(ctx context.Context, mod api.Module, stack []uint64) { + stack[0] = uint64(fn(ctx, mod, T0(stack[0]), T1(stack[1]), T2(stack[2]), T3(stack[3]))) +} + +func ExportFuncIIIIJ[TR, T0, T1, T2 i32, T3 i64](mod wazero.HostModuleBuilder, name string, fn func(context.Context, api.Module, T0, T1, T2, T3) TR) { + mod.NewFunctionBuilder(). + WithGoModuleFunction(funcIIIIJ[TR, T0, T1, T2, T3](fn), + []api.ValueType{api.ValueTypeI32, api.ValueTypeI32, api.ValueTypeI32, api.ValueTypeI64}, []api.ValueType{api.ValueTypeI32}). + Export(name) +} + +type funcIIJ[TR, T0 i32, T1 i64] func(context.Context, api.Module, T0, T1) TR + +func (fn funcIIJ[TR, T0, T1]) Call(ctx context.Context, mod api.Module, stack []uint64) { + stack[0] = uint64(fn(ctx, mod, T0(stack[0]), T1(stack[1]))) +} + +func ExportFuncIIJ[TR, T0 i32, T1 i64](mod wazero.HostModuleBuilder, name string, fn func(context.Context, api.Module, T0, T1) TR) { + mod.NewFunctionBuilder(). + WithGoModuleFunction(funcIIJ[TR, T0, T1](fn), + []api.ValueType{api.ValueTypeI32, api.ValueTypeI64}, []api.ValueType{api.ValueTypeI32}). + Export(name) +} diff --git a/vendor/github.com/ncruces/go-sqlite3/internal/util/handle.go b/vendor/github.com/ncruces/go-sqlite3/internal/util/handle.go new file mode 100644 index 000000000..4584324c1 --- /dev/null +++ b/vendor/github.com/ncruces/go-sqlite3/internal/util/handle.go @@ -0,0 +1,65 @@ +package util + +import ( + "context" + "io" +) + +type handleState struct { + handles []any + holes int +} + +func (s *handleState) CloseNotify(ctx context.Context, exitCode uint32) { + for _, h := range s.handles { + if c, ok := h.(io.Closer); ok { + c.Close() + } + } + s.handles = nil + s.holes = 0 +} + +func GetHandle(ctx context.Context, id uint32) any { + if id == 0 { + return nil + } + s := ctx.Value(moduleKey{}).(*moduleState) + return s.handles[^id] +} + +func DelHandle(ctx context.Context, id uint32) error { + if id == 0 { + return nil + } + s := ctx.Value(moduleKey{}).(*moduleState) + a := s.handles[^id] + s.handles[^id] = nil + s.holes++ + if c, ok := a.(io.Closer); ok { + return c.Close() + } + return nil +} + +func AddHandle(ctx context.Context, a any) (id uint32) { + if a == nil { + panic(NilErr) + } + s := ctx.Value(moduleKey{}).(*moduleState) + + // Find an empty slot. + if s.holes > cap(s.handles)-len(s.handles) { + for id, h := range s.handles { + if h == nil { + s.holes-- + s.handles[id] = a + return ^uint32(id) + } + } + } + + // Add a new slot. + s.handles = append(s.handles, a) + return -uint32(len(s.handles)) +} diff --git a/vendor/github.com/ncruces/go-sqlite3/internal/util/json.go b/vendor/github.com/ncruces/go-sqlite3/internal/util/json.go new file mode 100644 index 000000000..c0ba38cf0 --- /dev/null +++ b/vendor/github.com/ncruces/go-sqlite3/internal/util/json.go @@ -0,0 +1,35 @@ +package util + +import ( + "encoding/json" + "strconv" + "time" + "unsafe" +) + +type JSON struct{ Value any } + +func (j JSON) Scan(value any) error { + var buf []byte + + switch v := value.(type) { + case []byte: + buf = v + case string: + buf = unsafe.Slice(unsafe.StringData(v), len(v)) + case int64: + buf = strconv.AppendInt(nil, v, 10) + case float64: + buf = strconv.AppendFloat(nil, v, 'g', -1, 64) + case time.Time: + buf = append(buf, '"') + buf = v.AppendFormat(buf, time.RFC3339Nano) + buf = append(buf, '"') + case nil: + buf = append(buf, "null"...) + default: + panic(AssertErr()) + } + + return json.Unmarshal(buf, j.Value) +} diff --git a/vendor/github.com/ncruces/go-sqlite3/internal/util/mem.go b/vendor/github.com/ncruces/go-sqlite3/internal/util/mem.go new file mode 100644 index 000000000..a09523fd1 --- /dev/null +++ b/vendor/github.com/ncruces/go-sqlite3/internal/util/mem.go @@ -0,0 +1,134 @@ +package util + +import ( + "bytes" + "math" + + "github.com/tetratelabs/wazero/api" +) + +func View(mod api.Module, ptr uint32, size uint64) []byte { + if ptr == 0 { + panic(NilErr) + } + if size > math.MaxUint32 { + panic(RangeErr) + } + if size == 0 { + return nil + } + buf, ok := mod.Memory().Read(ptr, uint32(size)) + if !ok { + panic(RangeErr) + } + return buf +} + +func ReadUint8(mod api.Module, ptr uint32) uint8 { + if ptr == 0 { + panic(NilErr) + } + v, ok := mod.Memory().ReadByte(ptr) + if !ok { + panic(RangeErr) + } + return v +} + +func ReadUint32(mod api.Module, ptr uint32) uint32 { + if ptr == 0 { + panic(NilErr) + } + v, ok := mod.Memory().ReadUint32Le(ptr) + if !ok { + panic(RangeErr) + } + return v +} + +func WriteUint8(mod api.Module, ptr uint32, v uint8) { + if ptr == 0 { + panic(NilErr) + } + ok := mod.Memory().WriteByte(ptr, v) + if !ok { + panic(RangeErr) + } +} + +func WriteUint32(mod api.Module, ptr uint32, v uint32) { + if ptr == 0 { + panic(NilErr) + } + ok := mod.Memory().WriteUint32Le(ptr, v) + if !ok { + panic(RangeErr) + } +} + +func ReadUint64(mod api.Module, ptr uint32) uint64 { + if ptr == 0 { + panic(NilErr) + } + v, ok := mod.Memory().ReadUint64Le(ptr) + if !ok { + panic(RangeErr) + } + return v +} + +func WriteUint64(mod api.Module, ptr uint32, v uint64) { + if ptr == 0 { + panic(NilErr) + } + ok := mod.Memory().WriteUint64Le(ptr, v) + if !ok { + panic(RangeErr) + } +} + +func ReadFloat64(mod api.Module, ptr uint32) float64 { + return math.Float64frombits(ReadUint64(mod, ptr)) +} + +func WriteFloat64(mod api.Module, ptr uint32, v float64) { + WriteUint64(mod, ptr, math.Float64bits(v)) +} + +func ReadString(mod api.Module, ptr, maxlen uint32) string { + if ptr == 0 { + panic(NilErr) + } + switch maxlen { + case 0: + return "" + case math.MaxUint32: + // avoid overflow + default: + maxlen = maxlen + 1 + } + mem := mod.Memory() + buf, ok := mem.Read(ptr, maxlen) + if !ok { + buf, ok = mem.Read(ptr, mem.Size()-ptr) + if !ok { + panic(RangeErr) + } + } + if i := bytes.IndexByte(buf, 0); i < 0 { + panic(NoNulErr) + } else { + return string(buf[:i]) + } +} + +func WriteBytes(mod api.Module, ptr uint32, b []byte) { + buf := View(mod, ptr, uint64(len(b))) + copy(buf, b) +} + +func WriteString(mod api.Module, ptr uint32, s string) { + buf := View(mod, ptr, uint64(len(s)+1)) + buf[len(s)] = 0 + copy(buf, s) +} diff --git a/vendor/github.com/ncruces/go-sqlite3/internal/util/mmap.go b/vendor/github.com/ncruces/go-sqlite3/internal/util/mmap.go new file mode 100644 index 000000000..6783c9612 --- /dev/null +++ b/vendor/github.com/ncruces/go-sqlite3/internal/util/mmap.go @@ -0,0 +1,97 @@ +//go:build (darwin || linux) && (amd64 || arm64 || riscv64) && !(sqlite3_noshm || sqlite3_nosys) + +package util + +import ( + "context" + "os" + "unsafe" + + "github.com/tetratelabs/wazero/api" + "github.com/tetratelabs/wazero/experimental" + "golang.org/x/sys/unix" +) + +func withAllocator(ctx context.Context) context.Context { + return experimental.WithMemoryAllocator(ctx, + experimental.MemoryAllocatorFunc(virtualAlloc)) +} + +type mmapState struct { + regions []*MappedRegion +} + +func (s *mmapState) new(ctx context.Context, mod api.Module, size int32) *MappedRegion { + // Find unused region. + for _, r := range s.regions { + if !r.used && r.size == size { + return r + } + } + + // Allocate page aligned memmory. + alloc := mod.ExportedFunction("aligned_alloc") + stack := [2]uint64{ + uint64(unix.Getpagesize()), + uint64(size), + } + if err := alloc.CallWithStack(ctx, stack[:]); err != nil { + panic(err) + } + if stack[0] == 0 { + panic(OOMErr) + } + + // Save the newly allocated region. + ptr := uint32(stack[0]) + buf := View(mod, ptr, uint64(size)) + addr := uintptr(unsafe.Pointer(&buf[0])) + s.regions = append(s.regions, &MappedRegion{ + Ptr: ptr, + addr: addr, + size: size, + }) + return s.regions[len(s.regions)-1] +} + +type MappedRegion struct { + addr uintptr + Ptr uint32 + size int32 + used bool +} + +func MapRegion(ctx context.Context, mod api.Module, f *os.File, offset int64, size int32, prot int) (*MappedRegion, error) { + s := ctx.Value(moduleKey{}).(*moduleState) + r := s.new(ctx, mod, size) + err := r.mmap(f, offset, prot) + if err != nil { + return nil, err + } + return r, nil +} + +func (r *MappedRegion) Unmap() error { + // We can't munmap the region, otherwise it could be remaped. + // Instead, convert it to a protected, private, anonymous mapping. + // If successful, it can be reused for a subsequent mmap. + _, err := mmap(r.addr, uintptr(r.size), + unix.PROT_NONE, unix.MAP_PRIVATE|unix.MAP_ANON|unix.MAP_FIXED, + -1, 0) + r.used = err != nil + return err +} + +func (r *MappedRegion) mmap(f *os.File, offset int64, prot int) error { + _, err := mmap(r.addr, uintptr(r.size), + prot, unix.MAP_SHARED|unix.MAP_FIXED, + int(f.Fd()), offset) + r.used = err == nil + return err +} + +// We need the low level mmap for MAP_FIXED to work. +// Bind the syscall version hoping that it is more stable. + +//go:linkname mmap syscall.mmap +func mmap(addr, length uintptr, prot, flag, fd int, pos int64) (*byte, error) 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 new file mode 100644 index 000000000..1e81c9fd3 --- /dev/null +++ b/vendor/github.com/ncruces/go-sqlite3/internal/util/mmap_other.go @@ -0,0 +1,21 @@ +//go:build !(darwin || linux) || !(amd64 || arm64 || riscv64) || sqlite3_noshm || sqlite3_nosys + +package util + +import ( + "context" + + "github.com/tetratelabs/wazero/experimental" +) + +type mmapState struct{} + +func withAllocator(ctx context.Context) context.Context { + return experimental.WithMemoryAllocator(ctx, + experimental.MemoryAllocatorFunc(func(cap, max uint64) experimental.LinearMemory { + if cap == max { + return virtualAlloc(cap, max) + } + return sliceAlloc(cap, max) + })) +} diff --git a/vendor/github.com/ncruces/go-sqlite3/internal/util/module.go b/vendor/github.com/ncruces/go-sqlite3/internal/util/module.go new file mode 100644 index 000000000..22793e972 --- /dev/null +++ b/vendor/github.com/ncruces/go-sqlite3/internal/util/module.go @@ -0,0 +1,21 @@ +package util + +import ( + "context" + + "github.com/tetratelabs/wazero/experimental" +) + +type moduleKey struct{} +type moduleState struct { + mmapState + handleState +} + +func NewContext(ctx context.Context) context.Context { + state := new(moduleState) + ctx = withAllocator(ctx) + ctx = experimental.WithCloseNotifier(ctx, state) + ctx = context.WithValue(ctx, moduleKey{}, state) + return ctx +} diff --git a/vendor/github.com/ncruces/go-sqlite3/internal/util/pointer.go b/vendor/github.com/ncruces/go-sqlite3/internal/util/pointer.go new file mode 100644 index 000000000..eae4dae17 --- /dev/null +++ b/vendor/github.com/ncruces/go-sqlite3/internal/util/pointer.go @@ -0,0 +1,11 @@ +package util + +type Pointer[T any] struct{ Value T } + +func (p Pointer[T]) unwrap() any { return p.Value } + +type PointerUnwrap interface{ unwrap() any } + +func UnwrapPointer(p PointerUnwrap) any { + return p.unwrap() +} diff --git a/vendor/github.com/ncruces/go-sqlite3/internal/util/reflect.go b/vendor/github.com/ncruces/go-sqlite3/internal/util/reflect.go new file mode 100644 index 000000000..3104a7cf3 --- /dev/null +++ b/vendor/github.com/ncruces/go-sqlite3/internal/util/reflect.go @@ -0,0 +1,10 @@ +package util + +import "reflect" + +func ReflectType(v reflect.Value) reflect.Type { + if v.Kind() != reflect.Invalid { + return v.Type() + } + return nil +} diff --git a/vendor/github.com/ncruces/go-sqlite3/json.go b/vendor/github.com/ncruces/go-sqlite3/json.go new file mode 100644 index 000000000..9b2565e87 --- /dev/null +++ b/vendor/github.com/ncruces/go-sqlite3/json.go @@ -0,0 +1,11 @@ +package sqlite3 + +import "github.com/ncruces/go-sqlite3/internal/util" + +// JSON returns a value that can be used as an argument to +// [database/sql.DB.Exec], [database/sql.Row.Scan] and similar methods to +// store value as JSON, or decode JSON into value. +// JSON should NOT be used with [BindJSON] or [ResultJSON]. +func JSON(value any) any { + return util.JSON{Value: value} +} diff --git a/vendor/github.com/ncruces/go-sqlite3/pointer.go b/vendor/github.com/ncruces/go-sqlite3/pointer.go new file mode 100644 index 000000000..611c1528c --- /dev/null +++ b/vendor/github.com/ncruces/go-sqlite3/pointer.go @@ -0,0 +1,12 @@ +package sqlite3 + +import "github.com/ncruces/go-sqlite3/internal/util" + +// Pointer returns a pointer to a value that can be used as an argument to +// [database/sql.DB.Exec] and similar methods. +// Pointer should NOT be used with [BindPointer] or [ResultPointer]. +// +// https://sqlite.org/bindptr.html +func Pointer[T any](value T) any { + return util.Pointer[T]{Value: value} +} diff --git a/vendor/github.com/ncruces/go-sqlite3/quote.go b/vendor/github.com/ncruces/go-sqlite3/quote.go new file mode 100644 index 000000000..d1cd6fa87 --- /dev/null +++ b/vendor/github.com/ncruces/go-sqlite3/quote.go @@ -0,0 +1,112 @@ +package sqlite3 + +import ( + "bytes" + "math" + "strconv" + "strings" + "time" + "unsafe" + + "github.com/ncruces/go-sqlite3/internal/util" +) + +// Quote escapes and quotes a value +// making it safe to embed in SQL text. +func Quote(value any) string { + switch v := value.(type) { + case nil: + return "NULL" + case bool: + if v { + return "1" + } else { + return "0" + } + + case int: + return strconv.Itoa(v) + case int64: + return strconv.FormatInt(v, 10) + case float64: + switch { + case math.IsNaN(v): + return "NULL" + case math.IsInf(v, 1): + return "9.0e999" + case math.IsInf(v, -1): + return "-9.0e999" + } + return strconv.FormatFloat(v, 'g', -1, 64) + case time.Time: + return "'" + v.Format(time.RFC3339Nano) + "'" + + case string: + if strings.IndexByte(v, 0) >= 0 { + break + } + + buf := make([]byte, 2+len(v)+strings.Count(v, "'")) + buf[0] = '\'' + i := 1 + for _, b := range []byte(v) { + if b == '\'' { + buf[i] = b + i += 1 + } + buf[i] = b + i += 1 + } + buf[i] = '\'' + return unsafe.String(&buf[0], len(buf)) + + case []byte: + buf := make([]byte, 3+2*len(v)) + buf[0] = 'x' + buf[1] = '\'' + i := 2 + for _, b := range v { + const hex = "0123456789ABCDEF" + buf[i+0] = hex[b/16] + buf[i+1] = hex[b%16] + i += 2 + } + buf[i] = '\'' + return unsafe.String(&buf[0], len(buf)) + + case ZeroBlob: + if v > ZeroBlob(1e9-3)/2 { + break + } + + buf := bytes.Repeat([]byte("0"), int(3+2*int64(v))) + buf[0] = 'x' + buf[1] = '\'' + buf[len(buf)-1] = '\'' + return unsafe.String(&buf[0], len(buf)) + } + + panic(util.ValueErr) +} + +// QuoteIdentifier escapes and quotes an identifier +// making it safe to embed in SQL text. +func QuoteIdentifier(id string) string { + if strings.IndexByte(id, 0) >= 0 { + panic(util.ValueErr) + } + + buf := make([]byte, 2+len(id)+strings.Count(id, `"`)) + buf[0] = '"' + i := 1 + for _, b := range []byte(id) { + if b == '"' { + buf[i] = b + i += 1 + } + buf[i] = b + i += 1 + } + buf[i] = '"' + return unsafe.String(&buf[0], len(buf)) +} diff --git a/vendor/github.com/ncruces/go-sqlite3/sqlite.go b/vendor/github.com/ncruces/go-sqlite3/sqlite.go new file mode 100644 index 000000000..61a03652f --- /dev/null +++ b/vendor/github.com/ncruces/go-sqlite3/sqlite.go @@ -0,0 +1,341 @@ +// Package sqlite3 wraps the C SQLite API. +package sqlite3 + +import ( + "context" + "math" + "math/bits" + "os" + "sync" + "unsafe" + + "github.com/ncruces/go-sqlite3/internal/util" + "github.com/ncruces/go-sqlite3/vfs" + "github.com/tetratelabs/wazero" + "github.com/tetratelabs/wazero/api" +) + +// Configure SQLite Wasm. +// +// Importing package embed initializes [Binary] +// with an appropriate build of SQLite: +// +// import _ "github.com/ncruces/go-sqlite3/embed" +var ( + Binary []byte // Wasm binary to load. + Path string // Path to load the binary from. + + RuntimeConfig wazero.RuntimeConfig +) + +// Initialize decodes and compiles the SQLite Wasm binary. +// This is called implicitly when the first connection is openned, +// but is potentially slow, so you may want to call it at a more convenient time. +func Initialize() error { + instance.once.Do(compileSQLite) + return instance.err +} + +var instance struct { + runtime wazero.Runtime + compiled wazero.CompiledModule + err error + once sync.Once +} + +func compileSQLite() { + if RuntimeConfig == nil { + RuntimeConfig = wazero.NewRuntimeConfig() + } + + ctx := context.Background() + instance.runtime = wazero.NewRuntimeWithConfig(ctx, RuntimeConfig) + + env := instance.runtime.NewHostModuleBuilder("env") + env = vfs.ExportHostFunctions(env) + env = exportCallbacks(env) + _, instance.err = env.Instantiate(ctx) + if instance.err != nil { + return + } + + bin := Binary + if bin == nil && Path != "" { + bin, instance.err = os.ReadFile(Path) + if instance.err != nil { + return + } + } + if bin == nil { + instance.err = util.NoBinaryErr + return + } + + instance.compiled, instance.err = instance.runtime.CompileModule(ctx, bin) +} + +type sqlite struct { + ctx context.Context + mod api.Module + funcs struct { + fn [32]api.Function + id [32]*byte + mask uint32 + } + stack [8]uint64 + freer uint32 +} + +func instantiateSQLite() (sqlt *sqlite, err error) { + if err := Initialize(); err != nil { + return nil, err + } + + sqlt = new(sqlite) + sqlt.ctx = util.NewContext(context.Background()) + + sqlt.mod, err = instance.runtime.InstantiateModule(sqlt.ctx, + instance.compiled, wazero.NewModuleConfig().WithName("")) + if err != nil { + return nil, err + } + + global := sqlt.mod.ExportedGlobal("malloc_destructor") + if global == nil { + return nil, util.BadBinaryErr + } + + sqlt.freer = util.ReadUint32(sqlt.mod, uint32(global.Get())) + if sqlt.freer == 0 { + return nil, util.BadBinaryErr + } + return sqlt, nil +} + +func (sqlt *sqlite) close() error { + return sqlt.mod.Close(sqlt.ctx) +} + +func (sqlt *sqlite) error(rc uint64, handle uint32, sql ...string) error { + if rc == _OK { + return nil + } + + err := Error{code: rc} + + if err.Code() == NOMEM || err.ExtendedCode() == IOERR_NOMEM { + panic(util.OOMErr) + } + + if r := sqlt.call("sqlite3_errstr", rc); r != 0 { + err.str = util.ReadString(sqlt.mod, uint32(r), _MAX_NAME) + } + + if handle != 0 { + if r := sqlt.call("sqlite3_errmsg", uint64(handle)); r != 0 { + err.msg = util.ReadString(sqlt.mod, uint32(r), _MAX_LENGTH) + } + + if sql != nil { + if r := sqlt.call("sqlite3_error_offset", uint64(handle)); r != math.MaxUint32 { + err.sql = sql[0][r:] + } + } + } + + switch err.msg { + case err.str, "not an error": + err.msg = "" + } + return &err +} + +func (sqlt *sqlite) getfn(name string) api.Function { + c := &sqlt.funcs + p := unsafe.StringData(name) + for i := range c.id { + if c.id[i] == p { + c.id[i] = nil + c.mask &^= uint32(1) << i + return c.fn[i] + } + } + return sqlt.mod.ExportedFunction(name) +} + +func (sqlt *sqlite) putfn(name string, fn api.Function) { + c := &sqlt.funcs + p := unsafe.StringData(name) + i := bits.TrailingZeros32(^c.mask) + if i < 32 { + c.id[i] = p + c.fn[i] = fn + c.mask |= uint32(1) << i + } else { + c.id[0] = p + c.fn[0] = fn + c.mask = uint32(1) + } +} + +func (sqlt *sqlite) call(name string, params ...uint64) uint64 { + copy(sqlt.stack[:], params) + fn := sqlt.getfn(name) + err := fn.CallWithStack(sqlt.ctx, sqlt.stack[:]) + if err != nil { + panic(err) + } + sqlt.putfn(name, fn) + return sqlt.stack[0] +} + +func (sqlt *sqlite) free(ptr uint32) { + if ptr == 0 { + return + } + sqlt.call("free", uint64(ptr)) +} + +func (sqlt *sqlite) new(size uint64) uint32 { + if size > _MAX_ALLOCATION_SIZE { + panic(util.OOMErr) + } + ptr := uint32(sqlt.call("malloc", size)) + if ptr == 0 && size != 0 { + panic(util.OOMErr) + } + return ptr +} + +func (sqlt *sqlite) newBytes(b []byte) uint32 { + if (*[0]byte)(b) == nil { + return 0 + } + ptr := sqlt.new(uint64(len(b))) + util.WriteBytes(sqlt.mod, ptr, b) + return ptr +} + +func (sqlt *sqlite) newString(s string) uint32 { + ptr := sqlt.new(uint64(len(s) + 1)) + util.WriteString(sqlt.mod, ptr, s) + return ptr +} + +func (sqlt *sqlite) newArena(size uint64) arena { + // Ensure the arena's size is a multiple of 8. + size = (size + 7) &^ 7 + return arena{ + sqlt: sqlt, + size: uint32(size), + base: sqlt.new(size), + } +} + +type arena struct { + sqlt *sqlite + ptrs []uint32 + base uint32 + next uint32 + size uint32 +} + +func (a *arena) free() { + if a.sqlt == nil { + return + } + for _, ptr := range a.ptrs { + a.sqlt.free(ptr) + } + a.sqlt.free(a.base) + a.sqlt = nil +} + +func (a *arena) mark() (reset func()) { + ptrs := len(a.ptrs) + next := a.next + return func() { + for _, ptr := range a.ptrs[ptrs:] { + a.sqlt.free(ptr) + } + a.ptrs = a.ptrs[:ptrs] + a.next = next + } +} + +func (a *arena) new(size uint64) uint32 { + // Align the next address, to 4 or 8 bytes. + if size&7 != 0 { + a.next = (a.next + 3) &^ 3 + } else { + a.next = (a.next + 7) &^ 7 + } + if size <= uint64(a.size-a.next) { + ptr := a.base + a.next + a.next += uint32(size) + return ptr + } + ptr := a.sqlt.new(size) + a.ptrs = append(a.ptrs, ptr) + return ptr +} + +func (a *arena) bytes(b []byte) uint32 { + if (*[0]byte)(b) == nil { + return 0 + } + ptr := a.new(uint64(len(b))) + util.WriteBytes(a.sqlt.mod, ptr, b) + return ptr +} + +func (a *arena) string(s string) uint32 { + ptr := a.new(uint64(len(s) + 1)) + util.WriteString(a.sqlt.mod, ptr, s) + return ptr +} + +func exportCallbacks(env wazero.HostModuleBuilder) wazero.HostModuleBuilder { + util.ExportFuncII(env, "go_progress_handler", progressCallback) + util.ExportFuncIIII(env, "go_busy_timeout", timeoutCallback) + util.ExportFuncIII(env, "go_busy_handler", busyCallback) + util.ExportFuncII(env, "go_commit_hook", commitCallback) + util.ExportFuncVI(env, "go_rollback_hook", rollbackCallback) + util.ExportFuncVIIIIJ(env, "go_update_hook", updateCallback) + util.ExportFuncIIIII(env, "go_wal_hook", walCallback) + util.ExportFuncIIIIII(env, "go_autovacuum_pages", autoVacuumCallback) + util.ExportFuncIIIIIII(env, "go_authorizer", authorizerCallback) + util.ExportFuncVIII(env, "go_log", logCallback) + util.ExportFuncVI(env, "go_destroy", destroyCallback) + util.ExportFuncVIIII(env, "go_func", funcCallback) + util.ExportFuncVIIIII(env, "go_step", stepCallback) + util.ExportFuncVIII(env, "go_final", finalCallback) + util.ExportFuncVII(env, "go_value", valueCallback) + util.ExportFuncVIIII(env, "go_inverse", inverseCallback) + util.ExportFuncVIIII(env, "go_collation_needed", collationCallback) + util.ExportFuncIIIIII(env, "go_compare", compareCallback) + util.ExportFuncIIIIII(env, "go_vtab_create", vtabModuleCallback(xCreate)) + util.ExportFuncIIIIII(env, "go_vtab_connect", vtabModuleCallback(xConnect)) + util.ExportFuncII(env, "go_vtab_disconnect", vtabDisconnectCallback) + util.ExportFuncII(env, "go_vtab_destroy", vtabDestroyCallback) + util.ExportFuncIII(env, "go_vtab_best_index", vtabBestIndexCallback) + util.ExportFuncIIIII(env, "go_vtab_update", vtabUpdateCallback) + util.ExportFuncIII(env, "go_vtab_rename", vtabRenameCallback) + util.ExportFuncIIIII(env, "go_vtab_find_function", vtabFindFuncCallback) + util.ExportFuncII(env, "go_vtab_begin", vtabBeginCallback) + util.ExportFuncII(env, "go_vtab_sync", vtabSyncCallback) + util.ExportFuncII(env, "go_vtab_commit", vtabCommitCallback) + util.ExportFuncII(env, "go_vtab_rollback", vtabRollbackCallback) + util.ExportFuncIII(env, "go_vtab_savepoint", vtabSavepointCallback) + util.ExportFuncIII(env, "go_vtab_release", vtabReleaseCallback) + util.ExportFuncIII(env, "go_vtab_rollback_to", vtabRollbackToCallback) + util.ExportFuncIIIIII(env, "go_vtab_integrity", vtabIntegrityCallback) + util.ExportFuncIII(env, "go_cur_open", cursorOpenCallback) + util.ExportFuncII(env, "go_cur_close", cursorCloseCallback) + util.ExportFuncIIIIII(env, "go_cur_filter", cursorFilterCallback) + util.ExportFuncII(env, "go_cur_next", cursorNextCallback) + util.ExportFuncII(env, "go_cur_eof", cursorEOFCallback) + util.ExportFuncIIII(env, "go_cur_column", cursorColumnCallback) + util.ExportFuncIII(env, "go_cur_rowid", cursorRowIDCallback) + return env +} diff --git a/vendor/github.com/ncruces/go-sqlite3/stmt.go b/vendor/github.com/ncruces/go-sqlite3/stmt.go new file mode 100644 index 000000000..63c2085d0 --- /dev/null +++ b/vendor/github.com/ncruces/go-sqlite3/stmt.go @@ -0,0 +1,639 @@ +package sqlite3 + +import ( + "encoding/json" + "math" + "strconv" + "time" + + "github.com/ncruces/go-sqlite3/internal/util" +) + +// Stmt is a prepared statement object. +// +// https://sqlite.org/c3ref/stmt.html +type Stmt struct { + c *Conn + err error + handle uint32 +} + +// Close destroys the prepared statement object. +// +// It is safe to close a nil, zero or closed Stmt. +// +// https://sqlite.org/c3ref/finalize.html +func (s *Stmt) Close() error { + if s == nil || s.handle == 0 { + return nil + } + + r := s.c.call("sqlite3_finalize", uint64(s.handle)) + + s.handle = 0 + return s.c.error(r) +} + +// Conn returns the database connection to which the prepared statement belongs. +// +// https://sqlite.org/c3ref/db_handle.html +func (s *Stmt) Conn() *Conn { + return s.c +} + +// ReadOnly returns true if and only if the statement +// makes no direct changes to the content of the database file. +// +// https://sqlite.org/c3ref/stmt_readonly.html +func (s *Stmt) ReadOnly() bool { + r := s.c.call("sqlite3_stmt_readonly", uint64(s.handle)) + return r != 0 +} + +// Reset resets the prepared statement object. +// +// https://sqlite.org/c3ref/reset.html +func (s *Stmt) Reset() error { + r := s.c.call("sqlite3_reset", uint64(s.handle)) + s.err = nil + return s.c.error(r) +} + +// Busy determines if a prepared statement has been reset. +// +// https://sqlite.org/c3ref/stmt_busy.html +func (s *Stmt) Busy() bool { + r := s.c.call("sqlite3_stmt_busy", uint64(s.handle)) + return r != 0 +} + +// Step evaluates the SQL statement. +// If the SQL statement being executed returns any data, +// then true is returned each time a new row of data is ready for processing by the caller. +// The values may be accessed using the Column access functions. +// Step is called again to retrieve the next row of data. +// If an error has occurred, Step returns false; +// call [Stmt.Err] or [Stmt.Reset] to get the error. +// +// https://sqlite.org/c3ref/step.html +func (s *Stmt) Step() bool { + s.c.checkInterrupt() + r := s.c.call("sqlite3_step", uint64(s.handle)) + switch r { + case _ROW: + s.err = nil + return true + case _DONE: + s.err = nil + default: + s.err = s.c.error(r) + } + return false +} + +// Err gets the last error occurred during [Stmt.Step]. +// Err returns nil after [Stmt.Reset] is called. +// +// https://sqlite.org/c3ref/step.html +func (s *Stmt) Err() error { + return s.err +} + +// Exec is a convenience function that repeatedly calls [Stmt.Step] until it returns false, +// then calls [Stmt.Reset] to reset the statement and get any error that occurred. +func (s *Stmt) Exec() error { + for s.Step() { + } + return s.Reset() +} + +// Status monitors the performance characteristics of prepared statements. +// +// https://sqlite.org/c3ref/stmt_status.html +func (s *Stmt) Status(op StmtStatus, reset bool) int { + if op > STMTSTATUS_FILTER_HIT && op != STMTSTATUS_MEMUSED { + return 0 + } + var i uint64 + if reset { + i = 1 + } + r := s.c.call("sqlite3_stmt_status", uint64(s.handle), + uint64(op), i) + return int(int32(r)) +} + +// ClearBindings resets all bindings on the prepared statement. +// +// https://sqlite.org/c3ref/clear_bindings.html +func (s *Stmt) ClearBindings() error { + r := s.c.call("sqlite3_clear_bindings", uint64(s.handle)) + return s.c.error(r) +} + +// BindCount returns the number of SQL parameters in the prepared statement. +// +// https://sqlite.org/c3ref/bind_parameter_count.html +func (s *Stmt) BindCount() int { + r := s.c.call("sqlite3_bind_parameter_count", + uint64(s.handle)) + return int(int32(r)) +} + +// BindIndex returns the index of a parameter in the prepared statement +// given its name. +// +// https://sqlite.org/c3ref/bind_parameter_index.html +func (s *Stmt) BindIndex(name string) int { + defer s.c.arena.mark()() + namePtr := s.c.arena.string(name) + r := s.c.call("sqlite3_bind_parameter_index", + uint64(s.handle), uint64(namePtr)) + return int(int32(r)) +} + +// BindName returns the name of a parameter in the prepared statement. +// The leftmost SQL parameter has an index of 1. +// +// https://sqlite.org/c3ref/bind_parameter_name.html +func (s *Stmt) BindName(param int) string { + r := s.c.call("sqlite3_bind_parameter_name", + uint64(s.handle), uint64(param)) + + ptr := uint32(r) + if ptr == 0 { + return "" + } + return util.ReadString(s.c.mod, ptr, _MAX_NAME) +} + +// BindBool binds a bool to the prepared statement. +// The leftmost SQL parameter has an index of 1. +// SQLite does not have a separate boolean storage class. +// Instead, boolean values are stored as integers 0 (false) and 1 (true). +// +// https://sqlite.org/c3ref/bind_blob.html +func (s *Stmt) BindBool(param int, value bool) error { + var i int64 + if value { + i = 1 + } + return s.BindInt64(param, i) +} + +// BindInt binds an int to the prepared statement. +// The leftmost SQL parameter has an index of 1. +// +// https://sqlite.org/c3ref/bind_blob.html +func (s *Stmt) BindInt(param int, value int) error { + return s.BindInt64(param, int64(value)) +} + +// BindInt64 binds an int64 to the prepared statement. +// The leftmost SQL parameter has an index of 1. +// +// https://sqlite.org/c3ref/bind_blob.html +func (s *Stmt) BindInt64(param int, value int64) error { + r := s.c.call("sqlite3_bind_int64", + uint64(s.handle), uint64(param), uint64(value)) + return s.c.error(r) +} + +// BindFloat binds a float64 to the prepared statement. +// The leftmost SQL parameter has an index of 1. +// +// https://sqlite.org/c3ref/bind_blob.html +func (s *Stmt) BindFloat(param int, value float64) error { + r := s.c.call("sqlite3_bind_double", + uint64(s.handle), uint64(param), math.Float64bits(value)) + return s.c.error(r) +} + +// BindText binds a string to the prepared statement. +// The leftmost SQL parameter has an index of 1. +// +// https://sqlite.org/c3ref/bind_blob.html +func (s *Stmt) BindText(param int, value string) error { + if len(value) > _MAX_LENGTH { + return TOOBIG + } + ptr := s.c.newString(value) + r := s.c.call("sqlite3_bind_text64", + uint64(s.handle), uint64(param), + uint64(ptr), uint64(len(value)), + uint64(s.c.freer), _UTF8) + return s.c.error(r) +} + +// BindRawText binds a []byte to the prepared statement as text. +// The leftmost SQL parameter has an index of 1. +// +// https://sqlite.org/c3ref/bind_blob.html +func (s *Stmt) BindRawText(param int, value []byte) error { + if len(value) > _MAX_LENGTH { + return TOOBIG + } + ptr := s.c.newBytes(value) + r := s.c.call("sqlite3_bind_text64", + uint64(s.handle), uint64(param), + uint64(ptr), uint64(len(value)), + uint64(s.c.freer), _UTF8) + return s.c.error(r) +} + +// BindBlob binds a []byte to the prepared statement. +// The leftmost SQL parameter has an index of 1. +// Binding a nil slice is the same as calling [Stmt.BindNull]. +// +// https://sqlite.org/c3ref/bind_blob.html +func (s *Stmt) BindBlob(param int, value []byte) error { + if len(value) > _MAX_LENGTH { + return TOOBIG + } + ptr := s.c.newBytes(value) + r := s.c.call("sqlite3_bind_blob64", + uint64(s.handle), uint64(param), + uint64(ptr), uint64(len(value)), + uint64(s.c.freer)) + return s.c.error(r) +} + +// BindZeroBlob binds a zero-filled, length n BLOB to the prepared statement. +// The leftmost SQL parameter has an index of 1. +// +// https://sqlite.org/c3ref/bind_blob.html +func (s *Stmt) BindZeroBlob(param int, n int64) error { + r := s.c.call("sqlite3_bind_zeroblob64", + uint64(s.handle), uint64(param), uint64(n)) + return s.c.error(r) +} + +// BindNull binds a NULL to the prepared statement. +// The leftmost SQL parameter has an index of 1. +// +// https://sqlite.org/c3ref/bind_blob.html +func (s *Stmt) BindNull(param int) error { + r := s.c.call("sqlite3_bind_null", + uint64(s.handle), uint64(param)) + return s.c.error(r) +} + +// BindTime binds a [time.Time] to the prepared statement. +// The leftmost SQL parameter has an index of 1. +// +// https://sqlite.org/c3ref/bind_blob.html +func (s *Stmt) BindTime(param int, value time.Time, format TimeFormat) error { + if format == TimeFormatDefault { + return s.bindRFC3339Nano(param, value) + } + switch v := format.Encode(value).(type) { + case string: + s.BindText(param, v) + case int64: + s.BindInt64(param, v) + case float64: + s.BindFloat(param, v) + default: + panic(util.AssertErr()) + } + return nil +} + +func (s *Stmt) bindRFC3339Nano(param int, value time.Time) error { + const maxlen = uint64(len(time.RFC3339Nano)) + 5 + + ptr := s.c.new(maxlen) + buf := util.View(s.c.mod, ptr, maxlen) + buf = value.AppendFormat(buf[:0], time.RFC3339Nano) + + r := s.c.call("sqlite3_bind_text64", + uint64(s.handle), uint64(param), + uint64(ptr), uint64(len(buf)), + uint64(s.c.freer), _UTF8) + return s.c.error(r) +} + +// BindPointer binds a NULL to the prepared statement, just like [Stmt.BindNull], +// but it also associates ptr with that NULL value such that it can be retrieved +// within an application-defined SQL function using [Value.Pointer]. +// The leftmost SQL parameter has an index of 1. +// +// https://sqlite.org/c3ref/bind_blob.html +func (s *Stmt) BindPointer(param int, ptr any) error { + valPtr := util.AddHandle(s.c.ctx, ptr) + r := s.c.call("sqlite3_bind_pointer_go", + uint64(s.handle), uint64(param), uint64(valPtr)) + return s.c.error(r) +} + +// BindJSON binds the JSON encoding of value to the prepared statement. +// The leftmost SQL parameter has an index of 1. +// +// https://sqlite.org/c3ref/bind_blob.html +func (s *Stmt) BindJSON(param int, value any) error { + data, err := json.Marshal(value) + if err != nil { + return err + } + return s.BindRawText(param, data) +} + +// BindValue binds a copy of value to the prepared statement. +// The leftmost SQL parameter has an index of 1. +// +// https://sqlite.org/c3ref/bind_blob.html +func (s *Stmt) BindValue(param int, value Value) error { + if value.c != s.c { + return MISUSE + } + r := s.c.call("sqlite3_bind_value", + uint64(s.handle), uint64(param), uint64(value.handle)) + return s.c.error(r) +} + +// ColumnCount returns the number of columns in a result set. +// +// https://sqlite.org/c3ref/column_count.html +func (s *Stmt) ColumnCount() int { + r := s.c.call("sqlite3_column_count", + uint64(s.handle)) + return int(int32(r)) +} + +// ColumnName returns the name of the result column. +// The leftmost column of the result set has the index 0. +// +// https://sqlite.org/c3ref/column_name.html +func (s *Stmt) ColumnName(col int) string { + r := s.c.call("sqlite3_column_name", + uint64(s.handle), uint64(col)) + if r == 0 { + panic(util.OOMErr) + } + return util.ReadString(s.c.mod, uint32(r), _MAX_NAME) +} + +// ColumnType returns the initial [Datatype] of the result column. +// The leftmost column of the result set has the index 0. +// +// https://sqlite.org/c3ref/column_blob.html +func (s *Stmt) ColumnType(col int) Datatype { + r := s.c.call("sqlite3_column_type", + uint64(s.handle), uint64(col)) + return Datatype(r) +} + +// ColumnDeclType returns the declared datatype of the result column. +// The leftmost column of the result set has the index 0. +// +// https://sqlite.org/c3ref/column_decltype.html +func (s *Stmt) ColumnDeclType(col int) string { + r := s.c.call("sqlite3_column_decltype", + uint64(s.handle), uint64(col)) + if r == 0 { + return "" + } + return util.ReadString(s.c.mod, uint32(r), _MAX_NAME) +} + +// ColumnDatabaseName returns the name of the database +// that is the origin of a particular result column. +// The leftmost column of the result set has the index 0. +// +// https://sqlite.org/c3ref/column_database_name.html +func (s *Stmt) ColumnDatabaseName(col int) string { + r := s.c.call("sqlite3_column_database_name", + uint64(s.handle), uint64(col)) + if r == 0 { + return "" + } + return util.ReadString(s.c.mod, uint32(r), _MAX_NAME) +} + +// ColumnTableName returns the name of the table +// that is the origin of a particular result column. +// The leftmost column of the result set has the index 0. +// +// https://sqlite.org/c3ref/column_database_name.html +func (s *Stmt) ColumnTableName(col int) string { + r := s.c.call("sqlite3_column_table_name", + uint64(s.handle), uint64(col)) + if r == 0 { + return "" + } + return util.ReadString(s.c.mod, uint32(r), _MAX_NAME) +} + +// ColumnOriginName returns the name of the table column +// that is the origin of a particular result column. +// The leftmost column of the result set has the index 0. +// +// https://sqlite.org/c3ref/column_database_name.html +func (s *Stmt) ColumnOriginName(col int) string { + r := s.c.call("sqlite3_column_origin_name", + uint64(s.handle), uint64(col)) + if r == 0 { + return "" + } + return util.ReadString(s.c.mod, uint32(r), _MAX_NAME) +} + +// ColumnBool returns the value of the result column as a bool. +// The leftmost column of the result set has the index 0. +// SQLite does not have a separate boolean storage class. +// Instead, boolean values are retrieved as integers, +// with 0 converted to false and any other value to true. +// +// https://sqlite.org/c3ref/column_blob.html +func (s *Stmt) ColumnBool(col int) bool { + return s.ColumnInt64(col) != 0 +} + +// ColumnInt returns the value of the result column as an int. +// The leftmost column of the result set has the index 0. +// +// https://sqlite.org/c3ref/column_blob.html +func (s *Stmt) ColumnInt(col int) int { + return int(s.ColumnInt64(col)) +} + +// ColumnInt64 returns the value of the result column as an int64. +// The leftmost column of the result set has the index 0. +// +// https://sqlite.org/c3ref/column_blob.html +func (s *Stmt) ColumnInt64(col int) int64 { + r := s.c.call("sqlite3_column_int64", + uint64(s.handle), uint64(col)) + return int64(r) +} + +// ColumnFloat returns the value of the result column as a float64. +// The leftmost column of the result set has the index 0. +// +// https://sqlite.org/c3ref/column_blob.html +func (s *Stmt) ColumnFloat(col int) float64 { + r := s.c.call("sqlite3_column_double", + uint64(s.handle), uint64(col)) + return math.Float64frombits(r) +} + +// ColumnTime returns the value of the result column as a [time.Time]. +// The leftmost column of the result set has the index 0. +// +// https://sqlite.org/c3ref/column_blob.html +func (s *Stmt) ColumnTime(col int, format TimeFormat) time.Time { + var v any + switch s.ColumnType(col) { + case INTEGER: + v = s.ColumnInt64(col) + case FLOAT: + v = s.ColumnFloat(col) + case TEXT, BLOB: + v = s.ColumnText(col) + case NULL: + return time.Time{} + default: + panic(util.AssertErr()) + } + t, err := format.Decode(v) + if err != nil { + s.err = err + } + return t +} + +// ColumnText returns the value of the result column as a string. +// The leftmost column of the result set has the index 0. +// +// https://sqlite.org/c3ref/column_blob.html +func (s *Stmt) ColumnText(col int) string { + return string(s.ColumnRawText(col)) +} + +// ColumnBlob appends to buf and returns +// the value of the result column as a []byte. +// The leftmost column of the result set has the index 0. +// +// https://sqlite.org/c3ref/column_blob.html +func (s *Stmt) ColumnBlob(col int, buf []byte) []byte { + return append(buf, s.ColumnRawBlob(col)...) +} + +// ColumnRawText returns the value of the result column as a []byte. +// The []byte is owned by SQLite and may be invalidated by +// subsequent calls to [Stmt] methods. +// The leftmost column of the result set has the index 0. +// +// https://sqlite.org/c3ref/column_blob.html +func (s *Stmt) ColumnRawText(col int) []byte { + r := s.c.call("sqlite3_column_text", + uint64(s.handle), uint64(col)) + return s.columnRawBytes(col, uint32(r)) +} + +// ColumnRawBlob returns the value of the result column as a []byte. +// The []byte is owned by SQLite and may be invalidated by +// subsequent calls to [Stmt] methods. +// The leftmost column of the result set has the index 0. +// +// https://sqlite.org/c3ref/column_blob.html +func (s *Stmt) ColumnRawBlob(col int) []byte { + r := s.c.call("sqlite3_column_blob", + uint64(s.handle), uint64(col)) + return s.columnRawBytes(col, uint32(r)) +} + +func (s *Stmt) columnRawBytes(col int, ptr uint32) []byte { + if ptr == 0 { + r := s.c.call("sqlite3_errcode", uint64(s.c.handle)) + s.err = s.c.error(r) + return nil + } + + r := s.c.call("sqlite3_column_bytes", + uint64(s.handle), uint64(col)) + return util.View(s.c.mod, ptr, r) +} + +// ColumnJSON parses the JSON-encoded value of the result column +// and stores it in the value pointed to by ptr. +// The leftmost column of the result set has the index 0. +// +// https://sqlite.org/c3ref/column_blob.html +func (s *Stmt) ColumnJSON(col int, ptr any) error { + var data []byte + switch s.ColumnType(col) { + case NULL: + data = append(data, "null"...) + case TEXT: + data = s.ColumnRawText(col) + case BLOB: + data = s.ColumnRawBlob(col) + case INTEGER: + data = strconv.AppendInt(nil, s.ColumnInt64(col), 10) + case FLOAT: + data = strconv.AppendFloat(nil, s.ColumnFloat(col), 'g', -1, 64) + default: + panic(util.AssertErr()) + } + return json.Unmarshal(data, ptr) +} + +// ColumnValue returns the unprotected value of the result column. +// The leftmost column of the result set has the index 0. +// +// https://sqlite.org/c3ref/column_blob.html +func (s *Stmt) ColumnValue(col int) Value { + r := s.c.call("sqlite3_column_value", + uint64(s.handle), uint64(col)) + return Value{ + c: s.c, + unprot: true, + handle: uint32(r), + } +} + +// Columns populates result columns into the provided slice. +// The slice must have [Stmt.ColumnCount] length. +// +// [INTEGER] columns will be retrieved as int64 values, +// [FLOAT] as float64, [NULL] as nil, +// [TEXT] as string, and [BLOB] as []byte. +// Any []byte are owned by SQLite and may be invalidated by +// subsequent calls to [Stmt] methods. +func (s *Stmt) Columns(dest []any) error { + defer s.c.arena.mark()() + count := uint64(len(dest)) + typePtr := s.c.arena.new(count) + dataPtr := s.c.arena.new(8 * count) + + r := s.c.call("sqlite3_columns_go", + uint64(s.handle), count, uint64(typePtr), uint64(dataPtr)) + if err := s.c.error(r); err != nil { + return err + } + + types := util.View(s.c.mod, typePtr, count) + for i := range dest { + switch types[i] { + case byte(INTEGER): + dest[i] = int64(util.ReadUint64(s.c.mod, dataPtr+8*uint32(i))) + continue + case byte(FLOAT): + dest[i] = util.ReadFloat64(s.c.mod, dataPtr+8*uint32(i)) + continue + case byte(NULL): + dest[i] = nil + continue + } + ptr := util.ReadUint32(s.c.mod, dataPtr+8*uint32(i)+0) + len := util.ReadUint32(s.c.mod, dataPtr+8*uint32(i)+4) + buf := util.View(s.c.mod, ptr, uint64(len)) + if types[i] == byte(TEXT) { + dest[i] = string(buf) + } else { + dest[i] = buf + } + } + return nil +} diff --git a/vendor/github.com/ncruces/go-sqlite3/time.go b/vendor/github.com/ncruces/go-sqlite3/time.go new file mode 100644 index 000000000..0164a307b --- /dev/null +++ b/vendor/github.com/ncruces/go-sqlite3/time.go @@ -0,0 +1,354 @@ +package sqlite3 + +import ( + "math" + "strconv" + "strings" + "time" + + "github.com/ncruces/go-sqlite3/internal/util" + "github.com/ncruces/julianday" +) + +// TimeFormat specifies how to encode/decode time values. +// +// See the documentation for the [TimeFormatDefault] constant +// for formats recognized by SQLite. +// +// https://sqlite.org/lang_datefunc.html +type TimeFormat string + +// TimeFormats recognized by SQLite to encode/decode time values. +// +// https://sqlite.org/lang_datefunc.html#time_values +const ( + TimeFormatDefault TimeFormat = "" // time.RFC3339Nano + + // Text formats + TimeFormat1 TimeFormat = "2006-01-02" + TimeFormat2 TimeFormat = "2006-01-02 15:04" + TimeFormat3 TimeFormat = "2006-01-02 15:04:05" + TimeFormat4 TimeFormat = "2006-01-02 15:04:05.000" + TimeFormat5 TimeFormat = "2006-01-02T15:04" + TimeFormat6 TimeFormat = "2006-01-02T15:04:05" + TimeFormat7 TimeFormat = "2006-01-02T15:04:05.000" + TimeFormat8 TimeFormat = "15:04" + TimeFormat9 TimeFormat = "15:04:05" + TimeFormat10 TimeFormat = "15:04:05.000" + + TimeFormat2TZ = TimeFormat2 + "Z07:00" + TimeFormat3TZ = TimeFormat3 + "Z07:00" + TimeFormat4TZ = TimeFormat4 + "Z07:00" + TimeFormat5TZ = TimeFormat5 + "Z07:00" + TimeFormat6TZ = TimeFormat6 + "Z07:00" + TimeFormat7TZ = TimeFormat7 + "Z07:00" + TimeFormat8TZ = TimeFormat8 + "Z07:00" + TimeFormat9TZ = TimeFormat9 + "Z07:00" + TimeFormat10TZ = TimeFormat10 + "Z07:00" + + // Numeric formats + TimeFormatJulianDay TimeFormat = "julianday" + TimeFormatUnix TimeFormat = "unixepoch" + TimeFormatUnixFrac TimeFormat = "unixepoch_frac" + TimeFormatUnixMilli TimeFormat = "unixepoch_milli" // not an SQLite format + TimeFormatUnixMicro TimeFormat = "unixepoch_micro" // not an SQLite format + TimeFormatUnixNano TimeFormat = "unixepoch_nano" // not an SQLite format + + // Auto + TimeFormatAuto TimeFormat = "auto" +) + +// Encode encodes a time value using this format. +// +// [TimeFormatDefault] and [TimeFormatAuto] encode using [time.RFC3339Nano], +// with nanosecond accuracy, and preserving any timezone offset. +// +// This is the format used by the [database/sql] driver: +// [database/sql.Row.Scan] will decode as [time.Time] +// values encoded with [time.RFC3339Nano]. +// +// Time values encoded with [time.RFC3339Nano] cannot be sorted as strings +// to produce a time-ordered sequence. +// +// Assuming that the time zones of the time values are the same (e.g., all in UTC), +// and expressed using the same string (e.g., all "Z" or all "+00:00"), +// use the TIME [collating sequence] to produce a time-ordered sequence. +// +// Otherwise, use [TimeFormat7] for time-ordered encoding. +// +// Formats [TimeFormat1] through [TimeFormat10] +// convert time values to UTC before encoding. +// +// Returns a string for the text formats, +// a float64 for [TimeFormatJulianDay] and [TimeFormatUnixFrac], +// or an int64 for the other numeric formats. +// +// https://sqlite.org/lang_datefunc.html +// +// [collating sequence]: https://sqlite.org/datatype3.html#collating_sequences +func (f TimeFormat) Encode(t time.Time) any { + switch f { + // Numeric formats + case TimeFormatJulianDay: + return julianday.Float(t) + case TimeFormatUnix: + return t.Unix() + case TimeFormatUnixFrac: + return float64(t.Unix()) + float64(t.Nanosecond())*1e-9 + case TimeFormatUnixMilli: + return t.UnixMilli() + case TimeFormatUnixMicro: + return t.UnixMicro() + case TimeFormatUnixNano: + return t.UnixNano() + // Special formats. + case TimeFormatDefault, TimeFormatAuto: + f = time.RFC3339Nano + // SQLite assumes UTC if unspecified. + case + TimeFormat1, TimeFormat2, + TimeFormat3, TimeFormat4, + TimeFormat5, TimeFormat6, + TimeFormat7, TimeFormat8, + TimeFormat9, TimeFormat10: + t = t.UTC() + } + return t.Format(string(f)) +} + +// Decode decodes a time value using this format. +// +// The time value can be a string, an int64, or a float64. +// +// Formats [TimeFormat8] through [TimeFormat10] +// (and [TimeFormat8TZ] through [TimeFormat10TZ]) +// assume a date of 2000-01-01. +// +// The timezone indicator and fractional seconds are always optional +// for formats [TimeFormat2] through [TimeFormat10] +// (and [TimeFormat2TZ] through [TimeFormat10TZ]). +// +// [TimeFormatAuto] implements (and extends) the SQLite auto modifier. +// Julian day numbers are safe to use for historical dates, +// from 4712BC through 9999AD. +// Unix timestamps (expressed in seconds, milliseconds, microseconds, or nanoseconds) +// are safe to use for current events, from at least 1980 through at least 2260. +// Unix timestamps before 1980 and after 9999 may be misinterpreted as julian day numbers, +// or have the wrong time unit. +// +// https://sqlite.org/lang_datefunc.html +func (f TimeFormat) Decode(v any) (time.Time, error) { + switch f { + // Numeric formats. + case TimeFormatJulianDay: + switch v := v.(type) { + case string: + return julianday.Parse(v) + case float64: + return julianday.FloatTime(v), nil + case int64: + return julianday.Time(v, 0), nil + default: + return time.Time{}, util.TimeErr + } + + case TimeFormatUnix, TimeFormatUnixFrac: + if s, ok := v.(string); ok { + f, err := strconv.ParseFloat(s, 64) + if err != nil { + return time.Time{}, err + } + v = f + } + switch v := v.(type) { + case float64: + sec, frac := math.Modf(v) + nsec := math.Floor(frac * 1e9) + return time.Unix(int64(sec), int64(nsec)).UTC(), nil + case int64: + return time.Unix(v, 0).UTC(), nil + default: + return time.Time{}, util.TimeErr + } + + case TimeFormatUnixMilli: + if s, ok := v.(string); ok { + i, err := strconv.ParseInt(s, 10, 64) + if err != nil { + return time.Time{}, err + } + v = i + } + switch v := v.(type) { + case float64: + return time.UnixMilli(int64(math.Floor(v))).UTC(), nil + case int64: + return time.UnixMilli(v).UTC(), nil + default: + return time.Time{}, util.TimeErr + } + + case TimeFormatUnixMicro: + if s, ok := v.(string); ok { + i, err := strconv.ParseInt(s, 10, 64) + if err != nil { + return time.Time{}, err + } + v = i + } + switch v := v.(type) { + case float64: + return time.UnixMicro(int64(math.Floor(v))).UTC(), nil + case int64: + return time.UnixMicro(v).UTC(), nil + default: + return time.Time{}, util.TimeErr + } + + case TimeFormatUnixNano: + if s, ok := v.(string); ok { + i, err := strconv.ParseInt(s, 10, 64) + if err != nil { + return time.Time{}, util.TimeErr + } + v = i + } + switch v := v.(type) { + case float64: + return time.Unix(0, int64(math.Floor(v))).UTC(), nil + case int64: + return time.Unix(0, v).UTC(), nil + default: + return time.Time{}, util.TimeErr + } + + // Special formats. + case TimeFormatAuto: + switch s := v.(type) { + case string: + i, err := strconv.ParseInt(s, 10, 64) + if err == nil { + v = i + break + } + f, err := strconv.ParseFloat(s, 64) + if err == nil { + v = f + break + } + + dates := []TimeFormat{ + TimeFormat9, TimeFormat8, + TimeFormat6, TimeFormat5, + TimeFormat3, TimeFormat2, TimeFormat1, + } + for _, f := range dates { + t, err := f.Decode(s) + if err == nil { + return t, nil + } + } + } + switch v := v.(type) { + case float64: + if 0 <= v && v < 5373484.5 { + return TimeFormatJulianDay.Decode(v) + } + if v < 253402300800 { + return TimeFormatUnixFrac.Decode(v) + } + if v < 253402300800_000 { + return TimeFormatUnixMilli.Decode(v) + } + if v < 253402300800_000000 { + return TimeFormatUnixMicro.Decode(v) + } + return TimeFormatUnixNano.Decode(v) + case int64: + if 0 <= v && v < 5373485 { + return TimeFormatJulianDay.Decode(v) + } + if v < 253402300800 { + return TimeFormatUnixFrac.Decode(v) + } + if v < 253402300800_000 { + return TimeFormatUnixMilli.Decode(v) + } + if v < 253402300800_000000 { + return TimeFormatUnixMicro.Decode(v) + } + return TimeFormatUnixNano.Decode(v) + default: + return time.Time{}, util.TimeErr + } + + case + TimeFormat2, TimeFormat2TZ, + TimeFormat3, TimeFormat3TZ, + TimeFormat4, TimeFormat4TZ, + TimeFormat5, TimeFormat5TZ, + TimeFormat6, TimeFormat6TZ, + TimeFormat7, TimeFormat7TZ: + s, ok := v.(string) + if !ok { + return time.Time{}, util.TimeErr + } + return f.parseRelaxed(s) + + case + TimeFormat8, TimeFormat8TZ, + TimeFormat9, TimeFormat9TZ, + TimeFormat10, TimeFormat10TZ: + s, ok := v.(string) + if !ok { + return time.Time{}, util.TimeErr + } + t, err := f.parseRelaxed(s) + if err != nil { + return time.Time{}, err + } + return t.AddDate(2000, 0, 0), nil + + default: + s, ok := v.(string) + if !ok { + return time.Time{}, util.TimeErr + } + if f == "" { + f = time.RFC3339Nano + } + return time.Parse(string(f), s) + } +} + +func (f TimeFormat) parseRelaxed(s string) (time.Time, error) { + fs := string(f) + fs = strings.TrimSuffix(fs, "Z07:00") + fs = strings.TrimSuffix(fs, ".000") + t, err := time.Parse(fs+"Z07:00", s) + if err != nil { + return time.Parse(fs, s) + } + return t, nil +} + +// Scanner returns a [database/sql.Scanner] that can be used as an argument to +// [database/sql.Row.Scan] and similar methods to +// decode a time value into dest using this format. +func (f TimeFormat) Scanner(dest *time.Time) interface{ Scan(any) error } { + return timeScanner{dest, f} +} + +type timeScanner struct { + *time.Time + TimeFormat +} + +func (s timeScanner) Scan(src any) error { + var ok bool + var err error + if *s.Time, ok = src.(time.Time); !ok { + *s.Time, err = s.Decode(src) + } + return err +} diff --git a/vendor/github.com/ncruces/go-sqlite3/txn.go b/vendor/github.com/ncruces/go-sqlite3/txn.go new file mode 100644 index 000000000..0efbc2d80 --- /dev/null +++ b/vendor/github.com/ncruces/go-sqlite3/txn.go @@ -0,0 +1,294 @@ +package sqlite3 + +import ( + "context" + "errors" + "fmt" + "math/rand" + "runtime" + "strconv" + "strings" + + "github.com/ncruces/go-sqlite3/internal/util" + "github.com/tetratelabs/wazero/api" +) + +// Txn is an in-progress database transaction. +// +// https://sqlite.org/lang_transaction.html +type Txn struct { + c *Conn +} + +// Begin starts a deferred transaction. +// +// https://sqlite.org/lang_transaction.html +func (c *Conn) Begin() Txn { + // BEGIN even if interrupted. + err := c.txnExecInterrupted(`BEGIN DEFERRED`) + if err != nil { + panic(err) + } + return Txn{c} +} + +// BeginImmediate starts an immediate transaction. +// +// https://sqlite.org/lang_transaction.html +func (c *Conn) BeginImmediate() (Txn, error) { + err := c.Exec(`BEGIN IMMEDIATE`) + if err != nil { + return Txn{}, err + } + return Txn{c}, nil +} + +// BeginExclusive starts an exclusive transaction. +// +// https://sqlite.org/lang_transaction.html +func (c *Conn) BeginExclusive() (Txn, error) { + err := c.Exec(`BEGIN EXCLUSIVE`) + if err != nil { + return Txn{}, err + } + return Txn{c}, nil +} + +// End calls either [Txn.Commit] or [Txn.Rollback] +// depending on whether *error points to a nil or non-nil error. +// +// This is meant to be deferred: +// +// func doWork(db *sqlite3.Conn) (err error) { +// tx := db.Begin() +// defer tx.End(&err) +// +// // ... do work in the transaction +// } +// +// https://sqlite.org/lang_transaction.html +func (tx Txn) End(errp *error) { + recovered := recover() + if recovered != nil { + defer panic(recovered) + } + + if *errp == nil && recovered == nil { + // Success path. + if tx.c.GetAutocommit() { // There is nothing to commit. + return + } + *errp = tx.Commit() + if *errp == nil { + return + } + // Fall through to the error path. + } + + // Error path. + if tx.c.GetAutocommit() { // There is nothing to rollback. + return + } + err := tx.Rollback() + if err != nil { + panic(err) + } +} + +// Commit commits the transaction. +// +// https://sqlite.org/lang_transaction.html +func (tx Txn) Commit() error { + return tx.c.Exec(`COMMIT`) +} + +// Rollback rolls back the transaction, +// even if the connection has been interrupted. +// +// https://sqlite.org/lang_transaction.html +func (tx Txn) Rollback() error { + return tx.c.txnExecInterrupted(`ROLLBACK`) +} + +// Savepoint is a marker within a transaction +// that allows for partial rollback. +// +// https://sqlite.org/lang_savepoint.html +type Savepoint struct { + c *Conn + name string +} + +// Savepoint establishes a new transaction savepoint. +// +// https://sqlite.org/lang_savepoint.html +func (c *Conn) Savepoint() Savepoint { + // Names can be reused; this makes catching bugs more likely. + name := saveptName() + "_" + strconv.Itoa(int(rand.Int31())) + + err := c.txnExecInterrupted(fmt.Sprintf("SAVEPOINT %q;", name)) + if err != nil { + panic(err) + } + return Savepoint{c: c, name: name} +} + +func saveptName() (name string) { + defer func() { + if name == "" { + name = "sqlite3.Savepoint" + } + }() + + var pc [8]uintptr + n := runtime.Callers(3, pc[:]) + if n <= 0 { + return "" + } + frames := runtime.CallersFrames(pc[:n]) + frame, more := frames.Next() + for more && (strings.HasPrefix(frame.Function, "database/sql.") || + strings.HasPrefix(frame.Function, "github.com/ncruces/go-sqlite3/driver.")) { + frame, more = frames.Next() + } + return frame.Function +} + +// Release releases the savepoint rolling back any changes +// if *error points to a non-nil error. +// +// This is meant to be deferred: +// +// func doWork(db *sqlite3.Conn) (err error) { +// savept := db.Savepoint() +// defer savept.Release(&err) +// +// // ... do work in the transaction +// } +func (s Savepoint) Release(errp *error) { + recovered := recover() + if recovered != nil { + defer panic(recovered) + } + + if *errp == nil && recovered == nil { + // Success path. + if s.c.GetAutocommit() { // There is nothing to commit. + return + } + *errp = s.c.Exec(fmt.Sprintf("RELEASE %q;", s.name)) + if *errp == nil { + return + } + // Fall through to the error path. + } + + // Error path. + if s.c.GetAutocommit() { // There is nothing to rollback. + return + } + // ROLLBACK and RELEASE even if interrupted. + err := s.c.txnExecInterrupted(fmt.Sprintf(` + ROLLBACK TO %[1]q; + RELEASE %[1]q; + `, s.name)) + if err != nil { + panic(err) + } +} + +// Rollback rolls the transaction back to the savepoint, +// even if the connection has been interrupted. +// Rollback does not release the savepoint. +// +// https://sqlite.org/lang_transaction.html +func (s Savepoint) Rollback() error { + // ROLLBACK even if interrupted. + return s.c.txnExecInterrupted(fmt.Sprintf("ROLLBACK TO %q;", s.name)) +} + +func (c *Conn) txnExecInterrupted(sql string) error { + err := c.Exec(sql) + if errors.Is(err, INTERRUPT) { + old := c.SetInterrupt(context.Background()) + defer c.SetInterrupt(old) + err = c.Exec(sql) + } + return err +} + +// TxnState starts a deferred transaction. +// +// https://sqlite.org/c3ref/txn_state.html +func (c *Conn) TxnState(schema string) TxnState { + var ptr uint32 + if schema != "" { + defer c.arena.mark()() + ptr = c.arena.string(schema) + } + r := c.call("sqlite3_txn_state", uint64(c.handle), uint64(ptr)) + return TxnState(r) +} + +// CommitHook registers a callback function to be invoked +// whenever a transaction is committed. +// Return true to allow the commit operation to continue normally. +// +// https://sqlite.org/c3ref/commit_hook.html +func (c *Conn) CommitHook(cb func() (ok bool)) { + var enable uint64 + if cb != nil { + enable = 1 + } + c.call("sqlite3_commit_hook_go", uint64(c.handle), enable) + c.commit = cb +} + +// RollbackHook registers a callback function to be invoked +// whenever a transaction is rolled back. +// +// https://sqlite.org/c3ref/commit_hook.html +func (c *Conn) RollbackHook(cb func()) { + var enable uint64 + if cb != nil { + enable = 1 + } + c.call("sqlite3_rollback_hook_go", uint64(c.handle), enable) + c.rollback = cb +} + +// UpdateHook registers a callback function to be invoked +// whenever a row is updated, inserted or deleted in a rowid table. +// +// https://sqlite.org/c3ref/update_hook.html +func (c *Conn) UpdateHook(cb func(action AuthorizerActionCode, schema, table string, rowid int64)) { + var enable uint64 + if cb != nil { + enable = 1 + } + c.call("sqlite3_update_hook_go", uint64(c.handle), enable) + c.update = cb +} + +func commitCallback(ctx context.Context, mod api.Module, pDB uint32) (rollback uint32) { + if c, ok := ctx.Value(connKey{}).(*Conn); ok && c.handle == pDB && c.commit != nil { + if !c.commit() { + rollback = 1 + } + } + return rollback +} + +func rollbackCallback(ctx context.Context, mod api.Module, pDB uint32) { + if c, ok := ctx.Value(connKey{}).(*Conn); ok && c.handle == pDB && c.rollback != nil { + c.rollback() + } +} + +func updateCallback(ctx context.Context, mod api.Module, pDB uint32, action AuthorizerActionCode, zSchema, zTabName uint32, rowid uint64) { + if c, ok := ctx.Value(connKey{}).(*Conn); ok && c.handle == pDB && c.update != nil { + schema := util.ReadString(mod, zSchema, _MAX_NAME) + table := util.ReadString(mod, zTabName, _MAX_NAME) + c.update(action, schema, table, int64(rowid)) + } +} diff --git a/vendor/github.com/ncruces/go-sqlite3/util/osutil/open.go b/vendor/github.com/ncruces/go-sqlite3/util/osutil/open.go new file mode 100644 index 000000000..0242ad032 --- /dev/null +++ b/vendor/github.com/ncruces/go-sqlite3/util/osutil/open.go @@ -0,0 +1,16 @@ +//go:build !windows + +package osutil + +import ( + "io/fs" + "os" +) + +// OpenFile behaves the same as [os.OpenFile], +// except on Windows it sets [syscall.FILE_SHARE_DELETE]. +// +// See: https://go.dev/issue/32088#issuecomment-502850674 +func OpenFile(name string, flag int, perm fs.FileMode) (*os.File, error) { + return os.OpenFile(name, flag, perm) +} diff --git a/vendor/github.com/ncruces/go-sqlite3/util/osutil/open_windows.go b/vendor/github.com/ncruces/go-sqlite3/util/osutil/open_windows.go new file mode 100644 index 000000000..277f58bc3 --- /dev/null +++ b/vendor/github.com/ncruces/go-sqlite3/util/osutil/open_windows.go @@ -0,0 +1,112 @@ +package osutil + +import ( + "io/fs" + "os" + . "syscall" + "unsafe" +) + +// OpenFile behaves the same as [os.OpenFile], +// except on Windows it sets [syscall.FILE_SHARE_DELETE]. +// +// See: https://go.dev/issue/32088#issuecomment-502850674 +func OpenFile(name string, flag int, perm fs.FileMode) (*os.File, error) { + if name == "" { + return nil, &os.PathError{Op: "open", Path: name, Err: ENOENT} + } + r, e := syscallOpen(name, flag, uint32(perm.Perm())) + if e != nil { + return nil, &os.PathError{Op: "open", Path: name, Err: e} + } + return os.NewFile(uintptr(r), name), nil +} + +// syscallOpen is a copy of [syscall.Open] +// that uses [syscall.FILE_SHARE_DELETE]. +// +// https://go.dev/src/syscall/syscall_windows.go +func syscallOpen(path string, mode int, perm uint32) (fd Handle, err error) { + if len(path) == 0 { + return InvalidHandle, ERROR_FILE_NOT_FOUND + } + pathp, err := UTF16PtrFromString(path) + if err != nil { + return InvalidHandle, err + } + var access uint32 + switch mode & (O_RDONLY | O_WRONLY | O_RDWR) { + case O_RDONLY: + access = GENERIC_READ + case O_WRONLY: + access = GENERIC_WRITE + case O_RDWR: + access = GENERIC_READ | GENERIC_WRITE + } + if mode&O_CREAT != 0 { + access |= GENERIC_WRITE + } + if mode&O_APPEND != 0 { + access &^= GENERIC_WRITE + access |= FILE_APPEND_DATA + } + sharemode := uint32(FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE) + var sa *SecurityAttributes + if mode&O_CLOEXEC == 0 { + sa = makeInheritSa() + } + var createmode uint32 + switch { + case mode&(O_CREAT|O_EXCL) == (O_CREAT | O_EXCL): + createmode = CREATE_NEW + case mode&(O_CREAT|O_TRUNC) == (O_CREAT | O_TRUNC): + createmode = CREATE_ALWAYS + case mode&O_CREAT == O_CREAT: + createmode = OPEN_ALWAYS + case mode&O_TRUNC == O_TRUNC: + createmode = TRUNCATE_EXISTING + default: + createmode = OPEN_EXISTING + } + var attrs uint32 = FILE_ATTRIBUTE_NORMAL + if perm&S_IWRITE == 0 { + attrs = FILE_ATTRIBUTE_READONLY + if createmode == CREATE_ALWAYS { + const _ERROR_BAD_NETPATH = Errno(53) + // We have been asked to create a read-only file. + // If the file already exists, the semantics of + // the Unix open system call is to preserve the + // existing permissions. If we pass CREATE_ALWAYS + // and FILE_ATTRIBUTE_READONLY to CreateFile, + // and the file already exists, CreateFile will + // change the file permissions. + // Avoid that to preserve the Unix semantics. + h, e := CreateFile(pathp, access, sharemode, sa, TRUNCATE_EXISTING, FILE_ATTRIBUTE_NORMAL, 0) + switch e { + case ERROR_FILE_NOT_FOUND, _ERROR_BAD_NETPATH, ERROR_PATH_NOT_FOUND: + // File does not exist. These are the same + // errors as Errno.Is checks for ErrNotExist. + // Carry on to create the file. + default: + // Success or some different error. + return h, e + } + } + } + if createmode == OPEN_EXISTING && access == GENERIC_READ { + // Necessary for opening directory handles. + attrs |= FILE_FLAG_BACKUP_SEMANTICS + } + if mode&O_SYNC != 0 { + const _FILE_FLAG_WRITE_THROUGH = 0x80000000 + attrs |= _FILE_FLAG_WRITE_THROUGH + } + return CreateFile(pathp, access, sharemode, sa, createmode, attrs, 0) +} + +func makeInheritSa() *SecurityAttributes { + var sa SecurityAttributes + sa.Length = uint32(unsafe.Sizeof(sa)) + sa.InheritHandle = 1 + return &sa +} diff --git a/vendor/github.com/ncruces/go-sqlite3/util/osutil/osfs.go b/vendor/github.com/ncruces/go-sqlite3/util/osutil/osfs.go new file mode 100644 index 000000000..2e1195934 --- /dev/null +++ b/vendor/github.com/ncruces/go-sqlite3/util/osutil/osfs.go @@ -0,0 +1,33 @@ +package osutil + +import ( + "io/fs" + "os" +) + +// FS implements [fs.FS], [fs.StatFS], and [fs.ReadFileFS] +// using package [os]. +// +// This filesystem does not respect [fs.ValidPath] rules, +// and fails [testing/fstest.TestFS]! +// +// Still, it can be a useful tool to unify implementations +// that can access either the [os] filesystem or an [fs.FS]. +// It's OK to use this to open files, but you should avoid +// opening directories, resolving paths, or walking the file system. +type FS struct{} + +// Open implements [fs.FS]. +func (FS) Open(name string) (fs.File, error) { + return OpenFile(name, os.O_RDONLY, 0) +} + +// ReadFileFS implements [fs.StatFS]. +func (FS) Stat(name string) (fs.FileInfo, error) { + return os.Stat(name) +} + +// ReadFile implements [fs.ReadFileFS]. +func (FS) ReadFile(name string) ([]byte, error) { + return os.ReadFile(name) +} diff --git a/vendor/github.com/ncruces/go-sqlite3/util/osutil/osutil.go b/vendor/github.com/ncruces/go-sqlite3/util/osutil/osutil.go new file mode 100644 index 000000000..7fbd04787 --- /dev/null +++ b/vendor/github.com/ncruces/go-sqlite3/util/osutil/osutil.go @@ -0,0 +1,2 @@ +// Package osutil implements operating system utility functions. +package osutil diff --git a/vendor/github.com/ncruces/go-sqlite3/value.go b/vendor/github.com/ncruces/go-sqlite3/value.go new file mode 100644 index 000000000..61d3cbf70 --- /dev/null +++ b/vendor/github.com/ncruces/go-sqlite3/value.go @@ -0,0 +1,236 @@ +package sqlite3 + +import ( + "encoding/json" + "math" + "strconv" + "time" + + "github.com/ncruces/go-sqlite3/internal/util" +) + +// Value is any value that can be stored in a database table. +// +// https://sqlite.org/c3ref/value.html +type Value struct { + c *Conn + handle uint32 + unprot bool + copied bool +} + +func (v Value) protected() uint64 { + if v.unprot { + panic(util.ValueErr) + } + return uint64(v.handle) +} + +// Dup makes a copy of the SQL value and returns a pointer to that copy. +// +// https://sqlite.org/c3ref/value_dup.html +func (v Value) Dup() *Value { + r := v.c.call("sqlite3_value_dup", uint64(v.handle)) + return &Value{ + c: v.c, + copied: true, + handle: uint32(r), + } +} + +// Close frees an SQL value previously obtained by [Value.Dup]. +// +// https://sqlite.org/c3ref/value_dup.html +func (dup *Value) Close() error { + if !dup.copied { + panic(util.ValueErr) + } + dup.c.call("sqlite3_value_free", uint64(dup.handle)) + dup.handle = 0 + return nil +} + +// Type returns the initial datatype of the value. +// +// https://sqlite.org/c3ref/value_blob.html +func (v Value) Type() Datatype { + r := v.c.call("sqlite3_value_type", v.protected()) + return Datatype(r) +} + +// Type returns the numeric datatype of the value. +// +// https://sqlite.org/c3ref/value_blob.html +func (v Value) NumericType() Datatype { + r := v.c.call("sqlite3_value_numeric_type", v.protected()) + return Datatype(r) +} + +// Bool returns the value as a bool. +// SQLite does not have a separate boolean storage class. +// Instead, boolean values are retrieved as integers, +// with 0 converted to false and any other value to true. +// +// https://sqlite.org/c3ref/value_blob.html +func (v Value) Bool() bool { + return v.Int64() != 0 +} + +// Int returns the value as an int. +// +// https://sqlite.org/c3ref/value_blob.html +func (v Value) Int() int { + return int(v.Int64()) +} + +// Int64 returns the value as an int64. +// +// https://sqlite.org/c3ref/value_blob.html +func (v Value) Int64() int64 { + r := v.c.call("sqlite3_value_int64", v.protected()) + return int64(r) +} + +// Float returns the value as a float64. +// +// https://sqlite.org/c3ref/value_blob.html +func (v Value) Float() float64 { + r := v.c.call("sqlite3_value_double", v.protected()) + return math.Float64frombits(r) +} + +// Time returns the value as a [time.Time]. +// +// https://sqlite.org/c3ref/value_blob.html +func (v Value) Time(format TimeFormat) time.Time { + var a any + switch v.Type() { + case INTEGER: + a = v.Int64() + case FLOAT: + a = v.Float() + case TEXT, BLOB: + a = v.Text() + case NULL: + return time.Time{} + default: + panic(util.AssertErr()) + } + t, _ := format.Decode(a) + return t +} + +// Text returns the value as a string. +// +// https://sqlite.org/c3ref/value_blob.html +func (v Value) Text() string { + return string(v.RawText()) +} + +// Blob appends to buf and returns +// the value as a []byte. +// +// https://sqlite.org/c3ref/value_blob.html +func (v Value) Blob(buf []byte) []byte { + return append(buf, v.RawBlob()...) +} + +// RawText returns the value as a []byte. +// The []byte is owned by SQLite and may be invalidated by +// subsequent calls to [Value] methods. +// +// https://sqlite.org/c3ref/value_blob.html +func (v Value) RawText() []byte { + r := v.c.call("sqlite3_value_text", v.protected()) + return v.rawBytes(uint32(r)) +} + +// RawBlob returns the value as a []byte. +// The []byte is owned by SQLite and may be invalidated by +// subsequent calls to [Value] methods. +// +// https://sqlite.org/c3ref/value_blob.html +func (v Value) RawBlob() []byte { + r := v.c.call("sqlite3_value_blob", v.protected()) + return v.rawBytes(uint32(r)) +} + +func (v Value) rawBytes(ptr uint32) []byte { + if ptr == 0 { + return nil + } + + r := v.c.call("sqlite3_value_bytes", v.protected()) + return util.View(v.c.mod, ptr, r) +} + +// Pointer gets the pointer associated with this value, +// or nil if it has no associated pointer. +func (v Value) Pointer() any { + r := v.c.call("sqlite3_value_pointer_go", v.protected()) + return util.GetHandle(v.c.ctx, uint32(r)) +} + +// JSON parses a JSON-encoded value +// and stores the result in the value pointed to by ptr. +func (v Value) JSON(ptr any) error { + var data []byte + switch v.Type() { + case NULL: + data = append(data, "null"...) + case TEXT: + data = v.RawText() + case BLOB: + data = v.RawBlob() + case INTEGER: + data = strconv.AppendInt(nil, v.Int64(), 10) + case FLOAT: + data = strconv.AppendFloat(nil, v.Float(), 'g', -1, 64) + default: + panic(util.AssertErr()) + } + return json.Unmarshal(data, ptr) +} + +// NoChange returns true if and only if the value is unchanged +// in a virtual table update operatiom. +// +// https://sqlite.org/c3ref/value_blob.html +func (v Value) NoChange() bool { + r := v.c.call("sqlite3_value_nochange", v.protected()) + return r != 0 +} + +// InFirst returns the first element +// on the right-hand side of an IN constraint. +// +// https://sqlite.org/c3ref/vtab_in_first.html +func (v Value) InFirst() (Value, error) { + defer v.c.arena.mark()() + valPtr := v.c.arena.new(ptrlen) + r := v.c.call("sqlite3_vtab_in_first", uint64(v.handle), uint64(valPtr)) + if err := v.c.error(r); err != nil { + return Value{}, err + } + return Value{ + c: v.c, + handle: util.ReadUint32(v.c.mod, valPtr), + }, nil +} + +// InNext returns the next element +// on the right-hand side of an IN constraint. +// +// https://sqlite.org/c3ref/vtab_in_first.html +func (v Value) InNext() (Value, error) { + defer v.c.arena.mark()() + valPtr := v.c.arena.new(ptrlen) + r := v.c.call("sqlite3_vtab_in_next", uint64(v.handle), uint64(valPtr)) + if err := v.c.error(r); err != nil { + return Value{}, err + } + return Value{ + c: v.c, + handle: util.ReadUint32(v.c.mod, valPtr), + }, nil +} diff --git a/vendor/github.com/ncruces/go-sqlite3/vfs/README.md b/vendor/github.com/ncruces/go-sqlite3/vfs/README.md new file mode 100644 index 000000000..88059a41b --- /dev/null +++ b/vendor/github.com/ncruces/go-sqlite3/vfs/README.md @@ -0,0 +1,86 @@ +# Go SQLite VFS API + +This package implements the SQLite [OS Interface](https://sqlite.org/vfs.html) (aka VFS). + +It replaces the default SQLite VFS with a **pure Go** implementation, +and exposes [interfaces](https://pkg.go.dev/github.com/ncruces/go-sqlite3/vfs#VFS) +that should allow you to implement your own custom VFSes. + +Since it is a from scratch reimplementation, +there are naturally some ways it deviates from the original. + +The main differences are [file locking](#file-locking) and [WAL mode](#write-ahead-logging) support. + +### File Locking + +POSIX advisory locks, which SQLite uses on Unix, are +[broken by design](https://github.com/sqlite/sqlite/blob/b74eb0/src/os_unix.c#L1073-L1161). + +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. + +This module can also use +[BSD locks](https://man.freebsd.org/cgi/man.cgi?query=flock&sektion=2), +albeit with reduced concurrency (`BEGIN IMMEDIATE` behaves like `BEGIN EXCLUSIVE`). +On BSD, macOS, and illumos, BSD locks are fully compatible with POSIX advisory locks; +on Linux and z/OS, they are fully functional, but incompatible; +elsewhere, they are very likely broken. +BSD locks are the default on BSD and illumos, +but you can opt into them with the `sqlite3_flock` build tag. + +On Windows, this module uses `LockFileEx` and `UnlockFileEx`, +like SQLite. + +Otherwise, 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 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). + +You can use [`vfs.SupportsFileLocking`](https://pkg.go.dev/github.com/ncruces/go-sqlite3/vfs#SupportsFileLocking) +to check if your build supports file locking. + +### Write-Ahead Logging + +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. + +To allow `mmap` to work, each connection needs to reserve up to 4GB of address space. +To limit the address space each connection reserves, +use [`WithMemoryLimitPages`](../tests/testcfg/testcfg.go). + +Otherwise, [WAL support is limited](https://sqlite.org/wal.html#noshm), +and `EXCLUSIVE` locking mode must 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). + +You can use [`vfs.SupportsSharedMemory`](https://pkg.go.dev/github.com/ncruces/go-sqlite3/vfs#SupportsSharedMemory) +to check if your build 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. + +### Build Tags + +The VFS can be customized with a few build tags: +- `sqlite3_flock` forces the use of BSD locks; it can be used on z/OS to enable locking, + and elsewhere to test BSD locks. +- `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. + +> [!IMPORTANT] +> The default configuration of this package is compatible with +> the standard [Unix and Windows SQLite VFSes](https://sqlite.org/vfs.html#multiple_vfses); +> `sqlite3_flock` is compatible with the [`unix-flock` VFS](https://sqlite.org/compile.html#enable_locking_style). +> If incompatible file locking is used, accessing databases concurrently with _other_ SQLite libraries +> will eventually corrupt data. diff --git a/vendor/github.com/ncruces/go-sqlite3/vfs/api.go b/vendor/github.com/ncruces/go-sqlite3/vfs/api.go new file mode 100644 index 000000000..19c22ae8f --- /dev/null +++ b/vendor/github.com/ncruces/go-sqlite3/vfs/api.go @@ -0,0 +1,175 @@ +// Package vfs wraps the C SQLite VFS API. +package vfs + +import ( + "context" + "io" + + "github.com/tetratelabs/wazero/api" +) + +// A VFS defines the interface between the SQLite core and the underlying operating system. +// +// Use sqlite3.ErrorCode or sqlite3.ExtendedErrorCode to return specific error codes to SQLite. +// +// https://sqlite.org/c3ref/vfs.html +type VFS interface { + Open(name string, flags OpenFlag) (File, OpenFlag, error) + Delete(name string, syncDir bool) error + Access(name string, flags AccessFlag) (bool, error) + FullPathname(name string) (string, error) +} + +// VFSFilename extends VFS with the ability to use Filename +// objects for opening files. +// +// https://sqlite.org/c3ref/filename.html +type VFSFilename interface { + VFS + OpenFilename(name *Filename, flags OpenFlag) (File, OpenFlag, error) +} + +// A File represents an open file in the OS interface layer. +// +// Use sqlite3.ErrorCode or sqlite3.ExtendedErrorCode to return specific error codes to SQLite. +// In particular, sqlite3.BUSY is necessary to correctly implement lock methods. +// +// https://sqlite.org/c3ref/io_methods.html +type File interface { + Close() error + ReadAt(p []byte, off int64) (n int, err error) + WriteAt(p []byte, off int64) (n int, err error) + Truncate(size int64) error + Sync(flags SyncFlag) error + Size() (int64, error) + Lock(lock LockLevel) error + Unlock(lock LockLevel) error + CheckReservedLock() (bool, error) + SectorSize() int + DeviceCharacteristics() DeviceCharacteristic +} + +// FileLockState extends File to implement the +// SQLITE_FCNTL_LOCKSTATE file control opcode. +// +// https://sqlite.org/c3ref/c_fcntl_begin_atomic_write.html#sqlitefcntllockstate +type FileLockState interface { + File + 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. +// +// https://sqlite.org/c3ref/c_fcntl_begin_atomic_write.html#sqlitefcntlsizehint +type FileSizeHint interface { + File + SizeHint(size int64) error +} + +// FileHasMoved extends File to implement the +// SQLITE_FCNTL_HAS_MOVED file control opcode. +// +// https://sqlite.org/c3ref/c_fcntl_begin_atomic_write.html#sqlitefcntlhasmoved +type FileHasMoved interface { + File + HasMoved() (bool, error) +} + +// FileOverwrite extends File to implement the +// SQLITE_FCNTL_OVERWRITE file control opcode. +// +// https://sqlite.org/c3ref/c_fcntl_begin_atomic_write.html#sqlitefcntloverwrite +type FileOverwrite interface { + File + Overwrite() error +} + +// FilePersistentWAL extends File to implement the +// SQLITE_FCNTL_PERSIST_WAL file control opcode. +// +// https://sqlite.org/c3ref/c_fcntl_begin_atomic_write.html#sqlitefcntlpersistwal +type FilePersistentWAL interface { + File + PersistentWAL() bool + SetPersistentWAL(bool) +} + +// FilePowersafeOverwrite extends File to implement the +// SQLITE_FCNTL_POWERSAFE_OVERWRITE file control opcode. +// +// https://sqlite.org/c3ref/c_fcntl_begin_atomic_write.html#sqlitefcntlpowersafeoverwrite +type FilePowersafeOverwrite interface { + File + PowersafeOverwrite() bool + SetPowersafeOverwrite(bool) +} + +// FileCommitPhaseTwo extends File to implement the +// SQLITE_FCNTL_COMMIT_PHASETWO file control opcode. +// +// https://sqlite.org/c3ref/c_fcntl_begin_atomic_write.html#sqlitefcntlcommitphasetwo +type FileCommitPhaseTwo interface { + File + CommitPhaseTwo() error +} + +// FileBatchAtomicWrite extends File to implement the +// SQLITE_FCNTL_BEGIN_ATOMIC_WRITE, SQLITE_FCNTL_COMMIT_ATOMIC_WRITE +// and SQLITE_FCNTL_ROLLBACK_ATOMIC_WRITE file control opcodes. +// +// https://sqlite.org/c3ref/c_fcntl_begin_atomic_write.html#sqlitefcntlbeginatomicwrite +type FileBatchAtomicWrite interface { + File + BeginAtomicWrite() error + CommitAtomicWrite() error + RollbackAtomicWrite() error +} + +// 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 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 new file mode 100644 index 000000000..7f409f35f --- /dev/null +++ b/vendor/github.com/ncruces/go-sqlite3/vfs/const.go @@ -0,0 +1,234 @@ +package vfs + +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 +type _ErrorCode uint32 + +func (e _ErrorCode) Error() string { + return util.ErrorCodeString(uint32(e)) +} + +const ( + _OK _ErrorCode = util.OK + _ERROR _ErrorCode = util.ERROR + _PERM _ErrorCode = util.PERM + _BUSY _ErrorCode = util.BUSY + _READONLY _ErrorCode = util.READONLY + _IOERR _ErrorCode = util.IOERR + _NOTFOUND _ErrorCode = util.NOTFOUND + _CANTOPEN _ErrorCode = util.CANTOPEN + _IOERR_READ _ErrorCode = util.IOERR_READ + _IOERR_SHORT_READ _ErrorCode = util.IOERR_SHORT_READ + _IOERR_WRITE _ErrorCode = util.IOERR_WRITE + _IOERR_FSYNC _ErrorCode = util.IOERR_FSYNC + _IOERR_DIR_FSYNC _ErrorCode = util.IOERR_DIR_FSYNC + _IOERR_TRUNCATE _ErrorCode = util.IOERR_TRUNCATE + _IOERR_FSTAT _ErrorCode = util.IOERR_FSTAT + _IOERR_UNLOCK _ErrorCode = util.IOERR_UNLOCK + _IOERR_RDLOCK _ErrorCode = util.IOERR_RDLOCK + _IOERR_DELETE _ErrorCode = util.IOERR_DELETE + _IOERR_ACCESS _ErrorCode = util.IOERR_ACCESS + _IOERR_CHECKRESERVEDLOCK _ErrorCode = util.IOERR_CHECKRESERVEDLOCK + _IOERR_LOCK _ErrorCode = util.IOERR_LOCK + _IOERR_CLOSE _ErrorCode = util.IOERR_CLOSE + _IOERR_SHMOPEN _ErrorCode = util.IOERR_SHMOPEN + _IOERR_SHMSIZE _ErrorCode = util.IOERR_SHMSIZE + _IOERR_SHMLOCK _ErrorCode = util.IOERR_SHMLOCK + _IOERR_SHMMAP _ErrorCode = util.IOERR_SHMMAP + _IOERR_SEEK _ErrorCode = util.IOERR_SEEK + _IOERR_DELETE_NOENT _ErrorCode = util.IOERR_DELETE_NOENT + _IOERR_BEGIN_ATOMIC _ErrorCode = util.IOERR_BEGIN_ATOMIC + _IOERR_COMMIT_ATOMIC _ErrorCode = util.IOERR_COMMIT_ATOMIC + _IOERR_ROLLBACK_ATOMIC _ErrorCode = util.IOERR_ROLLBACK_ATOMIC + _CANTOPEN_FULLPATH _ErrorCode = util.CANTOPEN_FULLPATH + _CANTOPEN_ISDIR _ErrorCode = util.CANTOPEN_ISDIR + _READONLY_CANTINIT _ErrorCode = util.READONLY_CANTINIT + _OK_SYMLINK _ErrorCode = util.OK_SYMLINK +) + +// OpenFlag is a flag for the [VFS] Open method. +// +// https://sqlite.org/c3ref/c_open_autoproxy.html +type OpenFlag uint32 + +const ( + OPEN_READONLY OpenFlag = 0x00000001 /* Ok for sqlite3_open_v2() */ + OPEN_READWRITE OpenFlag = 0x00000002 /* Ok for sqlite3_open_v2() */ + OPEN_CREATE OpenFlag = 0x00000004 /* Ok for sqlite3_open_v2() */ + OPEN_DELETEONCLOSE OpenFlag = 0x00000008 /* VFS only */ + OPEN_EXCLUSIVE OpenFlag = 0x00000010 /* VFS only */ + OPEN_AUTOPROXY OpenFlag = 0x00000020 /* VFS only */ + OPEN_URI OpenFlag = 0x00000040 /* Ok for sqlite3_open_v2() */ + OPEN_MEMORY OpenFlag = 0x00000080 /* Ok for sqlite3_open_v2() */ + OPEN_MAIN_DB OpenFlag = 0x00000100 /* VFS only */ + OPEN_TEMP_DB OpenFlag = 0x00000200 /* VFS only */ + OPEN_TRANSIENT_DB OpenFlag = 0x00000400 /* VFS only */ + OPEN_MAIN_JOURNAL OpenFlag = 0x00000800 /* VFS only */ + OPEN_TEMP_JOURNAL OpenFlag = 0x00001000 /* VFS only */ + OPEN_SUBJOURNAL OpenFlag = 0x00002000 /* VFS only */ + OPEN_SUPER_JOURNAL OpenFlag = 0x00004000 /* VFS only */ + OPEN_NOMUTEX OpenFlag = 0x00008000 /* Ok for sqlite3_open_v2() */ + OPEN_FULLMUTEX OpenFlag = 0x00010000 /* Ok for sqlite3_open_v2() */ + OPEN_SHAREDCACHE OpenFlag = 0x00020000 /* Ok for sqlite3_open_v2() */ + OPEN_PRIVATECACHE OpenFlag = 0x00040000 /* Ok for sqlite3_open_v2() */ + OPEN_WAL OpenFlag = 0x00080000 /* VFS only */ + OPEN_NOFOLLOW OpenFlag = 0x01000000 /* Ok for sqlite3_open_v2() */ +) + +// AccessFlag is a flag for the [VFS] Access method. +// +// https://sqlite.org/c3ref/c_access_exists.html +type AccessFlag uint32 + +const ( + ACCESS_EXISTS AccessFlag = 0 + ACCESS_READWRITE AccessFlag = 1 /* Used by PRAGMA temp_store_directory */ + ACCESS_READ AccessFlag = 2 /* Unused */ +) + +// SyncFlag is a flag for the [File] Sync method. +// +// https://sqlite.org/c3ref/c_sync_dataonly.html +type SyncFlag uint32 + +const ( + SYNC_NORMAL SyncFlag = 0x00002 + SYNC_FULL SyncFlag = 0x00003 + SYNC_DATAONLY SyncFlag = 0x00010 +) + +// LockLevel is a value used with [File] Lock and Unlock methods. +// +// https://sqlite.org/c3ref/c_lock_exclusive.html +type LockLevel uint32 + +const ( + // No locks are held on the database. + // The database may be neither read nor written. + // Any internally cached data is considered suspect and subject to + // verification against the database file before being used. + // Other processes can read or write the database as their own locking + // states permit. + // This is the default state. + LOCK_NONE LockLevel = 0 /* xUnlock() only */ + + // The database may be read but not written. + // Any number of processes can hold SHARED locks at the same time, + // hence there can be many simultaneous readers. + // But no other thread or process is allowed to write to the database file + // while one or more SHARED locks are active. + LOCK_SHARED LockLevel = 1 /* xLock() or xUnlock() */ + + // A RESERVED lock means that the process is planning on writing to the + // database file at some point in the future but that it is currently just + // reading from the file. + // Only a single RESERVED lock may be active at one time, + // though multiple SHARED locks can coexist with a single RESERVED lock. + // RESERVED differs from PENDING in that new SHARED locks can be acquired + // while there is a RESERVED lock. + LOCK_RESERVED LockLevel = 2 /* xLock() only */ + + // A PENDING lock means that the process holding the lock wants to write to + // the database as soon as possible and is just waiting on all current + // SHARED locks to clear so that it can get an EXCLUSIVE lock. + // No new SHARED locks are permitted against the database if a PENDING lock + // is active, though existing SHARED locks are allowed to continue. + LOCK_PENDING LockLevel = 3 /* internal use only */ + + // An EXCLUSIVE lock is needed in order to write to the database file. + // Only one EXCLUSIVE lock is allowed on the file and no other locks of any + // kind are allowed to coexist with an EXCLUSIVE lock. + // In order to maximize concurrency, SQLite works to minimize the amount of + // time that EXCLUSIVE locks are held. + LOCK_EXCLUSIVE LockLevel = 4 /* xLock() only */ +) + +// DeviceCharacteristic is a flag retuned by the [File] DeviceCharacteristics method. +// +// https://sqlite.org/c3ref/c_iocap_atomic.html +type DeviceCharacteristic uint32 + +const ( + IOCAP_ATOMIC DeviceCharacteristic = 0x00000001 + IOCAP_ATOMIC512 DeviceCharacteristic = 0x00000002 + IOCAP_ATOMIC1K DeviceCharacteristic = 0x00000004 + IOCAP_ATOMIC2K DeviceCharacteristic = 0x00000008 + IOCAP_ATOMIC4K DeviceCharacteristic = 0x00000010 + IOCAP_ATOMIC8K DeviceCharacteristic = 0x00000020 + IOCAP_ATOMIC16K DeviceCharacteristic = 0x00000040 + IOCAP_ATOMIC32K DeviceCharacteristic = 0x00000080 + IOCAP_ATOMIC64K DeviceCharacteristic = 0x00000100 + IOCAP_SAFE_APPEND DeviceCharacteristic = 0x00000200 + IOCAP_SEQUENTIAL DeviceCharacteristic = 0x00000400 + IOCAP_UNDELETABLE_WHEN_OPEN DeviceCharacteristic = 0x00000800 + IOCAP_POWERSAFE_OVERWRITE DeviceCharacteristic = 0x00001000 + IOCAP_IMMUTABLE DeviceCharacteristic = 0x00002000 + IOCAP_BATCH_ATOMIC DeviceCharacteristic = 0x00004000 +) + +// https://sqlite.org/c3ref/c_fcntl_begin_atomic_write.html +type _FcntlOpcode uint32 + +const ( + _FCNTL_LOCKSTATE _FcntlOpcode = 1 + _FCNTL_GET_LOCKPROXYFILE _FcntlOpcode = 2 + _FCNTL_SET_LOCKPROXYFILE _FcntlOpcode = 3 + _FCNTL_LAST_ERRNO _FcntlOpcode = 4 + _FCNTL_SIZE_HINT _FcntlOpcode = 5 + _FCNTL_CHUNK_SIZE _FcntlOpcode = 6 + _FCNTL_FILE_POINTER _FcntlOpcode = 7 + _FCNTL_SYNC_OMITTED _FcntlOpcode = 8 + _FCNTL_WIN32_AV_RETRY _FcntlOpcode = 9 + _FCNTL_PERSIST_WAL _FcntlOpcode = 10 + _FCNTL_OVERWRITE _FcntlOpcode = 11 + _FCNTL_VFSNAME _FcntlOpcode = 12 + _FCNTL_POWERSAFE_OVERWRITE _FcntlOpcode = 13 + _FCNTL_PRAGMA _FcntlOpcode = 14 + _FCNTL_BUSYHANDLER _FcntlOpcode = 15 + _FCNTL_TEMPFILENAME _FcntlOpcode = 16 + _FCNTL_MMAP_SIZE _FcntlOpcode = 18 + _FCNTL_TRACE _FcntlOpcode = 19 + _FCNTL_HAS_MOVED _FcntlOpcode = 20 + _FCNTL_SYNC _FcntlOpcode = 21 + _FCNTL_COMMIT_PHASETWO _FcntlOpcode = 22 + _FCNTL_WIN32_SET_HANDLE _FcntlOpcode = 23 + _FCNTL_WAL_BLOCK _FcntlOpcode = 24 + _FCNTL_ZIPVFS _FcntlOpcode = 25 + _FCNTL_RBU _FcntlOpcode = 26 + _FCNTL_VFS_POINTER _FcntlOpcode = 27 + _FCNTL_JOURNAL_POINTER _FcntlOpcode = 28 + _FCNTL_WIN32_GET_HANDLE _FcntlOpcode = 29 + _FCNTL_PDB _FcntlOpcode = 30 + _FCNTL_BEGIN_ATOMIC_WRITE _FcntlOpcode = 31 + _FCNTL_COMMIT_ATOMIC_WRITE _FcntlOpcode = 32 + _FCNTL_ROLLBACK_ATOMIC_WRITE _FcntlOpcode = 33 + _FCNTL_LOCK_TIMEOUT _FcntlOpcode = 34 + _FCNTL_DATA_VERSION _FcntlOpcode = 35 + _FCNTL_SIZE_LIMIT _FcntlOpcode = 36 + _FCNTL_CKPT_DONE _FcntlOpcode = 37 + _FCNTL_RESERVE_BYTES _FcntlOpcode = 38 + _FCNTL_CKPT_START _FcntlOpcode = 39 + _FCNTL_EXTERNAL_READER _FcntlOpcode = 40 + _FCNTL_CKSM_FILE _FcntlOpcode = 41 + _FCNTL_RESET_CACHE _FcntlOpcode = 42 +) + +// https://sqlite.org/c3ref/c_shm_exclusive.html +type _ShmFlag uint32 + +const ( + _SHM_UNLOCK _ShmFlag = 1 + _SHM_LOCK _ShmFlag = 2 + _SHM_SHARED _ShmFlag = 4 + _SHM_EXCLUSIVE _ShmFlag = 8 +) diff --git a/vendor/github.com/ncruces/go-sqlite3/vfs/file.go b/vendor/github.com/ncruces/go-sqlite3/vfs/file.go new file mode 100644 index 000000000..ca8cf84f3 --- /dev/null +++ b/vendor/github.com/ncruces/go-sqlite3/vfs/file.go @@ -0,0 +1,217 @@ +package vfs + +import ( + "errors" + "io" + "io/fs" + "os" + "path/filepath" + "runtime" + "syscall" + + "github.com/ncruces/go-sqlite3/util/osutil" +) + +type vfsOS struct{} + +func (vfsOS) FullPathname(path string) (string, error) { + path, err := filepath.Abs(path) + if err != nil { + return "", err + } + fi, err := os.Lstat(path) + if err != nil { + if errors.Is(err, fs.ErrNotExist) { + return path, nil + } + return "", err + } + if fi.Mode()&fs.ModeSymlink != 0 { + err = _OK_SYMLINK + } + return path, err +} + +func (vfsOS) Delete(path string, syncDir bool) error { + err := os.Remove(path) + if err != nil { + if errors.Is(err, fs.ErrNotExist) { + return _IOERR_DELETE_NOENT + } + return err + } + if runtime.GOOS != "windows" && syncDir { + f, err := os.Open(filepath.Dir(path)) + if err != nil { + return _OK + } + defer f.Close() + err = osSync(f, false, false) + if err != nil { + return _IOERR_DIR_FSYNC + } + } + return nil +} + +func (vfsOS) Access(name string, flags AccessFlag) (bool, error) { + err := osAccess(name, flags) + if flags == ACCESS_EXISTS { + if errors.Is(err, fs.ErrNotExist) { + return false, nil + } + } else { + if errors.Is(err, fs.ErrPermission) { + return false, nil + } + } + return err == nil, err +} + +func (vfsOS) Open(name string, flags OpenFlag) (File, OpenFlag, error) { + return nil, 0, _CANTOPEN +} + +func (vfsOS) OpenFilename(name *Filename, flags OpenFlag) (File, OpenFlag, error) { + var oflags int + if flags&OPEN_EXCLUSIVE != 0 { + oflags |= os.O_EXCL + } + if flags&OPEN_CREATE != 0 { + oflags |= os.O_CREATE + } + if flags&OPEN_READONLY != 0 { + oflags |= os.O_RDONLY + } + if flags&OPEN_READWRITE != 0 { + oflags |= os.O_RDWR + } + + var err error + var f *os.File + if name == nil { + f, err = os.CreateTemp("", "*.db") + } else { + f, err = osutil.OpenFile(name.String(), oflags, 0666) + } + if err != nil { + if errors.Is(err, syscall.EISDIR) { + return nil, flags, _CANTOPEN_ISDIR + } + return nil, flags, err + } + + if modeof := name.URIParameter("modeof"); modeof != "" { + if err = osSetMode(f, modeof); err != nil { + f.Close() + return nil, flags, _IOERR_FSTAT + } + } + if flags&OPEN_DELETEONCLOSE != 0 { + os.Remove(f.Name()) + } + + file := vfsFile{ + File: f, + psow: true, + readOnly: flags&OPEN_READONLY != 0, + 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 SharedMemory + lock LockLevel + readOnly bool + keepWAL bool + syncDir bool + psow bool +} + +var ( + // Ensure these interfaces are implemented: + _ FileLockState = &vfsFile{} + _ FileHasMoved = &vfsFile{} + _ FileSizeHint = &vfsFile{} + _ FilePersistentWAL = &vfsFile{} + _ FilePowersafeOverwrite = &vfsFile{} +) + +func (f *vfsFile) Close() error { + if f.shm != nil { + f.shm.Close() + } + return f.File.Close() +} + +func (f *vfsFile) Sync(flags SyncFlag) error { + dataonly := (flags & SYNC_DATAONLY) != 0 + fullsync := (flags & 0x0f) == SYNC_FULL + + err := osSync(f.File, fullsync, dataonly) + if err != nil { + return err + } + if runtime.GOOS != "windows" && f.syncDir { + f.syncDir = false + d, err := os.Open(filepath.Dir(f.File.Name())) + if err != nil { + return nil + } + defer d.Close() + err = osSync(d, false, false) + if err != nil { + return _IOERR_DIR_FSYNC + } + } + return nil +} + +func (f *vfsFile) Size() (int64, error) { + return f.Seek(0, io.SeekEnd) +} + +func (f *vfsFile) SectorSize() int { + return _DEFAULT_SECTOR_SIZE +} + +func (f *vfsFile) DeviceCharacteristics() DeviceCharacteristic { + var res DeviceCharacteristic + if osBatchAtomic(f.File) { + res |= IOCAP_BATCH_ATOMIC + } + if f.psow { + res |= IOCAP_POWERSAFE_OVERWRITE + } + return res +} + +func (f *vfsFile) SizeHint(size int64) error { + return osAllocate(f.File, size) +} + +func (f *vfsFile) HasMoved() (bool, error) { + fi, err := f.Stat() + if err != nil { + return false, err + } + pi, err := os.Stat(f.Name()) + if err != nil { + if errors.Is(err, fs.ErrNotExist) { + return true, nil + } + return false, err + } + return !os.SameFile(fi, pi), nil +} + +func (f *vfsFile) LockState() LockLevel { return f.lock } +func (f *vfsFile) PowersafeOverwrite() bool { return f.psow } +func (f *vfsFile) PersistentWAL() bool { return f.keepWAL } +func (f *vfsFile) SetPowersafeOverwrite(psow bool) { f.psow = psow } +func (f *vfsFile) SetPersistentWAL(keepWAL bool) { f.keepWAL = keepWAL } 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/lock.go b/vendor/github.com/ncruces/go-sqlite3/vfs/lock.go new file mode 100644 index 000000000..86a988ae8 --- /dev/null +++ b/vendor/github.com/ncruces/go-sqlite3/vfs/lock.go @@ -0,0 +1,144 @@ +//go:build (linux || darwin || windows || freebsd || openbsd || netbsd || dragonfly || illumos || sqlite3_flock) && !sqlite3_nosys + +package vfs + +import "github.com/ncruces/go-sqlite3/internal/util" + +// SupportsFileLocking is false on platforms that do not support file locking. +// To open a database file on those platforms, +// you need to use the [nolock] or [immutable] URI parameters. +// +// [nolock]: https://sqlite.org/uri.html#urinolock +// [immutable]: https://sqlite.org/uri.html#uriimmutable +const SupportsFileLocking = true + +const ( + _PENDING_BYTE = 0x40000000 + _RESERVED_BYTE = (_PENDING_BYTE + 1) + _SHARED_FIRST = (_PENDING_BYTE + 2) + _SHARED_SIZE = 510 +) + +func (f *vfsFile) Lock(lock LockLevel) error { + // Argument check. SQLite never explicitly requests a pending lock. + if lock != LOCK_SHARED && lock != LOCK_RESERVED && lock != LOCK_EXCLUSIVE { + panic(util.AssertErr()) + } + + switch { + case f.lock < LOCK_NONE || f.lock > LOCK_EXCLUSIVE: + // Connection state check. + panic(util.AssertErr()) + case f.lock == LOCK_NONE && lock > LOCK_SHARED: + // We never move from unlocked to anything higher than a shared lock. + panic(util.AssertErr()) + case f.lock != LOCK_SHARED && lock == LOCK_RESERVED: + // A shared lock is always held when a reserved lock is requested. + panic(util.AssertErr()) + } + + // If we already have an equal or more restrictive lock, do nothing. + if f.lock >= lock { + return nil + } + + // Do not allow any kind of write-lock on a read-only database. + if f.readOnly && lock >= LOCK_RESERVED { + return _IOERR_LOCK + } + + switch lock { + case LOCK_SHARED: + // Must be unlocked to get SHARED. + if f.lock != LOCK_NONE { + panic(util.AssertErr()) + } + if rc := osGetSharedLock(f.File); rc != _OK { + return rc + } + f.lock = LOCK_SHARED + return nil + + case LOCK_RESERVED: + // Must be SHARED to get RESERVED. + if f.lock != LOCK_SHARED { + panic(util.AssertErr()) + } + if rc := osGetReservedLock(f.File); rc != _OK { + return rc + } + f.lock = LOCK_RESERVED + return nil + + case LOCK_EXCLUSIVE: + // Must be SHARED, RESERVED or PENDING to get EXCLUSIVE. + if f.lock <= LOCK_NONE || f.lock >= LOCK_EXCLUSIVE { + panic(util.AssertErr()) + } + reserved := f.lock == LOCK_RESERVED + // 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. + 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 { + return rc + } + f.lock = LOCK_EXCLUSIVE + return nil + + default: + panic(util.AssertErr()) + } +} + +func (f *vfsFile) Unlock(lock LockLevel) error { + // Argument check. + if lock != LOCK_NONE && lock != LOCK_SHARED { + panic(util.AssertErr()) + } + + // Connection state check. + if f.lock < LOCK_NONE || f.lock > LOCK_EXCLUSIVE { + panic(util.AssertErr()) + } + + // If we don't have a more restrictive lock, do nothing. + if f.lock <= lock { + return nil + } + + switch lock { + case LOCK_SHARED: + rc := osDowngradeLock(f.File, f.lock) + f.lock = LOCK_SHARED + return rc + + case LOCK_NONE: + rc := osReleaseLock(f.File, f.lock) + f.lock = LOCK_NONE + return rc + + default: + panic(util.AssertErr()) + } +} + +func (f *vfsFile) CheckReservedLock() (bool, error) { + // Connection state check. + if f.lock < LOCK_NONE || f.lock > LOCK_EXCLUSIVE { + panic(util.AssertErr()) + } + + if f.lock >= LOCK_RESERVED { + return true, nil + } + return osCheckReservedLock(f.File) +} diff --git a/vendor/github.com/ncruces/go-sqlite3/vfs/lock_other.go b/vendor/github.com/ncruces/go-sqlite3/vfs/lock_other.go new file mode 100644 index 000000000..c395f34a7 --- /dev/null +++ b/vendor/github.com/ncruces/go-sqlite3/vfs/lock_other.go @@ -0,0 +1,23 @@ +//go:build !(linux || darwin || windows || freebsd || openbsd || netbsd || dragonfly || illumos || sqlite3_flock) || sqlite3_nosys + +package vfs + +// SupportsFileLocking is false on platforms that do not support file locking. +// To open a database file on those platforms, +// you need to use the [nolock] or [immutable] URI parameters. +// +// [nolock]: https://sqlite.org/uri.html#urinolock +// [immutable]: https://sqlite.org/uri.html#uriimmutable +const SupportsFileLocking = false + +func (f *vfsFile) Lock(LockLevel) error { + return _IOERR_LOCK +} + +func (f *vfsFile) Unlock(LockLevel) error { + return _IOERR_UNLOCK +} + +func (f *vfsFile) CheckReservedLock() (bool, error) { + return false, _IOERR_CHECKRESERVEDLOCK +} diff --git a/vendor/github.com/ncruces/go-sqlite3/vfs/memdb/README.md b/vendor/github.com/ncruces/go-sqlite3/vfs/memdb/README.md new file mode 100644 index 000000000..193e29d98 --- /dev/null +++ b/vendor/github.com/ncruces/go-sqlite3/vfs/memdb/README.md @@ -0,0 +1,9 @@ +# Go `"memdb"` SQLite VFS + +This package implements the [`"memdb"`](https://sqlite.org/src/doc/tip/src/memdb.c) +SQLite VFS in pure Go. + +It has some benefits over the C version: +- the memory backing the database needs not be contiguous, +- the database can grow/shrink incrementally without copying, +- reader-writer concurrency is slightly improved.
\ No newline at end of file diff --git a/vendor/github.com/ncruces/go-sqlite3/vfs/memdb/api.go b/vendor/github.com/ncruces/go-sqlite3/vfs/memdb/api.go new file mode 100644 index 000000000..5a2b84c71 --- /dev/null +++ b/vendor/github.com/ncruces/go-sqlite3/vfs/memdb/api.go @@ -0,0 +1,68 @@ +// Package memdb implements the "memdb" SQLite VFS. +// +// The "memdb" [vfs.VFS] allows the same in-memory database to be shared +// among multiple database connections in the same process, +// as long as the database name begins with "/". +// +// Importing package memdb registers the VFS: +// +// import _ "github.com/ncruces/go-sqlite3/vfs/memdb" +package memdb + +import ( + "sync" + + "github.com/ncruces/go-sqlite3/vfs" +) + +func init() { + vfs.Register("memdb", memVFS{}) +} + +var ( + memoryMtx sync.Mutex + // +checklocks:memoryMtx + memoryDBs = map[string]*memDB{} +) + +// Create creates a shared memory database, +// using data as its initial contents. +// The new database takes ownership of data, +// and the caller should not use data after this call. +func Create(name string, data []byte) { + memoryMtx.Lock() + defer memoryMtx.Unlock() + + db := &memDB{ + refs: 1, + name: name, + size: int64(len(data)), + } + + // Convert data from WAL to rollback journal. + if len(data) >= 20 && data[18] == 2 && data[19] == 2 { + data[18] = 1 + data[19] = 1 + } + + sectors := divRoundUp(db.size, sectorSize) + db.data = make([]*[sectorSize]byte, sectors) + for i := range db.data { + sector := data[i*sectorSize:] + if len(sector) >= sectorSize { + db.data[i] = (*[sectorSize]byte)(sector) + } else { + db.data[i] = new([sectorSize]byte) + copy((*db.data[i])[:], sector) + } + } + + memoryDBs[name] = db +} + +// Delete deletes a shared memory database. +func Delete(name string) { + memoryMtx.Lock() + defer memoryMtx.Unlock() + delete(memoryDBs, name) +} diff --git a/vendor/github.com/ncruces/go-sqlite3/vfs/memdb/memdb.go b/vendor/github.com/ncruces/go-sqlite3/vfs/memdb/memdb.go new file mode 100644 index 000000000..8dc57ab9c --- /dev/null +++ b/vendor/github.com/ncruces/go-sqlite3/vfs/memdb/memdb.go @@ -0,0 +1,311 @@ +package memdb + +import ( + "io" + "runtime" + "sync" + "time" + + "github.com/ncruces/go-sqlite3" + "github.com/ncruces/go-sqlite3/vfs" +) + +// Must be a multiple of 64K (the largest page size). +const sectorSize = 65536 + +type memVFS struct{} + +func (memVFS) Open(name string, flags vfs.OpenFlag) (vfs.File, vfs.OpenFlag, error) { + // For simplicity, we do not support reading or writing data + // across "sector" boundaries. + // + // 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://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_TEMP_DB | + vfs.OPEN_TEMP_JOURNAL + if flags&types == 0 { + return nil, flags, sqlite3.CANTOPEN + } + + // A shared database has a name that begins with "/". + shared := len(name) > 1 && name[0] == '/' + + var db *memDB + if shared { + name = name[1:] + memoryMtx.Lock() + defer memoryMtx.Unlock() + db = memoryDBs[name] + } + if db == nil { + if flags&vfs.OPEN_CREATE == 0 { + return nil, flags, sqlite3.CANTOPEN + } + db = &memDB{name: name} + } + if shared { + db.refs++ // +checklocksforce: memoryMtx is held + memoryDBs[name] = db + } + + return &memFile{ + memDB: db, + readOnly: flags&vfs.OPEN_READONLY != 0, + }, flags | vfs.OPEN_MEMORY, nil +} + +func (memVFS) Delete(name string, dirSync bool) error { + return sqlite3.IOERR_DELETE +} + +func (memVFS) Access(name string, flag vfs.AccessFlag) (bool, error) { + return false, nil +} + +func (memVFS) FullPathname(name string) (string, error) { + return name, nil +} + +type memDB struct { + name string + + // +checklocks:lockMtx + pending *memFile + // +checklocks:lockMtx + reserved *memFile + + // +checklocks:dataMtx + data []*[sectorSize]byte + + // +checklocks:dataMtx + size int64 + + // +checklocks:lockMtx + shared int + + // +checklocks:memoryMtx + refs int + + lockMtx sync.Mutex + dataMtx sync.RWMutex +} + +func (m *memDB) release() { + memoryMtx.Lock() + defer memoryMtx.Unlock() + if m.refs--; m.refs == 0 && m == memoryDBs[m.name] { + delete(memoryDBs, m.name) + } +} + +type memFile struct { + *memDB + lock vfs.LockLevel + readOnly bool +} + +var ( + // Ensure these interfaces are implemented: + _ vfs.FileLockState = &memFile{} + _ vfs.FileSizeHint = &memFile{} +) + +func (m *memFile) Close() error { + m.release() + return m.Unlock(vfs.LOCK_NONE) +} + +func (m *memFile) ReadAt(b []byte, off int64) (n int, err error) { + m.dataMtx.RLock() + defer m.dataMtx.RUnlock() + + if off >= m.size { + return 0, io.EOF + } + + base := off / sectorSize + rest := off % sectorSize + have := int64(sectorSize) + if base == int64(len(m.data))-1 { + have = modRoundUp(m.size, sectorSize) + } + n = copy(b, (*m.data[base])[rest:have]) + if n < len(b) { + // Assume reads are page aligned. + return 0, io.ErrNoProgress + } + return n, nil +} + +func (m *memFile) WriteAt(b []byte, off int64) (n int, err error) { + m.dataMtx.Lock() + defer m.dataMtx.Unlock() + + base := off / sectorSize + rest := off % sectorSize + for base >= int64(len(m.data)) { + m.data = append(m.data, new([sectorSize]byte)) + } + n = copy((*m.data[base])[rest:], b) + if n < len(b) { + // Assume writes are page aligned. + return n, io.ErrShortWrite + } + if size := off + int64(len(b)); size > m.size { + m.size = size + } + return n, nil +} + +func (m *memFile) Truncate(size int64) error { + m.dataMtx.Lock() + defer m.dataMtx.Unlock() + return m.truncate(size) +} + +// +checklocks:m.dataMtx +func (m *memFile) truncate(size int64) error { + if size < m.size { + base := size / sectorSize + rest := size % sectorSize + if rest != 0 { + clear((*m.data[base])[rest:]) + } + } + sectors := divRoundUp(size, sectorSize) + for sectors > int64(len(m.data)) { + m.data = append(m.data, new([sectorSize]byte)) + } + clear(m.data[sectors:]) + m.data = m.data[:sectors] + m.size = size + return nil +} + +func (m *memFile) Sync(flag vfs.SyncFlag) error { + return nil +} + +func (m *memFile) Size() (int64, error) { + m.dataMtx.RLock() + defer m.dataMtx.RUnlock() + return m.size, nil +} + +const spinWait = 25 * time.Microsecond + +func (m *memFile) Lock(lock vfs.LockLevel) error { + if m.lock >= lock { + return nil + } + + if m.readOnly && lock >= vfs.LOCK_RESERVED { + return sqlite3.IOERR_LOCK + } + + m.lockMtx.Lock() + defer m.lockMtx.Unlock() + + switch lock { + case vfs.LOCK_SHARED: + if m.pending != nil { + return sqlite3.BUSY + } + m.shared++ + + case vfs.LOCK_RESERVED: + if m.reserved != nil { + return sqlite3.BUSY + } + m.reserved = m + + case vfs.LOCK_EXCLUSIVE: + if m.lock < vfs.LOCK_PENDING { + if m.pending != nil { + return sqlite3.BUSY + } + m.lock = vfs.LOCK_PENDING + m.pending = m + } + + for before := time.Now(); m.shared > 1; { + if time.Since(before) > spinWait { + return sqlite3.BUSY + } + m.lockMtx.Unlock() + runtime.Gosched() + m.lockMtx.Lock() + } + } + + m.lock = lock + return nil +} + +func (m *memFile) Unlock(lock vfs.LockLevel) error { + if m.lock <= lock { + return nil + } + + m.lockMtx.Lock() + defer m.lockMtx.Unlock() + + if m.pending == m { + m.pending = nil + } + if m.reserved == m { + m.reserved = nil + } + if lock < vfs.LOCK_SHARED { + m.shared-- + } + m.lock = lock + return nil +} + +func (m *memFile) CheckReservedLock() (bool, error) { + if m.lock >= vfs.LOCK_RESERVED { + return true, nil + } + m.lockMtx.Lock() + defer m.lockMtx.Unlock() + return m.reserved != nil, nil +} + +func (m *memFile) SectorSize() int { + return sectorSize +} + +func (m *memFile) DeviceCharacteristics() vfs.DeviceCharacteristic { + return vfs.IOCAP_ATOMIC | + vfs.IOCAP_SEQUENTIAL | + vfs.IOCAP_SAFE_APPEND | + vfs.IOCAP_POWERSAFE_OVERWRITE +} + +func (m *memFile) SizeHint(size int64) error { + m.dataMtx.Lock() + defer m.dataMtx.Unlock() + if size > m.size { + return m.truncate(size) + } + return nil +} + +func (m *memFile) LockState() vfs.LockLevel { + return m.lock +} + +func divRoundUp(a, b int64) int64 { + return (a + b - 1) / b +} + +func modRoundUp(a, b int64) int64 { + return b - (b-a%b)%b +} diff --git a/vendor/github.com/ncruces/go-sqlite3/vfs/os_bsd.go b/vendor/github.com/ncruces/go-sqlite3/vfs/os_bsd.go new file mode 100644 index 000000000..48ac5c9c9 --- /dev/null +++ b/vendor/github.com/ncruces/go-sqlite3/vfs/os_bsd.go @@ -0,0 +1,33 @@ +//go:build (freebsd || openbsd || netbsd || dragonfly || illumos || sqlite3_flock) && !sqlite3_nosys + +package vfs + +import ( + "os" + "time" + + "golang.org/x/sys/unix" +) + +func osUnlock(file *os.File, start, len int64) _ErrorCode { + if start == 0 && len == 0 { + err := unix.Flock(int(file.Fd()), unix.LOCK_UN) + if err != nil { + return _IOERR_UNLOCK + } + } + return _OK +} + +func osLock(file *os.File, how int, def _ErrorCode) _ErrorCode { + err := unix.Flock(int(file.Fd()), how) + return osLockErrorCode(err, def) +} + +func osReadLock(file *os.File, _ /*start*/, _ /*len*/ int64, _ /*timeout*/ time.Duration) _ErrorCode { + return osLock(file, unix.LOCK_SH|unix.LOCK_NB, _IOERR_RDLOCK) +} + +func osWriteLock(file *os.File, _ /*start*/, _ /*len*/ int64, _ /*timeout*/ time.Duration) _ErrorCode { + return osLock(file, unix.LOCK_EX|unix.LOCK_NB, _IOERR_LOCK) +} diff --git a/vendor/github.com/ncruces/go-sqlite3/vfs/os_darwin.go b/vendor/github.com/ncruces/go-sqlite3/vfs/os_darwin.go new file mode 100644 index 000000000..8bfe96bb1 --- /dev/null +++ b/vendor/github.com/ncruces/go-sqlite3/vfs/os_darwin.go @@ -0,0 +1,95 @@ +//go:build !(sqlite3_flock || sqlite3_nosys) + +package vfs + +import ( + "io" + "os" + "time" + + "golang.org/x/sys/unix" +) + +const ( + // https://github.com/apple/darwin-xnu/blob/main/bsd/sys/fcntl.h + _F_OFD_SETLK = 90 + _F_OFD_SETLKW = 91 + _F_OFD_SETLKWTIMEOUT = 93 +) + +type flocktimeout_t struct { + fl unix.Flock_t + timeout unix.Timespec +} + +func osSync(file *os.File, fullsync, _ /*dataonly*/ bool) error { + if fullsync { + return file.Sync() + } + return unix.Fsync(int(file.Fd())) +} + +func osAllocate(file *os.File, size int64) error { + off, err := file.Seek(0, io.SeekEnd) + if err != nil { + return err + } + if size <= off { + return nil + } + + store := unix.Fstore_t{ + Flags: unix.F_ALLOCATEALL | unix.F_ALLOCATECONTIG, + Posmode: unix.F_PEOFPOSMODE, + Offset: 0, + Length: size - off, + } + + // Try to get a continuous chunk of disk space. + err = unix.FcntlFstore(file.Fd(), unix.F_PREALLOCATE, &store) + if err != nil { + // OK, perhaps we are too fragmented, allocate non-continuous. + store.Flags = unix.F_ALLOCATEALL + unix.FcntlFstore(file.Fd(), unix.F_PREALLOCATE, &store) + } + return file.Truncate(size) +} + +func osUnlock(file *os.File, start, len int64) _ErrorCode { + err := unix.FcntlFlock(file.Fd(), _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 := flocktimeout_t{fl: unix.Flock_t{ + Type: typ, + Start: start, + Len: len, + }} + var err error + switch { + case timeout == 0: + err = unix.FcntlFlock(file.Fd(), _F_OFD_SETLK, &lock.fl) + case timeout < 0: + err = unix.FcntlFlock(file.Fd(), _F_OFD_SETLKW, &lock.fl) + default: + lock.timeout = unix.NsecToTimespec(int64(timeout / time.Nanosecond)) + err = unix.FcntlFlock(file.Fd(), _F_OFD_SETLKWTIMEOUT, &lock.fl) + } + 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_f2fs_linux.go b/vendor/github.com/ncruces/go-sqlite3/vfs/os_f2fs_linux.go new file mode 100644 index 000000000..a9f0e333c --- /dev/null +++ b/vendor/github.com/ncruces/go-sqlite3/vfs/os_f2fs_linux.go @@ -0,0 +1,34 @@ +//go:build (amd64 || arm64 || riscv64) && !sqlite3_nosys + +package vfs + +import ( + "os" + + "golang.org/x/sys/unix" +) + +const ( + _F2FS_IOC_START_ATOMIC_WRITE = 62721 + _F2FS_IOC_COMMIT_ATOMIC_WRITE = 62722 + _F2FS_IOC_ABORT_ATOMIC_WRITE = 62725 + _F2FS_IOC_GET_FEATURES = 2147808524 + _F2FS_FEATURE_ATOMIC_WRITE = 4 +) + +func osBatchAtomic(file *os.File) bool { + flags, err := unix.IoctlGetInt(int(file.Fd()), _F2FS_IOC_GET_FEATURES) + return err == nil && flags&_F2FS_FEATURE_ATOMIC_WRITE != 0 +} + +func (f *vfsFile) BeginAtomicWrite() error { + return unix.IoctlSetInt(int(f.Fd()), _F2FS_IOC_START_ATOMIC_WRITE, 0) +} + +func (f *vfsFile) CommitAtomicWrite() error { + return unix.IoctlSetInt(int(f.Fd()), _F2FS_IOC_COMMIT_ATOMIC_WRITE, 0) +} + +func (f *vfsFile) RollbackAtomicWrite() error { + return unix.IoctlSetInt(int(f.Fd()), _F2FS_IOC_ABORT_ATOMIC_WRITE, 0) +} diff --git a/vendor/github.com/ncruces/go-sqlite3/vfs/os_linux.go b/vendor/github.com/ncruces/go-sqlite3/vfs/os_linux.go new file mode 100644 index 000000000..11e683a04 --- /dev/null +++ b/vendor/github.com/ncruces/go-sqlite3/vfs/os_linux.go @@ -0,0 +1,71 @@ +//go:build !(sqlite3_flock || sqlite3_nosys) + +package vfs + +import ( + "math/rand" + "os" + "time" + + "golang.org/x/sys/unix" +) + +func osSync(file *os.File, _ /*fullsync*/, _ /*dataonly*/ bool) error { + // SQLite trusts Linux's fdatasync for all fsync's. + return unix.Fdatasync(int(file.Fd())) +} + +func osAllocate(file *os.File, size int64) error { + if size == 0 { + return nil + } + 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_std_access.go b/vendor/github.com/ncruces/go-sqlite3/vfs/os_std_access.go new file mode 100644 index 000000000..1621c0998 --- /dev/null +++ b/vendor/github.com/ncruces/go-sqlite3/vfs/os_std_access.go @@ -0,0 +1,36 @@ +//go:build !unix || sqlite3_nosys + +package vfs + +import ( + "io/fs" + "os" +) + +func osAccess(path string, flags AccessFlag) error { + fi, err := os.Stat(path) + if err != nil { + return err + } + if flags == ACCESS_EXISTS { + return nil + } + + const ( + S_IREAD = 0400 + S_IWRITE = 0200 + S_IEXEC = 0100 + ) + + var want fs.FileMode = S_IREAD + if flags == ACCESS_READWRITE { + want |= S_IWRITE + } + if fi.IsDir() { + want |= S_IEXEC + } + if fi.Mode()&want != want { + return fs.ErrPermission + } + return nil +} diff --git a/vendor/github.com/ncruces/go-sqlite3/vfs/os_std_alloc.go b/vendor/github.com/ncruces/go-sqlite3/vfs/os_std_alloc.go new file mode 100644 index 000000000..60c92182c --- /dev/null +++ b/vendor/github.com/ncruces/go-sqlite3/vfs/os_std_alloc.go @@ -0,0 +1,19 @@ +//go:build !(linux || darwin) || sqlite3_flock || sqlite3_nosys + +package vfs + +import ( + "io" + "os" +) + +func osAllocate(file *os.File, size int64) error { + off, err := file.Seek(0, io.SeekEnd) + if err != nil { + return err + } + if size <= off { + return nil + } + return file.Truncate(size) +} diff --git a/vendor/github.com/ncruces/go-sqlite3/vfs/os_std_atomic.go b/vendor/github.com/ncruces/go-sqlite3/vfs/os_std_atomic.go new file mode 100644 index 000000000..ecaff0245 --- /dev/null +++ b/vendor/github.com/ncruces/go-sqlite3/vfs/os_std_atomic.go @@ -0,0 +1,9 @@ +//go:build !linux || !(amd64 || arm64 || riscv64) || sqlite3_nosys + +package vfs + +import "os" + +func osBatchAtomic(*os.File) bool { + return false +} diff --git a/vendor/github.com/ncruces/go-sqlite3/vfs/os_std_mode.go b/vendor/github.com/ncruces/go-sqlite3/vfs/os_std_mode.go new file mode 100644 index 000000000..ac4904773 --- /dev/null +++ b/vendor/github.com/ncruces/go-sqlite3/vfs/os_std_mode.go @@ -0,0 +1,14 @@ +//go:build !unix || sqlite3_nosys + +package vfs + +import "os" + +func osSetMode(file *os.File, modeof string) error { + fi, err := os.Stat(modeof) + if err != nil { + return err + } + file.Chmod(fi.Mode()) + return nil +} diff --git a/vendor/github.com/ncruces/go-sqlite3/vfs/os_std_sleep.go b/vendor/github.com/ncruces/go-sqlite3/vfs/os_std_sleep.go new file mode 100644 index 000000000..c6bc40769 --- /dev/null +++ b/vendor/github.com/ncruces/go-sqlite3/vfs/os_std_sleep.go @@ -0,0 +1,9 @@ +//go:build !windows || sqlite3_nosys + +package vfs + +import "time" + +func osSleep(d time.Duration) { + time.Sleep(d) +} diff --git a/vendor/github.com/ncruces/go-sqlite3/vfs/os_std_sync.go b/vendor/github.com/ncruces/go-sqlite3/vfs/os_std_sync.go new file mode 100644 index 000000000..84dbd23bc --- /dev/null +++ b/vendor/github.com/ncruces/go-sqlite3/vfs/os_std_sync.go @@ -0,0 +1,9 @@ +//go:build !(linux || darwin) || sqlite3_flock || sqlite3_nosys + +package vfs + +import "os" + +func osSync(file *os.File, _ /*fullsync*/, _ /*dataonly*/ bool) error { + return file.Sync() +} diff --git a/vendor/github.com/ncruces/go-sqlite3/vfs/os_unix.go b/vendor/github.com/ncruces/go-sqlite3/vfs/os_unix.go new file mode 100644 index 000000000..bf4b44efd --- /dev/null +++ b/vendor/github.com/ncruces/go-sqlite3/vfs/os_unix.go @@ -0,0 +1,33 @@ +//go:build unix && !sqlite3_nosys + +package vfs + +import ( + "os" + "syscall" + + "golang.org/x/sys/unix" +) + +func osAccess(path string, flags AccessFlag) error { + var access uint32 // unix.F_OK + switch flags { + case ACCESS_READWRITE: + access = unix.R_OK | unix.W_OK + case ACCESS_READ: + access = unix.R_OK + } + return unix.Access(path, access) +} + +func osSetMode(file *os.File, modeof string) error { + fi, err := os.Stat(modeof) + if err != nil { + return err + } + file.Chmod(fi.Mode()) + if sys, ok := fi.Sys().(*syscall.Stat_t); ok { + file.Chown(int(sys.Uid), int(sys.Gid)) + } + return nil +} diff --git a/vendor/github.com/ncruces/go-sqlite3/vfs/os_unix_lock.go b/vendor/github.com/ncruces/go-sqlite3/vfs/os_unix_lock.go new file mode 100644 index 000000000..d04c1f6a0 --- /dev/null +++ b/vendor/github.com/ncruces/go-sqlite3/vfs/os_unix_lock.go @@ -0,0 +1,106 @@ +//go:build (linux || darwin || freebsd || openbsd || netbsd || dragonfly || illumos || sqlite3_flock) && !sqlite3_nosys + +package vfs + +import ( + "os" + "time" + + "golang.org/x/sys/unix" +) + +func osGetSharedLock(file *os.File) _ErrorCode { + // Test the PENDING lock before acquiring a new SHARED lock. + if lock, _ := osGetLock(file, _PENDING_BYTE, 1); lock == unix.F_WRLCK { + return _BUSY + } + // Acquire the SHARED lock. + return osReadLock(file, _SHARED_FIRST, _SHARED_SIZE, 0) +} + +func osGetReservedLock(file *os.File) _ErrorCode { + // Acquire the RESERVED lock. + return osWriteLock(file, _RESERVED_BYTE, 1, 0) +} + +func osGetPendingLock(file *os.File, block bool) _ErrorCode { + var timeout time.Duration + if block { + timeout = -1 + } + // Acquire the PENDING lock. + return osWriteLock(file, _PENDING_BYTE, 1, timeout) +} + +func osGetExclusiveLock(file *os.File, wait bool) _ErrorCode { + var timeout time.Duration + if wait { + timeout = time.Millisecond + } + // Acquire the EXCLUSIVE lock. + return osWriteLock(file, _SHARED_FIRST, _SHARED_SIZE, timeout) +} + +func osDowngradeLock(file *os.File, state LockLevel) _ErrorCode { + if state >= LOCK_EXCLUSIVE { + // Downgrade to a SHARED lock. + if rc := osReadLock(file, _SHARED_FIRST, _SHARED_SIZE, 0); rc != _OK { + // In theory, the downgrade to a SHARED cannot fail because another + // process is holding an incompatible lock. If it does, this + // indicates that the other process is not following the locking + // protocol. If this happens, return _IOERR_RDLOCK. Returning + // BUSY would confuse the upper layer. + return _IOERR_RDLOCK + } + } + // Release the PENDING and RESERVED locks. + return osUnlock(file, _PENDING_BYTE, 2) +} + +func osReleaseLock(file *os.File, _ LockLevel) _ErrorCode { + // Release all locks. + return osUnlock(file, 0, 0) +} + +func osCheckReservedLock(file *os.File) (bool, _ErrorCode) { + // Test the RESERVED lock. + lock, rc := osGetLock(file, _RESERVED_BYTE, 1) + return lock == unix.F_WRLCK, rc +} + +func osGetLock(file *os.File, start, len int64) (int16, _ErrorCode) { + lock := unix.Flock_t{ + Type: unix.F_WRLCK, + Start: start, + Len: len, + } + if unix.FcntlFlock(file.Fd(), unix.F_GETLK, &lock) != nil { + return 0, _IOERR_CHECKRESERVEDLOCK + } + return lock.Type, _OK +} + +func osLockErrorCode(err error, def _ErrorCode) _ErrorCode { + if err == nil { + return _OK + } + if errno, ok := err.(unix.Errno); ok { + switch errno { + case + unix.EACCES, + unix.EAGAIN, + unix.EBUSY, + unix.EINTR, + unix.ENOLCK, + unix.EDEADLK, + unix.ETIMEDOUT: + return _BUSY + case unix.EPERM: + return _PERM + } + if errno == unix.EWOULDBLOCK && unix.EWOULDBLOCK != unix.EAGAIN { + return _BUSY + } + } + return def +} diff --git a/vendor/github.com/ncruces/go-sqlite3/vfs/os_windows.go b/vendor/github.com/ncruces/go-sqlite3/vfs/os_windows.go new file mode 100644 index 000000000..5c68754f8 --- /dev/null +++ b/vendor/github.com/ncruces/go-sqlite3/vfs/os_windows.go @@ -0,0 +1,186 @@ +//go:build !sqlite3_nosys + +package vfs + +import ( + "math/rand" + "os" + "time" + + "golang.org/x/sys/windows" +) + +func osGetSharedLock(file *os.File) _ErrorCode { + // Acquire the PENDING lock temporarily before acquiring a new SHARED lock. + rc := osReadLock(file, _PENDING_BYTE, 1, 0) + if rc == _OK { + // Acquire the SHARED lock. + rc = osReadLock(file, _SHARED_FIRST, _SHARED_SIZE, 0) + + // Release the PENDING lock. + osUnlock(file, _PENDING_BYTE, 1) + } + return rc +} + +func osGetReservedLock(file *os.File) _ErrorCode { + // Acquire the RESERVED lock. + return osWriteLock(file, _RESERVED_BYTE, 1, 0) +} + +func osGetPendingLock(file *os.File, block bool) _ErrorCode { + var timeout time.Duration + if block { + timeout = -1 + } + + // Acquire the PENDING lock. + return osWriteLock(file, _PENDING_BYTE, 1, timeout) +} + +func osGetExclusiveLock(file *os.File, wait bool) _ErrorCode { + var timeout time.Duration + if wait { + timeout = time.Millisecond + } + + // Release the SHARED lock. + osUnlock(file, _SHARED_FIRST, _SHARED_SIZE) + + // Acquire the EXCLUSIVE lock. + rc := osWriteLock(file, _SHARED_FIRST, _SHARED_SIZE, timeout) + + if rc != _OK { + // Reacquire the SHARED lock. + osReadLock(file, _SHARED_FIRST, _SHARED_SIZE, 0) + } + return rc +} + +func osDowngradeLock(file *os.File, state LockLevel) _ErrorCode { + if state >= LOCK_EXCLUSIVE { + // Release the EXCLUSIVE lock. + osUnlock(file, _SHARED_FIRST, _SHARED_SIZE) + + // Reacquire the SHARED lock. + if rc := osReadLock(file, _SHARED_FIRST, _SHARED_SIZE, 0); rc != _OK { + // This should never happen. + // We should always be able to reacquire the read lock. + return _IOERR_RDLOCK + } + } + + // Release the PENDING and RESERVED locks. + if state >= LOCK_RESERVED { + osUnlock(file, _RESERVED_BYTE, 1) + } + if state >= LOCK_PENDING { + osUnlock(file, _PENDING_BYTE, 1) + } + return _OK +} + +func osReleaseLock(file *os.File, state LockLevel) _ErrorCode { + // Release all locks. + if state >= LOCK_RESERVED { + osUnlock(file, _RESERVED_BYTE, 1) + } + if state >= LOCK_SHARED { + osUnlock(file, _SHARED_FIRST, _SHARED_SIZE) + } + if state >= LOCK_PENDING { + osUnlock(file, _PENDING_BYTE, 1) + } + return _OK +} + +func osCheckReservedLock(file *os.File) (bool, _ErrorCode) { + // Test the RESERVED lock. + rc := osLock(file, 0, _RESERVED_BYTE, 1, 0, _IOERR_CHECKRESERVEDLOCK) + if rc == _BUSY { + return true, _OK + } + if rc == _OK { + // Release the RESERVED lock. + osUnlock(file, _RESERVED_BYTE, 1) + } + return false, rc +} + +func osUnlock(file *os.File, start, len uint32) _ErrorCode { + err := windows.UnlockFileEx(windows.Handle(file.Fd()), + 0, len, 0, &windows.Overlapped{Offset: start}) + if err == windows.ERROR_NOT_LOCKED { + return _OK + } + if err != nil { + return _IOERR_UNLOCK + } + return _OK +} + +func osLock(file *os.File, flags, start, len uint32, timeout time.Duration, def _ErrorCode) _ErrorCode { + var err error + switch { + case timeout == 0: + err = osLockEx(file, flags|windows.LOCKFILE_FAIL_IMMEDIATELY, start, len) + case timeout < 0: + err = osLockEx(file, flags, start, len) + default: + before := time.Now() + for { + err = osLockEx(file, flags|windows.LOCKFILE_FAIL_IMMEDIATELY, start, len) + if errno, _ := err.(windows.Errno); errno != windows.ERROR_LOCK_VIOLATION { + break + } + if timeout < time.Since(before) { + break + } + osSleep(time.Duration(rand.Int63n(int64(time.Millisecond)))) + } + } + return osLockErrorCode(err, def) +} + +func osLockEx(file *os.File, flags, start, len uint32) error { + return windows.LockFileEx(windows.Handle(file.Fd()), flags, + 0, len, 0, &windows.Overlapped{Offset: start}) +} + +func osReadLock(file *os.File, start, len uint32, timeout time.Duration) _ErrorCode { + return osLock(file, 0, start, len, timeout, _IOERR_RDLOCK) +} + +func osWriteLock(file *os.File, start, len uint32, timeout time.Duration) _ErrorCode { + return osLock(file, windows.LOCKFILE_EXCLUSIVE_LOCK, start, len, timeout, _IOERR_LOCK) +} + +func osLockErrorCode(err error, def _ErrorCode) _ErrorCode { + if err == nil { + return _OK + } + if errno, ok := err.(windows.Errno); ok { + // https://devblogs.microsoft.com/oldnewthing/20140905-00/?p=63 + switch errno { + case + windows.ERROR_LOCK_VIOLATION, + windows.ERROR_IO_PENDING, + windows.ERROR_OPERATION_ABORTED: + return _BUSY + } + } + return def +} + +func osSleep(d time.Duration) { + if d > 0 { + period := max(1, d/(5*time.Millisecond)) + if period < 16 { + windows.TimeBeginPeriod(uint32(period)) + } + time.Sleep(d) + if period < 16 { + windows.TimeEndPeriod(uint32(period)) + } + } +} diff --git a/vendor/github.com/ncruces/go-sqlite3/vfs/registry.go b/vendor/github.com/ncruces/go-sqlite3/vfs/registry.go new file mode 100644 index 000000000..42a2106fb --- /dev/null +++ b/vendor/github.com/ncruces/go-sqlite3/vfs/registry.go @@ -0,0 +1,48 @@ +package vfs + +import "sync" + +var ( + // +checklocks:vfsRegistryMtx + vfsRegistry map[string]VFS + vfsRegistryMtx sync.RWMutex +) + +// Find returns a VFS given its name. +// If there is no match, nil is returned. +// If name is empty, the default VFS is returned. +// +// https://sqlite.org/c3ref/vfs_find.html +func Find(name string) VFS { + if name == "" || name == "os" { + return vfsOS{} + } + vfsRegistryMtx.RLock() + defer vfsRegistryMtx.RUnlock() + return vfsRegistry[name] +} + +// Register registers a VFS. +// Empty and "os" are reserved names. +// +// https://sqlite.org/c3ref/vfs_find.html +func Register(name string, vfs VFS) { + if name == "" || name == "os" { + return + } + vfsRegistryMtx.Lock() + defer vfsRegistryMtx.Unlock() + if vfsRegistry == nil { + vfsRegistry = map[string]VFS{} + } + vfsRegistry[name] = vfs +} + +// Unregister unregisters a VFS. +// +// https://sqlite.org/c3ref/vfs_find.html +func Unregister(name string) { + vfsRegistryMtx.Lock() + defer vfsRegistryMtx.Unlock() + delete(vfsRegistry, name) +} diff --git a/vendor/github.com/ncruces/go-sqlite3/vfs/shm.go b/vendor/github.com/ncruces/go-sqlite3/vfs/shm.go new file mode 100644 index 000000000..2b76dd5dc --- /dev/null +++ b/vendor/github.com/ncruces/go-sqlite3/vfs/shm.go @@ -0,0 +1,173 @@ +//go:build (darwin || linux) && (amd64 || arm64 || riscv64) && !(sqlite3_flock || sqlite3_noshm || sqlite3_nosys) + +package vfs + +import ( + "context" + "io" + "os" + + "github.com/ncruces/go-sqlite3/internal/util" + "github.com/tetratelabs/wazero/api" + "golang.org/x/sys/unix" +) + +// 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 + +const ( + _SHM_NLOCK = 8 + _SHM_BASE = 120 + _SHM_DMS = _SHM_BASE + _SHM_NLOCK +) + +func (f *vfsFile) SharedMemory() SharedMemory { return f.shm } + +// 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 s.File == nil { + var flag int + if s.readOnly { + flag = unix.O_RDONLY + } else { + flag = unix.O_RDWR + } + f, err := os.OpenFile(s.path, + flag|unix.O_CREAT|unix.O_NOFOLLOW, 0666) + if err != nil { + return 0, _CANTOPEN + } + s.File = f + } + + // Dead man's switch. + 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 s.readOnly { + return 0, _READONLY_CANTINIT + } + if rc := osWriteLock(s.File, _SHM_DMS, 1, 0); rc != _OK { + return 0, rc + } + if err := s.Truncate(0); err != nil { + return 0, _IOERR_SHMOPEN + } + } + if rc := osReadLock(s.File, _SHM_DMS, 1, 0); rc != _OK { + return 0, rc + } + + // Check if file is big enough. + o, err := s.Seek(0, io.SeekEnd) + if err != nil { + return 0, _IOERR_SHMSIZE + } + if n := (int64(id) + 1) * int64(size); n > o { + if !extend { + return 0, nil + } + err := osAllocate(s.File, n) + if err != nil { + return 0, _IOERR_SHMSIZE + } + } + + var prot int + if s.readOnly { + prot = unix.PROT_READ + } else { + prot = unix.PROT_READ | unix.PROT_WRITE + } + r, err := util.MapRegion(ctx, mod, s.File, int64(id)*int64(size), size, prot) + if err != nil { + return 0, err + } + s.regions = append(s.regions, r) + return r.Ptr, nil +} + +func (s *vfsShm) shmLock(offset, n int32, flags _ShmFlag) error { + // Argument check. + if n <= 0 || offset < 0 || offset+n > _SHM_NLOCK { + panic(util.AssertErr()) + } + switch flags { + case + _SHM_LOCK | _SHM_SHARED, + _SHM_LOCK | _SHM_EXCLUSIVE, + _SHM_UNLOCK | _SHM_SHARED, + _SHM_UNLOCK | _SHM_EXCLUSIVE: + // + default: + panic(util.AssertErr()) + } + if n != 1 && flags&_SHM_EXCLUSIVE == 0 { + panic(util.AssertErr()) + } + + 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) + case flags&_SHM_EXCLUSIVE != 0: + return osWriteLock(s.File, _SHM_BASE+int64(offset), int64(n), 0) + default: + panic(util.AssertErr()) + } +} + +func (s *vfsShm) shmUnmap(delete bool) { + if s.File == nil { + return + } + + // Unmap regions. + for _, r := range s.regions { + r.Unmap() + } + clear(s.regions) + s.regions = s.regions[:0] + + // Close the file. + defer s.Close() + if delete { + os.Remove(s.Name()) + } + 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 new file mode 100644 index 000000000..21191979e --- /dev/null +++ b/vendor/github.com/ncruces/go-sqlite3/vfs/shm_other.go @@ -0,0 +1,21 @@ +//go:build !(darwin || linux) || !(amd64 || arm64 || riscv64) || sqlite3_flock || sqlite3_noshm || sqlite3_nosys + +package vfs + +// 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 + +// 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 new file mode 100644 index 000000000..1887e9f22 --- /dev/null +++ b/vendor/github.com/ncruces/go-sqlite3/vfs/vfs.go @@ -0,0 +1,459 @@ +package vfs + +import ( + "context" + "crypto/rand" + "io" + "reflect" + "sync" + "time" + + "github.com/ncruces/go-sqlite3/internal/util" + "github.com/ncruces/julianday" + "github.com/tetratelabs/wazero" + "github.com/tetratelabs/wazero/api" +) + +// ExportHostFunctions is an internal API users need not call directly. +// +// ExportHostFunctions registers the required VFS host functions +// with the provided env module. +func ExportHostFunctions(env wazero.HostModuleBuilder) wazero.HostModuleBuilder { + util.ExportFuncII(env, "go_vfs_find", vfsFind) + util.ExportFuncIIJ(env, "go_localtime", vfsLocaltime) + util.ExportFuncIIII(env, "go_randomness", vfsRandomness) + util.ExportFuncIII(env, "go_sleep", vfsSleep) + util.ExportFuncIII(env, "go_current_time_64", vfsCurrentTime64) + util.ExportFuncIIIII(env, "go_full_pathname", vfsFullPathname) + util.ExportFuncIIII(env, "go_delete", vfsDelete) + util.ExportFuncIIIII(env, "go_access", vfsAccess) + util.ExportFuncIIIIIII(env, "go_open", vfsOpen) + util.ExportFuncII(env, "go_close", vfsClose) + util.ExportFuncIIIIJ(env, "go_read", vfsRead) + util.ExportFuncIIIIJ(env, "go_write", vfsWrite) + util.ExportFuncIIJ(env, "go_truncate", vfsTruncate) + util.ExportFuncIII(env, "go_sync", vfsSync) + util.ExportFuncIII(env, "go_file_size", vfsFileSize) + util.ExportFuncIIII(env, "go_file_control", vfsFileControl) + util.ExportFuncII(env, "go_sector_size", vfsSectorSize) + util.ExportFuncII(env, "go_device_characteristics", vfsDeviceCharacteristics) + util.ExportFuncIII(env, "go_lock", vfsLock) + util.ExportFuncIII(env, "go_unlock", vfsUnlock) + util.ExportFuncIII(env, "go_check_reserved_lock", vfsCheckReservedLock) + util.ExportFuncIIIIII(env, "go_shm_map", vfsShmMap) + util.ExportFuncIIIII(env, "go_shm_lock", vfsShmLock) + util.ExportFuncIII(env, "go_shm_unmap", vfsShmUnmap) + util.ExportFuncVI(env, "go_shm_barrier", vfsShmBarrier) + return env +} + +func vfsFind(ctx context.Context, mod api.Module, zVfsName uint32) uint32 { + name := util.ReadString(mod, zVfsName, _MAX_NAME) + if vfs := Find(name); vfs != nil && vfs != (vfsOS{}) { + return 1 + } + return 0 +} + +func vfsLocaltime(ctx context.Context, mod api.Module, pTm uint32, t int64) _ErrorCode { + tm := time.Unix(t, 0) + var isdst int + if tm.IsDST() { + isdst = 1 + } + + const size = 32 / 8 + // https://pubs.opengroup.org/onlinepubs/7908799/xsh/time.h.html + util.WriteUint32(mod, pTm+0*size, uint32(tm.Second())) + util.WriteUint32(mod, pTm+1*size, uint32(tm.Minute())) + util.WriteUint32(mod, pTm+2*size, uint32(tm.Hour())) + util.WriteUint32(mod, pTm+3*size, uint32(tm.Day())) + util.WriteUint32(mod, pTm+4*size, uint32(tm.Month()-time.January)) + util.WriteUint32(mod, pTm+5*size, uint32(tm.Year()-1900)) + util.WriteUint32(mod, pTm+6*size, uint32(tm.Weekday()-time.Sunday)) + util.WriteUint32(mod, pTm+7*size, uint32(tm.YearDay()-1)) + util.WriteUint32(mod, pTm+8*size, uint32(isdst)) + return _OK +} + +func vfsRandomness(ctx context.Context, mod api.Module, pVfs uint32, nByte int32, zByte uint32) uint32 { + mem := util.View(mod, zByte, uint64(nByte)) + n, _ := rand.Reader.Read(mem) + return uint32(n) +} + +func vfsSleep(ctx context.Context, mod api.Module, pVfs uint32, nMicro int32) _ErrorCode { + osSleep(time.Duration(nMicro) * time.Microsecond) + return _OK +} + +func vfsCurrentTime64(ctx context.Context, mod api.Module, pVfs, piNow uint32) _ErrorCode { + day, nsec := julianday.Date(time.Now()) + msec := day*86_400_000 + nsec/1_000_000 + util.WriteUint64(mod, piNow, uint64(msec)) + return _OK +} + +func vfsFullPathname(ctx context.Context, mod api.Module, pVfs, zRelative uint32, nFull int32, zFull uint32) _ErrorCode { + vfs := vfsGet(mod, pVfs) + path := util.ReadString(mod, zRelative, _MAX_PATHNAME) + + path, err := vfs.FullPathname(path) + + if len(path) >= int(nFull) { + return _CANTOPEN_FULLPATH + } + util.WriteString(mod, zFull, path) + + return vfsErrorCode(err, _CANTOPEN_FULLPATH) +} + +func vfsDelete(ctx context.Context, mod api.Module, pVfs, zPath, syncDir uint32) _ErrorCode { + vfs := vfsGet(mod, pVfs) + path := util.ReadString(mod, zPath, _MAX_PATHNAME) + + err := vfs.Delete(path, syncDir != 0) + return vfsErrorCode(err, _IOERR_DELETE) +} + +func vfsAccess(ctx context.Context, mod api.Module, pVfs, zPath uint32, flags AccessFlag, pResOut uint32) _ErrorCode { + vfs := vfsGet(mod, pVfs) + path := util.ReadString(mod, zPath, _MAX_PATHNAME) + + ok, err := vfs.Access(path, flags) + var res uint32 + if ok { + res = 1 + } + + util.WriteUint32(mod, pResOut, res) + return vfsErrorCode(err, _IOERR_ACCESS) +} + +func vfsOpen(ctx context.Context, mod api.Module, pVfs, zPath, pFile uint32, flags OpenFlag, pOutFlags, pOutVFS uint32) _ErrorCode { + vfs := vfsGet(mod, pVfs) + + var path string + if zPath != 0 { + path = util.ReadString(mod, zPath, _MAX_PATHNAME) + } + + var file File + var err error + 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 { + 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)) + } + vfsFileRegister(ctx, mod, pFile, file) + return _OK +} + +func vfsClose(ctx context.Context, mod api.Module, pFile uint32) _ErrorCode { + err := vfsFileClose(ctx, mod, pFile) + if err != nil { + return vfsErrorCode(err, _IOERR_CLOSE) + } + return _OK +} + +func vfsRead(ctx context.Context, mod api.Module, pFile, zBuf uint32, iAmt int32, iOfst int64) _ErrorCode { + file := vfsFileGet(ctx, mod, pFile).(File) + buf := util.View(mod, zBuf, uint64(iAmt)) + + n, err := file.ReadAt(buf, iOfst) + if n == int(iAmt) { + return _OK + } + if err != io.EOF { + return vfsErrorCode(err, _IOERR_READ) + } + clear(buf[n:]) + return _IOERR_SHORT_READ +} + +func vfsWrite(ctx context.Context, mod api.Module, pFile, zBuf uint32, iAmt int32, iOfst int64) _ErrorCode { + file := vfsFileGet(ctx, mod, pFile).(File) + buf := util.View(mod, zBuf, uint64(iAmt)) + + _, err := file.WriteAt(buf, iOfst) + if err != nil { + return vfsErrorCode(err, _IOERR_WRITE) + } + return _OK +} + +func vfsTruncate(ctx context.Context, mod api.Module, pFile uint32, nByte int64) _ErrorCode { + 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) + 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) + 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) + 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) + 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) + locked, err := file.CheckReservedLock() + + var res uint32 + if locked { + res = 1 + } + + util.WriteUint32(mod, pResOut, res) + return vfsErrorCode(err, _IOERR_CHECKRESERVEDLOCK) +} + +func vfsFileControl(ctx context.Context, mod api.Module, pFile uint32, op _FcntlOpcode, pArg uint32) _ErrorCode { + file := vfsFileGet(ctx, mod, pFile).(File) + + switch op { + case _FCNTL_LOCKSTATE: + if file, ok := file.(FileLockState); ok { + util.WriteUint32(mod, pArg, uint32(file.LockState())) + return _OK + } + + case _FCNTL_PERSIST_WAL: + if file, ok := file.(FilePersistentWAL); ok { + if i := util.ReadUint32(mod, pArg); int32(i) >= 0 { + file.SetPersistentWAL(i != 0) + } else if file.PersistentWAL() { + util.WriteUint32(mod, pArg, 1) + } else { + util.WriteUint32(mod, pArg, 0) + } + return _OK + } + + case _FCNTL_POWERSAFE_OVERWRITE: + if file, ok := file.(FilePowersafeOverwrite); ok { + if i := util.ReadUint32(mod, pArg); int32(i) >= 0 { + file.SetPowersafeOverwrite(i != 0) + } else if file.PowersafeOverwrite() { + util.WriteUint32(mod, pArg, 1) + } else { + util.WriteUint32(mod, pArg, 0) + } + 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) + err := file.SizeHint(int64(size)) + return vfsErrorCode(err, _IOERR_TRUNCATE) + } + + case _FCNTL_HAS_MOVED: + if file, ok := file.(FileHasMoved); ok { + moved, err := file.HasMoved() + var res uint32 + if moved { + res = 1 + } + util.WriteUint32(mod, pArg, res) + return vfsErrorCode(err, _IOERR_FSTAT) + } + + case _FCNTL_OVERWRITE: + if file, ok := file.(FileOverwrite); ok { + err := file.Overwrite() + return vfsErrorCode(err, _IOERR) + } + + case _FCNTL_COMMIT_PHASETWO: + if file, ok := file.(FileCommitPhaseTwo); ok { + err := file.CommitPhaseTwo() + return vfsErrorCode(err, _IOERR) + } + + case _FCNTL_BEGIN_ATOMIC_WRITE: + if file, ok := file.(FileBatchAtomicWrite); ok { + err := file.BeginAtomicWrite() + return vfsErrorCode(err, _IOERR_BEGIN_ATOMIC) + } + case _FCNTL_COMMIT_ATOMIC_WRITE: + if file, ok := file.(FileBatchAtomicWrite); ok { + err := file.CommitAtomicWrite() + return vfsErrorCode(err, _IOERR_COMMIT_ATOMIC) + } + case _FCNTL_ROLLBACK_ATOMIC_WRITE: + if file, ok := file.(FileBatchAtomicWrite); ok { + 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_LAST_ERRNO + // _FCNTL_SYNC + return _NOTFOUND +} + +func vfsSectorSize(ctx context.Context, mod api.Module, pFile uint32) uint32 { + 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) + return file.DeviceCharacteristics() +} + +var shmBarrier sync.Mutex + +func vfsShmBarrier(ctx context.Context, mod api.Module, pFile uint32) { + shmBarrier.Lock() + defer shmBarrier.Unlock() +} + +func vfsShmMap(ctx context.Context, mod api.Module, pFile uint32, iRegion, szRegion int32, bExtend, pp uint32) _ErrorCode { + 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) + } + util.WriteUint32(mod, pp, p) + return _OK +} + +func vfsShmLock(ctx context.Context, mod api.Module, pFile uint32, offset, n int32, flags _ShmFlag) _ErrorCode { + 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 { + shm := vfsFileGet(ctx, mod, pFile).(FileSharedMemory).SharedMemory() + shm.shmUnmap(bDelete != 0) + return _OK +} + +func vfsGet(mod api.Module, pVfs uint32) VFS { + var name string + if pVfs != 0 { + const zNameOffset = 16 + name = util.ReadString(mod, util.ReadUint32(mod, pVfs+zNameOffset), _MAX_NAME) + } + if vfs := Find(name); vfs != nil { + return vfs + } + panic(util.NoVFSErr + util.ErrorString(name)) +} + +func vfsFileRegister(ctx context.Context, mod api.Module, pFile uint32, file File) { + const fileHandleOffset = 4 + id := util.AddHandle(ctx, file) + util.WriteUint32(mod, pFile+fileHandleOffset, id) +} + +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) +} + +func vfsFileClose(ctx context.Context, mod api.Module, pFile uint32) error { + const fileHandleOffset = 4 + id := util.ReadUint32(mod, pFile+fileHandleOffset) + return util.DelHandle(ctx, id) +} + +func vfsErrorCode(err error, def _ErrorCode) _ErrorCode { + if err == nil { + return _OK + } + switch v := reflect.ValueOf(err); v.Kind() { + case reflect.Uint8, reflect.Uint16, reflect.Uint32: + return _ErrorCode(v.Uint()) + } + return def +} diff --git a/vendor/github.com/ncruces/go-sqlite3/vtab.go b/vendor/github.com/ncruces/go-sqlite3/vtab.go new file mode 100644 index 000000000..a330c98ff --- /dev/null +++ b/vendor/github.com/ncruces/go-sqlite3/vtab.go @@ -0,0 +1,663 @@ +package sqlite3 + +import ( + "context" + "reflect" + + "github.com/ncruces/go-sqlite3/internal/util" + "github.com/tetratelabs/wazero/api" +) + +// CreateModule registers a new virtual table module name. +// If create is nil, the virtual table is eponymous. +// +// https://sqlite.org/c3ref/create_module.html +func CreateModule[T VTab](db *Conn, name string, create, connect VTabConstructor[T]) error { + var flags int + + const ( + VTAB_CREATOR = 0x01 + VTAB_DESTROYER = 0x02 + VTAB_UPDATER = 0x04 + VTAB_RENAMER = 0x08 + VTAB_OVERLOADER = 0x10 + VTAB_CHECKER = 0x20 + VTAB_TXN = 0x40 + VTAB_SAVEPOINTER = 0x80 + ) + + if create != nil { + flags |= VTAB_CREATOR + } + + vtab := reflect.TypeOf(connect).Out(0) + if implements[VTabDestroyer](vtab) { + flags |= VTAB_DESTROYER + } + if implements[VTabUpdater](vtab) { + flags |= VTAB_UPDATER + } + if implements[VTabRenamer](vtab) { + flags |= VTAB_RENAMER + } + if implements[VTabOverloader](vtab) { + flags |= VTAB_OVERLOADER + } + if implements[VTabChecker](vtab) { + flags |= VTAB_CHECKER + } + if implements[VTabTxn](vtab) { + flags |= VTAB_TXN + } + if implements[VTabSavepointer](vtab) { + flags |= VTAB_SAVEPOINTER + } + + defer db.arena.mark()() + namePtr := db.arena.string(name) + modulePtr := util.AddHandle(db.ctx, module[T]{create, connect}) + r := db.call("sqlite3_create_module_go", uint64(db.handle), + uint64(namePtr), uint64(flags), uint64(modulePtr)) + return db.error(r) +} + +func implements[T any](typ reflect.Type) bool { + var ptr *T + return typ.Implements(reflect.TypeOf(ptr).Elem()) +} + +// DeclareVTab declares the schema of a virtual table. +// +// https://sqlite.org/c3ref/declare_vtab.html +func (c *Conn) DeclareVTab(sql string) error { + defer c.arena.mark()() + sqlPtr := c.arena.string(sql) + r := c.call("sqlite3_declare_vtab", uint64(c.handle), uint64(sqlPtr)) + return c.error(r) +} + +// VTabConflictMode is a virtual table conflict resolution mode. +// +// https://sqlite.org/c3ref/c_fail.html +type VTabConflictMode uint8 + +const ( + VTAB_ROLLBACK VTabConflictMode = 1 + VTAB_IGNORE VTabConflictMode = 2 + VTAB_FAIL VTabConflictMode = 3 + VTAB_ABORT VTabConflictMode = 4 + VTAB_REPLACE VTabConflictMode = 5 +) + +// VTabOnConflict determines the virtual table conflict policy. +// +// https://sqlite.org/c3ref/vtab_on_conflict.html +func (c *Conn) VTabOnConflict() VTabConflictMode { + r := c.call("sqlite3_vtab_on_conflict", uint64(c.handle)) + return VTabConflictMode(r) +} + +// VTabConfigOption is a virtual table configuration option. +// +// https://sqlite.org/c3ref/c_vtab_constraint_support.html +type VTabConfigOption uint8 + +const ( + VTAB_CONSTRAINT_SUPPORT VTabConfigOption = 1 + VTAB_INNOCUOUS VTabConfigOption = 2 + VTAB_DIRECTONLY VTabConfigOption = 3 + VTAB_USES_ALL_SCHEMAS VTabConfigOption = 4 +) + +// VTabConfig configures various facets of the virtual table interface. +// +// https://sqlite.org/c3ref/vtab_config.html +func (c *Conn) VTabConfig(op VTabConfigOption, args ...any) error { + var i uint64 + if op == VTAB_CONSTRAINT_SUPPORT && len(args) > 0 { + if b, ok := args[0].(bool); ok && b { + i = 1 + } + } + r := c.call("sqlite3_vtab_config_go", uint64(c.handle), uint64(op), i) + return c.error(r) +} + +// VTabConstructor is a virtual table constructor function. +type VTabConstructor[T VTab] func(db *Conn, module, schema, table string, arg ...string) (T, error) + +type module[T VTab] [2]VTabConstructor[T] + +type vtabConstructor int + +const ( + xCreate vtabConstructor = 0 + xConnect vtabConstructor = 1 +) + +// A VTab describes a particular instance of the virtual table. +// A VTab may optionally implement [io.Closer] to free resources. +// +// https://sqlite.org/c3ref/vtab.html +type VTab interface { + // https://sqlite.org/vtab.html#xbestindex + BestIndex(*IndexInfo) error + // https://sqlite.org/vtab.html#xopen + Open() (VTabCursor, error) +} + +// A VTabDestroyer allows a virtual table to drop persistent state. +type VTabDestroyer interface { + VTab + // https://sqlite.org/vtab.html#sqlite3_module.xDestroy + Destroy() error +} + +// A VTabUpdater allows a virtual table to be updated. +type VTabUpdater interface { + VTab + // https://sqlite.org/vtab.html#xupdate + Update(arg ...Value) (rowid int64, err error) +} + +// A VTabRenamer allows a virtual table to be renamed. +type VTabRenamer interface { + VTab + // https://sqlite.org/vtab.html#xrename + Rename(new string) error +} + +// A VTabOverloader allows a virtual table to overload SQL functions. +type VTabOverloader interface { + VTab + // https://sqlite.org/vtab.html#xfindfunction + FindFunction(arg int, name string) (ScalarFunction, IndexConstraintOp) +} + +// A VTabChecker allows a virtual table to report errors +// to the PRAGMA integrity_check and PRAGMA quick_check commands. +// +// Integrity should return an error if it finds problems in the content of the virtual table, +// but should avoid returning a (wrapped) [Error], [ErrorCode] or [ExtendedErrorCode], +// as those indicate the Integrity method itself encountered problems +// while trying to evaluate the virtual table content. +type VTabChecker interface { + VTab + // https://sqlite.org/vtab.html#xintegrity + Integrity(schema, table string, flags int) error +} + +// A VTabTxn allows a virtual table to implement +// transactions with two-phase commit. +// +// Anything that is required as part of a commit that may fail +// should be performed in the Sync() callback. +// Current versions of SQLite ignore any errors +// returned by Commit() and Rollback(). +type VTabTxn interface { + VTab + // https://sqlite.org/vtab.html#xBegin + Begin() error + // https://sqlite.org/vtab.html#xsync + Sync() error + // https://sqlite.org/vtab.html#xcommit + Commit() error + // https://sqlite.org/vtab.html#xrollback + Rollback() error +} + +// A VTabSavepointer allows a virtual table to implement +// nested transactions. +// +// https://sqlite.org/vtab.html#xsavepoint +type VTabSavepointer interface { + VTabTxn + Savepoint(id int) error + Release(id int) error + RollbackTo(id int) error +} + +// A VTabCursor describes cursors that point +// into the virtual table and are used +// to loop through the virtual table. +// A VTabCursor may optionally implement +// [io.Closer] to free resources. +// +// http://sqlite.org/c3ref/vtab_cursor.html +type VTabCursor interface { + // https://sqlite.org/vtab.html#xfilter + Filter(idxNum int, idxStr string, arg ...Value) error + // https://sqlite.org/vtab.html#xnext + Next() error + // https://sqlite.org/vtab.html#xeof + EOF() bool + // https://sqlite.org/vtab.html#xcolumn + Column(ctx *Context, n int) error + // https://sqlite.org/vtab.html#xrowid + RowID() (int64, error) +} + +// An IndexInfo describes virtual table indexing information. +// +// https://sqlite.org/c3ref/index_info.html +type IndexInfo struct { + // Inputs + Constraint []IndexConstraint + OrderBy []IndexOrderBy + ColumnsUsed int64 + // Outputs + ConstraintUsage []IndexConstraintUsage + IdxNum int + IdxStr string + IdxFlags IndexScanFlag + OrderByConsumed bool + EstimatedCost float64 + EstimatedRows int64 + // Internal + c *Conn + handle uint32 +} + +// An IndexConstraint describes virtual table indexing constraint information. +// +// https://sqlite.org/c3ref/index_info.html +type IndexConstraint struct { + Column int + Op IndexConstraintOp + Usable bool +} + +// An IndexOrderBy describes virtual table indexing order by information. +// +// https://sqlite.org/c3ref/index_info.html +type IndexOrderBy struct { + Column int + Desc bool +} + +// An IndexConstraintUsage describes how virtual table indexing constraints will be used. +// +// https://sqlite.org/c3ref/index_info.html +type IndexConstraintUsage struct { + ArgvIndex int + Omit bool +} + +// RHSValue returns the value of the right-hand operand of a constraint +// if the right-hand operand is known. +// +// https://sqlite.org/c3ref/vtab_rhs_value.html +func (idx *IndexInfo) RHSValue(column int) (Value, error) { + defer idx.c.arena.mark()() + valPtr := idx.c.arena.new(ptrlen) + r := idx.c.call("sqlite3_vtab_rhs_value", uint64(idx.handle), + uint64(column), uint64(valPtr)) + if err := idx.c.error(r); err != nil { + return Value{}, err + } + return Value{ + c: idx.c, + handle: util.ReadUint32(idx.c.mod, valPtr), + }, nil +} + +// Collation returns the name of the collation for a virtual table constraint. +// +// https://sqlite.org/c3ref/vtab_collation.html +func (idx *IndexInfo) Collation(column int) string { + r := idx.c.call("sqlite3_vtab_collation", uint64(idx.handle), + uint64(column)) + return util.ReadString(idx.c.mod, uint32(r), _MAX_NAME) +} + +// Distinct determines if a virtual table query is DISTINCT. +// +// https://sqlite.org/c3ref/vtab_distinct.html +func (idx *IndexInfo) Distinct() int { + r := idx.c.call("sqlite3_vtab_distinct", uint64(idx.handle)) + return int(r) +} + +// In identifies and handles IN constraints. +// +// https://sqlite.org/c3ref/vtab_in.html +func (idx *IndexInfo) In(column, handle int) bool { + r := idx.c.call("sqlite3_vtab_in", uint64(idx.handle), + uint64(column), uint64(handle)) + return r != 0 +} + +func (idx *IndexInfo) load() { + // https://sqlite.org/c3ref/index_info.html + mod := idx.c.mod + ptr := idx.handle + + idx.Constraint = make([]IndexConstraint, util.ReadUint32(mod, ptr+0)) + idx.ConstraintUsage = make([]IndexConstraintUsage, util.ReadUint32(mod, ptr+0)) + idx.OrderBy = make([]IndexOrderBy, util.ReadUint32(mod, ptr+8)) + + constraintPtr := util.ReadUint32(mod, ptr+4) + for i := range idx.Constraint { + idx.Constraint[i] = IndexConstraint{ + Column: int(int32(util.ReadUint32(mod, constraintPtr+0))), + Op: IndexConstraintOp(util.ReadUint8(mod, constraintPtr+4)), + Usable: util.ReadUint8(mod, constraintPtr+5) != 0, + } + constraintPtr += 12 + } + + orderByPtr := util.ReadUint32(mod, ptr+12) + for i := range idx.OrderBy { + idx.OrderBy[i] = IndexOrderBy{ + Column: int(int32(util.ReadUint32(mod, orderByPtr+0))), + Desc: util.ReadUint8(mod, orderByPtr+4) != 0, + } + orderByPtr += 8 + } + + idx.EstimatedCost = util.ReadFloat64(mod, ptr+40) + idx.EstimatedRows = int64(util.ReadUint64(mod, ptr+48)) + idx.ColumnsUsed = int64(util.ReadUint64(mod, ptr+64)) +} + +func (idx *IndexInfo) save() { + // https://sqlite.org/c3ref/index_info.html + mod := idx.c.mod + ptr := idx.handle + + usagePtr := util.ReadUint32(mod, ptr+16) + for _, usage := range idx.ConstraintUsage { + util.WriteUint32(mod, usagePtr+0, uint32(usage.ArgvIndex)) + if usage.Omit { + util.WriteUint8(mod, usagePtr+4, 1) + } + usagePtr += 8 + } + + util.WriteUint32(mod, ptr+20, uint32(idx.IdxNum)) + if idx.IdxStr != "" { + util.WriteUint32(mod, ptr+24, idx.c.newString(idx.IdxStr)) + util.WriteUint32(mod, ptr+28, 1) // needToFreeIdxStr + } + if idx.OrderByConsumed { + util.WriteUint32(mod, ptr+32, 1) + } + util.WriteFloat64(mod, ptr+40, idx.EstimatedCost) + util.WriteUint64(mod, ptr+48, uint64(idx.EstimatedRows)) + util.WriteUint32(mod, ptr+56, uint32(idx.IdxFlags)) +} + +// IndexConstraintOp is a virtual table constraint operator code. +// +// https://sqlite.org/c3ref/c_index_constraint_eq.html +type IndexConstraintOp uint8 + +const ( + INDEX_CONSTRAINT_EQ IndexConstraintOp = 2 + INDEX_CONSTRAINT_GT IndexConstraintOp = 4 + INDEX_CONSTRAINT_LE IndexConstraintOp = 8 + INDEX_CONSTRAINT_LT IndexConstraintOp = 16 + INDEX_CONSTRAINT_GE IndexConstraintOp = 32 + INDEX_CONSTRAINT_MATCH IndexConstraintOp = 64 + INDEX_CONSTRAINT_LIKE IndexConstraintOp = 65 + INDEX_CONSTRAINT_GLOB IndexConstraintOp = 66 + INDEX_CONSTRAINT_REGEXP IndexConstraintOp = 67 + INDEX_CONSTRAINT_NE IndexConstraintOp = 68 + INDEX_CONSTRAINT_ISNOT IndexConstraintOp = 69 + INDEX_CONSTRAINT_ISNOTNULL IndexConstraintOp = 70 + INDEX_CONSTRAINT_ISNULL IndexConstraintOp = 71 + INDEX_CONSTRAINT_IS IndexConstraintOp = 72 + INDEX_CONSTRAINT_LIMIT IndexConstraintOp = 73 + INDEX_CONSTRAINT_OFFSET IndexConstraintOp = 74 + INDEX_CONSTRAINT_FUNCTION IndexConstraintOp = 150 +) + +// IndexScanFlag is a virtual table scan flag. +// +// https://sqlite.org/c3ref/c_index_scan_unique.html +type IndexScanFlag uint32 + +const ( + INDEX_SCAN_UNIQUE IndexScanFlag = 1 +) + +func vtabModuleCallback(i vtabConstructor) func(_ context.Context, _ api.Module, _, _, _, _, _ uint32) uint32 { + return func(ctx context.Context, mod api.Module, pMod, nArg, pArg, ppVTab, pzErr uint32) uint32 { + arg := make([]reflect.Value, 1+nArg) + arg[0] = reflect.ValueOf(ctx.Value(connKey{})) + + for i := uint32(0); i < nArg; i++ { + ptr := util.ReadUint32(mod, pArg+i*ptrlen) + arg[i+1] = reflect.ValueOf(util.ReadString(mod, ptr, _MAX_SQL_LENGTH)) + } + + module := vtabGetHandle(ctx, mod, pMod) + res := reflect.ValueOf(module).Index(int(i)).Call(arg) + err, _ := res[1].Interface().(error) + if err == nil { + vtabPutHandle(ctx, mod, ppVTab, res[0].Interface()) + } + + return vtabError(ctx, mod, pzErr, _PTR_ERROR, err) + } +} + +func vtabDisconnectCallback(ctx context.Context, mod api.Module, pVTab uint32) uint32 { + err := vtabDelHandle(ctx, mod, pVTab) + return vtabError(ctx, mod, 0, _PTR_ERROR, err) +} + +func vtabDestroyCallback(ctx context.Context, mod api.Module, pVTab uint32) uint32 { + vtab := vtabGetHandle(ctx, mod, pVTab).(VTabDestroyer) + err := vtab.Destroy() + if cerr := vtabDelHandle(ctx, mod, pVTab); err == nil { + err = cerr + } + return vtabError(ctx, mod, 0, _PTR_ERROR, err) +} + +func vtabBestIndexCallback(ctx context.Context, mod api.Module, pVTab, pIdxInfo uint32) uint32 { + var info IndexInfo + info.handle = pIdxInfo + info.c = ctx.Value(connKey{}).(*Conn) + info.load() + + vtab := vtabGetHandle(ctx, mod, pVTab).(VTab) + err := vtab.BestIndex(&info) + + info.save() + return vtabError(ctx, mod, pVTab, _VTAB_ERROR, err) +} + +func vtabUpdateCallback(ctx context.Context, mod api.Module, pVTab, nArg, pArg, pRowID uint32) uint32 { + vtab := vtabGetHandle(ctx, mod, pVTab).(VTabUpdater) + + db := ctx.Value(connKey{}).(*Conn) + args := make([]Value, nArg) + callbackArgs(db, args, pArg) + rowID, err := vtab.Update(args...) + if err == nil { + util.WriteUint64(mod, pRowID, uint64(rowID)) + } + + return vtabError(ctx, mod, pVTab, _VTAB_ERROR, err) +} + +func vtabRenameCallback(ctx context.Context, mod api.Module, pVTab, zNew uint32) uint32 { + vtab := vtabGetHandle(ctx, mod, pVTab).(VTabRenamer) + err := vtab.Rename(util.ReadString(mod, zNew, _MAX_NAME)) + return vtabError(ctx, mod, pVTab, _VTAB_ERROR, err) +} + +func vtabFindFuncCallback(ctx context.Context, mod api.Module, pVTab uint32, nArg int32, zName, pxFunc uint32) uint32 { + vtab := vtabGetHandle(ctx, mod, pVTab).(VTabOverloader) + f, op := vtab.FindFunction(int(nArg), util.ReadString(mod, zName, _MAX_NAME)) + if op != 0 { + var wrapper uint32 + wrapper = util.AddHandle(ctx, func(c Context, arg ...Value) { + defer util.DelHandle(ctx, wrapper) + f(c, arg...) + }) + util.WriteUint32(mod, pxFunc, wrapper) + } + return uint32(op) +} + +func vtabIntegrityCallback(ctx context.Context, mod api.Module, pVTab, zSchema, zTabName, mFlags, pzErr uint32) uint32 { + vtab := vtabGetHandle(ctx, mod, pVTab).(VTabChecker) + schema := util.ReadString(mod, zSchema, _MAX_NAME) + table := util.ReadString(mod, zTabName, _MAX_NAME) + err := vtab.Integrity(schema, table, int(mFlags)) + // xIntegrity should return OK - even if it finds problems in the content of the virtual table. + // https://sqlite.org/vtab.html#xintegrity + vtabError(ctx, mod, pzErr, _PTR_ERROR, err) + _, code := errorCode(err, _OK) + return code +} + +func vtabBeginCallback(ctx context.Context, mod api.Module, pVTab uint32) uint32 { + vtab := vtabGetHandle(ctx, mod, pVTab).(VTabTxn) + err := vtab.Begin() + return vtabError(ctx, mod, pVTab, _VTAB_ERROR, err) +} + +func vtabSyncCallback(ctx context.Context, mod api.Module, pVTab uint32) uint32 { + vtab := vtabGetHandle(ctx, mod, pVTab).(VTabTxn) + err := vtab.Sync() + return vtabError(ctx, mod, pVTab, _VTAB_ERROR, err) +} + +func vtabCommitCallback(ctx context.Context, mod api.Module, pVTab uint32) uint32 { + vtab := vtabGetHandle(ctx, mod, pVTab).(VTabTxn) + err := vtab.Commit() + return vtabError(ctx, mod, pVTab, _VTAB_ERROR, err) +} + +func vtabRollbackCallback(ctx context.Context, mod api.Module, pVTab uint32) uint32 { + vtab := vtabGetHandle(ctx, mod, pVTab).(VTabTxn) + err := vtab.Rollback() + return vtabError(ctx, mod, pVTab, _VTAB_ERROR, err) +} + +func vtabSavepointCallback(ctx context.Context, mod api.Module, pVTab uint32, id int32) uint32 { + vtab := vtabGetHandle(ctx, mod, pVTab).(VTabSavepointer) + err := vtab.Savepoint(int(id)) + return vtabError(ctx, mod, pVTab, _VTAB_ERROR, err) +} + +func vtabReleaseCallback(ctx context.Context, mod api.Module, pVTab uint32, id int32) uint32 { + vtab := vtabGetHandle(ctx, mod, pVTab).(VTabSavepointer) + err := vtab.Release(int(id)) + return vtabError(ctx, mod, pVTab, _VTAB_ERROR, err) +} + +func vtabRollbackToCallback(ctx context.Context, mod api.Module, pVTab uint32, id int32) uint32 { + vtab := vtabGetHandle(ctx, mod, pVTab).(VTabSavepointer) + err := vtab.RollbackTo(int(id)) + return vtabError(ctx, mod, pVTab, _VTAB_ERROR, err) +} + +func cursorOpenCallback(ctx context.Context, mod api.Module, pVTab, ppCur uint32) uint32 { + vtab := vtabGetHandle(ctx, mod, pVTab).(VTab) + + cursor, err := vtab.Open() + if err == nil { + vtabPutHandle(ctx, mod, ppCur, cursor) + } + + return vtabError(ctx, mod, pVTab, _VTAB_ERROR, err) +} + +func cursorCloseCallback(ctx context.Context, mod api.Module, pCur uint32) uint32 { + err := vtabDelHandle(ctx, mod, pCur) + return vtabError(ctx, mod, 0, _VTAB_ERROR, err) +} + +func cursorFilterCallback(ctx context.Context, mod api.Module, pCur uint32, idxNum int32, idxStr, nArg, pArg uint32) uint32 { + cursor := vtabGetHandle(ctx, mod, pCur).(VTabCursor) + db := ctx.Value(connKey{}).(*Conn) + args := make([]Value, nArg) + callbackArgs(db, args, pArg) + var idxName string + if idxStr != 0 { + idxName = util.ReadString(mod, idxStr, _MAX_LENGTH) + } + err := cursor.Filter(int(idxNum), idxName, args...) + return vtabError(ctx, mod, pCur, _CURSOR_ERROR, err) +} + +func cursorEOFCallback(ctx context.Context, mod api.Module, pCur uint32) uint32 { + cursor := vtabGetHandle(ctx, mod, pCur).(VTabCursor) + if cursor.EOF() { + return 1 + } + return 0 +} + +func cursorNextCallback(ctx context.Context, mod api.Module, pCur uint32) uint32 { + cursor := vtabGetHandle(ctx, mod, pCur).(VTabCursor) + err := cursor.Next() + return vtabError(ctx, mod, pCur, _CURSOR_ERROR, err) +} + +func cursorColumnCallback(ctx context.Context, mod api.Module, pCur, pCtx uint32, n int32) uint32 { + cursor := vtabGetHandle(ctx, mod, pCur).(VTabCursor) + db := ctx.Value(connKey{}).(*Conn) + err := cursor.Column(&Context{db, pCtx}, int(n)) + return vtabError(ctx, mod, pCur, _CURSOR_ERROR, err) +} + +func cursorRowIDCallback(ctx context.Context, mod api.Module, pCur, pRowID uint32) uint32 { + cursor := vtabGetHandle(ctx, mod, pCur).(VTabCursor) + + rowID, err := cursor.RowID() + if err == nil { + util.WriteUint64(mod, pRowID, uint64(rowID)) + } + + return vtabError(ctx, mod, pCur, _CURSOR_ERROR, err) +} + +const ( + _PTR_ERROR = iota + _VTAB_ERROR + _CURSOR_ERROR +) + +func vtabError(ctx context.Context, mod api.Module, ptr, kind uint32, err error) uint32 { + const zErrMsgOffset = 8 + msg, code := errorCode(err, ERROR) + if msg != "" && ptr != 0 { + switch kind { + case _VTAB_ERROR: + ptr = ptr + zErrMsgOffset // zErrMsg + case _CURSOR_ERROR: + ptr = util.ReadUint32(mod, ptr) + zErrMsgOffset // pVTab->zErrMsg + } + db := ctx.Value(connKey{}).(*Conn) + if ptr := util.ReadUint32(mod, ptr); ptr != 0 { + db.free(ptr) + } + util.WriteUint32(mod, ptr, db.newString(msg)) + } + return code +} + +func vtabGetHandle(ctx context.Context, mod api.Module, ptr uint32) any { + const handleOffset = 4 + handle := util.ReadUint32(mod, ptr-handleOffset) + return util.GetHandle(ctx, handle) +} + +func vtabDelHandle(ctx context.Context, mod api.Module, ptr uint32) error { + const handleOffset = 4 + handle := util.ReadUint32(mod, ptr-handleOffset) + return util.DelHandle(ctx, handle) +} + +func vtabPutHandle(ctx context.Context, mod api.Module, pptr uint32, val any) { + const handleOffset = 4 + handle := util.AddHandle(ctx, val) + ptr := util.ReadUint32(mod, pptr) + util.WriteUint32(mod, ptr-handleOffset, handle) +} |