mirror of
https://github.com/ncruces/go-sqlite3.git
synced 2026-01-13 22:49:12 +00:00
Compare commits
49 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a36d72c2dc | ||
|
|
e7f5604199 | ||
|
|
e50083912c | ||
|
|
d4764fb2fa | ||
|
|
42df71f3ff | ||
|
|
91e969c06b | ||
|
|
8ab0ddf53e | ||
|
|
74d22ded0a | ||
|
|
d962611796 | ||
|
|
7df3814c34 | ||
|
|
c5f49b835a | ||
|
|
0e55451a0b | ||
|
|
ea9a58ab19 | ||
|
|
0b46e74ea6 | ||
|
|
8dca850bee | ||
|
|
5b78823416 | ||
|
|
1764a571da | ||
|
|
9837310af7 | ||
|
|
ec8961a621 | ||
|
|
8c37aa2d97 | ||
|
|
ca93c498e7 | ||
|
|
15e9087fa8 | ||
|
|
7028e3a5b9 | ||
|
|
03bb20de6e | ||
|
|
20a51a344e | ||
|
|
2dbcc480f7 | ||
|
|
0f0716c438 | ||
|
|
0286e50e25 | ||
|
|
8ac10eb8b4 | ||
|
|
0ff41bb966 | ||
|
|
ba9caf0405 | ||
|
|
2c167dd116 | ||
|
|
ce0da893b4 | ||
|
|
9bbbab77f6 | ||
|
|
bab2d26652 | ||
|
|
3132b272de | ||
|
|
8f9a6ca4c1 | ||
|
|
99b097de3b | ||
|
|
4a956e80a2 | ||
|
|
5f4ff03f6f | ||
|
|
5890049488 | ||
|
|
5e73c5d714 | ||
|
|
6d92aa16ef | ||
|
|
191d1337e7 | ||
|
|
b65e894849 | ||
|
|
0b040d3f09 | ||
|
|
1db4366226 | ||
|
|
9e1cbfb5bb | ||
|
|
7f2d70a0f3 |
2
.github/workflows/build-test.sh
vendored
2
.github/workflows/build-test.sh
vendored
@@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
echo 'set -eu' > test.sh
|
||||
echo 'set -eux' > test.sh
|
||||
|
||||
for p in $(go list ./...); do
|
||||
dir=".${p#github.com/ncruces/go-sqlite3}"
|
||||
|
||||
4
.github/workflows/libc.yml
vendored
4
.github/workflows/libc.yml
vendored
@@ -10,11 +10,11 @@ jobs:
|
||||
test:
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-24.04, ubuntu-24.04-arm, macos-13, macos-15]
|
||||
os: [ubuntu-24.04, ubuntu-24.04-arm, macos-15, macos-15-intel]
|
||||
runs-on: ${{ matrix.os }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/setup-go@v6
|
||||
with: { go-version: stable }
|
||||
|
||||
|
||||
2
.github/workflows/repro.yml
vendored
2
.github/workflows/repro.yml
vendored
@@ -17,7 +17,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- uses: ilammy/msvc-dev-cmd@v1
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- name: Build
|
||||
shell: bash
|
||||
|
||||
94
.github/workflows/test.yml
vendored
94
.github/workflows/test.yml
vendored
@@ -30,7 +30,7 @@ jobs:
|
||||
contents: write
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/setup-go@v6
|
||||
with: { go-version: stable }
|
||||
|
||||
@@ -51,30 +51,30 @@ jobs:
|
||||
run: go vet ./...
|
||||
|
||||
- name: Build
|
||||
run: go build -v ./...
|
||||
run: go build ./...
|
||||
|
||||
- name: Test
|
||||
run: go test -v ./... -bench . -benchtime=1x
|
||||
run: go test ./... -bench . -benchtime=1x
|
||||
|
||||
- name: Test BSD locks
|
||||
run: go test -v -tags sqlite3_flock ./...
|
||||
run: go test -tags sqlite3_flock ./...
|
||||
if: matrix.os != 'windows-latest'
|
||||
|
||||
- name: Test dot locks
|
||||
run: go test -v -tags sqlite3_dotlk ./...
|
||||
run: go test -tags sqlite3_dotlk ./...
|
||||
if: matrix.os != 'windows-latest'
|
||||
|
||||
- name: Test modules
|
||||
shell: bash
|
||||
run: |
|
||||
go work init .
|
||||
go work use -r embed gormlite
|
||||
go test -v ./embed/bcw2/...
|
||||
go work use -r embed/bcw2 gormlite
|
||||
go test ./embed/bcw2 ./gormlite
|
||||
|
||||
- name: Test GORM
|
||||
shell: bash
|
||||
run: gormlite/test.sh
|
||||
if: matrix.os != 'windows-latest'
|
||||
if: matrix.os == 'ubuntu-latest'
|
||||
|
||||
- name: Collect coverage
|
||||
run: |
|
||||
@@ -93,44 +93,45 @@ jobs:
|
||||
github.event_name == 'push' &&
|
||||
matrix.os == 'ubuntu-latest'
|
||||
|
||||
test-bsd:
|
||||
test-cross:
|
||||
strategy:
|
||||
matrix:
|
||||
os:
|
||||
- name: freebsd
|
||||
version: '14.3'
|
||||
flags: '-test.v'
|
||||
version: '15.0'
|
||||
- name: netbsd
|
||||
version: '10.1'
|
||||
flags: '-test.v'
|
||||
- name: illumos
|
||||
action: omnios
|
||||
version: 'r151056'
|
||||
- name: openbsd
|
||||
version: '7.8'
|
||||
tflags: '-test.short'
|
||||
- name: freebsd
|
||||
arch: arm64
|
||||
version: '14.3'
|
||||
flags: '-test.v -test.short'
|
||||
version: '15.0'
|
||||
tflags: '-test.short'
|
||||
- name: netbsd
|
||||
arch: arm64
|
||||
version: '10.1'
|
||||
flags: '-test.v -test.short'
|
||||
- name: openbsd
|
||||
version: '7.7'
|
||||
flags: '-test.v -test.short'
|
||||
tflags: '-test.short'
|
||||
runs-on: ubuntu-latest
|
||||
needs: test
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- name: Build
|
||||
env:
|
||||
GOOS: ${{ matrix.os.name }}
|
||||
GOARCH: ${{ matrix.os.arch }}
|
||||
TESTFLAGS: ${{ matrix.os.flags }}
|
||||
TESTFLAGS: ${{ matrix.os.tflags }}
|
||||
run: .github/workflows/build-test.sh
|
||||
|
||||
- name: Test
|
||||
uses: cross-platform-actions/action@v0.29.0
|
||||
uses: cross-platform-actions/action@v0.32.0
|
||||
with:
|
||||
operating_system: ${{ matrix.os.name }}
|
||||
operating_system: ${{ matrix.os.action || matrix.os.name }}
|
||||
architecture: ${{ matrix.os.arch }}
|
||||
version: ${{ matrix.os.version }}
|
||||
shell: bash
|
||||
@@ -143,19 +144,16 @@ jobs:
|
||||
os:
|
||||
- name: dragonfly
|
||||
action: 'vmactions/dragonflybsd-vm@v1'
|
||||
tflags: '-test.v'
|
||||
- name: illumos
|
||||
action: 'vmactions/omnios-vm@v1'
|
||||
tflags: '-test.v'
|
||||
action: 'vmactions/openindiana-vm@v0'
|
||||
- name: solaris
|
||||
action: 'vmactions/solaris-vm@v1'
|
||||
bflags: '-tags sqlite3_dotlk'
|
||||
tflags: '-test.v'
|
||||
runs-on: ubuntu-latest
|
||||
needs: test
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- name: Build
|
||||
env:
|
||||
@@ -174,7 +172,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- uses: bytecodealliance/actions/wasmtime/setup@v1
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/setup-go@v6
|
||||
with: { go-version: stable }
|
||||
|
||||
@@ -187,7 +185,7 @@ jobs:
|
||||
GOARCH: wasm
|
||||
GOWASIRUNTIME: wasmtime
|
||||
GOWASIRUNTIMEARGS: '--env CI=true'
|
||||
run: go test -v -short -tags sqlite3_dotlk -skip Example ./...
|
||||
run: go test -short -tags sqlite3_dotlk -skip Example ./...
|
||||
|
||||
test-qemu:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -195,42 +193,60 @@ jobs:
|
||||
|
||||
steps:
|
||||
- uses: docker/setup-qemu-action@v3
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/setup-go@v6
|
||||
with: { go-version: stable }
|
||||
|
||||
- name: Test 386 (32-bit)
|
||||
run: GOARCH=386 go test -v -short ./...
|
||||
run: GOARCH=386 go test -short ./...
|
||||
|
||||
- name: Test riscv64 (interpreter)
|
||||
run: GOARCH=riscv64 go test -v -short ./...
|
||||
run: GOARCH=riscv64 go test -short ./...
|
||||
|
||||
- name: Test ppc64le (interpreter)
|
||||
run: GOARCH=ppc64le go test -v -short ./...
|
||||
run: GOARCH=ppc64le go test -short ./...
|
||||
|
||||
- name: Test loong64 (interpreter)
|
||||
run: GOARCH=loong64 go test -short ./...
|
||||
|
||||
- name: Test s390x (big-endian)
|
||||
run: GOARCH=s390x go test -v -short -tags sqlite3_dotlk ./...
|
||||
run: GOARCH=s390x go test -short -tags sqlite3_dotlk ./...
|
||||
|
||||
test-linuxarm:
|
||||
runs-on: ubuntu-24.04-arm
|
||||
needs: test
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/setup-go@v6
|
||||
with: { go-version: stable }
|
||||
|
||||
- name: Test
|
||||
run: go test -v ./...
|
||||
run: go test ./...
|
||||
|
||||
- name: Test arm (32-bit)
|
||||
run: GOARCH=arm GOARM=7 go test -short ./...
|
||||
|
||||
test-macintel:
|
||||
runs-on: macos-13
|
||||
runs-on: macos-15-intel
|
||||
needs: test
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/setup-go@v6
|
||||
with: { go-version: stable }
|
||||
|
||||
- name: Test
|
||||
run: go test -v ./...
|
||||
run: go test ./...
|
||||
|
||||
test-winarm:
|
||||
runs-on: windows-11-arm
|
||||
needs: test
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/setup-go@v6
|
||||
with: { go-version: stable }
|
||||
|
||||
- name: Test
|
||||
run: go test ./...
|
||||
@@ -84,14 +84,14 @@ It also benefits greatly from [SQLite's](https://sqlite.org/testing.html) and
|
||||
thorough testing.
|
||||
|
||||
Every commit is tested on:
|
||||
* Linux: amd64, arm64, 386, riscv64, ppc64le, s390x
|
||||
* Linux: amd64, arm64, 386, arm, riscv64, ppc64le, loong64, s390x
|
||||
* macOS: amd64, arm64
|
||||
* Windows: amd64
|
||||
* Windows: amd64, arm64
|
||||
* BSD:
|
||||
* FreeBSD: amd64, arm64
|
||||
* OpenBSD: amd64
|
||||
* NetBSD: amd64, arm64
|
||||
* DragonFly BSD: amd64
|
||||
* OpenBSD: amd64
|
||||
* illumos: amd64
|
||||
* Solaris: amd64
|
||||
|
||||
|
||||
12
config.go
12
config.go
@@ -157,16 +157,20 @@ func (c *Conn) FileControl(schema string, op FcntlOpcode, arg ...any) (any, erro
|
||||
stk_t(op), stk_t(ptr)))
|
||||
ret = util.Read32[vfs.LockLevel](c.mod, ptr)
|
||||
|
||||
case FCNTL_VFS_POINTER:
|
||||
case FCNTL_VFSNAME, FCNTL_VFS_POINTER:
|
||||
rc = res_t(c.call("sqlite3_file_control",
|
||||
stk_t(c.handle), stk_t(schemaPtr),
|
||||
stk_t(op), stk_t(ptr)))
|
||||
stk_t(FCNTL_VFS_POINTER), stk_t(ptr)))
|
||||
if rc == _OK {
|
||||
const zNameOffset = 16
|
||||
ptr = util.Read32[ptr_t](c.mod, ptr)
|
||||
ptr = util.Read32[ptr_t](c.mod, ptr+zNameOffset)
|
||||
name := util.ReadString(c.mod, ptr, _MAX_NAME)
|
||||
ret = vfs.Find(name)
|
||||
if op == FCNTL_VFS_POINTER {
|
||||
ret = vfs.Find(name)
|
||||
} else {
|
||||
ret = name
|
||||
}
|
||||
}
|
||||
|
||||
case FCNTL_FILE_POINTER, FCNTL_JOURNAL_POINTER:
|
||||
@@ -265,7 +269,7 @@ func traceCallback(ctx context.Context, mod api.Module, evt TraceEvent, pDB, pAr
|
||||
}
|
||||
}
|
||||
if arg1 != nil {
|
||||
_, rc = errorCode(c.trace(evt, arg1, arg2), ERROR)
|
||||
_ = c.trace(evt, arg1, arg2)
|
||||
}
|
||||
}
|
||||
return rc
|
||||
|
||||
12
conn.go
12
conn.go
@@ -420,21 +420,21 @@ func busyCallback(ctx context.Context, mod api.Module, pDB ptr_t, count int32) (
|
||||
// Status retrieves runtime status information about a database connection.
|
||||
//
|
||||
// https://sqlite.org/c3ref/db_status.html
|
||||
func (c *Conn) Status(op DBStatus, reset bool) (current, highwater int, err error) {
|
||||
func (c *Conn) Status(op DBStatus, reset bool) (current, highwater int64, err error) {
|
||||
defer c.arena.mark()()
|
||||
hiPtr := c.arena.new(intlen)
|
||||
curPtr := c.arena.new(intlen)
|
||||
hiPtr := c.arena.new(8)
|
||||
curPtr := c.arena.new(8)
|
||||
|
||||
var i int32
|
||||
if reset {
|
||||
i = 1
|
||||
}
|
||||
|
||||
rc := res_t(c.call("sqlite3_db_status", stk_t(c.handle),
|
||||
rc := res_t(c.call("sqlite3_db_status64", stk_t(c.handle),
|
||||
stk_t(op), stk_t(curPtr), stk_t(hiPtr), stk_t(i)))
|
||||
if err = c.error(rc); err == nil {
|
||||
current = int(util.Read32[int32](c.mod, curPtr))
|
||||
highwater = int(util.Read32[int32](c.mod, hiPtr))
|
||||
current = util.Read64[int64](c.mod, curPtr)
|
||||
highwater = util.Read64[int64](c.mod, hiPtr)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
24
const.go
24
const.go
@@ -73,6 +73,9 @@ const (
|
||||
ERROR_MISSING_COLLSEQ ExtendedErrorCode = xErrorCode(ERROR) | (1 << 8)
|
||||
ERROR_RETRY ExtendedErrorCode = xErrorCode(ERROR) | (2 << 8)
|
||||
ERROR_SNAPSHOT ExtendedErrorCode = xErrorCode(ERROR) | (3 << 8)
|
||||
ERROR_RESERVESIZE ExtendedErrorCode = xErrorCode(ERROR) | (4 << 8)
|
||||
ERROR_KEY ExtendedErrorCode = xErrorCode(ERROR) | (5 << 8)
|
||||
ERROR_UNABLE ExtendedErrorCode = xErrorCode(ERROR) | (6 << 8)
|
||||
IOERR_READ ExtendedErrorCode = xErrorCode(IOERR) | (1 << 8)
|
||||
IOERR_SHORT_READ ExtendedErrorCode = xErrorCode(IOERR) | (2 << 8)
|
||||
IOERR_WRITE ExtendedErrorCode = xErrorCode(IOERR) | (3 << 8)
|
||||
@@ -107,6 +110,8 @@ const (
|
||||
IOERR_DATA ExtendedErrorCode = xErrorCode(IOERR) | (32 << 8)
|
||||
IOERR_CORRUPTFS ExtendedErrorCode = xErrorCode(IOERR) | (33 << 8)
|
||||
IOERR_IN_PAGE ExtendedErrorCode = xErrorCode(IOERR) | (34 << 8)
|
||||
IOERR_BADKEY ExtendedErrorCode = xErrorCode(IOERR) | (35 << 8)
|
||||
IOERR_CODEC ExtendedErrorCode = xErrorCode(IOERR) | (36 << 8)
|
||||
LOCKED_SHAREDCACHE ExtendedErrorCode = xErrorCode(LOCKED) | (1 << 8)
|
||||
LOCKED_VTAB ExtendedErrorCode = xErrorCode(LOCKED) | (2 << 8)
|
||||
BUSY_RECOVERY ExtendedErrorCode = xErrorCode(BUSY) | (1 << 8)
|
||||
@@ -168,7 +173,7 @@ const (
|
||||
|
||||
// PrepareFlag is a flag that can be passed to [Conn.PrepareFlags].
|
||||
//
|
||||
// https://sqlite.org/c3ref/c_prepare_normalize.html
|
||||
// https://sqlite.org/c3ref/c_prepare_dont_log.html
|
||||
type PrepareFlag uint32
|
||||
|
||||
const (
|
||||
@@ -176,6 +181,7 @@ const (
|
||||
PREPARE_NORMALIZE PrepareFlag = 0x02
|
||||
PREPARE_NO_VTAB PrepareFlag = 0x04
|
||||
PREPARE_DONT_LOG PrepareFlag = 0x10
|
||||
PREPARE_FROM_DDL PrepareFlag = 0x20
|
||||
)
|
||||
|
||||
// FunctionFlag is a flag that can be passed to
|
||||
@@ -229,7 +235,8 @@ const (
|
||||
DBSTATUS_DEFERRED_FKS DBStatus = 10
|
||||
DBSTATUS_CACHE_USED_SHARED DBStatus = 11
|
||||
DBSTATUS_CACHE_SPILL DBStatus = 12
|
||||
// DBSTATUS_MAX DBStatus = 12
|
||||
DBSTATUS_TEMPBUF_SPILL DBStatus = 13
|
||||
// DBSTATUS_MAX DBStatus = 13
|
||||
)
|
||||
|
||||
// DBConfig are the available database connection configuration options.
|
||||
@@ -274,6 +281,7 @@ const (
|
||||
FCNTL_CHUNK_SIZE FcntlOpcode = 6
|
||||
FCNTL_FILE_POINTER FcntlOpcode = 7
|
||||
FCNTL_PERSIST_WAL FcntlOpcode = 10
|
||||
FCNTL_VFSNAME FcntlOpcode = 12
|
||||
FCNTL_POWERSAFE_OVERWRITE FcntlOpcode = 13
|
||||
FCNTL_VFS_POINTER FcntlOpcode = 27
|
||||
FCNTL_JOURNAL_POINTER FcntlOpcode = 28
|
||||
@@ -301,6 +309,7 @@ const (
|
||||
LIMIT_VARIABLE_NUMBER LimitCategory = 9
|
||||
LIMIT_TRIGGER_DEPTH LimitCategory = 10
|
||||
LIMIT_WORKER_THREADS LimitCategory = 11
|
||||
LIMIT_PARSER_DEPTH LimitCategory = 12
|
||||
)
|
||||
|
||||
// AuthorizerActionCode are the integer action codes
|
||||
@@ -362,13 +371,14 @@ const (
|
||||
// CheckpointMode are all the checkpoint mode values.
|
||||
//
|
||||
// https://sqlite.org/c3ref/c_checkpoint_full.html
|
||||
type CheckpointMode uint32
|
||||
type CheckpointMode int32
|
||||
|
||||
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 */
|
||||
CHECKPOINT_NOOP CheckpointMode = -1 /* Do no work at all */
|
||||
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].
|
||||
|
||||
@@ -198,14 +198,14 @@ func (ctx Context) ResultError(err error) {
|
||||
return
|
||||
}
|
||||
|
||||
msg, code := errorCode(err, _OK)
|
||||
msg, code := errorCode(err, ERROR)
|
||||
if msg != "" {
|
||||
defer ctx.c.arena.mark()()
|
||||
ptr := ctx.c.arena.string(msg)
|
||||
ctx.c.call("sqlite3_result_error",
|
||||
stk_t(ctx.handle), stk_t(ptr), stk_t(len(msg)))
|
||||
}
|
||||
if code != _OK {
|
||||
if code != res_t(ERROR) {
|
||||
ctx.c.call("sqlite3_result_error_code",
|
||||
stk_t(ctx.handle), stk_t(code))
|
||||
}
|
||||
|
||||
@@ -263,10 +263,8 @@ func (n *connector) Connect(ctx context.Context) (ret driver.Conn, err error) {
|
||||
return nil, err
|
||||
}
|
||||
defer s.Close()
|
||||
if s.Step() && s.ColumnBool(0) {
|
||||
c.readOnly = '1'
|
||||
} else {
|
||||
c.readOnly = '0'
|
||||
if s.Step() {
|
||||
c.readOnly = s.ColumnBool(0)
|
||||
}
|
||||
err = s.Close()
|
||||
if err != nil {
|
||||
@@ -322,7 +320,7 @@ type conn struct {
|
||||
txReset string
|
||||
tmRead sqlite3.TimeFormat
|
||||
tmWrite sqlite3.TimeFormat
|
||||
readOnly byte
|
||||
readOnly bool
|
||||
}
|
||||
|
||||
var (
|
||||
@@ -358,9 +356,9 @@ func (c *conn) BeginTx(ctx context.Context, opts driver.TxOptions) (driver.Tx, e
|
||||
|
||||
c.txReset = ``
|
||||
txBegin := `BEGIN ` + txLock
|
||||
if opts.ReadOnly {
|
||||
if opts.ReadOnly && !c.readOnly {
|
||||
txBegin += ` ; PRAGMA query_only=on`
|
||||
c.txReset = `; PRAGMA query_only=` + string(c.readOnly)
|
||||
c.txReset = `; PRAGMA query_only=off`
|
||||
}
|
||||
|
||||
if old := c.Conn.SetInterrupt(ctx); old != ctx {
|
||||
@@ -655,14 +653,12 @@ type rows struct {
|
||||
names []string
|
||||
types []string
|
||||
scans []scantype
|
||||
dest []driver.Value
|
||||
}
|
||||
|
||||
var (
|
||||
// Ensure these interfaces are implemented:
|
||||
_ driver.RowsColumnTypeDatabaseTypeName = &rows{}
|
||||
_ driver.RowsColumnTypeNullable = &rows{}
|
||||
// _ driver.RowsColumnScanner = &rows{}
|
||||
)
|
||||
|
||||
func (r *rows) Close() error {
|
||||
@@ -702,17 +698,23 @@ func (r *rows) loadColumnMetadata() {
|
||||
types := make([]string, count)
|
||||
scans := make([]scantype, count)
|
||||
for i := range types {
|
||||
var notnull bool
|
||||
if col := r.Stmt.ColumnOriginName(i); col != "" {
|
||||
types[i], _, notnull, _, _, _ = c.TableColumnMetadata(
|
||||
var declType string
|
||||
var notNull, autoInc bool
|
||||
if column := r.Stmt.ColumnOriginName(i); column != "" {
|
||||
declType, _, notNull, _, autoInc, _ = c.TableColumnMetadata(
|
||||
r.Stmt.ColumnDatabaseName(i),
|
||||
r.Stmt.ColumnTableName(i),
|
||||
col)
|
||||
types[i] = strings.ToUpper(types[i])
|
||||
scans[i] = scanFromDecl(types[i])
|
||||
if notnull {
|
||||
scans[i] |= _NOT_NULL
|
||||
}
|
||||
column)
|
||||
} else {
|
||||
declType = r.Stmt.ColumnDeclType(i)
|
||||
}
|
||||
if declType != "" {
|
||||
declType = strings.ToUpper(declType)
|
||||
scans[i] = scanFromDecl(declType)
|
||||
types[i] = declType
|
||||
}
|
||||
if notNull || autoInc {
|
||||
scans[i] |= _NOT_NULL
|
||||
}
|
||||
}
|
||||
r.types = types
|
||||
@@ -782,7 +784,6 @@ func (r *rows) ColumnTypeScanType(index int) (typ reflect.Type) {
|
||||
}
|
||||
|
||||
func (r *rows) Next(dest []driver.Value) error {
|
||||
r.dest = nil
|
||||
c := r.Stmt.Conn()
|
||||
if old := c.SetInterrupt(r.ctx); old != r.ctx {
|
||||
defer c.SetInterrupt(old)
|
||||
@@ -832,33 +833,5 @@ func (r *rows) Next(dest []driver.Value) error {
|
||||
}
|
||||
}
|
||||
}
|
||||
r.dest = dest
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *rows) ScanColumn(dest any, index int) (err error) {
|
||||
// notest // Go 1.26
|
||||
var tm *time.Time
|
||||
var ok *bool
|
||||
switch d := dest.(type) {
|
||||
case *time.Time:
|
||||
tm = d
|
||||
case *sql.NullTime:
|
||||
tm = &d.Time
|
||||
ok = &d.Valid
|
||||
case *sql.Null[time.Time]:
|
||||
tm = &d.V
|
||||
ok = &d.Valid
|
||||
default:
|
||||
return driver.ErrSkip
|
||||
}
|
||||
value := r.dest[index]
|
||||
*tm, err = r.tmRead.Decode(value)
|
||||
if ok != nil {
|
||||
*ok = err == nil
|
||||
if value == nil {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -8,7 +8,6 @@ import (
|
||||
"math"
|
||||
"net/url"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@@ -44,8 +43,8 @@ func Test_Open_dir(t *testing.T) {
|
||||
if err == nil {
|
||||
t.Fatal("want error")
|
||||
}
|
||||
if !errors.Is(err, sqlite3.CANTOPEN) {
|
||||
t.Errorf("got %v, want sqlite3.CANTOPEN", err)
|
||||
if !errors.Is(err, sqlite3.CANTOPEN_ISDIR) {
|
||||
t.Errorf("got %v, want sqlite3.CANTOPEN_ISDIR", err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -521,39 +520,6 @@ func Test_ColumnType_ScanType(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func Test_rows_ScanColumn(t *testing.T) {
|
||||
t.Parallel()
|
||||
dsn := memdb.TestDB(t)
|
||||
|
||||
db, err := Open(dsn)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
var tm time.Time
|
||||
err = db.QueryRow(`SELECT NULL`).Scan(&tm)
|
||||
if err == nil {
|
||||
t.Error("want error")
|
||||
}
|
||||
// Go 1.26
|
||||
err = db.QueryRow(`SELECT datetime()`).Scan(&tm)
|
||||
if err != nil && !strings.HasPrefix(err.Error(), "sql: Scan error") {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
var nt sql.NullTime
|
||||
err = db.QueryRow(`SELECT NULL`).Scan(&nt)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
// Go 1.26
|
||||
err = db.QueryRow(`SELECT datetime()`).Scan(&nt)
|
||||
if err != nil && !strings.HasPrefix(err.Error(), "sql: Scan error") {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func Benchmark_loop(b *testing.B) {
|
||||
db, err := Open(":memory:")
|
||||
if err != nil {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Embeddable Wasm build of SQLite
|
||||
|
||||
This folder includes an embeddable Wasm build of SQLite 3.50.4 for use with
|
||||
This folder includes an embeddable Wasm build of SQLite 3.51.1 for use with
|
||||
[`github.com/ncruces/go-sqlite3`](https://pkg.go.dev/github.com/ncruces/go-sqlite3).
|
||||
|
||||
The following optional features are compiled in:
|
||||
|
||||
Binary file not shown.
@@ -53,7 +53,7 @@ func Test_bcw2(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if version != "3.51.0" {
|
||||
if version != "3.52.0" {
|
||||
t.Error(version)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,14 +15,15 @@ cp "$ROOT"/sqlite3/*.[ch] build/
|
||||
cp "$ROOT"/sqlite3/*.patch build/
|
||||
cd sqlite/
|
||||
|
||||
# https://sqlite.org/src/info/ba2174bdca7d1d1a
|
||||
curl -#L https://github.com/sqlite/sqlite/archive/b46738f.tar.gz | tar xz --strip-components=1
|
||||
# curl -#L https://sqlite.org/src/tarball/sqlite.tar.gz?r=ba2174bdca | tar xz --strip-components=1
|
||||
# https://sqlite.org/src/info/f273f6b8245c5dca
|
||||
curl -#L https://github.com/sqlite/sqlite/archive/7c126d7.tar.gz | tar xz --strip-components=1
|
||||
# curl -#L https://sqlite.org/src/tarball/sqlite.tar.gz?r=f273f6b824 | tar xz --strip-components=1
|
||||
|
||||
if [[ "$OSTYPE" == "msys" || "$OSTYPE" == "cygwin" ]]; then
|
||||
MSYS_NO_PATHCONV=1 nmake /f makefile.msc sqlite3.c "OPTS=-DSQLITE_ENABLE_UPDATE_DELETE_LIMIT -DSQLITE_ENABLE_ORDERED_SET_AGGREGATES"
|
||||
else
|
||||
sh configure --enable-update-limit
|
||||
make verify-source
|
||||
OPTS=-DSQLITE_ENABLE_ORDERED_SET_AGGREGATES make sqlite3.c
|
||||
fi
|
||||
cd ~-
|
||||
@@ -58,13 +59,15 @@ cd ~-
|
||||
-Wl,--initial-memory=327680 \
|
||||
-D_HAVE_SQLITE_CONFIG_H \
|
||||
-DSQLITE_ENABLE_UPDATE_DELETE_LIMIT \
|
||||
-DSQLITE_ENABLE_ORDERED_SET_AGGREGATES \
|
||||
-DSQLITE_EXPERIMENTAL_PRAGMA_20251114 \
|
||||
-DSQLITE_CUSTOM_INCLUDE=sqlite_opt.h \
|
||||
$(awk '{print "-Wl,--export="$0}' ../exports.txt)
|
||||
|
||||
"$BINARYEN/wasm-ctor-eval" -g -c _initialize bcw2.wasm -o bcw2.tmp
|
||||
"$BINARYEN/wasm-opt" -g bcw2.tmp -o bcw2.wasm \
|
||||
--low-memory-unused --gufa --generate-global-effects --converge -O3 \
|
||||
--gufa --generate-global-effects --low-memory-unused --converge -O3 \
|
||||
--enable-mutable-globals --enable-nontrapping-float-to-int \
|
||||
--enable-simd --enable-bulk-memory --enable-sign-ext \
|
||||
--enable-reference-types --enable-multivalue \
|
||||
--strip --strip-producers
|
||||
--strip --strip-producers
|
||||
|
||||
@@ -2,11 +2,11 @@ module github.com/ncruces/go-sqlite3/embed/bcw2
|
||||
|
||||
go 1.24.0
|
||||
|
||||
require github.com/ncruces/go-sqlite3 v0.29.0
|
||||
require github.com/ncruces/go-sqlite3 v0.30.3
|
||||
|
||||
require (
|
||||
github.com/ncruces/julianday v1.0.0 // indirect
|
||||
github.com/ncruces/sort v0.1.5 // indirect
|
||||
github.com/tetratelabs/wazero v1.9.0 // indirect
|
||||
golang.org/x/sys v0.36.0 // indirect
|
||||
github.com/ncruces/sort v0.1.6 // indirect
|
||||
github.com/tetratelabs/wazero v1.11.0 // indirect
|
||||
golang.org/x/sys v0.39.0 // indirect
|
||||
)
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
github.com/ncruces/go-sqlite3 v0.29.0 h1:1tsLiagCoqZEfcHDeKsNSv5jvrY/Iu393pAnw2wLNJU=
|
||||
github.com/ncruces/go-sqlite3 v0.29.0/go.mod h1:r1hSvYKPNJ+OlUA1O3r8o9LAawzPAlqeZiIdxTBBBJ0=
|
||||
github.com/ncruces/go-sqlite3 v0.30.3 h1:X/CgWW9GzmIAkEPrifhKqf0cC15DuOVxAJaHFTTAURQ=
|
||||
github.com/ncruces/go-sqlite3 v0.30.3/go.mod h1:AxKu9sRxkludimFocbktlY6LiYSkxiI5gTA8r+os/Nw=
|
||||
github.com/ncruces/julianday v1.0.0 h1:fH0OKwa7NWvniGQtxdJRxAgkBMolni2BjDHaWTxqt7M=
|
||||
github.com/ncruces/julianday v1.0.0/go.mod h1:Dusn2KvZrrovOMJuOt0TNXL6tB7U2E8kvza5fFc9G7g=
|
||||
github.com/ncruces/sort v0.1.5 h1:fiFWXXAqKI8QckPf/6hu/bGFwcEPrirIOFaJqWujs4k=
|
||||
github.com/ncruces/sort v0.1.5/go.mod h1:obJToO4rYr6VWP0Uw5FYymgYGt3Br4RXcs/JdKaXAPk=
|
||||
github.com/tetratelabs/wazero v1.9.0 h1:IcZ56OuxrtaEz8UYNRHBrUa9bYeX9oVY93KspZZBf/I=
|
||||
github.com/tetratelabs/wazero v1.9.0/go.mod h1:TSbcXCfFP0L2FGkRPxHphadXPjo1T6W+CseNNY7EkjM=
|
||||
golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k=
|
||||
golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk=
|
||||
golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4=
|
||||
github.com/ncruces/sort v0.1.6 h1:TrsJfGRH1AoWoaeB4/+gCohot9+cA6u/INaH5agIhNk=
|
||||
github.com/ncruces/sort v0.1.6/go.mod h1:obJToO4rYr6VWP0Uw5FYymgYGt3Br4RXcs/JdKaXAPk=
|
||||
github.com/tetratelabs/wazero v1.11.0 h1:+gKemEuKCTevU4d7ZTzlsvgd1uaToIDtlQlmNbwqYhA=
|
||||
github.com/tetratelabs/wazero v1.11.0/go.mod h1:eV28rsN8Q+xwjogd7f4/Pp4xFxO7uOGbLcD/LzB1wiU=
|
||||
golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk=
|
||||
golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM=
|
||||
golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM=
|
||||
|
||||
@@ -23,13 +23,14 @@ trap 'rm -f sqlite3.tmp' EXIT
|
||||
-Wl,--import-undefined \
|
||||
-Wl,--initial-memory=327680 \
|
||||
-D_HAVE_SQLITE_CONFIG_H \
|
||||
-DSQLITE_EXPERIMENTAL_PRAGMA_20251114 \
|
||||
-DSQLITE_CUSTOM_INCLUDE=sqlite_opt.h \
|
||||
$(awk '{print "-Wl,--export="$0}' exports.txt)
|
||||
|
||||
"$BINARYEN/wasm-ctor-eval" -g -c _initialize sqlite3.wasm -o sqlite3.tmp
|
||||
"$BINARYEN/wasm-opt" -g sqlite3.tmp -o sqlite3.wasm \
|
||||
--low-memory-unused --gufa --generate-global-effects --converge -O3 \
|
||||
--gufa --generate-global-effects --low-memory-unused --converge -O3 \
|
||||
--enable-mutable-globals --enable-nontrapping-float-to-int \
|
||||
--enable-simd --enable-bulk-memory --enable-sign-ext \
|
||||
--enable-reference-types --enable-multivalue \
|
||||
--strip --strip-producers
|
||||
--strip --strip-producers
|
||||
|
||||
@@ -59,7 +59,7 @@ sqlite3_db_filename
|
||||
sqlite3_db_name
|
||||
sqlite3_db_readonly
|
||||
sqlite3_db_release_memory
|
||||
sqlite3_db_status
|
||||
sqlite3_db_status64
|
||||
sqlite3_declare_vtab
|
||||
sqlite3_errcode
|
||||
sqlite3_errmsg
|
||||
|
||||
@@ -17,5 +17,7 @@ import (
|
||||
var binary string
|
||||
|
||||
func init() {
|
||||
sqlite3.Binary = unsafe.Slice(unsafe.StringData(binary), len(binary))
|
||||
if sqlite3.Binary == nil {
|
||||
sqlite3.Binary = unsafe.Slice(unsafe.StringData(binary), len(binary))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ func Test_init(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if version != "3.50.4" {
|
||||
if version != "3.51.1" {
|
||||
t.Error(version)
|
||||
}
|
||||
}
|
||||
|
||||
Binary file not shown.
36
error.go
36
error.go
@@ -11,6 +11,7 @@ import (
|
||||
//
|
||||
// https://sqlite.org/c3ref/errcode.html
|
||||
type Error struct {
|
||||
sys error
|
||||
msg string
|
||||
sql string
|
||||
code res_t
|
||||
@@ -33,16 +34,28 @@ func (e *Error) ExtendedCode() ExtendedErrorCode {
|
||||
// Error implements the error interface.
|
||||
func (e *Error) Error() string {
|
||||
var b strings.Builder
|
||||
b.WriteString(util.ErrorCodeString(uint32(e.code)))
|
||||
b.WriteString(util.ErrorCodeString(e.code))
|
||||
|
||||
if e.msg != "" {
|
||||
b.WriteString(": ")
|
||||
b.WriteString(e.msg)
|
||||
}
|
||||
if e.sys != nil {
|
||||
b.WriteString(": ")
|
||||
b.WriteString(e.sys.Error())
|
||||
}
|
||||
|
||||
return b.String()
|
||||
}
|
||||
|
||||
// Unwrap returns the underlying operating system error
|
||||
// that caused the I/O error or failure to open a file.
|
||||
//
|
||||
// https://sqlite.org/c3ref/system_errno.html
|
||||
func (e *Error) Unwrap() error {
|
||||
return e.sys
|
||||
}
|
||||
|
||||
// Is tests whether this error matches a given [ErrorCode] or [ExtendedErrorCode].
|
||||
//
|
||||
// It makes it possible to do:
|
||||
@@ -90,7 +103,16 @@ func (e *Error) SQL() string {
|
||||
|
||||
// Error implements the error interface.
|
||||
func (e ErrorCode) Error() string {
|
||||
return util.ErrorCodeString(uint32(e))
|
||||
return util.ErrorCodeString(e)
|
||||
}
|
||||
|
||||
// As converts this error to an [ExtendedErrorCode].
|
||||
func (e ErrorCode) As(err any) bool {
|
||||
c, ok := err.(*xErrorCode)
|
||||
if ok {
|
||||
*c = xErrorCode(e)
|
||||
}
|
||||
return ok
|
||||
}
|
||||
|
||||
// Temporary returns true for [BUSY] errors.
|
||||
@@ -105,7 +127,7 @@ func (e ErrorCode) ExtendedCode() ExtendedErrorCode {
|
||||
|
||||
// Error implements the error interface.
|
||||
func (e ExtendedErrorCode) Error() string {
|
||||
return util.ErrorCodeString(uint32(e))
|
||||
return util.ErrorCodeString(e)
|
||||
}
|
||||
|
||||
// Is tests whether this error matches a given [ErrorCode].
|
||||
@@ -150,14 +172,10 @@ func errorCode(err error, def ErrorCode) (msg string, code res_t) {
|
||||
return code.msg, res_t(code.code)
|
||||
}
|
||||
|
||||
var ecode ErrorCode
|
||||
var xcode xErrorCode
|
||||
switch {
|
||||
case errors.As(err, &xcode):
|
||||
if errors.As(err, &xcode) {
|
||||
code = res_t(xcode)
|
||||
case errors.As(err, &ecode):
|
||||
code = res_t(ecode)
|
||||
default:
|
||||
} else {
|
||||
code = res_t(def)
|
||||
}
|
||||
return err.Error(), code
|
||||
|
||||
@@ -59,7 +59,8 @@ func (c *cursor) Next() error {
|
||||
}
|
||||
|
||||
func (c *cursor) RowID() (int64, error) {
|
||||
return int64(c.rowID), nil
|
||||
// One-based RowID for consistency with carray and other tables.
|
||||
return int64(c.rowID) + 1, nil
|
||||
}
|
||||
|
||||
func (c *cursor) Column(ctx sqlite3.Context, n int) error {
|
||||
|
||||
@@ -16,6 +16,7 @@ import (
|
||||
|
||||
"github.com/ncruces/go-sqlite3"
|
||||
"github.com/ncruces/go-sqlite3/internal/util"
|
||||
"github.com/ncruces/go-sqlite3/util/sql3util"
|
||||
)
|
||||
|
||||
// Register registers the bloom_filter virtual table:
|
||||
@@ -34,6 +35,8 @@ type bloom struct {
|
||||
hashes int
|
||||
}
|
||||
|
||||
const vtab = `CREATE TABLE x(present, word TEXT HIDDEN NOT NULL PRIMARY KEY) WITHOUT ROWID`
|
||||
|
||||
func create(db *sqlite3.Conn, _, schema, table string, arg ...string) (_ *bloom, err error) {
|
||||
b := bloom{
|
||||
db: db,
|
||||
@@ -55,11 +58,9 @@ func create(db *sqlite3.Conn, _, schema, table string, arg ...string) (_ *bloom,
|
||||
}
|
||||
|
||||
if len(arg) > 1 {
|
||||
b.prob, err = strconv.ParseFloat(arg[1], 64)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if b.prob <= 0 || b.prob >= 1 {
|
||||
var ok bool
|
||||
b.prob, ok = sql3util.ParseFloat(arg[1])
|
||||
if !ok || b.prob <= 0 || b.prob >= 1 {
|
||||
return nil, util.ErrorString("bloom: probability must be in the range (0,1)")
|
||||
}
|
||||
} else {
|
||||
@@ -80,8 +81,7 @@ func create(db *sqlite3.Conn, _, schema, table string, arg ...string) (_ *bloom,
|
||||
|
||||
b.bytes = numBytes(nelem, b.prob)
|
||||
|
||||
err = db.DeclareVTab(
|
||||
`CREATE TABLE x(present, word HIDDEN NOT NULL PRIMARY KEY) WITHOUT ROWID`)
|
||||
err = db.DeclareVTab(vtab)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -115,15 +115,15 @@ func connect(db *sqlite3.Conn, _, schema, table string, arg ...string) (_ *bloom
|
||||
storage: table + "_storage",
|
||||
}
|
||||
|
||||
err = db.DeclareVTab(
|
||||
`CREATE TABLE x(present, word HIDDEN NOT NULL PRIMARY KEY) WITHOUT ROWID`)
|
||||
err = db.DeclareVTab(vtab)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
load, _, err := db.Prepare(fmt.Sprintf(
|
||||
load, _, err := db.PrepareFlags(fmt.Sprintf(
|
||||
`SELECT m/8, p, k FROM %s.%s WHERE rowid = 1`,
|
||||
sqlite3.QuoteIdentifier(b.schema), sqlite3.QuoteIdentifier(b.storage)))
|
||||
sqlite3.QuoteIdentifier(b.schema), sqlite3.QuoteIdentifier(b.storage)),
|
||||
sqlite3.PREPARE_DONT_LOG)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -166,9 +166,10 @@ func (t *bloom) ShadowTables() {
|
||||
}
|
||||
|
||||
func (t *bloom) Integrity(schema, table string, flags int) error {
|
||||
load, _, err := t.db.Prepare(fmt.Sprintf(
|
||||
load, _, err := t.db.PrepareFlags(fmt.Sprintf(
|
||||
`SELECT typeof(data), length(data), p, n, m, k FROM %s.%s WHERE rowid = 1`,
|
||||
sqlite3.QuoteIdentifier(t.schema), sqlite3.QuoteIdentifier(t.storage)))
|
||||
sqlite3.QuoteIdentifier(t.schema), sqlite3.QuoteIdentifier(t.storage)),
|
||||
sqlite3.PREPARE_DONT_LOG)
|
||||
if err != nil {
|
||||
return fmt.Errorf("bloom: %v", err) // can't wrap!
|
||||
}
|
||||
|
||||
@@ -56,7 +56,7 @@ func Register(db *sqlite3.Conn) error {
|
||||
done.Add(key)
|
||||
}
|
||||
|
||||
err := db.DeclareVTab(`CREATE TABLE x(id,depth,root HIDDEN,tablename HIDDEN,idcolumn HIDDEN,parentcolumn HIDDEN)`)
|
||||
err := db.DeclareVTab(`CREATE TABLE x(id INT,depth INT,root HIDDEN,tablename TEXT HIDDEN,idcolumn TEXT HIDDEN,parentcolumn TEXT HIDDEN)`)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -154,6 +154,7 @@ func (c *closure) BestIndex(idx *sqlite3.IndexInfo) error {
|
||||
return sqlite3.CONSTRAINT
|
||||
}
|
||||
|
||||
idx.IdxFlags = sqlite3.INDEX_SCAN_HEX
|
||||
idx.EstimatedCost = cost
|
||||
idx.IdxNum = plan
|
||||
return nil
|
||||
@@ -201,7 +202,7 @@ func (c *cursor) Filter(idxNum int, idxStr string, arg ...sqlite3.Value) error {
|
||||
sqlite3.QuoteIdentifier(column),
|
||||
sqlite3.QuoteIdentifier(parent),
|
||||
)
|
||||
stmt, _, err := c.db.Prepare(sql)
|
||||
stmt, _, err := c.db.PrepareFlags(sql, sqlite3.PREPARE_DONT_LOG)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -100,7 +100,7 @@ func RegisterFS(db *sqlite3.Conn, fsys fs.FS) error {
|
||||
}
|
||||
schema = getSchema(header, columns, row)
|
||||
} else {
|
||||
t.typs, err = getColumnAffinities(schema)
|
||||
t.typs, err = getColumnAffinities(db, schema)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -254,19 +254,15 @@ func (c *cursor) Column(ctx sqlite3.Context, col int) error {
|
||||
|
||||
switch typ {
|
||||
case numeric, integer:
|
||||
if strings.TrimLeft(txt, "+-0123456789") == "" {
|
||||
if i, err := strconv.ParseInt(txt, 10, 64); err == nil {
|
||||
ctx.ResultInt64(i)
|
||||
return nil
|
||||
}
|
||||
if i, err := strconv.ParseInt(txt, 10, 64); err == nil {
|
||||
ctx.ResultInt64(i)
|
||||
return nil
|
||||
}
|
||||
fallthrough
|
||||
case real:
|
||||
if strings.TrimLeft(txt, "+-.0123456789Ee") == "" {
|
||||
if f, err := strconv.ParseFloat(txt, 64); err == nil {
|
||||
ctx.ResultFloat(f)
|
||||
return nil
|
||||
}
|
||||
if f, ok := sql3util.ParseFloat(txt); ok {
|
||||
ctx.ResultFloat(f)
|
||||
return nil
|
||||
}
|
||||
fallthrough
|
||||
default:
|
||||
|
||||
@@ -3,6 +3,8 @@ package csv
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/ncruces/go-sqlite3"
|
||||
"github.com/ncruces/go-sqlite3/internal/util"
|
||||
"github.com/ncruces/go-sqlite3/util/sql3util"
|
||||
)
|
||||
|
||||
@@ -16,7 +18,17 @@ const (
|
||||
real affinity = 4
|
||||
)
|
||||
|
||||
func getColumnAffinities(schema string) ([]affinity, error) {
|
||||
func getColumnAffinities(db *sqlite3.Conn, schema string) ([]affinity, error) {
|
||||
stmt, tail, err := db.PrepareFlags(schema,
|
||||
sqlite3.PREPARE_DONT_LOG|sqlite3.PREPARE_NO_VTAB|sqlite3.PREPARE_FROM_DDL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
stmt.Close()
|
||||
if tail != "" {
|
||||
return nil, util.TailErr
|
||||
}
|
||||
|
||||
tab, err := sql3util.ParseTable(schema)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -30,7 +30,7 @@ func RegisterFS(db *sqlite3.Conn, fsys fs.FS) error {
|
||||
db.CreateFunction("readfile", 1, sqlite3.DIRECTONLY, readfile(fsys)),
|
||||
db.CreateFunction("lsmode", 1, sqlite3.DETERMINISTIC, lsmode),
|
||||
sqlite3.CreateModule(db, "fsdir", nil, func(db *sqlite3.Conn, _, _, _ string, _ ...string) (fsdir, error) {
|
||||
err := db.DeclareVTab(`CREATE TABLE x(name,mode,mtime TIMESTAMP,data,path HIDDEN,dir HIDDEN)`)
|
||||
err := db.DeclareVTab(`CREATE TABLE x(name TEXT,mode INT,mtime TIMESTAMP,data BLOB,path HIDDEN,dir HIDDEN)`)
|
||||
if err == nil {
|
||||
err = db.VTabConfig(sqlite3.VTAB_DIRECTONLY)
|
||||
}
|
||||
|
||||
@@ -43,11 +43,14 @@ func declare(db *sqlite3.Conn, _, _, _ string, arg ...string) (ret *table, err e
|
||||
|
||||
// Row key query.
|
||||
t.scan = "SELECT * FROM\n" + arg[0]
|
||||
stmt, _, err := db.Prepare(t.scan)
|
||||
stmt, tail, err := db.PrepareFlags(t.scan, sqlite3.PREPARE_FROM_DDL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer stmt.Close()
|
||||
if tail != "" {
|
||||
return nil, util.TailErr
|
||||
}
|
||||
|
||||
t.keys = make([]string, stmt.ColumnCount())
|
||||
for i := range t.keys {
|
||||
@@ -55,15 +58,20 @@ func declare(db *sqlite3.Conn, _, _, _ string, arg ...string) (ret *table, err e
|
||||
t.keys[i] = name
|
||||
create.WriteString(sep)
|
||||
create.WriteString(name)
|
||||
create.WriteString(" ")
|
||||
create.WriteString(stmt.ColumnDeclType(i))
|
||||
sep = ","
|
||||
}
|
||||
stmt.Close()
|
||||
|
||||
// Column definition query.
|
||||
stmt, _, err = db.Prepare("SELECT * FROM\n" + arg[1])
|
||||
stmt, tail, err = db.PrepareFlags("SELECT * FROM\n"+arg[1], sqlite3.PREPARE_FROM_DDL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if tail != "" {
|
||||
return nil, util.TailErr
|
||||
}
|
||||
|
||||
if stmt.ColumnCount() != 2 {
|
||||
return nil, util.ErrorString("pivot: column definition query expects 2 result columns")
|
||||
@@ -71,17 +79,23 @@ func declare(db *sqlite3.Conn, _, _, _ string, arg ...string) (ret *table, err e
|
||||
for stmt.Step() {
|
||||
name := sqlite3.QuoteIdentifier(stmt.ColumnText(1))
|
||||
t.cols = append(t.cols, stmt.ColumnValue(0).Dup())
|
||||
create.WriteString(",")
|
||||
create.WriteString(sep)
|
||||
create.WriteString(name)
|
||||
create.WriteString(" ")
|
||||
create.WriteString(stmt.ColumnDeclType(1))
|
||||
sep = ","
|
||||
}
|
||||
stmt.Close()
|
||||
|
||||
// Pivot cell query.
|
||||
t.cell = "SELECT * FROM\n" + arg[2]
|
||||
stmt, _, err = db.Prepare(t.cell)
|
||||
stmt, tail, err = db.PrepareFlags(t.cell, sqlite3.PREPARE_FROM_DDL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if tail != "" {
|
||||
return nil, util.TailErr
|
||||
}
|
||||
|
||||
if stmt.ColumnCount() != 1 {
|
||||
return nil, util.ErrorString("pivot: cell query expects 1 result columns")
|
||||
@@ -182,7 +196,9 @@ func (c *cursor) Filter(idxNum int, idxStr string, arg ...sqlite3.Value) error {
|
||||
return err
|
||||
}
|
||||
|
||||
c.scan, _, err = c.table.db.Prepare(idxStr)
|
||||
const prepflags = sqlite3.PREPARE_DONT_LOG | sqlite3.PREPARE_FROM_DDL
|
||||
|
||||
c.scan, _, err = c.table.db.PrepareFlags(idxStr, prepflags)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -194,7 +210,7 @@ func (c *cursor) Filter(idxNum int, idxStr string, arg ...sqlite3.Value) error {
|
||||
}
|
||||
|
||||
if c.cell == nil {
|
||||
c.cell, _, err = c.table.db.Prepare(c.table.cell)
|
||||
c.cell, _, err = c.table.db.PrepareFlags(c.table.cell, prepflags)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -35,10 +35,15 @@ func declare(db *sqlite3.Conn, _, _, _ string, arg ...string) (*table, error) {
|
||||
|
||||
sql := "SELECT * FROM\n" + arg[0]
|
||||
|
||||
stmt, _, err := db.PrepareFlags(sql, sqlite3.PREPARE_PERSISTENT)
|
||||
stmt, tail, err := db.PrepareFlags(sql,
|
||||
sqlite3.PREPARE_PERSISTENT|sqlite3.PREPARE_FROM_DDL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if tail != "" {
|
||||
stmt.Close()
|
||||
return nil, util.TailErr
|
||||
}
|
||||
|
||||
var sep string
|
||||
var str strings.Builder
|
||||
@@ -129,7 +134,8 @@ func (t *table) Open() (_ sqlite3.VTabCursor, err error) {
|
||||
if !t.inuse {
|
||||
t.inuse = true
|
||||
} else {
|
||||
stmt, _, err = t.stmt.Conn().Prepare(t.sql)
|
||||
stmt, _, err = t.stmt.Conn().PrepareFlags(t.sql,
|
||||
sqlite3.PREPARE_DONT_LOG|sqlite3.PREPARE_FROM_DDL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
12
go.mod
12
go.mod
@@ -5,18 +5,18 @@ go 1.24.0
|
||||
require (
|
||||
github.com/ncruces/julianday v1.0.0
|
||||
github.com/ncruces/sort v0.1.6
|
||||
github.com/ncruces/wbt v0.2.0
|
||||
github.com/tetratelabs/wazero v1.9.0
|
||||
golang.org/x/sys v0.36.0
|
||||
github.com/ncruces/wbt v1.0.0
|
||||
github.com/tetratelabs/wazero v1.11.0
|
||||
golang.org/x/sys v0.40.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/dchest/siphash v1.2.3 // ext/bloom
|
||||
github.com/google/uuid v1.6.0 // ext/uuid
|
||||
github.com/psanford/httpreadat v0.1.0 // example
|
||||
golang.org/x/crypto v0.42.0 // vfs/adiantum vfs/xts
|
||||
golang.org/x/sync v0.17.0 // test
|
||||
golang.org/x/text v0.29.0 // ext/unicode
|
||||
golang.org/x/crypto v0.47.0 // vfs/adiantum vfs/xts
|
||||
golang.org/x/sync v0.19.0 // test
|
||||
golang.org/x/text v0.33.0 // ext/unicode
|
||||
lukechampine.com/adiantum v1.1.1 // vfs/adiantum
|
||||
)
|
||||
|
||||
|
||||
24
go.sum
24
go.sum
@@ -6,19 +6,19 @@ github.com/ncruces/julianday v1.0.0 h1:fH0OKwa7NWvniGQtxdJRxAgkBMolni2BjDHaWTxqt
|
||||
github.com/ncruces/julianday v1.0.0/go.mod h1:Dusn2KvZrrovOMJuOt0TNXL6tB7U2E8kvza5fFc9G7g=
|
||||
github.com/ncruces/sort v0.1.6 h1:TrsJfGRH1AoWoaeB4/+gCohot9+cA6u/INaH5agIhNk=
|
||||
github.com/ncruces/sort v0.1.6/go.mod h1:obJToO4rYr6VWP0Uw5FYymgYGt3Br4RXcs/JdKaXAPk=
|
||||
github.com/ncruces/wbt v0.2.0 h1:Q9zlKOBSZc7Yy/R2cGa35g6RKUUE3BjNIW3tfGC4F04=
|
||||
github.com/ncruces/wbt v0.2.0/go.mod h1:DtF92amvMxH69EmBFUSFWRDAlo6hOEfoNQnClxj9C/c=
|
||||
github.com/ncruces/wbt v1.0.0 h1:8iBE7UPjTLUpzu3/FCRjAmuQjWzgxo10RGBgt3ooLSc=
|
||||
github.com/ncruces/wbt v1.0.0/go.mod h1:DtF92amvMxH69EmBFUSFWRDAlo6hOEfoNQnClxj9C/c=
|
||||
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.9.0 h1:IcZ56OuxrtaEz8UYNRHBrUa9bYeX9oVY93KspZZBf/I=
|
||||
github.com/tetratelabs/wazero v1.9.0/go.mod h1:TSbcXCfFP0L2FGkRPxHphadXPjo1T6W+CseNNY7EkjM=
|
||||
golang.org/x/crypto v0.42.0 h1:chiH31gIWm57EkTXpwnqf8qeuMUi0yekh6mT2AvFlqI=
|
||||
golang.org/x/crypto v0.42.0/go.mod h1:4+rDnOTJhQCx2q7/j6rAN5XDw8kPjeaXEUR2eL94ix8=
|
||||
golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug=
|
||||
golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||
golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k=
|
||||
golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk=
|
||||
golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4=
|
||||
github.com/tetratelabs/wazero v1.11.0 h1:+gKemEuKCTevU4d7ZTzlsvgd1uaToIDtlQlmNbwqYhA=
|
||||
github.com/tetratelabs/wazero v1.11.0/go.mod h1:eV28rsN8Q+xwjogd7f4/Pp4xFxO7uOGbLcD/LzB1wiU=
|
||||
golang.org/x/crypto v0.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8=
|
||||
golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A=
|
||||
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
|
||||
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||
golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ=
|
||||
golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE=
|
||||
golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8=
|
||||
lukechampine.com/adiantum v1.1.1 h1:4fp6gTxWCqpEbLy40ExiYDDED3oUNWx5cTqBCtPdZqA=
|
||||
lukechampine.com/adiantum v1.1.1/go.mod h1:LrAYVnTYLnUtE/yMp5bQr0HstAf060YUF8nM0B6+rUw=
|
||||
|
||||
@@ -3,15 +3,15 @@ module github.com/ncruces/go-sqlite3/gormlite
|
||||
go 1.24.0
|
||||
|
||||
require (
|
||||
github.com/ncruces/go-sqlite3 v0.29.0
|
||||
gorm.io/gorm v1.30.5
|
||||
github.com/ncruces/go-sqlite3 v0.30.3
|
||||
gorm.io/gorm v1.31.1
|
||||
)
|
||||
|
||||
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.9.0 // indirect
|
||||
golang.org/x/sys v0.36.0 // indirect
|
||||
golang.org/x/text v0.29.0 // indirect
|
||||
github.com/tetratelabs/wazero v1.11.0 // indirect
|
||||
golang.org/x/sys v0.39.0 // indirect
|
||||
golang.org/x/text v0.32.0 // indirect
|
||||
)
|
||||
|
||||
@@ -2,15 +2,15 @@ github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD
|
||||
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
|
||||
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
|
||||
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
||||
github.com/ncruces/go-sqlite3 v0.29.0 h1:1tsLiagCoqZEfcHDeKsNSv5jvrY/Iu393pAnw2wLNJU=
|
||||
github.com/ncruces/go-sqlite3 v0.29.0/go.mod h1:r1hSvYKPNJ+OlUA1O3r8o9LAawzPAlqeZiIdxTBBBJ0=
|
||||
github.com/ncruces/go-sqlite3 v0.30.3 h1:X/CgWW9GzmIAkEPrifhKqf0cC15DuOVxAJaHFTTAURQ=
|
||||
github.com/ncruces/go-sqlite3 v0.30.3/go.mod h1:AxKu9sRxkludimFocbktlY6LiYSkxiI5gTA8r+os/Nw=
|
||||
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.9.0 h1:IcZ56OuxrtaEz8UYNRHBrUa9bYeX9oVY93KspZZBf/I=
|
||||
github.com/tetratelabs/wazero v1.9.0/go.mod h1:TSbcXCfFP0L2FGkRPxHphadXPjo1T6W+CseNNY7EkjM=
|
||||
golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k=
|
||||
golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk=
|
||||
golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4=
|
||||
gorm.io/gorm v1.30.5 h1:dvEfYwxL+i+xgCNSGGBT1lDjCzfELK8fHZxL3Ee9X0s=
|
||||
gorm.io/gorm v1.30.5/go.mod h1:8Z33v652h4//uMA76KjeDH8mJXPm1QNCYrMeatR0DOE=
|
||||
github.com/tetratelabs/wazero v1.11.0 h1:+gKemEuKCTevU4d7ZTzlsvgd1uaToIDtlQlmNbwqYhA=
|
||||
github.com/tetratelabs/wazero v1.11.0/go.mod h1:eV28rsN8Q+xwjogd7f4/Pp4xFxO7uOGbLcD/LzB1wiU=
|
||||
golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk=
|
||||
golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU=
|
||||
golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY=
|
||||
gorm.io/gorm v1.31.1 h1:7CA8FTFz/gRfgqgpeKIBcervUn3xSyPUmr6B2WXJ7kg=
|
||||
gorm.io/gorm v1.31.1/go.mod h1:XyQVbO2k6YkOis7C2437jSit3SsDK72s7n7rsSHd+Gs=
|
||||
|
||||
@@ -339,7 +339,7 @@ func (m _Migrator) GetIndexes(value interface{}) ([]gorm.Index, error) {
|
||||
indexes := make([]gorm.Index, 0)
|
||||
err := m.RunWithValue(value, func(stmt *gorm.Statement) error {
|
||||
rst := make([]*_Index, 0)
|
||||
if err := m.DB.Debug().Raw("SELECT * FROM PRAGMA_index_list(?)", stmt.Table).Scan(&rst).Error; err != nil { // alias `PRAGMA index_list(?)`
|
||||
if err := m.DB.Raw("SELECT * FROM PRAGMA_index_list(?)", stmt.Table).Scan(&rst).Error; err != nil { // alias `PRAGMA index_list(?)`
|
||||
return err
|
||||
}
|
||||
for _, index := range rst {
|
||||
|
||||
@@ -7,7 +7,7 @@ rm -rf gorm/ tests/
|
||||
go work use -r .
|
||||
go test
|
||||
|
||||
git clone --branch v1.30.5 --filter=blob:none https://github.com/go-gorm/gorm.git
|
||||
git clone --branch v1.31.1 --filter=blob:none https://github.com/go-gorm/gorm.git
|
||||
mv gorm/tests tests
|
||||
rm -rf gorm/
|
||||
|
||||
|
||||
@@ -5,8 +5,9 @@ package alloc
|
||||
import (
|
||||
"math"
|
||||
|
||||
"github.com/tetratelabs/wazero/experimental"
|
||||
"golang.org/x/sys/unix"
|
||||
|
||||
"github.com/tetratelabs/wazero/experimental"
|
||||
)
|
||||
|
||||
func NewMemory(cap, max uint64) experimental.LinearMemory {
|
||||
|
||||
@@ -5,8 +5,9 @@ import (
|
||||
"reflect"
|
||||
"unsafe"
|
||||
|
||||
"github.com/tetratelabs/wazero/experimental"
|
||||
"golang.org/x/sys/windows"
|
||||
|
||||
"github.com/tetratelabs/wazero/experimental"
|
||||
)
|
||||
|
||||
func NewMemory(cap, max uint64) experimental.LinearMemory {
|
||||
|
||||
@@ -39,6 +39,9 @@ const (
|
||||
ERROR_MISSING_COLLSEQ = ERROR | (1 << 8)
|
||||
ERROR_RETRY = ERROR | (2 << 8)
|
||||
ERROR_SNAPSHOT = ERROR | (3 << 8)
|
||||
ERROR_RESERVESIZE = ERROR | (4 << 8)
|
||||
ERROR_KEY = ERROR | (5 << 8)
|
||||
ERROR_UNABLE = ERROR | (6 << 8)
|
||||
IOERR_READ = IOERR | (1 << 8)
|
||||
IOERR_SHORT_READ = IOERR | (2 << 8)
|
||||
IOERR_WRITE = IOERR | (3 << 8)
|
||||
@@ -73,6 +76,8 @@ const (
|
||||
IOERR_DATA = IOERR | (32 << 8)
|
||||
IOERR_CORRUPTFS = IOERR | (33 << 8)
|
||||
IOERR_IN_PAGE = IOERR | (34 << 8)
|
||||
IOERR_BADKEY = IOERR | (35 << 8)
|
||||
IOERR_CODEC = IOERR | (36 << 8)
|
||||
LOCKED_SHAREDCACHE = LOCKED | (1 << 8)
|
||||
LOCKED_VTAB = LOCKED | (2 << 8)
|
||||
BUSY_RECOVERY = BUSY | (1 << 8)
|
||||
|
||||
@@ -33,8 +33,12 @@ func AssertErr() ErrorString {
|
||||
return ErrorString(msg)
|
||||
}
|
||||
|
||||
func ErrorCodeString(rc uint32) string {
|
||||
switch rc {
|
||||
type errorCode interface {
|
||||
~uint8 | ~uint16 | ~uint32 | ~int32
|
||||
}
|
||||
|
||||
func ErrorCodeString[T errorCode](rc T) string {
|
||||
switch uint32(rc) {
|
||||
case ABORT_ROLLBACK:
|
||||
return "sqlite3: abort due to ROLLBACK"
|
||||
case ROW:
|
||||
@@ -42,7 +46,7 @@ func ErrorCodeString(rc uint32) string {
|
||||
case DONE:
|
||||
return "sqlite3: no more rows available"
|
||||
}
|
||||
switch rc & 0xff {
|
||||
switch uint8(rc) {
|
||||
case OK:
|
||||
return "sqlite3: not an error"
|
||||
case ERROR:
|
||||
|
||||
@@ -7,8 +7,9 @@ import (
|
||||
"os"
|
||||
"unsafe"
|
||||
|
||||
"github.com/tetratelabs/wazero/api"
|
||||
"golang.org/x/sys/unix"
|
||||
|
||||
"github.com/tetratelabs/wazero/api"
|
||||
)
|
||||
|
||||
type mmapState struct {
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
package util
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"reflect"
|
||||
"unsafe"
|
||||
|
||||
"github.com/tetratelabs/wazero/api"
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
@@ -16,14 +14,21 @@ type MappedRegion struct {
|
||||
addr uintptr
|
||||
}
|
||||
|
||||
func MapRegion(ctx context.Context, mod api.Module, f *os.File, offset int64, size int32) (*MappedRegion, error) {
|
||||
h, err := windows.CreateFileMapping(windows.Handle(f.Fd()), nil, windows.PAGE_READWRITE, 0, 0, nil)
|
||||
func MapRegion(f *os.File, offset int64, size int32) (*MappedRegion, error) {
|
||||
maxSize := offset + int64(size)
|
||||
h, err := windows.CreateFileMapping(
|
||||
windows.Handle(f.Fd()), nil, windows.PAGE_READWRITE,
|
||||
uint32(maxSize>>32), uint32(maxSize), nil)
|
||||
if h == 0 {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
const allocationGranularity = 64 * 1024
|
||||
align := offset % allocationGranularity
|
||||
offset -= align
|
||||
|
||||
a, err := windows.MapViewOfFile(h, windows.FILE_MAP_WRITE,
|
||||
uint32(offset>>32), uint32(offset), uintptr(size))
|
||||
uint32(offset>>32), uint32(offset), uintptr(size)+uintptr(align))
|
||||
if a == 0 {
|
||||
windows.CloseHandle(h)
|
||||
return nil, err
|
||||
@@ -32,9 +37,9 @@ func MapRegion(ctx context.Context, mod api.Module, f *os.File, offset int64, si
|
||||
ret := &MappedRegion{Handle: h, addr: a}
|
||||
// SliceHeader, although deprecated, avoids a go vet warning.
|
||||
sh := (*reflect.SliceHeader)(unsafe.Pointer(&ret.Data))
|
||||
sh.Data = a + uintptr(align)
|
||||
sh.Len = int(size)
|
||||
sh.Cap = int(size)
|
||||
sh.Data = a
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ type ConnKey struct{}
|
||||
|
||||
type moduleKey struct{}
|
||||
type moduleState struct {
|
||||
sysError error
|
||||
mmapState
|
||||
handleState
|
||||
}
|
||||
@@ -23,3 +24,20 @@ func NewContext(ctx context.Context) context.Context {
|
||||
ctx = context.WithValue(ctx, moduleKey{}, state)
|
||||
return ctx
|
||||
}
|
||||
|
||||
func GetSystemError(ctx context.Context) error {
|
||||
// Test needed to simplify testing.
|
||||
s, ok := ctx.Value(moduleKey{}).(*moduleState)
|
||||
if ok {
|
||||
return s.sysError
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func SetSystemError(ctx context.Context, err error) {
|
||||
// Test needed to simplify testing.
|
||||
s, ok := ctx.Value(moduleKey{}).(*moduleState)
|
||||
if ok {
|
||||
s.sysError = err
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,8 +14,8 @@ var (
|
||||
// https://sqlite.org/c3ref/auto_extension.html
|
||||
func AutoExtension(entryPoint func(*Conn) error) {
|
||||
extRegistryMtx.Lock()
|
||||
defer extRegistryMtx.Unlock()
|
||||
extRegistry = append(extRegistry, entryPoint)
|
||||
extRegistryMtx.Unlock()
|
||||
}
|
||||
|
||||
func initExtensions(c *Conn) error {
|
||||
|
||||
27
sqlite.go
27
sqlite.go
@@ -5,12 +5,14 @@ import (
|
||||
"context"
|
||||
"math/bits"
|
||||
"os"
|
||||
"runtime"
|
||||
"strings"
|
||||
"sync"
|
||||
"unsafe"
|
||||
|
||||
"github.com/tetratelabs/wazero"
|
||||
"github.com/tetratelabs/wazero/api"
|
||||
"github.com/tetratelabs/wazero/experimental"
|
||||
|
||||
"github.com/ncruces/go-sqlite3/internal/util"
|
||||
"github.com/ncruces/go-sqlite3/vfs"
|
||||
@@ -79,7 +81,9 @@ func compileSQLite() {
|
||||
return
|
||||
}
|
||||
|
||||
instance.compiled, instance.err = instance.runtime.CompileModule(ctx, bin)
|
||||
instance.compiled, instance.err = instance.runtime.CompileModule(
|
||||
experimental.WithCompilationWorkers(ctx, runtime.GOMAXPROCS(0)/4),
|
||||
bin)
|
||||
}
|
||||
|
||||
type sqlite struct {
|
||||
@@ -125,14 +129,15 @@ func (sqlt *sqlite) error(rc res_t, handle ptr_t, sql ...string) error {
|
||||
panic(util.OOMErr)
|
||||
}
|
||||
|
||||
var msg, query string
|
||||
if handle != 0 {
|
||||
var msg, query string
|
||||
if ptr := ptr_t(sqlt.call("sqlite3_errmsg", stk_t(handle))); ptr != 0 {
|
||||
msg = util.ReadString(sqlt.mod, ptr, _MAX_LENGTH)
|
||||
if msg == "not an error" {
|
||||
msg = strings.TrimPrefix(msg, "sqlite3: ")
|
||||
msg = strings.TrimPrefix(msg, util.ErrorCodeString(rc)[len("sqlite3: "):])
|
||||
msg = strings.TrimPrefix(msg, ": ")
|
||||
if msg == "" || msg == "not an error" {
|
||||
msg = ""
|
||||
} else {
|
||||
msg = strings.TrimPrefix(msg, util.ErrorCodeString(uint32(rc))[len("sqlite3: "):])
|
||||
}
|
||||
}
|
||||
|
||||
@@ -141,10 +146,16 @@ func (sqlt *sqlite) error(rc res_t, handle ptr_t, sql ...string) error {
|
||||
query = sql[0][i:]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if msg != "" || query != "" {
|
||||
return &Error{code: rc, msg: msg, sql: query}
|
||||
}
|
||||
var sys error
|
||||
switch ErrorCode(rc) {
|
||||
case CANTOPEN, IOERR:
|
||||
sys = util.GetSystemError(sqlt.ctx)
|
||||
}
|
||||
|
||||
if sys != nil || msg != "" || query != "" {
|
||||
return &Error{code: rc, sys: sys, msg: msg, sql: query}
|
||||
}
|
||||
return xErrorCode(rc)
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
# handle, and interrupt, sqlite3_busy_timeout.
|
||||
--- sqlite3.c.orig
|
||||
+++ sqlite3.c
|
||||
@@ -184474,7 +184474,7 @@
|
||||
@@ -186667,7 +186667,7 @@
|
||||
if( !sqlite3SafetyCheckOk(db) ) return SQLITE_MISUSE_BKPT;
|
||||
#endif
|
||||
if( ms>0 ){
|
||||
|
||||
@@ -3,55 +3,54 @@ set -euo pipefail
|
||||
|
||||
cd -P -- "$(dirname -- "$0")"
|
||||
|
||||
curl -#OL "https://sqlite.org/2025/sqlite-amalgamation-3500400.zip"
|
||||
curl -#OL "https://sqlite.org/2025/sqlite-autoconf-3510100.tar.gz"
|
||||
|
||||
# Verify download.
|
||||
if hash=$(openssl dgst -sha3-256 sqlite-amalgamation-*.zip); then
|
||||
if ! [[ $hash =~ f131b68e6ba5fb891cc13ebb5ff9555054c77294cb92d8d1268bad5dba4fa2a1 ]]; then
|
||||
if hash=$(openssl dgst -sha3-256 sqlite-autoconf-*.tar.gz); then
|
||||
if ! [[ $hash =~ 9b2b1e73f577def1d5b75c5541555a7f42e6e073ad19f7a9118478389c9bbd9b ]]; then
|
||||
echo $hash
|
||||
exit 1
|
||||
fi
|
||||
fi 2> /dev/null
|
||||
|
||||
unzip -d . sqlite-amalgamation-*.zip
|
||||
mv sqlite-amalgamation-*/sqlite3.c .
|
||||
mv sqlite-amalgamation-*/sqlite3.h .
|
||||
mv sqlite-amalgamation-*/sqlite3ext.h .
|
||||
rm -rf sqlite-amalgamation-*
|
||||
tar xzf sqlite-autoconf-*.tar.gz
|
||||
|
||||
# To test a snapshot:
|
||||
# To test a snapshot instead:
|
||||
# curl -# https://sqlite.org/snapshot/sqlite-snapshot-202410081727.tar.gz | tar xz
|
||||
# mv sqlite-snapshot-*/sqlite3.c .
|
||||
# mv sqlite-snapshot-*/sqlite3.h .
|
||||
# mv sqlite-snapshot-*/sqlite3ext.h .
|
||||
# rm -rf sqlite-snapshot-*
|
||||
|
||||
mv sqlite-*/sqlite3.c .
|
||||
mv sqlite-*/sqlite3.h .
|
||||
mv sqlite-*/sqlite3ext.h .
|
||||
rm -r sqlite-*
|
||||
|
||||
GITHUB_TAG="https://github.com/sqlite/sqlite/raw/version-3.51.1"
|
||||
|
||||
mkdir -p ext/
|
||||
cd ext/
|
||||
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.50.4/ext/misc/anycollseq.c"
|
||||
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.50.4/ext/misc/base64.c"
|
||||
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.50.4/ext/misc/decimal.c"
|
||||
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.50.4/ext/misc/ieee754.c"
|
||||
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.50.4/ext/misc/regexp.c"
|
||||
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.50.4/ext/misc/series.c"
|
||||
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.50.4/ext/misc/spellfix.c"
|
||||
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.50.4/ext/misc/uint.c"
|
||||
curl -#OL "$GITHUB_TAG/ext/misc/anycollseq.c"
|
||||
curl -#OL "$GITHUB_TAG/ext/misc/base64.c"
|
||||
curl -#OL "$GITHUB_TAG/ext/misc/decimal.c"
|
||||
curl -#OL "$GITHUB_TAG/ext/misc/ieee754.c"
|
||||
curl -#OL "$GITHUB_TAG/ext/misc/regexp.c"
|
||||
curl -#OL "$GITHUB_TAG/ext/misc/series.c"
|
||||
curl -#OL "$GITHUB_TAG/ext/misc/spellfix.c"
|
||||
curl -#OL "$GITHUB_TAG/ext/misc/uint.c"
|
||||
cd ~-
|
||||
|
||||
cd ../vfs/tests/mptest/testdata/
|
||||
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.50.4/mptest/config01.test"
|
||||
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.50.4/mptest/config02.test"
|
||||
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.50.4/mptest/crash01.test"
|
||||
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.50.4/mptest/crash02.subtest"
|
||||
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.50.4/mptest/multiwrite01.test"
|
||||
curl -#OL "$GITHUB_TAG/mptest/config01.test"
|
||||
curl -#OL "$GITHUB_TAG/mptest/config02.test"
|
||||
curl -#OL "$GITHUB_TAG/mptest/crash01.test"
|
||||
curl -#OL "$GITHUB_TAG/mptest/crash02.subtest"
|
||||
curl -#OL "$GITHUB_TAG/mptest/multiwrite01.test"
|
||||
cd ~-
|
||||
|
||||
cd ../vfs/tests/mptest/wasm/
|
||||
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.50.4/mptest/mptest.c"
|
||||
curl -#OL "$GITHUB_TAG/mptest/mptest.c"
|
||||
cd ~-
|
||||
|
||||
cd ../vfs/tests/speedtest1/wasm/
|
||||
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.50.4/test/speedtest1.c"
|
||||
curl -#OL "$GITHUB_TAG/test/speedtest1.c"
|
||||
cd ~-
|
||||
|
||||
cat *.patch | patch -p0 --no-backup-if-mismatch
|
||||
cat *.patch | patch -p0 --no-backup-if-mismatch
|
||||
|
||||
@@ -39,12 +39,10 @@ EOF
|
||||
-Wl,--export=strcspn \
|
||||
-Wl,--export=strlen \
|
||||
-Wl,--export=strrchr \
|
||||
-Wl,--export=strspn \
|
||||
-Wl,--export=qsort
|
||||
-Wl,--export=strspn
|
||||
|
||||
"$BINARYEN/wasm-ctor-eval" -g -c _initialize libc.wasm -o libc.tmp
|
||||
"$BINARYEN/wasm-opt" -g libc.tmp -o libc.wasm \
|
||||
--low-memory-unused --generate-global-effects --converge -O3 \
|
||||
"$BINARYEN/wasm-opt" -g libc.tmp -o libc.wasm --converge -O3 \
|
||||
--enable-mutable-globals --enable-nontrapping-float-to-int \
|
||||
--enable-simd --enable-bulk-memory --enable-sign-ext \
|
||||
--enable-reference-types --enable-multivalue \
|
||||
|
||||
Binary file not shown.
@@ -1,13 +1,11 @@
|
||||
(module $libc.wasm
|
||||
(type $0 (func (param i32 i32) (result i32)))
|
||||
(type $1 (func (param i32 i32 i32) (result i32)))
|
||||
(type $2 (func (param i32 i32 i32 i32)))
|
||||
(type $3 (func (param i32) (result i32)))
|
||||
(type $0 (func (param i32 i32 i32) (result i32)))
|
||||
(type $1 (func (param i32 i32) (result i32)))
|
||||
(type $2 (func (param i32) (result i32)))
|
||||
(memory $0 256)
|
||||
(data $0 (i32.const 4096) "\01")
|
||||
(table $0 1 1 funcref)
|
||||
(data $.data (i32.const 4097) "\10\00\00\01\00\00\00\00\00\00\00\0c\10\00\00\0c\10\00\00\0c\10")
|
||||
(data $.data.1 (i32.const 4157) "\10\00\00\00\10")
|
||||
(export "memory" (memory $0))
|
||||
(export "qsort" (func $qsort))
|
||||
(export "memset" (func $memset))
|
||||
(export "memcpy" (func $memcpy))
|
||||
(export "memmove" (func $memcpy))
|
||||
@@ -20,423 +18,6 @@
|
||||
(export "strrchr" (func $strrchr))
|
||||
(export "strspn" (func $strspn))
|
||||
(export "strcspn" (func $strcspn))
|
||||
(func $qsort (param $0 i32) (param $1 i32) (param $2 i32) (param $3 i32)
|
||||
(local $4 i32)
|
||||
(local $5 i32)
|
||||
(local $6 i32)
|
||||
(local $7 i32)
|
||||
(local $8 i32)
|
||||
(local $9 i32)
|
||||
(local $10 i32)
|
||||
(local $11 i32)
|
||||
(local $12 i32)
|
||||
(local $13 i32)
|
||||
(local $14 i32)
|
||||
(local $15 i32)
|
||||
(local $16 i32)
|
||||
(local $17 i32)
|
||||
(local $18 i32)
|
||||
(local $19 i32)
|
||||
(local $20 v128)
|
||||
(local $scratch i32)
|
||||
(if
|
||||
(i32.ge_u
|
||||
(local.get $1)
|
||||
(i32.const 2)
|
||||
)
|
||||
(then
|
||||
(local.set $14
|
||||
(i32.mul
|
||||
(local.get $1)
|
||||
(local.get $2)
|
||||
)
|
||||
)
|
||||
(local.set $15
|
||||
(i32.and
|
||||
(local.get $2)
|
||||
(i32.const 15)
|
||||
)
|
||||
)
|
||||
(local.set $8
|
||||
(i32.and
|
||||
(local.get $2)
|
||||
(i32.const -16)
|
||||
)
|
||||
)
|
||||
(local.set $16
|
||||
(i32.add
|
||||
(local.get $0)
|
||||
(local.get $2)
|
||||
)
|
||||
)
|
||||
(local.set $17
|
||||
(i32.lt_u
|
||||
(local.get $2)
|
||||
(i32.const 16)
|
||||
)
|
||||
)
|
||||
(loop $label5
|
||||
(local.set $7
|
||||
(i32.eq
|
||||
(local.get $1)
|
||||
(i32.const 2)
|
||||
)
|
||||
)
|
||||
(local.set $18
|
||||
(i32.add
|
||||
(local.get $0)
|
||||
(i32.mul
|
||||
(local.get $2)
|
||||
(i32.add
|
||||
(local.tee $13
|
||||
(select
|
||||
(i32.const 1)
|
||||
(local.tee $1
|
||||
(i32.wrap_i64
|
||||
(i64.div_u
|
||||
(i64.sub
|
||||
(i64.mul
|
||||
(i64.extend_i32_u
|
||||
(local.get $1)
|
||||
)
|
||||
(i64.const 5)
|
||||
)
|
||||
(i64.const 1)
|
||||
)
|
||||
(i64.const 11)
|
||||
)
|
||||
)
|
||||
)
|
||||
(local.get $7)
|
||||
)
|
||||
)
|
||||
(i32.const 1)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
(local.set $10
|
||||
(local.tee $9
|
||||
(i32.mul
|
||||
(local.get $2)
|
||||
(local.get $13)
|
||||
)
|
||||
)
|
||||
)
|
||||
(loop $label4
|
||||
(block $block
|
||||
(br_if $block
|
||||
(i32.gt_u
|
||||
(local.tee $4
|
||||
(i32.sub
|
||||
(local.get $10)
|
||||
(local.get $9)
|
||||
)
|
||||
)
|
||||
(local.get $10)
|
||||
)
|
||||
)
|
||||
(loop $label3
|
||||
(br_if $block
|
||||
(i32.le_s
|
||||
(call_indirect $0 (type $0)
|
||||
(local.tee $5
|
||||
(i32.add
|
||||
(local.get $0)
|
||||
(local.tee $11
|
||||
(local.get $4)
|
||||
)
|
||||
)
|
||||
)
|
||||
(local.tee $4
|
||||
(i32.add
|
||||
(local.get $5)
|
||||
(local.get $9)
|
||||
)
|
||||
)
|
||||
(local.get $3)
|
||||
)
|
||||
(i32.const 0)
|
||||
)
|
||||
)
|
||||
(block $block2
|
||||
(block $block3
|
||||
(block $block1
|
||||
(br_if $block1
|
||||
(local.get $17)
|
||||
)
|
||||
(br_if $block1
|
||||
(i32.and
|
||||
(i32.lt_u
|
||||
(local.get $4)
|
||||
(i32.add
|
||||
(local.get $11)
|
||||
(local.get $16)
|
||||
)
|
||||
)
|
||||
(i32.gt_u
|
||||
(i32.add
|
||||
(local.get $11)
|
||||
(local.get $18)
|
||||
)
|
||||
(local.get $5)
|
||||
)
|
||||
)
|
||||
)
|
||||
(local.set $4
|
||||
(i32.add
|
||||
(local.get $4)
|
||||
(local.get $8)
|
||||
)
|
||||
)
|
||||
(local.set $6
|
||||
(i32.add
|
||||
(local.get $5)
|
||||
(local.get $8)
|
||||
)
|
||||
)
|
||||
(local.set $7
|
||||
(local.get $8)
|
||||
)
|
||||
(loop $label
|
||||
(local.set $20
|
||||
(v128.load align=1
|
||||
(local.get $5)
|
||||
)
|
||||
)
|
||||
(v128.store align=1
|
||||
(local.get $5)
|
||||
(v128.load align=1
|
||||
(local.tee $12
|
||||
(i32.add
|
||||
(local.get $5)
|
||||
(local.get $9)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
(v128.store align=1
|
||||
(local.get $12)
|
||||
(local.get $20)
|
||||
)
|
||||
(local.set $5
|
||||
(i32.add
|
||||
(local.get $5)
|
||||
(i32.const 16)
|
||||
)
|
||||
)
|
||||
(br_if $label
|
||||
(local.tee $7
|
||||
(i32.sub
|
||||
(local.get $7)
|
||||
(i32.const 16)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
(local.set $7
|
||||
(local.get $15)
|
||||
)
|
||||
(br_if $block2
|
||||
(i32.eq
|
||||
(local.get $2)
|
||||
(local.get $8)
|
||||
)
|
||||
)
|
||||
(br $block3)
|
||||
)
|
||||
(local.set $6
|
||||
(local.get $5)
|
||||
)
|
||||
(local.set $7
|
||||
(local.get $2)
|
||||
)
|
||||
)
|
||||
(br_if $block2
|
||||
(i32.lt_u
|
||||
(block (result i32)
|
||||
(local.set $scratch
|
||||
(i32.sub
|
||||
(local.get $7)
|
||||
(i32.const 1)
|
||||
)
|
||||
)
|
||||
(if
|
||||
(local.tee $5
|
||||
(i32.and
|
||||
(local.get $7)
|
||||
(i32.const 3)
|
||||
)
|
||||
)
|
||||
(then
|
||||
(local.set $7
|
||||
(i32.and
|
||||
(local.get $7)
|
||||
(i32.const -4)
|
||||
)
|
||||
)
|
||||
(loop $label1
|
||||
(local.set $19
|
||||
(i32.load8_u
|
||||
(local.get $6)
|
||||
)
|
||||
)
|
||||
(i32.store8
|
||||
(local.get $6)
|
||||
(i32.load8_u
|
||||
(local.get $4)
|
||||
)
|
||||
)
|
||||
(i32.store8
|
||||
(local.get $4)
|
||||
(local.get $19)
|
||||
)
|
||||
(local.set $4
|
||||
(i32.add
|
||||
(local.get $4)
|
||||
(i32.const 1)
|
||||
)
|
||||
)
|
||||
(local.set $6
|
||||
(i32.add
|
||||
(local.get $6)
|
||||
(i32.const 1)
|
||||
)
|
||||
)
|
||||
(br_if $label1
|
||||
(local.tee $5
|
||||
(i32.sub
|
||||
(local.get $5)
|
||||
(i32.const 1)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
(local.get $scratch)
|
||||
)
|
||||
(i32.const 3)
|
||||
)
|
||||
)
|
||||
(loop $label2
|
||||
(local.set $5
|
||||
(i32.load8_u
|
||||
(local.get $6)
|
||||
)
|
||||
)
|
||||
(i32.store8
|
||||
(local.get $6)
|
||||
(i32.load8_u
|
||||
(local.get $4)
|
||||
)
|
||||
)
|
||||
(i32.store8
|
||||
(local.get $4)
|
||||
(local.get $5)
|
||||
)
|
||||
(local.set $5
|
||||
(i32.load8_u offset=1
|
||||
(local.get $6)
|
||||
)
|
||||
)
|
||||
(i32.store8 offset=1
|
||||
(local.get $6)
|
||||
(i32.load8_u offset=1
|
||||
(local.get $4)
|
||||
)
|
||||
)
|
||||
(i32.store8 offset=1
|
||||
(local.get $4)
|
||||
(local.get $5)
|
||||
)
|
||||
(local.set $5
|
||||
(i32.load8_u offset=2
|
||||
(local.get $6)
|
||||
)
|
||||
)
|
||||
(i32.store8 offset=2
|
||||
(local.get $6)
|
||||
(i32.load8_u offset=2
|
||||
(local.get $4)
|
||||
)
|
||||
)
|
||||
(i32.store8 offset=2
|
||||
(local.get $4)
|
||||
(local.get $5)
|
||||
)
|
||||
(local.set $5
|
||||
(i32.load8_u offset=3
|
||||
(local.get $6)
|
||||
)
|
||||
)
|
||||
(i32.store8 offset=3
|
||||
(local.get $6)
|
||||
(i32.load8_u offset=3
|
||||
(local.get $4)
|
||||
)
|
||||
)
|
||||
(i32.store8 offset=3
|
||||
(local.get $4)
|
||||
(local.get $5)
|
||||
)
|
||||
(local.set $6
|
||||
(i32.add
|
||||
(local.get $6)
|
||||
(i32.const 4)
|
||||
)
|
||||
)
|
||||
(local.set $4
|
||||
(i32.add
|
||||
(local.get $4)
|
||||
(i32.const 4)
|
||||
)
|
||||
)
|
||||
(br_if $label2
|
||||
(local.tee $7
|
||||
(i32.sub
|
||||
(local.get $7)
|
||||
(i32.const 4)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
(br_if $label3
|
||||
(i32.le_u
|
||||
(local.tee $4
|
||||
(i32.sub
|
||||
(local.get $11)
|
||||
(local.get $9)
|
||||
)
|
||||
)
|
||||
(local.get $11)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
(br_if $label4
|
||||
(i32.lt_u
|
||||
(local.tee $10
|
||||
(i32.add
|
||||
(local.get $2)
|
||||
(local.get $10)
|
||||
)
|
||||
)
|
||||
(local.get $14)
|
||||
)
|
||||
)
|
||||
)
|
||||
(br_if $label5
|
||||
(i32.ge_u
|
||||
(local.get $13)
|
||||
(i32.const 2)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
(func $memset (param $0 i32) (param $1 i32) (param $2 i32) (result i32)
|
||||
(if
|
||||
(local.get $2)
|
||||
@@ -1493,33 +1074,243 @@
|
||||
(local $2 i32)
|
||||
(local $3 i32)
|
||||
(local $4 i32)
|
||||
(local $5 i32)
|
||||
(local $5 v128)
|
||||
(local $6 v128)
|
||||
(local $7 v128)
|
||||
(local $8 v128)
|
||||
(local $9 v128)
|
||||
(block $block
|
||||
(if
|
||||
(local.tee $3
|
||||
(i32.load8_u
|
||||
(local.get $1)
|
||||
(block $block2
|
||||
(block $block
|
||||
(br_if $block
|
||||
(i32.eqz
|
||||
(local.tee $3
|
||||
(i32.load8_u
|
||||
(local.get $1)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
(then
|
||||
(br_if $block
|
||||
(br_if $block
|
||||
(i32.eqz
|
||||
(i32.load8_u offset=1
|
||||
(local.get $1)
|
||||
)
|
||||
)
|
||||
)
|
||||
(loop $label
|
||||
(v128.store
|
||||
(i32.const 4080)
|
||||
(local.get $6)
|
||||
)
|
||||
(i32.store8
|
||||
(i32.or
|
||||
(local.tee $3
|
||||
(i32.and
|
||||
(local.tee $2
|
||||
(i32.load8_u
|
||||
(local.get $1)
|
||||
)
|
||||
)
|
||||
(i32.const 15)
|
||||
)
|
||||
)
|
||||
(i32.const 4080)
|
||||
)
|
||||
(i32.or
|
||||
(i32.load8_u
|
||||
(i32.or
|
||||
(local.get $3)
|
||||
(i32.const 4080)
|
||||
)
|
||||
)
|
||||
(i32.shl
|
||||
(i32.const 1)
|
||||
(i32.sub
|
||||
(local.tee $4
|
||||
(i32.shr_u
|
||||
(local.get $2)
|
||||
(i32.const 4)
|
||||
)
|
||||
)
|
||||
(i32.const 8)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
(v128.store
|
||||
(i32.const 4064)
|
||||
(local.get $5)
|
||||
)
|
||||
(i32.store8
|
||||
(local.tee $3
|
||||
(i32.or
|
||||
(local.get $3)
|
||||
(i32.const 4064)
|
||||
)
|
||||
)
|
||||
(i32.or
|
||||
(i32.load8_u
|
||||
(local.get $3)
|
||||
)
|
||||
(i32.shl
|
||||
(i32.const 1)
|
||||
(local.get $4)
|
||||
)
|
||||
)
|
||||
)
|
||||
(local.set $1
|
||||
(i32.add
|
||||
(local.get $1)
|
||||
(i32.const 1)
|
||||
)
|
||||
)
|
||||
(local.set $6
|
||||
(v128.load
|
||||
(i32.const 4080)
|
||||
)
|
||||
)
|
||||
(local.set $5
|
||||
(v128.load
|
||||
(i32.const 4064)
|
||||
)
|
||||
)
|
||||
(br_if $label
|
||||
(local.get $2)
|
||||
)
|
||||
)
|
||||
(block $block1
|
||||
(if
|
||||
(v128.any_true
|
||||
(local.tee $7
|
||||
(v128.and
|
||||
(v128.or
|
||||
(i8x16.swizzle
|
||||
(local.get $6)
|
||||
(v128.xor
|
||||
(local.tee $8
|
||||
(v128.and
|
||||
(local.tee $7
|
||||
(v128.load
|
||||
(local.tee $2
|
||||
(i32.and
|
||||
(local.get $0)
|
||||
(i32.const -16)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
(v128.const i32x4 0x8f8f8f8f 0x8f8f8f8f 0x8f8f8f8f 0x8f8f8f8f)
|
||||
)
|
||||
)
|
||||
(v128.const i32x4 0x80808080 0x80808080 0x80808080 0x80808080)
|
||||
)
|
||||
)
|
||||
(i8x16.swizzle
|
||||
(local.get $5)
|
||||
(local.get $8)
|
||||
)
|
||||
)
|
||||
(i8x16.swizzle
|
||||
(v128.const i32x4 0x08040201 0x80402010 0x08040201 0x80402010)
|
||||
(i8x16.shr_u
|
||||
(local.get $7)
|
||||
(i32.const 4)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
(then
|
||||
(br_if $block1
|
||||
(local.tee $1
|
||||
(i32.and
|
||||
(i32.xor
|
||||
(i8x16.bitmask
|
||||
(i8x16.eq
|
||||
(local.get $7)
|
||||
(v128.const i32x4 0x00000000 0x00000000 0x00000000 0x00000000)
|
||||
)
|
||||
)
|
||||
(i32.const 65535)
|
||||
)
|
||||
(i32.shl
|
||||
(i32.const -1)
|
||||
(i32.and
|
||||
(local.get $0)
|
||||
(i32.const 15)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
(loop $label1
|
||||
(br_if $label1
|
||||
(i32.eqz
|
||||
(v128.any_true
|
||||
(local.tee $7
|
||||
(v128.and
|
||||
(v128.or
|
||||
(i8x16.swizzle
|
||||
(local.get $6)
|
||||
(v128.xor
|
||||
(local.tee $8
|
||||
(v128.and
|
||||
(local.tee $7
|
||||
(v128.load
|
||||
(local.tee $2
|
||||
(i32.add
|
||||
(local.get $2)
|
||||
(i32.const 16)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
(v128.const i32x4 0x8f8f8f8f 0x8f8f8f8f 0x8f8f8f8f 0x8f8f8f8f)
|
||||
)
|
||||
)
|
||||
(v128.const i32x4 0x80808080 0x80808080 0x80808080 0x80808080)
|
||||
)
|
||||
)
|
||||
(i8x16.swizzle
|
||||
(local.get $5)
|
||||
(local.get $8)
|
||||
)
|
||||
)
|
||||
(i8x16.swizzle
|
||||
(v128.const i32x4 0x08040201 0x80402010 0x08040201 0x80402010)
|
||||
(i8x16.shr_u
|
||||
(local.get $7)
|
||||
(i32.const 4)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
(local.set $1
|
||||
(i32.xor
|
||||
(i8x16.bitmask
|
||||
(i8x16.eq
|
||||
(local.get $7)
|
||||
(v128.const i32x4 0x00000000 0x00000000 0x00000000 0x00000000)
|
||||
)
|
||||
)
|
||||
(i32.const 65535)
|
||||
)
|
||||
)
|
||||
)
|
||||
(br $block2)
|
||||
)
|
||||
(block $block1
|
||||
(block $block3
|
||||
(if
|
||||
(v128.any_true
|
||||
(local.tee $6
|
||||
(local.tee $5
|
||||
(v128.or
|
||||
(i8x16.eq
|
||||
(local.tee $7
|
||||
(local.tee $6
|
||||
(v128.load
|
||||
(local.tee $2
|
||||
(i32.and
|
||||
@@ -1532,8 +1323,8 @@
|
||||
(v128.const i32x4 0x00000000 0x00000000 0x00000000 0x00000000)
|
||||
)
|
||||
(i8x16.eq
|
||||
(local.get $7)
|
||||
(local.tee $7
|
||||
(local.get $6)
|
||||
(local.tee $6
|
||||
(i8x16.splat
|
||||
(local.get $3)
|
||||
)
|
||||
@@ -1543,11 +1334,11 @@
|
||||
)
|
||||
)
|
||||
(then
|
||||
(br_if $block1
|
||||
(br_if $block3
|
||||
(local.tee $1
|
||||
(i32.and
|
||||
(i8x16.bitmask
|
||||
(local.get $6)
|
||||
(local.get $5)
|
||||
)
|
||||
(i32.shl
|
||||
(i32.const -1)
|
||||
@@ -1561,14 +1352,14 @@
|
||||
)
|
||||
)
|
||||
)
|
||||
(loop $label
|
||||
(br_if $label
|
||||
(loop $label2
|
||||
(br_if $label2
|
||||
(i32.eqz
|
||||
(v128.any_true
|
||||
(local.tee $6
|
||||
(local.tee $5
|
||||
(v128.or
|
||||
(i8x16.eq
|
||||
(local.tee $6
|
||||
(local.tee $5
|
||||
(v128.load
|
||||
(local.tee $2
|
||||
(i32.add
|
||||
@@ -1581,8 +1372,8 @@
|
||||
(v128.const i32x4 0x00000000 0x00000000 0x00000000 0x00000000)
|
||||
)
|
||||
(i8x16.eq
|
||||
(local.get $5)
|
||||
(local.get $6)
|
||||
(local.get $7)
|
||||
)
|
||||
)
|
||||
)
|
||||
@@ -1592,230 +1383,10 @@
|
||||
)
|
||||
(local.set $1
|
||||
(i8x16.bitmask
|
||||
(local.get $6)
|
||||
)
|
||||
)
|
||||
)
|
||||
(return
|
||||
(i32.add
|
||||
(i32.ctz
|
||||
(local.get $1)
|
||||
)
|
||||
(i32.sub
|
||||
(local.get $2)
|
||||
(local.get $0)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
(local.set $4
|
||||
(i32.and
|
||||
(local.get $0)
|
||||
(i32.const 15)
|
||||
)
|
||||
)
|
||||
(loop $label1
|
||||
(v128.store
|
||||
(i32.const 4080)
|
||||
(local.get $7)
|
||||
)
|
||||
(i32.store8
|
||||
(i32.or
|
||||
(local.tee $3
|
||||
(i32.and
|
||||
(local.tee $2
|
||||
(i32.load8_u
|
||||
(local.get $1)
|
||||
)
|
||||
)
|
||||
(i32.const 15)
|
||||
)
|
||||
)
|
||||
(i32.const 4080)
|
||||
)
|
||||
(i32.or
|
||||
(i32.load8_u
|
||||
(i32.or
|
||||
(local.get $3)
|
||||
(i32.const 4080)
|
||||
)
|
||||
)
|
||||
(i32.shl
|
||||
(i32.const 1)
|
||||
(i32.sub
|
||||
(local.tee $5
|
||||
(i32.shr_u
|
||||
(local.get $2)
|
||||
(i32.const 4)
|
||||
)
|
||||
)
|
||||
(i32.const 8)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
(v128.store
|
||||
(i32.const 4064)
|
||||
(local.get $6)
|
||||
)
|
||||
(i32.store8
|
||||
(local.tee $3
|
||||
(i32.or
|
||||
(local.get $3)
|
||||
(i32.const 4064)
|
||||
)
|
||||
)
|
||||
(i32.or
|
||||
(i32.load8_u
|
||||
(local.get $3)
|
||||
)
|
||||
(i32.shl
|
||||
(i32.const 1)
|
||||
(local.get $5)
|
||||
)
|
||||
)
|
||||
)
|
||||
(local.set $1
|
||||
(i32.add
|
||||
(local.get $1)
|
||||
(i32.const 1)
|
||||
)
|
||||
)
|
||||
(local.set $7
|
||||
(v128.load
|
||||
(i32.const 4080)
|
||||
)
|
||||
)
|
||||
(local.set $6
|
||||
(v128.load
|
||||
(i32.const 4064)
|
||||
)
|
||||
)
|
||||
(br_if $label1
|
||||
(local.get $2)
|
||||
)
|
||||
)
|
||||
(block $block2
|
||||
(if
|
||||
(v128.any_true
|
||||
(local.tee $8
|
||||
(v128.and
|
||||
(v128.or
|
||||
(i8x16.swizzle
|
||||
(local.get $7)
|
||||
(v128.xor
|
||||
(local.tee $9
|
||||
(v128.and
|
||||
(local.tee $8
|
||||
(v128.load
|
||||
(local.tee $2
|
||||
(i32.and
|
||||
(local.get $0)
|
||||
(i32.const -16)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
(v128.const i32x4 0x8f8f8f8f 0x8f8f8f8f 0x8f8f8f8f 0x8f8f8f8f)
|
||||
)
|
||||
)
|
||||
(v128.const i32x4 0x80808080 0x80808080 0x80808080 0x80808080)
|
||||
)
|
||||
)
|
||||
(i8x16.swizzle
|
||||
(local.get $6)
|
||||
(local.get $9)
|
||||
)
|
||||
)
|
||||
(i8x16.swizzle
|
||||
(v128.const i32x4 0x08040201 0x80402010 0x08040201 0x80402010)
|
||||
(i8x16.shr_u
|
||||
(local.get $8)
|
||||
(i32.const 4)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
(then
|
||||
(br_if $block2
|
||||
(local.tee $1
|
||||
(i32.and
|
||||
(i32.xor
|
||||
(i8x16.bitmask
|
||||
(i8x16.eq
|
||||
(local.get $8)
|
||||
(v128.const i32x4 0x00000000 0x00000000 0x00000000 0x00000000)
|
||||
)
|
||||
)
|
||||
(i32.const 65535)
|
||||
)
|
||||
(i32.shl
|
||||
(i32.const -1)
|
||||
(local.get $4)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
(loop $label2
|
||||
(br_if $label2
|
||||
(i32.eqz
|
||||
(v128.any_true
|
||||
(local.tee $8
|
||||
(v128.and
|
||||
(v128.or
|
||||
(i8x16.swizzle
|
||||
(local.get $7)
|
||||
(v128.xor
|
||||
(local.tee $9
|
||||
(v128.and
|
||||
(local.tee $8
|
||||
(v128.load
|
||||
(local.tee $2
|
||||
(i32.add
|
||||
(local.get $2)
|
||||
(i32.const 16)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
(v128.const i32x4 0x8f8f8f8f 0x8f8f8f8f 0x8f8f8f8f 0x8f8f8f8f)
|
||||
)
|
||||
)
|
||||
(v128.const i32x4 0x80808080 0x80808080 0x80808080 0x80808080)
|
||||
)
|
||||
)
|
||||
(i8x16.swizzle
|
||||
(local.get $6)
|
||||
(local.get $9)
|
||||
)
|
||||
)
|
||||
(i8x16.swizzle
|
||||
(v128.const i32x4 0x08040201 0x80402010 0x08040201 0x80402010)
|
||||
(i8x16.shr_u
|
||||
(local.get $8)
|
||||
(i32.const 4)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
(local.set $1
|
||||
(i32.xor
|
||||
(i8x16.bitmask
|
||||
(i8x16.eq
|
||||
(local.get $8)
|
||||
(v128.const i32x4 0x00000000 0x00000000 0x00000000 0x00000000)
|
||||
)
|
||||
)
|
||||
(i32.const 65535)
|
||||
)
|
||||
)
|
||||
)
|
||||
(i32.add
|
||||
(i32.ctz
|
||||
|
||||
@@ -26,8 +26,8 @@ elif [[ "$OSTYPE" == "darwin"* ]]; then
|
||||
fi
|
||||
fi
|
||||
|
||||
WASI_SDK="https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-27/wasi-sdk-27.0-$WASI_SDK.tar.gz"
|
||||
BINARYEN="https://github.com/WebAssembly/binaryen/releases/download/version_124/binaryen-version_124-$BINARYEN.tar.gz"
|
||||
WASI_SDK="https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-29/wasi-sdk-29.0-$WASI_SDK.tar.gz"
|
||||
BINARYEN="https://github.com/WebAssembly/binaryen/releases/download/version_125/binaryen-version_125-$BINARYEN.tar.gz"
|
||||
|
||||
# Download tools
|
||||
mkdir -p "$ROOT/tools"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# Remove VFS registration. Go handles it.
|
||||
--- sqlite3.c.orig
|
||||
+++ sqlite3.c
|
||||
@@ -26884,7 +26884,7 @@
|
||||
@@ -27176,7 +27176,7 @@
|
||||
sqlite3_free(p);
|
||||
return sqlite3_os_init();
|
||||
}
|
||||
@@ -10,7 +10,7 @@
|
||||
/*
|
||||
** The list of all registered VFS implementations.
|
||||
*/
|
||||
@@ -26981,7 +26981,7 @@
|
||||
@@ -27273,7 +27273,7 @@
|
||||
sqlite3_mutex_leave(mutex);
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
@@ -189,13 +189,23 @@ func TestConn_FileControl(t *testing.T) {
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("FCNTL_VFSNAME", func(t *testing.T) {
|
||||
o, err := db.FileControl("", sqlite3.FCNTL_VFSNAME)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if o != "os" {
|
||||
t.Errorf(`got %q, want "os"`, o)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("FCNTL_VFS_POINTER", func(t *testing.T) {
|
||||
o, err := db.FileControl("", sqlite3.FCNTL_VFS_POINTER)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if o != vfs.Find("os") {
|
||||
t.Errorf("got %v, want os", o)
|
||||
t.Errorf(`got %v, want "os"`, o)
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
@@ -22,8 +22,8 @@ func TestConn_Open_dir(t *testing.T) {
|
||||
if err == nil {
|
||||
t.Fatal("want error")
|
||||
}
|
||||
if !errors.Is(err, sqlite3.CANTOPEN) {
|
||||
t.Errorf("got %v, want sqlite3.CANTOPEN", err)
|
||||
if !errors.Is(err, sqlite3.CANTOPEN_ISDIR) {
|
||||
t.Errorf("got %v, want sqlite3.CANTOPEN_ISDIR", err)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
15
time.go
15
time.go
@@ -7,6 +7,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/ncruces/go-sqlite3/internal/util"
|
||||
"github.com/ncruces/go-sqlite3/util/sql3util"
|
||||
"github.com/ncruces/julianday"
|
||||
)
|
||||
|
||||
@@ -157,11 +158,13 @@ func (f TimeFormat) Decode(v any) (time.Time, error) {
|
||||
|
||||
case TimeFormatUnix, TimeFormatUnixFrac:
|
||||
if s, ok := v.(string); ok {
|
||||
f, err := strconv.ParseFloat(s, 64)
|
||||
if err != nil {
|
||||
return time.Time{}, err
|
||||
if i, err := strconv.ParseInt(s, 10, 64); err == nil {
|
||||
v = i
|
||||
} else if f, ok := sql3util.ParseFloat(s); ok {
|
||||
v = f
|
||||
} else {
|
||||
return time.Time{}, util.TimeErr
|
||||
}
|
||||
v = f
|
||||
}
|
||||
switch v := v.(type) {
|
||||
case float64:
|
||||
@@ -234,8 +237,8 @@ func (f TimeFormat) Decode(v any) (time.Time, error) {
|
||||
v = i
|
||||
break
|
||||
}
|
||||
f, err := strconv.ParseFloat(s, 64)
|
||||
if err == nil {
|
||||
f, ok := sql3util.ParseFloat(s)
|
||||
if ok {
|
||||
v = f
|
||||
break
|
||||
}
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
package sql3util
|
||||
|
||||
import "strings"
|
||||
import (
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// NamedArg splits an named arg into a key and value,
|
||||
// around an equals sign.
|
||||
@@ -63,3 +67,124 @@ func ParseBool(s string) (b, ok bool) {
|
||||
}
|
||||
return false, false
|
||||
}
|
||||
|
||||
// ParseFloat parses a decimal floating point number.
|
||||
func ParseFloat(s string) (f float64, ok bool) {
|
||||
if strings.TrimLeft(s, "+-.0123456789Ee") != "" {
|
||||
return
|
||||
}
|
||||
f, err := strconv.ParseFloat(s, 64)
|
||||
return f, err == nil
|
||||
}
|
||||
|
||||
// ParseTimeShift parses a time shift modifier,
|
||||
// also the output of timediff.
|
||||
//
|
||||
// https://sqlite.org/lang_datefunc.html
|
||||
func ParseTimeShift(s string) (years, months, days int, duration time.Duration, ok bool) {
|
||||
// Sign part: ±
|
||||
neg := strings.HasPrefix(s, "-")
|
||||
sign := neg || strings.HasPrefix(s, "+")
|
||||
if sign {
|
||||
s = s[1:]
|
||||
}
|
||||
|
||||
if ok = len(s) >= 5; !ok {
|
||||
return // !ok
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if neg {
|
||||
years = -years
|
||||
months = -months
|
||||
days = -days
|
||||
duration = -duration
|
||||
}
|
||||
}()
|
||||
|
||||
// Date part: YYYY-MM-DD
|
||||
if s[4] == '-' {
|
||||
if ok = sign && len(s) >= 10 && s[7] == '-'; !ok {
|
||||
return // !ok
|
||||
}
|
||||
if years, ok = parseInt(s[0:4], 0); !ok {
|
||||
return // !ok
|
||||
}
|
||||
if months, ok = parseInt(s[5:7], 12); !ok {
|
||||
return // !ok
|
||||
}
|
||||
if days, ok = parseInt(s[8:10], 31); !ok {
|
||||
return // !ok
|
||||
}
|
||||
if len(s) == 10 {
|
||||
return
|
||||
}
|
||||
if ok = s[10] == ' '; !ok {
|
||||
return // !ok
|
||||
}
|
||||
s = s[11:]
|
||||
}
|
||||
|
||||
// Time part: HH:MM
|
||||
if ok = len(s) >= 5 && s[2] == ':'; !ok {
|
||||
return // !ok
|
||||
}
|
||||
|
||||
var hours, minutes int
|
||||
if hours, ok = parseInt(s[0:2], 24); !ok {
|
||||
return
|
||||
}
|
||||
if minutes, ok = parseInt(s[3:5], 60); !ok {
|
||||
return
|
||||
}
|
||||
duration = time.Duration(hours)*time.Hour + time.Duration(minutes)*time.Minute
|
||||
|
||||
if len(s) == 5 {
|
||||
return
|
||||
}
|
||||
if ok = len(s) >= 8 && s[5] == ':'; !ok {
|
||||
return // !ok
|
||||
}
|
||||
|
||||
// Seconds part: HH:MM:SS
|
||||
var seconds int
|
||||
if seconds, ok = parseInt(s[6:8], 60); !ok {
|
||||
return
|
||||
}
|
||||
duration += time.Duration(seconds) * time.Second
|
||||
|
||||
if len(s) == 8 {
|
||||
return
|
||||
}
|
||||
if ok = len(s) >= 10 && s[8] == '.'; !ok {
|
||||
return // !ok
|
||||
}
|
||||
s = s[9:]
|
||||
|
||||
// Nanosecond part: HH:MM:SS.SSS
|
||||
var nanos int
|
||||
if nanos, ok = parseInt(s[0:min(9, len(s))], 0); !ok {
|
||||
return
|
||||
}
|
||||
for i := len(s); i < 9; i++ {
|
||||
nanos *= 10
|
||||
}
|
||||
duration += time.Duration(nanos)
|
||||
|
||||
// Subnanosecond part.
|
||||
if len(s) > 9 {
|
||||
_, ok = parseInt(s[9:], 0)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func parseInt(s string, max int) (i int, _ bool) {
|
||||
for _, r := range []byte(s) {
|
||||
r -= '0'
|
||||
if r > 9 {
|
||||
return
|
||||
}
|
||||
i = i*10 + int(r)
|
||||
}
|
||||
return i, max == 0 || i < max
|
||||
}
|
||||
|
||||
@@ -2,7 +2,10 @@ package sql3util_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/ncruces/go-sqlite3"
|
||||
_ "github.com/ncruces/go-sqlite3/embed"
|
||||
"github.com/ncruces/go-sqlite3/util/sql3util"
|
||||
)
|
||||
|
||||
@@ -53,3 +56,112 @@ func TestParseBool(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseTimeShift(t *testing.T) {
|
||||
epoch := time.Unix(0, 0)
|
||||
tests := []struct {
|
||||
str string
|
||||
val time.Time
|
||||
ok bool
|
||||
}{
|
||||
{"", epoch, false},
|
||||
{"0001-11-30", epoch, false},
|
||||
{"+_001-11-30", epoch, false},
|
||||
{"+0001-_1-30", epoch.AddDate(1, 0, 0), false},
|
||||
{"+0001-11-_0", epoch.AddDate(1, 11, 0), false},
|
||||
{"+0001-11-30", epoch.AddDate(1, 11, 30), true},
|
||||
{"-0001-11-30", epoch.AddDate(-1, -11, -30), true},
|
||||
{"+0001-11-30T", epoch.AddDate(1, 11, 30), false},
|
||||
{"+0001-11-30 12", epoch.AddDate(1, 11, 30), false},
|
||||
{"+0001-11-30 _2:30", epoch.AddDate(1, 11, 30), false},
|
||||
{"+0001-11-30 12:_0", epoch.AddDate(1, 11, 30), false},
|
||||
{"+0001-11-30 12:30", epoch.AddDate(1, 11, 30).Add(12*time.Hour + 30*time.Minute), true},
|
||||
{"+0001-11-30 12:30:", epoch.AddDate(1, 11, 30).Add(12*time.Hour + 30*time.Minute), false},
|
||||
{"+0001-11-30 12:30:_0", epoch.AddDate(1, 11, 30).Add(12*time.Hour + 30*time.Minute), false},
|
||||
{"+0001-11-30 12:30:59", epoch.AddDate(1, 11, 30).Add(12*time.Hour + 30*time.Minute + 59*time.Second), true},
|
||||
{"+0001-11-30 12:30:59.", epoch.AddDate(1, 11, 30).Add(12*time.Hour + 30*time.Minute + 59*time.Second), false},
|
||||
{"+0001-11-30 12:30:59._", epoch.AddDate(1, 11, 30).Add(12*time.Hour + 30*time.Minute + 59*time.Second), false},
|
||||
{"+0001-11-30 12:30:59.1", epoch.AddDate(1, 11, 30).Add(12*time.Hour + 30*time.Minute + 59*time.Second + 100*time.Millisecond), true},
|
||||
{"+0001-11-30 12:30:59.123456789_", epoch.AddDate(1, 11, 30).Add(12*time.Hour + 30*time.Minute + 59*time.Second + 123456789), false},
|
||||
{"+0001-11-30 12:30:59.1234567890", epoch.AddDate(1, 11, 30).Add(12*time.Hour + 30*time.Minute + 59*time.Second + 123456789), true},
|
||||
{"-12:30:59.1234567890", epoch.Add(-12*time.Hour - 30*time.Minute - 59*time.Second - 123456789), true},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.str, func(t *testing.T) {
|
||||
years, months, days, duration, gotOK := sql3util.ParseTimeShift(tt.str)
|
||||
gotVal := epoch.AddDate(years, months, days).Add(duration)
|
||||
if !gotVal.Equal(tt.val) || gotOK != tt.ok {
|
||||
t.Errorf("ParseTimeShift(%q) = (%v, %v) want (%v, %v)", tt.str, gotVal, gotOK, tt.val, tt.ok)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func FuzzParseTimeShift(f *testing.F) {
|
||||
f.Add("")
|
||||
f.Add("0001-12-30")
|
||||
f.Add("+_001-12-30")
|
||||
f.Add("+0001-_2-30")
|
||||
f.Add("+0001-12-_0")
|
||||
f.Add("+0001-12-30")
|
||||
f.Add("-0001-12-30")
|
||||
f.Add("+0001-12-30T")
|
||||
f.Add("+0001-12-30 12")
|
||||
f.Add("+0001-12-30 _2:30")
|
||||
f.Add("+0001-12-30 12:_0")
|
||||
f.Add("+0001-12-30 12:30")
|
||||
f.Add("+0001-12-30 12:30:")
|
||||
f.Add("+0001-12-30 12:30:_0")
|
||||
f.Add("+0001-12-30 12:30:60")
|
||||
f.Add("+0001-12-30 12:30:60.")
|
||||
f.Add("+0001-12-30 12:30:60._")
|
||||
f.Add("+0001-12-30 12:30:60.1")
|
||||
f.Add("+0001-12-30 12:30:60.123456789_")
|
||||
f.Add("+0001-12-30 12:30:60.1234567890")
|
||||
f.Add("-12:30:60.1234567890")
|
||||
|
||||
c, err := sqlite3.Open(":memory:")
|
||||
if err != nil {
|
||||
f.Fatal(err)
|
||||
}
|
||||
defer c.Close()
|
||||
|
||||
s, _, err := c.Prepare(`SELECT julianday('00:00', ?)`)
|
||||
if err != nil {
|
||||
f.Fatal(err)
|
||||
}
|
||||
defer s.Close()
|
||||
|
||||
// Default SQLite date.
|
||||
epoch := time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC)
|
||||
|
||||
f.Fuzz(func(t *testing.T, str string) {
|
||||
years, months, days, duration, ok := sql3util.ParseTimeShift(str)
|
||||
|
||||
// Account for a full 400 year cycle.
|
||||
if years < -200 || years > +200 {
|
||||
t.Skip()
|
||||
}
|
||||
// SQLite only tracks milliseconds.
|
||||
if duration != duration.Truncate(time.Millisecond) {
|
||||
t.Skip()
|
||||
}
|
||||
|
||||
if ok {
|
||||
s.Reset()
|
||||
s.BindText(1, str)
|
||||
if !s.Step() {
|
||||
t.Fail()
|
||||
}
|
||||
|
||||
got := epoch.AddDate(years, months, days).Add(duration)
|
||||
|
||||
// Julian day introduces floating point inaccuracy.
|
||||
want := s.ColumnTime(0, sqlite3.TimeFormatJulianDay)
|
||||
want = want.Round(time.Millisecond)
|
||||
if !got.Equal(want) {
|
||||
t.Fatalf("with %q, got %v, want %v", str, got, want)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -59,3 +59,11 @@ const (
|
||||
ALTER_ADD_COLUMN
|
||||
ALTER_DROP_COLUMN
|
||||
)
|
||||
|
||||
type GenType uint32
|
||||
|
||||
const (
|
||||
GENTYPE_NONE GenType = iota
|
||||
GENTYPE_STORED
|
||||
GENTYPE_VIRTUA
|
||||
)
|
||||
|
||||
@@ -125,6 +125,8 @@ type Column struct {
|
||||
DefaultExpr string
|
||||
CollateName string
|
||||
ForeignKeyClause *ForeignKey
|
||||
GeneratedExpr string
|
||||
GeneratedType GenType
|
||||
}
|
||||
|
||||
func (c *Column) load(mod api.Module, ptr uint32, sql string) {
|
||||
@@ -152,6 +154,9 @@ func (c *Column) load(mod api.Module, ptr uint32, sql string) {
|
||||
c.ForeignKeyClause = &ForeignKey{}
|
||||
c.ForeignKeyClause.load(mod, ptr, sql)
|
||||
}
|
||||
|
||||
c.GeneratedExpr = loadString(mod, ptr+88, sql)
|
||||
c.GeneratedType = loadEnum[GenType](mod, ptr+96)
|
||||
}
|
||||
|
||||
type ForeignKey struct {
|
||||
|
||||
@@ -25,8 +25,8 @@ trap 'rm -f sql3parse_table.tmp' EXIT
|
||||
|
||||
"$BINARYEN/wasm-ctor-eval" -c _initialize sql3parse_table.wasm -o sql3parse_table.tmp
|
||||
"$BINARYEN/wasm-opt" sql3parse_table.tmp -o sql3parse_table.wasm \
|
||||
--low-memory-unused --gufa --generate-global-effects --converge -Oz \
|
||||
--gufa --generate-global-effects --converge -Oz \
|
||||
--enable-mutable-globals --enable-nontrapping-float-to-int \
|
||||
--enable-simd --enable-bulk-memory --enable-sign-ext \
|
||||
--enable-reference-types --enable-multivalue \
|
||||
--strip --strip-debug --strip-producers
|
||||
--strip --strip-producers
|
||||
|
||||
@@ -32,6 +32,8 @@ static_assert(offsetof(sql3column, check_expr) == 60, "Unexpected offset");
|
||||
static_assert(offsetof(sql3column, default_expr) == 68, "Unexpected offset");
|
||||
static_assert(offsetof(sql3column, collate_name) == 76, "Unexpected offset");
|
||||
static_assert(offsetof(sql3column, foreignkey_clause) == 84, "Unexpected offset");
|
||||
static_assert(offsetof(sql3column, generated_expr) == 88, "Unexpected offset");
|
||||
static_assert(offsetof(sql3column, generated_type) == 96, "Unexpected offset");
|
||||
|
||||
static_assert(offsetof(sql3foreignkey, table) == 0, "Unexpected offset");
|
||||
static_assert(offsetof(sql3foreignkey, num_columns) == 8, "Unexpected offset");
|
||||
|
||||
Binary file not shown.
@@ -20,11 +20,12 @@ The main differences to be aware of are
|
||||
### File Locking
|
||||
|
||||
POSIX advisory locks,
|
||||
which SQLite uses on [Unix](https://github.com/sqlite/sqlite/blob/5d60f4/src/os_unix.c#L13-L14),
|
||||
are [broken by design](https://github.com/sqlite/sqlite/blob/5d60f4/src/os_unix.c#L1074-L1162).
|
||||
which SQLite uses on [Unix](https://github.com/sqlite/sqlite/blob/41fda52/src/os_unix.c#L13-L14),
|
||||
are [broken by design](https://github.com/sqlite/sqlite/blob/41fda52/src/os_unix.c#L1188-L1276).
|
||||
Instead, on Linux and macOS, this package uses
|
||||
[OFD locks](https://gnu.org/software/libc/manual/html_node/Open-File-Description-Locks.html)
|
||||
[OFD locks](https://sourceware.org/glibc/manual/2.25/html_node/Open-File-Description-Locks.html)
|
||||
to synchronize access to database files.
|
||||
OFD locks require [Linux 3.15](https://lwn.net/Articles/586904/).
|
||||
|
||||
This package can also use
|
||||
[BSD locks](https://man.freebsd.org/cgi/man.cgi?query=flock&sektion=2),
|
||||
@@ -73,7 +74,7 @@ to check if your build supports shared memory.
|
||||
|
||||
### Blocking Locks
|
||||
|
||||
On Windows and macOS, this package implements
|
||||
On macOS, this package implements
|
||||
[Wal-mode blocking locks](https://sqlite.org/src/doc/tip/doc/wal-lock.md).
|
||||
|
||||
### Batch-Atomic Write
|
||||
@@ -116,9 +117,13 @@ The VFS can be customized with a few build tags:
|
||||
|
||||
- [`github.com/ncruces/go-sqlite3/vfs/memdb`](https://pkg.go.dev/github.com/ncruces/go-sqlite3/vfs/memdb)
|
||||
implements an in-memory VFS.
|
||||
- [`github.com/ncruces/go-sqlite3/vfs/mvcc`](https://pkg.go.dev/github.com/ncruces/go-sqlite3/vfs/mvcc)
|
||||
implements an in-memory MVCC VFS.
|
||||
- [`github.com/ncruces/go-sqlite3/vfs/readervfs`](https://pkg.go.dev/github.com/ncruces/go-sqlite3/vfs/readervfs)
|
||||
implements a VFS for immutable databases.
|
||||
- [`github.com/ncruces/go-sqlite3/vfs/adiantum`](https://pkg.go.dev/github.com/ncruces/go-sqlite3/vfs/adiantum)
|
||||
wraps a VFS to offer encryption at rest.
|
||||
- [`github.com/ncruces/go-sqlite3/vfs/xts`](https://pkg.go.dev/github.com/ncruces/go-sqlite3/vfs/xts)
|
||||
wraps a VFS to offer encryption at rest.
|
||||
- [`github.com/ncruces/litestream`](https://pkg.go.dev/github.com/ncruces/litestream)
|
||||
implements Litestream [lightweight read-replicas](https://fly.io/blog/litestream-revamped/#lightweight-read-replicas).
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"crypto/rand"
|
||||
|
||||
"golang.org/x/crypto/argon2"
|
||||
|
||||
"lukechampine.com/adiantum"
|
||||
"lukechampine.com/adiantum/hbsh"
|
||||
)
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
//go:build linux || darwin || windows || freebsd || openbsd || netbsd || dragonfly || illumos || sqlite3_flock || sqlite3_dotlk
|
||||
|
||||
package adiantum_test
|
||||
|
||||
import (
|
||||
@@ -8,6 +6,7 @@ import (
|
||||
"os"
|
||||
|
||||
"golang.org/x/crypto/argon2"
|
||||
|
||||
"lukechampine.com/adiantum/hbsh"
|
||||
"lukechampine.com/adiantum/hpolyc"
|
||||
|
||||
@@ -16,7 +15,7 @@ import (
|
||||
"github.com/ncruces/go-sqlite3/vfs/adiantum"
|
||||
)
|
||||
|
||||
func ExampleRegister_hpolyc() {
|
||||
func Example_hPolyC() {
|
||||
vfs.Register("hpolyc", adiantum.Wrap(vfs.Find(""), hpolycCreator{}))
|
||||
|
||||
db, err := sqlite3.Open("file:demo.db?vfs=hpolyc" +
|
||||
|
||||
@@ -56,7 +56,7 @@ func (h *hbshVFS) OpenFilename(name *vfs.Filename, flags vfs.OpenFlag) (file vfs
|
||||
|
||||
if hbsh == nil {
|
||||
file.Close()
|
||||
return nil, flags, sqlite3.CANTOPEN
|
||||
return nil, flags, sqlite3.IOERR_BADKEY
|
||||
}
|
||||
return &hbshFile{File: file, hbsh: hbsh, init: h.init}, flags, nil
|
||||
}
|
||||
@@ -108,7 +108,7 @@ func (h *hbshFile) Pragma(name string, value string) (string, error) {
|
||||
if h.hbsh = h.init.HBSH(key); h.hbsh != nil {
|
||||
return "ok", nil
|
||||
}
|
||||
return "", sqlite3.CANTOPEN
|
||||
return "", sqlite3.IOERR_BADKEY
|
||||
}
|
||||
|
||||
func (h *hbshFile) ReadAt(p []byte, off int64) (n int, err error) {
|
||||
|
||||
16
vfs/api.go
16
vfs/api.go
@@ -10,7 +10,8 @@ import (
|
||||
|
||||
// A VFS defines the interface between the SQLite core and the underlying operating system.
|
||||
//
|
||||
// Use sqlite3.ErrorCode or sqlite3.ExtendedErrorCode to return specific error codes to SQLite.
|
||||
// Use [SystemError], sqlite3.ErrorCode, or sqlite3.ExtendedErrorCode
|
||||
// to return specific error codes to SQLite.
|
||||
//
|
||||
// https://sqlite.org/c3ref/vfs.html
|
||||
type VFS interface {
|
||||
@@ -31,8 +32,9 @@ type VFSFilename interface {
|
||||
|
||||
// A File represents an open file in the OS interface layer.
|
||||
//
|
||||
// Use sqlite3.ErrorCode or sqlite3.ExtendedErrorCode to return specific error codes to SQLite.
|
||||
// In particular, sqlite3.BUSY is necessary to correctly implement lock methods.
|
||||
// Use [SystemError], sqlite3.ErrorCode, or sqlite3.ExtendedErrorCode
|
||||
// to return specific error codes to SQLite.
|
||||
// In particular, sqlite3.BUSY is needed to correctly implement lock methods.
|
||||
//
|
||||
// https://sqlite.org/c3ref/io_methods.html
|
||||
type File interface {
|
||||
@@ -193,8 +195,8 @@ type FileSharedMemory interface {
|
||||
// SharedMemory is a shared-memory WAL-index implementation.
|
||||
// Use [NewSharedMemory] to create a shared-memory.
|
||||
type SharedMemory interface {
|
||||
shmMap(context.Context, api.Module, int32, int32, bool) (ptr_t, _ErrorCode)
|
||||
shmLock(int32, int32, _ShmFlag) _ErrorCode
|
||||
shmMap(context.Context, api.Module, int32, int32, bool) (ptr_t, error)
|
||||
shmLock(int32, int32, _ShmFlag) error
|
||||
shmUnmap(bool)
|
||||
shmBarrier()
|
||||
io.Closer
|
||||
@@ -205,6 +207,10 @@ type blockingSharedMemory interface {
|
||||
shmEnableBlocking(block bool)
|
||||
}
|
||||
|
||||
// FileControl makes it easy to forward all fileControl methods,
|
||||
// which we want to do for the checksum VFS.
|
||||
// However, this is not a safe default, and other VFSes
|
||||
// should explicitly wrap the methods they want to wrap.
|
||||
type fileControl interface {
|
||||
File
|
||||
fileControl(ctx context.Context, mod api.Module, op _FcntlOpcode, pArg ptr_t) _ErrorCode
|
||||
|
||||
@@ -20,11 +20,11 @@ type (
|
||||
type _ErrorCode uint32
|
||||
|
||||
func (e _ErrorCode) Error() string {
|
||||
return util.ErrorCodeString(uint32(e))
|
||||
return util.ErrorCodeString(e)
|
||||
}
|
||||
|
||||
const (
|
||||
_OK _ErrorCode = util.OK
|
||||
_OK = util.OK
|
||||
_ERROR _ErrorCode = util.ERROR
|
||||
_PERM _ErrorCode = util.PERM
|
||||
_BUSY _ErrorCode = util.BUSY
|
||||
@@ -239,6 +239,7 @@ const (
|
||||
_FCNTL_RESET_CACHE _FcntlOpcode = 42
|
||||
_FCNTL_NULL_IO _FcntlOpcode = 43
|
||||
_FCNTL_BLOCK_ON_CONNECT _FcntlOpcode = 44
|
||||
_FCNTL_FILESTAT _FcntlOpcode = 45
|
||||
)
|
||||
|
||||
// https://sqlite.org/c3ref/c_shm_exclusive.html
|
||||
|
||||
16
vfs/file.go
16
vfs/file.go
@@ -40,7 +40,7 @@ func evalSymlinks(path string) (string, error) {
|
||||
func (vfsOS) Delete(path string, syncDir bool) error {
|
||||
err := os.Remove(path)
|
||||
if errors.Is(err, fs.ErrNotExist) {
|
||||
return _IOERR_DELETE_NOENT
|
||||
return sysError{err, _IOERR_DELETE_NOENT}
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -48,12 +48,12 @@ func (vfsOS) Delete(path string, syncDir bool) error {
|
||||
if isUnix && syncDir {
|
||||
f, err := os.Open(filepath.Dir(path))
|
||||
if err != nil {
|
||||
return _OK
|
||||
return nil
|
||||
}
|
||||
defer f.Close()
|
||||
err = osSync(f, 0, SYNC_FULL)
|
||||
if err != nil {
|
||||
return _IOERR_DIR_FSYNC
|
||||
return sysError{err, _IOERR_DIR_FSYNC}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
@@ -108,14 +108,14 @@ func (vfsOS) OpenFilename(name *Filename, flags OpenFlag) (File, OpenFlag, error
|
||||
}
|
||||
if err != nil {
|
||||
if name == nil {
|
||||
return nil, flags, _IOERR_GETTEMPPATH
|
||||
return nil, flags, sysError{err, _IOERR_GETTEMPPATH}
|
||||
}
|
||||
if errors.Is(err, syscall.EISDIR) {
|
||||
return nil, flags, _CANTOPEN_ISDIR
|
||||
return nil, flags, sysError{err, _CANTOPEN_ISDIR}
|
||||
}
|
||||
if isCreate && isJournl && errors.Is(err, fs.ErrPermission) &&
|
||||
osAccess(name.String(), ACCESS_EXISTS) != nil {
|
||||
return nil, flags, _READONLY_DIRECTORY
|
||||
return nil, flags, sysError{err, _READONLY_DIRECTORY}
|
||||
}
|
||||
return nil, flags, err
|
||||
}
|
||||
@@ -123,7 +123,7 @@ func (vfsOS) OpenFilename(name *Filename, flags OpenFlag) (File, OpenFlag, error
|
||||
if modeof := name.URIParameter("modeof"); modeof != "" {
|
||||
if err = osSetMode(f, modeof); err != nil {
|
||||
f.Close()
|
||||
return nil, flags, _IOERR_FSTAT
|
||||
return nil, flags, sysError{err, _IOERR_FSTAT}
|
||||
}
|
||||
}
|
||||
if isUnix && flags&OPEN_DELETEONCLOSE != 0 {
|
||||
@@ -193,7 +193,7 @@ func (f *vfsFile) Sync(flags SyncFlag) error {
|
||||
defer d.Close()
|
||||
err = osSync(f.File, f.flags, flags)
|
||||
if err != nil {
|
||||
return _IOERR_DIR_FSYNC
|
||||
return sysError{err, _IOERR_DIR_FSYNC}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
||||
@@ -120,7 +120,7 @@ func (n *Filename) URIParameter(key string) string {
|
||||
}
|
||||
|
||||
// Parse the format from:
|
||||
// https://github.com/sqlite/sqlite/blob/b74eb0/src/pager.c#L4797-L4840
|
||||
// https://github.com/sqlite/sqlite/blob/41fda52/src/pager.c#L4821-L4864
|
||||
// This avoids having to alloc/free the key just to find a value.
|
||||
for {
|
||||
k := util.ReadString(n.mod, ptr, _MAX_NAME)
|
||||
@@ -160,7 +160,7 @@ func (n *Filename) URIParameters() url.Values {
|
||||
var params url.Values
|
||||
|
||||
// Parse the format from:
|
||||
// https://github.com/sqlite/sqlite/blob/b74eb0/src/pager.c#L4797-L4840
|
||||
// https://github.com/sqlite/sqlite/blob/41fda52/src/pager.c#L4821-L4864
|
||||
// This is the only way to support multiple valued keys.
|
||||
for {
|
||||
k := util.ReadString(n.mod, ptr, _MAX_NAME)
|
||||
|
||||
20
vfs/lock.go
20
vfs/lock.go
@@ -51,8 +51,8 @@ func (f *vfsFile) Lock(lock LockLevel) error {
|
||||
if f.lock != LOCK_NONE {
|
||||
panic(util.AssertErr())
|
||||
}
|
||||
if rc := osGetSharedLock(f.File); rc != _OK {
|
||||
return rc
|
||||
if err := osGetSharedLock(f.File); err != nil {
|
||||
return err
|
||||
}
|
||||
f.lock = LOCK_SHARED
|
||||
return nil
|
||||
@@ -62,8 +62,8 @@ func (f *vfsFile) Lock(lock LockLevel) error {
|
||||
if f.lock != LOCK_SHARED {
|
||||
panic(util.AssertErr())
|
||||
}
|
||||
if rc := osGetReservedLock(f.File); rc != _OK {
|
||||
return rc
|
||||
if err := osGetReservedLock(f.File); err != nil {
|
||||
return err
|
||||
}
|
||||
f.lock = LOCK_RESERVED
|
||||
return nil
|
||||
@@ -73,8 +73,8 @@ func (f *vfsFile) Lock(lock LockLevel) error {
|
||||
if f.lock <= LOCK_NONE || f.lock >= LOCK_EXCLUSIVE {
|
||||
panic(util.AssertErr())
|
||||
}
|
||||
if rc := osGetExclusiveLock(f.File, &f.lock); rc != _OK {
|
||||
return rc
|
||||
if err := osGetExclusiveLock(f.File, &f.lock); err != nil {
|
||||
return err
|
||||
}
|
||||
f.lock = LOCK_EXCLUSIVE
|
||||
return nil
|
||||
@@ -101,14 +101,14 @@ func (f *vfsFile) Unlock(lock LockLevel) error {
|
||||
|
||||
switch lock {
|
||||
case LOCK_SHARED:
|
||||
rc := osDowngradeLock(f.File, f.lock)
|
||||
err := osDowngradeLock(f.File, f.lock)
|
||||
f.lock = LOCK_SHARED
|
||||
return rc
|
||||
return err
|
||||
|
||||
case LOCK_NONE:
|
||||
rc := osReleaseLock(f.File, f.lock)
|
||||
err := osReleaseLock(f.File, f.lock)
|
||||
f.lock = LOCK_NONE
|
||||
return rc
|
||||
return err
|
||||
|
||||
default:
|
||||
panic(util.AssertErr())
|
||||
|
||||
@@ -34,9 +34,6 @@ var (
|
||||
// The new database takes ownership of data,
|
||||
// and the caller should not use data after this call.
|
||||
func Create(name string, data []byte) {
|
||||
memoryMtx.Lock()
|
||||
defer memoryMtx.Unlock()
|
||||
|
||||
db := &memDB{
|
||||
refs: 1,
|
||||
name: name,
|
||||
@@ -63,14 +60,16 @@ func Create(name string, data []byte) {
|
||||
}
|
||||
}
|
||||
|
||||
memoryMtx.Lock()
|
||||
memoryDBs[name] = db
|
||||
memoryMtx.Unlock()
|
||||
}
|
||||
|
||||
// Delete deletes a shared memory database.
|
||||
func Delete(name string) {
|
||||
memoryMtx.Lock()
|
||||
defer memoryMtx.Unlock()
|
||||
delete(memoryDBs, name)
|
||||
memoryMtx.Unlock()
|
||||
}
|
||||
|
||||
// TestDB creates an empty shared memory database for the test to use.
|
||||
|
||||
@@ -92,10 +92,10 @@ type memDB struct {
|
||||
|
||||
func (m *memDB) release() {
|
||||
memoryMtx.Lock()
|
||||
defer memoryMtx.Unlock()
|
||||
if m.refs--; m.refs == 0 && m == memoryDBs[m.name] {
|
||||
delete(memoryDBs, m.name)
|
||||
}
|
||||
memoryMtx.Unlock()
|
||||
}
|
||||
|
||||
type memFile struct {
|
||||
@@ -223,7 +223,7 @@ func (m *memFile) Lock(lock vfs.LockLevel) error {
|
||||
m.reserved = true
|
||||
|
||||
case vfs.LOCK_EXCLUSIVE:
|
||||
if m.lock < vfs.LOCK_PENDING {
|
||||
if m.lock == vfs.LOCK_RESERVED {
|
||||
m.lock = vfs.LOCK_PENDING
|
||||
m.pending = true
|
||||
}
|
||||
|
||||
@@ -35,13 +35,12 @@ var (
|
||||
// using a snapshot as its initial contents.
|
||||
func Create(name string, snapshot Snapshot) {
|
||||
memoryMtx.Lock()
|
||||
defer memoryMtx.Unlock()
|
||||
|
||||
memoryDBs[name] = &mvccDB{
|
||||
refs: 1,
|
||||
name: name,
|
||||
data: snapshot.Tree,
|
||||
}
|
||||
memoryMtx.Unlock()
|
||||
}
|
||||
|
||||
// Delete deletes a shared memory database.
|
||||
@@ -49,8 +48,8 @@ func Delete(name string) {
|
||||
name = getName(name)
|
||||
|
||||
memoryMtx.Lock()
|
||||
defer memoryMtx.Unlock()
|
||||
delete(memoryDBs, name)
|
||||
memoryMtx.Unlock()
|
||||
}
|
||||
|
||||
// Snapshot represents a database snapshot.
|
||||
@@ -83,8 +82,9 @@ func TakeSnapshot(name string) Snapshot {
|
||||
name = getName(name)
|
||||
|
||||
memoryMtx.Lock()
|
||||
defer memoryMtx.Unlock()
|
||||
db := memoryDBs[name]
|
||||
memoryMtx.Unlock()
|
||||
|
||||
if db == nil {
|
||||
return Snapshot{}
|
||||
}
|
||||
|
||||
@@ -79,10 +79,10 @@ type mvccDB struct {
|
||||
|
||||
func (m *mvccDB) release() {
|
||||
memoryMtx.Lock()
|
||||
defer memoryMtx.Unlock()
|
||||
if m.refs--; m.refs == 0 && m == memoryDBs[m.name] {
|
||||
delete(memoryDBs, m.name)
|
||||
}
|
||||
memoryMtx.Unlock()
|
||||
}
|
||||
|
||||
type mvccFile struct {
|
||||
@@ -90,6 +90,7 @@ type mvccFile struct {
|
||||
data *wbt.Tree[int64, string]
|
||||
lock vfs.LockLevel
|
||||
readOnly bool
|
||||
wrflag bool
|
||||
}
|
||||
|
||||
var (
|
||||
@@ -104,10 +105,10 @@ func (m *mvccFile) Close() error {
|
||||
m.data = nil
|
||||
m.lock = vfs.LOCK_NONE
|
||||
m.mtx.Lock()
|
||||
defer m.mtx.Unlock()
|
||||
if m.owner == m {
|
||||
m.owner = nil
|
||||
}
|
||||
m.mtx.Unlock()
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -212,6 +213,14 @@ func (m *mvccFile) Truncate(size int64) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *mvccFile) Pragma(name, value string) (string, error) {
|
||||
// notest // https://sqlite.org/forum/forumpost/c4ca8e7f4a887aa4
|
||||
if name == "experimental_pragma_20251114" {
|
||||
m.wrflag = true
|
||||
}
|
||||
return "", sqlite3.NOTFOUND
|
||||
}
|
||||
|
||||
func (m *mvccFile) Lock(lock vfs.LockLevel) error {
|
||||
if m.lock >= lock {
|
||||
return nil
|
||||
@@ -225,7 +234,7 @@ func (m *mvccFile) Lock(lock vfs.LockLevel) error {
|
||||
defer m.mtx.Unlock()
|
||||
|
||||
// Take a snapshot of the database.
|
||||
if lock == vfs.LOCK_SHARED {
|
||||
if lock == vfs.LOCK_SHARED && !m.wrflag {
|
||||
m.data = m.mvccDB.data
|
||||
m.lock = lock
|
||||
return nil
|
||||
@@ -243,9 +252,8 @@ func (m *mvccFile) Lock(lock vfs.LockLevel) error {
|
||||
}
|
||||
defer time.AfterFunc(time.Millisecond, m.waiter.Broadcast).Stop()
|
||||
for m.owner != nil {
|
||||
// Our snapshot is invalid.
|
||||
if m.data != m.mvccDB.data {
|
||||
return sqlite3.BUSY_SNAPSHOT
|
||||
if m.data != nil && m.data != m.mvccDB.data {
|
||||
return sqlite3.BUSY_SNAPSHOT // Our snapshot is invalid.
|
||||
}
|
||||
if time.Since(before) > time.Millisecond {
|
||||
return sqlite3.BUSY
|
||||
@@ -253,9 +261,11 @@ func (m *mvccFile) Lock(lock vfs.LockLevel) error {
|
||||
m.waiter.Wait()
|
||||
}
|
||||
}
|
||||
// Our snapshot is invalid.
|
||||
if m.data != m.mvccDB.data {
|
||||
return sqlite3.BUSY_SNAPSHOT
|
||||
switch {
|
||||
case m.data == nil:
|
||||
m.data = m.mvccDB.data
|
||||
case m.data != m.mvccDB.data:
|
||||
return sqlite3.BUSY_SNAPSHOT // Our snapshot is invalid.
|
||||
}
|
||||
// Take ownership.
|
||||
m.lock = lock
|
||||
@@ -264,6 +274,7 @@ func (m *mvccFile) Lock(lock vfs.LockLevel) error {
|
||||
}
|
||||
|
||||
func (m *mvccFile) Unlock(lock vfs.LockLevel) error {
|
||||
m.wrflag = false // SQLite calls unlock even if locking is unsuccessful.
|
||||
if m.lock <= lock {
|
||||
return nil
|
||||
}
|
||||
@@ -274,11 +285,16 @@ func (m *mvccFile) Unlock(lock vfs.LockLevel) error {
|
||||
// Relase ownership, commit changes.
|
||||
if m.owner == m {
|
||||
m.owner = nil
|
||||
m.mvccDB.data = m.data
|
||||
if m.lock == vfs.LOCK_EXCLUSIVE {
|
||||
m.mvccDB.data = m.data
|
||||
}
|
||||
if m.waiter != nil {
|
||||
m.waiter.Broadcast()
|
||||
}
|
||||
}
|
||||
if lock == vfs.LOCK_NONE {
|
||||
m.data = nil
|
||||
}
|
||||
m.lock = lock
|
||||
return nil
|
||||
}
|
||||
@@ -297,8 +313,10 @@ func (m *mvccFile) CommitPhaseTwo() error {
|
||||
// Modified without lock, commit changes.
|
||||
if m.lock > vfs.LOCK_EXCLUSIVE {
|
||||
m.mtx.Lock()
|
||||
defer m.mtx.Unlock()
|
||||
m.mvccDB.data = m.data
|
||||
m.lock = vfs.LOCK_NONE
|
||||
m.data = nil
|
||||
m.mtx.Unlock()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -8,13 +8,13 @@ import (
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
func osGetSharedLock(file *os.File) _ErrorCode {
|
||||
func osGetSharedLock(file *os.File) error {
|
||||
return osFlock(file, unix.LOCK_SH|unix.LOCK_NB, _IOERR_RDLOCK)
|
||||
}
|
||||
|
||||
func osGetReservedLock(file *os.File) _ErrorCode {
|
||||
rc := osFlock(file, unix.LOCK_EX|unix.LOCK_NB, _IOERR_LOCK)
|
||||
if rc == _BUSY {
|
||||
func osGetReservedLock(file *os.File) error {
|
||||
err := osFlock(file, unix.LOCK_EX|unix.LOCK_NB, _IOERR_LOCK)
|
||||
if err == _BUSY {
|
||||
// The documentation states that a lock is upgraded by
|
||||
// releasing the previous lock, then acquiring the new lock.
|
||||
// Going over the source code of various BSDs, though,
|
||||
@@ -26,19 +26,19 @@ func osGetReservedLock(file *os.File) _ErrorCode {
|
||||
// and invoke the busy handler if appropriate.
|
||||
return _BUSY_SNAPSHOT
|
||||
}
|
||||
return rc
|
||||
return err
|
||||
}
|
||||
|
||||
func osGetExclusiveLock(file *os.File, state *LockLevel) _ErrorCode {
|
||||
func osGetExclusiveLock(file *os.File, state *LockLevel) error {
|
||||
if *state >= LOCK_RESERVED {
|
||||
return _OK
|
||||
return nil
|
||||
}
|
||||
return osGetReservedLock(file)
|
||||
}
|
||||
|
||||
func osDowngradeLock(file *os.File, _ LockLevel) _ErrorCode {
|
||||
rc := osFlock(file, unix.LOCK_SH|unix.LOCK_NB, _IOERR_RDLOCK)
|
||||
if rc == _BUSY {
|
||||
func osDowngradeLock(file *os.File, _ LockLevel) error {
|
||||
err := osFlock(file, unix.LOCK_SH|unix.LOCK_NB, _IOERR_RDLOCK)
|
||||
if err == _BUSY {
|
||||
// The documentation states that a lock is downgraded by
|
||||
// releasing the previous lock then acquiring the new lock.
|
||||
// Going over the source code of various BSDs, though,
|
||||
@@ -46,44 +46,44 @@ func osDowngradeLock(file *os.File, _ LockLevel) _ErrorCode {
|
||||
// Return IOERR_RDLOCK, as BUSY would cause an assert to fail.
|
||||
return _IOERR_RDLOCK
|
||||
}
|
||||
return _OK
|
||||
return err
|
||||
}
|
||||
|
||||
func osReleaseLock(file *os.File, _ LockLevel) _ErrorCode {
|
||||
func osReleaseLock(file *os.File, _ LockLevel) error {
|
||||
for {
|
||||
err := unix.Flock(int(file.Fd()), unix.LOCK_UN)
|
||||
if err == nil {
|
||||
return _OK
|
||||
return nil
|
||||
}
|
||||
if err != unix.EINTR {
|
||||
return _IOERR_UNLOCK
|
||||
return sysError{err, _IOERR_UNLOCK}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func osCheckReservedLock(file *os.File) (bool, _ErrorCode) {
|
||||
func osCheckReservedLock(file *os.File) (bool, error) {
|
||||
// Test the RESERVED lock with fcntl(F_GETLK).
|
||||
// This only works on systems where fcntl and flock are compatible.
|
||||
// However, SQLite only calls this while holding a shared lock,
|
||||
// so the difference is immaterial.
|
||||
lock, rc := osTestLock(file, _RESERVED_BYTE, 1)
|
||||
return lock == unix.F_WRLCK, rc
|
||||
lock, err := osTestLock(file, _RESERVED_BYTE, 1, _IOERR_CHECKRESERVEDLOCK)
|
||||
return lock == unix.F_WRLCK, err
|
||||
}
|
||||
|
||||
func osFlock(file *os.File, how int, def _ErrorCode) _ErrorCode {
|
||||
func osFlock(file *os.File, how int, def _ErrorCode) error {
|
||||
err := unix.Flock(int(file.Fd()), how)
|
||||
return osLockErrorCode(err, def)
|
||||
}
|
||||
|
||||
func osReadLock(file *os.File, start, len int64) _ErrorCode {
|
||||
func osReadLock(file *os.File, start, len int64) error {
|
||||
return osLock(file, unix.F_RDLCK, start, len, _IOERR_RDLOCK)
|
||||
}
|
||||
|
||||
func osWriteLock(file *os.File, start, len int64) _ErrorCode {
|
||||
func osWriteLock(file *os.File, start, len int64) error {
|
||||
return osLock(file, unix.F_WRLCK, start, len, _IOERR_LOCK)
|
||||
}
|
||||
|
||||
func osLock(file *os.File, typ int16, start, len int64, def _ErrorCode) _ErrorCode {
|
||||
func osLock(file *os.File, typ int16, start, len int64, def _ErrorCode) error {
|
||||
err := unix.FcntlFlock(file.Fd(), unix.F_SETLK, &unix.Flock_t{
|
||||
Type: typ,
|
||||
Start: start,
|
||||
@@ -92,7 +92,7 @@ func osLock(file *os.File, typ int16, start, len int64, def _ErrorCode) _ErrorCo
|
||||
return osLockErrorCode(err, def)
|
||||
}
|
||||
|
||||
func osUnlock(file *os.File, start, len int64) _ErrorCode {
|
||||
func osUnlock(file *os.File, start, len int64) error {
|
||||
lock := unix.Flock_t{
|
||||
Type: unix.F_UNLCK,
|
||||
Start: start,
|
||||
@@ -101,10 +101,10 @@ func osUnlock(file *os.File, start, len int64) _ErrorCode {
|
||||
for {
|
||||
err := unix.FcntlFlock(file.Fd(), unix.F_SETLK, &lock)
|
||||
if err == nil {
|
||||
return _OK
|
||||
return nil
|
||||
}
|
||||
if err != unix.EINTR {
|
||||
return _IOERR_UNLOCK
|
||||
return sysError{err, _IOERR_UNLOCK}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -75,15 +75,15 @@ func osAllocate(file *os.File, size int64) error {
|
||||
return file.Truncate(size)
|
||||
}
|
||||
|
||||
func osReadLock(file *os.File, start, len int64, timeout time.Duration) _ErrorCode {
|
||||
func osReadLock(file *os.File, start, len int64, timeout time.Duration) error {
|
||||
return osLock(file, unix.F_RDLCK, start, len, timeout, _IOERR_RDLOCK)
|
||||
}
|
||||
|
||||
func osWriteLock(file *os.File, start, len int64, timeout time.Duration) _ErrorCode {
|
||||
func osWriteLock(file *os.File, start, len int64, timeout time.Duration) error {
|
||||
return osLock(file, unix.F_WRLCK, start, len, timeout, _IOERR_LOCK)
|
||||
}
|
||||
|
||||
func osLock(file *os.File, typ int16, start, len int64, timeout time.Duration, def _ErrorCode) _ErrorCode {
|
||||
func osLock(file *os.File, typ int16, start, len int64, timeout time.Duration, def _ErrorCode) error {
|
||||
lock := &flocktimeout_t{fl: unix.Flock_t{
|
||||
Type: typ,
|
||||
Start: start,
|
||||
@@ -103,7 +103,7 @@ func osLock(file *os.File, typ int16, start, len int64, timeout time.Duration, d
|
||||
return osLockErrorCode(err, def)
|
||||
}
|
||||
|
||||
func osUnlock(file *os.File, start, len int64) _ErrorCode {
|
||||
func osUnlock(file *os.File, start, len int64) error {
|
||||
lock := unix.Flock_t{
|
||||
Type: unix.F_UNLCK,
|
||||
Start: start,
|
||||
@@ -112,10 +112,10 @@ func osUnlock(file *os.File, start, len int64) _ErrorCode {
|
||||
for {
|
||||
err := unix.FcntlFlock(file.Fd(), _F_OFD_SETLK, &lock)
|
||||
if err == nil {
|
||||
return _OK
|
||||
return nil
|
||||
}
|
||||
if err != unix.EINTR {
|
||||
return _IOERR_UNLOCK
|
||||
return sysError{err, _IOERR_UNLOCK}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ type vfsDotLocker struct {
|
||||
reserved *os.File // +checklocks:vfsDotLocksMtx
|
||||
}
|
||||
|
||||
func osGetSharedLock(file *os.File) _ErrorCode {
|
||||
func osGetSharedLock(file *os.File) error {
|
||||
vfsDotLocksMtx.Lock()
|
||||
defer vfsDotLocksMtx.Unlock()
|
||||
|
||||
@@ -34,7 +34,7 @@ func osGetSharedLock(file *os.File) _ErrorCode {
|
||||
if errors.Is(err, fs.ErrExist) {
|
||||
return _BUSY // Another process has the lock.
|
||||
}
|
||||
return _IOERR_LOCK
|
||||
return sysError{err, _IOERR_LOCK}
|
||||
}
|
||||
locker = &vfsDotLocker{}
|
||||
vfsDotLocks[name] = locker
|
||||
@@ -44,10 +44,10 @@ func osGetSharedLock(file *os.File) _ErrorCode {
|
||||
return _BUSY
|
||||
}
|
||||
locker.shared++
|
||||
return _OK
|
||||
return nil
|
||||
}
|
||||
|
||||
func osGetReservedLock(file *os.File) _ErrorCode {
|
||||
func osGetReservedLock(file *os.File) error {
|
||||
vfsDotLocksMtx.Lock()
|
||||
defer vfsDotLocksMtx.Unlock()
|
||||
|
||||
@@ -61,10 +61,10 @@ func osGetReservedLock(file *os.File) _ErrorCode {
|
||||
return _BUSY
|
||||
}
|
||||
locker.reserved = file
|
||||
return _OK
|
||||
return nil
|
||||
}
|
||||
|
||||
func osGetExclusiveLock(file *os.File, _ *LockLevel) _ErrorCode {
|
||||
func osGetExclusiveLock(file *os.File, _ *LockLevel) error {
|
||||
vfsDotLocksMtx.Lock()
|
||||
defer vfsDotLocksMtx.Unlock()
|
||||
|
||||
@@ -81,10 +81,10 @@ func osGetExclusiveLock(file *os.File, _ *LockLevel) _ErrorCode {
|
||||
if locker.shared > 1 {
|
||||
return _BUSY
|
||||
}
|
||||
return _OK
|
||||
return nil
|
||||
}
|
||||
|
||||
func osDowngradeLock(file *os.File, _ LockLevel) _ErrorCode {
|
||||
func osDowngradeLock(file *os.File, _ LockLevel) error {
|
||||
vfsDotLocksMtx.Lock()
|
||||
defer vfsDotLocksMtx.Unlock()
|
||||
|
||||
@@ -100,10 +100,10 @@ func osDowngradeLock(file *os.File, _ LockLevel) _ErrorCode {
|
||||
if locker.pending == file {
|
||||
locker.pending = nil
|
||||
}
|
||||
return _OK
|
||||
return nil
|
||||
}
|
||||
|
||||
func osReleaseLock(file *os.File, state LockLevel) _ErrorCode {
|
||||
func osReleaseLock(file *os.File, state LockLevel) error {
|
||||
vfsDotLocksMtx.Lock()
|
||||
defer vfsDotLocksMtx.Unlock()
|
||||
|
||||
@@ -115,7 +115,7 @@ func osReleaseLock(file *os.File, state LockLevel) _ErrorCode {
|
||||
|
||||
if locker.shared == 1 {
|
||||
if err := dotlk.Unlock(name + ".lock"); err != nil {
|
||||
return _IOERR_UNLOCK
|
||||
return sysError{err, _IOERR_UNLOCK}
|
||||
}
|
||||
delete(vfsDotLocks, name)
|
||||
}
|
||||
@@ -127,17 +127,14 @@ func osReleaseLock(file *os.File, state LockLevel) _ErrorCode {
|
||||
locker.pending = nil
|
||||
}
|
||||
locker.shared--
|
||||
return _OK
|
||||
return nil
|
||||
}
|
||||
|
||||
func osCheckReservedLock(file *os.File) (bool, _ErrorCode) {
|
||||
func osCheckReservedLock(file *os.File) (bool, error) {
|
||||
vfsDotLocksMtx.Lock()
|
||||
defer vfsDotLocksMtx.Unlock()
|
||||
|
||||
name := file.Name()
|
||||
locker := vfsDotLocks[name]
|
||||
if locker == nil {
|
||||
return false, _OK
|
||||
}
|
||||
return locker.reserved != nil, _OK
|
||||
return locker != nil && locker.reserved != nil, nil
|
||||
}
|
||||
|
||||
@@ -44,15 +44,15 @@ func osAllocate(file *os.File, size int64) error {
|
||||
|
||||
}
|
||||
|
||||
func osReadLock(file *os.File, start, len int64, timeout time.Duration) _ErrorCode {
|
||||
func osReadLock(file *os.File, start, len int64, timeout time.Duration) error {
|
||||
return osLock(file, unix.F_RDLCK, start, len, timeout, _IOERR_RDLOCK)
|
||||
}
|
||||
|
||||
func osWriteLock(file *os.File, start, len int64, timeout time.Duration) _ErrorCode {
|
||||
func osWriteLock(file *os.File, start, len int64, timeout time.Duration) error {
|
||||
return osLock(file, unix.F_WRLCK, start, len, timeout, _IOERR_LOCK)
|
||||
}
|
||||
|
||||
func osLock(file *os.File, typ int16, start, len int64, timeout time.Duration, def _ErrorCode) _ErrorCode {
|
||||
func osLock(file *os.File, typ int16, start, len int64, timeout time.Duration, def _ErrorCode) error {
|
||||
lock := unix.Flock_t{
|
||||
Type: typ,
|
||||
Start: start,
|
||||
@@ -68,7 +68,7 @@ func osLock(file *os.File, typ int16, start, len int64, timeout time.Duration, d
|
||||
return osLockErrorCode(err, def)
|
||||
}
|
||||
|
||||
func osUnlock(file *os.File, start, len int64) _ErrorCode {
|
||||
func osUnlock(file *os.File, start, len int64) error {
|
||||
lock := unix.Flock_t{
|
||||
Type: unix.F_UNLCK,
|
||||
Start: start,
|
||||
@@ -77,10 +77,10 @@ func osUnlock(file *os.File, start, len int64) _ErrorCode {
|
||||
for {
|
||||
err := unix.FcntlFlock(file.Fd(), unix.F_OFD_SETLK, &lock)
|
||||
if err == nil {
|
||||
return _OK
|
||||
return nil
|
||||
}
|
||||
if err != unix.EINTR {
|
||||
return _IOERR_UNLOCK
|
||||
return sysError{err, _IOERR_UNLOCK}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,25 +9,25 @@ import (
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
func osGetSharedLock(file *os.File) _ErrorCode {
|
||||
func osGetSharedLock(file *os.File) error {
|
||||
// Test the PENDING lock before acquiring a new SHARED lock.
|
||||
if lock, _ := osTestLock(file, _PENDING_BYTE, 1); lock == unix.F_WRLCK {
|
||||
if lock, _ := osTestLock(file, _PENDING_BYTE, 1, _IOERR); lock == unix.F_WRLCK {
|
||||
return _BUSY
|
||||
}
|
||||
// Acquire the SHARED lock.
|
||||
return osReadLock(file, _SHARED_FIRST, _SHARED_SIZE, 0)
|
||||
}
|
||||
|
||||
func osGetReservedLock(file *os.File) _ErrorCode {
|
||||
func osGetReservedLock(file *os.File) error {
|
||||
// Acquire the RESERVED lock.
|
||||
return osWriteLock(file, _RESERVED_BYTE, 1, 0)
|
||||
}
|
||||
|
||||
func osGetExclusiveLock(file *os.File, state *LockLevel) _ErrorCode {
|
||||
func osGetExclusiveLock(file *os.File, state *LockLevel) error {
|
||||
if *state == LOCK_RESERVED {
|
||||
// A PENDING lock is needed before acquiring an EXCLUSIVE lock.
|
||||
if rc := osWriteLock(file, _PENDING_BYTE, 1, -1); rc != _OK {
|
||||
return rc
|
||||
if err := osWriteLock(file, _PENDING_BYTE, 1, -1); err != nil {
|
||||
return err
|
||||
}
|
||||
*state = LOCK_PENDING
|
||||
}
|
||||
@@ -35,10 +35,10 @@ func osGetExclusiveLock(file *os.File, state *LockLevel) _ErrorCode {
|
||||
return osWriteLock(file, _SHARED_FIRST, _SHARED_SIZE, time.Millisecond)
|
||||
}
|
||||
|
||||
func osDowngradeLock(file *os.File, state LockLevel) _ErrorCode {
|
||||
func osDowngradeLock(file *os.File, state LockLevel) error {
|
||||
if state >= LOCK_EXCLUSIVE {
|
||||
// Downgrade to a SHARED lock.
|
||||
if rc := osReadLock(file, _SHARED_FIRST, _SHARED_SIZE, 0); rc != _OK {
|
||||
if err := osReadLock(file, _SHARED_FIRST, _SHARED_SIZE, 0); err != nil {
|
||||
// notest // this should never happen
|
||||
return _IOERR_RDLOCK
|
||||
}
|
||||
@@ -47,13 +47,13 @@ func osDowngradeLock(file *os.File, state LockLevel) _ErrorCode {
|
||||
return osUnlock(file, _PENDING_BYTE, 2)
|
||||
}
|
||||
|
||||
func osReleaseLock(file *os.File, _ LockLevel) _ErrorCode {
|
||||
func osReleaseLock(file *os.File, _ LockLevel) error {
|
||||
// Release all locks.
|
||||
return osUnlock(file, 0, 0)
|
||||
}
|
||||
|
||||
func osCheckReservedLock(file *os.File) (bool, _ErrorCode) {
|
||||
func osCheckReservedLock(file *os.File) (bool, error) {
|
||||
// Test the RESERVED lock.
|
||||
lock, rc := osTestLock(file, _RESERVED_BYTE, 1)
|
||||
return lock == unix.F_WRLCK, rc
|
||||
lock, err := osTestLock(file, _RESERVED_BYTE, 1, _IOERR_CHECKRESERVEDLOCK)
|
||||
return lock == unix.F_WRLCK, err
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@ func osReadAt(file *os.File, p []byte, off int64) (int, error) {
|
||||
unix.ERANGE,
|
||||
unix.EIO,
|
||||
unix.ENXIO:
|
||||
return n, _IOERR_CORRUPTFS
|
||||
return n, sysError{err, _IOERR_CORRUPTFS}
|
||||
}
|
||||
}
|
||||
return n, err
|
||||
@@ -42,7 +42,7 @@ func osReadAt(file *os.File, p []byte, off int64) (int, error) {
|
||||
func osWriteAt(file *os.File, p []byte, off int64) (int, error) {
|
||||
n, err := file.WriteAt(p, off)
|
||||
if errno, ok := err.(unix.Errno); ok && errno == unix.ENOSPC {
|
||||
return n, _FULL
|
||||
return n, sysError{err, _FULL}
|
||||
}
|
||||
return n, err
|
||||
}
|
||||
@@ -59,7 +59,7 @@ func osSetMode(file *os.File, modeof string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func osTestLock(file *os.File, start, len int64) (int16, _ErrorCode) {
|
||||
func osTestLock(file *os.File, start, len int64, def _ErrorCode) (int16, error) {
|
||||
lock := unix.Flock_t{
|
||||
Type: unix.F_WRLCK,
|
||||
Start: start,
|
||||
@@ -68,17 +68,17 @@ func osTestLock(file *os.File, start, len int64) (int16, _ErrorCode) {
|
||||
for {
|
||||
err := unix.FcntlFlock(file.Fd(), unix.F_GETLK, &lock)
|
||||
if err == nil {
|
||||
return lock.Type, _OK
|
||||
return lock.Type, nil
|
||||
}
|
||||
if err != unix.EINTR {
|
||||
return 0, _IOERR_CHECKRESERVEDLOCK
|
||||
return 0, sysError{err, def}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func osLockErrorCode(err error, def _ErrorCode) _ErrorCode {
|
||||
func osLockErrorCode(err error, def _ErrorCode) error {
|
||||
if err == nil {
|
||||
return _OK
|
||||
return nil
|
||||
}
|
||||
if errno, ok := err.(unix.Errno); ok {
|
||||
switch errno {
|
||||
@@ -92,12 +92,12 @@ func osLockErrorCode(err error, def _ErrorCode) _ErrorCode {
|
||||
unix.ETIMEDOUT:
|
||||
return _BUSY
|
||||
case unix.EPERM:
|
||||
return _PERM
|
||||
return sysError{err, _PERM}
|
||||
}
|
||||
// notest // usually EWOULDBLOCK == EAGAIN
|
||||
if errno == unix.EWOULDBLOCK && unix.EWOULDBLOCK != unix.EAGAIN {
|
||||
return _BUSY
|
||||
}
|
||||
}
|
||||
return def
|
||||
return sysError{err, def}
|
||||
}
|
||||
|
||||
@@ -20,31 +20,31 @@ func osWriteAt(file *os.File, p []byte, off int64) (int, error) {
|
||||
case
|
||||
windows.ERROR_HANDLE_DISK_FULL,
|
||||
windows.ERROR_DISK_FULL:
|
||||
return n, _FULL
|
||||
return n, sysError{err, _FULL}
|
||||
}
|
||||
}
|
||||
return n, err
|
||||
}
|
||||
|
||||
func osGetSharedLock(file *os.File) _ErrorCode {
|
||||
func osGetSharedLock(file *os.File) error {
|
||||
// Acquire the PENDING lock temporarily before acquiring a new SHARED lock.
|
||||
rc := osReadLock(file, _PENDING_BYTE, 1, 0)
|
||||
if rc == _OK {
|
||||
err := osReadLock(file, _PENDING_BYTE, 1, 0)
|
||||
if err == nil {
|
||||
// Acquire the SHARED lock.
|
||||
rc = osReadLock(file, _SHARED_FIRST, _SHARED_SIZE, 0)
|
||||
err = osReadLock(file, _SHARED_FIRST, _SHARED_SIZE, 0)
|
||||
|
||||
// Release the PENDING lock.
|
||||
osUnlock(file, _PENDING_BYTE, 1)
|
||||
}
|
||||
return rc
|
||||
return err
|
||||
}
|
||||
|
||||
func osGetReservedLock(file *os.File) _ErrorCode {
|
||||
func osGetReservedLock(file *os.File) error {
|
||||
// Acquire the RESERVED lock.
|
||||
return osWriteLock(file, _RESERVED_BYTE, 1, 0)
|
||||
}
|
||||
|
||||
func osGetExclusiveLock(file *os.File, state *LockLevel) _ErrorCode {
|
||||
func osGetExclusiveLock(file *os.File, state *LockLevel) error {
|
||||
// A PENDING lock is needed before releasing the SHARED lock.
|
||||
if *state < LOCK_PENDING {
|
||||
// If we were RESERVED, we can block indefinitely.
|
||||
@@ -52,8 +52,8 @@ func osGetExclusiveLock(file *os.File, state *LockLevel) _ErrorCode {
|
||||
if *state == LOCK_RESERVED {
|
||||
timeout = -1
|
||||
}
|
||||
if rc := osWriteLock(file, _PENDING_BYTE, 1, timeout); rc != _OK {
|
||||
return rc
|
||||
if err := osWriteLock(file, _PENDING_BYTE, 1, timeout); err != nil {
|
||||
return err
|
||||
}
|
||||
*state = LOCK_PENDING
|
||||
}
|
||||
@@ -63,25 +63,25 @@ func osGetExclusiveLock(file *os.File, state *LockLevel) _ErrorCode {
|
||||
|
||||
// Acquire the EXCLUSIVE lock.
|
||||
// Can't wait here, because the file is not OVERLAPPED.
|
||||
rc := osWriteLock(file, _SHARED_FIRST, _SHARED_SIZE, 0)
|
||||
err := osWriteLock(file, _SHARED_FIRST, _SHARED_SIZE, 0)
|
||||
|
||||
if rc != _OK {
|
||||
if err != nil {
|
||||
// Reacquire the SHARED lock.
|
||||
if rc := osReadLock(file, _SHARED_FIRST, _SHARED_SIZE, 0); rc != _OK {
|
||||
if err := osReadLock(file, _SHARED_FIRST, _SHARED_SIZE, 0); err != nil {
|
||||
// notest // this should never happen
|
||||
return _IOERR_RDLOCK
|
||||
}
|
||||
}
|
||||
return rc
|
||||
return err
|
||||
}
|
||||
|
||||
func osDowngradeLock(file *os.File, state LockLevel) _ErrorCode {
|
||||
func osDowngradeLock(file *os.File, state LockLevel) error {
|
||||
if state >= LOCK_EXCLUSIVE {
|
||||
// Release the EXCLUSIVE lock while holding the PENDING lock.
|
||||
osUnlock(file, _SHARED_FIRST, _SHARED_SIZE)
|
||||
|
||||
// Reacquire the SHARED lock.
|
||||
if rc := osReadLock(file, _SHARED_FIRST, _SHARED_SIZE, 0); rc != _OK {
|
||||
if err := osReadLock(file, _SHARED_FIRST, _SHARED_SIZE, 0); err != nil {
|
||||
// notest // this should never happen
|
||||
return _IOERR_RDLOCK
|
||||
}
|
||||
@@ -94,10 +94,10 @@ func osDowngradeLock(file *os.File, state LockLevel) _ErrorCode {
|
||||
if state >= LOCK_PENDING {
|
||||
osUnlock(file, _PENDING_BYTE, 1)
|
||||
}
|
||||
return _OK
|
||||
return nil
|
||||
}
|
||||
|
||||
func osReleaseLock(file *os.File, state LockLevel) _ErrorCode {
|
||||
func osReleaseLock(file *os.File, state LockLevel) error {
|
||||
// Release all locks, PENDING must be last.
|
||||
if state >= LOCK_RESERVED {
|
||||
osUnlock(file, _RESERVED_BYTE, 1)
|
||||
@@ -108,31 +108,32 @@ func osReleaseLock(file *os.File, state LockLevel) _ErrorCode {
|
||||
if state >= LOCK_PENDING {
|
||||
osUnlock(file, _PENDING_BYTE, 1)
|
||||
}
|
||||
return _OK
|
||||
return nil
|
||||
}
|
||||
|
||||
func osCheckReservedLock(file *os.File) (bool, _ErrorCode) {
|
||||
func osCheckReservedLock(file *os.File) (bool, error) {
|
||||
// Test the RESERVED lock.
|
||||
rc := osLock(file, 0, _RESERVED_BYTE, 1, 0, _IOERR_CHECKRESERVEDLOCK)
|
||||
if rc == _BUSY {
|
||||
return true, _OK
|
||||
err := osLock(file, 0, _RESERVED_BYTE, 1, 0, _IOERR_CHECKRESERVEDLOCK)
|
||||
if err == _BUSY {
|
||||
return true, nil
|
||||
}
|
||||
if rc == _OK {
|
||||
if err == nil {
|
||||
// Release the RESERVED lock.
|
||||
osUnlock(file, _RESERVED_BYTE, 1)
|
||||
return false, nil
|
||||
}
|
||||
return false, rc
|
||||
return false, err
|
||||
}
|
||||
|
||||
func osReadLock(file *os.File, start, len uint32, timeout time.Duration) _ErrorCode {
|
||||
func osReadLock(file *os.File, start, len uint32, timeout time.Duration) error {
|
||||
return osLock(file, 0, start, len, timeout, _IOERR_RDLOCK)
|
||||
}
|
||||
|
||||
func osWriteLock(file *os.File, start, len uint32, timeout time.Duration) _ErrorCode {
|
||||
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 osLock(file *os.File, flags, start, len uint32, timeout time.Duration, def _ErrorCode) _ErrorCode {
|
||||
func osLock(file *os.File, flags, start, len uint32, timeout time.Duration, def _ErrorCode) error {
|
||||
var err error
|
||||
switch {
|
||||
default:
|
||||
@@ -143,16 +144,16 @@ func osLock(file *os.File, flags, start, len uint32, timeout time.Duration, def
|
||||
return osLockErrorCode(err, def)
|
||||
}
|
||||
|
||||
func osUnlock(file *os.File, start, len uint32) _ErrorCode {
|
||||
func osUnlock(file *os.File, start, len uint32) error {
|
||||
err := windows.UnlockFileEx(windows.Handle(file.Fd()),
|
||||
0, len, 0, &windows.Overlapped{Offset: start})
|
||||
if err == windows.ERROR_NOT_LOCKED {
|
||||
return _OK
|
||||
return nil
|
||||
}
|
||||
if err != nil {
|
||||
return _IOERR_UNLOCK
|
||||
return sysError{err, _IOERR_UNLOCK}
|
||||
}
|
||||
return _OK
|
||||
return nil
|
||||
}
|
||||
|
||||
func osLockEx(file *os.File, flags, start, len uint32) error {
|
||||
@@ -160,9 +161,9 @@ func osLockEx(file *os.File, flags, start, len uint32) error {
|
||||
0, len, 0, &windows.Overlapped{Offset: start})
|
||||
}
|
||||
|
||||
func osLockErrorCode(err error, def _ErrorCode) _ErrorCode {
|
||||
func osLockErrorCode(err error, def _ErrorCode) error {
|
||||
if err == nil {
|
||||
return _OK
|
||||
return nil
|
||||
}
|
||||
if errno, ok := err.(windows.Errno); ok {
|
||||
// https://devblogs.microsoft.com/oldnewthing/20140905-00/?p=63
|
||||
@@ -175,5 +176,5 @@ func osLockErrorCode(err error, def _ErrorCode) _ErrorCode {
|
||||
return _BUSY
|
||||
}
|
||||
}
|
||||
return def
|
||||
return sysError{err, def}
|
||||
}
|
||||
|
||||
@@ -30,13 +30,13 @@ var (
|
||||
// otherwise SQLite might return incorrect query results and/or [sqlite3.CORRUPT] errors.
|
||||
func Create(name string, reader ioutil.SizeReaderAt) {
|
||||
readerMtx.Lock()
|
||||
defer readerMtx.Unlock()
|
||||
readerDBs[name] = reader
|
||||
readerMtx.Unlock()
|
||||
}
|
||||
|
||||
// Delete deletes a shared memory database.
|
||||
func Delete(name string) {
|
||||
readerMtx.Lock()
|
||||
defer readerMtx.Unlock()
|
||||
delete(readerDBs, name)
|
||||
readerMtx.Unlock()
|
||||
}
|
||||
|
||||
@@ -3,16 +3,15 @@ package readervfs
|
||||
import (
|
||||
"github.com/ncruces/go-sqlite3"
|
||||
"github.com/ncruces/go-sqlite3/util/ioutil"
|
||||
"github.com/ncruces/go-sqlite3/util/vfsutil"
|
||||
"github.com/ncruces/go-sqlite3/vfs"
|
||||
)
|
||||
|
||||
type readerVFS struct{}
|
||||
|
||||
func (readerVFS) Open(name string, flags vfs.OpenFlag) (vfs.File, vfs.OpenFlag, error) {
|
||||
// Temp journals, as used by the sorter, use SliceFile.
|
||||
if flags&vfs.OPEN_TEMP_JOURNAL != 0 {
|
||||
return &vfsutil.SliceFile{}, flags | vfs.OPEN_MEMORY, nil
|
||||
// Temporary files use the default VFS.
|
||||
if name == "" || flags&vfs.OPEN_DELETEONCLOSE != 0 {
|
||||
return vfs.Find("").Open(name, flags)
|
||||
}
|
||||
// Refuse to open all other file types.
|
||||
if flags&vfs.OPEN_MAIN_DB == 0 {
|
||||
|
||||
@@ -10,13 +10,17 @@ var (
|
||||
|
||||
// Find returns a VFS given its name.
|
||||
// If there is no match, nil is returned.
|
||||
// If name is empty, the default VFS is returned.
|
||||
// If name is empty or "os", the default VFS is returned.
|
||||
//
|
||||
// https://sqlite.org/c3ref/vfs_find.html
|
||||
func Find(name string) VFS {
|
||||
if name == "" || name == "os" {
|
||||
return vfsOS{}
|
||||
}
|
||||
return find(name)
|
||||
}
|
||||
|
||||
func find(name string) VFS {
|
||||
vfsRegistryMtx.RLock()
|
||||
defer vfsRegistryMtx.RUnlock()
|
||||
return vfsRegistry[name]
|
||||
@@ -31,11 +35,11 @@ func Register(name string, vfs VFS) {
|
||||
return
|
||||
}
|
||||
vfsRegistryMtx.Lock()
|
||||
defer vfsRegistryMtx.Unlock()
|
||||
if vfsRegistry == nil {
|
||||
vfsRegistry = map[string]VFS{}
|
||||
}
|
||||
vfsRegistry[name] = vfs
|
||||
vfsRegistryMtx.Unlock()
|
||||
}
|
||||
|
||||
// Unregister unregisters a VFS.
|
||||
@@ -43,6 +47,6 @@ func Register(name string, vfs VFS) {
|
||||
// https://sqlite.org/c3ref/vfs_find.html
|
||||
func Unregister(name string) {
|
||||
vfsRegistryMtx.Lock()
|
||||
defer vfsRegistryMtx.Unlock()
|
||||
delete(vfsRegistry, name)
|
||||
vfsRegistryMtx.Unlock()
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//go:build ((linux || darwin || windows || freebsd || openbsd || netbsd || dragonfly || illumos) && (386 || arm || amd64 || arm64 || riscv64 || ppc64le)) || sqlite3_flock || sqlite3_dotlk
|
||||
//go:build ((linux || darwin || windows || freebsd || openbsd || netbsd || dragonfly || illumos) && (386 || arm || amd64 || arm64 || riscv64 || ppc64le || loong64)) || sqlite3_flock || sqlite3_dotlk
|
||||
|
||||
package vfs
|
||||
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
//go:build ((freebsd || openbsd || netbsd || dragonfly || illumos) && (386 || arm || amd64 || arm64 || riscv64 || ppc64le) && !sqlite3_dotlk) || sqlite3_flock
|
||||
//go:build ((freebsd || openbsd || netbsd || dragonfly || illumos) && (386 || arm || amd64 || arm64 || riscv64 || ppc64le || loong64) && !sqlite3_dotlk) || sqlite3_flock
|
||||
|
||||
package vfs
|
||||
|
||||
import (
|
||||
"cmp"
|
||||
"context"
|
||||
"errors"
|
||||
"io"
|
||||
@@ -10,9 +11,10 @@ import (
|
||||
"os"
|
||||
"sync"
|
||||
|
||||
"github.com/tetratelabs/wazero/api"
|
||||
"golang.org/x/sys/unix"
|
||||
|
||||
"github.com/tetratelabs/wazero/api"
|
||||
|
||||
"github.com/ncruces/go-sqlite3/internal/util"
|
||||
)
|
||||
|
||||
@@ -68,9 +70,9 @@ func (s *vfsShm) Close() error {
|
||||
panic(util.AssertErr())
|
||||
}
|
||||
|
||||
func (s *vfsShm) shmOpen() (rc _ErrorCode) {
|
||||
func (s *vfsShm) shmOpen() (err error) {
|
||||
if s.vfsShmParent != nil {
|
||||
return _OK
|
||||
return nil
|
||||
}
|
||||
|
||||
vfsShmListMtx.Lock()
|
||||
@@ -80,7 +82,7 @@ func (s *vfsShm) shmOpen() (rc _ErrorCode) {
|
||||
// Closing it would release all POSIX locks on it.
|
||||
fi, err := os.Stat(s.path)
|
||||
if err != nil && !errors.Is(err, fs.ErrNotExist) {
|
||||
return _IOERR_FSTAT
|
||||
return sysError{err, _IOERR_FSTAT}
|
||||
}
|
||||
|
||||
// Find a shared file, increase the reference count.
|
||||
@@ -88,7 +90,7 @@ func (s *vfsShm) shmOpen() (rc _ErrorCode) {
|
||||
if g != nil && os.SameFile(fi, g.info) {
|
||||
s.vfsShmParent = g
|
||||
g.refs++
|
||||
return _OK
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -96,34 +98,34 @@ func (s *vfsShm) shmOpen() (rc _ErrorCode) {
|
||||
f, err := os.OpenFile(s.path,
|
||||
os.O_RDWR|os.O_CREATE|_O_NOFOLLOW, 0666)
|
||||
if err != nil {
|
||||
return _CANTOPEN
|
||||
return sysError{err, _CANTOPEN}
|
||||
}
|
||||
defer func() {
|
||||
if rc != _OK {
|
||||
if err != nil {
|
||||
f.Close()
|
||||
}
|
||||
}()
|
||||
|
||||
// Dead man's switch.
|
||||
if lock, rc := osTestLock(f, _SHM_DMS, 1); rc != _OK {
|
||||
return _IOERR_LOCK
|
||||
if lock, err := osTestLock(f, _SHM_DMS, 1, _IOERR_LOCK); err != nil {
|
||||
return err
|
||||
} else if lock == unix.F_WRLCK {
|
||||
return _BUSY
|
||||
} else if lock == unix.F_UNLCK {
|
||||
if rc := osWriteLock(f, _SHM_DMS, 1); rc != _OK {
|
||||
return rc
|
||||
if err := osWriteLock(f, _SHM_DMS, 1); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := f.Truncate(0); err != nil {
|
||||
return _IOERR_SHMOPEN
|
||||
return sysError{err, _IOERR_SHMOPEN}
|
||||
}
|
||||
}
|
||||
if rc := osReadLock(f, _SHM_DMS, 1); rc != _OK {
|
||||
return rc
|
||||
if err := osReadLock(f, _SHM_DMS, 1); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fi, err = f.Stat()
|
||||
if err != nil {
|
||||
return _IOERR_FSTAT
|
||||
return sysError{err, _IOERR_FSTAT}
|
||||
}
|
||||
|
||||
// Add the new shared file.
|
||||
@@ -134,53 +136,57 @@ func (s *vfsShm) shmOpen() (rc _ErrorCode) {
|
||||
for i, g := range vfsShmList {
|
||||
if g == nil {
|
||||
vfsShmList[i] = s.vfsShmParent
|
||||
return _OK
|
||||
return nil
|
||||
}
|
||||
}
|
||||
vfsShmList = append(vfsShmList, s.vfsShmParent)
|
||||
return _OK
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *vfsShm) shmMap(ctx context.Context, mod api.Module, id, size int32, extend bool) (ptr_t, _ErrorCode) {
|
||||
func (s *vfsShm) shmMap(ctx context.Context, mod api.Module, id, size int32, extend bool) (ptr_t, error) {
|
||||
// Ensure size is a multiple of the OS page size.
|
||||
if int(size)&(unix.Getpagesize()-1) != 0 {
|
||||
return 0, _IOERR_SHMMAP
|
||||
}
|
||||
|
||||
if rc := s.shmOpen(); rc != _OK {
|
||||
return 0, rc
|
||||
if err := s.shmOpen(); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
// Check if file is big enough.
|
||||
o, err := s.Seek(0, io.SeekEnd)
|
||||
if err != nil {
|
||||
return 0, _IOERR_SHMSIZE
|
||||
return 0, sysError{err, _IOERR_SHMSIZE}
|
||||
}
|
||||
if n := (int64(id) + 1) * int64(size); n > o {
|
||||
if !extend {
|
||||
return 0, _OK
|
||||
return 0, nil
|
||||
}
|
||||
if osAllocate(s.File, n) != nil {
|
||||
return 0, _IOERR_SHMSIZE
|
||||
if err := osAllocate(s.File, n); err != nil {
|
||||
return 0, sysError{err, _IOERR_SHMSIZE}
|
||||
}
|
||||
}
|
||||
|
||||
r, err := util.MapRegion(ctx, mod, s.File, int64(id)*int64(size), size, false)
|
||||
if err != nil {
|
||||
return 0, _IOERR_SHMMAP
|
||||
return 0, err
|
||||
}
|
||||
s.regions = append(s.regions, r)
|
||||
return r.Ptr, _OK
|
||||
return r.Ptr, nil
|
||||
}
|
||||
|
||||
func (s *vfsShm) shmLock(offset, n int32, flags _ShmFlag) _ErrorCode {
|
||||
func (s *vfsShm) shmLock(offset, n int32, flags _ShmFlag) error {
|
||||
if s.vfsShmParent == nil {
|
||||
return _IOERR_SHMLOCK
|
||||
}
|
||||
|
||||
s.Lock()
|
||||
defer s.Unlock()
|
||||
|
||||
// Check if we can obtain/release locks locally.
|
||||
rc := s.shmMemLock(offset, n, flags)
|
||||
if rc != _OK {
|
||||
return rc
|
||||
err := s.shmMemLock(offset, n, flags)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Obtain/release the appropriate file locks.
|
||||
@@ -192,36 +198,38 @@ func (s *vfsShm) shmLock(offset, n int32, flags _ShmFlag) _ErrorCode {
|
||||
for i := begin; i < end; i++ {
|
||||
if s.vfsShmParent.lock[i] != 0 {
|
||||
if i > begin {
|
||||
rc |= osUnlock(s.File, _SHM_BASE+int64(begin), int64(i-begin))
|
||||
err = cmp.Or(err,
|
||||
osUnlock(s.File, _SHM_BASE+int64(begin), int64(i-begin)))
|
||||
}
|
||||
begin = i + 1
|
||||
}
|
||||
}
|
||||
if end > begin {
|
||||
rc |= osUnlock(s.File, _SHM_BASE+int64(begin), int64(end-begin))
|
||||
err = cmp.Or(err,
|
||||
osUnlock(s.File, _SHM_BASE+int64(begin), int64(end-begin)))
|
||||
}
|
||||
return rc
|
||||
return err
|
||||
case flags&_SHM_SHARED != 0:
|
||||
// Acquiring a new shared lock on the file is only necessary
|
||||
// if there was a new shared lock in the range.
|
||||
for i := offset; i < offset+n; i++ {
|
||||
if s.vfsShmParent.lock[i] == 1 {
|
||||
rc = osReadLock(s.File, _SHM_BASE+int64(offset), int64(n))
|
||||
err = osReadLock(s.File, _SHM_BASE+int64(offset), int64(n))
|
||||
break
|
||||
}
|
||||
}
|
||||
case flags&_SHM_EXCLUSIVE != 0:
|
||||
// Acquiring an exclusive lock on the file is always necessary.
|
||||
rc = osWriteLock(s.File, _SHM_BASE+int64(offset), int64(n))
|
||||
err = osWriteLock(s.File, _SHM_BASE+int64(offset), int64(n))
|
||||
default:
|
||||
panic(util.AssertErr())
|
||||
}
|
||||
|
||||
// Release the local locks we had acquired.
|
||||
if rc != _OK {
|
||||
if err != nil {
|
||||
// Release the local locks we had acquired.
|
||||
s.shmMemLock(offset, n, flags^(_SHM_UNLOCK|_SHM_LOCK))
|
||||
}
|
||||
return rc
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *vfsShm) shmUnmap(delete bool) {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//go:build (windows && (386 || arm || amd64 || arm64 || riscv64 || ppc64le)) || sqlite3_dotlk
|
||||
//go:build (windows && (386 || arm || amd64 || arm64 || riscv64 || ppc64le || loong64)) || sqlite3_dotlk
|
||||
|
||||
package vfs
|
||||
|
||||
@@ -31,8 +31,8 @@ const (
|
||||
//
|
||||
// https://sqlite.org/walformat.html#the_wal_index_file_format
|
||||
|
||||
func (s *vfsShm) shmAcquire(ptr *_ErrorCode) {
|
||||
if ptr != nil && *ptr != _OK {
|
||||
func (s *vfsShm) shmAcquire(errp *error) {
|
||||
if errp != nil && *errp != nil {
|
||||
return
|
||||
}
|
||||
if len(s.ptrs) == 0 || shmEqual(s.shadow[0][:], s.shared[0][:]) {
|
||||
|
||||
@@ -59,16 +59,16 @@ func (s *vfsShm) Close() error {
|
||||
}
|
||||
|
||||
if err := dotlk.Unlock(s.path); err != nil {
|
||||
return _IOERR_UNLOCK
|
||||
return sysError{err, _IOERR_UNLOCK}
|
||||
}
|
||||
delete(vfsShmList, s.path)
|
||||
s.vfsShmParent = nil
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *vfsShm) shmOpen() _ErrorCode {
|
||||
func (s *vfsShm) shmOpen() error {
|
||||
if s.vfsShmParent != nil {
|
||||
return _OK
|
||||
return nil
|
||||
}
|
||||
|
||||
vfsShmListMtx.Lock()
|
||||
@@ -78,7 +78,7 @@ func (s *vfsShm) shmOpen() _ErrorCode {
|
||||
if g, ok := vfsShmList[s.path]; ok {
|
||||
s.vfsShmParent = g
|
||||
g.refs++
|
||||
return _OK
|
||||
return nil
|
||||
}
|
||||
|
||||
// Dead man's switch.
|
||||
@@ -87,16 +87,16 @@ func (s *vfsShm) shmOpen() _ErrorCode {
|
||||
return _BUSY
|
||||
}
|
||||
if err != nil {
|
||||
return _IOERR_LOCK
|
||||
return sysError{err, _IOERR_LOCK}
|
||||
}
|
||||
|
||||
// Add the new shared buffer.
|
||||
s.vfsShmParent = &vfsShmParent{}
|
||||
vfsShmList[s.path] = s.vfsShmParent
|
||||
return _OK
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *vfsShm) shmMap(ctx context.Context, mod api.Module, id, size int32, extend bool) (ptr_t, _ErrorCode) {
|
||||
func (s *vfsShm) shmMap(ctx context.Context, mod api.Module, id, size int32, extend bool) (ptr_t, error) {
|
||||
if size != _WALINDEX_PGSZ {
|
||||
return 0, _IOERR_SHMMAP
|
||||
}
|
||||
@@ -105,8 +105,8 @@ func (s *vfsShm) shmMap(ctx context.Context, mod api.Module, id, size int32, ext
|
||||
s.free = mod.ExportedFunction("sqlite3_free")
|
||||
s.alloc = mod.ExportedFunction("sqlite3_malloc64")
|
||||
}
|
||||
if rc := s.shmOpen(); rc != _OK {
|
||||
return 0, rc
|
||||
if err := s.shmOpen(); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
s.Lock()
|
||||
@@ -116,7 +116,7 @@ func (s *vfsShm) shmMap(ctx context.Context, mod api.Module, id, size int32, ext
|
||||
// Extend shared memory.
|
||||
if int(id) >= len(s.shared) {
|
||||
if !extend {
|
||||
return 0, _OK
|
||||
return 0, nil
|
||||
}
|
||||
s.shared = append(s.shared, make([][_WALINDEX_PGSZ]byte, int(id)-len(s.shared)+1)...)
|
||||
}
|
||||
@@ -140,16 +140,20 @@ func (s *vfsShm) shmMap(ctx context.Context, mod api.Module, id, size int32, ext
|
||||
}
|
||||
|
||||
s.shadow[0][4] = 1
|
||||
return s.ptrs[id], _OK
|
||||
return s.ptrs[id], nil
|
||||
}
|
||||
|
||||
func (s *vfsShm) shmLock(offset, n int32, flags _ShmFlag) (rc _ErrorCode) {
|
||||
func (s *vfsShm) shmLock(offset, n int32, flags _ShmFlag) (err error) {
|
||||
if s.vfsShmParent == nil {
|
||||
return _IOERR_SHMLOCK
|
||||
}
|
||||
|
||||
s.Lock()
|
||||
defer s.Unlock()
|
||||
|
||||
switch {
|
||||
case flags&_SHM_LOCK != 0:
|
||||
defer s.shmAcquire(&rc)
|
||||
defer s.shmAcquire(&err)
|
||||
case flags&_SHM_EXCLUSIVE != 0:
|
||||
s.shmRelease()
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
//go:build ((freebsd || openbsd || netbsd || dragonfly || illumos) && (386 || arm || amd64 || arm64 || riscv64 || ppc64le)) || sqlite3_flock || sqlite3_dotlk
|
||||
//go:build ((freebsd || openbsd || netbsd || dragonfly || illumos) && (386 || arm || amd64 || arm64 || riscv64 || ppc64le || loong64)) || sqlite3_flock || sqlite3_dotlk
|
||||
|
||||
package vfs
|
||||
|
||||
import "github.com/ncruces/go-sqlite3/internal/util"
|
||||
|
||||
// +checklocks:s.Mutex
|
||||
func (s *vfsShm) shmMemLock(offset, n int32, flags _ShmFlag) _ErrorCode {
|
||||
func (s *vfsShm) shmMemLock(offset, n int32, flags _ShmFlag) error {
|
||||
switch {
|
||||
case flags&_SHM_UNLOCK != 0:
|
||||
for i := offset; i < offset+n; i++ {
|
||||
@@ -48,6 +48,5 @@ func (s *vfsShm) shmMemLock(offset, n int32, flags _ShmFlag) _ErrorCode {
|
||||
default:
|
||||
panic(util.AssertErr())
|
||||
}
|
||||
|
||||
return _OK
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//go:build (linux || darwin) && (386 || arm || amd64 || arm64 || riscv64 || ppc64le) && !(sqlite3_flock || sqlite3_dotlk)
|
||||
//go:build (linux || darwin) && (386 || arm || amd64 || arm64 || riscv64 || ppc64le || loong64) && !(sqlite3_flock || sqlite3_dotlk)
|
||||
|
||||
package vfs
|
||||
|
||||
@@ -9,9 +9,10 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/tetratelabs/wazero/api"
|
||||
"golang.org/x/sys/unix"
|
||||
|
||||
"github.com/tetratelabs/wazero/api"
|
||||
|
||||
"github.com/ncruces/go-sqlite3/internal/util"
|
||||
)
|
||||
|
||||
@@ -27,7 +28,10 @@ type vfsShm struct {
|
||||
|
||||
var _ blockingSharedMemory = &vfsShm{}
|
||||
|
||||
func (s *vfsShm) shmOpen() _ErrorCode {
|
||||
func (s *vfsShm) shmOpen() error {
|
||||
if s.fileLock {
|
||||
return nil
|
||||
}
|
||||
if s.File == nil {
|
||||
f, err := os.OpenFile(s.path,
|
||||
os.O_RDWR|os.O_CREATE|_O_NOFOLLOW, 0666)
|
||||
@@ -37,17 +41,15 @@ func (s *vfsShm) shmOpen() _ErrorCode {
|
||||
s.readOnly = true
|
||||
}
|
||||
if err != nil {
|
||||
return _CANTOPEN
|
||||
return sysError{err, _CANTOPEN}
|
||||
}
|
||||
s.fileLock = false
|
||||
s.File = f
|
||||
}
|
||||
if s.fileLock {
|
||||
return _OK
|
||||
}
|
||||
|
||||
// Dead man's switch.
|
||||
if lock, rc := osTestLock(s.File, _SHM_DMS, 1); rc != _OK {
|
||||
return _IOERR_LOCK
|
||||
if lock, err := osTestLock(s.File, _SHM_DMS, 1, _IOERR_LOCK); err != nil {
|
||||
return err
|
||||
} else if lock == unix.F_WRLCK {
|
||||
return _BUSY
|
||||
} else if lock == unix.F_UNLCK {
|
||||
@@ -61,54 +63,57 @@ func (s *vfsShm) shmOpen() _ErrorCode {
|
||||
// but only downgrade it to a shared lock.
|
||||
// So no point in blocking here.
|
||||
// The call below to obtain the shared DMS lock may use a blocking lock.
|
||||
if rc := osWriteLock(s.File, _SHM_DMS, 1, 0); rc != _OK {
|
||||
return rc
|
||||
if err := osWriteLock(s.File, _SHM_DMS, 1, 0); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.Truncate(0); err != nil {
|
||||
return _IOERR_SHMOPEN
|
||||
return sysError{err, _IOERR_SHMOPEN}
|
||||
}
|
||||
}
|
||||
rc := osReadLock(s.File, _SHM_DMS, 1, time.Millisecond)
|
||||
s.fileLock = rc == _OK
|
||||
return rc
|
||||
err := osReadLock(s.File, _SHM_DMS, 1, time.Millisecond)
|
||||
s.fileLock = err == nil
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *vfsShm) shmMap(ctx context.Context, mod api.Module, id, size int32, extend bool) (ptr_t, _ErrorCode) {
|
||||
func (s *vfsShm) shmMap(ctx context.Context, mod api.Module, id, size int32, extend bool) (ptr_t, error) {
|
||||
// Ensure size is a multiple of the OS page size.
|
||||
if int(size)&(unix.Getpagesize()-1) != 0 {
|
||||
return 0, _IOERR_SHMMAP
|
||||
}
|
||||
|
||||
if rc := s.shmOpen(); rc != _OK {
|
||||
return 0, rc
|
||||
if err := s.shmOpen(); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
// Check if file is big enough.
|
||||
o, err := s.Seek(0, io.SeekEnd)
|
||||
if err != nil {
|
||||
return 0, _IOERR_SHMSIZE
|
||||
return 0, sysError{err, _IOERR_SHMSIZE}
|
||||
}
|
||||
if n := (int64(id) + 1) * int64(size); n > o {
|
||||
if !extend {
|
||||
return 0, _OK
|
||||
return 0, nil
|
||||
}
|
||||
if s.readOnly || osAllocate(s.File, n) != nil {
|
||||
if s.readOnly {
|
||||
return 0, _IOERR_SHMSIZE
|
||||
}
|
||||
if err := osAllocate(s.File, n); err != nil {
|
||||
return 0, sysError{err, _IOERR_SHMSIZE}
|
||||
}
|
||||
}
|
||||
|
||||
r, err := util.MapRegion(ctx, mod, s.File, int64(id)*int64(size), size, s.readOnly)
|
||||
if err != nil {
|
||||
return 0, _IOERR_SHMMAP
|
||||
return 0, err
|
||||
}
|
||||
s.regions = append(s.regions, r)
|
||||
if s.readOnly {
|
||||
return r.Ptr, _READONLY
|
||||
}
|
||||
return r.Ptr, _OK
|
||||
return r.Ptr, nil
|
||||
}
|
||||
|
||||
func (s *vfsShm) shmLock(offset, n int32, flags _ShmFlag) _ErrorCode {
|
||||
func (s *vfsShm) shmLock(offset, n int32, flags _ShmFlag) error {
|
||||
// Argument check.
|
||||
switch {
|
||||
case n <= 0:
|
||||
@@ -129,6 +134,10 @@ func (s *vfsShm) shmLock(offset, n int32, flags _ShmFlag) _ErrorCode {
|
||||
panic(util.AssertErr())
|
||||
}
|
||||
|
||||
if s.File == nil {
|
||||
return _IOERR_SHMLOCK
|
||||
}
|
||||
|
||||
var timeout time.Duration
|
||||
if s.blocking {
|
||||
timeout = time.Millisecond
|
||||
@@ -163,6 +172,7 @@ func (s *vfsShm) shmUnmap(delete bool) {
|
||||
}
|
||||
s.Close()
|
||||
s.File = nil
|
||||
s.fileLock = false
|
||||
}
|
||||
|
||||
func (s *vfsShm) shmBarrier() {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//go:build !(((linux || darwin || windows || freebsd || openbsd || netbsd || dragonfly || illumos) && (386 || arm || amd64 || arm64 || riscv64 || ppc64le)) || sqlite3_flock || sqlite3_dotlk)
|
||||
//go:build !(((linux || darwin || windows || freebsd || openbsd || netbsd || dragonfly || illumos) && (386 || arm || amd64 || arm64 || riscv64 || ppc64le || loong64)) || sqlite3_flock || sqlite3_dotlk)
|
||||
|
||||
package vfs
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//go:build (386 || arm || amd64 || arm64 || riscv64 || ppc64le) && !sqlite3_dotlk
|
||||
//go:build (386 || arm || amd64 || arm64 || riscv64 || ppc64le || loong64) && !sqlite3_dotlk
|
||||
|
||||
package vfs
|
||||
|
||||
@@ -8,9 +8,10 @@ import (
|
||||
"os"
|
||||
"sync"
|
||||
|
||||
"github.com/tetratelabs/wazero/api"
|
||||
"golang.org/x/sys/windows"
|
||||
|
||||
"github.com/tetratelabs/wazero/api"
|
||||
|
||||
"github.com/ncruces/go-sqlite3/internal/util"
|
||||
)
|
||||
|
||||
@@ -40,32 +41,33 @@ func (s *vfsShm) Close() error {
|
||||
return s.File.Close()
|
||||
}
|
||||
|
||||
func (s *vfsShm) shmOpen() _ErrorCode {
|
||||
func (s *vfsShm) shmOpen() error {
|
||||
if s.fileLock {
|
||||
return nil
|
||||
}
|
||||
if s.File == nil {
|
||||
f, err := os.OpenFile(s.path, os.O_RDWR|os.O_CREATE, 0666)
|
||||
if err != nil {
|
||||
return _CANTOPEN
|
||||
return sysError{err, _CANTOPEN}
|
||||
}
|
||||
s.fileLock = false
|
||||
s.File = f
|
||||
}
|
||||
if s.fileLock {
|
||||
return _OK
|
||||
}
|
||||
|
||||
// Dead man's switch.
|
||||
if rc := osWriteLock(s.File, _SHM_DMS, 1, 0); rc == _OK {
|
||||
if osWriteLock(s.File, _SHM_DMS, 1, 0) == nil {
|
||||
err := s.Truncate(0)
|
||||
osUnlock(s.File, _SHM_DMS, 1)
|
||||
if err != nil {
|
||||
return _IOERR_SHMOPEN
|
||||
return sysError{err, _IOERR_SHMOPEN}
|
||||
}
|
||||
}
|
||||
rc := osReadLock(s.File, _SHM_DMS, 1, 0)
|
||||
s.fileLock = rc == _OK
|
||||
return rc
|
||||
err := osReadLock(s.File, _SHM_DMS, 1, 0)
|
||||
s.fileLock = err == nil
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *vfsShm) shmMap(ctx context.Context, mod api.Module, id, size int32, extend bool) (_ ptr_t, rc _ErrorCode) {
|
||||
func (s *vfsShm) shmMap(ctx context.Context, mod api.Module, id, size int32, extend bool) (_ ptr_t, err error) {
|
||||
// Ensure size is a multiple of the OS page size.
|
||||
if size != _WALINDEX_PGSZ || (windows.Getpagesize()-1)&_WALINDEX_PGSZ != 0 {
|
||||
return 0, _IOERR_SHMMAP
|
||||
@@ -75,31 +77,31 @@ func (s *vfsShm) shmMap(ctx context.Context, mod api.Module, id, size int32, ext
|
||||
s.free = mod.ExportedFunction("sqlite3_free")
|
||||
s.alloc = mod.ExportedFunction("sqlite3_malloc64")
|
||||
}
|
||||
if rc := s.shmOpen(); rc != _OK {
|
||||
return 0, rc
|
||||
if err := s.shmOpen(); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
defer s.shmAcquire(&rc)
|
||||
defer s.shmAcquire(&err)
|
||||
|
||||
// Check if file is big enough.
|
||||
o, err := s.Seek(0, io.SeekEnd)
|
||||
if err != nil {
|
||||
return 0, _IOERR_SHMSIZE
|
||||
return 0, sysError{err, _IOERR_SHMSIZE}
|
||||
}
|
||||
if n := (int64(id) + 1) * int64(size); n > o {
|
||||
if !extend {
|
||||
return 0, _OK
|
||||
return 0, nil
|
||||
}
|
||||
if osAllocate(s.File, n) != nil {
|
||||
return 0, _IOERR_SHMSIZE
|
||||
if err := osAllocate(s.File, n); err != nil {
|
||||
return 0, sysError{err, _IOERR_SHMSIZE}
|
||||
}
|
||||
}
|
||||
|
||||
// Maps regions into memory.
|
||||
for int(id) >= len(s.shared) {
|
||||
r, err := util.MapRegion(ctx, mod, s.File, int64(id)*int64(size), size)
|
||||
r, err := util.MapRegion(s.File, int64(id)*int64(size), size)
|
||||
if err != nil {
|
||||
return 0, _IOERR_SHMMAP
|
||||
return 0, err
|
||||
}
|
||||
s.regions = append(s.regions, r)
|
||||
s.shared = append(s.shared, r.Data)
|
||||
@@ -124,13 +126,17 @@ func (s *vfsShm) shmMap(ctx context.Context, mod api.Module, id, size int32, ext
|
||||
}
|
||||
|
||||
s.shadow[0][4] = 1
|
||||
return s.ptrs[id], _OK
|
||||
return s.ptrs[id], nil
|
||||
}
|
||||
|
||||
func (s *vfsShm) shmLock(offset, n int32, flags _ShmFlag) (rc _ErrorCode) {
|
||||
func (s *vfsShm) shmLock(offset, n int32, flags _ShmFlag) (err error) {
|
||||
if s.File == nil {
|
||||
return _IOERR_SHMLOCK
|
||||
}
|
||||
|
||||
switch {
|
||||
case flags&_SHM_LOCK != 0:
|
||||
defer s.shmAcquire(&rc)
|
||||
defer s.shmAcquire(&err)
|
||||
case flags&_SHM_EXCLUSIVE != 0:
|
||||
s.shmRelease()
|
||||
}
|
||||
@@ -168,6 +174,7 @@ func (s *vfsShm) shmUnmap(delete bool) {
|
||||
// Close the file.
|
||||
s.Close()
|
||||
s.File = nil
|
||||
s.fileLock = false
|
||||
if delete {
|
||||
os.Remove(s.path)
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ WASI_SDK="$ROOT/tools/wasi-sdk/bin"
|
||||
$(awk '{print "-Wl,--export="$0}' exports.txt)
|
||||
|
||||
"$BINARYEN/wasm-opt" -g mptest.wasm -o mptest.tmp \
|
||||
--low-memory-unused --gufa --generate-global-effects --converge -O3 \
|
||||
--gufa --generate-global-effects --low-memory-unused --converge -O3 \
|
||||
--enable-mutable-globals --enable-nontrapping-float-to-int \
|
||||
--enable-simd --enable-bulk-memory --enable-sign-ext \
|
||||
--enable-reference-types --enable-multivalue \
|
||||
|
||||
Binary file not shown.
@@ -22,7 +22,7 @@ WASI_SDK="$ROOT/tools/wasi-sdk/bin"
|
||||
$(awk '{print "-Wl,--export="$0}' exports.txt)
|
||||
|
||||
"$BINARYEN/wasm-opt" -g speedtest1.wasm -o speedtest1.tmp \
|
||||
--low-memory-unused --gufa --generate-global-effects --converge -O3 \
|
||||
--gufa --generate-global-effects --low-memory-unused --converge -O3 \
|
||||
--enable-mutable-globals --enable-nontrapping-float-to-int \
|
||||
--enable-simd --enable-bulk-memory --enable-sign-ext \
|
||||
--enable-reference-types --enable-multivalue \
|
||||
|
||||
Binary file not shown.
99
vfs/vfs.go
99
vfs/vfs.go
@@ -50,8 +50,7 @@ func ExportHostFunctions(env wazero.HostModuleBuilder) wazero.HostModuleBuilder
|
||||
}
|
||||
|
||||
func vfsFind(ctx context.Context, mod api.Module, zVfsName ptr_t) uint32 {
|
||||
name := util.ReadString(mod, zVfsName, _MAX_NAME)
|
||||
if vfs := Find(name); vfs != nil && vfs != (vfsOS{}) {
|
||||
if find(util.ReadString(mod, zVfsName, _MAX_NAME)) != nil {
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
@@ -102,7 +101,7 @@ func vfsFullPathname(ctx context.Context, mod api.Module, pVfs, zRelative ptr_t,
|
||||
}
|
||||
util.WriteString(mod, zFull, path)
|
||||
|
||||
return vfsErrorCode(err, _CANTOPEN_FULLPATH)
|
||||
return vfsErrorCode(ctx, err, _CANTOPEN_FULLPATH)
|
||||
}
|
||||
|
||||
func vfsDelete(ctx context.Context, mod api.Module, pVfs, zPath ptr_t, syncDir int32) _ErrorCode {
|
||||
@@ -110,7 +109,7 @@ func vfsDelete(ctx context.Context, mod api.Module, pVfs, zPath ptr_t, syncDir i
|
||||
path := util.ReadString(mod, zPath, _MAX_PATHNAME)
|
||||
|
||||
err := vfs.Delete(path, syncDir != 0)
|
||||
return vfsErrorCode(err, _IOERR_DELETE)
|
||||
return vfsErrorCode(ctx, err, _IOERR_DELETE)
|
||||
}
|
||||
|
||||
func vfsAccess(ctx context.Context, mod api.Module, pVfs, zPath ptr_t, flags AccessFlag, pResOut ptr_t) _ErrorCode {
|
||||
@@ -119,7 +118,7 @@ func vfsAccess(ctx context.Context, mod api.Module, pVfs, zPath ptr_t, flags Acc
|
||||
|
||||
ok, err := vfs.Access(path, flags)
|
||||
util.WriteBool(mod, pResOut, ok)
|
||||
return vfsErrorCode(err, _IOERR_ACCESS)
|
||||
return vfsErrorCode(ctx, err, _IOERR_ACCESS)
|
||||
}
|
||||
|
||||
func vfsOpen(ctx context.Context, mod api.Module, pVfs, zPath, pFile ptr_t, flags OpenFlag, pOutFlags, pOutVFS ptr_t) _ErrorCode {
|
||||
@@ -134,7 +133,7 @@ func vfsOpen(ctx context.Context, mod api.Module, pVfs, zPath, pFile ptr_t, flag
|
||||
file, flags, err = vfs.Open(name.String(), flags)
|
||||
}
|
||||
if err != nil {
|
||||
return vfsErrorCode(err, _CANTOPEN)
|
||||
return vfsErrorCode(ctx, err, _CANTOPEN)
|
||||
}
|
||||
|
||||
if file, ok := file.(FilePowersafeOverwrite); ok {
|
||||
@@ -155,7 +154,7 @@ func vfsOpen(ctx context.Context, mod api.Module, pVfs, zPath, pFile ptr_t, flag
|
||||
|
||||
func vfsClose(ctx context.Context, mod api.Module, pFile ptr_t) _ErrorCode {
|
||||
err := vfsFileClose(ctx, mod, pFile)
|
||||
return vfsErrorCode(err, _IOERR_CLOSE)
|
||||
return vfsErrorCode(ctx, err, _IOERR_CLOSE)
|
||||
}
|
||||
|
||||
func vfsRead(ctx context.Context, mod api.Module, pFile, zBuf ptr_t, iAmt int32, iOfst int64) _ErrorCode {
|
||||
@@ -167,7 +166,7 @@ func vfsRead(ctx context.Context, mod api.Module, pFile, zBuf ptr_t, iAmt int32,
|
||||
return _OK
|
||||
}
|
||||
if err != io.EOF {
|
||||
return vfsErrorCode(err, _IOERR_READ)
|
||||
return vfsErrorCode(ctx, err, _IOERR_READ)
|
||||
}
|
||||
clear(buf[n:])
|
||||
return _IOERR_SHORT_READ
|
||||
@@ -178,45 +177,45 @@ func vfsWrite(ctx context.Context, mod api.Module, pFile, zBuf ptr_t, iAmt int32
|
||||
buf := util.View(mod, zBuf, int64(iAmt))
|
||||
|
||||
_, err := file.WriteAt(buf, iOfst)
|
||||
return vfsErrorCode(err, _IOERR_WRITE)
|
||||
return vfsErrorCode(ctx, err, _IOERR_WRITE)
|
||||
}
|
||||
|
||||
func vfsTruncate(ctx context.Context, mod api.Module, pFile ptr_t, nByte int64) _ErrorCode {
|
||||
file := vfsFileGet(ctx, mod, pFile).(File)
|
||||
err := file.Truncate(nByte)
|
||||
return vfsErrorCode(err, _IOERR_TRUNCATE)
|
||||
return vfsErrorCode(ctx, err, _IOERR_TRUNCATE)
|
||||
}
|
||||
|
||||
func vfsSync(ctx context.Context, mod api.Module, pFile ptr_t, flags SyncFlag) _ErrorCode {
|
||||
file := vfsFileGet(ctx, mod, pFile).(File)
|
||||
err := file.Sync(flags)
|
||||
return vfsErrorCode(err, _IOERR_FSYNC)
|
||||
return vfsErrorCode(ctx, err, _IOERR_FSYNC)
|
||||
}
|
||||
|
||||
func vfsFileSize(ctx context.Context, mod api.Module, pFile, pSize ptr_t) _ErrorCode {
|
||||
file := vfsFileGet(ctx, mod, pFile).(File)
|
||||
size, err := file.Size()
|
||||
util.Write64(mod, pSize, size)
|
||||
return vfsErrorCode(err, _IOERR_SEEK)
|
||||
return vfsErrorCode(ctx, err, _IOERR_SEEK)
|
||||
}
|
||||
|
||||
func vfsLock(ctx context.Context, mod api.Module, pFile ptr_t, eLock LockLevel) _ErrorCode {
|
||||
file := vfsFileGet(ctx, mod, pFile).(File)
|
||||
err := file.Lock(eLock)
|
||||
return vfsErrorCode(err, _IOERR_LOCK)
|
||||
return vfsErrorCode(ctx, err, _IOERR_LOCK)
|
||||
}
|
||||
|
||||
func vfsUnlock(ctx context.Context, mod api.Module, pFile ptr_t, eLock LockLevel) _ErrorCode {
|
||||
file := vfsFileGet(ctx, mod, pFile).(File)
|
||||
err := file.Unlock(eLock)
|
||||
return vfsErrorCode(err, _IOERR_UNLOCK)
|
||||
return vfsErrorCode(ctx, err, _IOERR_UNLOCK)
|
||||
}
|
||||
|
||||
func vfsCheckReservedLock(ctx context.Context, mod api.Module, pFile, pResOut ptr_t) _ErrorCode {
|
||||
file := vfsFileGet(ctx, mod, pFile).(File)
|
||||
locked, err := file.CheckReservedLock()
|
||||
util.WriteBool(mod, pResOut, locked)
|
||||
return vfsErrorCode(err, _IOERR_CHECKRESERVEDLOCK)
|
||||
return vfsErrorCode(ctx, err, _IOERR_CHECKRESERVEDLOCK)
|
||||
}
|
||||
|
||||
func vfsFileControl(ctx context.Context, mod api.Module, pFile ptr_t, op _FcntlOpcode, pArg ptr_t) _ErrorCode {
|
||||
@@ -268,20 +267,20 @@ func vfsFileControlImpl(ctx context.Context, mod api.Module, file File, op _Fcnt
|
||||
if file, ok := file.(FileSizeHint); ok {
|
||||
size := util.Read64[int64](mod, pArg)
|
||||
err := file.SizeHint(size)
|
||||
return vfsErrorCode(err, _IOERR_TRUNCATE)
|
||||
return vfsErrorCode(ctx, err, _IOERR_TRUNCATE)
|
||||
}
|
||||
|
||||
case _FCNTL_HAS_MOVED:
|
||||
if file, ok := file.(FileHasMoved); ok {
|
||||
moved, err := file.HasMoved()
|
||||
util.WriteBool(mod, pArg, moved)
|
||||
return vfsErrorCode(err, _IOERR_FSTAT)
|
||||
return vfsErrorCode(ctx, err, _IOERR_FSTAT)
|
||||
}
|
||||
|
||||
case _FCNTL_OVERWRITE:
|
||||
if file, ok := file.(FileOverwrite); ok {
|
||||
err := file.Overwrite()
|
||||
return vfsErrorCode(err, _IOERR)
|
||||
return vfsErrorCode(ctx, err, _IOERR)
|
||||
}
|
||||
|
||||
case _FCNTL_SYNC:
|
||||
@@ -291,29 +290,29 @@ func vfsFileControlImpl(ctx context.Context, mod api.Module, file File, op _Fcnt
|
||||
name = util.ReadString(mod, pArg, _MAX_PATHNAME)
|
||||
}
|
||||
err := file.SyncSuper(name)
|
||||
return vfsErrorCode(err, _IOERR)
|
||||
return vfsErrorCode(ctx, err, _IOERR)
|
||||
}
|
||||
|
||||
case _FCNTL_COMMIT_PHASETWO:
|
||||
if file, ok := file.(FileCommitPhaseTwo); ok {
|
||||
err := file.CommitPhaseTwo()
|
||||
return vfsErrorCode(err, _IOERR)
|
||||
return vfsErrorCode(ctx, err, _IOERR)
|
||||
}
|
||||
|
||||
case _FCNTL_BEGIN_ATOMIC_WRITE:
|
||||
if file, ok := file.(FileBatchAtomicWrite); ok {
|
||||
err := file.BeginAtomicWrite()
|
||||
return vfsErrorCode(err, _IOERR_BEGIN_ATOMIC)
|
||||
return vfsErrorCode(ctx, err, _IOERR_BEGIN_ATOMIC)
|
||||
}
|
||||
case _FCNTL_COMMIT_ATOMIC_WRITE:
|
||||
if file, ok := file.(FileBatchAtomicWrite); ok {
|
||||
err := file.CommitAtomicWrite()
|
||||
return vfsErrorCode(err, _IOERR_COMMIT_ATOMIC)
|
||||
return vfsErrorCode(ctx, err, _IOERR_COMMIT_ATOMIC)
|
||||
}
|
||||
case _FCNTL_ROLLBACK_ATOMIC_WRITE:
|
||||
if file, ok := file.(FileBatchAtomicWrite); ok {
|
||||
err := file.RollbackAtomicWrite()
|
||||
return vfsErrorCode(err, _IOERR_ROLLBACK_ATOMIC)
|
||||
return vfsErrorCode(ctx, err, _IOERR_ROLLBACK_ATOMIC)
|
||||
}
|
||||
|
||||
case _FCNTL_CKPT_START:
|
||||
@@ -329,16 +328,16 @@ func vfsFileControlImpl(ctx context.Context, mod api.Module, file File, op _Fcnt
|
||||
|
||||
case _FCNTL_PRAGMA:
|
||||
if file, ok := file.(FilePragma); ok {
|
||||
var value string
|
||||
ptr := util.Read32[ptr_t](mod, pArg+1*ptrlen)
|
||||
name := util.ReadString(mod, ptr, _MAX_SQL_LENGTH)
|
||||
var value string
|
||||
if ptr := util.Read32[ptr_t](mod, pArg+2*ptrlen); ptr != 0 {
|
||||
value = util.ReadString(mod, ptr, _MAX_SQL_LENGTH)
|
||||
}
|
||||
|
||||
out, err := file.Pragma(strings.ToLower(name), value)
|
||||
|
||||
ret := vfsErrorCode(err, _ERROR)
|
||||
ret := vfsErrorCode(ctx, err, _ERROR)
|
||||
if ret == _ERROR {
|
||||
out = err.Error()
|
||||
}
|
||||
@@ -407,14 +406,15 @@ func vfsShmBarrier(ctx context.Context, mod api.Module, pFile ptr_t) {
|
||||
|
||||
func vfsShmMap(ctx context.Context, mod api.Module, pFile ptr_t, iRegion, szRegion, bExtend int32, pp ptr_t) _ErrorCode {
|
||||
shm := vfsFileGet(ctx, mod, pFile).(FileSharedMemory).SharedMemory()
|
||||
p, rc := shm.shmMap(ctx, mod, iRegion, szRegion, bExtend != 0)
|
||||
p, err := shm.shmMap(ctx, mod, iRegion, szRegion, bExtend != 0)
|
||||
util.Write32(mod, pp, p)
|
||||
return rc
|
||||
return vfsErrorCode(ctx, err, _IOERR_SHMMAP)
|
||||
}
|
||||
|
||||
func vfsShmLock(ctx context.Context, mod api.Module, pFile ptr_t, offset, n int32, flags _ShmFlag) _ErrorCode {
|
||||
shm := vfsFileGet(ctx, mod, pFile).(FileSharedMemory).SharedMemory()
|
||||
return shm.shmLock(offset, n, flags)
|
||||
err := shm.shmLock(offset, n, flags)
|
||||
return vfsErrorCode(ctx, err, _IOERR_SHMLOCK)
|
||||
}
|
||||
|
||||
func vfsShmUnmap(ctx context.Context, mod api.Module, pFile ptr_t, bDelete int32) _ErrorCode {
|
||||
@@ -454,13 +454,40 @@ func vfsFileClose(ctx context.Context, mod api.Module, pFile ptr_t) error {
|
||||
return util.DelHandle(ctx, id)
|
||||
}
|
||||
|
||||
func vfsErrorCode(err error, def _ErrorCode) _ErrorCode {
|
||||
if err == nil {
|
||||
return _OK
|
||||
func vfsErrorCode(ctx context.Context, err error, code _ErrorCode) _ErrorCode {
|
||||
var sys error
|
||||
|
||||
switch err := err.(type) {
|
||||
case nil:
|
||||
code = _OK
|
||||
case _ErrorCode:
|
||||
code = err
|
||||
case sysError:
|
||||
code = err.code
|
||||
sys = err.error
|
||||
default:
|
||||
switch v := reflect.ValueOf(err); v.Kind() {
|
||||
case reflect.Uint8, reflect.Uint16:
|
||||
code = _ErrorCode(v.Uint())
|
||||
default:
|
||||
sys = err
|
||||
}
|
||||
}
|
||||
switch v := reflect.ValueOf(err); v.Kind() {
|
||||
case reflect.Uint8, reflect.Uint16, reflect.Uint32:
|
||||
return _ErrorCode(v.Uint())
|
||||
}
|
||||
return def
|
||||
|
||||
util.SetSystemError(ctx, sys)
|
||||
return code
|
||||
}
|
||||
|
||||
// SystemError tags an error with a given
|
||||
// sqlite3.ErrorCode or sqlite3.ExtendedErrorCode.
|
||||
func SystemError[T interface{ ~uint8 | ~uint16 }](err error, code T) error {
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
return sysError{error: err, code: _ErrorCode(code)}
|
||||
}
|
||||
|
||||
type sysError struct {
|
||||
error
|
||||
code _ErrorCode
|
||||
}
|
||||
|
||||
@@ -55,7 +55,7 @@ func (x *xtsVFS) OpenFilename(name *vfs.Filename, flags vfs.OpenFlag) (file vfs.
|
||||
|
||||
if cipher == nil {
|
||||
file.Close()
|
||||
return nil, flags, sqlite3.CANTOPEN
|
||||
return nil, flags, sqlite3.IOERR_BADKEY
|
||||
}
|
||||
return &xtsFile{File: file, cipher: cipher, init: x.init}, flags, nil
|
||||
}
|
||||
@@ -103,7 +103,7 @@ func (x *xtsFile) Pragma(name string, value string) (string, error) {
|
||||
if x.cipher = x.init.XTS(key); x.cipher != nil {
|
||||
return "ok", nil
|
||||
}
|
||||
return "", sqlite3.CANTOPEN
|
||||
return "", sqlite3.IOERR_BADKEY
|
||||
}
|
||||
|
||||
func (x *xtsFile) ReadAt(p []byte, off int64) (n int, err error) {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user