mirror of
https://github.com/ncruces/go-sqlite3.git
synced 2026-01-15 07:09:14 +00:00
Compare commits
23 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e6b2d2aef5 | ||
|
|
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
|
||||
|
||||
77
.github/workflows/test.yml
vendored
77
.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,17 +51,17 @@ 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
|
||||
@@ -69,12 +69,12 @@ jobs:
|
||||
run: |
|
||||
go work init .
|
||||
go work use -r embed gormlite
|
||||
go test -v ./embed/bcw2/...
|
||||
go test ./embed/bcw2/...
|
||||
|
||||
- name: Test GORM
|
||||
shell: bash
|
||||
run: gormlite/test.sh
|
||||
if: matrix.os != 'windows-latest'
|
||||
if: matrix.os == 'ubuntu-latest'
|
||||
|
||||
- name: Collect coverage
|
||||
run: |
|
||||
@@ -99,36 +99,34 @@ jobs:
|
||||
os:
|
||||
- name: freebsd
|
||||
version: '14.3'
|
||||
flags: '-test.v'
|
||||
- name: netbsd
|
||||
version: '10.1'
|
||||
flags: '-test.v'
|
||||
- name: freebsd
|
||||
arch: arm64
|
||||
version: '14.3'
|
||||
flags: '-test.v -test.short'
|
||||
tflags: '-test.short'
|
||||
- name: netbsd
|
||||
arch: arm64
|
||||
version: '10.1'
|
||||
flags: '-test.v -test.short'
|
||||
tflags: '-test.short'
|
||||
- name: openbsd
|
||||
version: '7.7'
|
||||
flags: '-test.v -test.short'
|
||||
version: '7.8'
|
||||
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.30.0
|
||||
with:
|
||||
operating_system: ${{ matrix.os.name }}
|
||||
architecture: ${{ matrix.os.arch }}
|
||||
@@ -143,19 +141,16 @@ jobs:
|
||||
os:
|
||||
- name: dragonfly
|
||||
action: 'vmactions/dragonflybsd-vm@v1'
|
||||
tflags: '-test.v'
|
||||
- name: illumos
|
||||
action: 'vmactions/omnios-vm@v1'
|
||||
tflags: '-test.v'
|
||||
- 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 +169,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 +182,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 +190,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
|
||||
|
||||
|
||||
@@ -265,7 +265,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
|
||||
}
|
||||
|
||||
19
const.go
19
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)
|
||||
@@ -229,7 +234,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.
|
||||
@@ -362,13 +368,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 {
|
||||
@@ -442,6 +440,22 @@ func (c *conn) CheckNamedValue(arg *driver.NamedValue) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Deprecated: for Litestream use only; may be removed at any time.
|
||||
func (c *conn) FileControlPersistWAL(schema string, mode int) (int, error) {
|
||||
// notest
|
||||
arg := make([]any, 1)
|
||||
if mode >= 0 {
|
||||
arg[0] = mode > 0
|
||||
} else {
|
||||
arg = arg[:0]
|
||||
}
|
||||
res, err := c.Conn.FileControl(schema, sqlite3.FCNTL_PERSIST_WAL, arg...)
|
||||
if res == true {
|
||||
return 1, err
|
||||
}
|
||||
return 0, err
|
||||
}
|
||||
|
||||
type stmt struct {
|
||||
*sqlite3.Stmt
|
||||
tmWrite sqlite3.TimeFormat
|
||||
|
||||
@@ -44,8 +44,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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
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,9 +15,9 @@ 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/352b363a5d727047
|
||||
curl -#L https://github.com/sqlite/sqlite/archive/dbd613c.tar.gz | tar xz --strip-components=1
|
||||
# curl -#L https://sqlite.org/src/tarball/sqlite.tar.gz?r=352b363a5d | 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"
|
||||
@@ -58,6 +58,8 @@ 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)
|
||||
|
||||
@@ -67,4 +69,4 @@ cd ~-
|
||||
--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.1
|
||||
|
||||
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.10.1 // indirect
|
||||
golang.org/x/sys v0.38.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.1 h1:pHC3YsyRdJv4pCMB4MO1Q2BXw/CAa+Hoj7GSaKtVk+g=
|
||||
github.com/ncruces/go-sqlite3 v0.30.1/go.mod h1:UVsWrQaq1qkcal5/vT5lOJnZCVlR5rsThKdwidjFsKc=
|
||||
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.10.1 h1:2DugeJf6VVk58KTPszlNfeeN8AhhpwcZqkJj2wwFuH8=
|
||||
github.com/tetratelabs/wazero v1.10.1/go.mod h1:DRm5twOQ5Gr1AoEdSi0CLjDQF1J9ZAuyqFIjl1KKfQU=
|
||||
golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
|
||||
golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k=
|
||||
golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM=
|
||||
|
||||
@@ -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.0" {
|
||||
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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
10
go.mod
10
go.mod
@@ -6,17 +6,17 @@ 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/tetratelabs/wazero v1.10.1
|
||||
golang.org/x/sys v0.38.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.45.0 // vfs/adiantum vfs/xts
|
||||
golang.org/x/sync v0.18.0 // test
|
||||
golang.org/x/text v0.31.0 // ext/unicode
|
||||
lukechampine.com/adiantum v1.1.1 // vfs/adiantum
|
||||
)
|
||||
|
||||
|
||||
20
go.sum
20
go.sum
@@ -10,15 +10,15 @@ github.com/ncruces/wbt v0.2.0 h1:Q9zlKOBSZc7Yy/R2cGa35g6RKUUE3BjNIW3tfGC4F04=
|
||||
github.com/ncruces/wbt v0.2.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.10.1 h1:2DugeJf6VVk58KTPszlNfeeN8AhhpwcZqkJj2wwFuH8=
|
||||
github.com/tetratelabs/wazero v1.10.1/go.mod h1:DRm5twOQ5Gr1AoEdSi0CLjDQF1J9ZAuyqFIjl1KKfQU=
|
||||
golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q=
|
||||
golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4=
|
||||
golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I=
|
||||
golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||
golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
|
||||
golang.org/x/sys v0.38.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=
|
||||
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.1
|
||||
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.10.1 // indirect
|
||||
golang.org/x/sys v0.38.0 // indirect
|
||||
golang.org/x/text v0.31.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.1 h1:pHC3YsyRdJv4pCMB4MO1Q2BXw/CAa+Hoj7GSaKtVk+g=
|
||||
github.com/ncruces/go-sqlite3 v0.30.1/go.mod h1:UVsWrQaq1qkcal5/vT5lOJnZCVlR5rsThKdwidjFsKc=
|
||||
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.10.1 h1:2DugeJf6VVk58KTPszlNfeeN8AhhpwcZqkJj2wwFuH8=
|
||||
github.com/tetratelabs/wazero v1.10.1/go.mod h1:DRm5twOQ5Gr1AoEdSi0CLjDQF1J9ZAuyqFIjl1KKfQU=
|
||||
golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
|
||||
golang.org/x/sys v0.38.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=
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
11
litestream/README.md
Normal file
11
litestream/README.md
Normal file
@@ -0,0 +1,11 @@
|
||||
# Litestream lightweight read-replicas
|
||||
|
||||
This package implements the **EXPERIMENTAL** `"litestream"` SQLite VFS
|
||||
that offers Litestream [lightweight read-replicas](https://fly.io/blog/litestream-revamped/#lightweight-read-replicas).
|
||||
|
||||
See the [example](vfs_test.go) for how to use.
|
||||
|
||||
To improve performance,
|
||||
increase `PollInterval` (and `MinLevel`) as much as you can,
|
||||
and set [`PRAGMA cache_size=N`](https://www.sqlite.org/pragma.html#pragma_cache_size)
|
||||
(or use `_pragma=cache_size(N)`).
|
||||
78
litestream/api.go
Normal file
78
litestream/api.go
Normal file
@@ -0,0 +1,78 @@
|
||||
// Package litestream implements a Litestream lightweight read-replica VFS.
|
||||
package litestream
|
||||
|
||||
import (
|
||||
"log/slog"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/benbjohnson/litestream"
|
||||
|
||||
"github.com/ncruces/go-sqlite3/vfs"
|
||||
)
|
||||
|
||||
const (
|
||||
// The default poll interval.
|
||||
DefaultPollInterval = 1 * time.Second
|
||||
|
||||
// The default cache size: 10 MiB.
|
||||
DefaultCacheSize = 10 * 1024 * 1024
|
||||
)
|
||||
|
||||
func init() {
|
||||
vfs.Register("litestream", liteVFS{})
|
||||
}
|
||||
|
||||
var (
|
||||
liteMtx sync.RWMutex
|
||||
// +checklocks:liteMtx
|
||||
liteDBs = map[string]*liteDB{}
|
||||
)
|
||||
|
||||
// ReplicaOptions represents options for [NewReplica].
|
||||
type ReplicaOptions struct {
|
||||
// Where to log error messages. May be nil.
|
||||
Logger *slog.Logger
|
||||
|
||||
// Replica poll interval.
|
||||
// Should be less than the compaction interval
|
||||
// used by the replica at MinLevel+1.
|
||||
PollInterval time.Duration
|
||||
|
||||
// Minimum compaction level to track.
|
||||
MinLevel int
|
||||
|
||||
// CacheSize is the maximum size of the page cache in bytes.
|
||||
// Zero means DefaultCacheSize, negative disables caching.
|
||||
CacheSize int
|
||||
}
|
||||
|
||||
// NewReplica creates a read-replica from a Litestream client.
|
||||
func NewReplica(name string, client litestream.ReplicaClient, options ReplicaOptions) {
|
||||
if options.Logger != nil {
|
||||
options.Logger = options.Logger.With("name", name)
|
||||
} else {
|
||||
options.Logger = slog.New(slog.DiscardHandler)
|
||||
}
|
||||
if options.PollInterval <= 0 {
|
||||
options.PollInterval = DefaultPollInterval
|
||||
}
|
||||
if options.CacheSize == 0 {
|
||||
options.CacheSize = DefaultCacheSize
|
||||
}
|
||||
|
||||
liteMtx.Lock()
|
||||
defer liteMtx.Unlock()
|
||||
liteDBs[name] = &liteDB{
|
||||
client: client,
|
||||
opts: options,
|
||||
cache: pageCache{size: options.CacheSize},
|
||||
}
|
||||
}
|
||||
|
||||
// RemoveReplica removes a replica by name.
|
||||
func RemoveReplica(name string) {
|
||||
liteMtx.Lock()
|
||||
defer liteMtx.Unlock()
|
||||
delete(liteDBs, name)
|
||||
}
|
||||
72
litestream/cache.go
Normal file
72
litestream/cache.go
Normal file
@@ -0,0 +1,72 @@
|
||||
package litestream
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"sync"
|
||||
|
||||
"golang.org/x/sync/singleflight"
|
||||
|
||||
"github.com/superfly/ltx"
|
||||
)
|
||||
|
||||
type pageCache struct {
|
||||
single singleflight.Group
|
||||
pages map[uint32]cachedPage // +checklocks:mtx
|
||||
size int
|
||||
mtx sync.Mutex
|
||||
}
|
||||
|
||||
type cachedPage struct {
|
||||
data []byte
|
||||
txid ltx.TXID
|
||||
}
|
||||
|
||||
func (c *pageCache) getOrFetch(pgno uint32, maxTXID ltx.TXID, fetch func() (any, error)) ([]byte, error) {
|
||||
if c.size >= 0 {
|
||||
c.mtx.Lock()
|
||||
if c.pages == nil {
|
||||
c.pages = map[uint32]cachedPage{}
|
||||
}
|
||||
page := c.pages[pgno]
|
||||
c.mtx.Unlock()
|
||||
|
||||
if page.txid == maxTXID {
|
||||
return page.data, nil
|
||||
}
|
||||
}
|
||||
|
||||
var key [12]byte
|
||||
binary.LittleEndian.PutUint32(key[0:], pgno)
|
||||
binary.LittleEndian.PutUint64(key[4:], uint64(maxTXID))
|
||||
v, err, _ := c.single.Do(string(key[:]), fetch)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
page := cachedPage{v.([]byte), maxTXID}
|
||||
if c.size >= 0 {
|
||||
c.mtx.Lock()
|
||||
c.evict(len(page.data))
|
||||
c.pages[pgno] = page
|
||||
c.mtx.Unlock()
|
||||
}
|
||||
return page.data, nil
|
||||
}
|
||||
|
||||
// +checklocks:c.mtx
|
||||
func (c *pageCache) evict(pageSize int) {
|
||||
// Evict random keys until we're under the maximum size.
|
||||
// SQLite has its own page cache, which it will use for each connection.
|
||||
// Since this is a second layer of shared cache,
|
||||
// random eviction is probably good enough.
|
||||
if pageSize*len(c.pages) < c.size {
|
||||
return
|
||||
}
|
||||
for key := range c.pages {
|
||||
delete(c.pages, key)
|
||||
if pageSize*len(c.pages) < c.size {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
48
litestream/example_test.go
Normal file
48
litestream/example_test.go
Normal file
@@ -0,0 +1,48 @@
|
||||
package litestream_test
|
||||
|
||||
import (
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"github.com/benbjohnson/litestream/s3"
|
||||
|
||||
"github.com/ncruces/go-sqlite3/driver"
|
||||
_ "github.com/ncruces/go-sqlite3/embed"
|
||||
"github.com/ncruces/go-sqlite3/litestream"
|
||||
)
|
||||
|
||||
func ExampleNewReplica() {
|
||||
client := s3.NewReplicaClient()
|
||||
client.Bucket = "test-bucket"
|
||||
client.Path = "fruits.db"
|
||||
|
||||
litestream.NewReplica("fruits.db", client, litestream.ReplicaOptions{
|
||||
PollInterval: 5 * time.Second,
|
||||
})
|
||||
|
||||
db, err := driver.Open("file:fruits.db?vfs=litestream")
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
for {
|
||||
time.Sleep(time.Second)
|
||||
rows, err := db.Query("SELECT * FROM fruits")
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
|
||||
for rows.Next() {
|
||||
var name, color string
|
||||
err := rows.Scan(&name, &color)
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
log.Println(name, color)
|
||||
}
|
||||
|
||||
log.Println("===")
|
||||
rows.Close()
|
||||
}
|
||||
}
|
||||
63
litestream/go.mod
Normal file
63
litestream/go.mod
Normal file
@@ -0,0 +1,63 @@
|
||||
module github.com/ncruces/go-sqlite3/litestream
|
||||
|
||||
go 1.24.4
|
||||
|
||||
require (
|
||||
github.com/benbjohnson/litestream v0.5.2
|
||||
github.com/ncruces/go-sqlite3 v0.30.1
|
||||
github.com/ncruces/wbt v0.2.0
|
||||
github.com/superfly/ltx v0.5.0
|
||||
)
|
||||
|
||||
// github.com/ncruces/go-sqlite3
|
||||
require (
|
||||
github.com/ncruces/julianday v1.0.0 // indirect
|
||||
github.com/tetratelabs/wazero v1.10.1 // indirect
|
||||
golang.org/x/sys v0.38.0 // indirect
|
||||
)
|
||||
|
||||
// github.com/superfly/ltx
|
||||
require github.com/pierrec/lz4/v4 v4.1.22 // indirect
|
||||
|
||||
// github.com/benbjohnson/litestream
|
||||
require (
|
||||
filippo.io/age v1.2.1 // indirect
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||
github.com/kr/text v0.2.0 // indirect
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||
github.com/prometheus/client_golang v1.23.2 // indirect
|
||||
github.com/prometheus/client_model v0.6.2 // indirect
|
||||
github.com/prometheus/common v0.67.3 // indirect
|
||||
github.com/prometheus/procfs v0.19.2 // indirect
|
||||
github.com/psanford/sqlite3vfs v0.0.0-20240315230605-24e1d98cf361 // indirect
|
||||
go.yaml.in/yaml/v2 v2.4.3 // indirect
|
||||
golang.org/x/crypto v0.45.0 // indirect
|
||||
google.golang.org/protobuf v1.36.10 // indirect
|
||||
modernc.org/sqlite v1.40.1 // indirect
|
||||
)
|
||||
|
||||
// github.com/benbjohnson/litestream/s3
|
||||
require (
|
||||
github.com/aws/aws-sdk-go-v2 v1.37.1 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.0 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/config v1.30.2 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.18.2 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.1 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.18.2 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.1 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.1 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.1 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.0 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.8.1 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.1 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.1 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.85.1 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.26.1 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.31.1 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.35.1 // indirect
|
||||
github.com/aws/smithy-go v1.22.5 // indirect
|
||||
)
|
||||
|
||||
replace modernc.org/sqlite => github.com/ncruces/go-sqlite3/litestream/modernc v0.0.0-20251109124432-99b097de3b79
|
||||
197
litestream/go.sum
Normal file
197
litestream/go.sum
Normal file
@@ -0,0 +1,197 @@
|
||||
c2sp.org/CCTV/age v0.0.0-20240306222714-3ec4d716e805 h1:u2qwJeEvnypw+OCPUHmoZE3IqwfuN5kgDfo5MLzpNM0=
|
||||
c2sp.org/CCTV/age v0.0.0-20240306222714-3ec4d716e805/go.mod h1:FomMrUJ2Lxt5jCLmZkG3FHa72zUprnhd3v/Z18Snm4w=
|
||||
cloud.google.com/go v0.111.0 h1:YHLKNupSD1KqjDbQ3+LVdQ81h/UJbJyZG203cEfnQgM=
|
||||
cloud.google.com/go v0.111.0/go.mod h1:0mibmpKP1TyOOFYQY5izo0LnT+ecvOQ0Sg3OdmMiNRU=
|
||||
cloud.google.com/go/compute v1.23.3 h1:6sVlXXBmbd7jNX0Ipq0trII3e4n1/MsADLK6a+aiVlk=
|
||||
cloud.google.com/go/compute v1.23.3/go.mod h1:VCgBUoMnIVIR0CscqQiPJLAG25E3ZRZMzcFZeQ+h8CI=
|
||||
cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY=
|
||||
cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA=
|
||||
cloud.google.com/go/iam v1.1.5 h1:1jTsCu4bcsNsE4iiqNT5SHwrDRCfRmIaaaVFhRveTJI=
|
||||
cloud.google.com/go/iam v1.1.5/go.mod h1:rB6P/Ic3mykPbFio+vo7403drjlgvoWfYpJhMXEbzv8=
|
||||
cloud.google.com/go/storage v1.36.0 h1:P0mOkAcaJxhCTvAkMhxMfrTKiNcub4YmmPBtlhAyTr8=
|
||||
cloud.google.com/go/storage v1.36.0/go.mod h1:M6M/3V/D3KpzMTJyPOR/HU6n2Si5QdaXYEsng2xgOs8=
|
||||
filippo.io/age v1.2.1 h1:X0TZjehAZylOIj4DubWYU1vWQxv9bJpo+Uu2/LGhi1o=
|
||||
filippo.io/age v1.2.1/go.mod h1:JL9ew2lTN+Pyft4RiNGguFfOpewKwSHm5ayKD/A4004=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.2 h1:Hr5FTipp7SL07o2FvoVOX9HRiRH3CR3Mj8pxqCcdD5A=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.2/go.mod h1:QyVsSSN64v5TGltphKLQ2sQxe4OBQg0J1eKRcVBnfgE=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.11.0 h1:MhRfI58HblXzCtWEZCO0feHs8LweePB3s90r7WaR1KU=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.11.0/go.mod h1:okZ+ZURbArNdlJ+ptXoyHNuOETzOl1Oww19rm8I2WLA=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2 h1:9iefClla7iYpfYWdzPCRDozdmndjTm8DXdpCzPajMgA=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2/go.mod h1:XtLgD3ZD34DAaVIIAyG3objl5DynM3CQ/vMcbBNJZGI=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.6.2 h1:FwladfywkNirM+FZYLBR2kBz5C8Tg0fw5w5Y7meRXWI=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.6.2/go.mod h1:vv5Ad0RrIoT1lJFdWBZwt4mB1+j+V8DUroixmKDTCdk=
|
||||
github.com/AzureAD/microsoft-authentication-library-for-go v1.4.2 h1:oygO0locgZJe7PpYPXT5A29ZkwJaPqcva7BVeemZOZs=
|
||||
github.com/AzureAD/microsoft-authentication-library-for-go v1.4.2/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI=
|
||||
github.com/aws/aws-sdk-go-v2 v1.37.1 h1:SMUxeNz3Z6nqGsXv0JuJXc8w5YMtrQMuIBmDx//bBDY=
|
||||
github.com/aws/aws-sdk-go-v2 v1.37.1/go.mod h1:9Q0OoGQoboYIAJyslFyF1f5K1Ryddop8gqMhWx/n4Wg=
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.0 h1:6GMWV6CNpA/6fbFHnoAjrv4+LGfyTqZz2LtCHnspgDg=
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.0/go.mod h1:/mXlTIVG9jbxkqDnr5UQNQxW1HRYxeGklkM9vAFeabg=
|
||||
github.com/aws/aws-sdk-go-v2/config v1.30.2 h1:YE1BmSc4fFYqFgN1mN8uzrtc7R9x+7oSWeX8ckoltAw=
|
||||
github.com/aws/aws-sdk-go-v2/config v1.30.2/go.mod h1:UNrLGZ6jfAVjgVJpkIxjLufRJqTXCVYOpkeVf83kwBo=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.18.2 h1:mfm0GKY/PHLhs7KO0sUaOtFnIQ15Qqxt+wXbO/5fIfs=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.18.2/go.mod h1:v0SdJX6ayPeZFQxgXUKw5RhLpAoZUuynxWDfh8+Eknc=
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.1 h1:owmNBboeA0kHKDcdF8KiSXmrIuXZustfMGGytv6OMkM=
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.1/go.mod h1:Bg1miN59SGxrZqlP8vJZSmXW+1N8Y1MjQDq1OfuNod8=
|
||||
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.18.2 h1:YFX4DvH1CPQXgQR8935b46Om+L7+6jus4aTdKqyDR84=
|
||||
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.18.2/go.mod h1:DgMPy7GqxcV0RSyaITnI3rw8HC3lIHB87U3KPQKDxHg=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.1 h1:ksZXBYv80EFTcgc8OJO48aQ8XDWXIQL7gGasPeCoTzI=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.1/go.mod h1:HSksQyyJETVZS7uM54cir0IgxttTD+8aEoJMPGepHBI=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.1 h1:+dn/xF/05utS7tUhjIcndbuaPjfll2LhbH1cCDGLYUQ=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.1/go.mod h1:hyAGz30LHdm5KBZDI58MXx5lDVZ5CUfvfTZvMu4HCZo=
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3 h1:bIqFDwgGXXN1Kpp99pDOdKMTTb5d2KyU5X/BZxjOkRo=
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3/go.mod h1:H5O/EsxDWyU+LP/V8i5sm8cxoZgc2fdNR9bxlOFrQTo=
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.1 h1:4HbnOGE9491a9zYJ9VpPh1ApgEq6ZlD4Kuv1PJenFpc=
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.1/go.mod h1:Z6QnHC6TmpJWUxAy8FI4JzA7rTwl6EIANkyK9OR5z5w=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.0 h1:6+lZi2JeGKtCraAj1rpoZfKqnQ9SptseRZioejfUOLM=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.0/go.mod h1:eb3gfbVIxIoGgJsi9pGne19dhCBpK6opTYpQqAmdy44=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.8.1 h1:ps3nrmBWdWwakZBydGX1CxeYFK80HsQ79JLMwm7Y4/c=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.8.1/go.mod h1:bAdfrfxENre68Hh2swNaGEVuFYE74o0SaSCAlaG9E74=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.1 h1:ky79ysLMxhwk5rxJtS+ILd3Mc8kC5fhsLBrP27r6h4I=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.1/go.mod h1:+2MmkvFvPYM1vsozBWduoLJUi5maxFk5B7KJFECujhY=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.1 h1:MdVYlN5pcQu1t1OYx4Ajo3fKl1IEhzgdPQbYFCRjYS8=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.1/go.mod h1:iikmNLrvHm2p4a3/4BPeix2S9P+nW8yM1IZW73x8bFA=
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.85.1 h1:Hsqo8+dFxSdDvv9B2PgIx1AJAnDpqgS0znVI+R+MoGY=
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.85.1/go.mod h1:8Q0TAPXD68Z8YqlcIGHs/UNIDHsxErV9H4dl4vJEpgw=
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.26.1 h1:uWaz3DoNK9MNhm7i6UGxqufwu3BEuJZm72WlpGwyVtY=
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.26.1/go.mod h1:ILpVNjL0BO+Z3Mm0SbEeUoYS9e0eJWV1BxNppp0fcb8=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.31.1 h1:XdG6/o1/ZDmn3wJU5SRAejHaWgKS4zHv0jBamuKuS2k=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.31.1/go.mod h1:oiotGTKadCOCl3vg/tYh4k45JlDF81Ka8rdumNhEnIQ=
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.35.1 h1:iF4Xxkc0H9c/K2dS0zZw3SCkj0Z7n6AMnUiiyoJND+I=
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.35.1/go.mod h1:0bxIatfN0aLq4mjoLDeBpOjOke68OsFlXPDFJ7V0MYw=
|
||||
github.com/aws/smithy-go v1.22.5 h1:P9ATCXPMb2mPjYBgueqJNCA5S9UfktsW0tTxi+a7eqw=
|
||||
github.com/aws/smithy-go v1.22.5/go.mod h1:t1ufH5HMublsJYulve2RKmHDC15xu1f26kHCp/HgceI=
|
||||
github.com/benbjohnson/litestream v0.5.2 h1:uD9I17n6RgUgyCwPM/Sw2YXNmMGixecUB5kmJ4FL08o=
|
||||
github.com/benbjohnson/litestream v0.5.2/go.mod h1:jSW6AGqbxmJnEXGjMHchlZclGphzbJ6jGrGo5fYIDhU=
|
||||
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
|
||||
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
||||
github.com/go-logr/logr v1.3.0 h1:2y3SDp0ZXuc6/cjLSZ+Q3ir+QB9T/iG5yYRXqsagWSY=
|
||||
github.com/go-logr/logr v1.3.0/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||
github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo=
|
||||
github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE=
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
|
||||
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||
github.com/google/s2a-go v0.1.7 h1:60BLSyTrOV4/haCDW4zb1guZItoSq8foHCXrAnjBo/o=
|
||||
github.com/google/s2a-go v0.1.7/go.mod h1:50CgR4k1jNlWBu4UfS4AcfhVe1r6pdZPygJ3R8F0Qdw=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.2 h1:Vie5ybvEvT75RniqhfFxPRy3Bf7vr3h0cechB90XaQs=
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.2/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0=
|
||||
github.com/googleapis/gax-go/v2 v2.12.0 h1:A+gCJKdRfqXkr+BIRGtZLibNXf0m1f9E4HG56etFpas=
|
||||
github.com/googleapis/gax-go/v2 v2.12.0/go.mod h1:y+aIqrI5eb1YGMVJfuV3185Ts/D7qKpsEkdD5+I6QGU=
|
||||
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
|
||||
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
|
||||
github.com/kr/fs v0.1.0 h1:Jskdu9ieNAYnjxsi0LbQp1ulIKZV1LAFgK1tWhpZgl8=
|
||||
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
|
||||
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
|
||||
github.com/mattn/go-sqlite3 v1.14.8/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
|
||||
github.com/mattn/go-sqlite3 v1.14.19 h1:fhGleo2h1p8tVChob4I9HpmVFIAkKGpiukdrgQbWfGI=
|
||||
github.com/mattn/go-sqlite3 v1.14.19/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
||||
github.com/nats-io/nats.go v1.44.0 h1:ECKVrDLdh/kDPV1g0gAQ+2+m2KprqZK5O/eJAyAnH2M=
|
||||
github.com/nats-io/nats.go v1.44.0/go.mod h1:iRWIPokVIFbVijxuMQq4y9ttaBTMe0SFdlZfMDd+33g=
|
||||
github.com/nats-io/nkeys v0.4.11 h1:q44qGV008kYd9W1b1nEBkNzvnWxtRSQ7A8BoqRrcfa0=
|
||||
github.com/nats-io/nkeys v0.4.11/go.mod h1:szDimtgmfOi9n25JpfIdGw12tZFYXqhGxjhVxsatHVE=
|
||||
github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw=
|
||||
github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
|
||||
github.com/ncruces/go-sqlite3 v0.30.1 h1:pHC3YsyRdJv4pCMB4MO1Q2BXw/CAa+Hoj7GSaKtVk+g=
|
||||
github.com/ncruces/go-sqlite3 v0.30.1/go.mod h1:UVsWrQaq1qkcal5/vT5lOJnZCVlR5rsThKdwidjFsKc=
|
||||
github.com/ncruces/go-sqlite3/litestream/modernc v0.0.0-20251109124432-99b097de3b79 h1:evpQceUV2vRbOe84U/QhBBchfqFERRHTx1JOadFFMLE=
|
||||
github.com/ncruces/go-sqlite3/litestream/modernc v0.0.0-20251109124432-99b097de3b79/go.mod h1:GSM2gXEOb9HIFFtsl0IUtnpvpDmVi7Kbp8z5GzwA0Tw=
|
||||
github.com/ncruces/julianday v1.0.0 h1:fH0OKwa7NWvniGQtxdJRxAgkBMolni2BjDHaWTxqt7M=
|
||||
github.com/ncruces/julianday v1.0.0/go.mod h1:Dusn2KvZrrovOMJuOt0TNXL6tB7U2E8kvza5fFc9G7g=
|
||||
github.com/ncruces/wbt v0.2.0 h1:Q9zlKOBSZc7Yy/R2cGa35g6RKUUE3BjNIW3tfGC4F04=
|
||||
github.com/ncruces/wbt v0.2.0/go.mod h1:DtF92amvMxH69EmBFUSFWRDAlo6hOEfoNQnClxj9C/c=
|
||||
github.com/pierrec/lz4/v4 v4.1.22 h1:cKFw6uJDK+/gfw5BcDL0JL5aBsAFdsIT18eRtLj7VIU=
|
||||
github.com/pierrec/lz4/v4 v4.1.22/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
|
||||
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ=
|
||||
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU=
|
||||
github.com/pkg/sftp v1.13.6 h1:JFZT4XbOU7l77xGSpOdW+pwIMqP044IyjXX6FGyEKFo=
|
||||
github.com/pkg/sftp v1.13.6/go.mod h1:tz1ryNURKu77RL+GuCzmoJYxQczL3wLNNpPWagdg4Qk=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o=
|
||||
github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg=
|
||||
github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
|
||||
github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
|
||||
github.com/prometheus/common v0.67.3 h1:shd26MlnwTw5jksTDhC7rTQIteBxy+ZZDr3t7F2xN2Q=
|
||||
github.com/prometheus/common v0.67.3/go.mod h1:gP0fq6YjjNCLssJCQp0yk4M8W6ikLURwkdd/YKtTbyI=
|
||||
github.com/prometheus/procfs v0.19.2 h1:zUMhqEW66Ex7OXIiDkll3tl9a1ZdilUOd/F6ZXw4Vws=
|
||||
github.com/prometheus/procfs v0.19.2/go.mod h1:M0aotyiemPhBCM0z5w87kL22CxfcH05ZpYlu+b4J7mw=
|
||||
github.com/psanford/sqlite3vfs v0.0.0-20240315230605-24e1d98cf361 h1:vAKifIJuYY306ZJSrwDgKonWcJGELijdaenABqbV03E=
|
||||
github.com/psanford/sqlite3vfs v0.0.0-20240315230605-24e1d98cf361/go.mod h1:iW4cSew5PAb1sMZiTEkVJAIBNrepaB6jTYjeP47WtI0=
|
||||
github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
|
||||
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
|
||||
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||
github.com/superfly/ltx v0.5.0 h1:dXNrcT3ZtMb6iKZopIV7z5UBscnapg0b0F02loQsk5o=
|
||||
github.com/superfly/ltx v0.5.0/go.mod h1:Nf50QAIXU/ET4ua3AuQ2fh31MbgNQZA7r/DYx6Os77s=
|
||||
github.com/tetratelabs/wazero v1.10.1 h1:2DugeJf6VVk58KTPszlNfeeN8AhhpwcZqkJj2wwFuH8=
|
||||
github.com/tetratelabs/wazero v1.10.1/go.mod h1:DRm5twOQ5Gr1AoEdSi0CLjDQF1J9ZAuyqFIjl1KKfQU=
|
||||
go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
|
||||
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.46.1 h1:SpGay3w+nEwMpfVnbqOLH5gY52/foP8RE8UzTZ1pdSE=
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.46.1/go.mod h1:4UoMYEZOC0yN/sPGH76KPkkU7zgiEWYWL9vwmbnTJPE=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.46.1 h1:aFJWCqJMNjENlcleuuOkGAPH82y0yULBScfXcIEdS24=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.46.1/go.mod h1:sEGXWArGqc3tVa+ekntsN65DmVbVeW+7lTKTjZF3/Fo=
|
||||
go.opentelemetry.io/otel v1.21.0 h1:hzLeKBZEL7Okw2mGzZ0cc4k/A7Fta0uoPgaJCr8fsFc=
|
||||
go.opentelemetry.io/otel v1.21.0/go.mod h1:QZzNPQPm1zLX4gZK4cMi+71eaorMSGT3A4znnUvNNEo=
|
||||
go.opentelemetry.io/otel/metric v1.21.0 h1:tlYWfeo+Bocx5kLEloTjbcDwBuELRrIFxwdQ36PlJu4=
|
||||
go.opentelemetry.io/otel/metric v1.21.0/go.mod h1:o1p3CA8nNHW8j5yuQLdc1eeqEaPfzug24uvsyIEJRWM=
|
||||
go.opentelemetry.io/otel/trace v1.21.0 h1:WD9i5gzvoUPuXIXH24ZNBudiarZDKuekPqi/E8fpfLc=
|
||||
go.opentelemetry.io/otel/trace v1.21.0/go.mod h1:LGbsEB0f9LGjN+OZaQQ26sohbOmiMR+BaslueVtS/qQ=
|
||||
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
||||
go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0=
|
||||
go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8=
|
||||
golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q=
|
||||
golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4=
|
||||
golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY=
|
||||
golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU=
|
||||
golang.org/x/oauth2 v0.32.0 h1:jsCblLleRMDrxMN29H3z/k1KliIvpLgCkE6R8FXXNgY=
|
||||
golang.org/x/oauth2 v0.32.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=
|
||||
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.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
|
||||
golang.org/x/sys v0.38.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=
|
||||
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
|
||||
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/api v0.154.0 h1:X7QkVKZBskztmpPKWQXgjJRPA2dJYrL6r+sYPRLj050=
|
||||
google.golang.org/api v0.154.0/go.mod h1:qhSMkM85hgqiokIYsrRyKxrjfBeIhgl4Z2JmeRkYylc=
|
||||
google.golang.org/genproto v0.0.0-20231212172506-995d672761c0 h1:YJ5pD9rF8o9Qtta0Cmy9rdBwkSjrTCT6XTiUQVOtIos=
|
||||
google.golang.org/genproto v0.0.0-20231212172506-995d672761c0/go.mod h1:l/k7rMz0vFTBPy+tFSGvXEd3z+BcoG1k7EHbqm+YBsY=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20231212172506-995d672761c0 h1:s1w3X6gQxwrLEpxnLd/qXTVLgQE2yXwaOaoa6IlY/+o=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20231212172506-995d672761c0/go.mod h1:CAny0tYF+0/9rmDB9fahA9YLzX3+AEVl1qXbv5hhj6c=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20231212172506-995d672761c0 h1:/jFB8jK5R3Sq3i/lmeZO0cATSzFfZaJq1J2Euan3XKU=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20231212172506-995d672761c0/go.mod h1:FUoWkonphQm3RhTS+kOEhF8h0iDpm4tdXolVCeZ9KKA=
|
||||
google.golang.org/grpc v1.60.1 h1:26+wFr+cNqSGFcOXcabYC0lUVJVRa2Sb2ortSK7VrEU=
|
||||
google.golang.org/grpc v1.60.1/go.mod h1:OlCHIeLYqSSsLi6i49B5QGdzaMZK9+M7LXN2FKz4eGM=
|
||||
google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE=
|
||||
google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
11
litestream/modernc/go.mod
Normal file
11
litestream/modernc/go.mod
Normal file
@@ -0,0 +1,11 @@
|
||||
module modernc.org/sqlite
|
||||
|
||||
go 1.24.0
|
||||
|
||||
require github.com/ncruces/go-sqlite3 v0.30.1
|
||||
|
||||
require (
|
||||
github.com/ncruces/julianday v1.0.0 // indirect
|
||||
github.com/tetratelabs/wazero v1.10.1 // indirect
|
||||
golang.org/x/sys v0.38.0 // indirect
|
||||
)
|
||||
10
litestream/modernc/go.sum
Normal file
10
litestream/modernc/go.sum
Normal file
@@ -0,0 +1,10 @@
|
||||
github.com/ncruces/go-sqlite3 v0.30.1 h1:pHC3YsyRdJv4pCMB4MO1Q2BXw/CAa+Hoj7GSaKtVk+g=
|
||||
github.com/ncruces/go-sqlite3 v0.30.1/go.mod h1:UVsWrQaq1qkcal5/vT5lOJnZCVlR5rsThKdwidjFsKc=
|
||||
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.10.1 h1:2DugeJf6VVk58KTPszlNfeeN8AhhpwcZqkJj2wwFuH8=
|
||||
github.com/tetratelabs/wazero v1.10.1/go.mod h1:DRm5twOQ5Gr1AoEdSi0CLjDQF1J9ZAuyqFIjl1KKfQU=
|
||||
golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
|
||||
golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k=
|
||||
golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM=
|
||||
20
litestream/modernc/sqlite.go
Normal file
20
litestream/modernc/sqlite.go
Normal file
@@ -0,0 +1,20 @@
|
||||
// Package sqlite provides a shim that allows Litestream to work with the ncruces SQLite driver.
|
||||
package sqlite
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"slices"
|
||||
|
||||
"github.com/ncruces/go-sqlite3/driver"
|
||||
_ "github.com/ncruces/go-sqlite3/embed"
|
||||
)
|
||||
|
||||
func init() {
|
||||
if !slices.Contains(sql.Drivers(), "sqlite") {
|
||||
sql.Register("sqlite", &driver.SQLite{})
|
||||
}
|
||||
}
|
||||
|
||||
type FileControl interface {
|
||||
FileControlPersistWAL(string, int) (int, error)
|
||||
}
|
||||
309
litestream/vfs.go
Normal file
309
litestream/vfs.go
Normal file
@@ -0,0 +1,309 @@
|
||||
package litestream
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/benbjohnson/litestream"
|
||||
"github.com/superfly/ltx"
|
||||
|
||||
"github.com/ncruces/go-sqlite3"
|
||||
"github.com/ncruces/go-sqlite3/util/vfsutil"
|
||||
"github.com/ncruces/go-sqlite3/vfs"
|
||||
"github.com/ncruces/wbt"
|
||||
)
|
||||
|
||||
type liteVFS struct{}
|
||||
|
||||
func (liteVFS) 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
|
||||
}
|
||||
// Refuse to open all other file types.
|
||||
if flags&vfs.OPEN_MAIN_DB == 0 {
|
||||
return nil, flags, sqlite3.CANTOPEN
|
||||
}
|
||||
|
||||
liteMtx.RLock()
|
||||
defer liteMtx.RUnlock()
|
||||
if db, ok := liteDBs[name]; ok {
|
||||
// Build the page index so we can lookup individual pages.
|
||||
if err := db.buildIndex(context.Background()); err != nil {
|
||||
db.opts.Logger.Error("build index", "error", err)
|
||||
return nil, 0, err
|
||||
}
|
||||
return &liteFile{db: db}, flags | vfs.OPEN_READONLY, nil
|
||||
}
|
||||
return nil, flags, sqlite3.CANTOPEN
|
||||
}
|
||||
|
||||
func (liteVFS) Delete(name string, dirSync bool) error {
|
||||
// notest // used to delete journals
|
||||
return sqlite3.IOERR_DELETE_NOENT
|
||||
}
|
||||
|
||||
func (liteVFS) Access(name string, flag vfs.AccessFlag) (bool, error) {
|
||||
// notest // used to check for journals
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func (liteVFS) FullPathname(name string) (string, error) {
|
||||
return name, nil
|
||||
}
|
||||
|
||||
type liteFile struct {
|
||||
db *liteDB
|
||||
conn *sqlite3.Conn
|
||||
pages *pageIndex
|
||||
txid ltx.TXID
|
||||
pageSize uint32
|
||||
}
|
||||
|
||||
func (f *liteFile) Close() error { return nil }
|
||||
|
||||
func (f *liteFile) ReadAt(p []byte, off int64) (n int, err error) {
|
||||
ctx := f.context()
|
||||
pages, txid := f.pages, f.txid
|
||||
if pages == nil {
|
||||
pages, txid, err = f.db.pollReplica(ctx)
|
||||
}
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
pgno := uint32(1)
|
||||
if off >= 512 {
|
||||
pgno += uint32(off / int64(f.pageSize))
|
||||
}
|
||||
|
||||
elem, ok := pages.Get(pgno)
|
||||
if !ok {
|
||||
return 0, io.EOF
|
||||
}
|
||||
|
||||
data, err := f.db.cache.getOrFetch(pgno, elem.MaxTXID, func() (any, error) {
|
||||
_, data, err := litestream.FetchPage(ctx, f.db.client, elem.Level, elem.MinTXID, elem.MaxTXID, elem.Offset, elem.Size)
|
||||
return data, err
|
||||
})
|
||||
if err != nil {
|
||||
f.db.opts.Logger.Error("fetch page", "error", err)
|
||||
return 0, err
|
||||
}
|
||||
|
||||
// Update the first page to pretend we are in journal mode,
|
||||
// load the page size and track changes to the database.
|
||||
if pgno == 1 && len(data) >= 100 &&
|
||||
data[18] >= 1 && data[19] >= 1 &&
|
||||
data[18] <= 3 && data[19] <= 3 {
|
||||
data[18], data[19] = 0x01, 0x01
|
||||
binary.BigEndian.PutUint32(data[24:28], uint32(txid))
|
||||
f.pageSize = uint32(256 * binary.LittleEndian.Uint16(data[16:18]))
|
||||
}
|
||||
|
||||
n = copy(p, data[off%int64(len(data)):])
|
||||
return n, nil
|
||||
}
|
||||
|
||||
func (f *liteFile) WriteAt(b []byte, off int64) (n int, err error) {
|
||||
// notest // OPEN_READONLY
|
||||
return 0, sqlite3.IOERR_WRITE
|
||||
}
|
||||
|
||||
func (f *liteFile) Truncate(size int64) error {
|
||||
// notest // OPEN_READONLY
|
||||
return sqlite3.IOERR_TRUNCATE
|
||||
}
|
||||
|
||||
func (f *liteFile) Sync(flag vfs.SyncFlag) error {
|
||||
// notest // OPEN_READONLY
|
||||
return sqlite3.IOERR_FSYNC
|
||||
}
|
||||
|
||||
func (f *liteFile) Size() (size int64, err error) {
|
||||
if max := f.pages.Max(); max != nil {
|
||||
size = int64(max.Key()) * int64(f.pageSize)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (f *liteFile) Lock(lock vfs.LockLevel) (err error) {
|
||||
if lock >= vfs.LOCK_RESERVED {
|
||||
return sqlite3.IOERR_LOCK
|
||||
}
|
||||
f.pages, f.txid, err = f.db.pollReplica(f.context())
|
||||
return err
|
||||
}
|
||||
|
||||
func (f *liteFile) Unlock(lock vfs.LockLevel) error {
|
||||
f.pages, f.txid = nil, 0
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *liteFile) CheckReservedLock() (bool, error) {
|
||||
// notest // used to check for hot journals
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func (f *liteFile) SectorSize() int {
|
||||
// notest // safe default
|
||||
return 0
|
||||
}
|
||||
|
||||
func (f *liteFile) DeviceCharacteristics() vfs.DeviceCharacteristic {
|
||||
// notest // safe default
|
||||
return 0
|
||||
}
|
||||
|
||||
func (f *liteFile) SetDB(conn any) {
|
||||
f.conn = conn.(*sqlite3.Conn)
|
||||
}
|
||||
|
||||
func (f *liteFile) context() context.Context {
|
||||
if f.conn != nil {
|
||||
return f.conn.GetInterrupt()
|
||||
}
|
||||
return context.Background()
|
||||
}
|
||||
|
||||
type liteDB struct {
|
||||
client litestream.ReplicaClient
|
||||
opts ReplicaOptions
|
||||
cache pageCache
|
||||
pages *pageIndex // +checklocks:mtx
|
||||
lastPoll time.Time // +checklocks:mtx
|
||||
txids levelTXIDs // +checklocks:mtx
|
||||
mtx sync.Mutex
|
||||
}
|
||||
|
||||
func (f *liteDB) buildIndex(ctx context.Context) error {
|
||||
f.mtx.Lock()
|
||||
defer f.mtx.Unlock()
|
||||
|
||||
// Skip if we already have an index.
|
||||
if f.pages != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Build the index from scratch from a Litestream restore plan.
|
||||
infos, err := litestream.CalcRestorePlan(ctx, f.client, 0, time.Time{}, f.opts.Logger)
|
||||
if err != nil {
|
||||
if !errors.Is(err, litestream.ErrTxNotAvailable) {
|
||||
return fmt.Errorf("calc restore plan: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
for _, info := range infos {
|
||||
err := f.updateInfo(ctx, info)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
f.lastPoll = time.Now()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *liteDB) pollReplica(ctx context.Context) (*pageIndex, ltx.TXID, error) {
|
||||
f.mtx.Lock()
|
||||
defer f.mtx.Unlock()
|
||||
|
||||
// Limit polling interval.
|
||||
if time.Since(f.lastPoll) < f.opts.PollInterval {
|
||||
return f.pages, f.txids[0], nil
|
||||
}
|
||||
|
||||
for level := range pollLevels(f.opts.MinLevel) {
|
||||
if err := f.updateLevel(ctx, level); err != nil {
|
||||
f.opts.Logger.Error("cannot poll replica", "error", err)
|
||||
return nil, 0, err
|
||||
}
|
||||
}
|
||||
|
||||
f.lastPoll = time.Now()
|
||||
return f.pages, f.txids[0], nil
|
||||
}
|
||||
|
||||
// +checklocks:f.mtx
|
||||
func (f *liteDB) updateLevel(ctx context.Context, level int) error {
|
||||
var nextTXID ltx.TXID
|
||||
// Snapshots must start from scratch,
|
||||
// other levels can start from where they were left.
|
||||
if level != litestream.SnapshotLevel {
|
||||
nextTXID = f.txids[level] + 1
|
||||
}
|
||||
|
||||
// Start reading from the next LTX file after the current position.
|
||||
itr, err := f.client.LTXFiles(ctx, level, nextTXID, false)
|
||||
if err != nil {
|
||||
return fmt.Errorf("ltx files: %w", err)
|
||||
}
|
||||
defer itr.Close()
|
||||
|
||||
// Build an update across all new LTX files.
|
||||
for itr.Next() {
|
||||
info := itr.Item()
|
||||
|
||||
// Skip LTX files already fully loaded into the index.
|
||||
if info.MaxTXID <= f.txids[level] {
|
||||
continue
|
||||
}
|
||||
|
||||
err := f.updateInfo(ctx, info)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if err := itr.Err(); err != nil {
|
||||
return err
|
||||
}
|
||||
return itr.Close()
|
||||
}
|
||||
|
||||
// +checklocks:f.mtx
|
||||
func (f *liteDB) updateInfo(ctx context.Context, info *ltx.FileInfo) error {
|
||||
idx, err := litestream.FetchPageIndex(ctx, f.client, info)
|
||||
if err != nil {
|
||||
return fmt.Errorf("fetch page index: %w", err)
|
||||
}
|
||||
|
||||
// Replace pages in the index with new pages.
|
||||
for k, v := range idx {
|
||||
// Patch avoids mutating the index for an unmodified page.
|
||||
f.pages = f.pages.Patch(k, func(node *pageIndex) (ltx.PageIndexElem, bool) {
|
||||
return v, node == nil || v != node.Value()
|
||||
})
|
||||
}
|
||||
|
||||
// Track the MaxTXID for each level.
|
||||
maxTXID := &f.txids[info.Level]
|
||||
*maxTXID = max(*maxTXID, info.MaxTXID)
|
||||
return nil
|
||||
}
|
||||
|
||||
func pollLevels(minLevel int) (r []int) {
|
||||
// Updating from lower to upper levels is non-racy,
|
||||
// since LTX files are compacted into higher levels
|
||||
// before the lower level LTX files are deleted.
|
||||
|
||||
// Also, only level 0 compactions and snapshots delete files,
|
||||
// so the intermediate levels never need to be updated.
|
||||
|
||||
if minLevel <= 0 {
|
||||
return append(r, 0, 1, litestream.SnapshotLevel)
|
||||
}
|
||||
if minLevel >= litestream.SnapshotLevel {
|
||||
return append(r, litestream.SnapshotLevel)
|
||||
}
|
||||
return append(r, minLevel, litestream.SnapshotLevel)
|
||||
}
|
||||
|
||||
// Type aliases; these are a mouthful.
|
||||
type pageIndex = wbt.Tree[uint32, ltx.PageIndexElem]
|
||||
type levelTXIDs = [litestream.SnapshotLevel + 1]ltx.TXID
|
||||
34
litestream/vfs_test.go
Normal file
34
litestream/vfs_test.go
Normal file
@@ -0,0 +1,34 @@
|
||||
package litestream
|
||||
|
||||
import (
|
||||
"slices"
|
||||
"strconv"
|
||||
"testing"
|
||||
|
||||
"github.com/benbjohnson/litestream"
|
||||
|
||||
_ "github.com/ncruces/go-sqlite3/embed"
|
||||
)
|
||||
|
||||
func Test_pollLevels(t *testing.T) {
|
||||
tests := []struct {
|
||||
minLevel int
|
||||
want []int
|
||||
}{
|
||||
{minLevel: -1, want: []int{0, 1, litestream.SnapshotLevel}},
|
||||
{minLevel: 0, want: []int{0, 1, litestream.SnapshotLevel}},
|
||||
{minLevel: 1, want: []int{1, litestream.SnapshotLevel}},
|
||||
{minLevel: 2, want: []int{2, litestream.SnapshotLevel}},
|
||||
{minLevel: 3, want: []int{3, litestream.SnapshotLevel}},
|
||||
{minLevel: litestream.SnapshotLevel, want: []int{litestream.SnapshotLevel}},
|
||||
{minLevel: litestream.SnapshotLevel + 1, want: []int{litestream.SnapshotLevel}},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(strconv.Itoa(tt.minLevel), func(t *testing.T) {
|
||||
got := pollLevels(tt.minLevel)
|
||||
if !slices.Equal(got, tt.want) {
|
||||
t.Errorf("pollLevels() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
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-3510000.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 =~ fa52f9cc74dbca004aa650ae698036a3350611f672649e165078f4eae21d6a2e ]]; 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.0"
|
||||
|
||||
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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
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/go-sqlite3/litestream`](https://pkg.go.dev/github.com/ncruces/go-sqlite3/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"
|
||||
)
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"os"
|
||||
|
||||
"golang.org/x/crypto/argon2"
|
||||
|
||||
"lukechampine.com/adiantum/hbsh"
|
||||
"lukechampine.com/adiantum/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
|
||||
|
||||
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())
|
||||
|
||||
@@ -90,6 +90,7 @@ type mvccFile struct {
|
||||
data *wbt.Tree[int64, string]
|
||||
lock vfs.LockLevel
|
||||
readOnly bool
|
||||
wrflag bool
|
||||
}
|
||||
|
||||
var (
|
||||
@@ -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
|
||||
@@ -244,7 +253,7 @@ 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 {
|
||||
if m.data != nil && m.data != m.mvccDB.data {
|
||||
return sqlite3.BUSY_SNAPSHOT
|
||||
}
|
||||
if time.Since(before) > time.Millisecond {
|
||||
@@ -253,11 +262,15 @@ func (m *mvccFile) Lock(lock vfs.LockLevel) error {
|
||||
m.waiter.Wait()
|
||||
}
|
||||
}
|
||||
// Our snapshot is invalid.
|
||||
if m.data != m.mvccDB.data {
|
||||
switch {
|
||||
case m.data == nil:
|
||||
m.data = m.mvccDB.data
|
||||
case m.data != m.mvccDB.data:
|
||||
// Our snapshot is invalid.
|
||||
return sqlite3.BUSY_SNAPSHOT
|
||||
}
|
||||
// Take ownership.
|
||||
m.wrflag = false
|
||||
m.lock = lock
|
||||
m.owner = m
|
||||
return nil
|
||||
@@ -279,6 +292,9 @@ func (m *mvccFile) Unlock(lock vfs.LockLevel) error {
|
||||
m.waiter.Broadcast()
|
||||
}
|
||||
}
|
||||
if lock == vfs.LOCK_NONE {
|
||||
m.data = nil
|
||||
}
|
||||
m.lock = lock
|
||||
return nil
|
||||
}
|
||||
@@ -299,6 +315,8 @@ func (m *mvccFile) CommitPhaseTwo() error {
|
||||
m.mtx.Lock()
|
||||
defer m.mtx.Unlock()
|
||||
m.mvccDB.data = m.data
|
||||
m.lock = vfs.LOCK_NONE
|
||||
m.data = nil
|
||||
}
|
||||
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}
|
||||
}
|
||||
|
||||
@@ -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,42 +31,48 @@ 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][:]) {
|
||||
if len(s.ptrs) == 0 {
|
||||
return
|
||||
}
|
||||
// Copies modified words from shared to private memory.
|
||||
for id, p := range s.ptrs {
|
||||
shared := shmPage(s.shared[id][:])
|
||||
shadow := shmPage(s.shadow[id][:])
|
||||
privat := shmPage(util.View(s.mod, p, _WALINDEX_PGSZ))
|
||||
for i, shared := range shared {
|
||||
if shadow[i] != shared {
|
||||
shadow[i] = shared
|
||||
privat[i] = shared
|
||||
}
|
||||
}
|
||||
if !shmCopyHeader(
|
||||
util.View(s.mod, s.ptrs[0], _WALINDEX_HDR_SIZE),
|
||||
s.shadow[0][:],
|
||||
s.shared[0][:]) {
|
||||
return
|
||||
}
|
||||
|
||||
skip := _WALINDEX_HDR_SIZE
|
||||
for id := range s.ptrs {
|
||||
shmCopyTables(
|
||||
util.View(s.mod, s.ptrs[id], _WALINDEX_PGSZ)[skip:],
|
||||
s.shadow[id][skip:],
|
||||
s.shared[id][skip:])
|
||||
skip = 0
|
||||
}
|
||||
}
|
||||
|
||||
func (s *vfsShm) shmRelease() {
|
||||
if len(s.ptrs) == 0 || shmEqual(s.shadow[0][:], util.View(s.mod, s.ptrs[0], _WALINDEX_HDR_SIZE)) {
|
||||
if len(s.ptrs) == 0 {
|
||||
return
|
||||
}
|
||||
// Copies modified words from private to shared memory.
|
||||
for id, p := range s.ptrs {
|
||||
shared := shmPage(s.shared[id][:])
|
||||
shadow := shmPage(s.shadow[id][:])
|
||||
privat := shmPage(util.View(s.mod, p, _WALINDEX_PGSZ))
|
||||
for i, privat := range privat {
|
||||
if shadow[i] != privat {
|
||||
shadow[i] = privat
|
||||
shared[i] = privat
|
||||
}
|
||||
}
|
||||
if !shmCopyHeader(
|
||||
s.shared[0][:],
|
||||
s.shadow[0][:],
|
||||
util.View(s.mod, s.ptrs[0], _WALINDEX_HDR_SIZE)) {
|
||||
return
|
||||
}
|
||||
|
||||
skip := _WALINDEX_HDR_SIZE
|
||||
for id := range s.ptrs {
|
||||
shmCopyTables(
|
||||
s.shared[id][skip:],
|
||||
s.shadow[id][skip:],
|
||||
util.View(s.mod, s.ptrs[id], _WALINDEX_PGSZ)[skip:])
|
||||
skip = 0
|
||||
}
|
||||
}
|
||||
|
||||
@@ -77,11 +83,40 @@ func (s *vfsShm) shmBarrier() {
|
||||
s.Unlock()
|
||||
}
|
||||
|
||||
func shmPage(s []byte) *[_WALINDEX_PGSZ / 4]uint32 {
|
||||
p := (*uint32)(unsafe.Pointer(unsafe.SliceData(s)))
|
||||
return (*[_WALINDEX_PGSZ / 4]uint32)(unsafe.Slice(p, _WALINDEX_PGSZ/4))
|
||||
func shmCopyTables(v1, v2, v3 []byte) {
|
||||
if string(v2) != string(v3) {
|
||||
copy(v1, v3)
|
||||
copy(v2, v3)
|
||||
}
|
||||
}
|
||||
|
||||
func shmEqual(v1, v2 []byte) bool {
|
||||
return *(*[_WALINDEX_HDR_SIZE]byte)(v1[:]) == *(*[_WALINDEX_HDR_SIZE]byte)(v2[:])
|
||||
func shmCopyHeader(s1, s2, s3 []byte) (ret bool) {
|
||||
// First copy of the WAL Index Information.
|
||||
if string(s2[:48]) != string(s3[:48]) {
|
||||
copy(s1, s3[:48])
|
||||
copy(s2, s3[:48])
|
||||
ret = true
|
||||
}
|
||||
// Second copy of the WAL Index Information.
|
||||
if string(s2[48:][:48]) != string(s3[48:][:48]) {
|
||||
copy(s1[48:], s3[48:][:48])
|
||||
copy(s2[48:], s3[48:][:48])
|
||||
ret = true
|
||||
}
|
||||
// Checkpoint Information and Locks.
|
||||
i1 := shmCheckpointInfo(s1)
|
||||
i2 := shmCheckpointInfo(s2)
|
||||
for i, i3 := range shmCheckpointInfo(s3) {
|
||||
if i2[i] != i3 {
|
||||
i1[i] = i3
|
||||
i2[i] = i3
|
||||
ret = true
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func shmCheckpointInfo(s []byte) *[10]uint32 {
|
||||
p := (*uint32)(unsafe.Pointer(&s[96]))
|
||||
return (*[10]uint32)(unsafe.Slice(p, 10))
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
Binary file not shown.
Binary file not shown.
94
vfs/vfs.go
94
vfs/vfs.go
@@ -102,7 +102,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 +110,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 +119,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 +134,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 +155,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 +167,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 +178,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 +268,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 +291,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:
|
||||
@@ -338,7 +338,7 @@ func vfsFileControlImpl(ctx context.Context, mod api.Module, file File, op _Fcnt
|
||||
|
||||
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 +407,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 +455,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) {
|
||||
|
||||
51
vtab.go
51
vtab.go
@@ -445,7 +445,8 @@ const (
|
||||
type IndexScanFlag uint32
|
||||
|
||||
const (
|
||||
INDEX_SCAN_UNIQUE IndexScanFlag = 1
|
||||
INDEX_SCAN_UNIQUE IndexScanFlag = 0x00000001
|
||||
INDEX_SCAN_HEX IndexScanFlag = 0x00000002
|
||||
)
|
||||
|
||||
func vtabModuleCallback(i vtabConstructor) func(_ context.Context, _ api.Module, _ ptr_t, _ int32, _, _, _ ptr_t) res_t {
|
||||
@@ -465,19 +466,19 @@ func vtabModuleCallback(i vtabConstructor) func(_ context.Context, _ api.Module,
|
||||
vtabPutHandle(ctx, mod, ppVTab, val[0].Interface())
|
||||
}
|
||||
|
||||
return vtabError(ctx, mod, pzErr, _PTR_ERROR, err)
|
||||
return vtabError(ctx, mod, pzErr, _PTR_ERROR, err, ERROR)
|
||||
}
|
||||
}
|
||||
|
||||
func vtabDisconnectCallback(ctx context.Context, mod api.Module, pVTab ptr_t) res_t {
|
||||
err := vtabDelHandle(ctx, mod, pVTab)
|
||||
return vtabError(ctx, mod, 0, _PTR_ERROR, err)
|
||||
return vtabError(ctx, mod, 0, _PTR_ERROR, err, ERROR)
|
||||
}
|
||||
|
||||
func vtabDestroyCallback(ctx context.Context, mod api.Module, pVTab ptr_t) res_t {
|
||||
vtab := vtabGetHandle(ctx, mod, pVTab).(VTabDestroyer)
|
||||
err := errors.Join(vtab.Destroy(), vtabDelHandle(ctx, mod, pVTab))
|
||||
return vtabError(ctx, mod, 0, _PTR_ERROR, err)
|
||||
return vtabError(ctx, mod, 0, _PTR_ERROR, err, ERROR)
|
||||
}
|
||||
|
||||
func vtabBestIndexCallback(ctx context.Context, mod api.Module, pVTab, pIdxInfo ptr_t) res_t {
|
||||
@@ -490,7 +491,7 @@ func vtabBestIndexCallback(ctx context.Context, mod api.Module, pVTab, pIdxInfo
|
||||
err := vtab.BestIndex(&info)
|
||||
|
||||
info.save()
|
||||
return vtabError(ctx, mod, pVTab, _VTAB_ERROR, err)
|
||||
return vtabError(ctx, mod, pVTab, _VTAB_ERROR, err, ERROR)
|
||||
}
|
||||
|
||||
func vtabUpdateCallback(ctx context.Context, mod api.Module, pVTab ptr_t, nArg int32, pArg, pRowID ptr_t) res_t {
|
||||
@@ -504,13 +505,13 @@ func vtabUpdateCallback(ctx context.Context, mod api.Module, pVTab ptr_t, nArg i
|
||||
util.Write64(mod, pRowID, rowID)
|
||||
}
|
||||
|
||||
return vtabError(ctx, mod, pVTab, _VTAB_ERROR, err)
|
||||
return vtabError(ctx, mod, pVTab, _VTAB_ERROR, err, ERROR)
|
||||
}
|
||||
|
||||
func vtabRenameCallback(ctx context.Context, mod api.Module, pVTab, zNew ptr_t) res_t {
|
||||
vtab := vtabGetHandle(ctx, mod, pVTab).(VTabRenamer)
|
||||
err := vtab.Rename(util.ReadString(mod, zNew, _MAX_NAME))
|
||||
return vtabError(ctx, mod, pVTab, _VTAB_ERROR, err)
|
||||
return vtabError(ctx, mod, pVTab, _VTAB_ERROR, err, ERROR)
|
||||
}
|
||||
|
||||
func vtabFindFuncCallback(ctx context.Context, mod api.Module, pVTab ptr_t, nArg int32, zName, pxFunc ptr_t) int32 {
|
||||
@@ -534,51 +535,49 @@ func vtabIntegrityCallback(ctx context.Context, mod api.Module, pVTab, zSchema,
|
||||
err := vtab.Integrity(schema, table, int(mFlags))
|
||||
// xIntegrity should return OK - even if it finds problems in the content of the virtual table.
|
||||
// https://sqlite.org/vtab.html#xintegrity
|
||||
vtabError(ctx, mod, pzErr, _PTR_ERROR, err)
|
||||
_, code := errorCode(err, _OK)
|
||||
return code
|
||||
return vtabError(ctx, mod, pzErr, _PTR_ERROR, err, _OK)
|
||||
}
|
||||
|
||||
func vtabBeginCallback(ctx context.Context, mod api.Module, pVTab ptr_t) res_t {
|
||||
vtab := vtabGetHandle(ctx, mod, pVTab).(VTabTxn)
|
||||
err := vtab.Begin()
|
||||
return vtabError(ctx, mod, pVTab, _VTAB_ERROR, err)
|
||||
return vtabError(ctx, mod, pVTab, _VTAB_ERROR, err, ERROR)
|
||||
}
|
||||
|
||||
func vtabSyncCallback(ctx context.Context, mod api.Module, pVTab ptr_t) res_t {
|
||||
vtab := vtabGetHandle(ctx, mod, pVTab).(VTabTxn)
|
||||
err := vtab.Sync()
|
||||
return vtabError(ctx, mod, pVTab, _VTAB_ERROR, err)
|
||||
return vtabError(ctx, mod, pVTab, _VTAB_ERROR, err, ERROR)
|
||||
}
|
||||
|
||||
func vtabCommitCallback(ctx context.Context, mod api.Module, pVTab ptr_t) res_t {
|
||||
vtab := vtabGetHandle(ctx, mod, pVTab).(VTabTxn)
|
||||
err := vtab.Commit()
|
||||
return vtabError(ctx, mod, pVTab, _VTAB_ERROR, err)
|
||||
return vtabError(ctx, mod, pVTab, _VTAB_ERROR, err, ERROR)
|
||||
}
|
||||
|
||||
func vtabRollbackCallback(ctx context.Context, mod api.Module, pVTab ptr_t) res_t {
|
||||
vtab := vtabGetHandle(ctx, mod, pVTab).(VTabTxn)
|
||||
err := vtab.Rollback()
|
||||
return vtabError(ctx, mod, pVTab, _VTAB_ERROR, err)
|
||||
return vtabError(ctx, mod, pVTab, _VTAB_ERROR, err, ERROR)
|
||||
}
|
||||
|
||||
func vtabSavepointCallback(ctx context.Context, mod api.Module, pVTab ptr_t, id int32) res_t {
|
||||
vtab := vtabGetHandle(ctx, mod, pVTab).(VTabSavepointer)
|
||||
err := vtab.Savepoint(int(id))
|
||||
return vtabError(ctx, mod, pVTab, _VTAB_ERROR, err)
|
||||
return vtabError(ctx, mod, pVTab, _VTAB_ERROR, err, ERROR)
|
||||
}
|
||||
|
||||
func vtabReleaseCallback(ctx context.Context, mod api.Module, pVTab ptr_t, id int32) res_t {
|
||||
vtab := vtabGetHandle(ctx, mod, pVTab).(VTabSavepointer)
|
||||
err := vtab.Release(int(id))
|
||||
return vtabError(ctx, mod, pVTab, _VTAB_ERROR, err)
|
||||
return vtabError(ctx, mod, pVTab, _VTAB_ERROR, err, ERROR)
|
||||
}
|
||||
|
||||
func vtabRollbackToCallback(ctx context.Context, mod api.Module, pVTab ptr_t, id int32) res_t {
|
||||
vtab := vtabGetHandle(ctx, mod, pVTab).(VTabSavepointer)
|
||||
err := vtab.RollbackTo(int(id))
|
||||
return vtabError(ctx, mod, pVTab, _VTAB_ERROR, err)
|
||||
return vtabError(ctx, mod, pVTab, _VTAB_ERROR, err, ERROR)
|
||||
}
|
||||
|
||||
func cursorOpenCallback(ctx context.Context, mod api.Module, pVTab, ppCur ptr_t) res_t {
|
||||
@@ -589,12 +588,12 @@ func cursorOpenCallback(ctx context.Context, mod api.Module, pVTab, ppCur ptr_t)
|
||||
vtabPutHandle(ctx, mod, ppCur, cursor)
|
||||
}
|
||||
|
||||
return vtabError(ctx, mod, pVTab, _VTAB_ERROR, err)
|
||||
return vtabError(ctx, mod, pVTab, _VTAB_ERROR, err, ERROR)
|
||||
}
|
||||
|
||||
func cursorCloseCallback(ctx context.Context, mod api.Module, pCur ptr_t) res_t {
|
||||
err := vtabDelHandle(ctx, mod, pCur)
|
||||
return vtabError(ctx, mod, 0, _VTAB_ERROR, err)
|
||||
return vtabError(ctx, mod, 0, _PTR_ERROR, err, ERROR)
|
||||
}
|
||||
|
||||
func cursorFilterCallback(ctx context.Context, mod api.Module, pCur ptr_t, idxNum int32, idxStr ptr_t, nArg int32, pArg ptr_t) res_t {
|
||||
@@ -609,7 +608,7 @@ func cursorFilterCallback(ctx context.Context, mod api.Module, pCur ptr_t, idxNu
|
||||
|
||||
cursor := vtabGetHandle(ctx, mod, pCur).(VTabCursor)
|
||||
err := cursor.Filter(int(idxNum), idxName, *args...)
|
||||
return vtabError(ctx, mod, pCur, _CURSOR_ERROR, err)
|
||||
return vtabError(ctx, mod, pCur, _CURSOR_ERROR, err, ERROR)
|
||||
}
|
||||
|
||||
func cursorEOFCallback(ctx context.Context, mod api.Module, pCur ptr_t) int32 {
|
||||
@@ -623,14 +622,14 @@ func cursorEOFCallback(ctx context.Context, mod api.Module, pCur ptr_t) int32 {
|
||||
func cursorNextCallback(ctx context.Context, mod api.Module, pCur ptr_t) res_t {
|
||||
cursor := vtabGetHandle(ctx, mod, pCur).(VTabCursor)
|
||||
err := cursor.Next()
|
||||
return vtabError(ctx, mod, pCur, _CURSOR_ERROR, err)
|
||||
return vtabError(ctx, mod, pCur, _CURSOR_ERROR, err, ERROR)
|
||||
}
|
||||
|
||||
func cursorColumnCallback(ctx context.Context, mod api.Module, pCur, pCtx ptr_t, n int32) res_t {
|
||||
cursor := vtabGetHandle(ctx, mod, pCur).(VTabCursor)
|
||||
db := ctx.Value(connKey{}).(*Conn)
|
||||
err := cursor.Column(Context{db, pCtx}, int(n))
|
||||
return vtabError(ctx, mod, pCur, _CURSOR_ERROR, err)
|
||||
return vtabError(ctx, mod, pCur, _CURSOR_ERROR, err, ERROR)
|
||||
}
|
||||
|
||||
func cursorRowIDCallback(ctx context.Context, mod api.Module, pCur, pRowID ptr_t) res_t {
|
||||
@@ -641,7 +640,7 @@ func cursorRowIDCallback(ctx context.Context, mod api.Module, pCur, pRowID ptr_t
|
||||
util.Write64(mod, pRowID, rowID)
|
||||
}
|
||||
|
||||
return vtabError(ctx, mod, pCur, _CURSOR_ERROR, err)
|
||||
return vtabError(ctx, mod, pCur, _CURSOR_ERROR, err, ERROR)
|
||||
}
|
||||
|
||||
const (
|
||||
@@ -650,10 +649,10 @@ const (
|
||||
_CURSOR_ERROR
|
||||
)
|
||||
|
||||
func vtabError(ctx context.Context, mod api.Module, ptr ptr_t, kind uint32, err error) res_t {
|
||||
func vtabError(ctx context.Context, mod api.Module, ptr ptr_t, kind uint32, err error, def ErrorCode) res_t {
|
||||
const zErrMsgOffset = 8
|
||||
msg, code := errorCode(err, ERROR)
|
||||
if msg != "" && ptr != 0 {
|
||||
msg, code := errorCode(err, def)
|
||||
if ptr != 0 && msg != "" {
|
||||
switch kind {
|
||||
case _VTAB_ERROR:
|
||||
ptr = ptr + zErrMsgOffset // zErrMsg
|
||||
|
||||
Reference in New Issue
Block a user