mirror of
https://github.com/ncruces/go-sqlite3.git
synced 2026-01-12 22:19:14 +00:00
Compare commits
44 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 | ||
|
|
ea860e407d | ||
|
|
d4561d08f9 | ||
|
|
14c1e490b4 | ||
|
|
23aad5f62f | ||
|
|
e5bd10a1ff | ||
|
|
5cf06c45f7 | ||
|
|
08f9fc758a | ||
|
|
b588d5f991 | ||
|
|
4c24bd0cb6 | ||
|
|
cc353e4848 | ||
|
|
c3ebb04045 | ||
|
|
11e064574c | ||
|
|
770420289a | ||
|
|
62f69011f1 | ||
|
|
4f9e3f900b | ||
|
|
4e90618350 | ||
|
|
54bb94ce58 | ||
|
|
07fec784e1 | ||
|
|
da4638cbff | ||
|
|
085872c2f3 | ||
|
|
de49aa2b06 |
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}"
|
||||
|
||||
6
.github/workflows/libc.yml
vendored
6
.github/workflows/libc.yml
vendored
@@ -10,12 +10,12 @@ 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/setup-go@v5
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/setup-go@v6
|
||||
with: { go-version: stable }
|
||||
|
||||
- name: Benchmark
|
||||
|
||||
4
.github/workflows/repro.yml
vendored
4
.github/workflows/repro.yml
vendored
@@ -17,13 +17,13 @@ jobs:
|
||||
|
||||
steps:
|
||||
- uses: ilammy/msvc-dev-cmd@v1
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- name: Build
|
||||
shell: bash
|
||||
run: .github/workflows/repro.sh
|
||||
|
||||
- uses: actions/attest-build-provenance@v2
|
||||
- uses: actions/attest-build-provenance@v3
|
||||
if: matrix.os == 'ubuntu-latest'
|
||||
with:
|
||||
subject-path: |
|
||||
|
||||
87
.github/workflows/test.yml
vendored
87
.github/workflows/test.yml
vendored
@@ -30,8 +30,8 @@ jobs:
|
||||
contents: write
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/setup-go@v5
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/setup-go@v6
|
||||
with: { go-version: stable }
|
||||
|
||||
- name: Format
|
||||
@@ -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,8 +169,8 @@ jobs:
|
||||
|
||||
steps:
|
||||
- uses: bytecodealliance/actions/wasmtime/setup@v1
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/setup-go@v5
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/setup-go@v6
|
||||
with: { go-version: stable }
|
||||
|
||||
- name: Set path
|
||||
@@ -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/setup-go@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/setup-go@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/setup-go@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
|
||||
|
||||
39
conn.go
39
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
|
||||
}
|
||||
@@ -444,20 +444,27 @@ func (c *Conn) Status(op DBStatus, reset bool) (current, highwater int, err erro
|
||||
// https://sqlite.org/c3ref/table_column_metadata.html
|
||||
func (c *Conn) TableColumnMetadata(schema, table, column string) (declType, collSeq string, notNull, primaryKey, autoInc bool, err error) {
|
||||
defer c.arena.mark()()
|
||||
|
||||
var schemaPtr, columnPtr ptr_t
|
||||
declTypePtr := c.arena.new(ptrlen)
|
||||
collSeqPtr := c.arena.new(ptrlen)
|
||||
notNullPtr := c.arena.new(ptrlen)
|
||||
autoIncPtr := c.arena.new(ptrlen)
|
||||
primaryKeyPtr := c.arena.new(ptrlen)
|
||||
var (
|
||||
declTypePtr ptr_t
|
||||
collSeqPtr ptr_t
|
||||
notNullPtr ptr_t
|
||||
primaryKeyPtr ptr_t
|
||||
autoIncPtr ptr_t
|
||||
columnPtr ptr_t
|
||||
schemaPtr ptr_t
|
||||
)
|
||||
if column != "" {
|
||||
declTypePtr = c.arena.new(ptrlen)
|
||||
collSeqPtr = c.arena.new(ptrlen)
|
||||
notNullPtr = c.arena.new(ptrlen)
|
||||
primaryKeyPtr = c.arena.new(ptrlen)
|
||||
autoIncPtr = c.arena.new(ptrlen)
|
||||
columnPtr = c.arena.string(column)
|
||||
}
|
||||
if schema != "" {
|
||||
schemaPtr = c.arena.string(schema)
|
||||
}
|
||||
tablePtr := c.arena.string(table)
|
||||
if column != "" {
|
||||
columnPtr = c.arena.string(column)
|
||||
}
|
||||
|
||||
rc := res_t(c.call("sqlite3_table_column_metadata", stk_t(c.handle),
|
||||
stk_t(schemaPtr), stk_t(tablePtr), stk_t(columnPtr),
|
||||
|
||||
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].
|
||||
|
||||
20
context.go
20
context.go
@@ -1,7 +1,6 @@
|
||||
package sqlite3
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"math"
|
||||
"time"
|
||||
@@ -173,21 +172,6 @@ func (ctx Context) ResultPointer(ptr any) {
|
||||
stk_t(ctx.handle), stk_t(valPtr))
|
||||
}
|
||||
|
||||
// ResultJSON sets the result of the function to the JSON encoding of value.
|
||||
//
|
||||
// https://sqlite.org/c3ref/result_blob.html
|
||||
func (ctx Context) ResultJSON(value any) {
|
||||
err := json.NewEncoder(callbackWriter(func(p []byte) (int, error) {
|
||||
ctx.ResultRawText(p[:len(p)-1]) // remove the newline
|
||||
return 0, nil
|
||||
})).Encode(value)
|
||||
|
||||
if err != nil {
|
||||
ctx.ResultError(err)
|
||||
return // notest
|
||||
}
|
||||
}
|
||||
|
||||
// ResultValue sets the result of the function to a copy of [Value].
|
||||
//
|
||||
// https://sqlite.org/c3ref/result_blob.html
|
||||
@@ -214,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))
|
||||
}
|
||||
|
||||
135
driver/driver.go
135
driver/driver.go
@@ -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
|
||||
@@ -607,14 +621,24 @@ func (r resultRowsAffected) RowsAffected() (int64, error) {
|
||||
type scantype byte
|
||||
|
||||
const (
|
||||
_ANY scantype = iota
|
||||
_INT scantype = scantype(sqlite3.INTEGER)
|
||||
_REAL scantype = scantype(sqlite3.FLOAT)
|
||||
_TEXT scantype = scantype(sqlite3.TEXT)
|
||||
_BLOB scantype = scantype(sqlite3.BLOB)
|
||||
_NULL scantype = scantype(sqlite3.NULL)
|
||||
_BOOL scantype = iota
|
||||
_ANY scantype = iota
|
||||
_INT
|
||||
_REAL
|
||||
_TEXT
|
||||
_BLOB
|
||||
_NULL
|
||||
_BOOL
|
||||
_TIME
|
||||
_NOT_NULL
|
||||
)
|
||||
|
||||
var (
|
||||
_ [0]struct{} = [scantype(sqlite3.INTEGER) - _INT]struct{}{}
|
||||
_ [0]struct{} = [scantype(sqlite3.FLOAT) - _REAL]struct{}{}
|
||||
_ [0]struct{} = [scantype(sqlite3.TEXT) - _TEXT]struct{}{}
|
||||
_ [0]struct{} = [scantype(sqlite3.BLOB) - _BLOB]struct{}{}
|
||||
_ [0]struct{} = [scantype(sqlite3.NULL) - _NULL]struct{}{}
|
||||
_ [0]struct{} = [_NOT_NULL & (_NOT_NULL - 1)]struct{}{}
|
||||
)
|
||||
|
||||
func scanFromDecl(decl string) scantype {
|
||||
@@ -644,8 +668,8 @@ type rows struct {
|
||||
*stmt
|
||||
names []string
|
||||
types []string
|
||||
nulls []bool
|
||||
scans []scantype
|
||||
dest []driver.Value
|
||||
}
|
||||
|
||||
var (
|
||||
@@ -675,34 +699,36 @@ func (r *rows) Columns() []string {
|
||||
|
||||
func (r *rows) scanType(index int) scantype {
|
||||
if r.scans == nil {
|
||||
count := r.Stmt.ColumnCount()
|
||||
count := len(r.names)
|
||||
scans := make([]scantype, count)
|
||||
for i := range scans {
|
||||
scans[i] = scanFromDecl(strings.ToUpper(r.Stmt.ColumnDeclType(i)))
|
||||
}
|
||||
r.scans = scans
|
||||
}
|
||||
return r.scans[index]
|
||||
return r.scans[index] &^ _NOT_NULL
|
||||
}
|
||||
|
||||
func (r *rows) loadColumnMetadata() {
|
||||
if r.nulls == nil {
|
||||
if r.types == nil {
|
||||
c := r.Stmt.Conn()
|
||||
count := r.Stmt.ColumnCount()
|
||||
nulls := make([]bool, count)
|
||||
count := len(r.names)
|
||||
types := make([]string, count)
|
||||
scans := make([]scantype, count)
|
||||
for i := range nulls {
|
||||
for i := range types {
|
||||
var notnull bool
|
||||
if col := r.Stmt.ColumnOriginName(i); col != "" {
|
||||
types[i], _, nulls[i], _, _, _ = c.TableColumnMetadata(
|
||||
types[i], _, notnull, _, _, _ = c.TableColumnMetadata(
|
||||
r.Stmt.ColumnDatabaseName(i),
|
||||
r.Stmt.ColumnTableName(i),
|
||||
col)
|
||||
types[i] = strings.ToUpper(types[i])
|
||||
scans[i] = scanFromDecl(types[i])
|
||||
if notnull {
|
||||
scans[i] |= _NOT_NULL
|
||||
}
|
||||
}
|
||||
}
|
||||
r.nulls = nulls
|
||||
r.types = types
|
||||
r.scans = scans
|
||||
}
|
||||
@@ -721,15 +747,13 @@ func (r *rows) ColumnTypeDatabaseTypeName(index int) string {
|
||||
|
||||
func (r *rows) ColumnTypeNullable(index int) (nullable, ok bool) {
|
||||
r.loadColumnMetadata()
|
||||
if r.nulls[index] {
|
||||
return false, true
|
||||
}
|
||||
return true, false
|
||||
nullable = r.scans[index]&^_NOT_NULL == 0
|
||||
return nullable, !nullable
|
||||
}
|
||||
|
||||
func (r *rows) ColumnTypeScanType(index int) (typ reflect.Type) {
|
||||
r.loadColumnMetadata()
|
||||
scan := r.scans[index]
|
||||
scan := r.scans[index] &^ _NOT_NULL
|
||||
|
||||
if r.Stmt.Busy() {
|
||||
// SQLite is dynamically typed and we now have a row.
|
||||
@@ -772,6 +796,7 @@ func (r *rows) ColumnTypeScanType(index int) (typ reflect.Type) {
|
||||
}
|
||||
|
||||
func (r *rows) Next(dest []driver.Value) error {
|
||||
r.dest = nil
|
||||
c := r.Stmt.Conn()
|
||||
if old := c.SetInterrupt(r.ctx); old != r.ctx {
|
||||
defer c.SetInterrupt(old)
|
||||
@@ -790,18 +815,7 @@ func (r *rows) Next(dest []driver.Value) error {
|
||||
}
|
||||
for i := range dest {
|
||||
scan := r.scanType(i)
|
||||
switch v := dest[i].(type) {
|
||||
case int64:
|
||||
if scan == _BOOL {
|
||||
switch v {
|
||||
case 1:
|
||||
dest[i] = true
|
||||
case 0:
|
||||
dest[i] = false
|
||||
}
|
||||
continue
|
||||
}
|
||||
case []byte:
|
||||
if v, ok := dest[i].([]byte); ok {
|
||||
if len(v) == cap(v) { // a BLOB
|
||||
continue
|
||||
}
|
||||
@@ -816,38 +830,49 @@ func (r *rows) Next(dest []driver.Value) error {
|
||||
}
|
||||
}
|
||||
dest[i] = string(v)
|
||||
case float64:
|
||||
break
|
||||
default:
|
||||
continue
|
||||
}
|
||||
if scan == _TIME {
|
||||
switch scan {
|
||||
case _TIME:
|
||||
t, err := r.tmRead.Decode(dest[i])
|
||||
if err == nil {
|
||||
dest[i] = t
|
||||
continue
|
||||
}
|
||||
case _BOOL:
|
||||
switch dest[i] {
|
||||
case int64(0):
|
||||
dest[i] = false
|
||||
case int64(1):
|
||||
dest[i] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
r.dest = dest
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *rows) ScanColumn(dest any, index int) error {
|
||||
func (r *rows) ScanColumn(dest any, index int) (err error) {
|
||||
// notest // Go 1.26
|
||||
var ptr *time.Time
|
||||
var tm *time.Time
|
||||
var ok *bool
|
||||
switch d := dest.(type) {
|
||||
case *time.Time:
|
||||
ptr = d
|
||||
tm = d
|
||||
case *sql.NullTime:
|
||||
ptr = &d.Time
|
||||
tm = &d.Time
|
||||
ok = &d.Valid
|
||||
case *sql.Null[time.Time]:
|
||||
ptr = &d.V
|
||||
tm = &d.V
|
||||
ok = &d.Valid
|
||||
default:
|
||||
return driver.ErrSkip
|
||||
}
|
||||
if t := r.Stmt.ColumnTime(index, r.tmRead); !t.IsZero() {
|
||||
*ptr = t
|
||||
return nil
|
||||
value := r.dest[index]
|
||||
*tm, err = r.tmRead.Decode(value)
|
||||
if ok != nil {
|
||||
*ok = err == nil
|
||||
if value == nil {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return driver.ErrSkip
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"math"
|
||||
"net/url"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@@ -33,7 +34,7 @@ func Test_Open_error(t *testing.T) {
|
||||
func Test_Open_dir(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
db, err := sql.Open("sqlite3", ".")
|
||||
db, err := Open(".")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -43,18 +44,18 @@ 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)
|
||||
}
|
||||
}
|
||||
|
||||
func Test_Open_pragma(t *testing.T) {
|
||||
t.Parallel()
|
||||
tmp := memdb.TestDB(t, url.Values{
|
||||
dsn := memdb.TestDB(t, url.Values{
|
||||
"_pragma": {"busy_timeout(1000)"},
|
||||
})
|
||||
|
||||
db, err := sql.Open("sqlite3", tmp)
|
||||
db, err := Open(dsn)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -72,11 +73,11 @@ func Test_Open_pragma(t *testing.T) {
|
||||
|
||||
func Test_Open_pragma_invalid(t *testing.T) {
|
||||
t.Parallel()
|
||||
tmp := memdb.TestDB(t, url.Values{
|
||||
dsn := memdb.TestDB(t, url.Values{
|
||||
"_pragma": {"busy_timeout 1000"},
|
||||
})
|
||||
|
||||
db, err := sql.Open("sqlite3", tmp)
|
||||
db, err := Open(dsn)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -100,12 +101,12 @@ func Test_Open_pragma_invalid(t *testing.T) {
|
||||
|
||||
func Test_Open_txLock(t *testing.T) {
|
||||
t.Parallel()
|
||||
tmp := memdb.TestDB(t, url.Values{
|
||||
dsn := memdb.TestDB(t, url.Values{
|
||||
"_txlock": {"exclusive"},
|
||||
"_pragma": {"busy_timeout(1000)"},
|
||||
})
|
||||
|
||||
db, err := sql.Open("sqlite3", tmp)
|
||||
db, err := Open(dsn)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -136,11 +137,11 @@ func Test_Open_txLock(t *testing.T) {
|
||||
|
||||
func Test_Open_txLock_invalid(t *testing.T) {
|
||||
t.Parallel()
|
||||
tmp := memdb.TestDB(t, url.Values{
|
||||
dsn := memdb.TestDB(t, url.Values{
|
||||
"_txlock": {"xclusive"},
|
||||
})
|
||||
|
||||
_, err := sql.Open("sqlite3", tmp+"_txlock=xclusive")
|
||||
_, err := Open(dsn)
|
||||
if err == nil {
|
||||
t.Fatal("want error")
|
||||
}
|
||||
@@ -151,31 +152,28 @@ func Test_Open_txLock_invalid(t *testing.T) {
|
||||
|
||||
func Test_BeginTx(t *testing.T) {
|
||||
t.Parallel()
|
||||
tmp := memdb.TestDB(t, url.Values{
|
||||
dsn := memdb.TestDB(t, url.Values{
|
||||
"_txlock": {"exclusive"},
|
||||
"_pragma": {"busy_timeout(0)"},
|
||||
})
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
db, err := sql.Open("sqlite3", tmp)
|
||||
db, err := Open(dsn)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
_, err = db.BeginTx(ctx, &sql.TxOptions{Isolation: sql.LevelReadCommitted})
|
||||
_, err = db.BeginTx(t.Context(), &sql.TxOptions{Isolation: sql.LevelReadCommitted})
|
||||
if err.Error() != string(util.IsolationErr) {
|
||||
t.Error("want isolationErr")
|
||||
}
|
||||
|
||||
tx1, err := db.BeginTx(ctx, &sql.TxOptions{ReadOnly: true})
|
||||
tx1, err := db.BeginTx(t.Context(), &sql.TxOptions{ReadOnly: true})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
tx2, err := db.BeginTx(ctx, &sql.TxOptions{ReadOnly: true})
|
||||
tx2, err := db.BeginTx(t.Context(), &sql.TxOptions{ReadOnly: true})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -201,9 +199,9 @@ func Test_BeginTx(t *testing.T) {
|
||||
|
||||
func Test_nested_context(t *testing.T) {
|
||||
t.Parallel()
|
||||
tmp := memdb.TestDB(t)
|
||||
dsn := memdb.TestDB(t)
|
||||
|
||||
db, err := sql.Open("sqlite3", tmp)
|
||||
db, err := Open(dsn)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -236,7 +234,7 @@ func Test_nested_context(t *testing.T) {
|
||||
|
||||
want(outer, 0)
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
ctx, cancel := context.WithCancel(t.Context())
|
||||
defer cancel()
|
||||
|
||||
inner, err := tx.QueryContext(ctx, `SELECT value FROM generate_series(0)`)
|
||||
@@ -259,9 +257,9 @@ func Test_nested_context(t *testing.T) {
|
||||
|
||||
func Test_Prepare(t *testing.T) {
|
||||
t.Parallel()
|
||||
tmp := memdb.TestDB(t)
|
||||
dsn := memdb.TestDB(t)
|
||||
|
||||
db, err := sql.Open("sqlite3", tmp)
|
||||
db, err := Open(dsn)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -300,24 +298,21 @@ func Test_Prepare(t *testing.T) {
|
||||
|
||||
func Test_QueryRow_named(t *testing.T) {
|
||||
t.Parallel()
|
||||
tmp := memdb.TestDB(t)
|
||||
dsn := memdb.TestDB(t)
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
db, err := sql.Open("sqlite3", tmp)
|
||||
db, err := Open(dsn)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
conn, err := db.Conn(ctx)
|
||||
conn, err := db.Conn(t.Context())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
stmt, err := conn.PrepareContext(ctx, `SELECT ?, ?5, :AAA, @AAA, $AAA`)
|
||||
stmt, err := conn.PrepareContext(t.Context(), `SELECT ?, ?5, :AAA, @AAA, $AAA`)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -353,9 +348,9 @@ func Test_QueryRow_named(t *testing.T) {
|
||||
|
||||
func Test_QueryRow_blob_null(t *testing.T) {
|
||||
t.Parallel()
|
||||
tmp := memdb.TestDB(t)
|
||||
dsn := memdb.TestDB(t)
|
||||
|
||||
db, err := sql.Open("sqlite3", tmp)
|
||||
db, err := Open(dsn)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -390,11 +385,11 @@ func Test_time(t *testing.T) {
|
||||
|
||||
for _, fmt := range []string{"auto", "sqlite", "rfc3339", time.ANSIC} {
|
||||
t.Run(fmt, func(t *testing.T) {
|
||||
tmp := memdb.TestDB(t, url.Values{
|
||||
dsn := memdb.TestDB(t, url.Values{
|
||||
"_timefmt": {fmt},
|
||||
})
|
||||
|
||||
db, err := sql.Open("sqlite3", tmp)
|
||||
db, err := Open(dsn)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -437,9 +432,9 @@ func Test_ColumnType_ScanType(t *testing.T) {
|
||||
)
|
||||
|
||||
t.Parallel()
|
||||
tmp := memdb.TestDB(t)
|
||||
dsn := memdb.TestDB(t)
|
||||
|
||||
db, err := sql.Open("sqlite3", tmp)
|
||||
db, err := Open(dsn)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -526,6 +521,39 @@ func Test_ColumnType_ScanType(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func Test_rows_ScanColumn(t *testing.T) {
|
||||
t.Parallel()
|
||||
dsn := memdb.TestDB(t)
|
||||
|
||||
db, err := Open(dsn)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
var tm time.Time
|
||||
err = db.QueryRow(`SELECT NULL`).Scan(&tm)
|
||||
if err == nil {
|
||||
t.Error("want error")
|
||||
}
|
||||
// Go 1.26
|
||||
err = db.QueryRow(`SELECT datetime()`).Scan(&tm)
|
||||
if err != nil && !strings.HasPrefix(err.Error(), "sql: Scan error") {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
var nt sql.NullTime
|
||||
err = db.QueryRow(`SELECT NULL`).Scan(&nt)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
// Go 1.26
|
||||
err = db.QueryRow(`SELECT datetime()`).Scan(&nt)
|
||||
if err != nil && !strings.HasPrefix(err.Error(), "sql: Scan error") {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func Benchmark_loop(b *testing.B) {
|
||||
db, err := Open(":memory:")
|
||||
if err != nil {
|
||||
@@ -539,12 +567,8 @@ func Benchmark_loop(b *testing.B) {
|
||||
b.Fatal(err)
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
b.Cleanup(cancel)
|
||||
|
||||
b.ResetTimer()
|
||||
for range b.N {
|
||||
_, err := db.ExecContext(ctx,
|
||||
for b.Loop() {
|
||||
_, err := db.ExecContext(b.Context(),
|
||||
`WITH RECURSIVE c(x) AS (VALUES(1) UNION ALL SELECT x+1 FROM c WHERE x < 1000000) SELECT x FROM c;`)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package driver
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql/driver"
|
||||
"slices"
|
||||
"testing"
|
||||
@@ -56,7 +55,7 @@ func Fuzz_notWhitespace(f *testing.F) {
|
||||
t.SkipNow()
|
||||
}
|
||||
|
||||
c, err := db.Conn(context.Background())
|
||||
c, err := db.Conn(t.Context())
|
||||
if err != nil {
|
||||
t.Fatal(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,12 +58,15 @@ cd ~-
|
||||
-Wl,--initial-memory=327680 \
|
||||
-D_HAVE_SQLITE_CONFIG_H \
|
||||
-DSQLITE_ENABLE_UPDATE_DELETE_LIMIT \
|
||||
-DSQLITE_ENABLE_ORDERED_SET_AGGREGATES \
|
||||
-DSQLITE_EXPERIMENTAL_PRAGMA_20251114 \
|
||||
-DSQLITE_CUSTOM_INCLUDE=sqlite_opt.h \
|
||||
$(awk '{print "-Wl,--export="$0}' ../exports.txt)
|
||||
|
||||
"$BINARYEN/wasm-ctor-eval" -g -c _initialize bcw2.wasm -o bcw2.tmp
|
||||
"$BINARYEN/wasm-opt" -g --strip --strip-producers -c -O3 \
|
||||
bcw2.tmp -o bcw2.wasm --low-memory-unused \
|
||||
"$BINARYEN/wasm-opt" -g bcw2.tmp -o bcw2.wasm \
|
||||
--low-memory-unused --gufa --generate-global-effects --converge -O3 \
|
||||
--enable-mutable-globals --enable-nontrapping-float-to-int \
|
||||
--enable-simd --enable-bulk-memory --enable-sign-ext \
|
||||
--enable-reference-types --enable-multivalue
|
||||
--enable-reference-types --enable-multivalue \
|
||||
--strip --strip-producers
|
||||
|
||||
@@ -1,14 +1,12 @@
|
||||
module github.com/ncruces/go-sqlite3/embed/bcw2
|
||||
|
||||
go 1.23.0
|
||||
go 1.24.0
|
||||
|
||||
toolchain go1.24.0
|
||||
|
||||
require github.com/ncruces/go-sqlite3 v0.27.1
|
||||
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.35.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.27.1 h1:suqlM7xhSyDVMV9RgX99MCPqt9mB6YOCzHZuiI36K34=
|
||||
github.com/ncruces/go-sqlite3 v0.27.1/go.mod h1:gpF5s+92aw2MbDmZK0ZOnCdFlpe11BH20CTspVqri0c=
|
||||
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.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI=
|
||||
golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4=
|
||||
golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU=
|
||||
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=
|
||||
|
||||
@@ -27,8 +27,9 @@ trap 'rm -f sqlite3.tmp' EXIT
|
||||
$(awk '{print "-Wl,--export="$0}' exports.txt)
|
||||
|
||||
"$BINARYEN/wasm-ctor-eval" -g -c _initialize sqlite3.wasm -o sqlite3.tmp
|
||||
"$BINARYEN/wasm-opt" -g --strip --strip-producers -c -O3 \
|
||||
sqlite3.tmp -o sqlite3.wasm --low-memory-unused \
|
||||
"$BINARYEN/wasm-opt" -g sqlite3.tmp -o sqlite3.wasm \
|
||||
--low-memory-unused --gufa --generate-global-effects --converge -O3 \
|
||||
--enable-mutable-globals --enable-nontrapping-float-to-int \
|
||||
--enable-simd --enable-bulk-memory --enable-sign-ext \
|
||||
--enable-reference-types --enable-multivalue
|
||||
--enable-reference-types --enable-multivalue \
|
||||
--strip --strip-producers
|
||||
@@ -59,7 +59,7 @@ sqlite3_db_filename
|
||||
sqlite3_db_name
|
||||
sqlite3_db_readonly
|
||||
sqlite3_db_release_memory
|
||||
sqlite3_db_status
|
||||
sqlite3_db_status64
|
||||
sqlite3_declare_vtab
|
||||
sqlite3_errcode
|
||||
sqlite3_errmsg
|
||||
|
||||
@@ -17,5 +17,7 @@ import (
|
||||
var binary string
|
||||
|
||||
func init() {
|
||||
sqlite3.Binary = unsafe.Slice(unsafe.StringData(binary), len(binary))
|
||||
if sqlite3.Binary == nil {
|
||||
sqlite3.Binary = unsafe.Slice(unsafe.StringData(binary), len(binary))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ func Test_init(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if version != "3.50.4" {
|
||||
if version != "3.51.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 {
|
||||
|
||||
@@ -88,9 +88,9 @@ func Example() {
|
||||
|
||||
func Test_cursor_Column(t *testing.T) {
|
||||
t.Parallel()
|
||||
tmp := memdb.TestDB(t)
|
||||
dsn := memdb.TestDB(t)
|
||||
|
||||
db, err := driver.Open(tmp, array.Register)
|
||||
db, err := driver.Open(dsn, array.Register)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
@@ -154,6 +154,7 @@ func (c *closure) BestIndex(idx *sqlite3.IndexInfo) error {
|
||||
return sqlite3.CONSTRAINT
|
||||
}
|
||||
|
||||
idx.IdxFlags = sqlite3.INDEX_SCAN_HEX
|
||||
idx.EstimatedCost = cost
|
||||
idx.IdxNum = plan
|
||||
return nil
|
||||
|
||||
@@ -17,9 +17,9 @@ import (
|
||||
|
||||
func Test_lsmode(t *testing.T) {
|
||||
t.Parallel()
|
||||
tmp := memdb.TestDB(t)
|
||||
dsn := memdb.TestDB(t)
|
||||
|
||||
db, err := driver.Open(tmp, fileio.Register)
|
||||
db, err := driver.Open(dsn, fileio.Register)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -53,9 +53,9 @@ func Test_readfile(t *testing.T) {
|
||||
|
||||
for _, fsys := range []fs.FS{nil, os.DirFS(".")} {
|
||||
t.Run("", func(t *testing.T) {
|
||||
tmp := memdb.TestDB(t)
|
||||
dsn := memdb.TestDB(t)
|
||||
|
||||
db, err := driver.Open(tmp, func(c *sqlite3.Conn) error {
|
||||
db, err := driver.Open(dsn, func(c *sqlite3.Conn) error {
|
||||
fileio.RegisterFS(c, fsys)
|
||||
return nil
|
||||
})
|
||||
|
||||
@@ -21,9 +21,9 @@ func Test_fsdir(t *testing.T) {
|
||||
|
||||
for _, fsys := range []fs.FS{nil, os.DirFS(".")} {
|
||||
t.Run("", func(t *testing.T) {
|
||||
tmp := memdb.TestDB(t)
|
||||
dsn := memdb.TestDB(t)
|
||||
|
||||
db, err := driver.Open(tmp, func(c *sqlite3.Conn) error {
|
||||
db, err := driver.Open(dsn, func(c *sqlite3.Conn) error {
|
||||
fileio.RegisterFS(c, fsys)
|
||||
return nil
|
||||
})
|
||||
|
||||
@@ -15,9 +15,9 @@ import (
|
||||
|
||||
func Test_writefile(t *testing.T) {
|
||||
t.Parallel()
|
||||
tmp := memdb.TestDB(t)
|
||||
dsn := memdb.TestDB(t)
|
||||
|
||||
db, err := driver.Open(tmp, Register)
|
||||
db, err := driver.Open(dsn, Register)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
_ "crypto/md5"
|
||||
_ "crypto/sha1"
|
||||
_ "crypto/sha256"
|
||||
_ "crypto/sha3"
|
||||
_ "crypto/sha512"
|
||||
"testing"
|
||||
|
||||
@@ -11,7 +12,6 @@ import (
|
||||
_ "golang.org/x/crypto/blake2s"
|
||||
_ "golang.org/x/crypto/md4"
|
||||
_ "golang.org/x/crypto/ripemd160"
|
||||
_ "golang.org/x/crypto/sha3"
|
||||
|
||||
"github.com/ncruces/go-sqlite3/driver"
|
||||
_ "github.com/ncruces/go-sqlite3/embed"
|
||||
@@ -21,7 +21,7 @@ import (
|
||||
|
||||
func TestRegister(t *testing.T) {
|
||||
t.Parallel()
|
||||
tmp := memdb.TestDB(t)
|
||||
dsn := memdb.TestDB(t)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
@@ -55,7 +55,7 @@ func TestRegister(t *testing.T) {
|
||||
{"blake2b('', 256)", "0E5751C026E543B2E8AB2EB06099DAA1D1E5DF47778F7787FAAB45CDF12FE3A8"},
|
||||
}
|
||||
|
||||
db, err := driver.Open(tmp, Register)
|
||||
db, err := driver.Open(dsn, Register)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
@@ -12,9 +12,9 @@ import (
|
||||
|
||||
func TestRegister(t *testing.T) {
|
||||
t.Parallel()
|
||||
tmp := memdb.TestDB(t)
|
||||
dsn := memdb.TestDB(t)
|
||||
|
||||
db, err := driver.Open(tmp, ipaddr.Register)
|
||||
db, err := driver.Open(dsn, ipaddr.Register)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
@@ -67,9 +67,9 @@ func Example() {
|
||||
|
||||
func Test_lines(t *testing.T) {
|
||||
t.Parallel()
|
||||
tmp := memdb.TestDB(t)
|
||||
dsn := memdb.TestDB(t)
|
||||
|
||||
db, err := driver.Open(tmp, lines.Register)
|
||||
db, err := driver.Open(dsn, lines.Register)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
@@ -98,9 +98,9 @@ func Test_lines(t *testing.T) {
|
||||
|
||||
func Test_lines_error(t *testing.T) {
|
||||
t.Parallel()
|
||||
tmp := memdb.TestDB(t)
|
||||
dsn := memdb.TestDB(t)
|
||||
|
||||
db, err := driver.Open(tmp, lines.Register)
|
||||
db, err := driver.Open(dsn, lines.Register)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
@@ -123,9 +123,9 @@ func Test_lines_error(t *testing.T) {
|
||||
|
||||
func Test_lines_read(t *testing.T) {
|
||||
t.Parallel()
|
||||
tmp := memdb.TestDB(t)
|
||||
dsn := memdb.TestDB(t)
|
||||
|
||||
db, err := driver.Open(tmp, lines.Register)
|
||||
db, err := driver.Open(dsn, lines.Register)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
@@ -155,9 +155,9 @@ func Test_lines_read(t *testing.T) {
|
||||
|
||||
func Test_lines_test(t *testing.T) {
|
||||
t.Parallel()
|
||||
tmp := memdb.TestDB(t)
|
||||
dsn := memdb.TestDB(t)
|
||||
|
||||
db, err := driver.Open(tmp, lines.Register)
|
||||
db, err := driver.Open(dsn, lines.Register)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
@@ -15,9 +15,9 @@ import (
|
||||
|
||||
func TestRegister(t *testing.T) {
|
||||
t.Parallel()
|
||||
tmp := memdb.TestDB(t)
|
||||
dsn := memdb.TestDB(t)
|
||||
|
||||
db, err := driver.Open(tmp, Register)
|
||||
db, err := driver.Open(dsn, Register)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -80,9 +80,9 @@ func TestRegister(t *testing.T) {
|
||||
|
||||
func TestRegister_errors(t *testing.T) {
|
||||
t.Parallel()
|
||||
tmp := memdb.TestDB(t)
|
||||
dsn := memdb.TestDB(t)
|
||||
|
||||
db, err := driver.Open(tmp, Register)
|
||||
db, err := driver.Open(dsn, Register)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -107,9 +107,9 @@ func TestRegister_errors(t *testing.T) {
|
||||
|
||||
func TestRegister_pointer(t *testing.T) {
|
||||
t.Parallel()
|
||||
tmp := memdb.TestDB(t)
|
||||
dsn := memdb.TestDB(t)
|
||||
|
||||
db, err := driver.Open(tmp, Register)
|
||||
db, err := driver.Open(dsn, Register)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
@@ -194,7 +194,7 @@ func timestamp(ctx sqlite3.Context, arg ...sqlite3.Value) {
|
||||
switch u.Version() {
|
||||
case 1, 2, 6, 7:
|
||||
ctx.ResultTime(
|
||||
time.Unix(u.Time().UnixTime()),
|
||||
time.Unix(u.Time().UnixTime()).UTC(),
|
||||
sqlite3.TimeFormatDefault)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,9 +14,9 @@ import (
|
||||
|
||||
func Test_generate(t *testing.T) {
|
||||
t.Parallel()
|
||||
tmp := memdb.TestDB(t)
|
||||
dsn := memdb.TestDB(t)
|
||||
|
||||
db, err := driver.Open(tmp, Register)
|
||||
db, err := driver.Open(dsn, Register)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -153,9 +153,9 @@ func Test_generate(t *testing.T) {
|
||||
|
||||
func Test_convert(t *testing.T) {
|
||||
t.Parallel()
|
||||
tmp := memdb.TestDB(t)
|
||||
dsn := memdb.TestDB(t)
|
||||
|
||||
db, err := driver.Open(tmp, Register)
|
||||
db, err := driver.Open(dsn, Register)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
@@ -14,9 +14,9 @@ import (
|
||||
|
||||
func Test_zorder(t *testing.T) {
|
||||
t.Parallel()
|
||||
tmp := memdb.TestDB(t)
|
||||
dsn := memdb.TestDB(t)
|
||||
|
||||
db, err := driver.Open(tmp, zorder.Register)
|
||||
db, err := driver.Open(dsn, zorder.Register)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -59,9 +59,9 @@ func Test_zorder(t *testing.T) {
|
||||
|
||||
func Test_unzorder(t *testing.T) {
|
||||
t.Parallel()
|
||||
tmp := memdb.TestDB(t)
|
||||
dsn := memdb.TestDB(t)
|
||||
|
||||
db, err := driver.Open(tmp, zorder.Register)
|
||||
db, err := driver.Open(dsn, zorder.Register)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -87,9 +87,9 @@ func Test_unzorder(t *testing.T) {
|
||||
|
||||
func Test_zorder_error(t *testing.T) {
|
||||
t.Parallel()
|
||||
tmp := memdb.TestDB(t)
|
||||
dsn := memdb.TestDB(t)
|
||||
|
||||
db, err := driver.Open(tmp, zorder.Register)
|
||||
db, err := driver.Open(dsn, zorder.Register)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -116,9 +116,9 @@ func Test_zorder_error(t *testing.T) {
|
||||
|
||||
func Test_unzorder_error(t *testing.T) {
|
||||
t.Parallel()
|
||||
tmp := memdb.TestDB(t)
|
||||
dsn := memdb.TestDB(t)
|
||||
|
||||
db, err := driver.Open(tmp, zorder.Register)
|
||||
db, err := driver.Open(dsn, zorder.Register)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
18
go.mod
18
go.mod
@@ -1,24 +1,22 @@
|
||||
module github.com/ncruces/go-sqlite3
|
||||
|
||||
go 1.23.0
|
||||
|
||||
toolchain go1.24.0
|
||||
go 1.24.0
|
||||
|
||||
require (
|
||||
github.com/ncruces/aa v0.3.0
|
||||
github.com/ncruces/julianday v1.0.0
|
||||
github.com/ncruces/sort v0.1.5
|
||||
github.com/tetratelabs/wazero v1.9.0
|
||||
golang.org/x/crypto v0.41.0
|
||||
golang.org/x/sys v0.35.0
|
||||
github.com/ncruces/sort v0.1.6
|
||||
github.com/ncruces/wbt v0.2.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/sync v0.16.0 // test
|
||||
golang.org/x/text v0.28.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
|
||||
)
|
||||
|
||||
|
||||
28
go.sum
28
go.sum
@@ -2,23 +2,23 @@ github.com/dchest/siphash v1.2.3 h1:QXwFc8cFOR2dSa/gE6o/HokBMWtLUaNDVd+22aKHeEA=
|
||||
github.com/dchest/siphash v1.2.3/go.mod h1:0NvQU092bT0ipiFN++/rXm69QG9tVxLAlQHIXMPAkHc=
|
||||
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/ncruces/aa v0.3.0 h1:6NPcK3jsyPWRZBZWCyF7c4IzEQX4eJtkKJBA+IRKTkQ=
|
||||
github.com/ncruces/aa v0.3.0/go.mod h1:ctOw1LVqfuqzqg2S9LlR045bLAiXtaTiPMCL3zzl7Ik=
|
||||
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/ncruces/sort v0.1.6 h1:TrsJfGRH1AoWoaeB4/+gCohot9+cA6u/INaH5agIhNk=
|
||||
github.com/ncruces/sort v0.1.6/go.mod h1:obJToO4rYr6VWP0Uw5FYymgYGt3Br4RXcs/JdKaXAPk=
|
||||
github.com/ncruces/wbt v0.2.0 h1:Q9zlKOBSZc7Yy/R2cGa35g6RKUUE3BjNIW3tfGC4F04=
|
||||
github.com/ncruces/wbt v0.2.0/go.mod h1:DtF92amvMxH69EmBFUSFWRDAlo6hOEfoNQnClxj9C/c=
|
||||
github.com/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.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4=
|
||||
golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc=
|
||||
golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw=
|
||||
golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||
golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI=
|
||||
golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng=
|
||||
golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU=
|
||||
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=
|
||||
|
||||
@@ -1,19 +1,17 @@
|
||||
module github.com/ncruces/go-sqlite3/gormlite
|
||||
|
||||
go 1.23.0
|
||||
|
||||
toolchain go1.24.0
|
||||
go 1.24.0
|
||||
|
||||
require (
|
||||
github.com/ncruces/go-sqlite3 v0.27.1
|
||||
gorm.io/gorm v1.30.1
|
||||
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.35.0 // indirect
|
||||
golang.org/x/text v0.28.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.27.1 h1:suqlM7xhSyDVMV9RgX99MCPqt9mB6YOCzHZuiI36K34=
|
||||
github.com/ncruces/go-sqlite3 v0.27.1/go.mod h1:gpF5s+92aw2MbDmZK0ZOnCdFlpe11BH20CTspVqri0c=
|
||||
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.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI=
|
||||
golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng=
|
||||
golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU=
|
||||
gorm.io/gorm v1.30.1 h1:lSHg33jJTBxs2mgJRfRZeLDG+WZaHYCk3Wtfl6Ngzo4=
|
||||
gorm.io/gorm v1.30.1/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 {
|
||||
|
||||
@@ -14,10 +14,10 @@ import (
|
||||
)
|
||||
|
||||
func TestDialector(t *testing.T) {
|
||||
tmp := memdb.TestDB(t)
|
||||
dsn := memdb.TestDB(t)
|
||||
|
||||
// Custom connection with a custom function called "my_custom_function".
|
||||
db, err := driver.Open(tmp, func(conn *sqlite3.Conn) error {
|
||||
db, err := driver.Open(dsn, func(conn *sqlite3.Conn) error {
|
||||
return conn.CreateFunction("my_custom_function", 0, sqlite3.DETERMINISTIC,
|
||||
func(ctx sqlite3.Context, arg ...sqlite3.Value) {
|
||||
ctx.ResultText("my-result")
|
||||
@@ -36,14 +36,14 @@ func TestDialector(t *testing.T) {
|
||||
}{
|
||||
{
|
||||
description: "Default driver",
|
||||
dialector: Open(tmp),
|
||||
dialector: Open(dsn),
|
||||
openSuccess: true,
|
||||
query: "SELECT 1",
|
||||
querySuccess: true,
|
||||
},
|
||||
{
|
||||
description: "Custom function",
|
||||
dialector: Open(tmp),
|
||||
dialector: Open(dsn),
|
||||
openSuccess: true,
|
||||
query: "SELECT my_custom_function()",
|
||||
querySuccess: false,
|
||||
|
||||
@@ -7,7 +7,7 @@ rm -rf gorm/ tests/
|
||||
go work use -r .
|
||||
go test
|
||||
|
||||
git clone --branch v1.30.1 --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,28 +5,36 @@ package alloc
|
||||
import (
|
||||
"math"
|
||||
|
||||
"github.com/tetratelabs/wazero/experimental"
|
||||
"golang.org/x/sys/unix"
|
||||
|
||||
"github.com/tetratelabs/wazero/experimental"
|
||||
)
|
||||
|
||||
func NewMemory(_, max uint64) experimental.LinearMemory {
|
||||
func NewMemory(cap, max uint64) experimental.LinearMemory {
|
||||
// Round up to the page size.
|
||||
rnd := uint64(unix.Getpagesize() - 1)
|
||||
max = (max + rnd) &^ rnd
|
||||
res := (max + rnd) &^ rnd
|
||||
|
||||
if max > math.MaxInt {
|
||||
// This ensures int(max) overflows to a negative value,
|
||||
if res > math.MaxInt {
|
||||
// This ensures int(res) overflows to a negative value,
|
||||
// and unix.Mmap returns EINVAL.
|
||||
max = math.MaxUint64
|
||||
res = math.MaxUint64
|
||||
}
|
||||
|
||||
// Reserve max bytes of address space, to ensure we won't need to move it.
|
||||
com := res
|
||||
prot := unix.PROT_READ | unix.PROT_WRITE
|
||||
if cap < max { // Commit memory only if cap=max.
|
||||
com = 0
|
||||
prot = unix.PROT_NONE
|
||||
}
|
||||
|
||||
// Reserve res bytes of address space, to ensure we won't need to move it.
|
||||
// A protected, private, anonymous mapping should not commit memory.
|
||||
b, err := unix.Mmap(-1, 0, int(max), unix.PROT_NONE, unix.MAP_PRIVATE|unix.MAP_ANON)
|
||||
b, err := unix.Mmap(-1, 0, int(res), prot, unix.MAP_PRIVATE|unix.MAP_ANON)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return &mmappedMemory{buf: b[:0]}
|
||||
return &mmappedMemory{buf: b[:com]}
|
||||
}
|
||||
|
||||
// The slice covers the entire mmapped memory:
|
||||
@@ -40,9 +48,11 @@ func (m *mmappedMemory) Reallocate(size uint64) []byte {
|
||||
com := uint64(len(m.buf))
|
||||
res := uint64(cap(m.buf))
|
||||
if com < size && size <= res {
|
||||
// Round up to the page size.
|
||||
// Grow geometrically, round up to the page size.
|
||||
rnd := uint64(unix.Getpagesize() - 1)
|
||||
new := (size + rnd) &^ rnd
|
||||
new := com + com>>3
|
||||
new = min(max(size, new), res)
|
||||
new = (new + rnd) &^ rnd
|
||||
|
||||
// Commit additional memory up to new bytes.
|
||||
err := unix.Mprotect(m.buf[com:new], unix.PROT_READ|unix.PROT_WRITE)
|
||||
@@ -50,8 +60,7 @@ func (m *mmappedMemory) Reallocate(size uint64) []byte {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Update committed memory.
|
||||
m.buf = m.buf[:new]
|
||||
m.buf = m.buf[:new] // Update committed memory.
|
||||
}
|
||||
// Limit returned capacity because bytes beyond
|
||||
// len(m.buf) have not yet been committed.
|
||||
|
||||
@@ -5,24 +5,31 @@ import (
|
||||
"reflect"
|
||||
"unsafe"
|
||||
|
||||
"github.com/tetratelabs/wazero/experimental"
|
||||
"golang.org/x/sys/windows"
|
||||
|
||||
"github.com/tetratelabs/wazero/experimental"
|
||||
)
|
||||
|
||||
func NewMemory(_, max uint64) experimental.LinearMemory {
|
||||
func NewMemory(cap, max uint64) experimental.LinearMemory {
|
||||
// Round up to the page size.
|
||||
rnd := uint64(windows.Getpagesize() - 1)
|
||||
max = (max + rnd) &^ rnd
|
||||
res := (max + rnd) &^ rnd
|
||||
|
||||
if max > math.MaxInt {
|
||||
// This ensures uintptr(max) overflows to a large value,
|
||||
if res > math.MaxInt {
|
||||
// This ensures uintptr(res) overflows to a large value,
|
||||
// and windows.VirtualAlloc returns an error.
|
||||
max = math.MaxUint64
|
||||
res = math.MaxUint64
|
||||
}
|
||||
|
||||
// Reserve max bytes of address space, to ensure we won't need to move it.
|
||||
// This does not commit memory.
|
||||
r, err := windows.VirtualAlloc(0, uintptr(max), windows.MEM_RESERVE, windows.PAGE_READWRITE)
|
||||
com := res
|
||||
kind := windows.MEM_COMMIT
|
||||
if cap < max { // Commit memory only if cap=max.
|
||||
com = 0
|
||||
kind = windows.MEM_RESERVE
|
||||
}
|
||||
|
||||
// Reserve res bytes of address space, to ensure we won't need to move it.
|
||||
r, err := windows.VirtualAlloc(0, uintptr(res), uint32(kind), windows.PAGE_READWRITE)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@@ -30,8 +37,9 @@ func NewMemory(_, max uint64) experimental.LinearMemory {
|
||||
mem := virtualMemory{addr: r}
|
||||
// SliceHeader, although deprecated, avoids a go vet warning.
|
||||
sh := (*reflect.SliceHeader)(unsafe.Pointer(&mem.buf))
|
||||
sh.Cap = int(max)
|
||||
sh.Data = r
|
||||
sh.Len = int(com)
|
||||
sh.Cap = int(res)
|
||||
return &mem
|
||||
}
|
||||
|
||||
@@ -47,9 +55,11 @@ func (m *virtualMemory) Reallocate(size uint64) []byte {
|
||||
com := uint64(len(m.buf))
|
||||
res := uint64(cap(m.buf))
|
||||
if com < size && size <= res {
|
||||
// Round up to the page size.
|
||||
// Grow geometrically, round up to the page size.
|
||||
rnd := uint64(windows.Getpagesize() - 1)
|
||||
new := (size + rnd) &^ rnd
|
||||
new := com + com>>3
|
||||
new = min(max(size, new), res)
|
||||
new = (new + rnd) &^ rnd
|
||||
|
||||
// Commit additional memory up to new bytes.
|
||||
_, err := windows.VirtualAlloc(m.addr, uintptr(new), windows.MEM_COMMIT, windows.PAGE_READWRITE)
|
||||
@@ -57,8 +67,7 @@ func (m *virtualMemory) Reallocate(size uint64) []byte {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Update committed memory.
|
||||
m.buf = m.buf[:new]
|
||||
m.buf = m.buf[:new] // Update committed memory.
|
||||
}
|
||||
// Limit returned capacity because bytes beyond
|
||||
// len(m.buf) have not yet been committed.
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
//go:build !goexperiment.jsonv2
|
||||
|
||||
package util
|
||||
|
||||
import (
|
||||
|
||||
52
internal/util/json_v2.go
Normal file
52
internal/util/json_v2.go
Normal file
@@ -0,0 +1,52 @@
|
||||
//go:build goexperiment.jsonv2
|
||||
|
||||
package util
|
||||
|
||||
import (
|
||||
"encoding/json/v2"
|
||||
"math"
|
||||
"strconv"
|
||||
"time"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
type JSON struct{ Value any }
|
||||
|
||||
func (j JSON) Scan(value any) error {
|
||||
var buf []byte
|
||||
|
||||
switch v := value.(type) {
|
||||
case []byte:
|
||||
buf = v
|
||||
case string:
|
||||
buf = unsafe.Slice(unsafe.StringData(v), len(v))
|
||||
case int64:
|
||||
buf = strconv.AppendInt(nil, v, 10)
|
||||
case float64:
|
||||
buf = AppendNumber(nil, v)
|
||||
case time.Time:
|
||||
buf = append(buf, '"')
|
||||
buf = v.AppendFormat(buf, time.RFC3339Nano)
|
||||
buf = append(buf, '"')
|
||||
case nil:
|
||||
buf = []byte("null")
|
||||
default:
|
||||
panic(AssertErr())
|
||||
}
|
||||
|
||||
return json.Unmarshal(buf, j.Value)
|
||||
}
|
||||
|
||||
func AppendNumber(dst []byte, f float64) []byte {
|
||||
switch {
|
||||
case math.IsNaN(f):
|
||||
dst = append(dst, "null"...)
|
||||
case math.IsInf(f, 1):
|
||||
dst = append(dst, "9.0e999"...)
|
||||
case math.IsInf(f, -1):
|
||||
dst = append(dst, "-9.0e999"...)
|
||||
default:
|
||||
return strconv.AppendFloat(dst, f, 'g', -1, 64)
|
||||
}
|
||||
return dst
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
83
json.go
83
json.go
@@ -1,6 +1,13 @@
|
||||
//go:build !goexperiment.jsonv2
|
||||
|
||||
package sqlite3
|
||||
|
||||
import "github.com/ncruces/go-sqlite3/internal/util"
|
||||
import (
|
||||
"encoding/json"
|
||||
"strconv"
|
||||
|
||||
"github.com/ncruces/go-sqlite3/internal/util"
|
||||
)
|
||||
|
||||
// JSON returns a value that can be used as an argument to
|
||||
// [database/sql.DB.Exec], [database/sql.Row.Scan] and similar methods to
|
||||
@@ -10,3 +17,77 @@ import "github.com/ncruces/go-sqlite3/internal/util"
|
||||
func JSON(value any) any {
|
||||
return util.JSON{Value: value}
|
||||
}
|
||||
|
||||
// ResultJSON sets the result of the function to the JSON encoding of value.
|
||||
//
|
||||
// https://sqlite.org/c3ref/result_blob.html
|
||||
func (ctx Context) ResultJSON(value any) {
|
||||
err := json.NewEncoder(callbackWriter(func(p []byte) (int, error) {
|
||||
ctx.ResultRawText(p[:len(p)-1]) // remove the newline
|
||||
return 0, nil
|
||||
})).Encode(value)
|
||||
|
||||
if err != nil {
|
||||
ctx.ResultError(err)
|
||||
return // notest
|
||||
}
|
||||
}
|
||||
|
||||
// BindJSON binds the JSON encoding of value to the prepared statement.
|
||||
// The leftmost SQL parameter has an index of 1.
|
||||
//
|
||||
// https://sqlite.org/c3ref/bind_blob.html
|
||||
func (s *Stmt) BindJSON(param int, value any) error {
|
||||
return json.NewEncoder(callbackWriter(func(p []byte) (int, error) {
|
||||
return 0, s.BindRawText(param, p[:len(p)-1]) // remove the newline
|
||||
})).Encode(value)
|
||||
}
|
||||
|
||||
// ColumnJSON parses the JSON-encoded value of the result column
|
||||
// and stores it in the value pointed to by ptr.
|
||||
// The leftmost column of the result set has the index 0.
|
||||
//
|
||||
// https://sqlite.org/c3ref/column_blob.html
|
||||
func (s *Stmt) ColumnJSON(col int, ptr any) error {
|
||||
var data []byte
|
||||
switch s.ColumnType(col) {
|
||||
case NULL:
|
||||
data = []byte("null")
|
||||
case TEXT:
|
||||
data = s.ColumnRawText(col)
|
||||
case BLOB:
|
||||
data = s.ColumnRawBlob(col)
|
||||
case INTEGER:
|
||||
data = strconv.AppendInt(nil, s.ColumnInt64(col), 10)
|
||||
case FLOAT:
|
||||
data = util.AppendNumber(nil, s.ColumnFloat(col))
|
||||
default:
|
||||
panic(util.AssertErr())
|
||||
}
|
||||
return json.Unmarshal(data, ptr)
|
||||
}
|
||||
|
||||
// JSON parses a JSON-encoded value
|
||||
// and stores the result in the value pointed to by ptr.
|
||||
func (v Value) JSON(ptr any) error {
|
||||
var data []byte
|
||||
switch v.Type() {
|
||||
case NULL:
|
||||
data = []byte("null")
|
||||
case TEXT:
|
||||
data = v.RawText()
|
||||
case BLOB:
|
||||
data = v.RawBlob()
|
||||
case INTEGER:
|
||||
data = strconv.AppendInt(nil, v.Int64(), 10)
|
||||
case FLOAT:
|
||||
data = util.AppendNumber(nil, v.Float())
|
||||
default:
|
||||
panic(util.AssertErr())
|
||||
}
|
||||
return json.Unmarshal(data, ptr)
|
||||
}
|
||||
|
||||
type callbackWriter func(p []byte) (int, error)
|
||||
|
||||
func (fn callbackWriter) Write(p []byte) (int, error) { return fn(p) }
|
||||
|
||||
113
json_v2.go
Normal file
113
json_v2.go
Normal file
@@ -0,0 +1,113 @@
|
||||
//go:build goexperiment.jsonv2
|
||||
|
||||
package sqlite3
|
||||
|
||||
import (
|
||||
"encoding/json/v2"
|
||||
"strconv"
|
||||
|
||||
"github.com/ncruces/go-sqlite3/internal/util"
|
||||
)
|
||||
|
||||
// JSON returns a value that can be used as an argument to
|
||||
// [database/sql.DB.Exec], [database/sql.Row.Scan] and similar methods to
|
||||
// store value as JSON, or decode JSON into value.
|
||||
// JSON should NOT be used with [Stmt.BindJSON], [Stmt.ColumnJSON],
|
||||
// [Value.JSON], or [Context.ResultJSON].
|
||||
func JSON(value any) any {
|
||||
return util.JSON{Value: value}
|
||||
}
|
||||
|
||||
// ResultJSON sets the result of the function to the JSON encoding of value.
|
||||
//
|
||||
// https://sqlite.org/c3ref/result_blob.html
|
||||
func (ctx Context) ResultJSON(value any) {
|
||||
w := bytesWriter{sqlite: ctx.c.sqlite}
|
||||
if err := json.MarshalWrite(&w, value); err != nil {
|
||||
ctx.c.free(w.ptr)
|
||||
ctx.ResultError(err)
|
||||
return // notest
|
||||
}
|
||||
ctx.c.call("sqlite3_result_text_go",
|
||||
stk_t(ctx.handle), stk_t(w.ptr), stk_t(len(w.buf)))
|
||||
}
|
||||
|
||||
// BindJSON binds the JSON encoding of value to the prepared statement.
|
||||
// The leftmost SQL parameter has an index of 1.
|
||||
//
|
||||
// https://sqlite.org/c3ref/bind_blob.html
|
||||
func (s *Stmt) BindJSON(param int, value any) error {
|
||||
w := bytesWriter{sqlite: s.c.sqlite}
|
||||
if err := json.MarshalWrite(&w, value); err != nil {
|
||||
s.c.free(w.ptr)
|
||||
return err
|
||||
}
|
||||
rc := res_t(s.c.call("sqlite3_bind_text_go",
|
||||
stk_t(s.handle), stk_t(param),
|
||||
stk_t(w.ptr), stk_t(len(w.buf))))
|
||||
return s.c.error(rc)
|
||||
}
|
||||
|
||||
// ColumnJSON parses the JSON-encoded value of the result column
|
||||
// and stores it in the value pointed to by ptr.
|
||||
// The leftmost column of the result set has the index 0.
|
||||
//
|
||||
// https://sqlite.org/c3ref/column_blob.html
|
||||
func (s *Stmt) ColumnJSON(col int, ptr any) error {
|
||||
var data []byte
|
||||
switch s.ColumnType(col) {
|
||||
case NULL:
|
||||
data = []byte("null")
|
||||
case TEXT:
|
||||
data = s.ColumnRawText(col)
|
||||
case BLOB:
|
||||
data = s.ColumnRawBlob(col)
|
||||
case INTEGER:
|
||||
data = strconv.AppendInt(nil, s.ColumnInt64(col), 10)
|
||||
case FLOAT:
|
||||
data = util.AppendNumber(nil, s.ColumnFloat(col))
|
||||
default:
|
||||
panic(util.AssertErr())
|
||||
}
|
||||
return json.Unmarshal(data, ptr)
|
||||
}
|
||||
|
||||
// JSON parses a JSON-encoded value
|
||||
// and stores the result in the value pointed to by ptr.
|
||||
func (v Value) JSON(ptr any) error {
|
||||
var data []byte
|
||||
switch v.Type() {
|
||||
case NULL:
|
||||
data = []byte("null")
|
||||
case TEXT:
|
||||
data = v.RawText()
|
||||
case BLOB:
|
||||
data = v.RawBlob()
|
||||
case INTEGER:
|
||||
data = strconv.AppendInt(nil, v.Int64(), 10)
|
||||
case FLOAT:
|
||||
data = util.AppendNumber(nil, v.Float())
|
||||
default:
|
||||
panic(util.AssertErr())
|
||||
}
|
||||
return json.Unmarshal(data, ptr)
|
||||
}
|
||||
|
||||
type bytesWriter struct {
|
||||
*sqlite
|
||||
buf []byte
|
||||
ptr ptr_t
|
||||
}
|
||||
|
||||
func (b *bytesWriter) Write(p []byte) (n int, err error) {
|
||||
if len(p) > cap(b.buf)-len(b.buf) {
|
||||
want := int64(len(b.buf)) + int64(len(p))
|
||||
grow := int64(cap(b.buf))
|
||||
grow += grow >> 1
|
||||
want = max(want, grow)
|
||||
b.ptr = b.realloc(b.ptr, want)
|
||||
b.buf = util.View(b.mod, b.ptr, want)[:len(b.buf)]
|
||||
}
|
||||
b.buf = append(b.buf, p...)
|
||||
return len(p), nil
|
||||
}
|
||||
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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
29
sqlite.go
29
sqlite.go
@@ -5,11 +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"
|
||||
@@ -78,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 {
|
||||
@@ -124,14 +129,14 @@ 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)
|
||||
switch {
|
||||
case msg == "not an error":
|
||||
msg = ""
|
||||
case msg == util.ErrorCodeString(uint32(rc))[len("sqlite3: "):]:
|
||||
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 = ""
|
||||
}
|
||||
}
|
||||
@@ -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,46 +3,54 @@ set -euo pipefail
|
||||
|
||||
cd -P -- "$(dirname -- "$0")"
|
||||
|
||||
curl -#OL "https://sqlite.org/2025/sqlite-amalgamation-3500400.zip"
|
||||
unzip -d . sqlite-amalgamation-*.zip
|
||||
mv sqlite-amalgamation-*/sqlite3.c .
|
||||
mv sqlite-amalgamation-*/sqlite3.h .
|
||||
mv sqlite-amalgamation-*/sqlite3ext.h .
|
||||
rm -rf sqlite-amalgamation-*
|
||||
curl -#OL "https://sqlite.org/2025/sqlite-autoconf-3510000.tar.gz"
|
||||
|
||||
# To test a snapshot:
|
||||
# Verify download.
|
||||
if hash=$(openssl dgst -sha3-256 sqlite-autoconf-*.tar.gz); then
|
||||
if ! [[ $hash =~ fa52f9cc74dbca004aa650ae698036a3350611f672649e165078f4eae21d6a2e ]]; then
|
||||
echo $hash
|
||||
exit 1
|
||||
fi
|
||||
fi 2> /dev/null
|
||||
|
||||
tar xzf sqlite-autoconf-*.tar.gz
|
||||
|
||||
# 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
|
||||
|
||||
@@ -43,10 +43,11 @@ EOF
|
||||
-Wl,--export=qsort
|
||||
|
||||
"$BINARYEN/wasm-ctor-eval" -g -c _initialize libc.wasm -o libc.tmp
|
||||
"$BINARYEN/wasm-opt" -g --strip --strip-producers -c -O3 \
|
||||
libc.tmp -o libc.wasm \
|
||||
"$BINARYEN/wasm-opt" -g libc.tmp -o libc.wasm \
|
||||
--low-memory-unused --generate-global-effects --converge -O3 \
|
||||
--enable-mutable-globals --enable-nontrapping-float-to-int \
|
||||
--enable-simd --enable-bulk-memory --enable-sign-ext \
|
||||
--enable-reference-types --enable-multivalue
|
||||
--enable-reference-types --enable-multivalue \
|
||||
--strip --strip-debug --strip-producers
|
||||
|
||||
"$BINARYEN/wasm-dis" -o libc.wat libc.wasm
|
||||
Binary file not shown.
File diff suppressed because it is too large
Load Diff
@@ -75,8 +75,7 @@ func Benchmark_memset(b *testing.B) {
|
||||
clear(memory)
|
||||
|
||||
b.SetBytes(size)
|
||||
b.ResetTimer()
|
||||
for range b.N {
|
||||
for b.Loop() {
|
||||
call(memset, ptr1, 3, size)
|
||||
}
|
||||
}
|
||||
@@ -86,8 +85,7 @@ func Benchmark_memcpy(b *testing.B) {
|
||||
fill(memory[ptr2:ptr2+size], 5)
|
||||
|
||||
b.SetBytes(size)
|
||||
b.ResetTimer()
|
||||
for range b.N {
|
||||
for b.Loop() {
|
||||
call(memcpy, ptr1, ptr2, size)
|
||||
}
|
||||
}
|
||||
@@ -97,8 +95,7 @@ func Benchmark_strlen(b *testing.B) {
|
||||
fill(memory[ptr1:ptr1+size-1], 5)
|
||||
|
||||
b.SetBytes(size)
|
||||
b.ResetTimer()
|
||||
for range b.N {
|
||||
for b.Loop() {
|
||||
call(strlen, ptr1)
|
||||
}
|
||||
}
|
||||
@@ -109,8 +106,7 @@ func Benchmark_memchr(b *testing.B) {
|
||||
fill(memory[ptr1+size/2:ptr1+size], 5)
|
||||
|
||||
b.SetBytes(size/2 + 1)
|
||||
b.ResetTimer()
|
||||
for range b.N {
|
||||
for b.Loop() {
|
||||
call(memchr, ptr1, 5, size)
|
||||
}
|
||||
}
|
||||
@@ -121,8 +117,7 @@ func Benchmark_strchr(b *testing.B) {
|
||||
fill(memory[ptr1+size/2:ptr1+size-1], 5)
|
||||
|
||||
b.SetBytes(size/2 + 1)
|
||||
b.ResetTimer()
|
||||
for range b.N {
|
||||
for b.Loop() {
|
||||
call(strchr, ptr1, 5)
|
||||
}
|
||||
}
|
||||
@@ -133,8 +128,7 @@ func Benchmark_strrchr(b *testing.B) {
|
||||
fill(memory[ptr1+size/2:ptr1+size-1], 7)
|
||||
|
||||
b.SetBytes(size/2 + 1)
|
||||
b.ResetTimer()
|
||||
for range b.N {
|
||||
for b.Loop() {
|
||||
call(strrchr, ptr1, 5)
|
||||
}
|
||||
}
|
||||
@@ -146,8 +140,7 @@ func Benchmark_memcmp(b *testing.B) {
|
||||
fill(memory[ptr2+size/2:ptr2+size], 5)
|
||||
|
||||
b.SetBytes(size/2 + 1)
|
||||
b.ResetTimer()
|
||||
for range b.N {
|
||||
for b.Loop() {
|
||||
call(memcmp, ptr1, ptr2, size)
|
||||
}
|
||||
}
|
||||
@@ -162,8 +155,7 @@ func Benchmark_strspn(b *testing.B) {
|
||||
memory[ptr2+3] = 9
|
||||
|
||||
b.SetBytes(size)
|
||||
b.ResetTimer()
|
||||
for range b.N {
|
||||
for b.Loop() {
|
||||
call(strspn, ptr1, ptr2)
|
||||
}
|
||||
}
|
||||
@@ -176,8 +168,7 @@ func Benchmark_strcspn(b *testing.B) {
|
||||
memory[ptr2+1] = 9
|
||||
|
||||
b.SetBytes(size)
|
||||
b.ResetTimer()
|
||||
for range b.N {
|
||||
for b.Loop() {
|
||||
call(strcspn, ptr1, ptr2)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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_123/binaryen-version_123-$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;
|
||||
}
|
||||
|
||||
40
stmt.go
40
stmt.go
@@ -1,9 +1,7 @@
|
||||
package sqlite3
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"math"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/ncruces/go-sqlite3/internal/util"
|
||||
@@ -362,16 +360,6 @@ func (s *Stmt) BindPointer(param int, ptr any) error {
|
||||
return s.c.error(rc)
|
||||
}
|
||||
|
||||
// BindJSON binds the JSON encoding of value to the prepared statement.
|
||||
// The leftmost SQL parameter has an index of 1.
|
||||
//
|
||||
// https://sqlite.org/c3ref/bind_blob.html
|
||||
func (s *Stmt) BindJSON(param int, value any) error {
|
||||
return json.NewEncoder(callbackWriter(func(p []byte) (int, error) {
|
||||
return 0, s.BindRawText(param, p[:len(p)-1]) // remove the newline
|
||||
})).Encode(value)
|
||||
}
|
||||
|
||||
// BindValue binds a copy of value to the prepared statement.
|
||||
// The leftmost SQL parameter has an index of 1.
|
||||
//
|
||||
@@ -598,30 +586,6 @@ func (s *Stmt) columnRawBytes(col int, ptr ptr_t, nul int32) []byte {
|
||||
return util.View(s.c.mod, ptr, int64(n+nul))[:n]
|
||||
}
|
||||
|
||||
// ColumnJSON parses the JSON-encoded value of the result column
|
||||
// and stores it in the value pointed to by ptr.
|
||||
// The leftmost column of the result set has the index 0.
|
||||
//
|
||||
// https://sqlite.org/c3ref/column_blob.html
|
||||
func (s *Stmt) ColumnJSON(col int, ptr any) error {
|
||||
var data []byte
|
||||
switch s.ColumnType(col) {
|
||||
case NULL:
|
||||
data = []byte("null")
|
||||
case TEXT:
|
||||
data = s.ColumnRawText(col)
|
||||
case BLOB:
|
||||
data = s.ColumnRawBlob(col)
|
||||
case INTEGER:
|
||||
data = strconv.AppendInt(nil, s.ColumnInt64(col), 10)
|
||||
case FLOAT:
|
||||
data = util.AppendNumber(nil, s.ColumnFloat(col))
|
||||
default:
|
||||
panic(util.AssertErr())
|
||||
}
|
||||
return json.Unmarshal(data, ptr)
|
||||
}
|
||||
|
||||
// ColumnValue returns the unprotected value of the result column.
|
||||
// The leftmost column of the result set has the index 0.
|
||||
//
|
||||
@@ -748,7 +712,3 @@ func (s *Stmt) columns(count int64) ([]byte, ptr_t, error) {
|
||||
|
||||
return util.View(s.c.mod, typePtr, count), dataPtr, nil
|
||||
}
|
||||
|
||||
type callbackWriter func(p []byte) (int, error)
|
||||
|
||||
func (fn callbackWriter) Write(p []byte) (int, error) { return fn(p) }
|
||||
|
||||
@@ -45,10 +45,7 @@ func TestBlob(t *testing.T) {
|
||||
}
|
||||
|
||||
var data [1280]byte
|
||||
_, err = rand.Read(data[:])
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
rand.Read(data[:])
|
||||
|
||||
_, err = blob.Write(data[:size/2])
|
||||
if err != nil {
|
||||
|
||||
@@ -388,11 +388,11 @@ func TestConn_Trace(t *testing.T) {
|
||||
|
||||
func TestConn_AutoVacuumPages(t *testing.T) {
|
||||
t.Parallel()
|
||||
tmp := memdb.TestDB(t, url.Values{
|
||||
dsn := memdb.TestDB(t, url.Values{
|
||||
"_pragma": {"auto_vacuum(full)"},
|
||||
})
|
||||
|
||||
db, err := sqlite3.Open(tmp)
|
||||
db, err := sqlite3.Open(dsn)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -163,7 +163,7 @@ func TestConn_SetInterrupt(t *testing.T) {
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
ctx, cancel := context.WithCancel(t.Context())
|
||||
db.SetInterrupt(ctx)
|
||||
|
||||
// Interrupt doesn't interrupt this.
|
||||
@@ -202,9 +202,7 @@ func TestConn_SetInterrupt(t *testing.T) {
|
||||
t.Errorf("got %v, want sqlite3.INTERRUPT", err)
|
||||
}
|
||||
|
||||
ctx, cancel = context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
db.SetInterrupt(ctx)
|
||||
db.SetInterrupt(t.Context())
|
||||
|
||||
// Interrupting can be cleared.
|
||||
err = db.Exec(`SELECT 1`)
|
||||
@@ -212,9 +210,8 @@ func TestConn_SetInterrupt(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
db.SetInterrupt(ctx)
|
||||
if got := db.GetInterrupt(); got != ctx {
|
||||
t.Errorf("got %v, want %v", got, ctx)
|
||||
if got := db.GetInterrupt(); got != t.Context() {
|
||||
t.Errorf("got %v, want %v", got, t.Context())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package tests
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/ncruces/go-sqlite3"
|
||||
@@ -13,12 +12,9 @@ import (
|
||||
|
||||
func TestDriver(t *testing.T) {
|
||||
t.Parallel()
|
||||
tmp := memdb.TestDB(t)
|
||||
dsn := memdb.TestDB(t)
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
db, err := driver.Open(tmp, nil, func(c *sqlite3.Conn) error {
|
||||
db, err := driver.Open(dsn, nil, func(c *sqlite3.Conn) error {
|
||||
return c.Exec(`PRAGMA optimize`)
|
||||
})
|
||||
if err != nil {
|
||||
@@ -26,13 +22,13 @@ func TestDriver(t *testing.T) {
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
conn, err := db.Conn(ctx)
|
||||
conn, err := db.Conn(t.Context())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
res, err := conn.ExecContext(ctx,
|
||||
res, err := conn.ExecContext(t.Context(),
|
||||
`CREATE TABLE users (id INTEGER PRIMARY KEY NOT NULL, name VARCHAR(10))`)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -52,7 +48,7 @@ func TestDriver(t *testing.T) {
|
||||
t.Errorf("got %d want 0", changes)
|
||||
}
|
||||
|
||||
res, err = conn.ExecContext(ctx,
|
||||
res, err = conn.ExecContext(t.Context(),
|
||||
`INSERT INTO users (id, name) VALUES (0, 'go'), (1, 'zig'), (2, 'whatever')`)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -65,7 +61,7 @@ func TestDriver(t *testing.T) {
|
||||
t.Errorf("got %d want 3", changes)
|
||||
}
|
||||
|
||||
stmt, err := conn.PrepareContext(context.Background(),
|
||||
stmt, err := conn.PrepareContext(t.Context(),
|
||||
`SELECT id, name FROM users`)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package tests
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"math"
|
||||
"testing"
|
||||
@@ -17,31 +16,28 @@ import (
|
||||
|
||||
func TestJSON(t *testing.T) {
|
||||
t.Parallel()
|
||||
tmp := memdb.TestDB(t)
|
||||
dsn := memdb.TestDB(t)
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
db, err := driver.Open(tmp)
|
||||
db, err := driver.Open(dsn)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
conn, err := db.Conn(ctx)
|
||||
conn, err := db.Conn(t.Context())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
_, err = conn.ExecContext(ctx, `CREATE TABLE test (col)`)
|
||||
_, err = conn.ExecContext(t.Context(), `CREATE TABLE test (col)`)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
reference := time.Date(2013, 10, 7, 4, 23, 19, 120_000_000, time.FixedZone("", -4*3600))
|
||||
|
||||
_, err = conn.ExecContext(ctx,
|
||||
_, err = conn.ExecContext(t.Context(),
|
||||
`INSERT INTO test (col) VALUES (?), (?), (?), (?)`,
|
||||
nil, 1, math.Pi, reference,
|
||||
)
|
||||
@@ -49,7 +45,7 @@ func TestJSON(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
_, err = conn.ExecContext(ctx,
|
||||
_, err = conn.ExecContext(t.Context(),
|
||||
`INSERT INTO test (col) VALUES (?), (?), (?), (?)`,
|
||||
sqlite3.JSON(math.Pi), sqlite3.JSON(false),
|
||||
julianday.Format(reference), sqlite3.JSON([]string{}))
|
||||
@@ -57,7 +53,7 @@ func TestJSON(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
rows, err := conn.QueryContext(ctx, "SELECT * FROM test")
|
||||
rows, err := conn.QueryContext(t.Context(), "SELECT * FROM test")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
@@ -107,9 +107,9 @@ func Test_mvcc(t *testing.T) {
|
||||
iter = 5000
|
||||
}
|
||||
|
||||
mvcc.Create("test.db", "")
|
||||
name := "file:/test.db?vfs=mvcc" +
|
||||
"&_pragma=busy_timeout(10000)"
|
||||
name := mvcc.TestDB(t, mvcc.Snapshot{}, url.Values{
|
||||
"_pragma": {"busy_timeout(10000)"},
|
||||
})
|
||||
createDB(t, name)
|
||||
testParallel(t, name, iter)
|
||||
testIntegrity(t, name)
|
||||
@@ -330,9 +330,9 @@ func Benchmark_memdb(b *testing.B) {
|
||||
}
|
||||
|
||||
func Benchmark_mvcc(b *testing.B) {
|
||||
mvcc.Create("test.db", "")
|
||||
name := "file:/test.db?vfs=mvcc" +
|
||||
"&_pragma=busy_timeout(10000)"
|
||||
name := mvcc.TestDB(b, mvcc.Snapshot{}, url.Values{
|
||||
"_pragma": {"busy_timeout(10000)"},
|
||||
})
|
||||
createDB(b, name)
|
||||
|
||||
b.ResetTimer()
|
||||
|
||||
@@ -496,7 +496,7 @@ func TestStmt(t *testing.T) {
|
||||
if got := stmt.ColumnBlob(0, nil); string(got) != "true" {
|
||||
t.Errorf("got %q, want true", got)
|
||||
}
|
||||
var got any = 1
|
||||
var got any
|
||||
if err := stmt.ColumnJSON(0, &got); err != nil {
|
||||
t.Error(err)
|
||||
} else if got != true {
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package tests
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"testing"
|
||||
@@ -134,37 +133,36 @@ func TestTimeFormat_Decode(t *testing.T) {
|
||||
|
||||
func TestTimeFormat_Scanner(t *testing.T) {
|
||||
t.Parallel()
|
||||
tmp := memdb.TestDB(t)
|
||||
dsn := memdb.TestDB(t)
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
db, err := driver.Open(tmp)
|
||||
db, err := driver.Open(dsn)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
conn, err := db.Conn(ctx)
|
||||
conn, err := db.Conn(t.Context())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
_, err = conn.ExecContext(ctx, `CREATE TABLE test (col)`)
|
||||
_, err = conn.ExecContext(t.Context(), `CREATE TABLE test (col)`)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
reference := time.Date(2013, 10, 7, 4, 23, 19, 120_000_000, time.FixedZone("", -4*3600))
|
||||
|
||||
_, err = conn.ExecContext(ctx, `INSERT INTO test VALUES (?)`, sqlite3.TimeFormat7TZ.Encode(reference))
|
||||
_, err = conn.ExecContext(t.Context(), `INSERT INTO test VALUES (?)`,
|
||||
sqlite3.TimeFormat7TZ.Encode(reference))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
var got time.Time
|
||||
err = conn.QueryRowContext(ctx, "SELECT * FROM test").Scan(sqlite3.TimeFormatAuto.Scanner(&got))
|
||||
err = conn.QueryRowContext(t.Context(), "SELECT * FROM test").
|
||||
Scan(sqlite3.TimeFormatAuto.Scanner(&got))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
@@ -170,7 +170,7 @@ func TestConn_Transaction_interrupt(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
ctx, cancel := context.WithCancel(t.Context())
|
||||
db.SetInterrupt(ctx)
|
||||
|
||||
tx, err = db.BeginExclusive()
|
||||
@@ -203,7 +203,7 @@ func TestConn_Transaction_interrupt(t *testing.T) {
|
||||
t.Errorf("got %v, want sqlite3.INTERRUPT", err)
|
||||
}
|
||||
|
||||
db.SetInterrupt(context.Background())
|
||||
db.SetInterrupt(t.Context())
|
||||
stmt, _, err := db.Prepare(`SELECT count(*) FROM test`)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -226,7 +226,7 @@ func TestConn_Transaction_interrupted(t *testing.T) {
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
ctx, cancel := context.WithCancel(t.Context())
|
||||
db.SetInterrupt(ctx)
|
||||
cancel()
|
||||
|
||||
@@ -246,15 +246,15 @@ func TestConn_Transaction_interrupted(t *testing.T) {
|
||||
|
||||
func TestConn_Transaction_busy(t *testing.T) {
|
||||
t.Parallel()
|
||||
tmp := memdb.TestDB(t)
|
||||
dsn := memdb.TestDB(t)
|
||||
|
||||
db1, err := sqlite3.Open(tmp)
|
||||
db1, err := sqlite3.Open(dsn)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer db1.Close()
|
||||
|
||||
db2, err := sqlite3.Open(tmp + "&_pragma=busy_timeout(10000)")
|
||||
db2, err := sqlite3.Open(dsn + "&_pragma=busy_timeout(10000)")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -274,7 +274,7 @@ func TestConn_Transaction_busy(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
ctx, cancel := context.WithCancel(t.Context())
|
||||
db2.SetInterrupt(ctx)
|
||||
go cancel()
|
||||
|
||||
@@ -493,7 +493,7 @@ func TestConn_Savepoint_interrupt(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
ctx, cancel := context.WithCancel(t.Context())
|
||||
db.SetInterrupt(ctx)
|
||||
|
||||
savept1 := db.Savepoint()
|
||||
@@ -530,7 +530,7 @@ func TestConn_Savepoint_interrupt(t *testing.T) {
|
||||
t.Errorf("got %v, want sqlite3.INTERRUPT", err)
|
||||
}
|
||||
|
||||
db.SetInterrupt(context.Background())
|
||||
db.SetInterrupt(t.Context())
|
||||
stmt, _, err := db.Prepare(`SELECT count(*) FROM test`)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
|
||||
2
time.go
2
time.go
@@ -94,7 +94,7 @@ func (f TimeFormat) Encode(t time.Time) any {
|
||||
case TimeFormatUnix:
|
||||
return t.Unix()
|
||||
case TimeFormatUnixFrac:
|
||||
return float64(t.Unix()) + float64(t.Nanosecond())*1e-9
|
||||
return math.FMA(1e-9, float64(t.Nanosecond()), float64(t.Unix()))
|
||||
case TimeFormatUnixMilli:
|
||||
return t.UnixMilli()
|
||||
case TimeFormatUnixMicro:
|
||||
|
||||
@@ -24,8 +24,9 @@ trap 'rm -f sql3parse_table.tmp' EXIT
|
||||
-Wl,--export=sql3parse_table
|
||||
|
||||
"$BINARYEN/wasm-ctor-eval" -c _initialize sql3parse_table.wasm -o sql3parse_table.tmp
|
||||
"$BINARYEN/wasm-opt" --strip --strip-debug --strip-producers -c -Oz \
|
||||
sql3parse_table.tmp -o sql3parse_table.wasm --low-memory-unused \
|
||||
"$BINARYEN/wasm-opt" sql3parse_table.tmp -o sql3parse_table.wasm \
|
||||
--low-memory-unused --gufa --generate-global-effects --converge -Oz \
|
||||
--enable-mutable-globals --enable-nontrapping-float-to-int \
|
||||
--enable-simd --enable-bulk-memory --enable-sign-ext \
|
||||
--enable-reference-types --enable-multivalue
|
||||
--enable-reference-types --enable-multivalue \
|
||||
--strip --strip-debug --strip-producers
|
||||
Binary file not shown.
23
value.go
23
value.go
@@ -1,9 +1,7 @@
|
||||
package sqlite3
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"math"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/ncruces/go-sqlite3/internal/util"
|
||||
@@ -162,27 +160,6 @@ func (v Value) Pointer() any {
|
||||
return util.GetHandle(v.c.ctx, ptr)
|
||||
}
|
||||
|
||||
// JSON parses a JSON-encoded value
|
||||
// and stores the result in the value pointed to by ptr.
|
||||
func (v Value) JSON(ptr any) error {
|
||||
var data []byte
|
||||
switch v.Type() {
|
||||
case NULL:
|
||||
data = []byte("null")
|
||||
case TEXT:
|
||||
data = v.RawText()
|
||||
case BLOB:
|
||||
data = v.RawBlob()
|
||||
case INTEGER:
|
||||
data = strconv.AppendInt(nil, v.Int64(), 10)
|
||||
case FLOAT:
|
||||
data = util.AppendNumber(nil, v.Float())
|
||||
default:
|
||||
panic(util.AssertErr())
|
||||
}
|
||||
return json.Unmarshal(data, ptr)
|
||||
}
|
||||
|
||||
// NoChange returns true if and only if the value is unchanged
|
||||
// in a virtual table update operatiom.
|
||||
//
|
||||
|
||||
@@ -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"
|
||||
)
|
||||
@@ -25,8 +26,8 @@ func (adiantumCreator) HBSH(key []byte) *hbsh.HBSH {
|
||||
func (adiantumCreator) KDF(text string) []byte {
|
||||
if text == "" {
|
||||
key := make([]byte, 32)
|
||||
n, _ := rand.Read(key)
|
||||
return key[:n]
|
||||
rand.Read(key)
|
||||
return key
|
||||
}
|
||||
return argon2.IDKey([]byte(text), []byte(pepper), 3, 64*1024, 4, 32)
|
||||
}
|
||||
|
||||
@@ -54,9 +54,8 @@ func Test_fileformat(t *testing.T) {
|
||||
func Benchmark_nokey(b *testing.B) {
|
||||
tmp := filepath.Join(b.TempDir(), "test.db")
|
||||
sqlite3.Initialize()
|
||||
b.ResetTimer()
|
||||
|
||||
for range b.N {
|
||||
for b.Loop() {
|
||||
db, err := sqlite3.Open("file:" + filepath.ToSlash(tmp) + "?nolock=1")
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
@@ -68,9 +67,8 @@ func Benchmark_nokey(b *testing.B) {
|
||||
func Benchmark_hexkey(b *testing.B) {
|
||||
tmp := filepath.Join(b.TempDir(), "test.db")
|
||||
sqlite3.Initialize()
|
||||
b.ResetTimer()
|
||||
|
||||
for range b.N {
|
||||
for b.Loop() {
|
||||
db, err := sqlite3.Open("file:" + filepath.ToSlash(tmp) + "?nolock=1" +
|
||||
"&vfs=adiantum&hexkey=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855")
|
||||
if err != nil {
|
||||
@@ -83,9 +81,8 @@ func Benchmark_hexkey(b *testing.B) {
|
||||
func Benchmark_textkey(b *testing.B) {
|
||||
tmp := filepath.Join(b.TempDir(), "test.db")
|
||||
sqlite3.Initialize()
|
||||
b.ResetTimer()
|
||||
|
||||
for range b.N {
|
||||
for b.Loop() {
|
||||
db, err := sqlite3.Open("file:" + filepath.ToSlash(tmp) + "?nolock=1" +
|
||||
"&vfs=adiantum&textkey=correct+horse+battery+staple")
|
||||
if err != nil {
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"os"
|
||||
|
||||
"golang.org/x/crypto/argon2"
|
||||
|
||||
"lukechampine.com/adiantum/hbsh"
|
||||
"lukechampine.com/adiantum/hpolyc"
|
||||
|
||||
@@ -45,8 +46,8 @@ func (hpolycCreator) KDF(secret string) []byte {
|
||||
if secret == "" {
|
||||
// No secret is given, generate a random key.
|
||||
key := make([]byte, 32)
|
||||
n, _ := rand.Read(key)
|
||||
return key[:n]
|
||||
rand.Read(key)
|
||||
return key
|
||||
}
|
||||
// Hash the secret with a KDF.
|
||||
return argon2.IDKey([]byte(secret), []byte("hpolyc"), 3, 64*1024, 4, 32)
|
||||
|
||||
@@ -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
|
||||
@@ -94,6 +94,10 @@ const (
|
||||
OPEN_PRIVATECACHE OpenFlag = 0x00040000 /* Ok for sqlite3_open_v2() */
|
||||
OPEN_WAL OpenFlag = 0x00080000 /* VFS only */
|
||||
OPEN_NOFOLLOW OpenFlag = 0x01000000 /* Ok for sqlite3_open_v2() */
|
||||
_FLAG_ATOMIC OpenFlag = 0x10000000
|
||||
_FLAG_KEEP_WAL OpenFlag = 0x20000000
|
||||
_FLAG_PSOW OpenFlag = 0x40000000
|
||||
_FLAG_SYNC_DIR OpenFlag = 0x80000000
|
||||
)
|
||||
|
||||
// AccessFlag is a flag for the [VFS] Access method.
|
||||
|
||||
84
vfs/file.go
84
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, false, false)
|
||||
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 {
|
||||
@@ -131,27 +131,24 @@ func (vfsOS) OpenFilename(name *Filename, flags OpenFlag) (File, OpenFlag, error
|
||||
}
|
||||
|
||||
file := vfsFile{
|
||||
File: f,
|
||||
psow: true,
|
||||
atomic: osBatchAtomic(f),
|
||||
readOnly: flags&OPEN_READONLY != 0,
|
||||
syncDir: isUnix && isCreate && isJournl,
|
||||
delete: !isUnix && flags&OPEN_DELETEONCLOSE != 0,
|
||||
shm: NewSharedMemory(name.String()+"-shm", flags),
|
||||
File: f,
|
||||
flags: flags | _FLAG_PSOW,
|
||||
shm: NewSharedMemory(name.String()+"-shm", flags),
|
||||
}
|
||||
if osBatchAtomic(f) {
|
||||
file.flags |= _FLAG_ATOMIC
|
||||
}
|
||||
if isUnix && isCreate && isJournl {
|
||||
file.flags |= _FLAG_SYNC_DIR
|
||||
}
|
||||
return &file, flags, nil
|
||||
}
|
||||
|
||||
type vfsFile struct {
|
||||
*os.File
|
||||
shm SharedMemory
|
||||
lock LockLevel
|
||||
readOnly bool
|
||||
keepWAL bool
|
||||
syncDir bool
|
||||
atomic bool
|
||||
delete bool
|
||||
psow bool
|
||||
shm SharedMemory
|
||||
lock LockLevel
|
||||
flags OpenFlag
|
||||
}
|
||||
|
||||
var (
|
||||
@@ -164,7 +161,7 @@ var (
|
||||
)
|
||||
|
||||
func (f *vfsFile) Close() error {
|
||||
if f.delete {
|
||||
if !isUnix && f.flags&OPEN_DELETEONCLOSE != 0 {
|
||||
defer os.Remove(f.Name())
|
||||
}
|
||||
if f.shm != nil {
|
||||
@@ -183,23 +180,20 @@ func (f *vfsFile) WriteAt(p []byte, off int64) (n int, err error) {
|
||||
}
|
||||
|
||||
func (f *vfsFile) Sync(flags SyncFlag) error {
|
||||
dataonly := (flags & SYNC_DATAONLY) != 0
|
||||
fullsync := (flags & 0x0f) == SYNC_FULL
|
||||
|
||||
err := osSync(f.File, fullsync, dataonly)
|
||||
err := osSync(f.File, f.flags, flags)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if isUnix && f.syncDir {
|
||||
f.syncDir = false
|
||||
if isUnix && f.flags&_FLAG_SYNC_DIR != 0 {
|
||||
f.flags ^= _FLAG_SYNC_DIR
|
||||
d, err := os.Open(filepath.Dir(f.File.Name()))
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
defer d.Close()
|
||||
err = osSync(d, false, false)
|
||||
err = osSync(f.File, f.flags, flags)
|
||||
if err != nil {
|
||||
return _IOERR_DIR_FSYNC
|
||||
return sysError{err, _IOERR_DIR_FSYNC}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
@@ -215,10 +209,10 @@ func (f *vfsFile) SectorSize() int {
|
||||
|
||||
func (f *vfsFile) DeviceCharacteristics() DeviceCharacteristic {
|
||||
ret := IOCAP_SUBPAGE_READ
|
||||
if f.atomic {
|
||||
if f.flags&_FLAG_ATOMIC != 0 {
|
||||
ret |= IOCAP_BATCH_ATOMIC
|
||||
}
|
||||
if f.psow {
|
||||
if f.flags&_FLAG_PSOW != 0 {
|
||||
ret |= IOCAP_POWERSAFE_OVERWRITE
|
||||
}
|
||||
if runtime.GOOS == "windows" {
|
||||
@@ -249,8 +243,20 @@ func (f *vfsFile) HasMoved() (bool, error) {
|
||||
return !os.SameFile(fi, pi), nil
|
||||
}
|
||||
|
||||
func (f *vfsFile) LockState() LockLevel { return f.lock }
|
||||
func (f *vfsFile) PowersafeOverwrite() bool { return f.psow }
|
||||
func (f *vfsFile) PersistWAL() bool { return f.keepWAL }
|
||||
func (f *vfsFile) SetPowersafeOverwrite(psow bool) { f.psow = psow }
|
||||
func (f *vfsFile) SetPersistWAL(keepWAL bool) { f.keepWAL = keepWAL }
|
||||
func (f *vfsFile) LockState() LockLevel { return f.lock }
|
||||
func (f *vfsFile) PowersafeOverwrite() bool { return f.flags&_FLAG_PSOW != 0 }
|
||||
func (f *vfsFile) PersistWAL() bool { return f.flags&_FLAG_KEEP_WAL != 0 }
|
||||
|
||||
func (f *vfsFile) SetPowersafeOverwrite(psow bool) {
|
||||
f.flags &^= _FLAG_PSOW
|
||||
if psow {
|
||||
f.flags |= _FLAG_PSOW
|
||||
}
|
||||
}
|
||||
|
||||
func (f *vfsFile) SetPersistWAL(keepWAL bool) {
|
||||
f.flags &^= _FLAG_KEEP_WAL
|
||||
if keepWAL {
|
||||
f.flags |= _FLAG_KEEP_WAL
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
22
vfs/lock.go
22
vfs/lock.go
@@ -41,7 +41,7 @@ func (f *vfsFile) Lock(lock LockLevel) error {
|
||||
}
|
||||
|
||||
// Do not allow any kind of write-lock on a read-only database.
|
||||
if f.readOnly && lock >= LOCK_RESERVED {
|
||||
if lock >= LOCK_RESERVED && f.flags&OPEN_READONLY != 0 {
|
||||
return _IOERR_LOCK
|
||||
}
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -6,4 +6,7 @@ SQLite VFS in pure Go.
|
||||
It has some benefits over the C version:
|
||||
- the memory backing the database needs not be contiguous,
|
||||
- the database can grow/shrink incrementally without copying,
|
||||
- reader-writer concurrency is slightly improved.
|
||||
- reader-writer concurrency is slightly improved.
|
||||
|
||||
[`memdb.TestDB`](https://pkg.go.dev/github.com/ncruces/go-sqlite3/vfs/memdb#TestDB)
|
||||
is the preferred way to setup an in-memory database for testing.
|
||||
@@ -10,6 +10,7 @@
|
||||
package memdb
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"sync"
|
||||
@@ -74,11 +75,27 @@ func Delete(name string) {
|
||||
|
||||
// TestDB creates an empty shared memory database for the test to use.
|
||||
// The database is automatically deleted when the test and all its subtests complete.
|
||||
// Returns a URI filename appropriate to call Open with.
|
||||
// Each subsequent call to TestDB returns a unique database.
|
||||
//
|
||||
// func Test_something(t *testing.T) {
|
||||
// t.Parallel()
|
||||
// dsn := memdb.TestDB(t, url.Values{
|
||||
// "_pragma": {"busy_timeout(1000)"},
|
||||
// })
|
||||
//
|
||||
// db, err := sql.Open("sqlite3", dsn)
|
||||
// if err != nil {
|
||||
// t.Fatal(err)
|
||||
// }
|
||||
// defer db.Close()
|
||||
//
|
||||
// // ...
|
||||
// }
|
||||
func TestDB(tb testing.TB, params ...url.Values) string {
|
||||
tb.Helper()
|
||||
|
||||
name := fmt.Sprintf("%s_%p", tb.Name(), tb)
|
||||
name := fmt.Sprintf("%s_%s", tb.Name(), rand.Text())
|
||||
tb.Cleanup(func() { Delete(name) })
|
||||
Create(name, nil)
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user