Compare commits

..

24 Commits

Author SHA1 Message Date
Nuno Cruces
ec609ea131 F2FS. 2024-04-16 02:52:37 +01:00
Nuno Cruces
7bab8bb949 wazero 1.7.1. 2024-04-16 01:59:29 +01:00
Nuno Cruces
ce97e820d5 wasi-sdk-22. 2024-04-16 01:58:49 +01:00
Nuno Cruces
7d8249efa5 SQLite 3.45.3. 2024-04-16 01:58:48 +01:00
Nuno Cruces
d2362b0311 Use coroutines. 2024-04-16 01:05:24 +01:00
Nuno Cruces
17f1681477 Remove test. 2024-04-15 13:55:12 +01:00
Nuno Cruces
cc0b011e8d Readonly WAL. 2024-04-13 17:11:13 +01:00
dependabot[bot]
46086916d4 Bump cross-platform-actions/action from 0.23.0 to 0.24.0 (#76)
Bumps [cross-platform-actions/action](https://github.com/cross-platform-actions/action) from 0.23.0 to 0.24.0.
- [Release notes](https://github.com/cross-platform-actions/action/releases)
- [Changelog](https://github.com/cross-platform-actions/action/blob/master/changelog.md)
- [Commits](https://github.com/cross-platform-actions/action/compare/v0.23.0...v0.24.0)

---
updated-dependencies:
- dependency-name: cross-platform-actions/action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-13 00:45:41 +01:00
Nuno Cruces
4322c71a09 Fix race. 2024-04-12 16:28:53 +01:00
Nuno Cruces
da9077cbea Fix repeat runs. 2024-04-12 15:54:48 +01:00
Nuno Cruces
1c3ad12434 WAL and vacuum hooks. 2024-04-12 15:02:01 +01:00
Nuno Cruces
7260962aba Update README.md 2024-04-11 15:18:49 +01:00
Nuno Cruces
e503be641a Refactors. 2024-04-11 12:00:17 +01:00
Nuno Cruces
11c03a16f9 Implement shared memory WAL. (#71)
- enabled by default on 64-bit macOS and Linux (`amd64`/`arm64`)
- depends on merged but unreleased wazero
- may cause small performance regression
- users may need WithMemoryLimitPages if not enough address space available
- needs docs
2024-04-10 13:15:36 +01:00
dependabot[bot]
f1c376cb49 Bump golang.org/x/sync from 0.6.0 to 0.7.0 (#72)
Bumps [golang.org/x/sync](https://github.com/golang/sync) from 0.6.0 to 0.7.0.
- [Commits](https://github.com/golang/sync/compare/v0.6.0...v0.7.0)

---
updated-dependencies:
- dependency-name: golang.org/x/sync
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-04 23:52:16 +01:00
dependabot[bot]
91fd1457aa Bump golang.org/x/crypto from 0.21.0 to 0.22.0 (#74)
Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.21.0 to 0.22.0.
- [Commits](https://github.com/golang/crypto/compare/v0.21.0...v0.22.0)

---
updated-dependencies:
- dependency-name: golang.org/x/crypto
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-04 23:50:02 +01:00
Nuno Cruces
63938d5705 Simplify tests. 2024-04-04 01:25:52 +01:00
Nuno Cruces
10daa594f5 Gorm v1.25.8. 2024-03-28 00:10:07 +00:00
Nuno Cruces
2c2b6835b4 Tweaks, docs. 2024-03-27 07:54:15 +00:00
Nuno Cruces
af7fc3dcb7 Remove deprecations. 2024-03-27 07:54:08 +00:00
Nuno Cruces
0f9ce387b9 Documentation. 2024-03-22 00:21:00 +00:00
Nuno Cruces
b7d22e8fbf Fdatasync. 2024-03-21 15:04:59 +00:00
Nuno Cruces
617982f947 F2FS atomic writes. (#66)
https://sqlite.org/cgi/src/technote/714f6cbbf7
2024-03-21 13:59:47 +00:00
Nuno Cruces
36583542e1 Updated dependencies. 2024-03-17 16:34:02 +00:00
86 changed files with 1325 additions and 509 deletions

View File

@@ -1,4 +1,5 @@
#!/usr/bin/env bash
set -euo pipefail
echo 'set -euo pipefail' > test.sh

View File

@@ -17,7 +17,9 @@ echo aix ; GOOS=aix GOARCH=ppc64 go build .
echo js ; GOOS=js GOARCH=wasm go build .
echo wasip1 ; GOOS=wasip1 GOARCH=wasm go build .
echo darwin-flock ; GOOS=darwin GOARCH=amd64 go build -tags sqlite3_flock .
echo darwin-noshm ; GOOS=darwin GOARCH=amd64 go build -tags sqlite3_noshm .
echo darwin-nosys ; GOOS=darwin GOARCH=amd64 go build -tags sqlite3_nosys .
echo linux-noshm ; GOOS=linux GOARCH=amd64 go build -tags sqlite3_noshm .
echo linux-nosys ; GOOS=linux GOARCH=amd64 go build -tags sqlite3_nosys .
echo windows-nosys ; GOOS=windows GOARCH=amd64 go build -tags sqlite3_nosys .
echo freebsd-nosys ; GOOS=freebsd GOARCH=amd64 go build -tags sqlite3_nosys .

View File

@@ -2,22 +2,22 @@
set -euo pipefail
if [[ "$OSTYPE" == "linux"* ]]; then
WASI_SDK="https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-21/wasi-sdk-21.0-linux.tar.gz"
WASI_SDK="https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-22/wasi-sdk-22.0-linux.tar.gz"
BINARYEN="https://github.com/WebAssembly/binaryen/releases/download/version_117/binaryen-version_117-x86_64-linux.tar.gz"
elif [[ "$OSTYPE" == "darwin"* ]]; then
WASI_SDK="https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-21/wasi-sdk-21.0-macos.tar.gz"
WASI_SDK="https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-22/wasi-sdk-22.0-macos.tar.gz"
BINARYEN="https://github.com/WebAssembly/binaryen/releases/download/version_117/binaryen-version_117-x86_64-macos.tar.gz"
elif [[ "$OSTYPE" == "msys" || "$OSTYPE" == "cygwin" ]]; then
WASI_SDK="https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-21/wasi-sdk-21.0.m-mingw.tar.gz"
WASI_SDK="https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-22/wasi-sdk-22.0.m-mingw.tar.gz"
BINARYEN="https://github.com/WebAssembly/binaryen/releases/download/version_117/binaryen-version_117-x86_64-windows.tar.gz"
fi
# Download tools
mkdir -p tools
mkdir -p tools/
[ -d "tools/wasi-sdk"* ] || curl -#L "$WASI_SDK" | tar xzC tools &
[ -d "tools/binaryen-version"* ] || curl -#L "$BINARYEN" | tar xzC tools &
wait
sqlite3/download.sh # Download SQLite
embed/build.sh # Build WASM
embed/build.sh # Build Wasm
git diff --exit-code # Check diffs

View File

@@ -47,6 +47,10 @@ jobs:
run: go test -v -tags sqlite3_flock ./...
if: matrix.os == 'macos-latest'
- name: Test no shared memory
run: go test -v -tags sqlite3_noshm ./...
if: matrix.os == 'ubuntu-latest'
- name: Test no locks
run: go test -v -tags sqlite3_nosys ./tests -run TestDB_nolock
@@ -76,7 +80,7 @@ jobs:
run: .github/workflows/bsd.sh
- name: Test
uses: cross-platform-actions/action@v0.23.0
uses: cross-platform-actions/action@v0.24.0
with:
operating_system: freebsd
version: '14.0'

View File

@@ -8,7 +8,7 @@ Go module `github.com/ncruces/go-sqlite3` is `cgo`-free [SQLite](https://sqlite.
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 of SQLite, and uses [wazero](https://wazero.io/) as the runtime.\
It wraps a [Wasm](https://webassembly.org/) build 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.
### Packages
@@ -72,49 +72,10 @@ Go, wazero and [`x/sys`](https://pkg.go.dev/golang.org/x/sys) are the _only_ run
### Caveats
This module replaces the SQLite [OS Interface](https://sqlite.org/vfs.html)
(aka VFS) with a [pure Go](vfs/) implementation.
This has benefits, but also comes with some drawbacks.
(aka VFS) with a [pure Go](vfs/) implementation,
which has advantages and disadvantages.
#### Write-Ahead Logging
Because WASM does not support shared memory,
[WAL](https://sqlite.org/wal.html) support is [limited](https://sqlite.org/wal.html#noshm).
To work around this limitation, SQLite is [patched](sqlite3/locking_mode.patch)
to always use `EXCLUSIVE` locking mode for WAL databases.
Because connection pooling is incompatible with `EXCLUSIVE` locking mode,
to use the [`database/sql`](https://pkg.go.dev/database/sql) driver
with WAL mode databases you should disable connection pooling by calling
[`db.SetMaxOpenConns(1)`](https://pkg.go.dev/database/sql#DB.SetMaxOpenConns).
#### File Locking
POSIX advisory locks, which SQLite uses on Unix, are
[broken by design](https://sqlite.org/src/artifact/2e8b12?ln=1073-1161).
On Linux, macOS and illumos, this module uses
[OFD locks](https://www.gnu.org/software/libc/manual/html_node/Open-File-Description-Locks.html)
to synchronize access to database files.
OFD locks are fully compatible with POSIX advisory locks.
On BSD Unixes, this module uses
[BSD locks](https://man.freebsd.org/cgi/man.cgi?query=flock&sektion=2).
On BSD Unixes, BSD locks are fully compatible with POSIX advisory locks.
On Windows, this module uses `LockFileEx` and `UnlockFileEx`,
like SQLite.
On all other platforms, file locking is not supported, and you must use
[`nolock=1`](https://sqlite.org/uri.html#urinolock)
(or [`immutable=1`](https://sqlite.org/uri.html#uriimmutable))
to open database files.
You can use [`vfs.SupportsFileLocking`](https://pkg.go.dev/github.com/ncruces/go-sqlite3/vfs#SupportsFileLocking)
to check if your platform supports file locking.
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).
Read more about the Go VFS design [here](vfs/README.md).
### Testing
@@ -122,7 +83,7 @@ This project aims for [high test coverage](https://github.com/ncruces/go-sqlite3
It also benefits greatly from [SQLite's](https://sqlite.org/testing.html) and
[wazero's](https://tetrate.io/blog/introducing-wazero-from-tetrate/#:~:text=Rock%2Dsolid%20test%20approach) thorough testing.
The pure Go VFS is tested by running SQLite's
The Go VFS is tested by running SQLite's
[mptest](https://github.com/sqlite/sqlite/blob/master/mptest/mptest.c)
on Linux, macOS, Windows and FreeBSD.
@@ -131,7 +92,7 @@ on Linux, macOS, Windows and FreeBSD.
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
The Wasm and VFS layers are also tested by running SQLite's
[speedtest1](https://github.com/sqlite/sqlite/blob/master/test/speedtest1.c).
### Alternatives

View File

@@ -82,7 +82,7 @@ func (c *Conn) SetAuthorizer(cb func(action AuthorizerActionCode, name3rd, name4
}
func authorizerCallback(ctx context.Context, mod api.Module, pDB uint32, action AuthorizerActionCode, zName3rd, zName4th, zSchema, zNameInner uint32) AuthorizerReturnCode {
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 {
@@ -97,7 +97,68 @@ func authorizerCallback(ctx context.Context, mod api.Module, pDB uint32, action
if zNameInner != 0 {
nameInner = util.ReadString(mod, zNameInner, _MAX_NAME)
}
return c.authorizer(action, name3rd, name4th, schema, nameInner)
rc = c.authorizer(action, name3rd, name4th, schema, nameInner)
}
return AUTH_OK
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)))
}

36
conn.go
View File

@@ -29,6 +29,7 @@ type Conn struct {
update func(AuthorizerActionCode, string, string, int64)
commit func() bool
rollback func()
wal func(*Conn, string, int) error
arena arena
handle uint32
@@ -281,6 +282,12 @@ func (c *Conn) ReleaseMemory() error {
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]
@@ -325,13 +332,13 @@ func (c *Conn) checkInterrupt() {
}
}
func progressCallback(ctx context.Context, mod api.Module, pDB uint32) uint32 {
func progressCallback(ctx context.Context, mod api.Module, pDB uint32) (interrupt uint32) {
if c, ok := ctx.Value(connKey{}).(*Conn); ok && c.handle == pDB && c.commit != nil {
if c.interrupt != nil && c.interrupt.Err() != nil {
return 1
interrupt = 1
}
}
return 0
return interrupt
}
// BusyTimeout sets a busy timeout.
@@ -359,28 +366,13 @@ func (c *Conn) BusyHandler(cb func(count int) (retry bool)) error {
return nil
}
func busyCallback(ctx context.Context, mod api.Module, pDB, count uint32) uint32 {
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 {
if retry := c.busy(int(count)); retry {
return 1
if c.busy(int(count)) {
retry = 1
}
}
return 0
}
// Deprecated: executes a PRAGMA statement and returns results.
func (c *Conn) Pragma(str string) ([]string, error) {
stmt, _, err := c.Prepare(`PRAGMA ` + str)
if err != nil {
return nil, err
}
defer stmt.Close()
var pragmas []string
for stmt.Step() {
pragmas = append(pragmas, stmt.ColumnText(0))
}
return pragmas, stmt.Close()
return retry
}
func (c *Conn) error(rc uint64, sql ...string) error {

View File

@@ -256,41 +256,41 @@ const (
type AuthorizerActionCode uint32
const (
/************************************************ 3rd ************ 4th ***********/
CREATE_INDEX AuthorizerActionCode = 1 /* Index Name Table Name */
CREATE_TABLE AuthorizerActionCode = 2 /* Table Name NULL */
CREATE_TEMP_INDEX AuthorizerActionCode = 3 /* Index Name Table Name */
CREATE_TEMP_TABLE AuthorizerActionCode = 4 /* Table Name NULL */
CREATE_TEMP_TRIGGER AuthorizerActionCode = 5 /* Trigger Name Table Name */
CREATE_TEMP_VIEW AuthorizerActionCode = 6 /* View Name NULL */
CREATE_TRIGGER AuthorizerActionCode = 7 /* Trigger Name Table Name */
CREATE_VIEW AuthorizerActionCode = 8 /* View Name NULL */
DELETE AuthorizerActionCode = 9 /* Table Name NULL */
DROP_INDEX AuthorizerActionCode = 10 /* Index Name Table Name */
DROP_TABLE AuthorizerActionCode = 11 /* Table Name NULL */
DROP_TEMP_INDEX AuthorizerActionCode = 12 /* Index Name Table Name */
DROP_TEMP_TABLE AuthorizerActionCode = 13 /* Table Name NULL */
DROP_TEMP_TRIGGER AuthorizerActionCode = 14 /* Trigger Name Table Name */
DROP_TEMP_VIEW AuthorizerActionCode = 15 /* View Name NULL */
DROP_TRIGGER AuthorizerActionCode = 16 /* Trigger Name Table Name */
DROP_VIEW AuthorizerActionCode = 17 /* View Name NULL */
INSERT AuthorizerActionCode = 18 /* Table Name NULL */
PRAGMA AuthorizerActionCode = 19 /* Pragma Name 1st arg or NULL */
READ AuthorizerActionCode = 20 /* Table Name Column Name */
SELECT AuthorizerActionCode = 21 /* NULL NULL */
TRANSACTION AuthorizerActionCode = 22 /* Operation NULL */
UPDATE AuthorizerActionCode = 23 /* Table Name Column Name */
ATTACH AuthorizerActionCode = 24 /* Filename NULL */
DETACH AuthorizerActionCode = 25 /* Database Name NULL */
ALTER_TABLE AuthorizerActionCode = 26 /* Database Name Table Name */
REINDEX AuthorizerActionCode = 27 /* Index Name NULL */
ANALYZE AuthorizerActionCode = 28 /* Table Name NULL */
CREATE_VTABLE AuthorizerActionCode = 29 /* Table Name Module Name */
DROP_VTABLE AuthorizerActionCode = 30 /* Table Name Module Name */
FUNCTION AuthorizerActionCode = 31 /* NULL Function Name */
SAVEPOINT AuthorizerActionCode = 32 /* Operation Savepoint Name */
COPY AuthorizerActionCode = 0 /* No longer used */
RECURSIVE AuthorizerActionCode = 33 /* NULL NULL */
/***************************************************** 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
@@ -305,6 +305,18 @@ const (
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

View File

@@ -153,7 +153,7 @@ func Test_BeginTx(t *testing.T) {
t.Fatal(err)
}
_, err = tx1.Exec(`CREATE TABLE IF NOT EXISTS test (col)`)
_, err = tx1.Exec(`CREATE TABLE test (col)`)
if err == nil {
t.Error("want error")
}
@@ -310,7 +310,7 @@ func Test_time(t *testing.T) {
twosday := time.Date(2022, 2, 22, 22, 22, 22, 0, time.UTC)
_, err = db.Exec(`CREATE TABLE IF NOT EXISTS test (at DATETIME)`)
_, err = db.Exec(`CREATE TABLE test (at DATETIME)`)
if err != nil {
t.Fatal(err)
}

View File

@@ -18,7 +18,7 @@ func Example_json() {
defer db.Close()
_, err = db.Exec(`
CREATE TABLE IF NOT EXISTS orders (
CREATE TABLE orders (
cart_id INTEGER PRIMARY KEY,
user_id INTEGER NOT NULL,
cart TEXT

View File

@@ -16,7 +16,7 @@ func ExampleSavepoint() {
}
defer db.Close()
_, err = db.Exec(`CREATE TABLE IF NOT EXISTS users (id INT, name VARCHAR(10))`)
_, err = db.Exec(`CREATE TABLE users (id INT, name VARCHAR(10))`)
if err != nil {
log.Fatal(err)
}

View File

@@ -1,6 +1,6 @@
# Embeddable WASM build of SQLite
# Embeddable Wasm build of SQLite
This folder includes an embeddable WASM build of SQLite 3.45.1 for use with
This folder includes an embeddable Wasm build of SQLite 3.45.3 for use with
[`github.com/ncruces/go-sqlite3`](https://pkg.go.dev/github.com/ncruces/go-sqlite3).
The following optional features are compiled in:

View File

@@ -5,7 +5,7 @@ cd -P -- "$(dirname -- "$0")"
ROOT=../
BINARYEN="$ROOT/tools/binaryen-version_117/bin"
WASI_SDK="$ROOT/tools/wasi-sdk-21.0/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 \

View File

@@ -1,7 +1,9 @@
aligned_alloc
free
malloc
malloc_destructor
sqlite3_anycollseq_init
sqlite3_autovacuum_pages_go
sqlite3_backup_finish
sqlite3_backup_init
sqlite3_backup_pagecount
@@ -114,4 +116,7 @@ sqlite3_vtab_in_first
sqlite3_vtab_in_next
sqlite3_vtab_nochange
sqlite3_vtab_on_conflict
sqlite3_vtab_rhs_value
sqlite3_vtab_rhs_value
sqlite3_wal_autocheckpoint
sqlite3_wal_checkpoint_v2
sqlite3_wal_hook_go

Binary file not shown.

View File

@@ -16,7 +16,7 @@ func Example() {
log.Fatal(err)
}
err = db.Exec(`CREATE TABLE IF NOT EXISTS users (id INT, name VARCHAR(10))`)
err = db.Exec(`CREATE TABLE users (id INT, name VARCHAR(10))`)
if err != nil {
log.Fatal(err)
}

View File

@@ -27,7 +27,7 @@ func Example() {
}
defer db.Close()
_, err = db.Exec(`CREATE TABLE IF NOT EXISTS test (col)`)
_, err = db.Exec(`CREATE TABLE test (col)`)
if err != nil {
log.Fatal(err)
}
@@ -79,8 +79,8 @@ func Test_readblob(t *testing.T) {
}
err = db.Exec(`
CREATE TABLE IF NOT EXISTS test1 (col);
CREATE TABLE IF NOT EXISTS test2 (col);
CREATE TABLE test1 (col);
CREATE TABLE test2 (col);
INSERT INTO test1 VALUES (x'cafe');
INSERT INTO test2 VALUES (x'babe');
`)
@@ -139,8 +139,8 @@ func Test_openblob(t *testing.T) {
}
err = db.Exec(`
CREATE TABLE IF NOT EXISTS test1 (col);
CREATE TABLE IF NOT EXISTS test2 (col);
CREATE TABLE test1 (col);
CREATE TABLE test2 (col);
INSERT INTO test1 VALUES (x'cafe');
INSERT INTO test2 VALUES (x'babe');
`)

View File

@@ -20,7 +20,7 @@ func Example() {
csv.Register(db)
err = db.Exec(`
CREATE VIRTUAL TABLE IF NOT EXISTS eurofxref USING csv(
CREATE VIRTUAL TABLE eurofxref USING csv(
filename = 'testdata/eurofxref.csv',
header = YES,
columns = 42,

68
ext/fileio/coro.go Normal file
View File

@@ -0,0 +1,68 @@
package fileio
import (
"fmt"
"github.com/ncruces/go-sqlite3/internal/util"
)
// Adapted from: https://research.swtch.com/coro
const errCoroCanceled = util.ErrorString("coroutine canceled")
func coroNew[In, Out any](f func(In, func(Out) In) Out) (resume func(In) (Out, bool), cancel func()) {
type msg[T any] struct {
panic any
val T
}
cin := make(chan msg[In])
cout := make(chan msg[Out])
running := true
resume = func(in In) (out Out, ok bool) {
if !running {
return
}
cin <- msg[In]{val: in}
m := <-cout
if m.panic != nil {
panic(m.panic)
}
return m.val, running
}
cancel = func() {
if !running {
return
}
e := fmt.Errorf("%w", errCoroCanceled)
cin <- msg[In]{panic: e}
m := <-cout
if m.panic != nil && m.panic != e {
panic(m.panic)
}
}
yield := func(out Out) In {
cout <- msg[Out]{val: out}
m := <-cin
if m.panic != nil {
panic(m.panic)
}
return m.val
}
go func() {
defer func() {
if running {
running = false
cout <- msg[Out]{panic: recover()}
}
}()
var out Out
m := <-cin
if m.panic == nil {
out = f(m.val, yield)
}
running = false
cout <- msg[Out]{val: out}
}()
return resume, cancel
}

View File

@@ -53,12 +53,12 @@ func (d fsdir) Open() (sqlite3.VTabCursor, error) {
type cursor struct {
fsdir
curr entry
next chan entry
done chan struct{}
base string
rowID int64
eof bool
base string
resume func(struct{}) (entry, bool)
cancel func()
curr entry
eof bool
rowID int64
}
type entry struct {
@@ -68,12 +68,8 @@ type entry struct {
}
func (c *cursor) Close() error {
if c.done != nil {
close(c.done)
s := <-c.next
c.done = nil
c.next = nil
return s.err
if c.cancel != nil {
c.cancel()
}
return nil
}
@@ -96,16 +92,25 @@ func (c *cursor) Filter(idxNum int, idxStr string, arg ...sqlite3.Value) error {
c.base = base
}
c.rowID = 0
c.resume, c.cancel = coroNew(func(_ struct{}, yield func(entry) struct{}) entry {
walkDir := func(path string, d fs.DirEntry, err error) error {
yield(entry{d, err, path})
return nil
}
if c.fsys != nil {
fs.WalkDir(c.fsys, root, walkDir)
} else {
filepath.WalkDir(root, walkDir)
}
return entry{}
})
c.eof = false
c.next = make(chan entry)
c.done = make(chan struct{})
go c.WalkDir(root)
c.rowID = 0
return c.Next()
}
func (c *cursor) Next() error {
curr, ok := <-c.next
curr, ok := c.resume(struct{}{})
c.curr = curr
c.eof = !ok
c.rowID++
@@ -165,22 +170,3 @@ func (c *cursor) Column(ctx *sqlite3.Context, n int) error {
}
return nil
}
func (c *cursor) WalkDir(path string) {
defer close(c.next)
if c.fsys != nil {
fs.WalkDir(c.fsys, path, c.WalkDirFunc)
} else {
filepath.WalkDir(path, c.WalkDirFunc)
}
}
func (c *cursor) WalkDirFunc(path string, d fs.DirEntry, err error) error {
select {
case <-c.done:
return fs.SkipAll
case c.next <- entry{d, err, path}:
return nil
}
}

View File

@@ -28,7 +28,7 @@ func Test_fsdir(t *testing.T) {
}
defer db.Close()
rows, err := db.Query(`SELECT * FROM fsdir('.', '.') LIMIT 4`)
rows, err := db.Query(`SELECT * FROM fsdir('.', '.')`)
if err != nil {
t.Fatal(err)
}

View File

@@ -20,7 +20,7 @@ func TestRegister_variance(t *testing.T) {
stats.Register(db)
err = db.Exec(`CREATE TABLE IF NOT EXISTS data (x)`)
err = db.Exec(`CREATE TABLE data (x)`)
if err != nil {
t.Fatal(err)
}
@@ -92,7 +92,7 @@ func TestRegister_covariance(t *testing.T) {
stats.Register(db)
err = db.Exec(`CREATE TABLE IF NOT EXISTS data (y, x)`)
err = db.Exec(`CREATE TABLE data (y, x)`)
if err != nil {
t.Fatal(err)
}

View File

@@ -81,7 +81,7 @@ func TestRegister_collation(t *testing.T) {
Register(db)
err = db.Exec(`CREATE TABLE IF NOT EXISTS words (word VARCHAR(10))`)
err = db.Exec(`CREATE TABLE words (word VARCHAR(10))`)
if err != nil {
t.Fatal(err)
}

View File

@@ -18,7 +18,7 @@ func ExampleConn_CreateCollation() {
}
defer db.Close()
err = db.Exec(`CREATE TABLE IF NOT EXISTS words (word VARCHAR(10))`)
err = db.Exec(`CREATE TABLE words (word VARCHAR(10))`)
if err != nil {
log.Fatal(err)
}
@@ -66,7 +66,7 @@ func ExampleConn_CreateFunction() {
}
defer db.Close()
err = db.Exec(`CREATE TABLE IF NOT EXISTS words (word VARCHAR(10))`)
err = db.Exec(`CREATE TABLE words (word VARCHAR(10))`)
if err != nil {
log.Fatal(err)
}
@@ -111,7 +111,7 @@ func ExampleContext_SetAuxData() {
}
defer db.Close()
err = db.Exec(`CREATE TABLE IF NOT EXISTS words (word VARCHAR(10))`)
err = db.Exec(`CREATE TABLE words (word VARCHAR(10))`)
if err != nil {
log.Fatal(err)
}

View File

@@ -16,7 +16,7 @@ func ExampleConn_CreateWindowFunction() {
}
defer db.Close()
err = db.Exec(`CREATE TABLE IF NOT EXISTS words (word VARCHAR(10))`)
err = db.Exec(`CREATE TABLE words (word VARCHAR(10))`)
if err != nil {
log.Fatal(err)
}

8
go.mod
View File

@@ -5,10 +5,10 @@ go 1.21
require (
github.com/ncruces/julianday v1.0.0
github.com/psanford/httpreadat v0.1.0
github.com/tetratelabs/wazero v1.7.0
golang.org/x/crypto v0.21.0
golang.org/x/sync v0.6.0
golang.org/x/sys v0.18.0
github.com/tetratelabs/wazero v1.7.1
golang.org/x/crypto v0.22.0
golang.org/x/sync v0.7.0
golang.org/x/sys v0.19.0
golang.org/x/text v0.14.0
)

16
go.sum
View File

@@ -2,13 +2,13 @@ github.com/ncruces/julianday v1.0.0 h1:fH0OKwa7NWvniGQtxdJRxAgkBMolni2BjDHaWTxqt
github.com/ncruces/julianday v1.0.0/go.mod h1:Dusn2KvZrrovOMJuOt0TNXL6tB7U2E8kvza5fFc9G7g=
github.com/psanford/httpreadat v0.1.0 h1:VleW1HS2zO7/4c7c7zNl33fO6oYACSagjJIyMIwZLUE=
github.com/psanford/httpreadat v0.1.0/go.mod h1:Zg7P+TlBm3bYbyHTKv/EdtSJZn3qwbPwpfZ/I9GKCRE=
github.com/tetratelabs/wazero v1.7.0 h1:jg5qPydno59wqjpGrHph81lbtHzTrWzwwtD4cD88+hQ=
github.com/tetratelabs/wazero v1.7.0/go.mod h1:ytl6Zuh20R/eROuyDaGPkp82O9C/DJfXAwJfQ3X6/7Y=
golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA=
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ=
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
github.com/tetratelabs/wazero v1.7.1 h1:QtSfd6KLc41DIMpDYlJdoMc6k7QTN246DM2+n2Y/Dx8=
github.com/tetratelabs/wazero v1.7.1/go.mod h1:ytl6Zuh20R/eROuyDaGPkp82O9C/DJfXAwJfQ3X6/7Y=
golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30=
golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M=
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=
golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=

View File

@@ -1,4 +1,8 @@
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.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58=
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/tools v0.6.0 h1:BOw41kyTf3PuCW1pVQf8+Cyg8pMlkYB1oo9iJ6D/lKM=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=

View File

@@ -3,14 +3,14 @@ module github.com/ncruces/go-sqlite3/gormlite
go 1.21
require (
github.com/ncruces/go-sqlite3 v0.12.2
gorm.io/gorm v1.25.7
github.com/ncruces/go-sqlite3 v0.13.0
gorm.io/gorm v1.25.9
)
require (
github.com/jinzhu/inflection v1.0.0 // indirect
github.com/jinzhu/now v1.1.5 // indirect
github.com/ncruces/julianday v1.0.0 // indirect
github.com/tetratelabs/wazero v1.7.0-pre.1 // indirect
golang.org/x/sys v0.18.0 // indirect
github.com/tetratelabs/wazero v1.7.0 // indirect
golang.org/x/sys v0.19.0 // indirect
)

View File

@@ -2,15 +2,15 @@ github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
github.com/ncruces/go-sqlite3 v0.12.2 h1:NO8lFyFTA6aUtDWviQX2Rzqi1RX3X52peWq/MLgV1Gc=
github.com/ncruces/go-sqlite3 v0.12.2/go.mod h1:+8dWcBxb2Yar4EcCwav1a21MpKZbztwOYBLSRYt9bMY=
github.com/ncruces/go-sqlite3 v0.13.0 h1:+N1VHVLnrCJasyXdAKQL9MNTKC3wHZa8pLMUuP8wb3k=
github.com/ncruces/go-sqlite3 v0.13.0/go.mod h1:y9zPUI+C42V9xuuJeN+tGhpOjr4gUHz2Pi2RLFVEdZg=
github.com/ncruces/julianday v1.0.0 h1:fH0OKwa7NWvniGQtxdJRxAgkBMolni2BjDHaWTxqt7M=
github.com/ncruces/julianday v1.0.0/go.mod h1:Dusn2KvZrrovOMJuOt0TNXL6tB7U2E8kvza5fFc9G7g=
github.com/tetratelabs/wazero v1.7.0-pre.1 h1:mOcomS6m5tz4gZgUaocVm0o64uDPPAmErJJmiOVLHvw=
github.com/tetratelabs/wazero v1.7.0-pre.1/go.mod h1:ytl6Zuh20R/eROuyDaGPkp82O9C/DJfXAwJfQ3X6/7Y=
golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
github.com/tetratelabs/wazero v1.7.0 h1:jg5qPydno59wqjpGrHph81lbtHzTrWzwwtD4cD88+hQ=
github.com/tetratelabs/wazero v1.7.0/go.mod h1:ytl6Zuh20R/eROuyDaGPkp82O9C/DJfXAwJfQ3X6/7Y=
golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=
golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
gorm.io/gorm v1.25.7 h1:VsD6acwRjz2zFxGO50gPO6AkNs7KKnvfzUjHQhZDz/A=
gorm.io/gorm v1.25.7/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8=
gorm.io/gorm v1.25.9 h1:wct0gxZIELDk8+ZqF/MVnHLkA1rvYlBWUMv2EdsK1g8=
gorm.io/gorm v1.25.9/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8=

View File

@@ -334,7 +334,7 @@ type _Index struct {
// GetIndexes return Indexes []gorm.Index and execErr error,
// See the [doc]
//
// [doc]: https://www.sqlite.org/pragma.html#pragma_index_list
// [doc]: https://sqlite.org/pragma.html#pragma_index_list
func (m _Migrator) GetIndexes(value interface{}) ([]gorm.Index, error) {
indexes := make([]gorm.Index, 0)
err := m.RunWithValue(value, func(stmt *gorm.Statement) error {

View File

@@ -7,7 +7,7 @@ rm -rf gorm/ tests/
go work use -r .
go test
git clone --branch v1.25.7 --filter=blob:none https://github.com/go-gorm/gorm.git
git clone --branch v1.25.9 --filter=blob:none https://github.com/go-gorm/gorm.git
mv gorm/tests tests
rm -rf gorm/

71
internal/util/alloc.go Normal file
View File

@@ -0,0 +1,71 @@
//go:build unix
package util
import (
"math"
"github.com/tetratelabs/wazero/experimental"
"golang.org/x/sys/unix"
)
func mmappedAllocator(cap, max uint64) experimental.LinearMemory {
// Round up to the page size.
rnd := uint64(unix.Getpagesize() - 1)
max = (max + rnd) &^ rnd
cap = (cap + 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)
}
// Commit the initial cap bytes of memory.
err = unix.Mprotect(b[:cap], unix.PROT_READ|unix.PROT_WRITE)
if err != nil {
unix.Munmap(b)
panic(err)
}
return &mmappedMemory{buf: b[:cap]}
}
// 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 {
if com := uint64(len(m.buf)); com < size {
// 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
}

View File

@@ -3,21 +3,11 @@ package util
import (
"context"
"io"
"github.com/tetratelabs/wazero/experimental"
)
type handleKey struct{}
type handleState struct {
handles []any
empty int
}
func NewContext(ctx context.Context) context.Context {
state := new(handleState)
ctx = experimental.WithCloseNotifier(ctx, state)
ctx = context.WithValue(ctx, handleKey{}, state)
return ctx
holes int
}
func (s *handleState) CloseNotify(ctx context.Context, exitCode uint32) {
@@ -27,14 +17,14 @@ func (s *handleState) CloseNotify(ctx context.Context, exitCode uint32) {
}
}
s.handles = nil
s.empty = 0
s.holes = 0
}
func GetHandle(ctx context.Context, id uint32) any {
if id == 0 {
return nil
}
s := ctx.Value(handleKey{}).(*handleState)
s := ctx.Value(moduleKey{}).(*moduleState)
return s.handles[^id]
}
@@ -42,10 +32,10 @@ func DelHandle(ctx context.Context, id uint32) error {
if id == 0 {
return nil
}
s := ctx.Value(handleKey{}).(*handleState)
s := ctx.Value(moduleKey{}).(*moduleState)
a := s.handles[^id]
s.handles[^id] = nil
s.empty++
s.holes++
if c, ok := a.(io.Closer); ok {
return c.Close()
}
@@ -56,13 +46,13 @@ func AddHandle(ctx context.Context, a any) (id uint32) {
if a == nil {
panic(NilErr)
}
s := ctx.Value(handleKey{}).(*handleState)
s := ctx.Value(moduleKey{}).(*moduleState)
// Find an empty slot.
if s.empty > cap(s.handles)-len(s.handles) {
if s.holes > cap(s.handles)-len(s.handles) {
for id, h := range s.handles {
if h == nil {
s.empty--
s.holes--
s.handles[id] = a
return ^uint32(id)
}

106
internal/util/mmap.go Normal file
View File

@@ -0,0 +1,106 @@
//go:build (darwin || linux || illumos) && (amd64 || arm64 || riscv64) && !sqlite3_flock && !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"
)
type mmapState struct {
regions []*MappedRegion
enabled bool
}
func (s *mmapState) init(ctx context.Context, enabled bool) context.Context {
if s.enabled = enabled; enabled {
return experimental.WithMemoryAllocator(ctx,
experimental.MemoryAllocatorFunc(mmappedAllocator))
}
return ctx
}
func CanMap(ctx context.Context) bool {
s := ctx.Value(moduleKey{}).(*moduleState)
return s.mmapState.enabled
}
func (s *mmapState) new(ctx context.Context, mod api.Module, size int32) *MappedRegion {
// 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)

View File

@@ -0,0 +1,15 @@
//go:build !(darwin || linux || illumos) || !(amd64 || arm64 || riscv64) || sqlite3_flock || sqlite3_noshm || sqlite3_nosys
package util
import "context"
type mmapState struct{}
func (s *mmapState) init(ctx context.Context, _ bool) context.Context {
return ctx
}
func CanMap(ctx context.Context) bool {
return false
}

21
internal/util/module.go Normal file
View File

@@ -0,0 +1,21 @@
package util
import (
"context"
"github.com/tetratelabs/wazero/experimental"
)
type moduleKey struct{}
type moduleState struct {
handleState
mmapState
}
func NewContext(ctx context.Context, mappableMemory bool) context.Context {
state := new(moduleState)
ctx = context.WithValue(ctx, moduleKey{}, state)
ctx = experimental.WithCloseNotifier(ctx, state)
ctx = state.mmapState.init(ctx, mappableMemory)
return ctx
}

View File

@@ -15,14 +15,14 @@ import (
"github.com/tetratelabs/wazero/api"
)
// Configure SQLite WASM.
// Configure SQLite Wasm.
//
// Importing package embed initializes these
// 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.
Binary []byte // Wasm binary to load.
Path string // Path to load the binary from.
RuntimeConfig wazero.RuntimeConfig
@@ -85,10 +85,10 @@ func instantiateSQLite() (sqlt *sqlite, err error) {
}
sqlt = new(sqlite)
sqlt.ctx = util.NewContext(context.Background())
sqlt.ctx = util.NewContext(context.Background(), vfs.SupportsSharedMemory)
sqlt.mod, err = instance.runtime.InstantiateModule(sqlt.ctx,
instance.compiled, wazero.NewModuleConfig())
instance.compiled, wazero.NewModuleConfig().WithName(""))
if err != nil {
return nil, err
}
@@ -99,8 +99,8 @@ func instantiateSQLite() (sqlt *sqlite, err error) {
}
sqlt.freer = util.ReadUint32(sqlt.mod, uint32(global.Get()))
if err != nil {
return nil, err
if sqlt.freer == 0 {
return nil, util.BadBinaryErr
}
return sqlt, nil
}
@@ -274,7 +274,7 @@ func (a *arena) new(size uint64) uint32 {
}
func (a *arena) bytes(b []byte) uint32 {
if b == nil {
if (*[0]byte)(b) == nil {
return 0
}
ptr := a.new(uint64(len(b)))
@@ -294,6 +294,8 @@ func exportCallbacks(env wazero.HostModuleBuilder) wazero.HostModuleBuilder {
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)

View File

@@ -12,25 +12,25 @@ cat *.patch | patch --no-backup-if-mismatch
mkdir -p ext/
cd ext/
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.45.2/ext/misc/anycollseq.c"
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.45.2/ext/misc/base64.c"
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.45.2/ext/misc/decimal.c"
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.45.2/ext/misc/ieee754.c"
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.45.2/ext/misc/regexp.c"
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.45.2/ext/misc/series.c"
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.45.2/ext/misc/uint.c"
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.45.2/ext/misc/uuid.c"
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.45.3/ext/misc/anycollseq.c"
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.45.3/ext/misc/base64.c"
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.45.3/ext/misc/decimal.c"
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.45.3/ext/misc/ieee754.c"
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.45.3/ext/misc/regexp.c"
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.45.3/ext/misc/series.c"
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.45.3/ext/misc/uint.c"
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.45.3/ext/misc/uuid.c"
cd ~-
cd ../vfs/tests/mptest/testdata/
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.45.2/mptest/mptest.c"
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.45.2/mptest/config01.test"
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.45.2/mptest/config02.test"
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.45.2/mptest/crash01.test"
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.45.2/mptest/crash02.subtest"
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.45.2/mptest/multiwrite01.test"
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.45.3/mptest/mptest.c"
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.45.3/mptest/config01.test"
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.45.3/mptest/config02.test"
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.45.3/mptest/crash01.test"
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.45.3/mptest/crash02.subtest"
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.45.3/mptest/multiwrite01.test"
cd ~-
cd ../vfs/tests/speedtest1/testdata/
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.45.2/test/speedtest1.c"
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.45.3/test/speedtest1.c"
cd ~-

View File

@@ -8,12 +8,16 @@ int go_busy_handler(void *, int);
int go_commit_hook(void *);
void go_rollback_hook(void *);
void go_update_hook(void *, int, char const *, char const *, sqlite3_int64);
int go_wal_hook(void *, sqlite3 *, const char *, int);
int go_authorizer(void *, int, const char *, const char *, const char *,
const char *);
void go_log(void *, int, const char *);
unsigned int go_autovacuum_pages(void *, const char *, unsigned int,
unsigned int, unsigned int);
void sqlite3_progress_handler_go(sqlite3 *db, int n) {
sqlite3_progress_handler(db, n, go_progress_handler, /*arg=*/db);
}
@@ -34,6 +38,10 @@ void sqlite3_update_hook_go(sqlite3 *db, bool enable) {
sqlite3_update_hook(db, enable ? go_update_hook : NULL, /*arg=*/db);
}
void sqlite3_wal_hook_go(sqlite3 *db, bool enable) {
sqlite3_wal_hook(db, enable ? go_wal_hook : NULL, /*arg=*/NULL);
}
int sqlite3_set_authorizer_go(sqlite3 *db, bool enable) {
return sqlite3_set_authorizer(db, enable ? go_authorizer : NULL, /*arg=*/db);
}
@@ -41,4 +49,10 @@ int sqlite3_set_authorizer_go(sqlite3 *db, bool enable) {
int sqlite3_config_log_go(bool enable) {
return sqlite3_config(SQLITE_CONFIG_LOG, enable ? go_log : NULL,
/*arg=*/NULL);
}
int sqlite3_autovacuum_pages_go(sqlite3 *db, go_handle app) {
int rc = sqlite3_autovacuum_pages(db, go_autovacuum_pages, app, go_destroy);
if (rc) go_destroy(app);
return rc;
}

View File

@@ -57,8 +57,8 @@
#define SQLITE_ENABLE_ATOMIC_WRITE
#define SQLITE_ENABLE_BATCH_ATOMIC_WRITE
// Because WASM does not support shared memory,
// SQLite disables WAL for WASM builds.
// Because Wasm does not support shared memory,
// SQLite disables WAL for Wasm builds.
// We patch SQLite to use exclusive locking mode instead.
// https://sqlite.org/wal.html#noshm
#undef SQLITE_OMIT_WAL

View File

@@ -5,15 +5,15 @@
#include "include.h"
#include "sqlite3.h"
int go_localtime(struct tm *, sqlite3_int64);
int go_vfs_find(const char *zVfsName);
int go_localtime(struct tm *, sqlite3_int64);
int go_randomness(sqlite3_vfs *, int nByte, char *zOut);
int go_sleep(sqlite3_vfs *, int microseconds);
int go_current_time_64(sqlite3_vfs *, sqlite3_int64 *);
int go_open(sqlite3_vfs *, sqlite3_filename zName, sqlite3_file *, int flags,
int *pOutFlags);
int *pOutFlags, int *pOutVFS);
int go_delete(sqlite3_vfs *, const char *zName, int syncDir);
int go_access(sqlite3_vfs *, const char *zName, int flags, int *pResOut);
int go_full_pathname(sqlite3_vfs *, const char *zName, int nOut, char *zOut);
@@ -32,29 +32,55 @@ int go_lock(sqlite3_file *, int eLock);
int go_unlock(sqlite3_file *, int eLock);
int go_check_reserved_lock(sqlite3_file *, int *pResOut);
int go_shm_map(sqlite3_file *, int iPg, int pgsz, int, void volatile **);
int go_shm_lock(sqlite3_file *, int offset, int n, int flags);
int go_shm_unmap(sqlite3_file *, int deleteFlag);
void go_shm_barrier(sqlite3_file *);
static int go_open_wrapper(sqlite3_vfs *vfs, sqlite3_filename zName,
sqlite3_file *file, int flags, int *pOutFlags) {
static const sqlite3_io_methods os_io = {
.iVersion = 1,
.xClose = go_close,
.xRead = go_read,
.xWrite = go_write,
.xTruncate = go_truncate,
.xSync = go_sync,
.xFileSize = go_file_size,
.xLock = go_lock,
.xUnlock = go_unlock,
.xCheckReservedLock = go_check_reserved_lock,
.xFileControl = go_file_control,
.xSectorSize = go_sector_size,
.xDeviceCharacteristics = go_device_characteristics,
};
static const sqlite3_io_methods go_io[2] = {
{
.iVersion = 1,
.xClose = go_close,
.xRead = go_read,
.xWrite = go_write,
.xTruncate = go_truncate,
.xSync = go_sync,
.xFileSize = go_file_size,
.xLock = go_lock,
.xUnlock = go_unlock,
.xCheckReservedLock = go_check_reserved_lock,
.xFileControl = go_file_control,
.xSectorSize = go_sector_size,
.xDeviceCharacteristics = go_device_characteristics,
},
{
.iVersion = 2,
.xClose = go_close,
.xRead = go_read,
.xWrite = go_write,
.xTruncate = go_truncate,
.xSync = go_sync,
.xFileSize = go_file_size,
.xLock = go_lock,
.xUnlock = go_unlock,
.xCheckReservedLock = go_check_reserved_lock,
.xFileControl = go_file_control,
.xSectorSize = go_sector_size,
.xDeviceCharacteristics = go_device_characteristics,
.xShmMap = go_shm_map,
.xShmLock = go_shm_lock,
.xShmBarrier = go_shm_barrier,
.xShmUnmap = go_shm_unmap,
}};
int vfsID = 0;
memset(file, 0, vfs->szOsFile);
int rc = go_open(vfs, zName, file, flags, pOutFlags);
int rc = go_open(vfs, zName, file, flags, pOutFlags, &vfsID);
if (rc) {
return rc;
}
file->pMethods = &os_io;
file->pMethods = &go_io[vfsID];
return SQLITE_OK;
}

16
stmt.go
View File

@@ -120,7 +120,7 @@ func (s *Stmt) Status(op StmtStatus, reset bool) int {
}
r := s.c.call("sqlite3_stmt_status", uint64(s.handle),
uint64(op), i)
return int(r)
return int(int32(r))
}
// ClearBindings resets all bindings on the prepared statement.
@@ -137,7 +137,7 @@ func (s *Stmt) ClearBindings() error {
func (s *Stmt) BindCount() int {
r := s.c.call("sqlite3_bind_parameter_count",
uint64(s.handle))
return int(r)
return int(int32(r))
}
// BindIndex returns the index of a parameter in the prepared statement
@@ -149,7 +149,7 @@ func (s *Stmt) BindIndex(name string) int {
namePtr := s.c.arena.string(name)
r := s.c.call("sqlite3_bind_parameter_index",
uint64(s.handle), uint64(namePtr))
return int(r)
return int(int32(r))
}
// BindName returns the name of a parameter in the prepared statement.
@@ -357,7 +357,7 @@ func (s *Stmt) BindValue(param int, value Value) error {
func (s *Stmt) ColumnCount() int {
r := s.c.call("sqlite3_column_count",
uint64(s.handle))
return int(r)
return int(int32(r))
}
// ColumnName returns the name of the result column.
@@ -553,6 +553,14 @@ func (s *Stmt) ColumnValue(col int) Value {
}
}
// 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))

View File

@@ -20,7 +20,7 @@ func TestBackup(t *testing.T) {
}
defer db.Close()
err = db.Exec(`CREATE TABLE IF NOT EXISTS users (id INT, name VARCHAR(10))`)
err = db.Exec(`CREATE TABLE users (id INT, name VARCHAR(10))`)
if err != nil {
t.Fatal(err)
}

View File

@@ -22,7 +22,7 @@ func TestBlob(t *testing.T) {
}
defer db.Close()
err = db.Exec(`CREATE TABLE IF NOT EXISTS test (col)`)
err = db.Exec(`CREATE TABLE test (col)`)
if err != nil {
t.Fatal(err)
}
@@ -97,7 +97,7 @@ func TestBlob_large(t *testing.T) {
}
defer db.Close()
err = db.Exec(`CREATE TABLE IF NOT EXISTS test (col)`)
err = db.Exec(`CREATE TABLE test (col)`)
if err != nil {
t.Fatal(err)
}
@@ -158,7 +158,7 @@ func TestBlob_overflow(t *testing.T) {
}
defer db.Close()
err = db.Exec(`CREATE TABLE IF NOT EXISTS test (col)`)
err = db.Exec(`CREATE TABLE test (col)`)
if err != nil {
t.Fatal(err)
}
@@ -217,7 +217,7 @@ func TestBlob_invalid(t *testing.T) {
}
defer db.Close()
err = db.Exec(`CREATE TABLE IF NOT EXISTS test (col)`)
err = db.Exec(`CREATE TABLE test (col)`)
if err != nil {
t.Fatal(err)
}
@@ -242,7 +242,7 @@ func TestBlob_Write_readonly(t *testing.T) {
}
defer db.Close()
err = db.Exec(`CREATE TABLE IF NOT EXISTS test (col)`)
err = db.Exec(`CREATE TABLE test (col)`)
if err != nil {
t.Fatal(err)
}
@@ -273,7 +273,7 @@ func TestBlob_Read_expired(t *testing.T) {
}
defer db.Close()
err = db.Exec(`CREATE TABLE IF NOT EXISTS test (col)`)
err = db.Exec(`CREATE TABLE test (col)`)
if err != nil {
t.Fatal(err)
}
@@ -309,7 +309,7 @@ func TestBlob_Seek(t *testing.T) {
}
defer db.Close()
err = db.Exec(`CREATE TABLE IF NOT EXISTS test (col)`)
err = db.Exec(`CREATE TABLE test (col)`)
if err != nil {
t.Fatal(err)
}
@@ -358,7 +358,7 @@ func TestBlob_Reopen(t *testing.T) {
}
defer db.Close()
err = db.Exec(`CREATE TABLE IF NOT EXISTS test (col)`)
err = db.Exec(`CREATE TABLE test (col)`)
if err != nil {
t.Fatal(err)
}

View File

@@ -6,12 +6,12 @@ import (
"math"
"os"
"path/filepath"
"reflect"
"strings"
"testing"
"github.com/ncruces/go-sqlite3"
_ "github.com/ncruces/go-sqlite3/embed"
_ "github.com/ncruces/go-sqlite3/vfs/memdb"
)
func TestConn_Open_dir(t *testing.T) {
@@ -111,41 +111,6 @@ func TestConn_Close_BUSY(t *testing.T) {
}
}
func TestConn_Pragma(t *testing.T) {
t.Parallel()
db, err := sqlite3.Open("file::memory:?_pragma=busy_timeout(1000)")
if err != nil {
t.Fatal(err)
}
defer db.Close()
got, err := db.Pragma("busy_timeout")
if err != nil {
t.Fatal(err)
}
want := []string{"1000"}
if !reflect.DeepEqual(got, want) {
t.Errorf("got %v, want %v", got, want)
}
var serr *sqlite3.Error
_, err = db.Pragma("+")
if err == nil {
t.Error("want: error")
}
if !errors.As(err, &serr) {
t.Fatalf("got %T, want sqlite3.Error", err)
}
if rc := serr.Code(); rc != sqlite3.ERROR {
t.Errorf("got %d, want sqlite3.ERROR", rc)
}
if got := err.Error(); got != `sqlite3: SQL logic error: near "+": syntax error` {
t.Error("got message:", got)
}
}
func TestConn_SetInterrupt(t *testing.T) {
t.Parallel()
@@ -485,3 +450,35 @@ func TestConn_DBName(t *testing.T) {
t.Errorf("got %s", name)
}
}
func TestConn_AutoVacuumPages(t *testing.T) {
t.Parallel()
db, err := sqlite3.Open("file:test.db?vfs=memdb&_pragma=auto_vacuum(FULL)")
if err != nil {
t.Fatal(err)
}
defer db.Close()
err = db.AutoVacuumPages(func(schema string, dbPages, freePages, bytesPerPage uint) uint {
return freePages
})
if err != nil {
t.Fatal(err)
}
err = db.Exec(`CREATE TABLE test (col)`)
if err != nil {
t.Fatal(err)
}
err = db.Exec(`INSERT INTO test VALUES (zeroblob(1024*1024))`)
if err != nil {
t.Fatal(err)
}
err = db.Exec(`DROP TABLE test`)
if err != nil {
t.Fatal(err)
}
}

View File

@@ -27,7 +27,7 @@ func TestDriver(t *testing.T) {
defer conn.Close()
res, err := conn.ExecContext(ctx,
`CREATE TABLE IF NOT EXISTS users (id INT, name VARCHAR(10))`)
`CREATE TABLE users (id INT, name VARCHAR(10))`)
if err != nil {
t.Fatal(err)
}

View File

@@ -196,7 +196,7 @@ func TestAnyCollationNeeded(t *testing.T) {
}
defer db.Close()
err = db.Exec(`CREATE TABLE IF NOT EXISTS users (id INT, name VARCHAR(10))`)
err = db.Exec(`CREATE TABLE users (id INT, name VARCHAR(10))`)
if err != nil {
t.Fatal(err)
}

View File

@@ -31,7 +31,7 @@ func TestJSON(t *testing.T) {
}
defer conn.Close()
_, err = conn.ExecContext(ctx, `CREATE TABLE IF NOT EXISTS test (col)`)
_, err = conn.ExecContext(ctx, `CREATE TABLE test (col)`)
if err != nil {
t.Fatal(err)
}

View File

@@ -12,9 +12,16 @@ import (
"github.com/ncruces/go-sqlite3"
_ "github.com/ncruces/go-sqlite3/embed"
"github.com/ncruces/go-sqlite3/vfs"
"github.com/ncruces/go-sqlite3/vfs/memdb"
"github.com/tetratelabs/wazero"
)
func TestMain(m *testing.M) {
sqlite3.RuntimeConfig = wazero.NewRuntimeConfig().WithMemoryLimitPages(1024)
os.Exit(m.Run())
}
func TestParallel(t *testing.T) {
var iter int
if testing.Short() {
@@ -32,6 +39,20 @@ func TestParallel(t *testing.T) {
testIntegrity(t, name)
}
func TestWAL(t *testing.T) {
if !vfs.SupportsSharedMemory {
t.Skip("skipping without shared memory")
}
name := "file:" +
filepath.Join(t.TempDir(), "test.db") +
"?_pragma=busy_timeout(10000)" +
"&_pragma=journal_mode(wal)" +
"&_pragma=synchronous(off)"
testParallel(t, name, 1000)
testIntegrity(t, name)
}
func TestMemory(t *testing.T) {
var iter int
if testing.Short() {

View File

@@ -19,7 +19,7 @@ func TestStmt(t *testing.T) {
}
defer db.Close()
err = db.Exec(`CREATE TABLE IF NOT EXISTS test (col)`)
err = db.Exec(`CREATE TABLE test (col)`)
if err != nil {
t.Fatal(err)
}

BIN
tests/testdata/f2fs.img.gz vendored Normal file

Binary file not shown.

21
tests/testdata/f2fs.sh vendored Executable file
View File

@@ -0,0 +1,21 @@
#!/usr/bin/env bash
set -euo pipefail
cd -P -- "$(dirname -- "$0")"
ROOT=../../
if mountpoint -q f2fs/; then
sudo umount f2fs/
fi
mkdir -p f2fs/
gunzip -c f2fs.img.gz > f2fs.img
sudo mount -nv -o loop f2fs.img f2fs/
mkdir -p f2fs/tmp/
go test -c "$ROOT/tests" -coverpkg github.com/ncruces/go-sqlite3/...
TMPDIR=f2fs/tmp/ ./tests.test -test.v -test.short -test.coverprofile cover.out
go tool cover -html cover.out
sudo umount f2fs/
rm -r f2fs/ f2fs.img cover.out *.test

View File

@@ -146,8 +146,7 @@ func TestTimeFormat_Scanner(t *testing.T) {
}
defer conn.Close()
_, err = conn.ExecContext(ctx,
`CREATE TABLE IF NOT EXISTS test (col)`)
_, err = conn.ExecContext(ctx, `CREATE TABLE test (col)`)
if err != nil {
t.Fatal(err)
}
@@ -178,7 +177,7 @@ func TestDB_timeCollation(t *testing.T) {
}
defer db.Close()
err = db.Exec(`CREATE TABLE IF NOT EXISTS times (tstamp COLLATE TIME)`)
err = db.Exec(`CREATE TABLE times (tstamp COLLATE TIME)`)
if err != nil {
t.Fatal(err)
}

View File

@@ -22,7 +22,7 @@ func TestConn_Transaction_exec(t *testing.T) {
db.CommitHook(func() bool { return true })
db.UpdateHook(func(sqlite3.AuthorizerActionCode, string, string, int64) {})
err = db.Exec(`CREATE TABLE IF NOT EXISTS test (col)`)
err = db.Exec(`CREATE TABLE test (col)`)
if err != nil {
t.Fatal(err)
}
@@ -95,7 +95,7 @@ func TestConn_Transaction_panic(t *testing.T) {
}
defer db.Close()
err = db.Exec(`CREATE TABLE IF NOT EXISTS test (col)`)
err = db.Exec(`CREATE TABLE test (col)`)
if err != nil {
t.Fatal(err)
}
@@ -153,7 +153,7 @@ func TestConn_Transaction_interrupt(t *testing.T) {
}
defer db.Close()
err = db.Exec(`CREATE TABLE IF NOT EXISTS test (col)`)
err = db.Exec(`CREATE TABLE test (col)`)
if err != nil {
t.Fatal(err)
}
@@ -255,7 +255,7 @@ func TestConn_Transaction_rollback(t *testing.T) {
}
defer db.Close()
err = db.Exec(`CREATE TABLE IF NOT EXISTS test (col)`)
err = db.Exec(`CREATE TABLE test (col)`)
if err != nil {
t.Fatal(err)
}
@@ -301,7 +301,7 @@ func TestConn_Savepoint_exec(t *testing.T) {
}
defer db.Close()
err = db.Exec(`CREATE TABLE IF NOT EXISTS test (col)`)
err = db.Exec(`CREATE TABLE test (col)`)
if err != nil {
t.Fatal(err)
}
@@ -369,7 +369,7 @@ func TestConn_Savepoint_panic(t *testing.T) {
}
defer db.Close()
err = db.Exec(`CREATE TABLE IF NOT EXISTS test (col)`)
err = db.Exec(`CREATE TABLE test (col)`)
if err != nil {
t.Fatal(err)
}
@@ -426,7 +426,7 @@ func TestConn_Savepoint_interrupt(t *testing.T) {
}
defer db.Close()
err = db.Exec(`CREATE TABLE IF NOT EXISTS test (col)`)
err = db.Exec(`CREATE TABLE test (col)`)
if err != nil {
t.Fatal(err)
}
@@ -506,7 +506,7 @@ func TestConn_Savepoint_rollback(t *testing.T) {
}
defer db.Close()
err = db.Exec(`CREATE TABLE IF NOT EXISTS test (col)`)
err = db.Exec(`CREATE TABLE test (col)`)
if err != nil {
t.Fatal(err)
}

96
tests/wal_test.go Normal file
View File

@@ -0,0 +1,96 @@
package tests
import (
"os"
"path/filepath"
"testing"
"github.com/ncruces/go-sqlite3"
"github.com/ncruces/go-sqlite3/vfs"
)
func TestWAL_enter_exit(t *testing.T) {
t.Parallel()
file := filepath.Join(t.TempDir(), "test.db")
db, err := sqlite3.Open(file)
if err != nil {
t.Fatal(err)
}
defer db.Close()
err = db.Exec(`
CREATE TABLE test (col);
PRAGMA journal_mode=WAL;
SELECT * FROM test;
PRAGMA journal_mode=DELETE;
SELECT * FROM test;
PRAGMA journal_mode=WAL;
SELECT * FROM test;
`)
if err != nil {
t.Fatal(err)
}
}
func TestWAL_readonly(t *testing.T) {
if !vfs.SupportsSharedMemory {
t.Skip("skipping without shared memory")
}
t.Parallel()
tmp := filepath.Join(t.TempDir(), "test.db")
err := os.WriteFile(tmp, waldb, 0666)
if err != nil {
t.Fatal(err)
}
db, err := sqlite3.OpenFlags(tmp, sqlite3.OPEN_READONLY)
if err != nil {
t.Fatal(err)
}
defer db.Close()
stmt, _, err := db.Prepare(`SELECT * FROM sqlite_master`)
if err != nil {
t.Fatal(err)
}
defer stmt.Close()
if stmt.Step() {
t.Error("want no rows")
}
}
func TestConn_WalCheckpoint(t *testing.T) {
t.Parallel()
file := filepath.Join(t.TempDir(), "test.db")
db, err := sqlite3.Open(file)
if err != nil {
t.Fatal(err)
}
defer db.Close()
err = db.WalAutoCheckpoint(1000)
if err != nil {
t.Fatal(err)
}
db.WalHook(func(db *sqlite3.Conn, schema string, pages int) error {
log, ckpt, err := db.WalCheckpoint(schema, sqlite3.CHECKPOINT_FULL)
t.Log(log, ckpt, err)
return err
})
err = db.Exec(`
PRAGMA journal_mode=WAL;
CREATE TABLE test (col);
`)
if err != nil {
t.Fatal(err)
}
}

13
txn.go
View File

@@ -230,9 +230,6 @@ func (c *Conn) TxnState(schema string) TxnState {
return TxnState(r)
}
// Deprecated: renamed for consistency with [Conn.TxnState].
type Tx = Txn
// CommitHook registers a callback function to be invoked
// whenever a transaction is committed.
// Return true to allow the commit operation to continue normally.
@@ -260,7 +257,7 @@ func (c *Conn) RollbackHook(cb func()) {
c.rollback = cb
}
// RollbackHook registers a callback function to be invoked
// 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
@@ -273,13 +270,13 @@ func (c *Conn) UpdateHook(cb func(action AuthorizerActionCode, schema, table str
c.update = cb
}
func commitCallback(ctx context.Context, mod api.Module, pDB uint32) uint32 {
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 ok := c.commit(); !ok {
return 1
if !c.commit() {
rollback = 1
}
}
return 0
return rollback
}
func rollbackCallback(ctx context.Context, mod api.Module, pDB uint32) {

View File

@@ -1,3 +1,4 @@
// Package fsutil implements filesystem utility functions.
package fsutil
import (

View File

@@ -8,8 +8,8 @@ import (
// SeekingReaderAt implements [io.ReaderAt]
// through an underlying [io.ReadSeeker].
type SeekingReaderAt struct {
l sync.Mutex
r io.ReadSeeker
l sync.Mutex
}
// NewSeekingReaderAt creates a new SeekingReaderAt.

View File

@@ -2,6 +2,79 @@
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.
It replaces the default SQLite VFS with a **pure Go** implementation.
It also exposes interfaces that should allow you to implement your own custom VFSes.
It also 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://sqlite.org/src/artifact/2e8b12?ln=1073-1161).
On Linux, macOS and illumos, this module uses
[OFD locks](https://www.gnu.org/software/libc/manual/html_node/Open-File-Description-Locks.html)
to synchronize access to database files.
OFD locks are fully compatible with POSIX advisory locks.
On BSD Unixes, this module uses
[BSD locks](https://man.freebsd.org/cgi/man.cgi?query=flock&sektion=2).
On BSD Unixes, BSD locks are fully compatible with POSIX advisory locks.
However, concurrency is reduced with BSD locks
(`BEGIN IMMEDIATE` behaves the same as `BEGIN EXCLUSIVE`).
On Windows, this module uses `LockFileEx` and `UnlockFileEx`,
like SQLite.
On all other platforms, file locking is not supported, and you must use
[`nolock=1`](https://sqlite.org/uri.html#urinolock)
(or [`immutable=1`](https://sqlite.org/uri.html#uriimmutable))
to open database files.
To 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 platform supports file locking.
### Write-Ahead Logging
On 64-bit Linux, macOS and illumos, 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 amount of address space each connection needs,
use [`WithMemoryLimitPages`](../tests/parallel/parallel_test.go#L21).
On all other platforms, [WAL](https://sqlite.org/wal.html) support is
[limited](https://sqlite.org/wal.html#noshm).
To work around this limitation, SQLite is [patched](sqlite3/locking_mode.patch)
to automatically use `EXCLUSIVE` locking mode for WAL databases on such platforms.
To use the [`database/sql`](https://pkg.go.dev/database/sql) driver
with `EXCLUSIVE` locking mode you should disable connection pooling by calling
[`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 platform supports shared memory.
### Batch-Atomic Write
On 64-bit Linux, this module supports [batch-atomic writes](https://sqlite.org/cgi/src/technote/714)
on the F2FS filesystem.
### 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 macOS to test the BSD locking implementation.
- `sqlite3_nosys` prevents importing [`x/sys`](https://pkg.go.dev/golang.org/x/sys);
disables locking _and_ shared memory on all platforms.
- `sqlite3_noshm` disables shared memory on all platforms.

View File

@@ -37,6 +37,10 @@ const (
_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
@@ -44,6 +48,7 @@ const (
_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
)
@@ -213,3 +218,13 @@ const (
_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
)

View File

@@ -1,6 +1,7 @@
package vfs
import (
"context"
"errors"
"io"
"io/fs"
@@ -11,6 +12,7 @@ import (
"syscall"
"github.com/ncruces/go-sqlite3/util/osutil"
"github.com/tetratelabs/wazero/api"
)
type vfsOS struct{}
@@ -125,6 +127,7 @@ func (vfsOS) OpenParams(name string, flags OpenFlag, params url.Values) (File, O
type vfsFile struct {
*os.File
shm vfsShm
lock LockLevel
readOnly bool
keepWAL bool
@@ -141,6 +144,11 @@ var (
_ FilePowersafeOverwrite = &vfsFile{}
)
func (f *vfsFile) Close() error {
f.shm.Close()
return f.File.Close()
}
func (f *vfsFile) Sync(flags SyncFlag) error {
dataonly := (flags & SYNC_DATAONLY) != 0
fullsync := (flags & 0x0f) == SYNC_FULL
@@ -173,10 +181,14 @@ func (*vfsFile) SectorSize() int {
}
func (f *vfsFile) DeviceCharacteristics() DeviceCharacteristic {
if f.psow {
return IOCAP_POWERSAFE_OVERWRITE
var res DeviceCharacteristic
if osBatchAtomic(f.File) {
res |= IOCAP_BATCH_ATOMIC
}
return 0
if f.psow {
res |= IOCAP_POWERSAFE_OVERWRITE
}
return res
}
func (f *vfsFile) SizeHint(size int64) error {
@@ -203,3 +215,9 @@ 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 }
type fileShm interface {
shmMap(context.Context, api.Module, int32, int32, bool) (uint32, error)
shmLock(int32, int32, _ShmFlag) error
shmUnmap(bool)
}

View File

@@ -1,7 +1,17 @@
//go:build (linux || darwin || windows || freebsd || openbsd || netbsd || dragonfly || illumos) && !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)
@@ -107,11 +117,9 @@ func (f *vfsFile) Unlock(lock LockLevel) error {
switch lock {
case LOCK_SHARED:
if rc := osDowngradeLock(f.File, f.lock); rc != _OK {
return rc
}
rc := osDowngradeLock(f.File, f.lock)
f.lock = LOCK_SHARED
return nil
return rc
case LOCK_NONE:
rc := osReleaseLock(f.File, f.lock)

23
vfs/lock_other.go Normal file
View File

@@ -0,0 +1,23 @@
//go:build !(linux || darwin || windows || freebsd || openbsd || netbsd || dragonfly || illumos) || 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
}

View File

@@ -33,7 +33,7 @@ func Test_vfsLock(t *testing.T) {
pOutput = 32
)
mod := wazerotest.NewModule(wazerotest.NewMemory(wazerotest.PageSize))
ctx := util.NewContext(context.TODO())
ctx := util.NewContext(context.TODO(), false)
vfsFileRegister(ctx, mod, pFile1, &vfsFile{File: file1})
vfsFileRegister(ctx, mod, pFile2, &vfsFile{File: file2})

View File

@@ -21,7 +21,7 @@ func Test_wal(t *testing.T) {
}
defer db.Close()
err = db.Exec(`CREATE TABLE IF NOT EXISTS users (id INT, name VARCHAR(10))`)
err = db.Exec(`CREATE TABLE users (id INT, name VARCHAR(10))`)
if err != nil {
t.Fatal(err)
}

View File

@@ -31,15 +31,3 @@ func osReadLock(file *os.File, _ /*start*/, _ /*len*/ int64, _ /*timeout*/ time.
func osWriteLock(file *os.File, _ /*start*/, _ /*len*/ int64, _ /*timeout*/ time.Duration) _ErrorCode {
return osLock(file, unix.LOCK_EX|unix.LOCK_NB, _IOERR_LOCK)
}
func osCheckLock(file *os.File, start, len int64) (bool, _ErrorCode) {
lock := unix.Flock_t{
Type: unix.F_RDLCK,
Start: start,
Len: len,
}
if unix.FcntlFlock(file.Fd(), unix.F_GETLK, &lock) != nil {
return false, _IOERR_CHECKRESERVEDLOCK
}
return lock.Type != unix.F_UNLCK, _OK
}

View File

@@ -14,7 +14,6 @@ const (
// https://github.com/apple/darwin-xnu/blob/main/bsd/sys/fcntl.h
_F_OFD_SETLK = 90
_F_OFD_SETLKW = 91
_F_OFD_GETLK = 92
_F_OFD_SETLKWTIMEOUT = 93
)
@@ -94,15 +93,3 @@ func osReadLock(file *os.File, start, len int64, timeout time.Duration) _ErrorCo
func osWriteLock(file *os.File, start, len int64, timeout time.Duration) _ErrorCode {
return osLock(file, unix.F_WRLCK, start, len, timeout, _IOERR_LOCK)
}
func osCheckLock(file *os.File, start, len int64) (bool, _ErrorCode) {
lock := unix.Flock_t{
Type: unix.F_RDLCK,
Start: start,
Len: len,
}
if unix.FcntlFlock(file.Fd(), _F_OFD_GETLK, &lock) != nil {
return false, _IOERR_CHECKRESERVEDLOCK
}
return lock.Type != unix.F_UNLCK, _OK
}

34
vfs/os_f2fs_linux.go Normal file
View File

@@ -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)
}

View File

@@ -8,15 +8,9 @@ import (
"golang.org/x/sys/unix"
)
func osSync(file *os.File, _ /*fullsync*/, dataonly bool) error {
if dataonly {
_, _, err := unix.Syscall(unix.SYS_FDATASYNC, file.Fd(), 0, 0)
if err != 0 {
return err
}
return nil
}
return file.Sync()
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 {

View File

@@ -1,41 +0,0 @@
//go:build !(linux || darwin || windows || freebsd || openbsd || netbsd || dragonfly || illumos) || sqlite3_nosys
package vfs
import "os"
// SupportsFileLocking is false on platforms that do not support file locking.
// To open a database file in one such platform,
// 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 osGetSharedLock(_ *os.File) _ErrorCode {
return _IOERR_RDLOCK
}
func osGetReservedLock(_ *os.File) _ErrorCode {
return _IOERR_LOCK
}
func osGetPendingLock(_ *os.File, _ bool) _ErrorCode {
return _IOERR_LOCK
}
func osGetExclusiveLock(_ *os.File, _ bool) _ErrorCode {
return _IOERR_LOCK
}
func osDowngradeLock(_ *os.File, _ LockLevel) _ErrorCode {
return _IOERR_RDLOCK
}
func osReleaseLock(_ *os.File, _ LockLevel) _ErrorCode {
return _IOERR_UNLOCK
}
func osCheckReservedLock(_ *os.File) (bool, _ErrorCode) {
return false, _IOERR_CHECKRESERVEDLOCK
}

View File

@@ -3,6 +3,7 @@
package vfs
import (
"math/rand"
"os"
"time"
@@ -40,10 +41,10 @@ func osLock(file *os.File, typ int16, start, len int64, timeout time.Duration, d
if errno, _ := err.(unix.Errno); errno != unix.EAGAIN {
break
}
if timeout <= 0 || timeout < time.Since(before) {
if timeout < time.Since(before) {
break
}
osSleep(time.Millisecond)
osSleep(time.Duration(rand.Int63n(int64(time.Millisecond))))
}
}
return osLockErrorCode(err, def)
@@ -56,15 +57,3 @@ func osReadLock(file *os.File, start, len int64, timeout time.Duration) _ErrorCo
func osWriteLock(file *os.File, start, len int64, timeout time.Duration) _ErrorCode {
return osLock(file, unix.F_WRLCK, start, len, timeout, _IOERR_LOCK)
}
func osCheckLock(file *os.File, start, len int64) (bool, _ErrorCode) {
lock := unix.Flock_t{
Type: unix.F_RDLCK,
Start: start,
Len: len,
}
if unix.FcntlFlock(file.Fd(), unix.F_OFD_GETLK, &lock) != nil {
return false, _IOERR_CHECKRESERVEDLOCK
}
return lock.Type != unix.F_UNLCK, _OK
}

9
vfs/os_std_atomic.go Normal file
View File

@@ -0,0 +1,9 @@
//go:build !linux || !(amd64 || arm64 || riscv64) || sqlite3_nosys
package vfs
import "os"
func osBatchAtomic(*os.File) bool {
return false
}

View File

@@ -9,17 +9,9 @@ import (
"golang.org/x/sys/unix"
)
// SupportsFileLocking is false on platforms that do not support file locking.
// To open a database file in one such platform,
// 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
func osGetSharedLock(file *os.File) _ErrorCode {
// Test the PENDING lock before acquiring a new SHARED lock.
if pending, _ := osCheckLock(file, _PENDING_BYTE, 1); pending {
if lock, _ := osGetLock(file, _PENDING_BYTE, 1); lock == unix.F_WRLCK {
return _BUSY
}
// Acquire the SHARED lock.
@@ -72,7 +64,20 @@ func osReleaseLock(file *os.File, _ LockLevel) _ErrorCode {
func osCheckReservedLock(file *os.File) (bool, _ErrorCode) {
// Test the RESERVED lock.
return osCheckLock(file, _RESERVED_BYTE, 1)
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 {

View File

@@ -3,24 +3,16 @@
package vfs
import (
"math/rand"
"os"
"time"
"golang.org/x/sys/windows"
)
// SupportsFileLocking is false on platforms that do not support file locking.
// To open a database file in one such platform,
// 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
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)
@@ -104,7 +96,15 @@ func osReleaseLock(file *os.File, state LockLevel) _ErrorCode {
func osCheckReservedLock(file *os.File) (bool, _ErrorCode) {
// Test the RESERVED lock.
return osCheckLock(file, _RESERVED_BYTE, 1)
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 {
@@ -133,10 +133,10 @@ func osLock(file *os.File, flags, start, len uint32, timeout time.Duration, def
if errno, _ := err.(windows.Errno); errno != windows.ERROR_LOCK_VIOLATION {
break
}
if timeout <= 0 || timeout < time.Since(before) {
if timeout < time.Since(before) {
break
}
osSleep(time.Millisecond)
osSleep(time.Duration(rand.Int63n(int64(time.Millisecond))))
}
}
return osLockErrorCode(err, def)
@@ -155,17 +155,6 @@ func osWriteLock(file *os.File, start, len uint32, timeout time.Duration) _Error
return osLock(file, windows.LOCKFILE_EXCLUSIVE_LOCK, start, len, timeout, _IOERR_LOCK)
}
func osCheckLock(file *os.File, start, len uint32) (bool, _ErrorCode) {
rc := osLock(file, 0, start, len, 0, _IOERR_CHECKRESERVEDLOCK)
if rc == _BUSY {
return true, _OK
}
if rc == _OK {
osUnlock(file, start, len)
}
return false, rc
}
func osLockErrorCode(err error, def _ErrorCode) _ErrorCode {
if err == nil {
return _OK

151
vfs/shm.go Normal file
View File

@@ -0,0 +1,151 @@
//go:build (darwin || linux || illumos) && (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 true on platforms that support shared memory.
// To enable shared memory support on those platforms,
// you need to set the appropriate [wazero.RuntimeConfig];
// otherwise, [EXCLUSIVE locking mode] is activated automatically
// to use [WAL without shared-memory].
//
// [WAL without shared-memory]: https://sqlite.org/wal.html#noshm
// [EXCLUSIVE locking mode]: https://sqlite.org/pragma.html#pragma_locking_mode
const SupportsSharedMemory = true
type vfsShm struct {
*os.File
regions []*util.MappedRegion
}
const (
_SHM_NLOCK = 8
_SHM_BASE = 120
_SHM_DMS = _SHM_BASE + _SHM_NLOCK
)
func (f *vfsFile) shmMap(ctx context.Context, mod api.Module, id, size int32, extend bool) (uint32, error) {
// Ensure size is a multiple of the OS page size.
if int(size)&(unix.Getpagesize()-1) != 0 {
return 0, _IOERR_SHMMAP
}
if f.shm.File == nil {
var flag int
if f.readOnly {
flag = unix.O_RDONLY
} else {
flag = unix.O_RDWR
}
s, err := os.OpenFile(f.Name()+"-shm",
flag|unix.O_CREAT|unix.O_NOFOLLOW, 0666)
if err != nil {
return 0, _CANTOPEN
}
f.shm.File = s
}
// Dead man's switch.
if lock, rc := osGetLock(f.shm.File, _SHM_DMS, 1); rc != _OK {
return 0, _IOERR_LOCK
} else if lock == unix.F_WRLCK {
return 0, _BUSY
} else if lock == unix.F_UNLCK {
if f.readOnly {
return 0, _READONLY_CANTINIT
}
if rc := osWriteLock(f.shm.File, _SHM_DMS, 1, 0); rc != _OK {
return 0, rc
}
if err := f.shm.Truncate(0); err != nil {
return 0, _IOERR_SHMOPEN
}
}
if rc := osReadLock(f.shm.File, _SHM_DMS, 1, 0); rc != _OK {
return 0, rc
}
// Check if file is big enough.
s, err := f.shm.Seek(0, io.SeekEnd)
if err != nil {
return 0, _IOERR_SHMSIZE
}
if n := (int64(id) + 1) * int64(size); n > s {
if !extend {
return 0, nil
}
err := osAllocate(f.shm.File, n)
if err != nil {
return 0, _IOERR_SHMSIZE
}
}
var prot int
if f.readOnly {
prot = unix.PROT_READ
} else {
prot = unix.PROT_READ | unix.PROT_WRITE
}
r, err := util.MapRegion(ctx, mod, f.shm.File, int64(id)*int64(size), size, prot)
if err != nil {
return 0, err
}
f.shm.regions = append(f.shm.regions, r)
return r.Ptr, nil
}
func (f *vfsFile) 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(f.shm.File, _SHM_BASE+int64(offset), int64(n))
case flags&_SHM_SHARED != 0:
return osReadLock(f.shm.File, _SHM_BASE+int64(offset), int64(n), 0)
case flags&_SHM_EXCLUSIVE != 0:
return osWriteLock(f.shm.File, _SHM_BASE+int64(offset), int64(n), 0)
default:
panic(util.AssertErr())
}
}
func (f *vfsFile) shmUnmap(delete bool) {
// Unmap regions.
for _, r := range f.shm.regions {
r.Unmap()
}
clear(f.shm.regions)
f.shm.regions = f.shm.regions[:0]
// Close the file.
if delete && f.shm.File != nil {
os.Remove(f.shm.Name())
}
f.shm.Close()
f.shm.File = nil
}

17
vfs/shm_other.go Normal file
View File

@@ -0,0 +1,17 @@
//go:build !(darwin || linux || illumos) || !(amd64 || arm64 || riscv64) || sqlite3_flock || sqlite3_noshm || sqlite3_nosys
package vfs
// SupportsSharedMemory is true on platforms that support shared memory.
// To enable shared memory support on those platforms,
// you need to set the appropriate [wazero.RuntimeConfig];
// otherwise, [EXCLUSIVE locking mode] is activated automatically
// to use [WAL without shared-memory].
//
// [WAL without shared-memory]: https://sqlite.org/wal.html#noshm
// [EXCLUSIVE locking mode]: https://sqlite.org/pragma.html#pragma_locking_mode
const SupportsSharedMemory = false
type vfsShm struct{}
func (vfsShm) Close() error { return nil }

View File

@@ -19,7 +19,7 @@ import (
"github.com/ncruces/go-sqlite3/internal/util"
"github.com/ncruces/go-sqlite3/vfs"
_ "github.com/ncruces/go-sqlite3/vfs/memdb"
"github.com/ncruces/go-sqlite3/vfs/memdb"
"github.com/tetratelabs/wazero"
"github.com/tetratelabs/wazero/api"
"github.com/tetratelabs/wazero/imports/wasi_snapshot_preview1"
@@ -92,7 +92,7 @@ func system(ctx context.Context, mod api.Module, ptr uint32) uint32 {
cfg := config(ctx).WithArgs(args...)
go func() {
ctx := util.NewContext(ctx)
ctx := util.NewContext(ctx, true)
mod, _ := rt.InstantiateModule(ctx, module, cfg)
mod.Close(ctx)
}()
@@ -100,7 +100,7 @@ func system(ctx context.Context, mod api.Module, ptr uint32) uint32 {
}
func Test_config01(t *testing.T) {
ctx := util.NewContext(newContext(t))
ctx := util.NewContext(newContext(t), false)
name := filepath.Join(t.TempDir(), "test.db")
cfg := config(ctx).WithArgs("mptest", name, "config01.test")
mod, err := rt.InstantiateModule(ctx, module, cfg)
@@ -118,7 +118,7 @@ func Test_config02(t *testing.T) {
t.Skip("skipping in CI")
}
ctx := util.NewContext(newContext(t))
ctx := util.NewContext(newContext(t), false)
name := filepath.Join(t.TempDir(), "test.db")
cfg := config(ctx).WithArgs("mptest", name, "config02.test")
mod, err := rt.InstantiateModule(ctx, module, cfg)
@@ -136,7 +136,7 @@ func Test_crash01(t *testing.T) {
t.Skip("skipping in CI")
}
ctx := util.NewContext(newContext(t))
ctx := util.NewContext(newContext(t), false)
name := filepath.Join(t.TempDir(), "test.db")
cfg := config(ctx).WithArgs("mptest", name, "crash01.test")
mod, err := rt.InstantiateModule(ctx, module, cfg)
@@ -151,7 +151,7 @@ func Test_multiwrite01(t *testing.T) {
t.Skip("skipping in short mode")
}
ctx := util.NewContext(newContext(t))
ctx := util.NewContext(newContext(t), false)
name := filepath.Join(t.TempDir(), "test.db")
cfg := config(ctx).WithArgs("mptest", name, "multiwrite01.test")
mod, err := rt.InstantiateModule(ctx, module, cfg)
@@ -162,7 +162,8 @@ func Test_multiwrite01(t *testing.T) {
}
func Test_config01_memory(t *testing.T) {
ctx := util.NewContext(newContext(t))
memdb.Delete("test.db")
ctx := util.NewContext(newContext(t), false)
cfg := config(ctx).WithArgs("mptest", "/test.db", "config01.test",
"--vfs", "memdb")
mod, err := rt.InstantiateModule(ctx, module, cfg)
@@ -177,7 +178,8 @@ func Test_multiwrite01_memory(t *testing.T) {
t.Skip("skipping in short mode")
}
ctx := util.NewContext(newContext(t))
memdb.Delete("test.db")
ctx := util.NewContext(newContext(t), false)
cfg := config(ctx).WithArgs("mptest", "/test.db", "multiwrite01.test",
"--vfs", "memdb")
mod, err := rt.InstantiateModule(ctx, module, cfg)
@@ -187,6 +189,47 @@ func Test_multiwrite01_memory(t *testing.T) {
mod.Close(ctx)
}
func Test_crash01_wal(t *testing.T) {
if testing.Short() {
t.Skip("skipping in short mode")
}
if os.Getenv("CI") != "" {
t.Skip("skipping in CI")
}
if !vfs.SupportsSharedMemory {
t.Skip("skipping without shared memory")
}
ctx := util.NewContext(newContext(t), true)
name := filepath.Join(t.TempDir(), "test.db")
cfg := config(ctx).WithArgs("mptest", name, "crash01.test",
"--journalmode", "wal")
mod, err := rt.InstantiateModule(ctx, module, cfg)
if err != nil {
t.Fatal(err)
}
mod.Close(ctx)
}
func Test_multiwrite01_wal(t *testing.T) {
if testing.Short() {
t.Skip("skipping in short mode")
}
if !vfs.SupportsSharedMemory {
t.Skip("skipping without shared memory")
}
ctx := util.NewContext(newContext(t), true)
name := filepath.Join(t.TempDir(), "test.db")
cfg := config(ctx).WithArgs("mptest", name, "multiwrite01.test",
"--journalmode", "wal")
mod, err := rt.InstantiateModule(ctx, module, cfg)
if err != nil {
t.Fatal(err)
}
mod.Close(ctx)
}
func newContext(t *testing.T) context.Context {
return context.WithValue(context.Background(), logger{}, &testWriter{T: t})
}

View File

@@ -5,7 +5,7 @@ cd -P -- "$(dirname -- "$0")"
ROOT=../../../../
BINARYEN="$ROOT/tools/binaryen-version_117/bin"
WASI_SDK="$ROOT/tools/wasi-sdk-21.0/bin"
WASI_SDK="$ROOT/tools/wasi-sdk-22.0/bin"
"$WASI_SDK/clang" --target=wasm32-wasi -std=c17 -flto -g0 -O2 \
-o mptest.wasm main.c \
@@ -21,7 +21,8 @@ WASI_SDK="$ROOT/tools/wasi-sdk-21.0/bin"
-DSQLITE_DEFAULT_LOCKING_MODE=0 \
-DHAVE_USLEEP -DSQLITE_NO_SYNC \
-DSQLITE_THREADSAFE=0 -DSQLITE_OMIT_LOAD_EXTENSION \
-D_WASI_EMULATED_GETPID -lwasi-emulated-getpid
-D_WASI_EMULATED_GETPID -lwasi-emulated-getpid \
-Wl,--export=aligned_alloc
"$BINARYEN/wasm-opt" -g --strip --strip-producers -c -O3 \
mptest.wasm -o mptest.tmp \

View File

@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:b5f4778b49a6b99a1be11db5cb07e3c5b52f91a932a97e33c895f52c6f54bf57
size 469149
oid sha256:7bf8ef8a52def8dec9b4af62b21cbea22a6cd2a702770aa9c75f445691f1abd5
size 469700

View File

@@ -84,7 +84,7 @@ func initFlags() {
func Benchmark_speedtest1(b *testing.B) {
output.Reset()
ctx := util.NewContext(context.Background())
ctx := util.NewContext(context.Background(), true)
name := filepath.Join(b.TempDir(), "test.db")
args := append(options, "--size", strconv.Itoa(b.N), name)
cfg := wazero.NewModuleConfig().

View File

@@ -5,7 +5,7 @@ cd -P -- "$(dirname -- "$0")"
ROOT=../../../../
BINARYEN="$ROOT/tools/binaryen-version_117/bin"
WASI_SDK="$ROOT/tools/wasi-sdk-21.0/bin"
WASI_SDK="$ROOT/tools/wasi-sdk-22.0/bin"
"$WASI_SDK/clang" --target=wasm32-wasi -std=c17 -flto -g0 -O2 \
-o speedtest1.wasm main.c \
@@ -16,7 +16,8 @@ WASI_SDK="$ROOT/tools/wasi-sdk-21.0/bin"
-fno-stack-protector -fno-stack-clash-protection \
-Wl,--stack-first \
-Wl,--import-undefined \
-D_HAVE_SQLITE_CONFIG_H
-D_HAVE_SQLITE_CONFIG_H \
-Wl,--export=aligned_alloc
"$BINARYEN/wasm-opt" -g --strip --strip-producers -c -O3 \
speedtest1.wasm -o speedtest1.tmp \

View File

@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:06882576fdea2c8e164dd2d97dce394dc825dca3cee30c9efc9601b84de92865
size 483191
oid sha256:7f669eede3ba9c9a104d37c3a90ecec26086a87aea54c1f650df202923d75cb9
size 483426

View File

@@ -6,6 +6,7 @@ import (
"io"
"net/url"
"reflect"
"sync"
"time"
"github.com/ncruces/go-sqlite3/internal/util"
@@ -27,7 +28,7 @@ func ExportHostFunctions(env wazero.HostModuleBuilder) wazero.HostModuleBuilder
util.ExportFuncIIIII(env, "go_full_pathname", vfsFullPathname)
util.ExportFuncIIII(env, "go_delete", vfsDelete)
util.ExportFuncIIIII(env, "go_access", vfsAccess)
util.ExportFuncIIIIII(env, "go_open", vfsOpen)
util.ExportFuncIIIIIII(env, "go_open", vfsOpen)
util.ExportFuncII(env, "go_close", vfsClose)
util.ExportFuncIIIIJ(env, "go_read", vfsRead)
util.ExportFuncIIIIJ(env, "go_write", vfsWrite)
@@ -40,6 +41,10 @@ func ExportHostFunctions(env wazero.HostModuleBuilder) wazero.HostModuleBuilder
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
}
@@ -72,13 +77,13 @@ func vfsLocaltime(ctx context.Context, mod api.Module, pTm uint32, t int64) _Err
return _OK
}
func vfsRandomness(ctx context.Context, mod api.Module, pVfs, nByte, zByte uint32) uint32 {
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, nMicro uint32) _ErrorCode {
func vfsSleep(ctx context.Context, mod api.Module, pVfs uint32, nMicro int32) _ErrorCode {
osSleep(time.Duration(nMicro) * time.Microsecond)
return _OK
}
@@ -90,19 +95,16 @@ func vfsCurrentTime64(ctx context.Context, mod api.Module, pVfs, piNow uint32) _
return _OK
}
func vfsFullPathname(ctx context.Context, mod api.Module, pVfs, zRelative, nFull, zFull uint32) _ErrorCode {
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)
size := uint64(len(path) + 1)
if size > uint64(nFull) {
if len(path) >= int(nFull) {
return _CANTOPEN_FULLPATH
}
mem := util.View(mod, zFull, size)
mem[len(path)] = 0
copy(mem, path)
util.WriteString(mod, zFull, path)
return vfsErrorCode(err, _CANTOPEN_FULLPATH)
}
@@ -129,7 +131,7 @@ func vfsAccess(ctx context.Context, mod api.Module, pVfs, zPath uint32, flags Ac
return vfsErrorCode(err, _IOERR_ACCESS)
}
func vfsOpen(ctx context.Context, mod api.Module, pVfs, zPath, pFile uint32, flags OpenFlag, pOutFlags uint32) _ErrorCode {
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
@@ -165,6 +167,9 @@ func vfsOpen(ctx context.Context, mod api.Module, pVfs, zPath, pFile uint32, fla
if pOutFlags != 0 {
util.WriteUint32(mod, pOutFlags, uint32(flags))
}
if pOutVFS != 0 && util.CanMap(ctx) {
util.WriteUint32(mod, pOutVFS, 1)
}
vfsFileRegister(ctx, mod, pFile, file)
return _OK
}
@@ -177,7 +182,7 @@ func vfsClose(ctx context.Context, mod api.Module, pFile uint32) _ErrorCode {
return _OK
}
func vfsRead(ctx context.Context, mod api.Module, pFile, zBuf, iAmt uint32, iOfst int64) _ErrorCode {
func vfsRead(ctx context.Context, mod api.Module, pFile, zBuf uint32, iAmt int32, iOfst int64) _ErrorCode {
file := vfsFileGet(ctx, mod, pFile)
buf := util.View(mod, zBuf, uint64(iAmt))
@@ -192,7 +197,7 @@ func vfsRead(ctx context.Context, mod api.Module, pFile, zBuf, iAmt uint32, iOfs
return _IOERR_SHORT_READ
}
func vfsWrite(ctx context.Context, mod api.Module, pFile, zBuf, iAmt uint32, iOfst int64) _ErrorCode {
func vfsWrite(ctx context.Context, mod api.Module, pFile, zBuf uint32, iAmt int32, iOfst int64) _ErrorCode {
file := vfsFileGet(ctx, mod, pFile)
buf := util.View(mod, zBuf, uint64(iAmt))
@@ -348,6 +353,35 @@ func vfsDeviceCharacteristics(ctx context.Context, mod api.Module, pFile uint32)
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 {
file := vfsFileGet(ctx, mod, pFile).(fileShm)
p, err := file.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 {
file := vfsFileGet(ctx, mod, pFile).(fileShm)
err := file.shmLock(offset, n, flags)
return vfsErrorCode(err, _IOERR_SHMLOCK)
}
func vfsShmUnmap(ctx context.Context, mod api.Module, pFile, bDelete uint32) _ErrorCode {
file := vfsFileGet(ctx, mod, pFile).(fileShm)
file.shmUnmap(bDelete != 0)
return _OK
}
func vfsURIParameters(ctx context.Context, mod api.Module, zPath uint32, flags OpenFlag) url.Values {
if flags&OPEN_URI == 0 {
return nil

View File

@@ -209,10 +209,10 @@ func Test_vfsAccess(t *testing.T) {
func Test_vfsFile(t *testing.T) {
mod := wazerotest.NewModule(wazerotest.NewMemory(wazerotest.PageSize))
ctx := util.NewContext(context.TODO())
ctx := util.NewContext(context.TODO(), false)
// Open a temporary file.
rc := vfsOpen(ctx, mod, 0, 0, 4, OPEN_CREATE|OPEN_EXCLUSIVE|OPEN_READWRITE|OPEN_DELETEONCLOSE, 0)
rc := vfsOpen(ctx, mod, 0, 0, 4, OPEN_CREATE|OPEN_EXCLUSIVE|OPEN_READWRITE|OPEN_DELETEONCLOSE, 0, 0)
if rc != _OK {
t.Fatal("returned", rc)
}
@@ -225,7 +225,7 @@ func Test_vfsFile(t *testing.T) {
// Write stuff.
text := "Hello world!"
util.WriteString(mod, 16, text)
rc = vfsWrite(ctx, mod, 4, 16, uint32(len(text)), 0)
rc = vfsWrite(ctx, mod, 4, 16, int32(len(text)), 0)
if rc != _OK {
t.Fatal("returned", rc)
}
@@ -240,7 +240,7 @@ func Test_vfsFile(t *testing.T) {
}
// Partial read at offset.
rc = vfsRead(ctx, mod, 4, 16, uint32(len(text)), 4)
rc = vfsRead(ctx, mod, 4, 16, int32(len(text)), 4)
if rc != _IOERR_SHORT_READ {
t.Fatal("returned", rc)
}
@@ -281,10 +281,10 @@ func Test_vfsFile(t *testing.T) {
func Test_vfsFile_psow(t *testing.T) {
mod := wazerotest.NewModule(wazerotest.NewMemory(wazerotest.PageSize))
ctx := util.NewContext(context.TODO())
ctx := util.NewContext(context.TODO(), false)
// Open a temporary file.
rc := vfsOpen(ctx, mod, 0, 0, 4, OPEN_CREATE|OPEN_EXCLUSIVE|OPEN_READWRITE|OPEN_DELETEONCLOSE, 0)
rc := vfsOpen(ctx, mod, 0, 0, 4, OPEN_CREATE|OPEN_EXCLUSIVE|OPEN_READWRITE|OPEN_DELETEONCLOSE, 0, 0)
if rc != _OK {
t.Fatal("returned", rc)
}

15
vtab.go
View File

@@ -206,9 +206,6 @@ type VTabTxn interface {
Rollback() error
}
// Deprecated: renamed for consistency with [Conn.TxnState].
type VTabTx = VTabTxn
// A VTabSavepointer allows a virtual table to implement
// nested transactions.
//
@@ -492,7 +489,7 @@ func vtabRenameCallback(ctx context.Context, mod api.Module, pVTab, zNew uint32)
return vtabError(ctx, mod, pVTab, _VTAB_ERROR, err)
}
func vtabFindFuncCallback(ctx context.Context, mod api.Module, pVTab, nArg, zName, pxFunc uint32) uint32 {
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 {
@@ -542,19 +539,19 @@ func vtabRollbackCallback(ctx context.Context, mod api.Module, pVTab uint32) uin
return vtabError(ctx, mod, pVTab, _VTAB_ERROR, err)
}
func vtabSavepointCallback(ctx context.Context, mod api.Module, pVTab, id uint32) uint32 {
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, id uint32) uint32 {
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, id uint32) uint32 {
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)
@@ -576,7 +573,7 @@ func cursorCloseCallback(ctx context.Context, mod api.Module, pCur uint32) uint3
return vtabError(ctx, mod, 0, _VTAB_ERROR, err)
}
func cursorFilterCallback(ctx context.Context, mod api.Module, pCur, idxNum, idxStr, nArg, pArg uint32) uint32 {
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)
@@ -603,7 +600,7 @@ func cursorNextCallback(ctx context.Context, mod api.Module, pCur uint32) uint32
return vtabError(ctx, mod, pCur, _CURSOR_ERROR, err)
}
func cursorColumnCallback(ctx context.Context, mod api.Module, pCur, pCtx, n uint32) uint32 {
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))