mirror of
https://github.com/ncruces/go-sqlite3.git
synced 2026-01-16 15:49:13 +00:00
Compare commits
32 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1f3ad0165e | ||
|
|
0bda48d1d9 | ||
|
|
0026bc91aa | ||
|
|
d84ca9d627 | ||
|
|
5d14e01f94 | ||
|
|
342df983d4 | ||
|
|
00476fb1e2 | ||
|
|
8a64ee6eaa | ||
|
|
8f9a8e2752 | ||
|
|
d8880e4cee | ||
|
|
4b154a842c | ||
|
|
758a53e9bf | ||
|
|
1a42b4c590 | ||
|
|
7e4ec1df1c | ||
|
|
2c582a1d66 | ||
|
|
20a67ca669 | ||
|
|
789e2dc136 | ||
|
|
0399f10c06 | ||
|
|
75c6744b5b | ||
|
|
754e806164 | ||
|
|
2640c9fb54 | ||
|
|
9719d4b0e3 | ||
|
|
b21c69dc1f | ||
|
|
b0f8ff44a5 | ||
|
|
f37bca6a80 | ||
|
|
b4e8fcb752 | ||
|
|
14b98a5d05 | ||
|
|
36a62264f9 | ||
|
|
33ea564f38 | ||
|
|
5c55d8692f | ||
|
|
be2f3036b4 | ||
|
|
784f82f42f |
2
.github/workflows/libc.yml
vendored
2
.github/workflows/libc.yml
vendored
@@ -14,7 +14,7 @@ jobs:
|
||||
runs-on: ${{ matrix.os }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/setup-go@v5
|
||||
with: { go-version: stable }
|
||||
|
||||
|
||||
2
.github/workflows/repro.yml
vendored
2
.github/workflows/repro.yml
vendored
@@ -17,7 +17,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- uses: ilammy/msvc-dev-cmd@v1
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
|
||||
- name: Build
|
||||
shell: bash
|
||||
|
||||
20
.github/workflows/test.yml
vendored
20
.github/workflows/test.yml
vendored
@@ -30,7 +30,7 @@ jobs:
|
||||
contents: write
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/setup-go@v5
|
||||
with: { go-version: stable }
|
||||
|
||||
@@ -98,14 +98,14 @@ jobs:
|
||||
matrix:
|
||||
os:
|
||||
- name: freebsd
|
||||
version: '14.2'
|
||||
version: '14.3'
|
||||
flags: '-test.v'
|
||||
- name: netbsd
|
||||
version: '10.1'
|
||||
flags: '-test.v'
|
||||
- name: freebsd
|
||||
arch: arm64
|
||||
version: '14.2'
|
||||
version: '14.3'
|
||||
flags: '-test.v -test.short'
|
||||
- name: netbsd
|
||||
arch: arm64
|
||||
@@ -118,7 +118,7 @@ jobs:
|
||||
needs: test
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
|
||||
- name: Build
|
||||
env:
|
||||
@@ -128,7 +128,7 @@ jobs:
|
||||
run: .github/workflows/build-test.sh
|
||||
|
||||
- name: Test
|
||||
uses: cross-platform-actions/action@v0.28.0
|
||||
uses: cross-platform-actions/action@v0.29.0
|
||||
with:
|
||||
operating_system: ${{ matrix.os.name }}
|
||||
architecture: ${{ matrix.os.arch }}
|
||||
@@ -155,7 +155,7 @@ jobs:
|
||||
needs: test
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
|
||||
- name: Build
|
||||
env:
|
||||
@@ -174,7 +174,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- uses: bytecodealliance/actions/wasmtime/setup@v1
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/setup-go@v5
|
||||
with: { go-version: stable }
|
||||
|
||||
@@ -195,7 +195,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- uses: docker/setup-qemu-action@v3
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/setup-go@v5
|
||||
with: { go-version: stable }
|
||||
|
||||
@@ -216,7 +216,7 @@ jobs:
|
||||
needs: test
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/setup-go@v5
|
||||
with: { go-version: stable }
|
||||
|
||||
@@ -228,7 +228,7 @@ jobs:
|
||||
needs: test
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/setup-go@v5
|
||||
with: { go-version: stable }
|
||||
|
||||
|
||||
12
const.go
12
const.go
@@ -185,12 +185,12 @@ const (
|
||||
type FunctionFlag uint32
|
||||
|
||||
const (
|
||||
DETERMINISTIC FunctionFlag = 0x000000800
|
||||
DIRECTONLY FunctionFlag = 0x000080000
|
||||
INNOCUOUS FunctionFlag = 0x000200000
|
||||
SELFORDER1 FunctionFlag = 0x002000000
|
||||
// SUBTYPE FunctionFlag = 0x000100000
|
||||
// RESULT_SUBTYPE FunctionFlag = 0x001000000
|
||||
DETERMINISTIC FunctionFlag = 0x000000800
|
||||
DIRECTONLY FunctionFlag = 0x000080000
|
||||
SUBTYPE FunctionFlag = 0x000100000
|
||||
INNOCUOUS FunctionFlag = 0x000200000
|
||||
RESULT_SUBTYPE FunctionFlag = 0x001000000
|
||||
SELFORDER1 FunctionFlag = 0x002000000
|
||||
)
|
||||
|
||||
// StmtStatus name counter values associated with the [Stmt.Status] method.
|
||||
|
||||
@@ -227,6 +227,14 @@ func (ctx Context) ResultError(err error) {
|
||||
}
|
||||
}
|
||||
|
||||
// ResultSubtype sets the subtype of the result of the function.
|
||||
//
|
||||
// https://sqlite.org/c3ref/result_subtype.html
|
||||
func (ctx Context) ResultSubtype(t uint) {
|
||||
ctx.c.call("sqlite3_result_subtype",
|
||||
stk_t(ctx.handle), stk_t(uint32(t)))
|
||||
}
|
||||
|
||||
// VTabNoChange may return true if a column is being fetched as part
|
||||
// of an update during which the column value will not change.
|
||||
//
|
||||
|
||||
@@ -546,8 +546,8 @@ func (s *stmt) setupBindings(args []driver.NamedValue) (err error) {
|
||||
err = s.Stmt.BindTime(id, a, s.tmWrite)
|
||||
case util.JSON:
|
||||
err = s.Stmt.BindJSON(id, a.Value)
|
||||
case util.PointerUnwrap:
|
||||
err = s.Stmt.BindPointer(id, util.UnwrapPointer(a))
|
||||
case util.Pointer:
|
||||
err = s.Stmt.BindPointer(id, a.Value)
|
||||
case nil:
|
||||
err = s.Stmt.BindNull(id)
|
||||
default:
|
||||
@@ -565,7 +565,7 @@ func (s *stmt) CheckNamedValue(arg *driver.NamedValue) error {
|
||||
switch arg.Value.(type) {
|
||||
case bool, int, int64, float64, string, []byte,
|
||||
time.Time, sqlite3.ZeroBlob,
|
||||
util.JSON, util.PointerUnwrap,
|
||||
util.JSON, util.Pointer,
|
||||
nil:
|
||||
return nil
|
||||
default:
|
||||
@@ -604,15 +604,6 @@ func (r resultRowsAffected) RowsAffected() (int64, error) {
|
||||
return int64(r), nil
|
||||
}
|
||||
|
||||
type rows struct {
|
||||
ctx context.Context
|
||||
*stmt
|
||||
names []string
|
||||
types []string
|
||||
nulls []bool
|
||||
scans []scantype
|
||||
}
|
||||
|
||||
type scantype byte
|
||||
|
||||
const (
|
||||
@@ -648,10 +639,20 @@ func scanFromDecl(decl string) scantype {
|
||||
return _ANY
|
||||
}
|
||||
|
||||
type rows struct {
|
||||
ctx context.Context
|
||||
*stmt
|
||||
names []string
|
||||
types []string
|
||||
nulls []bool
|
||||
scans []scantype
|
||||
}
|
||||
|
||||
var (
|
||||
// Ensure these interfaces are implemented:
|
||||
_ driver.RowsColumnTypeDatabaseTypeName = &rows{}
|
||||
_ driver.RowsColumnTypeNullable = &rows{}
|
||||
// _ driver.RowsColumnScanner = &rows{}
|
||||
)
|
||||
|
||||
func (r *rows) Close() error {
|
||||
@@ -740,7 +741,7 @@ func (r *rows) ColumnTypeScanType(index int) (typ reflect.Type) {
|
||||
switch {
|
||||
case scan == _TIME && val != _BLOB && val != _NULL:
|
||||
t := r.Stmt.ColumnTime(index, r.tmRead)
|
||||
useValType = t == time.Time{}
|
||||
useValType = t.IsZero()
|
||||
case scan == _BOOL && val == _INT:
|
||||
i := r.Stmt.ColumnInt64(index)
|
||||
useValType = i != 0 && i != 1
|
||||
@@ -830,3 +831,23 @@ func (r *rows) Next(dest []driver.Value) error {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *rows) ScanColumn(dest any, index int) error {
|
||||
// notest // Go 1.26
|
||||
var ptr *time.Time
|
||||
switch d := dest.(type) {
|
||||
case *time.Time:
|
||||
ptr = d
|
||||
case *sql.NullTime:
|
||||
ptr = &d.Time
|
||||
case *sql.Null[time.Time]:
|
||||
ptr = &d.V
|
||||
default:
|
||||
return driver.ErrSkip
|
||||
}
|
||||
if t := r.Stmt.ColumnTime(index, r.tmRead); !t.IsZero() {
|
||||
*ptr = t
|
||||
return nil
|
||||
}
|
||||
return driver.ErrSkip
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Embeddable Wasm build of SQLite
|
||||
|
||||
This folder includes an embeddable Wasm build of SQLite 3.50.1 for use with
|
||||
This folder includes an embeddable Wasm build of SQLite 3.50.4 for use with
|
||||
[`github.com/ncruces/go-sqlite3`](https://pkg.go.dev/github.com/ncruces/go-sqlite3).
|
||||
|
||||
The following optional features are compiled in:
|
||||
|
||||
Binary file not shown.
@@ -7,16 +7,18 @@ ROOT=../../
|
||||
BINARYEN="$ROOT/tools/binaryen/bin"
|
||||
WASI_SDK="$ROOT/tools/wasi-sdk/bin"
|
||||
|
||||
trap 'rm -rf build/ sqlite/ bcw2.tmp' EXIT
|
||||
trap 'rm -rf sqlite/ build/ bcw2.tmp' EXIT
|
||||
|
||||
mkdir -p sqlite/
|
||||
mkdir -p build/ext/
|
||||
cp "$ROOT"/sqlite3/*.[ch] build/
|
||||
cp "$ROOT"/sqlite3/*.patch build/
|
||||
cd sqlite/
|
||||
|
||||
# https://sqlite.org/src/info/93740658c8c6f531
|
||||
curl -# https://sqlite.org/src/tarball/sqlite.tar.gz?r=93740658c8 | tar xz
|
||||
# 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
|
||||
|
||||
cd sqlite
|
||||
if [[ "$OSTYPE" == "msys" || "$OSTYPE" == "cygwin" ]]; then
|
||||
MSYS_NO_PATHCONV=1 nmake /f makefile.msc sqlite3.c "OPTS=-DSQLITE_ENABLE_UPDATE_DELETE_LIMIT -DSQLITE_ENABLE_ORDERED_SET_AGGREGATES"
|
||||
else
|
||||
@@ -49,7 +51,8 @@ cd ~-
|
||||
-mmutable-globals -mnontrapping-fptoint \
|
||||
-msimd128 -mbulk-memory -msign-ext \
|
||||
-mreference-types -mmultivalue \
|
||||
-fno-stack-protector -fno-stack-clash-protection \
|
||||
-mno-extended-const \
|
||||
-fno-stack-protector \
|
||||
-Wl,--stack-first \
|
||||
-Wl,--import-undefined \
|
||||
-Wl,--initial-memory=327680 \
|
||||
|
||||
@@ -4,11 +4,11 @@ go 1.23.0
|
||||
|
||||
toolchain go1.24.0
|
||||
|
||||
require github.com/ncruces/go-sqlite3 v0.26.0
|
||||
require github.com/ncruces/go-sqlite3 v0.27.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.33.0 // indirect
|
||||
golang.org/x/sys v0.35.0 // indirect
|
||||
)
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
github.com/ncruces/go-sqlite3 v0.26.0 h1:dY6ASfuhSEbtSge6kJwjyJVC7bXCpgEVOycmdboKJek=
|
||||
github.com/ncruces/go-sqlite3 v0.26.0/go.mod h1:46HIzeCQQ+aNleAxCli+vpA2tfh7ttSnw24kQahBc1o=
|
||||
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/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.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
|
||||
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4=
|
||||
golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA=
|
||||
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=
|
||||
|
||||
@@ -17,7 +17,8 @@ trap 'rm -f sqlite3.tmp' EXIT
|
||||
-mmutable-globals -mnontrapping-fptoint \
|
||||
-msimd128 -mbulk-memory -msign-ext \
|
||||
-mreference-types -mmultivalue \
|
||||
-fno-stack-protector -fno-stack-clash-protection \
|
||||
-mno-extended-const \
|
||||
-fno-stack-protector \
|
||||
-Wl,--stack-first \
|
||||
-Wl,--import-undefined \
|
||||
-Wl,--initial-memory=327680 \
|
||||
|
||||
@@ -98,6 +98,7 @@ sqlite3_result_error_toobig
|
||||
sqlite3_result_int64
|
||||
sqlite3_result_null
|
||||
sqlite3_result_pointer_go
|
||||
sqlite3_result_subtype
|
||||
sqlite3_result_text_go
|
||||
sqlite3_result_value
|
||||
sqlite3_result_zeroblob64
|
||||
@@ -126,6 +127,7 @@ sqlite3_value_int64
|
||||
sqlite3_value_nochange
|
||||
sqlite3_value_numeric_type
|
||||
sqlite3_value_pointer_go
|
||||
sqlite3_value_subtype
|
||||
sqlite3_value_text
|
||||
sqlite3_value_type
|
||||
sqlite3_vtab_collation
|
||||
|
||||
@@ -19,7 +19,7 @@ func Test_init(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if version != "3.50.1" {
|
||||
if version != "3.50.4" {
|
||||
t.Error(version)
|
||||
}
|
||||
}
|
||||
|
||||
Binary file not shown.
@@ -38,11 +38,11 @@ you can load into your database connections.
|
||||
- [`github.com/ncruces/go-sqlite3/ext/zorder`](https://pkg.go.dev/github.com/ncruces/go-sqlite3/ext/zorder)
|
||||
maps multidimensional data to one dimension.
|
||||
|
||||
### Pakages
|
||||
### Packages
|
||||
|
||||
These packages may also be useful to work with SQLite:
|
||||
|
||||
- [`github.com/ncruces/decimal`](https://pkg.go.dev/github.com/ncruces/decimal)
|
||||
decimal arithmetic.
|
||||
- [`github.com/ncruces/julianday`](https://pkg.go.dev/github.com/ncruces/julianday)
|
||||
Julian day math.
|
||||
Julian day math.
|
||||
|
||||
@@ -144,18 +144,16 @@ func Test_readblob(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
if stmt.Step() {
|
||||
got := stmt.ColumnText(0)
|
||||
if got != tt.want1 {
|
||||
t.Errorf("got %q", got)
|
||||
}
|
||||
if !stmt.Step() {
|
||||
t.Fatal(stmt.Err())
|
||||
} else if got := stmt.ColumnText(0); got != tt.want1 {
|
||||
t.Errorf("got %q", got)
|
||||
}
|
||||
|
||||
if stmt.Step() {
|
||||
got := stmt.ColumnText(0)
|
||||
if got != tt.want2 {
|
||||
t.Errorf("got %q", got)
|
||||
}
|
||||
if !stmt.Step() {
|
||||
t.Fatal(stmt.Err())
|
||||
} else if got := stmt.ColumnText(0); got != tt.want2 {
|
||||
t.Errorf("got %q", got)
|
||||
}
|
||||
|
||||
err = stmt.Err()
|
||||
|
||||
@@ -147,20 +147,21 @@ func TestAffinity(t *testing.T) {
|
||||
}
|
||||
defer stmt.Close()
|
||||
|
||||
if stmt.Step() {
|
||||
if got := stmt.ColumnText(0); got != "1" {
|
||||
t.Errorf("got %q want 1", got)
|
||||
}
|
||||
if !stmt.Step() {
|
||||
t.Fatal(stmt.Err())
|
||||
} else if got := stmt.ColumnText(0); got != "1" {
|
||||
t.Errorf("got %q want 1", got)
|
||||
}
|
||||
if stmt.Step() {
|
||||
if got := stmt.ColumnText(0); got != "0.1" {
|
||||
t.Errorf("got %q want 0.1", got)
|
||||
}
|
||||
if !stmt.Step() {
|
||||
t.Fatal(stmt.Err())
|
||||
} else if got := stmt.ColumnText(0); got != "0.1" {
|
||||
t.Errorf("got %q want 0.1", got)
|
||||
}
|
||||
if stmt.Step() {
|
||||
if got := stmt.ColumnText(0); got != "e" {
|
||||
t.Errorf("got %q want e", got)
|
||||
}
|
||||
|
||||
if !stmt.Step() {
|
||||
t.Fatal(stmt.Err())
|
||||
} else if got := stmt.ColumnText(0); got != "e" {
|
||||
t.Errorf("got %q want e", got)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ func Register(db *sqlite3.Conn) error {
|
||||
return RegisterFS(db, nil)
|
||||
}
|
||||
|
||||
// Register registers SQL functions readfile, lsmode,
|
||||
// RegisterFS registers SQL functions readfile, lsmode,
|
||||
// and the table-valued function fsdir;
|
||||
// fsys will be used to read files and list directories.
|
||||
func RegisterFS(db *sqlite3.Conn, fsys fs.FS) error {
|
||||
|
||||
@@ -141,10 +141,10 @@ func TestRegister(t *testing.T) {
|
||||
}
|
||||
defer stmt.Close()
|
||||
|
||||
if stmt.Step() {
|
||||
if got := stmt.ColumnInt(0); got != 3 {
|
||||
t.Errorf("got %d, want 3", got)
|
||||
}
|
||||
if !stmt.Step() {
|
||||
t.Fatal(stmt.Err())
|
||||
} else if got := stmt.ColumnInt(0); got != 3 {
|
||||
t.Errorf("got %d, want 3", got)
|
||||
}
|
||||
|
||||
err = db.Exec(`ALTER TABLE v_x RENAME TO v_y`)
|
||||
|
||||
@@ -2,9 +2,8 @@
|
||||
package serdes
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
||||
"github.com/ncruces/go-sqlite3"
|
||||
"github.com/ncruces/go-sqlite3/util/vfsutil"
|
||||
"github.com/ncruces/go-sqlite3/vfs"
|
||||
)
|
||||
|
||||
@@ -14,16 +13,16 @@ func init() {
|
||||
vfs.Register(vfsName, sliceVFS{})
|
||||
}
|
||||
|
||||
var fileToOpen = make(chan *sliceFile, 1)
|
||||
var fileToOpen = make(chan *[]byte, 1)
|
||||
|
||||
// Serialize backs up a database into a byte slice.
|
||||
//
|
||||
// https://sqlite.org/c3ref/serialize.html
|
||||
func Serialize(db *sqlite3.Conn, schema string) ([]byte, error) {
|
||||
var file sliceFile
|
||||
var file []byte
|
||||
fileToOpen <- &file
|
||||
err := db.Backup(schema, "file:serdes.db?vfs="+vfsName)
|
||||
return file.data, err
|
||||
err := db.Backup(schema, "file:serdes.db?nolock=1&vfs="+vfsName)
|
||||
return file, err
|
||||
}
|
||||
|
||||
// Deserialize restores a database from a byte slice,
|
||||
@@ -41,8 +40,8 @@ func Serialize(db *sqlite3.Conn, schema string) ([]byte, error) {
|
||||
// ["memdb"]: https://pkg.go.dev/github.com/ncruces/go-sqlite3/vfs/memdb
|
||||
// ["reader"]: https://pkg.go.dev/github.com/ncruces/go-sqlite3/vfs/readervfs
|
||||
func Deserialize(db *sqlite3.Conn, schema string, data []byte) error {
|
||||
fileToOpen <- &sliceFile{data}
|
||||
return db.Restore(schema, "file:serdes.db?vfs="+vfsName)
|
||||
fileToOpen <- &data
|
||||
return db.Restore(schema, "file:serdes.db?immutable=1&vfs="+vfsName)
|
||||
}
|
||||
|
||||
type sliceVFS struct{}
|
||||
@@ -53,14 +52,14 @@ func (sliceVFS) Open(name string, flags vfs.OpenFlag) (vfs.File, vfs.OpenFlag, e
|
||||
}
|
||||
select {
|
||||
case file := <-fileToOpen:
|
||||
return file, flags | vfs.OPEN_MEMORY, nil
|
||||
return (*vfsutil.SliceFile)(file), flags | vfs.OPEN_MEMORY, nil
|
||||
default:
|
||||
return nil, flags, sqlite3.MISUSE
|
||||
}
|
||||
}
|
||||
|
||||
func (sliceVFS) Delete(name string, dirSync bool) error {
|
||||
// notest // OPEN_MEMORY
|
||||
// notest // no journals to delete
|
||||
return sqlite3.IOERR_DELETE
|
||||
}
|
||||
|
||||
@@ -71,70 +70,3 @@ func (sliceVFS) Access(name string, flag vfs.AccessFlag) (bool, error) {
|
||||
func (sliceVFS) FullPathname(name string) (string, error) {
|
||||
return name, nil
|
||||
}
|
||||
|
||||
type sliceFile struct{ data []byte }
|
||||
|
||||
func (f *sliceFile) ReadAt(b []byte, off int64) (n int, err error) {
|
||||
if d := f.data; off < int64(len(d)) {
|
||||
n = copy(b, d[off:])
|
||||
}
|
||||
if n == 0 {
|
||||
err = io.EOF
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (f *sliceFile) WriteAt(b []byte, off int64) (n int, err error) {
|
||||
if d := f.data; off > int64(len(d)) {
|
||||
f.data = append(d, make([]byte, off-int64(len(d)))...)
|
||||
}
|
||||
d := append(f.data[:off], b...)
|
||||
if len(d) > len(f.data) {
|
||||
f.data = d
|
||||
}
|
||||
return len(b), nil
|
||||
}
|
||||
|
||||
func (f *sliceFile) Size() (int64, error) {
|
||||
return int64(len(f.data)), nil
|
||||
}
|
||||
|
||||
func (f *sliceFile) Truncate(size int64) error {
|
||||
if d := f.data; size < int64(len(d)) {
|
||||
f.data = d[:size]
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *sliceFile) SizeHint(size int64) error {
|
||||
if d := f.data; size > int64(len(d)) {
|
||||
f.data = append(d, make([]byte, size-int64(len(d)))...)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (*sliceFile) Close() error { return nil }
|
||||
|
||||
func (*sliceFile) Sync(flag vfs.SyncFlag) error { return nil }
|
||||
|
||||
func (*sliceFile) Lock(lock vfs.LockLevel) error { return nil }
|
||||
|
||||
func (*sliceFile) Unlock(lock vfs.LockLevel) error { return nil }
|
||||
|
||||
func (*sliceFile) CheckReservedLock() (bool, error) {
|
||||
// notest // OPEN_MEMORY
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func (*sliceFile) SectorSize() int {
|
||||
// notest // IOCAP_POWERSAFE_OVERWRITE
|
||||
return 0
|
||||
}
|
||||
|
||||
func (*sliceFile) DeviceCharacteristics() vfs.DeviceCharacteristic {
|
||||
return vfs.IOCAP_ATOMIC |
|
||||
vfs.IOCAP_SAFE_APPEND |
|
||||
vfs.IOCAP_SEQUENTIAL |
|
||||
vfs.IOCAP_POWERSAFE_OVERWRITE |
|
||||
vfs.IOCAP_SUBPAGE_READ
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package serdes_test
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
"errors"
|
||||
"io"
|
||||
"net/http"
|
||||
@@ -11,7 +12,30 @@ import (
|
||||
"github.com/ncruces/go-sqlite3/ext/serdes"
|
||||
)
|
||||
|
||||
func TestDeserialize(t *testing.T) {
|
||||
//go:embed testdata/wal.db
|
||||
var walDB []byte
|
||||
|
||||
func Test_wal(t *testing.T) {
|
||||
db, err := sqlite3.Open("testdata/wal.db")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
data, err := serdes.Serialize(db, "main")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
compareDBs(t, data, walDB)
|
||||
|
||||
err = serdes.Deserialize(db, "temp", walDB)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func Test_northwind(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("skipping in short mode")
|
||||
}
|
||||
@@ -37,10 +61,14 @@ func TestDeserialize(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if len(input) != len(output) {
|
||||
compareDBs(t, input, output)
|
||||
}
|
||||
|
||||
func compareDBs(t *testing.T, a, b []byte) {
|
||||
if len(a) != len(b) {
|
||||
t.Fatal("lengths are different")
|
||||
}
|
||||
for i := range input {
|
||||
for i := range a {
|
||||
// These may be different.
|
||||
switch {
|
||||
case 24 <= i && i < 28:
|
||||
@@ -53,14 +81,14 @@ func TestDeserialize(t *testing.T) {
|
||||
// SQLite version that wrote the file.
|
||||
continue
|
||||
}
|
||||
if input[i] != output[i] {
|
||||
t.Errorf("difference at %d: %d %d", i, input[i], output[i])
|
||||
if a[i] != b[i] {
|
||||
t.Errorf("difference at %d: %d %d", i, a[i], b[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func httpGet() ([]byte, error) {
|
||||
res, err := http.Get("https://raw.githubusercontent.com/jpwhite3/northwind-SQLite3/refs/heads/main/dist/northwind.db")
|
||||
res, err := http.Get("https://github.com/jpwhite3/northwind-SQLite3/raw/refs/heads/main/dist/northwind.db")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
BIN
ext/serdes/testdata/wal.db
vendored
Normal file
BIN
ext/serdes/testdata/wal.db
vendored
Normal file
Binary file not shown.
@@ -92,7 +92,9 @@ func TestRegister(t *testing.T) {
|
||||
}
|
||||
defer stmt.Close()
|
||||
|
||||
if stmt.Step() {
|
||||
if !stmt.Step() {
|
||||
t.Fatal(stmt.Err())
|
||||
} else {
|
||||
x := stmt.ColumnInt(0)
|
||||
y := stmt.ColumnInt(1)
|
||||
hypot := stmt.ColumnInt(2)
|
||||
|
||||
@@ -37,7 +37,9 @@ func TestRegister_boolean(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if stmt.Step() {
|
||||
if !stmt.Step() {
|
||||
t.Fatal(stmt.Err())
|
||||
} else {
|
||||
if got := stmt.ColumnBool(0); got != true {
|
||||
t.Errorf("got %v, want true", got)
|
||||
}
|
||||
|
||||
@@ -19,8 +19,8 @@ type mode struct {
|
||||
|
||||
func (m mode) Value(ctx sqlite3.Context) {
|
||||
var (
|
||||
max = 0
|
||||
typ = sqlite3.NULL
|
||||
max uint
|
||||
i64 int64
|
||||
f64 float64
|
||||
str string
|
||||
@@ -32,7 +32,6 @@ func (m mode) Value(ctx sqlite3.Context) {
|
||||
i64 = k
|
||||
}
|
||||
}
|
||||
f64 = float64(i64)
|
||||
for k, v := range m.reals {
|
||||
if v > max || v == max && k < f64 {
|
||||
typ = sqlite3.FLOAT
|
||||
@@ -66,33 +65,45 @@ func (m mode) Value(ctx sqlite3.Context) {
|
||||
}
|
||||
}
|
||||
|
||||
func (b *mode) Step(ctx sqlite3.Context, arg ...sqlite3.Value) {
|
||||
func (m *mode) Step(ctx sqlite3.Context, arg ...sqlite3.Value) {
|
||||
switch arg[0].Type() {
|
||||
case sqlite3.INTEGER:
|
||||
b.ints.add(arg[0].Int64())
|
||||
if m.reals == nil {
|
||||
m.ints.add(arg[0].Int64())
|
||||
break
|
||||
}
|
||||
fallthrough
|
||||
case sqlite3.FLOAT:
|
||||
b.reals.add(arg[0].Float())
|
||||
m.reals.add(arg[0].Float())
|
||||
for k, v := range m.ints {
|
||||
m.reals[float64(k)] += v
|
||||
}
|
||||
m.ints = nil
|
||||
case sqlite3.TEXT:
|
||||
b.texts.add(arg[0].Text())
|
||||
m.texts.add(arg[0].Text())
|
||||
case sqlite3.BLOB:
|
||||
b.blobs.add(string(arg[0].RawBlob()))
|
||||
m.blobs.add(string(arg[0].RawBlob()))
|
||||
}
|
||||
}
|
||||
|
||||
func (b *mode) Inverse(ctx sqlite3.Context, arg ...sqlite3.Value) {
|
||||
func (m *mode) Inverse(ctx sqlite3.Context, arg ...sqlite3.Value) {
|
||||
switch arg[0].Type() {
|
||||
case sqlite3.INTEGER:
|
||||
b.ints.del(arg[0].Int64())
|
||||
if m.reals == nil {
|
||||
m.ints.del(arg[0].Int64())
|
||||
break
|
||||
}
|
||||
fallthrough
|
||||
case sqlite3.FLOAT:
|
||||
b.reals.del(arg[0].Float())
|
||||
m.reals.del(arg[0].Float())
|
||||
case sqlite3.TEXT:
|
||||
b.texts.del(arg[0].Text())
|
||||
m.texts.del(arg[0].Text())
|
||||
case sqlite3.BLOB:
|
||||
b.blobs.del(string(arg[0].RawBlob()))
|
||||
m.blobs.del(string(arg[0].RawBlob()))
|
||||
}
|
||||
}
|
||||
|
||||
type counter[T comparable] map[T]int
|
||||
type counter[T comparable] map[T]uint
|
||||
|
||||
func (c *counter[T]) add(k T) {
|
||||
if (*c) == nil {
|
||||
@@ -102,11 +113,9 @@ func (c *counter[T]) add(k T) {
|
||||
}
|
||||
|
||||
func (c counter[T]) del(k T) {
|
||||
switch n := c[k]; n {
|
||||
default:
|
||||
c[k] = n - 1
|
||||
case 1:
|
||||
if n := c[k]; n == 1 {
|
||||
delete(c, k)
|
||||
case 0:
|
||||
} else {
|
||||
c[k] = n - 1
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,10 +21,10 @@ func TestRegister_mode(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if stmt.Step() {
|
||||
if got := stmt.ColumnInt(0); got != 3 {
|
||||
t.Errorf("got %v, want 3", got)
|
||||
}
|
||||
if !stmt.Step() {
|
||||
t.Fatal(stmt.Err())
|
||||
} else if got := stmt.ColumnInt(0); got != 3 {
|
||||
t.Errorf("got %v, want 3", got)
|
||||
}
|
||||
stmt.Close()
|
||||
|
||||
@@ -32,10 +32,10 @@ func TestRegister_mode(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if stmt.Step() {
|
||||
if got := stmt.ColumnInt(0); got != 1 {
|
||||
t.Errorf("got %v, want 1", got)
|
||||
}
|
||||
if !stmt.Step() {
|
||||
t.Fatal(stmt.Err())
|
||||
} else if got := stmt.ColumnInt(0); got != 1 {
|
||||
t.Errorf("got %v, want 1", got)
|
||||
}
|
||||
stmt.Close()
|
||||
|
||||
@@ -43,10 +43,10 @@ func TestRegister_mode(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if stmt.Step() {
|
||||
if got := stmt.ColumnFloat(0); got != 2.5 {
|
||||
t.Errorf("got %v, want 2.5", got)
|
||||
}
|
||||
if !stmt.Step() {
|
||||
t.Fatal(stmt.Err())
|
||||
} else if got := stmt.ColumnFloat(0); got != 2.5 {
|
||||
t.Errorf("got %v, want 2.5", got)
|
||||
}
|
||||
stmt.Close()
|
||||
|
||||
@@ -54,21 +54,22 @@ func TestRegister_mode(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if stmt.Step() {
|
||||
if got := stmt.ColumnText(0); got != "red" {
|
||||
t.Errorf("got %q, want red", got)
|
||||
}
|
||||
if !stmt.Step() {
|
||||
t.Fatal(stmt.Err())
|
||||
} else if got := stmt.ColumnText(0); got != "red" {
|
||||
t.Errorf("got %q, want red", got)
|
||||
}
|
||||
|
||||
stmt.Close()
|
||||
|
||||
stmt, _, err = db.Prepare(`SELECT mode(column1) FROM (VALUES (X'cafebabe'), ('green'), ('blue'), (X'cafebabe'))`)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if stmt.Step() {
|
||||
if got := stmt.ColumnText(0); got != "\xca\xfe\xba\xbe" {
|
||||
t.Errorf("got %q, want cafebabe", got)
|
||||
}
|
||||
if !stmt.Step() {
|
||||
t.Fatal(stmt.Err())
|
||||
} else if got := stmt.ColumnText(0); got != "\xca\xfe\xba\xbe" {
|
||||
t.Errorf("got %q, want cafebabe", got)
|
||||
}
|
||||
stmt.Close()
|
||||
|
||||
@@ -82,4 +83,20 @@ func TestRegister_mode(t *testing.T) {
|
||||
for stmt.Step() {
|
||||
}
|
||||
stmt.Close()
|
||||
|
||||
stmt, _, err = db.Prepare(`SELECT mode(column1) FROM (VALUES (?), (?), (?), (?), (?))`)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
stmt.BindInt(1, 1)
|
||||
stmt.BindInt(2, 1)
|
||||
stmt.BindInt(3, 2)
|
||||
stmt.BindFloat(4, 2)
|
||||
stmt.BindFloat(5, 2)
|
||||
if !stmt.Step() {
|
||||
t.Fatal(stmt.Err())
|
||||
} else if got := stmt.ColumnInt(0); got != 2 {
|
||||
t.Errorf("got %v, want 2", got)
|
||||
}
|
||||
stmt.Close()
|
||||
}
|
||||
|
||||
@@ -38,7 +38,9 @@ func TestRegister_percentile(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if stmt.Step() {
|
||||
if !stmt.Step() {
|
||||
t.Fatal(stmt.Err())
|
||||
} else {
|
||||
if got := stmt.ColumnFloat(0); got != 10 {
|
||||
t.Errorf("got %v, want 10", got)
|
||||
}
|
||||
@@ -65,30 +67,30 @@ func TestRegister_percentile(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if stmt.Step() {
|
||||
if got := stmt.ColumnFloat(0); got != 5.5 {
|
||||
t.Errorf("got %v, want 5.5", got)
|
||||
}
|
||||
if !stmt.Step() {
|
||||
t.Fatal(stmt.Err())
|
||||
} else if got := stmt.ColumnFloat(0); got != 5.5 {
|
||||
t.Errorf("got %v, want 5.5", got)
|
||||
}
|
||||
if stmt.Step() {
|
||||
if got := stmt.ColumnFloat(0); got != 7 {
|
||||
t.Errorf("got %v, want 7", got)
|
||||
}
|
||||
if !stmt.Step() {
|
||||
t.Fatal(stmt.Err())
|
||||
} else if got := stmt.ColumnFloat(0); got != 7 {
|
||||
t.Errorf("got %v, want 7", got)
|
||||
}
|
||||
if stmt.Step() {
|
||||
if got := stmt.ColumnFloat(0); got != 10 {
|
||||
t.Errorf("got %v, want 10", got)
|
||||
}
|
||||
if !stmt.Step() {
|
||||
t.Fatal(stmt.Err())
|
||||
} else if got := stmt.ColumnFloat(0); got != 10 {
|
||||
t.Errorf("got %v, want 10", got)
|
||||
}
|
||||
if stmt.Step() {
|
||||
if got := stmt.ColumnFloat(0); got != 14.5 {
|
||||
t.Errorf("got %v, want 14.5", got)
|
||||
}
|
||||
if !stmt.Step() {
|
||||
t.Fatal(stmt.Err())
|
||||
} else if got := stmt.ColumnFloat(0); got != 14.5 {
|
||||
t.Errorf("got %v, want 14.5", got)
|
||||
}
|
||||
if stmt.Step() {
|
||||
if got := stmt.ColumnFloat(0); got != 16 {
|
||||
t.Errorf("got %v, want 16", got)
|
||||
}
|
||||
if !stmt.Step() {
|
||||
t.Fatal(stmt.Err())
|
||||
} else if got := stmt.ColumnFloat(0); got != 16 {
|
||||
t.Errorf("got %v, want 16", got)
|
||||
}
|
||||
stmt.Close()
|
||||
|
||||
@@ -103,7 +105,9 @@ func TestRegister_percentile(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if stmt.Step() {
|
||||
if !stmt.Step() {
|
||||
t.Fatal(stmt.Err())
|
||||
} else {
|
||||
if got := stmt.ColumnFloat(0); got != 4 {
|
||||
t.Errorf("got %v, want 4", got)
|
||||
}
|
||||
@@ -134,7 +138,9 @@ func TestRegister_percentile(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if stmt.Step() {
|
||||
if !stmt.Step() {
|
||||
t.Fatal(stmt.Err())
|
||||
} else {
|
||||
if got := stmt.ColumnType(0); got != sqlite3.NULL {
|
||||
t.Error("want NULL")
|
||||
}
|
||||
|
||||
@@ -58,8 +58,11 @@ import (
|
||||
|
||||
// Register registers statistics functions.
|
||||
func Register(db *sqlite3.Conn) error {
|
||||
const flags = sqlite3.DETERMINISTIC | sqlite3.INNOCUOUS
|
||||
const order = sqlite3.SELFORDER1 | flags
|
||||
const (
|
||||
flags = sqlite3.DETERMINISTIC | sqlite3.INNOCUOUS
|
||||
json = sqlite3.RESULT_SUBTYPE | flags
|
||||
order = sqlite3.SELFORDER1 | flags
|
||||
)
|
||||
return errors.Join(
|
||||
db.CreateWindowFunction("var_pop", 1, flags, newVariance(var_pop)),
|
||||
db.CreateWindowFunction("var_samp", 1, flags, newVariance(var_samp)),
|
||||
@@ -81,7 +84,7 @@ func Register(db *sqlite3.Conn) error {
|
||||
db.CreateWindowFunction("regr_slope", 2, flags, newCovariance(regr_slope)),
|
||||
db.CreateWindowFunction("regr_intercept", 2, flags, newCovariance(regr_intercept)),
|
||||
db.CreateWindowFunction("regr_count", 2, flags, newCovariance(regr_count)),
|
||||
db.CreateWindowFunction("regr_json", 2, flags, newCovariance(regr_json)),
|
||||
db.CreateWindowFunction("regr_json", 2, json, newCovariance(regr_json)),
|
||||
db.CreateWindowFunction("median", 1, order, newPercentile(median)),
|
||||
db.CreateWindowFunction("percentile", 2, order, newPercentile(percentile_100)),
|
||||
db.CreateWindowFunction("percentile_cont", 2, order, newPercentile(percentile_cont)),
|
||||
@@ -227,6 +230,7 @@ func (fn *covariance) Value(ctx sqlite3.Context) {
|
||||
case regr_json:
|
||||
var buf [128]byte
|
||||
ctx.ResultRawText(fn.regr_json(buf[:0]))
|
||||
ctx.ResultSubtype('J')
|
||||
return
|
||||
}
|
||||
ctx.ResultFloat(r)
|
||||
|
||||
@@ -34,10 +34,10 @@ func TestRegister_variance(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if stmt.Step() {
|
||||
if got := stmt.ColumnType(0); got != sqlite3.NULL {
|
||||
t.Errorf("got %v, want NULL", got)
|
||||
}
|
||||
if !stmt.Step() {
|
||||
t.Fatal(stmt.Err())
|
||||
} else if got := stmt.ColumnType(0); got != sqlite3.NULL {
|
||||
t.Errorf("got %v, want NULL", got)
|
||||
}
|
||||
stmt.Close()
|
||||
|
||||
@@ -57,7 +57,9 @@ func TestRegister_variance(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if stmt.Step() {
|
||||
if !stmt.Step() {
|
||||
t.Fatal(stmt.Err())
|
||||
} else {
|
||||
if got := stmt.ColumnFloat(0); got != 40 {
|
||||
t.Errorf("got %v, want 40", got)
|
||||
}
|
||||
@@ -131,7 +133,9 @@ func TestRegister_covariance(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if stmt.Step() {
|
||||
if !stmt.Step() {
|
||||
t.Fatal(stmt.Err())
|
||||
} else {
|
||||
if got := stmt.ColumnInt(0); got != 0 {
|
||||
t.Errorf("got %v, want 0", got)
|
||||
}
|
||||
@@ -156,49 +160,50 @@ func TestRegister_covariance(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if stmt.Step() {
|
||||
if got := stmt.ColumnFloat(0); got != 0.9881049293224639 {
|
||||
t.Errorf("got %v, want 0.9881049293224639", got)
|
||||
}
|
||||
if got := stmt.ColumnFloat(1); got != 21.25 {
|
||||
t.Errorf("got %v, want 21.25", got)
|
||||
}
|
||||
if got := stmt.ColumnFloat(2); got != 17 {
|
||||
t.Errorf("got %v, want 17", got)
|
||||
}
|
||||
if got := stmt.ColumnFloat(3); got != 4.2 {
|
||||
t.Errorf("got %v, want 4.2", got)
|
||||
}
|
||||
if got := stmt.ColumnFloat(4); got != 75 {
|
||||
t.Errorf("got %v, want 75", got)
|
||||
}
|
||||
if got := stmt.ColumnFloat(5); got != 14.8 {
|
||||
t.Errorf("got %v, want 14.8", got)
|
||||
}
|
||||
if got := stmt.ColumnFloat(6); got != 500 {
|
||||
t.Errorf("got %v, want 500", got)
|
||||
}
|
||||
if got := stmt.ColumnFloat(7); got != 85 {
|
||||
t.Errorf("got %v, want 85", got)
|
||||
}
|
||||
if got := stmt.ColumnFloat(8); got != 0.17 {
|
||||
t.Errorf("got %v, want 0.17", got)
|
||||
}
|
||||
if got := stmt.ColumnFloat(9); got != -8.55 {
|
||||
t.Errorf("got %v, want -8.55", got)
|
||||
}
|
||||
if got := stmt.ColumnFloat(10); got != 0.9763513513513513 {
|
||||
t.Errorf("got %v, want 0.9763513513513513", got)
|
||||
}
|
||||
if got := stmt.ColumnInt(11); got != 5 {
|
||||
t.Errorf("got %v, want 5", got)
|
||||
}
|
||||
var a map[string]float64
|
||||
if err := stmt.ColumnJSON(12, &a); err != nil {
|
||||
t.Error(err)
|
||||
} else if got := a["count"]; got != 5 {
|
||||
t.Errorf("got %v, want 5", got)
|
||||
}
|
||||
if !stmt.Step() {
|
||||
t.Fatal(stmt.Err())
|
||||
}
|
||||
if got := stmt.ColumnFloat(0); got != 0.9881049293224639 {
|
||||
t.Errorf("got %v, want 0.9881049293224639", got)
|
||||
}
|
||||
if got := stmt.ColumnFloat(1); got != 21.25 {
|
||||
t.Errorf("got %v, want 21.25", got)
|
||||
}
|
||||
if got := stmt.ColumnFloat(2); got != 17 {
|
||||
t.Errorf("got %v, want 17", got)
|
||||
}
|
||||
if got := stmt.ColumnFloat(3); got != 4.2 {
|
||||
t.Errorf("got %v, want 4.2", got)
|
||||
}
|
||||
if got := stmt.ColumnFloat(4); got != 75 {
|
||||
t.Errorf("got %v, want 75", got)
|
||||
}
|
||||
if got := stmt.ColumnFloat(5); got != 14.8 {
|
||||
t.Errorf("got %v, want 14.8", got)
|
||||
}
|
||||
if got := stmt.ColumnFloat(6); got != 500 {
|
||||
t.Errorf("got %v, want 500", got)
|
||||
}
|
||||
if got := stmt.ColumnFloat(7); got != 85 {
|
||||
t.Errorf("got %v, want 85", got)
|
||||
}
|
||||
if got := stmt.ColumnFloat(8); got != 0.17 {
|
||||
t.Errorf("got %v, want 0.17", got)
|
||||
}
|
||||
if got := stmt.ColumnFloat(9); got != -8.55 {
|
||||
t.Errorf("got %v, want -8.55", got)
|
||||
}
|
||||
if got := stmt.ColumnFloat(10); got != 0.9763513513513513 {
|
||||
t.Errorf("got %v, want 0.9763513513513513", got)
|
||||
}
|
||||
if got := stmt.ColumnInt(11); got != 5 {
|
||||
t.Errorf("got %v, want 5", got)
|
||||
}
|
||||
var a map[string]float64
|
||||
if err := stmt.ColumnJSON(12, &a); err != nil {
|
||||
t.Error(err)
|
||||
} else if got := a["count"]; got != 5 {
|
||||
t.Errorf("got %v, want 5", got)
|
||||
}
|
||||
stmt.Close()
|
||||
|
||||
@@ -248,7 +253,9 @@ func Benchmark_average(b *testing.B) {
|
||||
b.Fatal(err)
|
||||
}
|
||||
|
||||
if stmt.Step() {
|
||||
if !stmt.Step() {
|
||||
b.Fatal(stmt.Err())
|
||||
} else {
|
||||
want := float64(b.N) / 2
|
||||
if got := stmt.ColumnFloat(0); got != want {
|
||||
b.Errorf("got %v, want %v", got, want)
|
||||
@@ -282,7 +289,9 @@ func Benchmark_variance(b *testing.B) {
|
||||
b.Fatal(err)
|
||||
}
|
||||
|
||||
if stmt.Step() && b.N > 100 {
|
||||
if !stmt.Step() {
|
||||
b.Fatal(stmt.Err())
|
||||
} else if b.N > 100 {
|
||||
want := float64(b.N*b.N) / 12
|
||||
if got := stmt.ColumnFloat(0); want > (got-want)*float64(b.N) {
|
||||
b.Errorf("got %v, want %v", got, want)
|
||||
|
||||
@@ -43,7 +43,7 @@ import (
|
||||
"github.com/ncruces/go-sqlite3/internal/util"
|
||||
)
|
||||
|
||||
// Set RegisterLike to false to not register a Unicode aware LIKE operator.
|
||||
// RegisterLike must be set to false to not register a Unicode aware LIKE operator.
|
||||
// Overriding the built-in LIKE operator disables the [LIKE optimization].
|
||||
//
|
||||
// [LIKE optimization]: https://sqlite.org/optoverview.html#the_like_optimization
|
||||
|
||||
@@ -26,11 +26,10 @@ func TestRegister(t *testing.T) {
|
||||
}
|
||||
defer stmt.Close()
|
||||
|
||||
if stmt.Step() {
|
||||
return stmt.ColumnText(0)
|
||||
if !stmt.Step() {
|
||||
t.Fatal(stmt.Err())
|
||||
}
|
||||
t.Fatal(stmt.Err())
|
||||
return ""
|
||||
return stmt.ColumnText(0)
|
||||
}
|
||||
|
||||
Register(db)
|
||||
|
||||
@@ -19,9 +19,9 @@ func Register(db *sqlite3.Conn) error {
|
||||
}
|
||||
|
||||
func zorder(ctx sqlite3.Context, arg ...sqlite3.Value) {
|
||||
var x [63]int64
|
||||
if len(arg) > len(x) {
|
||||
ctx.ResultError(util.ErrorString("zorder: too many parameters"))
|
||||
var x [24]int64
|
||||
if n := len(arg); n < 2 || n > 24 {
|
||||
ctx.ResultError(util.ErrorString("zorder: needs between 2 and 24 dimensions"))
|
||||
return
|
||||
}
|
||||
for i := range arg {
|
||||
@@ -29,17 +29,15 @@ func zorder(ctx sqlite3.Context, arg ...sqlite3.Value) {
|
||||
}
|
||||
|
||||
var z int64
|
||||
if len(arg) > 0 {
|
||||
for i := range x {
|
||||
j := i % len(arg)
|
||||
z |= (x[j] & 1) << i
|
||||
x[j] >>= 1
|
||||
}
|
||||
for i := range 63 {
|
||||
j := i % len(arg)
|
||||
z |= (x[j] & 1) << i
|
||||
x[j] >>= 1
|
||||
}
|
||||
|
||||
for i := range arg {
|
||||
if x[i] != 0 {
|
||||
ctx.ResultError(util.ErrorString("zorder: parameter too large"))
|
||||
ctx.ResultError(util.ErrorString("zorder: argument out of range"))
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -51,6 +49,19 @@ func unzorder(ctx sqlite3.Context, arg ...sqlite3.Value) {
|
||||
n := arg[1].Int64()
|
||||
z := arg[0].Int64()
|
||||
|
||||
if n < 2 || n > 24 {
|
||||
ctx.ResultError(util.ErrorString("unzorder: needs between 2 and 24 dimensions"))
|
||||
return
|
||||
}
|
||||
if i < 0 || i >= n {
|
||||
ctx.ResultError(util.ErrorString("unzorder: index out of range"))
|
||||
return
|
||||
}
|
||||
if z < 0 {
|
||||
ctx.ResultError(util.ErrorString("unzorder: argument out of range"))
|
||||
return
|
||||
}
|
||||
|
||||
var k int
|
||||
var x int64
|
||||
for j := i; j < 63; j += n {
|
||||
|
||||
@@ -12,7 +12,7 @@ import (
|
||||
"github.com/ncruces/go-sqlite3/vfs/memdb"
|
||||
)
|
||||
|
||||
func TestRegister_zorder(t *testing.T) {
|
||||
func Test_zorder(t *testing.T) {
|
||||
t.Parallel()
|
||||
tmp := memdb.TestDB(t)
|
||||
|
||||
@@ -57,7 +57,7 @@ func TestRegister_zorder(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestRegister_unzorder(t *testing.T) {
|
||||
func Test_unzorder(t *testing.T) {
|
||||
t.Parallel()
|
||||
tmp := memdb.TestDB(t)
|
||||
|
||||
@@ -85,7 +85,7 @@ func TestRegister_unzorder(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestRegister_error(t *testing.T) {
|
||||
func Test_zorder_error(t *testing.T) {
|
||||
t.Parallel()
|
||||
tmp := memdb.TestDB(t)
|
||||
|
||||
@@ -103,7 +103,7 @@ func TestRegister_error(t *testing.T) {
|
||||
|
||||
var buf strings.Builder
|
||||
buf.WriteString("SELECT zorder(0")
|
||||
for i := 1; i < 80; i++ {
|
||||
for i := 1; i < 25; i++ {
|
||||
buf.WriteByte(',')
|
||||
buf.WriteString(strconv.Itoa(0))
|
||||
}
|
||||
@@ -113,3 +113,30 @@ func TestRegister_error(t *testing.T) {
|
||||
t.Error("want error")
|
||||
}
|
||||
}
|
||||
|
||||
func Test_unzorder_error(t *testing.T) {
|
||||
t.Parallel()
|
||||
tmp := memdb.TestDB(t)
|
||||
|
||||
db, err := driver.Open(tmp, zorder.Register)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
var got int64
|
||||
err = db.QueryRow(`SELECT unzorder(-1, 2, 0)`).Scan(&got)
|
||||
if err == nil {
|
||||
t.Error("want error")
|
||||
}
|
||||
|
||||
err = db.QueryRow(`SELECT unzorder(0, 2, 2)`).Scan(&got)
|
||||
if err == nil {
|
||||
t.Error("want error")
|
||||
}
|
||||
|
||||
err = db.QueryRow(`SELECT unzorder(0, 25, 2)`).Scan(&got)
|
||||
if err == nil {
|
||||
t.Error("want error")
|
||||
}
|
||||
}
|
||||
|
||||
14
func.go
14
func.go
@@ -59,7 +59,7 @@ func (c *Conn) CreateCollation(name string, fn CollatingFunction) error {
|
||||
return c.error(rc)
|
||||
}
|
||||
|
||||
// Collating function is the type of a collation callback.
|
||||
// CollatingFunction is the type of a collation callback.
|
||||
// Implementations must not retain a or b.
|
||||
type CollatingFunction func(a, b []byte) int
|
||||
|
||||
@@ -132,7 +132,7 @@ func (c *Conn) CreateWindowFunction(name string, nArg int, flag FunctionFlag, fn
|
||||
if win, ok := agg.(WindowFunction); ok {
|
||||
return win
|
||||
}
|
||||
return windowFunc{agg, name}
|
||||
return agg
|
||||
}))
|
||||
}
|
||||
rc := res_t(c.call("sqlite3_create_window_function_go",
|
||||
@@ -307,13 +307,3 @@ func (a *aggregateFunc) Close() error {
|
||||
a.stop()
|
||||
return nil
|
||||
}
|
||||
|
||||
type windowFunc struct {
|
||||
AggregateFunction
|
||||
name string
|
||||
}
|
||||
|
||||
func (w windowFunc) Inverse(ctx Context, arg ...Value) {
|
||||
// Implementing inverse allows certain queries that don't really need it to succeed.
|
||||
ctx.ResultError(util.ErrorString(w.name + ": may not be used as a window function"))
|
||||
}
|
||||
|
||||
9
go.mod
9
go.mod
@@ -5,19 +5,20 @@ go 1.23.0
|
||||
toolchain go1.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.39.0
|
||||
golang.org/x/sys v0.33.0
|
||||
golang.org/x/crypto v0.41.0
|
||||
golang.org/x/sys v0.35.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.15.0 // test
|
||||
golang.org/x/text v0.26.0 // ext/unicode
|
||||
golang.org/x/sync v0.16.0 // test
|
||||
golang.org/x/text v0.28.0 // ext/unicode
|
||||
lukechampine.com/adiantum v1.1.1 // vfs/adiantum
|
||||
)
|
||||
|
||||
|
||||
18
go.sum
18
go.sum
@@ -2,6 +2,8 @@ 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=
|
||||
@@ -10,13 +12,13 @@ github.com/psanford/httpreadat v0.1.0 h1:VleW1HS2zO7/4c7c7zNl33fO6oYACSagjJIyMIw
|
||||
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.39.0 h1:SHs+kF4LP+f+p14esP5jAoDpHU8Gu/v9lFRK6IT5imM=
|
||||
golang.org/x/crypto v0.39.0/go.mod h1:L+Xg3Wf6HoL4Bn4238Z6ft6KfEpN0tJGo53AAPC632U=
|
||||
golang.org/x/sync v0.15.0 h1:KWH3jNZsfyT6xfAfKiz6MRNmd46ByHDYaZ7KSkCtdW8=
|
||||
golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
|
||||
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M=
|
||||
golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA=
|
||||
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=
|
||||
lukechampine.com/adiantum v1.1.1 h1:4fp6gTxWCqpEbLy40ExiYDDED3oUNWx5cTqBCtPdZqA=
|
||||
lukechampine.com/adiantum v1.1.1/go.mod h1:LrAYVnTYLnUtE/yMp5bQr0HstAf060YUF8nM0B6+rUw=
|
||||
|
||||
@@ -5,8 +5,8 @@ go 1.23.0
|
||||
toolchain go1.24.0
|
||||
|
||||
require (
|
||||
github.com/ncruces/go-sqlite3 v0.26.0
|
||||
gorm.io/gorm v1.30.0
|
||||
github.com/ncruces/go-sqlite3 v0.27.1
|
||||
gorm.io/gorm v1.30.1
|
||||
)
|
||||
|
||||
require (
|
||||
@@ -14,6 +14,6 @@ require (
|
||||
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.33.0 // indirect
|
||||
golang.org/x/text v0.26.0 // indirect
|
||||
golang.org/x/sys v0.35.0 // indirect
|
||||
golang.org/x/text v0.28.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.26.0 h1:dY6ASfuhSEbtSge6kJwjyJVC7bXCpgEVOycmdboKJek=
|
||||
github.com/ncruces/go-sqlite3 v0.26.0/go.mod h1:46HIzeCQQ+aNleAxCli+vpA2tfh7ttSnw24kQahBc1o=
|
||||
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/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.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
|
||||
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M=
|
||||
golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA=
|
||||
gorm.io/gorm v1.30.0 h1:qbT5aPv1UH8gI99OsRlvDToLxW5zR7FzS9acZDOZcgs=
|
||||
gorm.io/gorm v1.30.0/go.mod h1:8Z33v652h4//uMA76KjeDH8mJXPm1QNCYrMeatR0DOE=
|
||||
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=
|
||||
|
||||
@@ -7,7 +7,7 @@ rm -rf gorm/ tests/
|
||||
go work use -r .
|
||||
go test
|
||||
|
||||
git clone --branch v1.30.0 --filter=blob:none https://github.com/go-gorm/gorm.git
|
||||
git clone --branch v1.30.1 --filter=blob:none https://github.com/go-gorm/gorm.git
|
||||
mv gorm/tests tests
|
||||
rm -rf gorm/
|
||||
|
||||
|
||||
@@ -7,13 +7,14 @@ diff --git a/tests/.gitignore b/tests/.gitignore
|
||||
diff --git a/tests/tests_test.go b/tests/tests_test.go
|
||||
--- a/tests/tests_test.go
|
||||
+++ b/tests/tests_test.go
|
||||
@@ -7,9 +7,11 @@ import (
|
||||
@@ -8,9 +8,11 @@ import (
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
+ _ "github.com/ncruces/go-sqlite3/embed"
|
||||
+ sqlite "github.com/ncruces/go-sqlite3/gormlite"
|
||||
+
|
||||
"gorm.io/driver/gaussdb"
|
||||
"gorm.io/driver/mysql"
|
||||
"gorm.io/driver/postgres"
|
||||
- "gorm.io/driver/sqlite"
|
||||
|
||||
@@ -20,20 +20,6 @@ func ExportFuncVI[T0 i32](mod wazero.HostModuleBuilder, name string, fn func(con
|
||||
Export(name)
|
||||
}
|
||||
|
||||
type funcVII[T0, T1 i32] func(context.Context, api.Module, T0, T1)
|
||||
|
||||
func (fn funcVII[T0, T1]) Call(ctx context.Context, mod api.Module, stack []uint64) {
|
||||
_ = stack[1] // prevent bounds check on every slice access
|
||||
fn(ctx, mod, T0(stack[0]), T1(stack[1]))
|
||||
}
|
||||
|
||||
func ExportFuncVII[T0, T1 i32](mod wazero.HostModuleBuilder, name string, fn func(context.Context, api.Module, T0, T1)) {
|
||||
mod.NewFunctionBuilder().
|
||||
WithGoModuleFunction(funcVII[T0, T1](fn),
|
||||
[]api.ValueType{api.ValueTypeI32, api.ValueTypeI32}, nil).
|
||||
Export(name)
|
||||
}
|
||||
|
||||
type funcVIII[T0, T1, T2 i32] func(context.Context, api.Module, T0, T1, T2)
|
||||
|
||||
func (fn funcVIII[T0, T1, T2]) Call(ctx context.Context, mod api.Module, stack []uint64) {
|
||||
|
||||
@@ -1,11 +1,3 @@
|
||||
package util
|
||||
|
||||
type Pointer[T any] struct{ Value T }
|
||||
|
||||
func (p Pointer[T]) unwrap() any { return p.Value }
|
||||
|
||||
type PointerUnwrap interface{ unwrap() any }
|
||||
|
||||
func UnwrapPointer(p PointerUnwrap) any {
|
||||
return p.unwrap()
|
||||
}
|
||||
type Pointer struct{ Value any }
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
package util
|
||||
|
||||
import (
|
||||
"math"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestUnwrapPointer(t *testing.T) {
|
||||
p := Pointer[float64]{Value: math.Pi}
|
||||
if got := UnwrapPointer(p); got != math.Pi {
|
||||
t.Errorf("want π, got %v", got)
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,6 @@ import "github.com/ncruces/go-sqlite3/internal/util"
|
||||
// [Value.Pointer], or [Context.ResultPointer].
|
||||
//
|
||||
// https://sqlite.org/bindptr.html
|
||||
func Pointer[T any](value T) any {
|
||||
return util.Pointer[T]{Value: value}
|
||||
func Pointer(value any) any {
|
||||
return util.Pointer{Value: value}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
# handle, and interrupt, sqlite3_busy_timeout.
|
||||
--- sqlite3.c.orig
|
||||
+++ sqlite3.c
|
||||
@@ -183364,7 +183364,7 @@
|
||||
@@ -184474,7 +184474,7 @@
|
||||
if( !sqlite3SafetyCheckOk(db) ) return SQLITE_MISUSE_BKPT;
|
||||
#endif
|
||||
if( ms>0 ){
|
||||
@@ -10,4 +10,4 @@
|
||||
+ sqlite3_busy_handler(db, (int(*)(void*,int))sqliteBusyCallback,
|
||||
(void*)db);
|
||||
db->busyTimeout = ms;
|
||||
}else{
|
||||
#ifdef SQLITE_ENABLE_SETLK_TIMEOUT
|
||||
|
||||
@@ -3,7 +3,7 @@ set -euo pipefail
|
||||
|
||||
cd -P -- "$(dirname -- "$0")"
|
||||
|
||||
curl -#OL "https://sqlite.org/2025/sqlite-amalgamation-3500100.zip"
|
||||
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 .
|
||||
@@ -19,30 +19,30 @@ rm -rf sqlite-amalgamation-*
|
||||
|
||||
mkdir -p ext/
|
||||
cd ext/
|
||||
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.50.1/ext/misc/anycollseq.c"
|
||||
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.50.1/ext/misc/base64.c"
|
||||
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.50.1/ext/misc/decimal.c"
|
||||
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.50.1/ext/misc/ieee754.c"
|
||||
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.50.1/ext/misc/regexp.c"
|
||||
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.50.1/ext/misc/series.c"
|
||||
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.50.1/ext/misc/spellfix.c"
|
||||
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.50.1/ext/misc/uint.c"
|
||||
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"
|
||||
cd ~-
|
||||
|
||||
cd ../vfs/tests/mptest/testdata/
|
||||
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.50.1/mptest/config01.test"
|
||||
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.50.1/mptest/config02.test"
|
||||
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.50.1/mptest/crash01.test"
|
||||
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.50.1/mptest/crash02.subtest"
|
||||
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.50.1/mptest/multiwrite01.test"
|
||||
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"
|
||||
cd ~-
|
||||
|
||||
cd ../vfs/tests/mptest/wasm/
|
||||
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.50.1/mptest/mptest.c"
|
||||
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.50.4/mptest/mptest.c"
|
||||
cd ~-
|
||||
|
||||
cd ../vfs/tests/speedtest1/wasm/
|
||||
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.50.1/test/speedtest1.c"
|
||||
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.50.4/test/speedtest1.c"
|
||||
cd ~-
|
||||
|
||||
cat *.patch | patch -p0 --no-backup-if-mismatch
|
||||
@@ -13,7 +13,6 @@ trap 'rm -f libc.c libc.tmp' EXIT
|
||||
cat << EOF > libc.c
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <strings.h>
|
||||
EOF
|
||||
|
||||
"$WASI_SDK/clang" --target=wasm32-wasi -std=c23 -g0 -O2 \
|
||||
@@ -23,7 +22,8 @@ EOF
|
||||
-mmutable-globals -mnontrapping-fptoint \
|
||||
-msimd128 -mbulk-memory -msign-ext \
|
||||
-mreference-types -mmultivalue \
|
||||
-fno-stack-protector -fno-stack-clash-protection \
|
||||
-mno-extended-const \
|
||||
-fno-stack-protector \
|
||||
-Wl,-z,stack-size=4096 \
|
||||
-Wl,--stack-first \
|
||||
-Wl,--import-undefined \
|
||||
|
||||
Binary file not shown.
@@ -627,9 +627,8 @@
|
||||
(func $memchr (param $0 i32) (param $1 i32) (param $2 i32) (result i32)
|
||||
(local $3 i32)
|
||||
(local $4 i32)
|
||||
(local $5 i32)
|
||||
(local $5 v128)
|
||||
(local $6 v128)
|
||||
(local $7 v128)
|
||||
(block $block2
|
||||
(block $block
|
||||
(br_if $block
|
||||
@@ -653,13 +652,8 @@
|
||||
(br_if $block1
|
||||
(i32.eqz
|
||||
(v128.any_true
|
||||
(local.tee $6
|
||||
(local.tee $5
|
||||
(i8x16.eq
|
||||
(local.tee $7
|
||||
(i8x16.splat
|
||||
(local.get $1)
|
||||
)
|
||||
)
|
||||
(v128.load
|
||||
(local.tee $2
|
||||
(i32.and
|
||||
@@ -668,6 +662,11 @@
|
||||
)
|
||||
)
|
||||
)
|
||||
(local.tee $6
|
||||
(i8x16.splat
|
||||
(local.get $1)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
@@ -675,10 +674,10 @@
|
||||
)
|
||||
(br_if $block1
|
||||
(i32.eqz
|
||||
(local.tee $5
|
||||
(local.tee $1
|
||||
(i32.and
|
||||
(i8x16.bitmask
|
||||
(local.get $6)
|
||||
(local.get $5)
|
||||
)
|
||||
(i32.shl
|
||||
(i32.const -1)
|
||||
@@ -688,14 +687,14 @@
|
||||
)
|
||||
)
|
||||
)
|
||||
(local.set $1
|
||||
(local.set $0
|
||||
(local.get $4)
|
||||
)
|
||||
(br $block2)
|
||||
)
|
||||
(br_if $block
|
||||
(i32.gt_u
|
||||
(local.tee $1
|
||||
(local.tee $0
|
||||
(i32.sub
|
||||
(i32.add
|
||||
(local.get $3)
|
||||
@@ -709,29 +708,26 @@
|
||||
)
|
||||
(local.set $2
|
||||
(i32.add
|
||||
(i32.sub
|
||||
(local.get $0)
|
||||
(local.get $3)
|
||||
)
|
||||
(local.get $2)
|
||||
(i32.const 16)
|
||||
)
|
||||
)
|
||||
(loop $label
|
||||
(if
|
||||
(v128.any_true
|
||||
(local.tee $6
|
||||
(local.tee $5
|
||||
(i8x16.eq
|
||||
(local.get $7)
|
||||
(v128.load
|
||||
(local.get $2)
|
||||
)
|
||||
(local.get $6)
|
||||
)
|
||||
)
|
||||
)
|
||||
(then
|
||||
(local.set $5
|
||||
(local.set $1
|
||||
(i8x16.bitmask
|
||||
(local.get $6)
|
||||
(local.get $5)
|
||||
)
|
||||
)
|
||||
(local.set $3
|
||||
@@ -748,10 +744,10 @@
|
||||
)
|
||||
(br_if $label
|
||||
(i32.ge_u
|
||||
(local.get $1)
|
||||
(local.tee $1
|
||||
(local.get $0)
|
||||
(local.tee $0
|
||||
(i32.sub
|
||||
(local.get $1)
|
||||
(local.get $0)
|
||||
(i32.const 16)
|
||||
)
|
||||
)
|
||||
@@ -766,19 +762,19 @@
|
||||
(select
|
||||
(i32.add
|
||||
(local.get $2)
|
||||
(local.tee $0
|
||||
(local.tee $1
|
||||
(i32.ctz
|
||||
(local.get $5)
|
||||
(local.get $1)
|
||||
)
|
||||
)
|
||||
)
|
||||
(i32.const 0)
|
||||
(i32.le_u
|
||||
(i32.sub
|
||||
(local.get $0)
|
||||
(local.get $1)
|
||||
(local.get $3)
|
||||
)
|
||||
(local.get $1)
|
||||
(local.get $0)
|
||||
)
|
||||
)
|
||||
)
|
||||
@@ -898,9 +894,9 @@
|
||||
(local $1 i32)
|
||||
(local $2 i32)
|
||||
(local $3 v128)
|
||||
(block $block1
|
||||
(block $block
|
||||
(br_if $block
|
||||
(block $block
|
||||
(if
|
||||
(i32.eqz
|
||||
(i8x16.all_true
|
||||
(local.tee $3
|
||||
(v128.load
|
||||
@@ -914,8 +910,8 @@
|
||||
)
|
||||
)
|
||||
)
|
||||
(br_if $block
|
||||
(i32.eqz
|
||||
(then
|
||||
(br_if $block
|
||||
(local.tee $2
|
||||
(i32.and
|
||||
(i8x16.bitmask
|
||||
@@ -935,23 +931,20 @@
|
||||
)
|
||||
)
|
||||
)
|
||||
(br $block1)
|
||||
)
|
||||
(loop $label
|
||||
(local.set $3
|
||||
(v128.load offset=16
|
||||
(local.get $1)
|
||||
)
|
||||
)
|
||||
(local.set $1
|
||||
(i32.add
|
||||
(local.get $1)
|
||||
(i32.const 16)
|
||||
)
|
||||
)
|
||||
(br_if $label
|
||||
(i8x16.all_true
|
||||
(local.get $3)
|
||||
(local.tee $3
|
||||
(v128.load
|
||||
(local.tee $1
|
||||
(i32.add
|
||||
(local.get $1)
|
||||
(i32.const 16)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
@@ -984,7 +977,7 @@
|
||||
(local.tee $2
|
||||
(v128.or
|
||||
(i8x16.eq
|
||||
(local.tee $2
|
||||
(local.tee $3
|
||||
(v128.load
|
||||
(local.tee $4
|
||||
(i32.and
|
||||
@@ -997,12 +990,12 @@
|
||||
(v128.const i32x4 0x00000000 0x00000000 0x00000000 0x00000000)
|
||||
)
|
||||
(i8x16.eq
|
||||
(local.get $3)
|
||||
(local.tee $3
|
||||
(i8x16.splat
|
||||
(local.get $1)
|
||||
)
|
||||
)
|
||||
(local.get $2)
|
||||
)
|
||||
)
|
||||
)
|
||||
@@ -1027,29 +1020,27 @@
|
||||
)
|
||||
)
|
||||
(loop $label
|
||||
(local.set $2
|
||||
(v128.load offset=16
|
||||
(local.get $4)
|
||||
)
|
||||
)
|
||||
(local.set $4
|
||||
(i32.add
|
||||
(local.get $4)
|
||||
(i32.const 16)
|
||||
)
|
||||
)
|
||||
(br_if $label
|
||||
(i32.eqz
|
||||
(v128.any_true
|
||||
(local.tee $2
|
||||
(v128.or
|
||||
(i8x16.eq
|
||||
(local.get $2)
|
||||
(local.tee $2
|
||||
(v128.load
|
||||
(local.tee $4
|
||||
(i32.add
|
||||
(local.get $4)
|
||||
(i32.const 16)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
(v128.const i32x4 0x00000000 0x00000000 0x00000000 0x00000000)
|
||||
)
|
||||
(i8x16.eq
|
||||
(local.get $3)
|
||||
(local.get $2)
|
||||
(local.get $3)
|
||||
)
|
||||
)
|
||||
)
|
||||
@@ -1080,7 +1071,7 @@
|
||||
(local.tee $2
|
||||
(v128.or
|
||||
(i8x16.eq
|
||||
(local.tee $2
|
||||
(local.tee $3
|
||||
(v128.load
|
||||
(local.tee $4
|
||||
(i32.and
|
||||
@@ -1093,12 +1084,12 @@
|
||||
(v128.const i32x4 0x00000000 0x00000000 0x00000000 0x00000000)
|
||||
)
|
||||
(i8x16.eq
|
||||
(local.get $3)
|
||||
(local.tee $3
|
||||
(i8x16.splat
|
||||
(local.get $1)
|
||||
)
|
||||
)
|
||||
(local.get $2)
|
||||
)
|
||||
)
|
||||
)
|
||||
@@ -1123,29 +1114,27 @@
|
||||
)
|
||||
)
|
||||
(loop $label
|
||||
(local.set $2
|
||||
(v128.load offset=16
|
||||
(local.get $4)
|
||||
)
|
||||
)
|
||||
(local.set $4
|
||||
(i32.add
|
||||
(local.get $4)
|
||||
(i32.const 16)
|
||||
)
|
||||
)
|
||||
(br_if $label
|
||||
(i32.eqz
|
||||
(v128.any_true
|
||||
(local.tee $2
|
||||
(v128.or
|
||||
(i8x16.eq
|
||||
(local.get $2)
|
||||
(local.tee $2
|
||||
(v128.load
|
||||
(local.tee $4
|
||||
(i32.add
|
||||
(local.get $4)
|
||||
(i32.const 16)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
(v128.const i32x4 0x00000000 0x00000000 0x00000000 0x00000000)
|
||||
)
|
||||
(i8x16.eq
|
||||
(local.get $3)
|
||||
(local.get $2)
|
||||
(local.get $3)
|
||||
)
|
||||
)
|
||||
)
|
||||
@@ -1196,14 +1185,15 @@
|
||||
(local $2 v128)
|
||||
(local $3 v128)
|
||||
(local $4 v128)
|
||||
(local $5 i32)
|
||||
(local $5 v128)
|
||||
(local $6 i32)
|
||||
(local $7 i32)
|
||||
(local $8 i32)
|
||||
(local $9 i32)
|
||||
(local $10 i32)
|
||||
(if
|
||||
(i32.eqz
|
||||
(local.tee $5
|
||||
(local.tee $7
|
||||
(i32.load8_u
|
||||
(local.get $1)
|
||||
)
|
||||
@@ -1221,7 +1211,7 @@
|
||||
(i32.const -16)
|
||||
)
|
||||
)
|
||||
(local.set $8
|
||||
(local.set $9
|
||||
(i32.and
|
||||
(local.get $0)
|
||||
(i32.const 15)
|
||||
@@ -1244,11 +1234,11 @@
|
||||
(local.get $4)
|
||||
)
|
||||
(i32.store8
|
||||
(local.tee $9
|
||||
(local.tee $10
|
||||
(i32.or
|
||||
(local.tee $7
|
||||
(local.tee $8
|
||||
(i32.and
|
||||
(local.get $5)
|
||||
(local.get $7)
|
||||
(i32.const 15)
|
||||
)
|
||||
)
|
||||
@@ -1257,17 +1247,14 @@
|
||||
)
|
||||
(i32.or
|
||||
(i32.load8_u
|
||||
(local.get $9)
|
||||
(local.get $10)
|
||||
)
|
||||
(i32.shl
|
||||
(i32.const 1)
|
||||
(local.tee $5
|
||||
(i32.and
|
||||
(i32.shr_u
|
||||
(local.get $5)
|
||||
(i32.const 4)
|
||||
)
|
||||
(i32.const 15)
|
||||
(local.tee $7
|
||||
(i32.shr_u
|
||||
(local.get $7)
|
||||
(i32.const 4)
|
||||
)
|
||||
)
|
||||
)
|
||||
@@ -1278,26 +1265,26 @@
|
||||
(local.get $3)
|
||||
)
|
||||
(i32.store8
|
||||
(local.tee $7
|
||||
(local.tee $8
|
||||
(i32.or
|
||||
(local.get $7)
|
||||
(local.get $8)
|
||||
(i32.const 4080)
|
||||
)
|
||||
)
|
||||
(i32.or
|
||||
(i32.load8_u
|
||||
(local.get $7)
|
||||
(local.get $8)
|
||||
)
|
||||
(i32.shl
|
||||
(i32.const 1)
|
||||
(i32.sub
|
||||
(local.get $5)
|
||||
(local.get $7)
|
||||
(i32.const 8)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
(local.set $5
|
||||
(local.set $7
|
||||
(i32.load8_u
|
||||
(local.get $1)
|
||||
)
|
||||
@@ -1319,7 +1306,7 @@
|
||||
)
|
||||
)
|
||||
(br_if $label
|
||||
(local.get $5)
|
||||
(local.get $7)
|
||||
)
|
||||
)
|
||||
(block $block
|
||||
@@ -1331,22 +1318,24 @@
|
||||
(v128.and
|
||||
(v128.or
|
||||
(i8x16.swizzle
|
||||
(local.get $4)
|
||||
(v128.and
|
||||
(local.tee $2
|
||||
(v128.load
|
||||
(local.get $6)
|
||||
(local.get $3)
|
||||
(v128.xor
|
||||
(local.tee $5
|
||||
(v128.and
|
||||
(local.tee $2
|
||||
(v128.load
|
||||
(local.get $6)
|
||||
)
|
||||
)
|
||||
(v128.const i32x4 0x8f8f8f8f 0x8f8f8f8f 0x8f8f8f8f 0x8f8f8f8f)
|
||||
)
|
||||
)
|
||||
(v128.const i32x4 0x8f8f8f8f 0x8f8f8f8f 0x8f8f8f8f 0x8f8f8f8f)
|
||||
(v128.const i32x4 0x80808080 0x80808080 0x80808080 0x80808080)
|
||||
)
|
||||
)
|
||||
(i8x16.swizzle
|
||||
(local.get $3)
|
||||
(v128.and
|
||||
(local.get $2)
|
||||
(v128.const i32x4 0x0f0f0f0f 0x0f0f0f0f 0x0f0f0f0f 0x0f0f0f0f)
|
||||
)
|
||||
(local.get $4)
|
||||
(local.get $5)
|
||||
)
|
||||
)
|
||||
(local.tee $2
|
||||
@@ -1376,7 +1365,7 @@
|
||||
)
|
||||
(i32.shl
|
||||
(i32.const -1)
|
||||
(local.get $8)
|
||||
(local.get $9)
|
||||
)
|
||||
)
|
||||
)
|
||||
@@ -1384,17 +1373,6 @@
|
||||
)
|
||||
)
|
||||
(loop $label1
|
||||
(local.set $2
|
||||
(v128.load offset=16
|
||||
(local.get $6)
|
||||
)
|
||||
)
|
||||
(local.set $6
|
||||
(i32.add
|
||||
(local.get $6)
|
||||
(i32.const 16)
|
||||
)
|
||||
)
|
||||
(br_if $label1
|
||||
(i8x16.all_true
|
||||
(local.tee $2
|
||||
@@ -1402,18 +1380,29 @@
|
||||
(v128.and
|
||||
(v128.or
|
||||
(i8x16.swizzle
|
||||
(local.get $4)
|
||||
(v128.and
|
||||
(local.get $2)
|
||||
(v128.const i32x4 0x8f8f8f8f 0x8f8f8f8f 0x8f8f8f8f 0x8f8f8f8f)
|
||||
(local.get $3)
|
||||
(v128.xor
|
||||
(local.tee $5
|
||||
(v128.and
|
||||
(local.tee $2
|
||||
(v128.load
|
||||
(local.tee $6
|
||||
(i32.add
|
||||
(local.get $6)
|
||||
(i32.const 16)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
(v128.const i32x4 0x8f8f8f8f 0x8f8f8f8f 0x8f8f8f8f 0x8f8f8f8f)
|
||||
)
|
||||
)
|
||||
(v128.const i32x4 0x80808080 0x80808080 0x80808080 0x80808080)
|
||||
)
|
||||
)
|
||||
(i8x16.swizzle
|
||||
(local.get $3)
|
||||
(v128.and
|
||||
(local.get $2)
|
||||
(v128.const i32x4 0x0f0f0f0f 0x0f0f0f0f 0x0f0f0f0f 0x0f0f0f0f)
|
||||
)
|
||||
(local.get $4)
|
||||
(local.get $5)
|
||||
)
|
||||
)
|
||||
(local.tee $2
|
||||
@@ -1454,27 +1443,27 @@
|
||||
)
|
||||
)
|
||||
)
|
||||
(block $block2
|
||||
(block $block1
|
||||
(br_if $block1
|
||||
(block $block1
|
||||
(if
|
||||
(i32.eqz
|
||||
(i8x16.all_true
|
||||
(local.tee $3
|
||||
(i8x16.eq
|
||||
(local.tee $4
|
||||
(i8x16.splat
|
||||
(local.get $5)
|
||||
)
|
||||
)
|
||||
(v128.load
|
||||
(local.get $6)
|
||||
)
|
||||
(local.tee $4
|
||||
(i8x16.splat
|
||||
(local.get $7)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
(br_if $block1
|
||||
(i32.eqz
|
||||
(local.tee $5
|
||||
(then
|
||||
(br_if $block1
|
||||
(local.tee $1
|
||||
(i32.and
|
||||
(i32.xor
|
||||
(i8x16.bitmask
|
||||
@@ -1484,43 +1473,33 @@
|
||||
)
|
||||
(i32.shl
|
||||
(i32.const -1)
|
||||
(local.get $8)
|
||||
(local.get $9)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
(local.set $1
|
||||
(local.get $6)
|
||||
)
|
||||
(br $block2)
|
||||
)
|
||||
(loop $label2
|
||||
(local.set $3
|
||||
(v128.load offset=16
|
||||
(local.get $6)
|
||||
)
|
||||
)
|
||||
(local.set $6
|
||||
(local.tee $1
|
||||
(i32.add
|
||||
(local.get $6)
|
||||
(i32.const 16)
|
||||
)
|
||||
)
|
||||
)
|
||||
(br_if $label2
|
||||
(i8x16.all_true
|
||||
(local.tee $3
|
||||
(i8x16.eq
|
||||
(v128.load
|
||||
(local.tee $6
|
||||
(i32.add
|
||||
(local.get $6)
|
||||
(i32.const 16)
|
||||
)
|
||||
)
|
||||
)
|
||||
(local.get $4)
|
||||
(local.get $3)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
(local.set $5
|
||||
(local.set $1
|
||||
(i32.xor
|
||||
(i8x16.bitmask
|
||||
(local.get $3)
|
||||
@@ -1531,10 +1510,10 @@
|
||||
)
|
||||
(i32.add
|
||||
(i32.ctz
|
||||
(local.get $5)
|
||||
(local.get $1)
|
||||
)
|
||||
(i32.sub
|
||||
(local.get $1)
|
||||
(local.get $6)
|
||||
(local.get $0)
|
||||
)
|
||||
)
|
||||
@@ -1543,14 +1522,14 @@
|
||||
(local $2 v128)
|
||||
(local $3 v128)
|
||||
(local $4 v128)
|
||||
(local $5 i32)
|
||||
(local $5 v128)
|
||||
(local $6 i32)
|
||||
(local $7 i32)
|
||||
(local $8 i32)
|
||||
(local $9 i32)
|
||||
(block $block
|
||||
(if
|
||||
(local.tee $6
|
||||
(local.tee $7
|
||||
(i32.load8_u
|
||||
(local.get $1)
|
||||
)
|
||||
@@ -1569,9 +1548,9 @@
|
||||
(local.tee $3
|
||||
(v128.or
|
||||
(i8x16.eq
|
||||
(local.tee $3
|
||||
(local.tee $4
|
||||
(v128.load
|
||||
(local.tee $5
|
||||
(local.tee $6
|
||||
(i32.and
|
||||
(local.get $0)
|
||||
(i32.const -16)
|
||||
@@ -1582,12 +1561,12 @@
|
||||
(v128.const i32x4 0x00000000 0x00000000 0x00000000 0x00000000)
|
||||
)
|
||||
(i8x16.eq
|
||||
(local.get $4)
|
||||
(local.tee $4
|
||||
(i8x16.splat
|
||||
(local.get $6)
|
||||
(local.get $7)
|
||||
)
|
||||
)
|
||||
(local.get $3)
|
||||
)
|
||||
)
|
||||
)
|
||||
@@ -1612,29 +1591,27 @@
|
||||
)
|
||||
)
|
||||
(loop $label
|
||||
(local.set $3
|
||||
(v128.load offset=16
|
||||
(local.get $5)
|
||||
)
|
||||
)
|
||||
(local.set $5
|
||||
(i32.add
|
||||
(local.get $5)
|
||||
(i32.const 16)
|
||||
)
|
||||
)
|
||||
(br_if $label
|
||||
(i32.eqz
|
||||
(v128.any_true
|
||||
(local.tee $3
|
||||
(v128.or
|
||||
(i8x16.eq
|
||||
(local.get $3)
|
||||
(local.tee $3
|
||||
(v128.load
|
||||
(local.tee $6
|
||||
(i32.add
|
||||
(local.get $6)
|
||||
(i32.const 16)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
(v128.const i32x4 0x00000000 0x00000000 0x00000000 0x00000000)
|
||||
)
|
||||
(i8x16.eq
|
||||
(local.get $4)
|
||||
(local.get $3)
|
||||
(local.get $4)
|
||||
)
|
||||
)
|
||||
)
|
||||
@@ -1649,38 +1626,33 @@
|
||||
)
|
||||
)
|
||||
(return
|
||||
(i32.sub
|
||||
(i32.add
|
||||
(local.get $5)
|
||||
(i32.ctz
|
||||
(local.get $1)
|
||||
)
|
||||
(i32.add
|
||||
(i32.ctz
|
||||
(local.get $1)
|
||||
)
|
||||
(i32.sub
|
||||
(local.get $6)
|
||||
(local.get $0)
|
||||
)
|
||||
(local.get $0)
|
||||
)
|
||||
)
|
||||
)
|
||||
(local.set $8
|
||||
(i32.sub
|
||||
(i32.const 0)
|
||||
(local.tee $7
|
||||
(i32.and
|
||||
(local.get $0)
|
||||
(i32.const 15)
|
||||
)
|
||||
)
|
||||
(i32.and
|
||||
(local.get $0)
|
||||
(i32.const 15)
|
||||
)
|
||||
)
|
||||
(loop $label1
|
||||
(v128.store
|
||||
(i32.const 4080)
|
||||
(local.get $3)
|
||||
(local.get $4)
|
||||
)
|
||||
(i32.store8
|
||||
(i32.or
|
||||
(local.tee $6
|
||||
(local.tee $7
|
||||
(i32.and
|
||||
(local.tee $5
|
||||
(local.tee $6
|
||||
(i32.load8_u
|
||||
(local.get $1)
|
||||
)
|
||||
@@ -1693,7 +1665,7 @@
|
||||
(i32.or
|
||||
(i32.load8_u
|
||||
(i32.or
|
||||
(local.get $6)
|
||||
(local.get $7)
|
||||
(i32.const 4080)
|
||||
)
|
||||
)
|
||||
@@ -1702,7 +1674,7 @@
|
||||
(i32.sub
|
||||
(local.tee $9
|
||||
(i32.shr_u
|
||||
(local.get $5)
|
||||
(local.get $6)
|
||||
(i32.const 4)
|
||||
)
|
||||
)
|
||||
@@ -1713,18 +1685,18 @@
|
||||
)
|
||||
(v128.store
|
||||
(i32.const 4064)
|
||||
(local.get $4)
|
||||
(local.get $3)
|
||||
)
|
||||
(i32.store8
|
||||
(local.tee $6
|
||||
(local.tee $7
|
||||
(i32.or
|
||||
(local.get $6)
|
||||
(local.get $7)
|
||||
(i32.const 4064)
|
||||
)
|
||||
)
|
||||
(i32.or
|
||||
(i32.load8_u
|
||||
(local.get $6)
|
||||
(local.get $7)
|
||||
)
|
||||
(i32.shl
|
||||
(i32.const 1)
|
||||
@@ -1738,18 +1710,18 @@
|
||||
(i32.const 1)
|
||||
)
|
||||
)
|
||||
(local.set $3
|
||||
(local.set $4
|
||||
(v128.load
|
||||
(i32.const 4080)
|
||||
)
|
||||
)
|
||||
(local.set $4
|
||||
(local.set $3
|
||||
(v128.load
|
||||
(i32.const 4064)
|
||||
)
|
||||
)
|
||||
(br_if $label1
|
||||
(local.get $5)
|
||||
(local.get $6)
|
||||
)
|
||||
)
|
||||
(block $block2
|
||||
@@ -1761,26 +1733,28 @@
|
||||
(v128.or
|
||||
(i8x16.swizzle
|
||||
(local.get $4)
|
||||
(v128.and
|
||||
(local.tee $2
|
||||
(v128.load
|
||||
(local.tee $5
|
||||
(i32.add
|
||||
(local.get $0)
|
||||
(local.get $8)
|
||||
(v128.xor
|
||||
(local.tee $5
|
||||
(v128.and
|
||||
(local.tee $2
|
||||
(v128.load
|
||||
(local.tee $6
|
||||
(i32.and
|
||||
(local.get $0)
|
||||
(i32.const -16)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
(v128.const i32x4 0x8f8f8f8f 0x8f8f8f8f 0x8f8f8f8f 0x8f8f8f8f)
|
||||
)
|
||||
)
|
||||
(v128.const i32x4 0x8f8f8f8f 0x8f8f8f8f 0x8f8f8f8f 0x8f8f8f8f)
|
||||
(v128.const i32x4 0x80808080 0x80808080 0x80808080 0x80808080)
|
||||
)
|
||||
)
|
||||
(i8x16.swizzle
|
||||
(local.get $3)
|
||||
(v128.and
|
||||
(local.get $2)
|
||||
(v128.const i32x4 0x0f0f0f0f 0x0f0f0f0f 0x0f0f0f0f 0x0f0f0f0f)
|
||||
)
|
||||
(local.get $5)
|
||||
)
|
||||
)
|
||||
(local.tee $2
|
||||
@@ -1806,7 +1780,7 @@
|
||||
)
|
||||
(i32.shl
|
||||
(i32.const -1)
|
||||
(local.get $7)
|
||||
(local.get $8)
|
||||
)
|
||||
)
|
||||
)
|
||||
@@ -1814,17 +1788,6 @@
|
||||
)
|
||||
)
|
||||
(loop $label2
|
||||
(local.set $2
|
||||
(v128.load offset=16
|
||||
(local.get $5)
|
||||
)
|
||||
)
|
||||
(local.set $5
|
||||
(i32.add
|
||||
(local.get $5)
|
||||
(i32.const 16)
|
||||
)
|
||||
)
|
||||
(br_if $label2
|
||||
(i32.eqz
|
||||
(v128.any_true
|
||||
@@ -1834,17 +1797,28 @@
|
||||
(v128.or
|
||||
(i8x16.swizzle
|
||||
(local.get $4)
|
||||
(v128.and
|
||||
(local.get $2)
|
||||
(v128.const i32x4 0x8f8f8f8f 0x8f8f8f8f 0x8f8f8f8f 0x8f8f8f8f)
|
||||
(v128.xor
|
||||
(local.tee $5
|
||||
(v128.and
|
||||
(local.tee $2
|
||||
(v128.load
|
||||
(local.tee $6
|
||||
(i32.add
|
||||
(local.get $6)
|
||||
(i32.const 16)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
(v128.const i32x4 0x8f8f8f8f 0x8f8f8f8f 0x8f8f8f8f 0x8f8f8f8f)
|
||||
)
|
||||
)
|
||||
(v128.const i32x4 0x80808080 0x80808080 0x80808080 0x80808080)
|
||||
)
|
||||
)
|
||||
(i8x16.swizzle
|
||||
(local.get $3)
|
||||
(v128.and
|
||||
(local.get $2)
|
||||
(v128.const i32x4 0x0f0f0f0f 0x0f0f0f0f 0x0f0f0f0f 0x0f0f0f0f)
|
||||
)
|
||||
(local.get $5)
|
||||
)
|
||||
)
|
||||
(local.tee $2
|
||||
@@ -1875,7 +1849,7 @@
|
||||
(local.get $1)
|
||||
)
|
||||
(i32.sub
|
||||
(local.get $5)
|
||||
(local.get $6)
|
||||
(local.get $0)
|
||||
)
|
||||
)
|
||||
|
||||
@@ -4,10 +4,10 @@ import (
|
||||
"bytes"
|
||||
"context"
|
||||
_ "embed"
|
||||
"math"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/tetratelabs/wazero"
|
||||
"github.com/tetratelabs/wazero/api"
|
||||
@@ -230,6 +230,10 @@ func Test_memchr(t *testing.T) {
|
||||
fill(memory[ptr:ptr+max(pos, length)], 5)
|
||||
memory[ptr+pos] = 7
|
||||
|
||||
if pos >= 0 {
|
||||
memory[ptr+pos+2] = 7
|
||||
}
|
||||
|
||||
got := call(memchr, uint64(ptr), 7, uint64(length))
|
||||
if uint32(got) != uint32(want) {
|
||||
t.Errorf("memchr(%d, %d, %d) = %d, want %d",
|
||||
@@ -243,9 +247,14 @@ func Test_memchr(t *testing.T) {
|
||||
fill(memory[ptr:ptr+length], 5)
|
||||
memory[len(memory)-1] = 7
|
||||
|
||||
want := len(memory) - 1
|
||||
if length == 0 {
|
||||
want = 0
|
||||
var want int
|
||||
if length != 0 {
|
||||
want = len(memory) - 1
|
||||
got := call(memchr, uint64(ptr), 7, math.MaxUint32)
|
||||
if uint32(got) != uint32(want) {
|
||||
t.Errorf("memchr(%d, %d, %d) = %d, want %d",
|
||||
ptr, 7, uint32(math.MaxUint32), uint32(got), uint32(want))
|
||||
}
|
||||
}
|
||||
|
||||
got := call(memchr, uint64(ptr), 7, uint64(length))
|
||||
@@ -398,7 +407,7 @@ func Test_strspn(t *testing.T) {
|
||||
fill(memory[ptr:ptr+max(pos, length)], 5)
|
||||
memory[ptr+pos] = 7
|
||||
memory[ptr+length] = 0
|
||||
memory[128] = 3
|
||||
memory[128] = 7 | 128
|
||||
memory[129] = 5
|
||||
|
||||
got := call(strspn, uint64(ptr), 129)
|
||||
@@ -424,7 +433,7 @@ func Test_strspn(t *testing.T) {
|
||||
clear(memory)
|
||||
fill(memory[ptr:ptr+length], 5)
|
||||
memory[len(memory)-1] = 7
|
||||
memory[128] = 3
|
||||
memory[128] = 7 | 128
|
||||
memory[129] = 5
|
||||
|
||||
got := call(strspn, uint64(ptr), 129)
|
||||
@@ -452,7 +461,7 @@ func Test_strcspn(t *testing.T) {
|
||||
fill(memory[ptr:ptr+max(pos, length)], 5)
|
||||
memory[ptr+pos] = 7
|
||||
memory[ptr+length] = 0
|
||||
memory[128] = 3
|
||||
memory[128] = 5 | 128
|
||||
memory[129] = 7
|
||||
|
||||
got := call(strcspn, uint64(ptr), 129)
|
||||
@@ -478,7 +487,7 @@ func Test_strcspn(t *testing.T) {
|
||||
clear(memory)
|
||||
fill(memory[ptr:ptr+length], 5)
|
||||
memory[len(memory)-1] = 7
|
||||
memory[128] = 3
|
||||
memory[128] = 5 | 128
|
||||
memory[129] = 7
|
||||
|
||||
got := call(strcspn, uint64(ptr), 129)
|
||||
@@ -740,19 +749,13 @@ func Fuzz_strspn(f *testing.F) {
|
||||
|
||||
s = term(s)
|
||||
chars = term(chars)
|
||||
want := strings.IndexFunc(s, func(r rune) bool {
|
||||
if uint32(r) >= utf8.RuneSelf {
|
||||
t.Skip()
|
||||
}
|
||||
return strings.IndexByte(chars, byte(r)) < 0
|
||||
})
|
||||
if want < 0 {
|
||||
want = len(s)
|
||||
}
|
||||
want := indexNotByte(s, chars)
|
||||
|
||||
if uint32(got) != uint32(want) {
|
||||
t.Errorf("strspn(%q, %q) = %d, want %d",
|
||||
s, chars, uint32(got), uint32(want))
|
||||
t.Errorf("strspn(%v, %v) = %d, want %d",
|
||||
[]byte(memory[ptr1:ptr1+len(s)]),
|
||||
[]byte(memory[ptr2:ptr2+len(chars)]),
|
||||
uint32(got), uint32(want))
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -766,11 +769,6 @@ func Fuzz_strcspn(f *testing.F) {
|
||||
if len(s) > 128 || len(chars) > 128 {
|
||||
t.SkipNow()
|
||||
}
|
||||
if strings.ContainsFunc(chars, func(r rune) bool {
|
||||
return uint32(r) >= utf8.RuneSelf
|
||||
}) {
|
||||
t.SkipNow()
|
||||
}
|
||||
copy(memory[ptr1:], s)
|
||||
copy(memory[ptr2:], chars)
|
||||
memory[ptr1+len(s)] = 0
|
||||
@@ -780,10 +778,7 @@ func Fuzz_strcspn(f *testing.F) {
|
||||
|
||||
s = term(s)
|
||||
chars = term(chars)
|
||||
want := strings.IndexAny(s, chars)
|
||||
if want < 0 {
|
||||
want = len(s)
|
||||
}
|
||||
want := indexAnyByte(s, chars)
|
||||
|
||||
if uint32(got) != uint32(want) {
|
||||
t.Errorf("strcspn(%q, %q) = %d, want %d",
|
||||
@@ -826,3 +821,21 @@ func term1[T interface{ []byte | string }](s T) T {
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func indexNotByte(s, chars string) int {
|
||||
for i, c := range []byte(s) {
|
||||
if strings.IndexByte(chars, c) < 0 {
|
||||
return i
|
||||
}
|
||||
}
|
||||
return len(s)
|
||||
}
|
||||
|
||||
func indexAnyByte(s, chars string) int {
|
||||
for i, c := range []byte(s) {
|
||||
if strings.IndexByte(chars, c) >= 0 {
|
||||
return i
|
||||
}
|
||||
}
|
||||
return len(s)
|
||||
}
|
||||
|
||||
@@ -3,11 +3,8 @@
|
||||
#ifndef _WASM_SIMD128_STRING_H
|
||||
#define _WASM_SIMD128_STRING_H
|
||||
|
||||
#include <ctype.h>
|
||||
#include <stdint.h>
|
||||
#include <strings.h>
|
||||
#include <wasm_simd128.h>
|
||||
#include <__macro_PAGESIZE.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
@@ -17,7 +14,6 @@ extern "C" {
|
||||
|
||||
// Use the builtins if compiled with bulk memory operations.
|
||||
// Clang will intrinsify using SIMD for small, constant N.
|
||||
// For everything else, this helps inlining.
|
||||
|
||||
__attribute__((weak, always_inline))
|
||||
void *memset(void *dest, int c, size_t n) {
|
||||
@@ -80,7 +76,7 @@ int memcmp(const void *vl, const void *vr, size_t n) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
__attribute__((weak, noinline))
|
||||
__attribute__((weak))
|
||||
void *memchr(const void *s, int c, size_t n) {
|
||||
// When n is zero, a function that locates a character finds no occurrence.
|
||||
// Otherwise, decrement n to ensure sub_overflow overflows
|
||||
@@ -91,30 +87,30 @@ void *memchr(const void *s, int c, size_t n) {
|
||||
|
||||
// memchr must behave as if it reads characters sequentially
|
||||
// and stops as soon as a match is found.
|
||||
// Aligning ensures loads beyond the first match are safe.
|
||||
// Volatile avoids compiler tricks around out of bounds loads.
|
||||
// Aligning ensures out of bounds loads are safe.
|
||||
uintptr_t align = (uintptr_t)s % sizeof(v128_t);
|
||||
const volatile v128_t *v = (v128_t *)((char *)s - align);
|
||||
const v128_t vc = wasm_i8x16_splat(c);
|
||||
uintptr_t addr = (uintptr_t)s - align;
|
||||
v128_t vc = wasm_i8x16_splat(c);
|
||||
|
||||
for (;;) {
|
||||
const v128_t cmp = wasm_i8x16_eq(*v, vc);
|
||||
v128_t v = *(v128_t *)addr;
|
||||
v128_t cmp = wasm_i8x16_eq(v, vc);
|
||||
// Bitmask is slow on AArch64, any_true is much faster.
|
||||
if (wasm_v128_any_true(cmp)) {
|
||||
// Clear the bits corresponding to alignment (little-endian)
|
||||
// Clear the bits corresponding to align (little-endian)
|
||||
// so we can count trailing zeros.
|
||||
int mask = wasm_i8x16_bitmask(cmp) >> align << align;
|
||||
// At least one bit will be set, unless we cleared them.
|
||||
// Knowing this helps the compiler.
|
||||
// At least one bit will be set, unless align cleared them.
|
||||
// Knowing this helps the compiler if it unrolls the loop.
|
||||
__builtin_assume(mask || align);
|
||||
// If the mask is zero because of alignment,
|
||||
// If the mask became zero because of align,
|
||||
// it's as if we didn't find anything.
|
||||
if (mask) {
|
||||
// Find the offset of the first one bit (little-endian).
|
||||
// That's a match, unless it is beyond the end of the object.
|
||||
// Recall that we decremented n, so less-than-or-equal-to is correct.
|
||||
size_t ctz = __builtin_ctz(mask);
|
||||
return ctz - align <= n ? (char *)v + ctz : NULL;
|
||||
return ctz - align <= n ? (char *)s + (addr - (uintptr_t)s + ctz) : NULL;
|
||||
}
|
||||
}
|
||||
// Decrement n; if it overflows we're done.
|
||||
@@ -122,11 +118,11 @@ void *memchr(const void *s, int c, size_t n) {
|
||||
return NULL;
|
||||
}
|
||||
align = 0;
|
||||
v++;
|
||||
addr += sizeof(v128_t);
|
||||
}
|
||||
}
|
||||
|
||||
__attribute__((weak, noinline))
|
||||
__attribute__((weak))
|
||||
void *memrchr(const void *s, int c, size_t n) {
|
||||
// memrchr is allowed to read up to n bytes from the object.
|
||||
// Search backward for the last matching character.
|
||||
@@ -137,8 +133,10 @@ void *memrchr(const void *s, int c, size_t n) {
|
||||
// Bitmask is slow on AArch64, any_true is much faster.
|
||||
if (wasm_v128_any_true(cmp)) {
|
||||
// Find the offset of the last one bit (little-endian).
|
||||
size_t clz = __builtin_clz(wasm_i8x16_bitmask(cmp)) - 15;
|
||||
return (char *)(v + 1) - clz;
|
||||
// The leading 16 bits of the bitmask are always zero,
|
||||
// and to be ignored.
|
||||
size_t clz = __builtin_clz(wasm_i8x16_bitmask(cmp)) - 16;
|
||||
return (char *)(v + 1) - (clz + 1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -150,64 +148,63 @@ void *memrchr(const void *s, int c, size_t n) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
__attribute__((weak, noinline))
|
||||
__attribute__((weak))
|
||||
size_t strlen(const char *s) {
|
||||
// strlen must stop as soon as it finds the terminator.
|
||||
// Aligning ensures loads beyond the terminator are safe.
|
||||
// Aligning ensures out of bounds loads are safe.
|
||||
uintptr_t align = (uintptr_t)s % sizeof(v128_t);
|
||||
const volatile v128_t *v = (v128_t *)(s - align);
|
||||
uintptr_t addr = (uintptr_t)s - align;
|
||||
|
||||
for (;;) {
|
||||
const v128_t vv = *v;
|
||||
v128_t v = *(v128_t *)addr;
|
||||
// Bitmask is slow on AArch64, all_true is much faster.
|
||||
if (!wasm_i8x16_all_true(vv)) {
|
||||
const v128_t cmp = wasm_i8x16_eq(vv, (v128_t){});
|
||||
// Clear the bits corresponding to alignment (little-endian)
|
||||
if (!wasm_i8x16_all_true(v)) {
|
||||
const v128_t cmp = wasm_i8x16_eq(v, (v128_t){});
|
||||
// Clear the bits corresponding to align (little-endian)
|
||||
// so we can count trailing zeros.
|
||||
int mask = wasm_i8x16_bitmask(cmp) >> align << align;
|
||||
// At least one bit will be set, unless we cleared them.
|
||||
// Knowing this helps the compiler.
|
||||
// At least one bit will be set, unless align cleared them.
|
||||
// Knowing this helps the compiler if it unrolls the loop.
|
||||
__builtin_assume(mask || align);
|
||||
// If the mask is zero because of alignment,
|
||||
// If the mask became zero because of align,
|
||||
// it's as if we didn't find anything.
|
||||
if (mask) {
|
||||
// Find the offset of the first one bit (little-endian).
|
||||
return (char *)v - s + __builtin_ctz(mask);
|
||||
return addr - (uintptr_t)s + __builtin_ctz(mask);
|
||||
}
|
||||
}
|
||||
align = 0;
|
||||
v++;
|
||||
addr += sizeof(v128_t);
|
||||
}
|
||||
}
|
||||
|
||||
static char *__strchrnul(const char *s, int c) {
|
||||
// strchrnul must stop as soon as it finds the terminator.
|
||||
// Aligning ensures loads beyond the terminator are safe.
|
||||
// Volatile avoids compiler tricks around out of bounds loads.
|
||||
// strchrnul must stop as soon as a match is found.
|
||||
// Aligning ensures out of bounds loads are safe.
|
||||
uintptr_t align = (uintptr_t)s % sizeof(v128_t);
|
||||
const volatile v128_t *v = (v128_t *)(s - align);
|
||||
const v128_t vc = wasm_i8x16_splat(c);
|
||||
uintptr_t addr = (uintptr_t)s - align;
|
||||
v128_t vc = wasm_i8x16_splat(c);
|
||||
|
||||
for (;;) {
|
||||
const v128_t vv = *v;
|
||||
const v128_t cmp = wasm_i8x16_eq(vv, (v128_t){}) | wasm_i8x16_eq(vv, vc);
|
||||
v128_t v = *(v128_t *)addr;
|
||||
const v128_t cmp = wasm_i8x16_eq(v, (v128_t){}) | wasm_i8x16_eq(v, vc);
|
||||
// Bitmask is slow on AArch64, any_true is much faster.
|
||||
if (wasm_v128_any_true(cmp)) {
|
||||
// Clear the bits corresponding to alignment (little-endian)
|
||||
// Clear the bits corresponding to align (little-endian)
|
||||
// so we can count trailing zeros.
|
||||
int mask = wasm_i8x16_bitmask(cmp) >> align << align;
|
||||
// At least one bit will be set, unless we cleared them.
|
||||
// Knowing this helps the compiler.
|
||||
// At least one bit will be set, unless align cleared them.
|
||||
// Knowing this helps the compiler if it unrolls the loop.
|
||||
__builtin_assume(mask || align);
|
||||
// If the mask is zero because of alignment,
|
||||
// If the mask became zero because of align,
|
||||
// it's as if we didn't find anything.
|
||||
if (mask) {
|
||||
// Find the offset of the first one bit (little-endian).
|
||||
return (char *)v + __builtin_ctz(mask);
|
||||
return (char *)s + (addr - (uintptr_t)s + __builtin_ctz(mask));
|
||||
}
|
||||
}
|
||||
align = 0;
|
||||
v++;
|
||||
addr += sizeof(v128_t);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -245,17 +242,21 @@ char *strrchr(const char *s, int c) {
|
||||
// SIMDized check which bytes are in a set (Geoff Langdale)
|
||||
// http://0x80.pl/notesen/2018-10-18-simd-byte-lookup.html
|
||||
|
||||
// This is the same algorithm as truffle from Hyperscan:
|
||||
// https://github.com/intel/hyperscan/blob/v5.4.2/src/nfa/truffle.c#L64-L81
|
||||
// https://github.com/intel/hyperscan/blob/v5.4.2/src/nfa/trufflecompile.cpp
|
||||
|
||||
typedef struct {
|
||||
__u8x16 l;
|
||||
__u8x16 h;
|
||||
__u8x16 lo;
|
||||
__u8x16 hi;
|
||||
} __wasm_v128_bitmap256_t;
|
||||
|
||||
__attribute__((always_inline))
|
||||
static void __wasm_v128_setbit(__wasm_v128_bitmap256_t *bitmap, int i) {
|
||||
uint8_t hi_nibble = (uint8_t)i >> 4;
|
||||
uint8_t lo_nibble = (uint8_t)i & 0xf;
|
||||
bitmap->l[lo_nibble] |= 1 << (hi_nibble - 0);
|
||||
bitmap->h[lo_nibble] |= 1 << (hi_nibble - 8);
|
||||
static void __wasm_v128_setbit(__wasm_v128_bitmap256_t *bitmap, uint8_t i) {
|
||||
uint8_t hi_nibble = i >> 4;
|
||||
uint8_t lo_nibble = i & 0xf;
|
||||
bitmap->lo[lo_nibble] |= (uint8_t)(1u << (hi_nibble - 0));
|
||||
bitmap->hi[lo_nibble] |= (uint8_t)(1u << (hi_nibble - 8));
|
||||
}
|
||||
|
||||
#ifndef __wasm_relaxed_simd__
|
||||
@@ -266,54 +267,52 @@ static void __wasm_v128_setbit(__wasm_v128_bitmap256_t *bitmap, int i) {
|
||||
|
||||
__attribute__((always_inline))
|
||||
static v128_t __wasm_v128_chkbits(__wasm_v128_bitmap256_t bitmap, v128_t v) {
|
||||
v128_t indices_0_7 = v & wasm_u8x16_const_splat(0x8f);
|
||||
v128_t indices_8_15 = (v & wasm_u8x16_const_splat(0x80)) ^ indices_0_7;
|
||||
|
||||
v128_t row_0_7 = wasm_i8x16_swizzle(bitmap.l, indices_0_7);
|
||||
v128_t row_8_15 = wasm_i8x16_swizzle(bitmap.h, indices_8_15);
|
||||
|
||||
v128_t bitsets = row_0_7 | row_8_15;
|
||||
|
||||
v128_t hi_nibbles = wasm_u8x16_shr(v, 4);
|
||||
v128_t bitmask_lookup = wasm_u8x16_const(1, 2, 4, 8, 16, 32, 64, 128, //
|
||||
1, 2, 4, 8, 16, 32, 64, 128);
|
||||
v128_t bitmask_lookup = wasm_u64x2_const_splat(0x8040201008040201);
|
||||
v128_t bitmask = wasm_i8x16_relaxed_swizzle(bitmask_lookup, hi_nibbles);
|
||||
|
||||
return wasm_i8x16_eq(bitsets & bitmask, bitmask);
|
||||
v128_t indices_0_7 = v & wasm_u8x16_const_splat(0x8f);
|
||||
v128_t indices_8_15 = indices_0_7 ^ wasm_u8x16_const_splat(0x80);
|
||||
|
||||
v128_t row_0_7 = wasm_i8x16_swizzle((v128_t)bitmap.lo, indices_0_7);
|
||||
v128_t row_8_15 = wasm_i8x16_swizzle((v128_t)bitmap.hi, indices_8_15);
|
||||
|
||||
v128_t bitsets = row_0_7 | row_8_15;
|
||||
return bitsets & bitmask;
|
||||
}
|
||||
|
||||
#undef wasm_i8x16_relaxed_swizzle
|
||||
|
||||
__attribute__((weak, noinline))
|
||||
__attribute__((weak))
|
||||
size_t strspn(const char *s, const char *c) {
|
||||
// strspn must stop as soon as it finds the terminator.
|
||||
// Aligning ensures loads beyond the terminator are safe.
|
||||
// Volatile avoids compiler tricks around out of bounds loads.
|
||||
// Aligning ensures out of bounds loads are safe.
|
||||
uintptr_t align = (uintptr_t)s % sizeof(v128_t);
|
||||
const volatile v128_t *v = (v128_t *)(s - align);
|
||||
uintptr_t addr = (uintptr_t)s - align;
|
||||
|
||||
if (!c[0]) return 0;
|
||||
if (!c[1]) {
|
||||
const v128_t vc = wasm_i8x16_splat(*c);
|
||||
v128_t vc = wasm_i8x16_splat(*c);
|
||||
for (;;) {
|
||||
const v128_t cmp = wasm_i8x16_eq(*v, vc);
|
||||
v128_t v = *(v128_t *)addr;
|
||||
v128_t cmp = wasm_i8x16_eq(v, vc);
|
||||
// Bitmask is slow on AArch64, all_true is much faster.
|
||||
if (!wasm_i8x16_all_true(cmp)) {
|
||||
// Clear the bits corresponding to alignment (little-endian)
|
||||
// Clear the bits corresponding to align (little-endian)
|
||||
// so we can count trailing zeros.
|
||||
int mask = (uint16_t)~wasm_i8x16_bitmask(cmp) >> align << align;
|
||||
// At least one bit will be set, unless we cleared them.
|
||||
// Knowing this helps the compiler.
|
||||
// At least one bit will be set, unless align cleared them.
|
||||
// Knowing this helps the compiler if it unrolls the loop.
|
||||
__builtin_assume(mask || align);
|
||||
// If the mask is zero because of alignment,
|
||||
// If the mask became zero because of align,
|
||||
// it's as if we didn't find anything.
|
||||
if (mask) {
|
||||
// Find the offset of the first one bit (little-endian).
|
||||
return (char *)v - s + __builtin_ctz(mask);
|
||||
return addr - (uintptr_t)s + __builtin_ctz(mask);
|
||||
}
|
||||
}
|
||||
align = 0;
|
||||
v++;
|
||||
addr += sizeof(v128_t);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -321,67 +320,70 @@ size_t strspn(const char *s, const char *c) {
|
||||
|
||||
for (; *c; c++) {
|
||||
// Terminator IS NOT on the bitmap.
|
||||
__wasm_v128_setbit(&bitmap, *c);
|
||||
__wasm_v128_setbit(&bitmap, (uint8_t)*c);
|
||||
}
|
||||
|
||||
for (;;) {
|
||||
const v128_t cmp = __wasm_v128_chkbits(bitmap, *v);
|
||||
v128_t v = *(v128_t *)addr;
|
||||
v128_t found = __wasm_v128_chkbits(bitmap, v);
|
||||
// Bitmask is slow on AArch64, all_true is much faster.
|
||||
if (!wasm_i8x16_all_true(cmp)) {
|
||||
// Clear the bits corresponding to alignment (little-endian)
|
||||
if (!wasm_i8x16_all_true(found)) {
|
||||
v128_t cmp = wasm_i8x16_eq(found, (v128_t){});
|
||||
// Clear the bits corresponding to align (little-endian)
|
||||
// so we can count trailing zeros.
|
||||
int mask = (uint16_t)~wasm_i8x16_bitmask(cmp) >> align << align;
|
||||
// At least one bit will be set, unless we cleared them.
|
||||
// Knowing this helps the compiler.
|
||||
int mask = wasm_i8x16_bitmask(cmp) >> align << align;
|
||||
// At least one bit will be set, unless align cleared them.
|
||||
// Knowing this helps the compiler if it unrolls the loop.
|
||||
__builtin_assume(mask || align);
|
||||
// If the mask is zero because of alignment,
|
||||
// If the mask became zero because of align,
|
||||
// it's as if we didn't find anything.
|
||||
if (mask) {
|
||||
// Find the offset of the first one bit (little-endian).
|
||||
return (char *)v - s + __builtin_ctz(mask);
|
||||
return addr - (uintptr_t)s + __builtin_ctz(mask);
|
||||
}
|
||||
}
|
||||
align = 0;
|
||||
v++;
|
||||
addr += sizeof(v128_t);
|
||||
}
|
||||
}
|
||||
|
||||
__attribute__((weak, noinline))
|
||||
__attribute__((weak))
|
||||
size_t strcspn(const char *s, const char *c) {
|
||||
if (!c[0] || !c[1]) return __strchrnul(s, *c) - s;
|
||||
|
||||
// strcspn must stop as soon as it finds the terminator.
|
||||
// Aligning ensures loads beyond the terminator are safe.
|
||||
// Volatile avoids compiler tricks around out of bounds loads.
|
||||
// Aligning ensures out of bounds loads are safe.
|
||||
uintptr_t align = (uintptr_t)s % sizeof(v128_t);
|
||||
const volatile v128_t *v = (v128_t *)(s - align);
|
||||
uintptr_t addr = (uintptr_t)s - align;
|
||||
|
||||
__wasm_v128_bitmap256_t bitmap = {};
|
||||
|
||||
do {
|
||||
// Terminator IS on the bitmap.
|
||||
__wasm_v128_setbit(&bitmap, *c);
|
||||
__wasm_v128_setbit(&bitmap, (uint8_t)*c);
|
||||
} while (*c++);
|
||||
|
||||
for (;;) {
|
||||
const v128_t cmp = __wasm_v128_chkbits(bitmap, *v);
|
||||
v128_t v = *(v128_t *)addr;
|
||||
v128_t found = __wasm_v128_chkbits(bitmap, v);
|
||||
// Bitmask is slow on AArch64, any_true is much faster.
|
||||
if (wasm_v128_any_true(cmp)) {
|
||||
// Clear the bits corresponding to alignment (little-endian)
|
||||
if (wasm_v128_any_true(found)) {
|
||||
v128_t cmp = wasm_i8x16_eq(found, (v128_t){});
|
||||
// Clear the bits corresponding to align (little-endian)
|
||||
// so we can count trailing zeros.
|
||||
int mask = wasm_i8x16_bitmask(cmp) >> align << align;
|
||||
// At least one bit will be set, unless we cleared them.
|
||||
// Knowing this helps the compiler.
|
||||
int mask = (uint16_t)~wasm_i8x16_bitmask(cmp) >> align << align;
|
||||
// At least one bit will be set, unless align cleared them.
|
||||
// Knowing this helps the compiler if it unrolls the loop.
|
||||
__builtin_assume(mask || align);
|
||||
// If the mask is zero because of alignment,
|
||||
// If the mask became zero because of align,
|
||||
// it's as if we didn't find anything.
|
||||
if (mask) {
|
||||
// Find the offset of the first one bit (little-endian).
|
||||
return (char *)v - s + __builtin_ctz(mask);
|
||||
return addr - (uintptr_t)s + __builtin_ctz(mask);
|
||||
}
|
||||
}
|
||||
align = 0;
|
||||
v++;
|
||||
addr += sizeof(v128_t);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ elif [[ "$OSTYPE" == "darwin"* ]]; then
|
||||
fi
|
||||
fi
|
||||
|
||||
WASI_SDK="https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-25/wasi-sdk-25.0-$WASI_SDK.tar.gz"
|
||||
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"
|
||||
|
||||
# Download tools
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# Remove VFS registration. Go handles it.
|
||||
--- sqlite3.c.orig
|
||||
+++ sqlite3.c
|
||||
@@ -26726,7 +26726,7 @@
|
||||
@@ -26884,7 +26884,7 @@
|
||||
sqlite3_free(p);
|
||||
return sqlite3_os_init();
|
||||
}
|
||||
@@ -10,7 +10,7 @@
|
||||
/*
|
||||
** The list of all registered VFS implementations.
|
||||
*/
|
||||
@@ -26823,7 +26823,7 @@
|
||||
@@ -26981,7 +26981,7 @@
|
||||
sqlite3_mutex_leave(mutex);
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
1
stmt.go
1
stmt.go
@@ -631,7 +631,6 @@ func (s *Stmt) ColumnValue(col int) Value {
|
||||
stk_t(s.handle), stk_t(col)))
|
||||
return Value{
|
||||
c: s.c,
|
||||
unprot: true,
|
||||
handle: ptr,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@ type params struct {
|
||||
*sql.DB
|
||||
}
|
||||
|
||||
func (t params) mustExec(sql string, args ...interface{}) sql.Result {
|
||||
func (t params) mustExec(sql string, args ...any) sql.Result {
|
||||
res, err := t.DB.Exec(sql, args...)
|
||||
if err != nil {
|
||||
t.Fatalf("Error running %q: %v", sql, err)
|
||||
|
||||
@@ -12,7 +12,7 @@ import (
|
||||
"github.com/ncruces/go-sqlite3"
|
||||
_ "github.com/ncruces/go-sqlite3/embed"
|
||||
_ "github.com/ncruces/go-sqlite3/internal/testcfg"
|
||||
_ "github.com/ncruces/go-sqlite3/vfs/memdb"
|
||||
"github.com/ncruces/go-sqlite3/vfs/memdb"
|
||||
)
|
||||
|
||||
func TestConn_Open_dir(t *testing.T) {
|
||||
@@ -112,6 +112,48 @@ func TestConn_Close_BUSY(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestConn_BusyHandler(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
dsn := memdb.TestDB(t)
|
||||
|
||||
db1, err := sqlite3.Open(dsn)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer db1.Close()
|
||||
|
||||
db2, err := sqlite3.Open(dsn)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer db2.Close()
|
||||
|
||||
var called bool
|
||||
err = db2.BusyHandler(func(ctx context.Context, count int) (retry bool) {
|
||||
called = true
|
||||
return count < 1
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
tx, err := db1.BeginExclusive()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer tx.End(&err)
|
||||
|
||||
_, err = db2.BeginExclusive()
|
||||
if !errors.Is(err, sqlite3.BUSY) {
|
||||
t.Errorf("got %v, want sqlite3.BUSY", err)
|
||||
}
|
||||
|
||||
if !called {
|
||||
t.Error("busy handler not called")
|
||||
}
|
||||
}
|
||||
|
||||
func TestConn_SetInterrupt(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
|
||||
@@ -59,7 +59,9 @@ func Test_endianness(t *testing.T) {
|
||||
}
|
||||
defer stmt.Close()
|
||||
|
||||
if stmt.Step() {
|
||||
if !stmt.Step() {
|
||||
t.Fatal(stmt.Err())
|
||||
} else {
|
||||
if got := stmt.ColumnInt64(0); got != value {
|
||||
t.Errorf("got %d, want %d", got, value)
|
||||
}
|
||||
@@ -67,9 +69,5 @@ func Test_endianness(t *testing.T) {
|
||||
t.Errorf("got %s, want %d", got, value)
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -64,7 +64,9 @@ func TestCreateFunction(t *testing.T) {
|
||||
}
|
||||
defer stmt.Close()
|
||||
|
||||
if stmt.Step() {
|
||||
if !stmt.Step() {
|
||||
t.Fatal(stmt.Err())
|
||||
} else {
|
||||
if got := stmt.ColumnType(0); got != sqlite3.INTEGER {
|
||||
t.Errorf("got %v, want INTEGER", got)
|
||||
}
|
||||
@@ -73,7 +75,9 @@ func TestCreateFunction(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
if stmt.Step() {
|
||||
if !stmt.Step() {
|
||||
t.Fatal(stmt.Err())
|
||||
} else {
|
||||
if got := stmt.ColumnType(0); got != sqlite3.INTEGER {
|
||||
t.Errorf("got %v, want INTEGER", got)
|
||||
}
|
||||
@@ -82,7 +86,9 @@ func TestCreateFunction(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
if stmt.Step() {
|
||||
if !stmt.Step() {
|
||||
t.Fatal(stmt.Err())
|
||||
} else {
|
||||
if got := stmt.ColumnType(0); got != sqlite3.INTEGER {
|
||||
t.Errorf("got %v, want INTEGER", got)
|
||||
}
|
||||
@@ -91,7 +97,9 @@ func TestCreateFunction(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
if stmt.Step() {
|
||||
if !stmt.Step() {
|
||||
t.Fatal(stmt.Err())
|
||||
} else {
|
||||
if got := stmt.ColumnType(0); got != sqlite3.FLOAT {
|
||||
t.Errorf("got %v, want FLOAT", got)
|
||||
}
|
||||
@@ -100,7 +108,9 @@ func TestCreateFunction(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
if stmt.Step() {
|
||||
if !stmt.Step() {
|
||||
t.Fatal(stmt.Err())
|
||||
} else {
|
||||
if got := stmt.ColumnType(0); got != sqlite3.TEXT {
|
||||
t.Errorf("got %v, want TEXT", got)
|
||||
}
|
||||
@@ -109,7 +119,9 @@ func TestCreateFunction(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
if stmt.Step() {
|
||||
if !stmt.Step() {
|
||||
t.Fatal(stmt.Err())
|
||||
} else {
|
||||
if got := stmt.ColumnType(0); got != sqlite3.BLOB {
|
||||
t.Errorf("got %v, want BLOB", got)
|
||||
}
|
||||
@@ -118,7 +130,9 @@ func TestCreateFunction(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
if stmt.Step() {
|
||||
if !stmt.Step() {
|
||||
t.Fatal(stmt.Err())
|
||||
} else {
|
||||
if got := stmt.ColumnType(0); got != sqlite3.BLOB {
|
||||
t.Errorf("got %v, want BLOB", got)
|
||||
}
|
||||
@@ -127,7 +141,9 @@ func TestCreateFunction(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
if stmt.Step() {
|
||||
if !stmt.Step() {
|
||||
t.Fatal(stmt.Err())
|
||||
} else {
|
||||
if got := stmt.ColumnType(0); got != sqlite3.TEXT {
|
||||
t.Errorf("got %v, want TEXT", got)
|
||||
}
|
||||
@@ -136,7 +152,9 @@ func TestCreateFunction(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
if stmt.Step() {
|
||||
if !stmt.Step() {
|
||||
t.Fatal(stmt.Err())
|
||||
} else {
|
||||
if got := stmt.ColumnType(0); got != sqlite3.TEXT {
|
||||
t.Errorf("got %v, want TEXT", got)
|
||||
}
|
||||
@@ -148,7 +166,9 @@ func TestCreateFunction(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
if stmt.Step() {
|
||||
if !stmt.Step() {
|
||||
t.Fatal(stmt.Err())
|
||||
} else {
|
||||
if got := stmt.ColumnType(0); got != sqlite3.INTEGER {
|
||||
t.Errorf("got %v, want INTEGER", got)
|
||||
}
|
||||
@@ -157,7 +177,9 @@ func TestCreateFunction(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
if stmt.Step() {
|
||||
if !stmt.Step() {
|
||||
t.Fatal(stmt.Err())
|
||||
} else {
|
||||
if got := stmt.ColumnType(0); got != sqlite3.NULL {
|
||||
t.Errorf("got %v, want NULL", got)
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ import (
|
||||
"github.com/ncruces/go-sqlite3/vfs"
|
||||
_ "github.com/ncruces/go-sqlite3/vfs/adiantum"
|
||||
"github.com/ncruces/go-sqlite3/vfs/memdb"
|
||||
"github.com/ncruces/go-sqlite3/vfs/mvcc"
|
||||
_ "github.com/ncruces/go-sqlite3/vfs/xts"
|
||||
)
|
||||
|
||||
@@ -98,6 +99,22 @@ func Test_memdb(t *testing.T) {
|
||||
testIntegrity(t, name)
|
||||
}
|
||||
|
||||
func Test_mvcc(t *testing.T) {
|
||||
var iter int
|
||||
if testing.Short() {
|
||||
iter = 1000
|
||||
} else {
|
||||
iter = 5000
|
||||
}
|
||||
|
||||
mvcc.Create("test.db", "")
|
||||
name := "file:/test.db?vfs=mvcc" +
|
||||
"&_pragma=busy_timeout(10000)"
|
||||
createDB(t, name)
|
||||
testParallel(t, name, iter)
|
||||
testIntegrity(t, name)
|
||||
}
|
||||
|
||||
func Test_adiantum(t *testing.T) {
|
||||
if !vfs.SupportsFileLocking {
|
||||
t.Skip("skipping without locks")
|
||||
@@ -312,6 +329,16 @@ func Benchmark_memdb(b *testing.B) {
|
||||
testParallel(b, name, b.N)
|
||||
}
|
||||
|
||||
func Benchmark_mvcc(b *testing.B) {
|
||||
mvcc.Create("test.db", "")
|
||||
name := "file:/test.db?vfs=mvcc" +
|
||||
"&_pragma=busy_timeout(10000)"
|
||||
createDB(b, name)
|
||||
|
||||
b.ResetTimer()
|
||||
testParallel(b, name, b.N)
|
||||
}
|
||||
|
||||
func createDB(t testing.TB, name string) {
|
||||
db, err := sqlite3.Open(name)
|
||||
if err != nil {
|
||||
|
||||
@@ -168,7 +168,9 @@ func TestStmt(t *testing.T) {
|
||||
t.Errorf(`got %q, want "main"`, got)
|
||||
}
|
||||
|
||||
if stmt.Step() {
|
||||
if !stmt.Step() {
|
||||
t.Fatal(stmt.Err())
|
||||
} else {
|
||||
if got := stmt.ColumnType(0); got != sqlite3.INTEGER {
|
||||
t.Errorf("got %v, want INTEGER", got)
|
||||
}
|
||||
@@ -195,7 +197,9 @@ func TestStmt(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
if stmt.Step() {
|
||||
if !stmt.Step() {
|
||||
t.Fatal(stmt.Err())
|
||||
} else {
|
||||
if got := stmt.ColumnType(0); got != sqlite3.INTEGER {
|
||||
t.Errorf("got %v, want INTEGER", got)
|
||||
}
|
||||
@@ -222,7 +226,9 @@ func TestStmt(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
if stmt.Step() {
|
||||
if !stmt.Step() {
|
||||
t.Fatal(stmt.Err())
|
||||
} else {
|
||||
if got := stmt.ColumnType(0); got != sqlite3.INTEGER {
|
||||
t.Errorf("got %v, want INTEGER", got)
|
||||
}
|
||||
@@ -249,7 +255,9 @@ func TestStmt(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
if stmt.Step() {
|
||||
if !stmt.Step() {
|
||||
t.Fatal(stmt.Err())
|
||||
} else {
|
||||
if got := stmt.ColumnType(0); got != sqlite3.FLOAT {
|
||||
t.Errorf("got %v, want FLOAT", got)
|
||||
}
|
||||
@@ -276,7 +284,9 @@ func TestStmt(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
if stmt.Step() {
|
||||
if !stmt.Step() {
|
||||
t.Fatal(stmt.Err())
|
||||
} else {
|
||||
if got := stmt.ColumnType(0); got != sqlite3.NULL {
|
||||
t.Errorf("got %v, want NULL", got)
|
||||
}
|
||||
@@ -303,7 +313,9 @@ func TestStmt(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
if stmt.Step() {
|
||||
if !stmt.Step() {
|
||||
t.Fatal(stmt.Err())
|
||||
} else {
|
||||
if got := stmt.ColumnType(0); got != sqlite3.TEXT {
|
||||
t.Errorf("got %v, want TEXT", got)
|
||||
}
|
||||
@@ -328,7 +340,9 @@ func TestStmt(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
if stmt.Step() {
|
||||
if !stmt.Step() {
|
||||
t.Fatal(stmt.Err())
|
||||
} else {
|
||||
if got := stmt.ColumnType(0); got != sqlite3.TEXT {
|
||||
t.Errorf("got %v, want TEXT", got)
|
||||
}
|
||||
@@ -353,7 +367,9 @@ func TestStmt(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
if stmt.Step() {
|
||||
if !stmt.Step() {
|
||||
t.Fatal(stmt.Err())
|
||||
} else {
|
||||
if got := stmt.ColumnType(0); got != sqlite3.TEXT {
|
||||
t.Errorf("got %v, want TEXT", got)
|
||||
}
|
||||
@@ -378,7 +394,9 @@ func TestStmt(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
if stmt.Step() {
|
||||
if !stmt.Step() {
|
||||
t.Fatal(stmt.Err())
|
||||
} else {
|
||||
if got := stmt.ColumnType(0); got != sqlite3.BLOB {
|
||||
t.Errorf("got %v, want BLOB", got)
|
||||
}
|
||||
@@ -403,7 +421,9 @@ func TestStmt(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
if stmt.Step() {
|
||||
if !stmt.Step() {
|
||||
t.Fatal(stmt.Err())
|
||||
} else {
|
||||
if got := stmt.ColumnType(0); got != sqlite3.BLOB {
|
||||
t.Errorf("got %v, want BLOB", got)
|
||||
}
|
||||
@@ -428,7 +448,9 @@ func TestStmt(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
if stmt.Step() {
|
||||
if !stmt.Step() {
|
||||
t.Fatal(stmt.Err())
|
||||
} else {
|
||||
if got := stmt.ColumnType(0); got != sqlite3.BLOB {
|
||||
t.Errorf("got %v, want BLOB", got)
|
||||
}
|
||||
@@ -453,7 +475,9 @@ func TestStmt(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
if stmt.Step() {
|
||||
if !stmt.Step() {
|
||||
t.Fatal(stmt.Err())
|
||||
} else {
|
||||
if got := stmt.ColumnType(0); got != sqlite3.TEXT {
|
||||
t.Errorf("got %v, want TEXT", got)
|
||||
}
|
||||
@@ -480,7 +504,9 @@ func TestStmt(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
if stmt.Step() {
|
||||
if !stmt.Step() {
|
||||
t.Fatal(stmt.Err())
|
||||
} else {
|
||||
if got := stmt.ColumnType(0); got != sqlite3.NULL {
|
||||
t.Errorf("got %v, want NULL", got)
|
||||
}
|
||||
@@ -643,7 +669,9 @@ func TestStmt_ColumnValue(t *testing.T) {
|
||||
}
|
||||
defer stmt.Close()
|
||||
|
||||
if stmt.Step() {
|
||||
if !stmt.Step() {
|
||||
t.Fatal(stmt.Err())
|
||||
} else {
|
||||
val := stmt.ColumnValue(0)
|
||||
if _, err := val.InFirst(); err == nil {
|
||||
t.Error("want error")
|
||||
@@ -675,7 +703,9 @@ func TestStmt_Columns(t *testing.T) {
|
||||
}
|
||||
defer stmt.Close()
|
||||
|
||||
if stmt.Step() {
|
||||
if !stmt.Step() {
|
||||
t.Fatal(stmt.Err())
|
||||
} else {
|
||||
var dest [5]any
|
||||
if err := stmt.Columns(dest[:]...); err != nil {
|
||||
t.Fatal(err)
|
||||
|
||||
@@ -244,7 +244,9 @@ func TestDB_isoWeek(t *testing.T) {
|
||||
tstart := time.Date(1500, 1, 1, 12, 0, 0, 0, time.UTC)
|
||||
for tm := tstart; tm.Before(tend); tm = tm.AddDate(0, 0, 1) {
|
||||
stmt.BindTime(1, tm, sqlite3.TimeFormatDefault)
|
||||
if stmt.Step() {
|
||||
if !stmt.Step() {
|
||||
t.Fatal(stmt.Err())
|
||||
} else {
|
||||
y, w := tm.ISOWeek()
|
||||
d := tm.Weekday()
|
||||
if d == 0 {
|
||||
|
||||
@@ -37,11 +37,10 @@ func TestConn_Transaction_exec(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer stmt.Close()
|
||||
if stmt.Step() {
|
||||
return stmt.ColumnInt(0)
|
||||
if !stmt.Step() {
|
||||
t.Fatal(stmt.Err())
|
||||
}
|
||||
t.Fatal(stmt.Err())
|
||||
return 0
|
||||
return stmt.ColumnInt(0)
|
||||
}
|
||||
|
||||
insert := func(succeed bool) (err error) {
|
||||
@@ -130,14 +129,12 @@ func TestConn_Transaction_panic(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer stmt.Close()
|
||||
if stmt.Step() {
|
||||
got := stmt.ColumnInt(0)
|
||||
if got != 1 {
|
||||
t.Errorf("got %d, want 1", got)
|
||||
}
|
||||
return
|
||||
if !stmt.Step() {
|
||||
t.Fatal(stmt.Err())
|
||||
}
|
||||
if got := stmt.ColumnInt(0); got != 1 {
|
||||
t.Errorf("got %d, want 1", got)
|
||||
}
|
||||
t.Fatal(stmt.Err())
|
||||
}()
|
||||
|
||||
err = panics()
|
||||
@@ -213,15 +210,10 @@ func TestConn_Transaction_interrupt(t *testing.T) {
|
||||
}
|
||||
defer stmt.Close()
|
||||
|
||||
if stmt.Step() {
|
||||
got := stmt.ColumnInt(0)
|
||||
if got != 1 {
|
||||
t.Errorf("got %d, want 1", got)
|
||||
}
|
||||
}
|
||||
err = stmt.Err()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
if !stmt.Step() {
|
||||
t.Fatal(stmt.Err())
|
||||
} else if got := stmt.ColumnInt(0); got != 1 {
|
||||
t.Errorf("got %d, want 1", got)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -333,15 +325,10 @@ func TestConn_Transaction_rollback(t *testing.T) {
|
||||
}
|
||||
defer stmt.Close()
|
||||
|
||||
if stmt.Step() {
|
||||
got := stmt.ColumnInt(0)
|
||||
if got != 1 {
|
||||
t.Errorf("got %d, want 1", got)
|
||||
}
|
||||
}
|
||||
err = stmt.Err()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
if !stmt.Step() {
|
||||
t.Fatal(stmt.Err())
|
||||
} else if got := stmt.ColumnInt(0); got != 1 {
|
||||
t.Errorf("got %d, want 1", got)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -382,11 +369,10 @@ func TestConn_Savepoint_exec(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer stmt.Close()
|
||||
if stmt.Step() {
|
||||
return stmt.ColumnInt(0)
|
||||
if !stmt.Step() {
|
||||
t.Fatal(stmt.Err())
|
||||
}
|
||||
t.Fatal(stmt.Err())
|
||||
return 0
|
||||
return stmt.ColumnInt(0)
|
||||
}
|
||||
|
||||
insert := func(succeed bool) (err error) {
|
||||
@@ -469,14 +455,12 @@ func TestConn_Savepoint_panic(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer stmt.Close()
|
||||
if stmt.Step() {
|
||||
got := stmt.ColumnInt(0)
|
||||
if got != 1 {
|
||||
t.Errorf("got %d, want 1", got)
|
||||
}
|
||||
return
|
||||
if !stmt.Step() {
|
||||
t.Fatal(stmt.Err())
|
||||
}
|
||||
if got := stmt.ColumnInt(0); got != 1 {
|
||||
t.Errorf("got %d, want 1", got)
|
||||
}
|
||||
t.Fatal(stmt.Err())
|
||||
}()
|
||||
|
||||
err = panics()
|
||||
@@ -553,15 +537,10 @@ func TestConn_Savepoint_interrupt(t *testing.T) {
|
||||
}
|
||||
defer stmt.Close()
|
||||
|
||||
if stmt.Step() {
|
||||
got := stmt.ColumnInt(0)
|
||||
if got != 1 {
|
||||
t.Errorf("got %d, want 1", got)
|
||||
}
|
||||
}
|
||||
err = stmt.Err()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
if !stmt.Step() {
|
||||
t.Fatal(stmt.Err())
|
||||
} else if got := stmt.ColumnInt(0); got != 1 {
|
||||
t.Errorf("got %d, want 1", got)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -599,14 +578,9 @@ func TestConn_Savepoint_rollback(t *testing.T) {
|
||||
}
|
||||
defer stmt.Close()
|
||||
|
||||
if stmt.Step() {
|
||||
got := stmt.ColumnInt(0)
|
||||
if got != 1 {
|
||||
t.Errorf("got %d, want 1", got)
|
||||
}
|
||||
}
|
||||
err = stmt.Err()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
if !stmt.Step() {
|
||||
t.Fatal(stmt.Err())
|
||||
} else if got := stmt.ColumnInt(0); got != 1 {
|
||||
t.Errorf("got %d, want 1", got)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,7 +49,7 @@ func (s *SeekingReaderAt) Size() (int64, error) {
|
||||
return s.r.Seek(0, io.SeekEnd)
|
||||
}
|
||||
|
||||
// ReadAt implements [io.Closer].
|
||||
// Close implements [io.Closer].
|
||||
func (s *SeekingReaderAt) Close() error {
|
||||
s.l.Lock()
|
||||
defer s.l.Unlock()
|
||||
|
||||
@@ -23,7 +23,7 @@ func (FS) Open(name string) (fs.File, error) {
|
||||
return os.OpenFile(name, os.O_RDONLY, 0)
|
||||
}
|
||||
|
||||
// ReadFileFS implements [fs.StatFS].
|
||||
// Stat implements [fs.StatFS].
|
||||
func (FS) Stat(name string) (fs.FileInfo, error) {
|
||||
return os.Stat(name)
|
||||
}
|
||||
|
||||
@@ -17,7 +17,8 @@ trap 'rm -f sql3parse_table.tmp' EXIT
|
||||
-mmutable-globals -mnontrapping-fptoint \
|
||||
-msimd128 -mbulk-memory -msign-ext \
|
||||
-mreference-types -mmultivalue \
|
||||
-fno-stack-protector -fno-stack-clash-protection \
|
||||
-mno-extended-const \
|
||||
-fno-stack-protector \
|
||||
-Wl,--stack-first \
|
||||
-Wl,--import-undefined \
|
||||
-Wl,--export=sql3parse_table
|
||||
|
||||
Binary file not shown.
102
util/vfsutil/slice.go
Normal file
102
util/vfsutil/slice.go
Normal file
@@ -0,0 +1,102 @@
|
||||
package vfsutil
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
||||
"github.com/ncruces/go-sqlite3"
|
||||
"github.com/ncruces/go-sqlite3/vfs"
|
||||
)
|
||||
|
||||
// SliceFile implements [vfs.File] with a byte slice.
|
||||
// It is suitable for temporary files (such as [vfs.OPEN_TEMP_JOURNAL]),
|
||||
// but not concurrency safe.
|
||||
type SliceFile []byte
|
||||
|
||||
var (
|
||||
// Ensure these interfaces are implemented:
|
||||
_ vfs.FileSizeHint = &SliceFile{}
|
||||
)
|
||||
|
||||
// ReadAt implements [io.ReaderAt].
|
||||
func (f *SliceFile) ReadAt(b []byte, off int64) (n int, err error) {
|
||||
if d := *f; off < int64(len(d)) {
|
||||
n = copy(b, d[off:])
|
||||
}
|
||||
if n < len(b) {
|
||||
err = io.EOF
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// WriteAt implements [io.WriterAt].
|
||||
func (f *SliceFile) WriteAt(b []byte, off int64) (n int, err error) {
|
||||
d := *f
|
||||
if off > int64(len(d)) {
|
||||
d = append(d, make([]byte, off-int64(len(d)))...)
|
||||
}
|
||||
d = append(d[:off], b...)
|
||||
if len(d) > len(*f) {
|
||||
*f = d
|
||||
}
|
||||
return len(b), nil
|
||||
}
|
||||
|
||||
// Size implements [vfs.File].
|
||||
func (f *SliceFile) Size() (int64, error) {
|
||||
return int64(len(*f)), nil
|
||||
}
|
||||
|
||||
// Truncate implements [vfs.File].
|
||||
func (f *SliceFile) Truncate(size int64) error {
|
||||
if d := *f; size < int64(len(d)) {
|
||||
*f = d[:size]
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// SizeHint implements [vfs.FileSizeHint].
|
||||
func (f *SliceFile) SizeHint(size int64) error {
|
||||
if d := *f; size > int64(len(d)) {
|
||||
*f = append(d, make([]byte, size-int64(len(d)))...)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Close implements [io.Closer].
|
||||
func (*SliceFile) Close() error { return nil }
|
||||
|
||||
// Sync implements [vfs.File].
|
||||
func (*SliceFile) Sync(flags vfs.SyncFlag) error { return nil }
|
||||
|
||||
// Lock implements [vfs.File].
|
||||
func (*SliceFile) Lock(lock vfs.LockLevel) error {
|
||||
// notest // not concurrency safe
|
||||
return sqlite3.IOERR_LOCK
|
||||
}
|
||||
|
||||
// Unlock implements [vfs.File].
|
||||
func (*SliceFile) Unlock(lock vfs.LockLevel) error {
|
||||
// notest // not concurrency safe
|
||||
return sqlite3.IOERR_UNLOCK
|
||||
}
|
||||
|
||||
// CheckReservedLock implements [vfs.File].
|
||||
func (*SliceFile) CheckReservedLock() (bool, error) {
|
||||
// notest // not concurrency safe
|
||||
return false, sqlite3.IOERR_CHECKRESERVEDLOCK
|
||||
}
|
||||
|
||||
// SectorSize implements [vfs.File].
|
||||
func (*SliceFile) SectorSize() int {
|
||||
// notest // safe default
|
||||
return 0
|
||||
}
|
||||
|
||||
// DeviceCharacteristics implements [vfs.File].
|
||||
func (*SliceFile) DeviceCharacteristics() vfs.DeviceCharacteristic {
|
||||
return vfs.IOCAP_ATOMIC |
|
||||
vfs.IOCAP_SEQUENTIAL |
|
||||
vfs.IOCAP_SAFE_APPEND |
|
||||
vfs.IOCAP_POWERSAFE_OVERWRITE |
|
||||
vfs.IOCAP_SUBPAGE_READ
|
||||
}
|
||||
@@ -22,6 +22,14 @@ func UnwrapFile[T vfs.File](f vfs.File) (_ T, _ bool) {
|
||||
}
|
||||
}
|
||||
|
||||
// WrapOpen helps wrap [vfs.VFS].
|
||||
func WrapOpen(f vfs.VFS, name string, flags vfs.OpenFlag) (file vfs.File, _ vfs.OpenFlag, err error) {
|
||||
if f, ok := f.(vfs.VFSFilename); name == "" && ok {
|
||||
return f.OpenFilename(nil, flags)
|
||||
}
|
||||
return f.Open(name, flags)
|
||||
}
|
||||
|
||||
// WrapOpenFilename helps wrap [vfs.VFSFilename].
|
||||
func WrapOpenFilename(f vfs.VFS, name *vfs.Filename, flags vfs.OpenFlag) (file vfs.File, _ vfs.OpenFlag, err error) {
|
||||
if f, ok := f.(vfs.VFSFilename); ok {
|
||||
|
||||
330
util/vfsutil/wrap_test.go
Normal file
330
util/vfsutil/wrap_test.go
Normal file
@@ -0,0 +1,330 @@
|
||||
// Package vfsutil implements virtual filesystem utilities.
|
||||
package vfsutil
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/ncruces/go-sqlite3/vfs"
|
||||
)
|
||||
|
||||
func TestWrapOpen(t *testing.T) {
|
||||
called := 0
|
||||
|
||||
WrapOpen(mockVFS{open: func(name string, flags vfs.OpenFlag) (vfs.File, vfs.OpenFlag, error) {
|
||||
called++
|
||||
return nil, flags, nil
|
||||
}}, "", 0)
|
||||
|
||||
if called != 1 {
|
||||
t.Error("open not called")
|
||||
}
|
||||
|
||||
WrapOpenFilename(mockVFS{open: func(name string, flags vfs.OpenFlag) (vfs.File, vfs.OpenFlag, error) {
|
||||
called++
|
||||
return nil, flags, nil
|
||||
}}, nil, 0)
|
||||
|
||||
if called != 2 {
|
||||
t.Error("open not called")
|
||||
}
|
||||
}
|
||||
|
||||
func TestWrapOpenFilename(t *testing.T) {
|
||||
called := 0
|
||||
|
||||
WrapOpen(mockVFSFilename{openFilename: func(name *vfs.Filename, flags vfs.OpenFlag) (vfs.File, vfs.OpenFlag, error) {
|
||||
called++
|
||||
return nil, flags, nil
|
||||
}}, "", 0)
|
||||
|
||||
if called != 1 {
|
||||
t.Error("openFilename not called")
|
||||
}
|
||||
|
||||
WrapOpenFilename(mockVFSFilename{openFilename: func(name *vfs.Filename, flags vfs.OpenFlag) (vfs.File, vfs.OpenFlag, error) {
|
||||
called++
|
||||
return nil, flags, nil
|
||||
}}, nil, 0)
|
||||
|
||||
if called != 2 {
|
||||
t.Error("openFilename not called")
|
||||
}
|
||||
}
|
||||
|
||||
func TestWrapLockState(t *testing.T) {
|
||||
called := 0
|
||||
|
||||
WrapLockState(mockFile{lockState: func() vfs.LockLevel {
|
||||
called++
|
||||
return 0
|
||||
}})
|
||||
|
||||
if called != 1 {
|
||||
t.Error("lockState not called")
|
||||
}
|
||||
}
|
||||
|
||||
func TestWrapPersistWAL(t *testing.T) {
|
||||
persist := false
|
||||
WrapSetPersistWAL(mockFile{setPersistWAL: func(b bool) { persist = b }}, true)
|
||||
if !persist {
|
||||
t.Error("setPersistWAL not called")
|
||||
}
|
||||
|
||||
called := 0
|
||||
WrapPersistWAL(mockFile{persistWAL: func() bool { called++; return persist }})
|
||||
if !persist {
|
||||
t.Error("persistWAL not called")
|
||||
}
|
||||
if called != 1 {
|
||||
}
|
||||
}
|
||||
|
||||
func TestWrapPowersafeOverwrite(t *testing.T) {
|
||||
persist := false
|
||||
WrapSetPowersafeOverwrite(mockFile{setPowersafeOverwrite: func(b bool) { persist = b }}, true)
|
||||
if !persist {
|
||||
t.Error("setPowersafeOverwrite not called")
|
||||
}
|
||||
|
||||
called := 0
|
||||
WrapPowersafeOverwrite(mockFile{powersafeOverwrite: func() bool { called++; return persist }})
|
||||
if !persist {
|
||||
t.Error("powersafeOverwrite not called")
|
||||
}
|
||||
if called != 1 {
|
||||
}
|
||||
}
|
||||
|
||||
func TestWrapChunkSize(t *testing.T) {
|
||||
var chunk int
|
||||
|
||||
WrapChunkSize(mockFile{chunkSize: func(size int) {
|
||||
chunk = size
|
||||
}}, 5)
|
||||
|
||||
if chunk != 5 {
|
||||
t.Error("chunkSize not called")
|
||||
}
|
||||
}
|
||||
|
||||
func TestWrapSizeHint(t *testing.T) {
|
||||
var hint int64
|
||||
|
||||
WrapSizeHint(mockFile{sizeHint: func(size int64) error {
|
||||
hint = size
|
||||
return nil
|
||||
}}, 5)
|
||||
|
||||
if hint != 5 {
|
||||
t.Error("sizeHint not called")
|
||||
}
|
||||
}
|
||||
|
||||
func TestWrapHasMoved(t *testing.T) {
|
||||
called := 0
|
||||
|
||||
WrapHasMoved(mockFile{hasMoved: func() (bool, error) {
|
||||
called++
|
||||
return false, nil
|
||||
}})
|
||||
|
||||
if called != 1 {
|
||||
t.Error("hasMoved not called")
|
||||
}
|
||||
}
|
||||
|
||||
func TestWrapOverwrite(t *testing.T) {
|
||||
called := 0
|
||||
|
||||
WrapOverwrite(mockFile{overwrite: func() error {
|
||||
called++
|
||||
return nil
|
||||
}})
|
||||
|
||||
if called != 1 {
|
||||
t.Error("overwrite not called")
|
||||
}
|
||||
}
|
||||
|
||||
func TestWrapSyncSuper(t *testing.T) {
|
||||
called := 0
|
||||
|
||||
WrapSyncSuper(mockFile{syncSuper: func(super string) error {
|
||||
called++
|
||||
return nil
|
||||
}}, "")
|
||||
|
||||
if called != 1 {
|
||||
t.Error("syncSuper not called")
|
||||
}
|
||||
}
|
||||
|
||||
func TestWrapCommitPhaseTwo(t *testing.T) {
|
||||
called := 0
|
||||
|
||||
WrapCommitPhaseTwo(mockFile{commitPhaseTwo: func() error {
|
||||
called++
|
||||
return nil
|
||||
}})
|
||||
|
||||
if called != 1 {
|
||||
t.Error("commitPhaseTwo not called")
|
||||
}
|
||||
}
|
||||
|
||||
func TestWrapBatchAtomicWrite(t *testing.T) {
|
||||
calledBegin := 0
|
||||
calledCommit := 0
|
||||
calledRollback := 0
|
||||
|
||||
f := mockFile{
|
||||
begin: func() error { calledBegin++; return nil },
|
||||
commit: func() error { calledCommit++; return nil },
|
||||
rollback: func() error { calledRollback++; return nil },
|
||||
}
|
||||
WrapBeginAtomicWrite(f)
|
||||
WrapCommitAtomicWrite(f)
|
||||
WrapRollbackAtomicWrite(f)
|
||||
|
||||
if calledBegin != 1 {
|
||||
t.Error("beginAtomicWrite not called")
|
||||
}
|
||||
if calledCommit != 1 {
|
||||
t.Error("commitAtomicWrite not called")
|
||||
}
|
||||
if calledRollback != 1 {
|
||||
t.Error("rollbackAtomicWrite not called")
|
||||
}
|
||||
}
|
||||
|
||||
func TestWrapCheckpoint(t *testing.T) {
|
||||
calledStart := 0
|
||||
calledDone := 0
|
||||
|
||||
f := mockFile{
|
||||
ckptStart: func() { calledStart++ },
|
||||
ckptDone: func() { calledDone++ },
|
||||
}
|
||||
WrapCheckpointStart(f)
|
||||
WrapCheckpointDone(f)
|
||||
|
||||
if calledStart != 1 {
|
||||
t.Error("checkpointStart not called")
|
||||
}
|
||||
if calledDone != 1 {
|
||||
t.Error("checkpointDone not called")
|
||||
}
|
||||
}
|
||||
|
||||
func TestWrapPragma(t *testing.T) {
|
||||
called := 0
|
||||
|
||||
val, err := WrapPragma(mockFile{
|
||||
pragma: func(name, value string) (string, error) {
|
||||
called++
|
||||
if name != "foo" || value != "bar" {
|
||||
t.Error("wrong pragma arguments")
|
||||
}
|
||||
return "baz", nil
|
||||
},
|
||||
}, "foo", "bar")
|
||||
|
||||
if called != 1 {
|
||||
t.Error("pragma not called")
|
||||
}
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if val != "baz" {
|
||||
t.Error("unexpected pragma return value")
|
||||
}
|
||||
}
|
||||
|
||||
func TestWrapBusyHandler(t *testing.T) {
|
||||
called := 0
|
||||
|
||||
WrapBusyHandler(mockFile{
|
||||
busyHandler: func(handler func() bool) {
|
||||
handler()
|
||||
called++
|
||||
},
|
||||
}, func() bool { return true })
|
||||
|
||||
if called != 1 {
|
||||
t.Error("busyHandler not called")
|
||||
}
|
||||
}
|
||||
|
||||
type mockVFS struct {
|
||||
open func(name string, flags vfs.OpenFlag) (vfs.File, vfs.OpenFlag, error)
|
||||
}
|
||||
|
||||
func (m mockVFS) Open(name string, flags vfs.OpenFlag) (vfs.File, vfs.OpenFlag, error) {
|
||||
return m.open(name, flags)
|
||||
}
|
||||
|
||||
func (m mockVFS) Delete(name string, syncDir bool) error { panic("unimplemented") }
|
||||
func (m mockVFS) FullPathname(name string) (string, error) { panic("unimplemented") }
|
||||
func (m mockVFS) Access(name string, flags vfs.AccessFlag) (bool, error) { panic("unimplemented") }
|
||||
|
||||
type mockVFSFilename struct {
|
||||
mockVFS
|
||||
openFilename func(name *vfs.Filename, flags vfs.OpenFlag) (vfs.File, vfs.OpenFlag, error)
|
||||
}
|
||||
|
||||
func (m mockVFSFilename) OpenFilename(name *vfs.Filename, flags vfs.OpenFlag) (vfs.File, vfs.OpenFlag, error) {
|
||||
return m.openFilename(name, flags)
|
||||
}
|
||||
|
||||
type mockFile struct {
|
||||
lockState func() vfs.LockLevel
|
||||
persistWAL func() bool
|
||||
setPersistWAL func(bool)
|
||||
powersafeOverwrite func() bool
|
||||
setPowersafeOverwrite func(bool)
|
||||
chunkSize func(int)
|
||||
sizeHint func(int64) error
|
||||
hasMoved func() (bool, error)
|
||||
overwrite func() error
|
||||
syncSuper func(super string) error
|
||||
commitPhaseTwo func() error
|
||||
begin func() error
|
||||
commit func() error
|
||||
rollback func() error
|
||||
ckptStart func()
|
||||
ckptDone func()
|
||||
busyHandler func(func() bool)
|
||||
pragma func(name, value string) (string, error)
|
||||
}
|
||||
|
||||
func (m mockFile) LockState() vfs.LockLevel { return m.lockState() }
|
||||
func (m mockFile) PersistWAL() bool { return m.persistWAL() }
|
||||
func (m mockFile) SetPersistWAL(v bool) { m.setPersistWAL(v) }
|
||||
func (m mockFile) PowersafeOverwrite() bool { return m.powersafeOverwrite() }
|
||||
func (m mockFile) SetPowersafeOverwrite(v bool) { m.setPowersafeOverwrite(v) }
|
||||
func (m mockFile) ChunkSize(s int) { m.chunkSize(s) }
|
||||
func (m mockFile) SizeHint(s int64) error { return m.sizeHint(s) }
|
||||
func (m mockFile) HasMoved() (bool, error) { return m.hasMoved() }
|
||||
func (m mockFile) Overwrite() error { return m.overwrite() }
|
||||
func (m mockFile) SyncSuper(s string) error { return m.syncSuper(s) }
|
||||
func (m mockFile) CommitPhaseTwo() error { return m.commitPhaseTwo() }
|
||||
func (m mockFile) BeginAtomicWrite() error { return m.begin() }
|
||||
func (m mockFile) CommitAtomicWrite() error { return m.commit() }
|
||||
func (m mockFile) RollbackAtomicWrite() error { return m.rollback() }
|
||||
func (m mockFile) CheckpointStart() { m.ckptStart() }
|
||||
func (m mockFile) CheckpointDone() { m.ckptDone() }
|
||||
func (m mockFile) BusyHandler(f func() bool) { m.busyHandler(f) }
|
||||
func (m mockFile) Pragma(n, v string) (string, error) { return m.pragma(n, v) }
|
||||
|
||||
func (m mockFile) Close() error { panic("unimplemented") }
|
||||
func (m mockFile) ReadAt(p []byte, off int64) (n int, err error) { panic("unimplemented") }
|
||||
func (m mockFile) WriteAt(p []byte, off int64) (n int, err error) { panic("unimplemented") }
|
||||
func (m mockFile) Truncate(size int64) error { panic("unimplemented") }
|
||||
func (m mockFile) Sync(flags vfs.SyncFlag) error { panic("unimplemented") }
|
||||
func (m mockFile) Size() (int64, error) { panic("unimplemented") }
|
||||
func (m mockFile) Lock(lock vfs.LockLevel) error { panic("unimplemented") }
|
||||
func (m mockFile) Unlock(lock vfs.LockLevel) error { panic("unimplemented") }
|
||||
func (m mockFile) CheckReservedLock() (bool, error) { panic("unimplemented") }
|
||||
func (m mockFile) SectorSize() int { panic("unimplemented") }
|
||||
func (m mockFile) DeviceCharacteristics() vfs.DeviceCharacteristic { panic("unimplemented") }
|
||||
48
value.go
48
value.go
@@ -15,15 +15,6 @@ import (
|
||||
type Value struct {
|
||||
c *Conn
|
||||
handle ptr_t
|
||||
unprot bool
|
||||
copied bool
|
||||
}
|
||||
|
||||
func (v Value) protected() stk_t {
|
||||
if v.unprot {
|
||||
panic(util.ValueErr)
|
||||
}
|
||||
return stk_t(v.handle)
|
||||
}
|
||||
|
||||
// Dup makes a copy of the SQL value and returns a pointer to that copy.
|
||||
@@ -33,7 +24,6 @@ func (v Value) Dup() *Value {
|
||||
ptr := ptr_t(v.c.call("sqlite3_value_dup", stk_t(v.handle)))
|
||||
return &Value{
|
||||
c: v.c,
|
||||
copied: true,
|
||||
handle: ptr,
|
||||
}
|
||||
}
|
||||
@@ -41,12 +31,9 @@ func (v Value) Dup() *Value {
|
||||
// Close frees an SQL value previously obtained by [Value.Dup].
|
||||
//
|
||||
// https://sqlite.org/c3ref/value_dup.html
|
||||
func (dup *Value) Close() error {
|
||||
if !dup.copied {
|
||||
panic(util.ValueErr)
|
||||
}
|
||||
dup.c.call("sqlite3_value_free", stk_t(dup.handle))
|
||||
dup.handle = 0
|
||||
func (v *Value) Close() error {
|
||||
v.c.call("sqlite3_value_free", stk_t(v.handle))
|
||||
v.handle = 0
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -54,14 +41,21 @@ func (dup *Value) Close() error {
|
||||
//
|
||||
// https://sqlite.org/c3ref/value_blob.html
|
||||
func (v Value) Type() Datatype {
|
||||
return Datatype(v.c.call("sqlite3_value_type", v.protected()))
|
||||
return Datatype(v.c.call("sqlite3_value_type", stk_t(v.handle)))
|
||||
}
|
||||
|
||||
// Type returns the numeric datatype of the value.
|
||||
// Subtype returns the subtype of the value.
|
||||
//
|
||||
// https://sqlite.org/c3ref/value_subtype.html
|
||||
func (v Value) Subtype() uint {
|
||||
return uint(uint32(v.c.call("sqlite3_value_subtype", stk_t(v.handle))))
|
||||
}
|
||||
|
||||
// NumericType returns the numeric datatype of the value.
|
||||
//
|
||||
// https://sqlite.org/c3ref/value_blob.html
|
||||
func (v Value) NumericType() Datatype {
|
||||
return Datatype(v.c.call("sqlite3_value_numeric_type", v.protected()))
|
||||
return Datatype(v.c.call("sqlite3_value_numeric_type", stk_t(v.handle)))
|
||||
}
|
||||
|
||||
// Bool returns the value as a bool.
|
||||
@@ -85,14 +79,14 @@ func (v Value) Int() int {
|
||||
//
|
||||
// https://sqlite.org/c3ref/value_blob.html
|
||||
func (v Value) Int64() int64 {
|
||||
return int64(v.c.call("sqlite3_value_int64", v.protected()))
|
||||
return int64(v.c.call("sqlite3_value_int64", stk_t(v.handle)))
|
||||
}
|
||||
|
||||
// Float returns the value as a float64.
|
||||
//
|
||||
// https://sqlite.org/c3ref/value_blob.html
|
||||
func (v Value) Float() float64 {
|
||||
f := uint64(v.c.call("sqlite3_value_double", v.protected()))
|
||||
f := uint64(v.c.call("sqlite3_value_double", stk_t(v.handle)))
|
||||
return math.Float64frombits(f)
|
||||
}
|
||||
|
||||
@@ -138,7 +132,7 @@ func (v Value) Blob(buf []byte) []byte {
|
||||
//
|
||||
// https://sqlite.org/c3ref/value_blob.html
|
||||
func (v Value) RawText() []byte {
|
||||
ptr := ptr_t(v.c.call("sqlite3_value_text", v.protected()))
|
||||
ptr := ptr_t(v.c.call("sqlite3_value_text", stk_t(v.handle)))
|
||||
return v.rawBytes(ptr, 1)
|
||||
}
|
||||
|
||||
@@ -148,7 +142,7 @@ func (v Value) RawText() []byte {
|
||||
//
|
||||
// https://sqlite.org/c3ref/value_blob.html
|
||||
func (v Value) RawBlob() []byte {
|
||||
ptr := ptr_t(v.c.call("sqlite3_value_blob", v.protected()))
|
||||
ptr := ptr_t(v.c.call("sqlite3_value_blob", stk_t(v.handle)))
|
||||
return v.rawBytes(ptr, 0)
|
||||
}
|
||||
|
||||
@@ -157,14 +151,14 @@ func (v Value) rawBytes(ptr ptr_t, nul int32) []byte {
|
||||
return nil
|
||||
}
|
||||
|
||||
n := int32(v.c.call("sqlite3_value_bytes", v.protected()))
|
||||
n := int32(v.c.call("sqlite3_value_bytes", stk_t(v.handle)))
|
||||
return util.View(v.c.mod, ptr, int64(n+nul))[:n]
|
||||
}
|
||||
|
||||
// Pointer gets the pointer associated with this value,
|
||||
// or nil if it has no associated pointer.
|
||||
func (v Value) Pointer() any {
|
||||
ptr := ptr_t(v.c.call("sqlite3_value_pointer_go", v.protected()))
|
||||
ptr := ptr_t(v.c.call("sqlite3_value_pointer_go", stk_t(v.handle)))
|
||||
return util.GetHandle(v.c.ctx, ptr)
|
||||
}
|
||||
|
||||
@@ -194,7 +188,7 @@ func (v Value) JSON(ptr any) error {
|
||||
//
|
||||
// https://sqlite.org/c3ref/value_blob.html
|
||||
func (v Value) NoChange() bool {
|
||||
b := int32(v.c.call("sqlite3_value_nochange", v.protected()))
|
||||
b := int32(v.c.call("sqlite3_value_nochange", stk_t(v.handle)))
|
||||
return b != 0
|
||||
}
|
||||
|
||||
@@ -202,7 +196,7 @@ func (v Value) NoChange() bool {
|
||||
//
|
||||
// https://sqlite.org/c3ref/value_blob.html
|
||||
func (v Value) FromBind() bool {
|
||||
b := int32(v.c.call("sqlite3_value_frombind", v.protected()))
|
||||
b := int32(v.c.call("sqlite3_value_frombind", stk_t(v.handle)))
|
||||
return b != 0
|
||||
}
|
||||
|
||||
|
||||
@@ -20,7 +20,10 @@ type hbshVFS struct {
|
||||
|
||||
func (h *hbshVFS) Open(name string, flags vfs.OpenFlag) (vfs.File, vfs.OpenFlag, error) {
|
||||
// notest // OpenFilename is called instead
|
||||
return nil, 0, sqlite3.CANTOPEN
|
||||
if name == "" {
|
||||
return h.OpenFilename(nil, flags)
|
||||
}
|
||||
return nil, flags, sqlite3.CANTOPEN
|
||||
}
|
||||
|
||||
func (h *hbshVFS) OpenFilename(name *vfs.Filename, flags vfs.OpenFlag) (file vfs.File, _ vfs.OpenFlag, err error) {
|
||||
@@ -160,10 +163,11 @@ func (h *hbshFile) WriteAt(p []byte, off int64) (n int, err error) {
|
||||
if off > min || len(p[n:]) < blockSize {
|
||||
// Partial block write: read-update-write.
|
||||
m, err := h.File.ReadAt(h.block[:], min)
|
||||
if m != blockSize {
|
||||
if err != io.EOF {
|
||||
return n, err
|
||||
}
|
||||
if m == blockSize {
|
||||
data = h.hbsh.Decrypt(h.block[:], h.tweak[:])
|
||||
} else if err != io.EOF {
|
||||
return n, err
|
||||
} else {
|
||||
// Writing past the EOF.
|
||||
// We're either appending an entirely new block,
|
||||
// or the final block was only partially written.
|
||||
@@ -171,8 +175,6 @@ func (h *hbshFile) WriteAt(p []byte, off int64) (n int, err error) {
|
||||
// and is as good as corrupt.
|
||||
// Either way, zero pad the file to the next block size.
|
||||
clear(data)
|
||||
} else {
|
||||
data = h.hbsh.Decrypt(h.block[:], h.tweak[:])
|
||||
}
|
||||
if off > min {
|
||||
data = data[off-min:]
|
||||
@@ -223,16 +225,16 @@ func (h *hbshFile) SizeHint(size int64) error {
|
||||
return vfsutil.WrapSizeHint(h.File, roundUp(size))
|
||||
}
|
||||
|
||||
// Wrap optional methods.
|
||||
|
||||
func (h *hbshFile) Unwrap() vfs.File {
|
||||
return h.File
|
||||
return h.File // notest
|
||||
}
|
||||
|
||||
func (h *hbshFile) SharedMemory() vfs.SharedMemory {
|
||||
return vfsutil.WrapSharedMemory(h.File)
|
||||
return vfsutil.WrapSharedMemory(h.File) // notest
|
||||
}
|
||||
|
||||
// Wrap optional methods.
|
||||
|
||||
func (h *hbshFile) LockState() vfs.LockLevel {
|
||||
return vfsutil.WrapLockState(h.File) // notest
|
||||
}
|
||||
|
||||
@@ -36,9 +36,9 @@ type VFSFilename interface {
|
||||
//
|
||||
// https://sqlite.org/c3ref/io_methods.html
|
||||
type File interface {
|
||||
Close() error
|
||||
ReadAt(p []byte, off int64) (n int, err error)
|
||||
WriteAt(p []byte, off int64) (n int, err error)
|
||||
io.Closer
|
||||
io.ReaderAt
|
||||
io.WriterAt
|
||||
Truncate(size int64) error
|
||||
Sync(flags SyncFlag) error
|
||||
Size() (int64, error)
|
||||
|
||||
99
vfs/cksm.go
99
vfs/cksm.go
@@ -5,7 +5,6 @@ import (
|
||||
"context"
|
||||
_ "embed"
|
||||
"encoding/binary"
|
||||
"strconv"
|
||||
|
||||
"github.com/tetratelabs/wazero/api"
|
||||
|
||||
@@ -13,48 +12,30 @@ import (
|
||||
"github.com/ncruces/go-sqlite3/util/sql3util"
|
||||
)
|
||||
|
||||
func cksmWrapFile(name *Filename, flags OpenFlag, file File) File {
|
||||
// Checksum only main databases and WALs.
|
||||
if flags&(OPEN_MAIN_DB|OPEN_WAL) == 0 {
|
||||
func cksmWrapFile(file File, flags OpenFlag) File {
|
||||
// Checksum only main databases.
|
||||
if flags&OPEN_MAIN_DB == 0 {
|
||||
return file
|
||||
}
|
||||
|
||||
cksm := cksmFile{File: file}
|
||||
|
||||
if flags&OPEN_WAL != 0 {
|
||||
main, _ := name.DatabaseFile().(cksmFile)
|
||||
cksm.cksmFlags = main.cksmFlags
|
||||
} else {
|
||||
cksm.cksmFlags = new(cksmFlags)
|
||||
cksm.isDB = true
|
||||
}
|
||||
|
||||
return cksm
|
||||
return &cksmFile{File: file}
|
||||
}
|
||||
|
||||
type cksmFile struct {
|
||||
File
|
||||
*cksmFlags
|
||||
isDB bool
|
||||
}
|
||||
|
||||
type cksmFlags struct {
|
||||
computeCksm bool
|
||||
verifyCksm bool
|
||||
inCkpt bool
|
||||
pageSize int
|
||||
computeCksm bool
|
||||
}
|
||||
|
||||
func (c cksmFile) ReadAt(p []byte, off int64) (n int, err error) {
|
||||
func (c *cksmFile) ReadAt(p []byte, off int64) (n int, err error) {
|
||||
n, err = c.File.ReadAt(p, off)
|
||||
p = p[:n]
|
||||
|
||||
if isHeader(c.isDB, p, off) {
|
||||
if isHeader(p, off) {
|
||||
c.init((*[100]byte)(p))
|
||||
}
|
||||
|
||||
// Verify checksums.
|
||||
if c.verifyCksm && !c.inCkpt && len(p) == c.pageSize {
|
||||
if c.verifyCksm && sql3util.ValidPageSize(len(p)) {
|
||||
cksm1 := cksmCompute(p[:len(p)-8])
|
||||
cksm2 := *(*[8]byte)(p[len(p)-8:])
|
||||
if cksm1 != cksm2 {
|
||||
@@ -64,20 +45,20 @@ func (c cksmFile) ReadAt(p []byte, off int64) (n int, err error) {
|
||||
return n, err
|
||||
}
|
||||
|
||||
func (c cksmFile) WriteAt(p []byte, off int64) (n int, err error) {
|
||||
if isHeader(c.isDB, p, off) {
|
||||
func (c *cksmFile) WriteAt(p []byte, off int64) (n int, err error) {
|
||||
if isHeader(p, off) {
|
||||
c.init((*[100]byte)(p))
|
||||
}
|
||||
|
||||
// Compute checksums.
|
||||
if c.computeCksm && !c.inCkpt && len(p) == c.pageSize {
|
||||
if c.computeCksm && sql3util.ValidPageSize(len(p)) {
|
||||
*(*[8]byte)(p[len(p)-8:]) = cksmCompute(p[:len(p)-8])
|
||||
}
|
||||
|
||||
return c.File.WriteAt(p, off)
|
||||
}
|
||||
|
||||
func (c cksmFile) Pragma(name string, value string) (string, error) {
|
||||
func (c *cksmFile) Pragma(name string, value string) (string, error) {
|
||||
switch name {
|
||||
case "checksum_verification":
|
||||
b, ok := sql3util.ParseBool(value)
|
||||
@@ -90,15 +71,15 @@ func (c cksmFile) Pragma(name string, value string) (string, error) {
|
||||
return "1", nil
|
||||
|
||||
case "page_size":
|
||||
if c.computeCksm {
|
||||
if c.computeCksm && value != "" {
|
||||
// Do not allow page size changes on a checksum database.
|
||||
return strconv.Itoa(c.pageSize), nil
|
||||
return "", nil
|
||||
}
|
||||
}
|
||||
return "", _NOTFOUND
|
||||
}
|
||||
|
||||
func (c cksmFile) DeviceCharacteristics() DeviceCharacteristic {
|
||||
func (c *cksmFile) DeviceCharacteristics() DeviceCharacteristic {
|
||||
ret := c.File.DeviceCharacteristics()
|
||||
if c.verifyCksm {
|
||||
ret &^= IOCAP_SUBPAGE_READ
|
||||
@@ -106,13 +87,8 @@ func (c cksmFile) DeviceCharacteristics() DeviceCharacteristic {
|
||||
return ret
|
||||
}
|
||||
|
||||
func (c cksmFile) fileControl(ctx context.Context, mod api.Module, op _FcntlOpcode, pArg ptr_t) _ErrorCode {
|
||||
switch op {
|
||||
case _FCNTL_CKPT_START:
|
||||
c.inCkpt = true
|
||||
case _FCNTL_CKPT_DONE:
|
||||
c.inCkpt = false
|
||||
case _FCNTL_PRAGMA:
|
||||
func (c *cksmFile) fileControl(ctx context.Context, mod api.Module, op _FcntlOpcode, pArg ptr_t) _ErrorCode {
|
||||
if op == _FCNTL_PRAGMA {
|
||||
rc := vfsFileControlImpl(ctx, mod, c, op, pArg)
|
||||
if rc != _NOTFOUND {
|
||||
return rc
|
||||
@@ -121,24 +97,26 @@ func (c cksmFile) fileControl(ctx context.Context, mod api.Module, op _FcntlOpco
|
||||
return vfsFileControlImpl(ctx, mod, c.File, op, pArg)
|
||||
}
|
||||
|
||||
func (f *cksmFlags) init(header *[100]byte) {
|
||||
f.pageSize = 256 * int(binary.LittleEndian.Uint16(header[16:18]))
|
||||
if r := header[20] == 8; r != f.computeCksm {
|
||||
f.computeCksm = r
|
||||
f.verifyCksm = r
|
||||
}
|
||||
if !sql3util.ValidPageSize(f.pageSize) {
|
||||
f.computeCksm = false
|
||||
f.verifyCksm = false
|
||||
func (c *cksmFile) init(header *[100]byte) {
|
||||
if r := header[20] == 8; r != c.computeCksm {
|
||||
c.computeCksm = r
|
||||
c.verifyCksm = r
|
||||
}
|
||||
}
|
||||
|
||||
func isHeader(isDB bool, p []byte, off int64) bool {
|
||||
check := sql3util.ValidPageSize(len(p))
|
||||
if isDB {
|
||||
check = off == 0 && len(p) >= 100
|
||||
func (c *cksmFile) SharedMemory() SharedMemory {
|
||||
if f, ok := c.File.(FileSharedMemory); ok {
|
||||
return f.SharedMemory()
|
||||
}
|
||||
return check && bytes.HasPrefix(p, []byte("SQLite format 3\000"))
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *cksmFile) Unwrap() File {
|
||||
return c.File
|
||||
}
|
||||
|
||||
func isHeader(p []byte, off int64) bool {
|
||||
return off == 0 && len(p) >= 100 && bytes.HasPrefix(p, []byte("SQLite format 3\000"))
|
||||
}
|
||||
|
||||
func cksmCompute(a []byte) (cksm [8]byte) {
|
||||
@@ -155,14 +133,3 @@ func cksmCompute(a []byte) (cksm [8]byte) {
|
||||
binary.LittleEndian.PutUint32(cksm[4:8], s2)
|
||||
return
|
||||
}
|
||||
|
||||
func (c cksmFile) SharedMemory() SharedMemory {
|
||||
if f, ok := c.File.(FileSharedMemory); ok {
|
||||
return f.SharedMemory()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c cksmFile) Unwrap() File {
|
||||
return c.File
|
||||
}
|
||||
|
||||
@@ -75,6 +75,9 @@ func (vfsOS) Access(name string, flags AccessFlag) (bool, error) {
|
||||
|
||||
func (vfsOS) Open(name string, flags OpenFlag) (File, OpenFlag, error) {
|
||||
// notest // OpenFilename is called instead
|
||||
if name == "" {
|
||||
return vfsOS{}.OpenFilename(nil, flags)
|
||||
}
|
||||
return nil, 0, _CANTOPEN
|
||||
}
|
||||
|
||||
|
||||
@@ -56,7 +56,7 @@ func (n *Filename) Journal() string {
|
||||
return n.path("sqlite3_filename_journal")
|
||||
}
|
||||
|
||||
// Journal returns the name of the corresponding WAL file.
|
||||
// WAL returns the name of the corresponding WAL file.
|
||||
//
|
||||
// https://sqlite.org/c3ref/filename_database.html
|
||||
func (n *Filename) WAL() string {
|
||||
|
||||
@@ -2,41 +2,39 @@ package memdb
|
||||
|
||||
import (
|
||||
"io"
|
||||
"runtime"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/ncruces/go-sqlite3"
|
||||
"github.com/ncruces/go-sqlite3/util/vfsutil"
|
||||
"github.com/ncruces/go-sqlite3/vfs"
|
||||
)
|
||||
|
||||
const sectorSize = 65536
|
||||
|
||||
// Ensure sectorSize is a multiple of 64K (the largest page size).
|
||||
var _ [0]struct{} = [sectorSize & 65535]struct{}{}
|
||||
|
||||
type memVFS struct{}
|
||||
|
||||
func (memVFS) Open(name string, flags vfs.OpenFlag) (vfs.File, vfs.OpenFlag, error) {
|
||||
// For simplicity, we do not support reading or writing data
|
||||
// across "sector" boundaries.
|
||||
//
|
||||
// This is not a problem for most SQLite file types:
|
||||
// - databases, which only do page aligned reads/writes;
|
||||
// - temp journals, as used by the sorter, which does the same:
|
||||
// https://github.com/sqlite/sqlite/blob/b74eb0/src/vdbesort.c#L409-L412
|
||||
//
|
||||
// We refuse to open all other file types,
|
||||
// but returning OPEN_MEMORY means SQLite won't ask us to.
|
||||
const types = vfs.OPEN_MAIN_DB | vfs.OPEN_TEMP_DB |
|
||||
vfs.OPEN_TRANSIENT_DB | vfs.OPEN_TEMP_JOURNAL
|
||||
if flags&types == 0 {
|
||||
// This is not a problem for SQLite database files.
|
||||
const databases = vfs.OPEN_MAIN_DB | vfs.OPEN_TEMP_DB | vfs.OPEN_TRANSIENT_DB
|
||||
|
||||
// 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.
|
||||
// Returning OPEN_MEMORY means SQLite won't ask us to.
|
||||
if flags&databases == 0 {
|
||||
// notest // OPEN_MEMORY
|
||||
return nil, flags, sqlite3.CANTOPEN
|
||||
}
|
||||
|
||||
// A shared database has a name that begins with "/".
|
||||
shared := len(name) > 1 && name[0] == '/'
|
||||
shared := strings.HasPrefix(name, "/")
|
||||
|
||||
var db *memDB
|
||||
if shared {
|
||||
@@ -77,14 +75,13 @@ func (memVFS) FullPathname(name string) (string, error) {
|
||||
type memDB struct {
|
||||
name string
|
||||
|
||||
// +checklocks:lockMtx
|
||||
waiter *sync.Cond
|
||||
// +checklocks:dataMtx
|
||||
data []*[sectorSize]byte
|
||||
// +checklocks:dataMtx
|
||||
size int64
|
||||
|
||||
// +checklocks:memoryMtx
|
||||
refs int32
|
||||
|
||||
size int64 // +checklocks:dataMtx
|
||||
refs int32 // +checklocks:memoryMtx
|
||||
shared int32 // +checklocks:lockMtx
|
||||
pending bool // +checklocks:lockMtx
|
||||
reserved bool // +checklocks:lockMtx
|
||||
@@ -129,7 +126,7 @@ func (m *memFile) ReadAt(b []byte, off int64) (n int, err error) {
|
||||
base := off / sectorSize
|
||||
rest := off % sectorSize
|
||||
have := int64(sectorSize)
|
||||
if base == int64(len(m.data))-1 {
|
||||
if m.size < off+int64(len(b)) {
|
||||
have = modRoundUp(m.size, sectorSize)
|
||||
}
|
||||
n = copy(b, (*m.data[base])[rest:have])
|
||||
@@ -150,22 +147,37 @@ func (m *memFile) WriteAt(b []byte, off int64) (n int, err error) {
|
||||
m.data = append(m.data, new([sectorSize]byte))
|
||||
}
|
||||
n = copy((*m.data[base])[rest:], b)
|
||||
if size := off + int64(n); size > m.size {
|
||||
m.size = size
|
||||
}
|
||||
if n < len(b) {
|
||||
// notest // assume writes are page aligned
|
||||
return n, io.ErrShortWrite
|
||||
}
|
||||
if size := off + int64(len(b)); size > m.size {
|
||||
m.size = size
|
||||
}
|
||||
return n, nil
|
||||
}
|
||||
|
||||
func (m *memFile) Size() (int64, error) {
|
||||
m.dataMtx.RLock()
|
||||
defer m.dataMtx.RUnlock()
|
||||
return m.size, nil
|
||||
}
|
||||
|
||||
func (m *memFile) Truncate(size int64) error {
|
||||
m.dataMtx.Lock()
|
||||
defer m.dataMtx.Unlock()
|
||||
return m.truncate(size)
|
||||
}
|
||||
|
||||
func (m *memFile) SizeHint(size int64) error {
|
||||
m.dataMtx.Lock()
|
||||
defer m.dataMtx.Unlock()
|
||||
if size > m.size {
|
||||
return m.truncate(size)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// +checklocks:m.dataMtx
|
||||
func (m *memFile) truncate(size int64) error {
|
||||
if size < m.size {
|
||||
@@ -185,18 +197,6 @@ func (m *memFile) truncate(size int64) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *memFile) Sync(flag vfs.SyncFlag) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *memFile) Size() (int64, error) {
|
||||
m.dataMtx.RLock()
|
||||
defer m.dataMtx.RUnlock()
|
||||
return m.size, nil
|
||||
}
|
||||
|
||||
const spinWait = 25 * time.Microsecond
|
||||
|
||||
func (m *memFile) Lock(lock vfs.LockLevel) error {
|
||||
if m.lock >= lock {
|
||||
return nil
|
||||
@@ -228,13 +228,18 @@ func (m *memFile) Lock(lock vfs.LockLevel) error {
|
||||
m.pending = true
|
||||
}
|
||||
|
||||
for before := time.Now(); m.shared > 1; {
|
||||
if time.Since(before) > spinWait {
|
||||
return sqlite3.BUSY
|
||||
if m.shared > 1 {
|
||||
before := time.Now()
|
||||
if m.waiter == nil {
|
||||
m.waiter = sync.NewCond(&m.lockMtx)
|
||||
}
|
||||
defer time.AfterFunc(time.Millisecond, m.waiter.Broadcast).Stop()
|
||||
for m.shared > 1 {
|
||||
if time.Since(before) > time.Millisecond {
|
||||
return sqlite3.BUSY
|
||||
}
|
||||
m.waiter.Wait()
|
||||
}
|
||||
m.lockMtx.Unlock()
|
||||
runtime.Gosched()
|
||||
m.lockMtx.Lock()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -257,7 +262,9 @@ func (m *memFile) Unlock(lock vfs.LockLevel) error {
|
||||
m.pending = false
|
||||
}
|
||||
if lock < vfs.LOCK_SHARED {
|
||||
m.shared--
|
||||
if m.shared--; m.pending && m.shared <= 1 && m.waiter != nil {
|
||||
m.waiter.Broadcast()
|
||||
}
|
||||
}
|
||||
m.lock = lock
|
||||
return nil
|
||||
@@ -273,31 +280,24 @@ func (m *memFile) CheckReservedLock() (bool, error) {
|
||||
return m.reserved, nil
|
||||
}
|
||||
|
||||
func (m *memFile) SectorSize() int {
|
||||
func (m *memFile) LockState() vfs.LockLevel {
|
||||
return m.lock
|
||||
}
|
||||
|
||||
func (*memFile) Sync(flag vfs.SyncFlag) error { return nil }
|
||||
|
||||
func (*memFile) SectorSize() int {
|
||||
// notest // IOCAP_POWERSAFE_OVERWRITE
|
||||
return sectorSize
|
||||
}
|
||||
|
||||
func (m *memFile) DeviceCharacteristics() vfs.DeviceCharacteristic {
|
||||
func (*memFile) DeviceCharacteristics() vfs.DeviceCharacteristic {
|
||||
return vfs.IOCAP_ATOMIC |
|
||||
vfs.IOCAP_SEQUENTIAL |
|
||||
vfs.IOCAP_SAFE_APPEND |
|
||||
vfs.IOCAP_POWERSAFE_OVERWRITE
|
||||
}
|
||||
|
||||
func (m *memFile) SizeHint(size int64) error {
|
||||
m.dataMtx.Lock()
|
||||
defer m.dataMtx.Unlock()
|
||||
if size > m.size {
|
||||
return m.truncate(size)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *memFile) LockState() vfs.LockLevel {
|
||||
return m.lock
|
||||
}
|
||||
|
||||
func divRoundUp(a, b int64) int64 {
|
||||
return (a + b - 1) / b
|
||||
}
|
||||
|
||||
9
vfs/mvcc/README.md
Normal file
9
vfs/mvcc/README.md
Normal file
@@ -0,0 +1,9 @@
|
||||
# Go `mvcc` SQLite VFS
|
||||
|
||||
This package implements the **EXPERIMENTAL** `"mvcc"` in-memory SQLite VFS.
|
||||
|
||||
It has some benefits over the [`"memdb"`](../memdb/README.md) VFS:
|
||||
- panics do not corrupt a shared database;
|
||||
- single-writer not blocked by readers,
|
||||
- readers never block,
|
||||
- instant snapshots.
|
||||
67
vfs/mvcc/api.go
Normal file
67
vfs/mvcc/api.go
Normal file
@@ -0,0 +1,67 @@
|
||||
// Package mvcc implements the "mvcc" SQLite VFS.
|
||||
//
|
||||
// The "mvcc" [vfs.VFS] allows the same in-memory database to be shared
|
||||
// among multiple database connections in the same process,
|
||||
// as long as the database name begins with "/".
|
||||
//
|
||||
// Importing package mvcc registers the VFS:
|
||||
//
|
||||
// import _ "github.com/ncruces/go-sqlite3/vfs/mvcc"
|
||||
package mvcc
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/ncruces/go-sqlite3/vfs"
|
||||
)
|
||||
|
||||
func init() {
|
||||
vfs.Register("mvcc", mvccVFS{})
|
||||
}
|
||||
|
||||
var (
|
||||
memoryMtx sync.Mutex
|
||||
// +checklocks:memoryMtx
|
||||
memoryDBs = map[string]*mvccDB{}
|
||||
)
|
||||
|
||||
// Create creates a shared memory database,
|
||||
// using data as its initial contents.
|
||||
func Create(name string, data string) {
|
||||
memoryMtx.Lock()
|
||||
defer memoryMtx.Unlock()
|
||||
|
||||
db := &mvccDB{
|
||||
refs: 1,
|
||||
name: name,
|
||||
}
|
||||
memoryDBs[name] = db
|
||||
if len(data) == 0 {
|
||||
return
|
||||
}
|
||||
// Convert data from WAL/2 to rollback journal.
|
||||
if len(data) >= 20 && (false ||
|
||||
data[18] == 2 && data[19] == 2 ||
|
||||
data[18] == 3 && data[19] == 3) {
|
||||
db.data = db.data.
|
||||
Put(0, data[:18]).
|
||||
Put(18, "\001\001").
|
||||
Put(20, data[20:])
|
||||
} else {
|
||||
db.data = db.data.Put(0, data)
|
||||
}
|
||||
}
|
||||
|
||||
// Delete deletes a shared memory database.
|
||||
func Delete(name string) {
|
||||
memoryMtx.Lock()
|
||||
defer memoryMtx.Unlock()
|
||||
delete(memoryDBs, name)
|
||||
}
|
||||
|
||||
// Snapshot stores a snapshot of database src into dst.
|
||||
func Snapshot(dst, src string) {
|
||||
memoryMtx.Lock()
|
||||
defer memoryMtx.Unlock()
|
||||
memoryDBs[dst] = memoryDBs[src].fork()
|
||||
}
|
||||
50
vfs/mvcc/example_test.go
Normal file
50
vfs/mvcc/example_test.go
Normal file
@@ -0,0 +1,50 @@
|
||||
package mvcc_test
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
_ "embed"
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
_ "github.com/ncruces/go-sqlite3/driver"
|
||||
_ "github.com/ncruces/go-sqlite3/embed"
|
||||
"github.com/ncruces/go-sqlite3/vfs/mvcc"
|
||||
)
|
||||
|
||||
//go:embed testdata/test.db
|
||||
var testDB string
|
||||
|
||||
func Example() {
|
||||
mvcc.Create("test.db", testDB)
|
||||
|
||||
db, err := sql.Open("sqlite3", "file:/test.db?vfs=mvcc")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
_, err = db.Exec(`INSERT INTO users (id, name) VALUES (3, 'rust')`)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
rows, err := db.Query(`SELECT id, name FROM users`)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
for rows.Next() {
|
||||
var id, name string
|
||||
err = rows.Scan(&id, &name)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
fmt.Printf("%s %s\n", id, name)
|
||||
}
|
||||
// Output:
|
||||
// 0 go
|
||||
// 1 zig
|
||||
// 2 whatever
|
||||
// 3 rust
|
||||
}
|
||||
333
vfs/mvcc/mvcc.go
Normal file
333
vfs/mvcc/mvcc.go
Normal file
@@ -0,0 +1,333 @@
|
||||
package mvcc
|
||||
|
||||
import (
|
||||
"io"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/ncruces/aa"
|
||||
"github.com/ncruces/go-sqlite3"
|
||||
"github.com/ncruces/go-sqlite3/util/vfsutil"
|
||||
"github.com/ncruces/go-sqlite3/vfs"
|
||||
)
|
||||
|
||||
type mvccVFS struct{}
|
||||
|
||||
func (mvccVFS) Open(name string, flags vfs.OpenFlag) (vfs.File, vfs.OpenFlag, error) {
|
||||
// Temporary files use SliceFile.
|
||||
if name == "" || flags&vfs.OPEN_DELETEONCLOSE != 0 {
|
||||
return &vfsutil.SliceFile{}, flags | vfs.OPEN_MEMORY, nil
|
||||
}
|
||||
|
||||
// Only main databases benefit from multiversion concurrency control.
|
||||
// Refuse to open all other file types.
|
||||
// Returning OPEN_MEMORY means SQLite won't ask us to.
|
||||
if flags&vfs.OPEN_MAIN_DB == 0 {
|
||||
// notest // OPEN_MEMORY
|
||||
return nil, flags, sqlite3.CANTOPEN
|
||||
}
|
||||
|
||||
// A shared database has a name that begins with "/".
|
||||
shared := strings.HasPrefix(name, "/")
|
||||
|
||||
var db *mvccDB
|
||||
if shared {
|
||||
name = name[1:]
|
||||
memoryMtx.Lock()
|
||||
defer memoryMtx.Unlock()
|
||||
db = memoryDBs[name]
|
||||
}
|
||||
if db == nil {
|
||||
if flags&vfs.OPEN_CREATE == 0 {
|
||||
return nil, flags, sqlite3.CANTOPEN
|
||||
}
|
||||
db = &mvccDB{name: name}
|
||||
}
|
||||
if shared {
|
||||
db.refs++ // +checklocksforce: memoryMtx is held
|
||||
memoryDBs[name] = db
|
||||
}
|
||||
|
||||
return &mvccFile{
|
||||
mvccDB: db,
|
||||
readOnly: flags&vfs.OPEN_READONLY != 0,
|
||||
}, flags | vfs.OPEN_MEMORY, nil
|
||||
}
|
||||
|
||||
func (mvccVFS) Delete(name string, dirSync bool) error {
|
||||
return sqlite3.IOERR_DELETE_NOENT // used to delete journals
|
||||
}
|
||||
|
||||
func (mvccVFS) Access(name string, flag vfs.AccessFlag) (bool, error) {
|
||||
return false, nil // used to check for journals
|
||||
}
|
||||
|
||||
func (mvccVFS) FullPathname(name string) (string, error) {
|
||||
return name, nil
|
||||
}
|
||||
|
||||
type mvccDB struct {
|
||||
data *aa.Tree[int64, string] // +checklocks:mtx
|
||||
owner *mvccFile // +checklocks:mtx
|
||||
waiter *sync.Cond // +checklocks:mtx
|
||||
|
||||
name string
|
||||
refs int // +checklocks:memoryMtx
|
||||
mtx sync.Mutex
|
||||
}
|
||||
|
||||
func (m *mvccDB) release() {
|
||||
memoryMtx.Lock()
|
||||
defer memoryMtx.Unlock()
|
||||
if m.refs--; m.refs == 0 && m == memoryDBs[m.name] {
|
||||
delete(memoryDBs, m.name)
|
||||
}
|
||||
}
|
||||
|
||||
func (m *mvccDB) fork() *mvccDB {
|
||||
m.mtx.Lock()
|
||||
defer m.mtx.Unlock()
|
||||
return &mvccDB{
|
||||
refs: 1,
|
||||
name: m.name,
|
||||
data: m.data,
|
||||
}
|
||||
}
|
||||
|
||||
type mvccFile struct {
|
||||
*mvccDB
|
||||
data *aa.Tree[int64, string]
|
||||
lock vfs.LockLevel
|
||||
readOnly bool
|
||||
}
|
||||
|
||||
var (
|
||||
// Ensure these interfaces are implemented:
|
||||
_ vfs.FileLockState = &mvccFile{}
|
||||
_ vfs.FileCommitPhaseTwo = &mvccFile{}
|
||||
)
|
||||
|
||||
func (m *mvccFile) Close() error {
|
||||
// Relase ownership, discard changes.
|
||||
m.release()
|
||||
m.data = nil
|
||||
m.lock = vfs.LOCK_NONE
|
||||
m.mtx.Lock()
|
||||
defer m.mtx.Unlock()
|
||||
if m.owner == m {
|
||||
m.owner = nil
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *mvccFile) ReadAt(b []byte, off int64) (n int, err error) {
|
||||
// If unlocked, use a snapshot of the database.
|
||||
data := m.data
|
||||
if m.lock == vfs.LOCK_NONE {
|
||||
m.mtx.Lock()
|
||||
defer m.mtx.Unlock()
|
||||
data = m.mvccDB.data
|
||||
}
|
||||
|
||||
for k, v := range data.AscendFloor(off) {
|
||||
if i := k - off; i >= 0 {
|
||||
if +i > int64(n) {
|
||||
// Missing data.
|
||||
clear(b[n:])
|
||||
}
|
||||
if +i < int64(len(b)) {
|
||||
// Copy prefix.
|
||||
n = copy(b[+i:], v) + int(i)
|
||||
}
|
||||
} else {
|
||||
if -i < int64(len(v)) {
|
||||
// Copy suffix.
|
||||
n = copy(b, v[-i:])
|
||||
}
|
||||
}
|
||||
if n >= len(b) {
|
||||
return n, nil
|
||||
}
|
||||
}
|
||||
return n, io.EOF
|
||||
}
|
||||
|
||||
func (m *mvccFile) WriteAt(b []byte, off int64) (n int, err error) {
|
||||
// If unlocked, take a snapshot of the database.
|
||||
data := m.data
|
||||
if m.lock == vfs.LOCK_NONE {
|
||||
m.mtx.Lock()
|
||||
defer m.mtx.Unlock()
|
||||
data = m.mvccDB.data
|
||||
m.lock = vfs.LOCK_EXCLUSIVE + 1 // UNKNOWN_LOCK
|
||||
}
|
||||
|
||||
next := off + int64(len(b))
|
||||
for k, v := range data.AscendFloor(off) {
|
||||
if k >= next {
|
||||
break
|
||||
}
|
||||
switch {
|
||||
case k > off:
|
||||
// Delete overlap.
|
||||
data = data.Delete(k)
|
||||
case k < off && off < k+int64(len(v)):
|
||||
// Reinsert prefix.
|
||||
data = data.Put(k, v[:off-k])
|
||||
}
|
||||
if k+int64(len(v)) > next {
|
||||
// Reinsert suffix.
|
||||
data = data.Put(next, v[next-k:])
|
||||
}
|
||||
}
|
||||
|
||||
m.data = data.Put(off, string(b))
|
||||
return len(b), nil
|
||||
}
|
||||
|
||||
func (m *mvccFile) Size() (int64, error) {
|
||||
// If unlocked, use a snapshot of the database.
|
||||
data := m.data
|
||||
if m.lock == vfs.LOCK_NONE {
|
||||
m.mtx.Lock()
|
||||
defer m.mtx.Unlock()
|
||||
data = m.mvccDB.data
|
||||
}
|
||||
|
||||
if data == nil {
|
||||
return 0, nil
|
||||
}
|
||||
data = data.Max()
|
||||
return data.Key() + int64(len(data.Value())), nil
|
||||
}
|
||||
|
||||
func (m *mvccFile) Truncate(size int64) error {
|
||||
// If unlocked, take a snapshot of the database.
|
||||
data := m.data
|
||||
if m.lock == vfs.LOCK_NONE {
|
||||
m.mtx.Lock()
|
||||
defer m.mtx.Unlock()
|
||||
data = m.mvccDB.data
|
||||
m.lock = vfs.LOCK_EXCLUSIVE + 1 // UNKNOWN_LOCK
|
||||
}
|
||||
|
||||
for data != nil && data.Key() >= size {
|
||||
data = data.Left()
|
||||
}
|
||||
for k := range data.AscendCeil(size) {
|
||||
data = data.Delete(k)
|
||||
}
|
||||
m.data = data
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *mvccFile) Lock(lock vfs.LockLevel) error {
|
||||
if m.lock >= lock {
|
||||
return nil
|
||||
}
|
||||
|
||||
if m.readOnly && lock >= vfs.LOCK_RESERVED {
|
||||
return sqlite3.IOERR_LOCK
|
||||
}
|
||||
|
||||
m.mtx.Lock()
|
||||
defer m.mtx.Unlock()
|
||||
|
||||
// Take a snapshot of the database.
|
||||
if lock == vfs.LOCK_SHARED {
|
||||
m.data = m.mvccDB.data
|
||||
m.lock = lock
|
||||
return nil
|
||||
}
|
||||
// We are the owners.
|
||||
if m.owner == m {
|
||||
m.lock = lock
|
||||
return nil
|
||||
}
|
||||
// Someone else is the owner.
|
||||
if m.owner != nil {
|
||||
before := time.Now()
|
||||
if m.waiter == nil {
|
||||
m.waiter = sync.NewCond(&m.mtx)
|
||||
}
|
||||
defer time.AfterFunc(time.Millisecond, m.waiter.Broadcast).Stop()
|
||||
for m.owner != nil {
|
||||
// Our snapshot is invalid.
|
||||
if m.data != m.mvccDB.data {
|
||||
return sqlite3.BUSY_SNAPSHOT
|
||||
}
|
||||
if time.Since(before) > time.Millisecond {
|
||||
return sqlite3.BUSY
|
||||
}
|
||||
m.waiter.Wait()
|
||||
}
|
||||
}
|
||||
// Our snapshot is invalid.
|
||||
if m.data != m.mvccDB.data {
|
||||
return sqlite3.BUSY_SNAPSHOT
|
||||
}
|
||||
// Take ownership.
|
||||
m.lock = lock
|
||||
m.owner = m
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *mvccFile) Unlock(lock vfs.LockLevel) error {
|
||||
if m.lock <= lock {
|
||||
return nil
|
||||
}
|
||||
|
||||
m.mtx.Lock()
|
||||
defer m.mtx.Unlock()
|
||||
|
||||
// Relase ownership, commit changes.
|
||||
if m.owner == m {
|
||||
m.owner = nil
|
||||
m.mvccDB.data = m.data
|
||||
if m.waiter != nil {
|
||||
m.waiter.Broadcast()
|
||||
}
|
||||
}
|
||||
m.lock = lock
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *mvccFile) CheckReservedLock() (bool, error) {
|
||||
// notest // OPEN_MEMORY
|
||||
if m.lock >= vfs.LOCK_RESERVED {
|
||||
return true, nil
|
||||
}
|
||||
m.mtx.Lock()
|
||||
defer m.mtx.Unlock()
|
||||
return m.owner != nil, nil
|
||||
}
|
||||
|
||||
func (m *mvccFile) CommitPhaseTwo() error {
|
||||
// Modified without lock, commit changes.
|
||||
if m.lock > vfs.LOCK_EXCLUSIVE {
|
||||
m.mtx.Lock()
|
||||
defer m.mtx.Unlock()
|
||||
m.mvccDB.data = m.data
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *mvccFile) LockState() vfs.LockLevel {
|
||||
return m.lock
|
||||
}
|
||||
|
||||
func (*mvccFile) Sync(flag vfs.SyncFlag) error { return nil }
|
||||
|
||||
func (*mvccFile) SectorSize() int {
|
||||
// notest // safe default
|
||||
return 0
|
||||
}
|
||||
|
||||
func (*mvccFile) DeviceCharacteristics() vfs.DeviceCharacteristic {
|
||||
return vfs.IOCAP_ATOMIC |
|
||||
vfs.IOCAP_SEQUENTIAL |
|
||||
vfs.IOCAP_SAFE_APPEND |
|
||||
vfs.IOCAP_POWERSAFE_OVERWRITE |
|
||||
vfs.IOCAP_SUBPAGE_READ
|
||||
}
|
||||
30
vfs/mvcc/mvcc_test.go
Normal file
30
vfs/mvcc/mvcc_test.go
Normal file
@@ -0,0 +1,30 @@
|
||||
package mvcc
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
"testing"
|
||||
|
||||
"github.com/ncruces/go-sqlite3"
|
||||
_ "github.com/ncruces/go-sqlite3/embed"
|
||||
_ "github.com/ncruces/go-sqlite3/internal/testcfg"
|
||||
)
|
||||
|
||||
//go:embed testdata/wal.db
|
||||
var walDB string
|
||||
|
||||
func Test_wal(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
Create("test.db", walDB)
|
||||
|
||||
db, err := sqlite3.Open("file:/test.db?vfs=mvcc")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
err = db.Exec(`CREATE TABLE users (id INT, name VARCHAR(10))`)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
BIN
vfs/mvcc/testdata/test.db
vendored
Normal file
BIN
vfs/mvcc/testdata/test.db
vendored
Normal file
Binary file not shown.
BIN
vfs/mvcc/testdata/wal.db
vendored
Normal file
BIN
vfs/mvcc/testdata/wal.db
vendored
Normal file
Binary file not shown.
@@ -3,14 +3,19 @@ package readervfs
|
||||
import (
|
||||
"github.com/ncruces/go-sqlite3"
|
||||
"github.com/ncruces/go-sqlite3/util/ioutil"
|
||||
"github.com/ncruces/go-sqlite3/util/vfsutil"
|
||||
"github.com/ncruces/go-sqlite3/vfs"
|
||||
)
|
||||
|
||||
type readerVFS struct{}
|
||||
|
||||
func (readerVFS) Open(name string, flags vfs.OpenFlag) (vfs.File, vfs.OpenFlag, error) {
|
||||
// Temp journals, as used by the sorter, use SliceFile.
|
||||
if flags&vfs.OPEN_TEMP_JOURNAL != 0 {
|
||||
return &vfsutil.SliceFile{}, flags | vfs.OPEN_MEMORY, nil
|
||||
}
|
||||
// Refuse to open all other file types.
|
||||
if flags&vfs.OPEN_MAIN_DB == 0 {
|
||||
// notest
|
||||
return nil, flags, sqlite3.CANTOPEN
|
||||
}
|
||||
readerMtx.RLock()
|
||||
@@ -22,13 +27,13 @@ func (readerVFS) Open(name string, flags vfs.OpenFlag) (vfs.File, vfs.OpenFlag,
|
||||
}
|
||||
|
||||
func (readerVFS) Delete(name string, dirSync bool) error {
|
||||
// notest
|
||||
// notest // IOCAP_IMMUTABLE
|
||||
return sqlite3.IOERR_DELETE
|
||||
}
|
||||
|
||||
func (readerVFS) Access(name string, flag vfs.AccessFlag) (bool, error) {
|
||||
// notest
|
||||
return false, nil
|
||||
// notest // IOCAP_IMMUTABLE
|
||||
return false, sqlite3.IOERR_ACCESS
|
||||
}
|
||||
|
||||
func (readerVFS) FullPathname(name string) (string, error) {
|
||||
@@ -42,37 +47,37 @@ func (readerFile) Close() error {
|
||||
}
|
||||
|
||||
func (readerFile) WriteAt(b []byte, off int64) (n int, err error) {
|
||||
// notest
|
||||
return 0, sqlite3.READONLY
|
||||
// notest // IOCAP_IMMUTABLE
|
||||
return 0, sqlite3.IOERR_WRITE
|
||||
}
|
||||
|
||||
func (readerFile) Truncate(size int64) error {
|
||||
// notest
|
||||
return sqlite3.READONLY
|
||||
// notest // IOCAP_IMMUTABLE
|
||||
return sqlite3.IOERR_TRUNCATE
|
||||
}
|
||||
|
||||
func (readerFile) Sync(flag vfs.SyncFlag) error {
|
||||
// notest
|
||||
return nil
|
||||
// notest // IOCAP_IMMUTABLE
|
||||
return sqlite3.IOERR_FSYNC
|
||||
}
|
||||
|
||||
func (readerFile) Lock(lock vfs.LockLevel) error {
|
||||
// notest
|
||||
return nil
|
||||
// notest // IOCAP_IMMUTABLE
|
||||
return sqlite3.IOERR_LOCK
|
||||
}
|
||||
|
||||
func (readerFile) Unlock(lock vfs.LockLevel) error {
|
||||
// notest
|
||||
return nil
|
||||
// notest // IOCAP_IMMUTABLE
|
||||
return sqlite3.IOERR_UNLOCK
|
||||
}
|
||||
|
||||
func (readerFile) CheckReservedLock() (bool, error) {
|
||||
// notest
|
||||
return false, nil
|
||||
// notest // IOCAP_IMMUTABLE
|
||||
return false, sqlite3.IOERR_CHECKRESERVEDLOCK
|
||||
}
|
||||
|
||||
func (readerFile) SectorSize() int {
|
||||
// notest
|
||||
// notest // IOCAP_IMMUTABLE
|
||||
return 0
|
||||
}
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@ import (
|
||||
"github.com/ncruces/go-sqlite3/vfs"
|
||||
_ "github.com/ncruces/go-sqlite3/vfs/adiantum"
|
||||
"github.com/ncruces/go-sqlite3/vfs/memdb"
|
||||
"github.com/ncruces/go-sqlite3/vfs/mvcc"
|
||||
_ "github.com/ncruces/go-sqlite3/vfs/xts"
|
||||
)
|
||||
|
||||
@@ -195,6 +196,50 @@ func Test_multiwrite01_memory(t *testing.T) {
|
||||
mod.Close(ctx)
|
||||
}
|
||||
|
||||
func Test_config01_mvcc(t *testing.T) {
|
||||
mvcc.Create("test.db", "")
|
||||
ctx := util.NewContext(newContext(t))
|
||||
cfg := config(ctx).WithArgs("mptest", "/test.db", "config01.test",
|
||||
"--vfs", "mvcc")
|
||||
mod, err := rt.InstantiateModule(ctx, module, cfg)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
mod.Close(ctx)
|
||||
}
|
||||
|
||||
func Test_crash01_mvcc(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("skipping in short mode")
|
||||
}
|
||||
|
||||
mvcc.Create("test.db", "")
|
||||
ctx := util.NewContext(newContext(t))
|
||||
cfg := config(ctx).WithArgs("mptest", "/test.db", "crash01.test",
|
||||
"--vfs", "mvcc")
|
||||
mod, err := rt.InstantiateModule(ctx, module, cfg)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
mod.Close(ctx)
|
||||
}
|
||||
|
||||
func Test_multiwrite01_mvcc(t *testing.T) {
|
||||
if testing.Short() && os.Getenv("CI") != "" {
|
||||
t.Skip("skipping in slow CI")
|
||||
}
|
||||
|
||||
mvcc.Create("test.db", "")
|
||||
ctx := util.NewContext(newContext(t))
|
||||
cfg := config(ctx).WithArgs("mptest", "/test.db", "multiwrite01.test",
|
||||
"--vfs", "mvcc")
|
||||
mod, err := rt.InstantiateModule(ctx, module, cfg)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
mod.Close(ctx)
|
||||
}
|
||||
|
||||
func Test_crash01_wal(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("skipping in short mode")
|
||||
|
||||
@@ -13,7 +13,8 @@ WASI_SDK="$ROOT/tools/wasi-sdk/bin"
|
||||
-mmutable-globals -mnontrapping-fptoint \
|
||||
-msimd128 -mbulk-memory -msign-ext \
|
||||
-mreference-types -mmultivalue \
|
||||
-fno-stack-protector -fno-stack-clash-protection \
|
||||
-mno-extended-const \
|
||||
-fno-stack-protector \
|
||||
-Wl,--stack-first \
|
||||
-Wl,--import-undefined \
|
||||
-D_HAVE_SQLITE_CONFIG_H -DSQLITE_USE_URI \
|
||||
|
||||
Binary file not shown.
@@ -21,6 +21,7 @@ import (
|
||||
"github.com/ncruces/go-sqlite3/vfs"
|
||||
_ "github.com/ncruces/go-sqlite3/vfs/adiantum"
|
||||
_ "github.com/ncruces/go-sqlite3/vfs/memdb"
|
||||
_ "github.com/ncruces/go-sqlite3/vfs/mvcc"
|
||||
_ "github.com/ncruces/go-sqlite3/vfs/xts"
|
||||
)
|
||||
|
||||
@@ -35,7 +36,7 @@ func TestMain(m *testing.M) {
|
||||
initFlags()
|
||||
|
||||
ctx := context.Background()
|
||||
cfg := wazero.NewRuntimeConfig().WithMemoryLimitPages(512)
|
||||
cfg := wazero.NewRuntimeConfig().WithMemoryLimitPages(2048)
|
||||
rt = wazero.NewRuntimeWithConfig(ctx, cfg)
|
||||
wasi_snapshot_preview1.MustInstantiate(ctx, rt)
|
||||
env := vfs.ExportHostFunctions(rt.NewHostModuleBuilder("env"))
|
||||
|
||||
@@ -13,7 +13,8 @@ WASI_SDK="$ROOT/tools/wasi-sdk/bin"
|
||||
-mmutable-globals -mnontrapping-fptoint \
|
||||
-msimd128 -mbulk-memory -msign-ext \
|
||||
-mreference-types -mmultivalue \
|
||||
-fno-stack-protector -fno-stack-clash-protection \
|
||||
-mno-extended-const \
|
||||
-fno-stack-protector \
|
||||
-Wl,--stack-first \
|
||||
-Wl,--import-undefined \
|
||||
-D_HAVE_SQLITE_CONFIG_H -DSQLITE_USE_URI \
|
||||
|
||||
Binary file not shown.
@@ -148,7 +148,7 @@ func vfsOpen(ctx context.Context, mod api.Module, pVfs, zPath, pFile ptr_t, flag
|
||||
if pOutFlags != 0 {
|
||||
util.Write32(mod, pOutFlags, flags)
|
||||
}
|
||||
file = cksmWrapFile(name, flags, file)
|
||||
file = cksmWrapFile(file, flags)
|
||||
vfsFileRegister(ctx, mod, pFile, file)
|
||||
return _OK
|
||||
}
|
||||
|
||||
@@ -19,7 +19,10 @@ type xtsVFS struct {
|
||||
|
||||
func (x *xtsVFS) Open(name string, flags vfs.OpenFlag) (vfs.File, vfs.OpenFlag, error) {
|
||||
// notest // OpenFilename is called instead
|
||||
return nil, 0, sqlite3.CANTOPEN
|
||||
if name == "" {
|
||||
return x.OpenFilename(nil, flags)
|
||||
}
|
||||
return nil, flags, sqlite3.CANTOPEN
|
||||
}
|
||||
|
||||
func (x *xtsVFS) OpenFilename(name *vfs.Filename, flags vfs.OpenFlag) (file vfs.File, _ vfs.OpenFlag, err error) {
|
||||
@@ -124,10 +127,10 @@ func (x *xtsFile) ReadAt(p []byte, off int64) (n int, err error) {
|
||||
return n, err
|
||||
}
|
||||
|
||||
sectorNum := uint64(min / sectorSize)
|
||||
x.cipher.Decrypt(x.sector[:], x.sector[:], sectorNum)
|
||||
|
||||
data := x.sector[:]
|
||||
sectorNum := uint64(min / sectorSize)
|
||||
x.cipher.Decrypt(data, x.sector[:], sectorNum)
|
||||
|
||||
if off > min {
|
||||
data = data[off-min:]
|
||||
}
|
||||
@@ -150,16 +153,17 @@ func (x *xtsFile) WriteAt(p []byte, off int64) (n int, err error) {
|
||||
|
||||
// Write one block at a time.
|
||||
for ; min < max; min += sectorSize {
|
||||
sectorNum := uint64(min / sectorSize)
|
||||
data := x.sector[:]
|
||||
sectorNum := uint64(min / sectorSize)
|
||||
|
||||
if off > min || len(p[n:]) < sectorSize {
|
||||
// Partial block write: read-update-write.
|
||||
m, err := x.File.ReadAt(x.sector[:], min)
|
||||
if m != sectorSize {
|
||||
if err != io.EOF {
|
||||
return n, err
|
||||
}
|
||||
if m == sectorSize {
|
||||
x.cipher.Decrypt(data, x.sector[:], sectorNum)
|
||||
} else if err != io.EOF {
|
||||
return n, err
|
||||
} else {
|
||||
// Writing past the EOF.
|
||||
// We're either appending an entirely new block,
|
||||
// or the final block was only partially written.
|
||||
@@ -167,8 +171,6 @@ func (x *xtsFile) WriteAt(p []byte, off int64) (n int, err error) {
|
||||
// and is as good as corrupt.
|
||||
// Either way, zero pad the file to the next block size.
|
||||
clear(data)
|
||||
} else {
|
||||
x.cipher.Decrypt(data, data, sectorNum)
|
||||
}
|
||||
if off > min {
|
||||
data = data[off-min:]
|
||||
@@ -219,16 +221,16 @@ func (x *xtsFile) SizeHint(size int64) error {
|
||||
return vfsutil.WrapSizeHint(x.File, roundUp(size))
|
||||
}
|
||||
|
||||
// Wrap optional methods.
|
||||
|
||||
func (x *xtsFile) Unwrap() vfs.File {
|
||||
return x.File
|
||||
return x.File // notest
|
||||
}
|
||||
|
||||
func (x *xtsFile) SharedMemory() vfs.SharedMemory {
|
||||
return vfsutil.WrapSharedMemory(x.File)
|
||||
return vfsutil.WrapSharedMemory(x.File) // notest
|
||||
}
|
||||
|
||||
// Wrap optional methods.
|
||||
|
||||
func (x *xtsFile) LockState() vfs.LockLevel {
|
||||
return vfsutil.WrapLockState(x.File) // notest
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user