mirror of
https://github.com/ncruces/go-sqlite3.git
synced 2026-01-14 06:49:12 +00:00
Compare commits
80 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9719d4b0e3 | ||
|
|
b21c69dc1f | ||
|
|
b0f8ff44a5 | ||
|
|
f37bca6a80 | ||
|
|
b4e8fcb752 | ||
|
|
14b98a5d05 | ||
|
|
36a62264f9 | ||
|
|
33ea564f38 | ||
|
|
5c55d8692f | ||
|
|
be2f3036b4 | ||
|
|
784f82f42f | ||
|
|
cd6ba43e77 | ||
|
|
d7aef63844 | ||
|
|
64e5046f10 | ||
|
|
0bdce8aa68 | ||
|
|
69a2881a10 | ||
|
|
24ad4445f1 | ||
|
|
c159bbd88f | ||
|
|
c90f8205f7 | ||
|
|
b64b9b0415 | ||
|
|
9142e19d61 | ||
|
|
4a76f2b064 | ||
|
|
c9b364507e | ||
|
|
2204b96ff6 | ||
|
|
b46f480d79 | ||
|
|
040a026925 | ||
|
|
e678040a4e | ||
|
|
f1cc12569c | ||
|
|
721a987e0e | ||
|
|
f3d65142cc | ||
|
|
93f711c77b | ||
|
|
341bd063e8 | ||
|
|
f765882670 | ||
|
|
ff3676ff4a | ||
|
|
54877a53cd | ||
|
|
fccc6c10a7 | ||
|
|
fc21ffcc71 | ||
|
|
687e643d7a | ||
|
|
fc5ced209c | ||
|
|
c1bed07e3a | ||
|
|
a0771f2363 | ||
|
|
6bad547d3d | ||
|
|
c2c1aea578 | ||
|
|
60ab485b29 | ||
|
|
e17a432fde | ||
|
|
c780ef16e2 | ||
|
|
b609930142 | ||
|
|
fd165ce724 | ||
|
|
d3973b23e3 | ||
|
|
320b68e74f | ||
|
|
2c3850e5d1 | ||
|
|
db7aacff9f | ||
|
|
d748d98e39 | ||
|
|
13b8642384 | ||
|
|
29c5c816cb | ||
|
|
b32db76da6 | ||
|
|
383f620a1e | ||
|
|
a3c3515e96 | ||
|
|
e580f080b9 | ||
|
|
9ea7099c24 | ||
|
|
29aa365806 | ||
|
|
bb87a920f7 | ||
|
|
48379336dc | ||
|
|
251a92fa1a | ||
|
|
f5206ea8da | ||
|
|
68ef4593d6 | ||
|
|
79bf171210 | ||
|
|
ad16d329ea | ||
|
|
9706fa9607 | ||
|
|
45494f5fb6 | ||
|
|
1b0bf3495e | ||
|
|
73ac7e06f6 | ||
|
|
a3ce8f9de5 | ||
|
|
2043d5fca4 | ||
|
|
3bd11a0a86 | ||
|
|
39f3fa64eb | ||
|
|
4c19387535 | ||
|
|
e6c9f18934 | ||
|
|
970eb6a2f9 | ||
|
|
fac27b8bab |
23
.github/workflows/libc.yml
vendored
Normal file
23
.github/workflows/libc.yml
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
name: Benchmark libc
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
test:
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-24.04, ubuntu-24.04-arm, macos-13, macos-15]
|
||||
runs-on: ${{ matrix.os }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-go@v5
|
||||
with: { go-version: stable }
|
||||
|
||||
- name: Benchmark
|
||||
shell: bash
|
||||
run: sqlite3/libc/benchmark.sh
|
||||
21
.github/workflows/repro.sh
vendored
21
.github/workflows/repro.sh
vendored
@@ -1,28 +1,9 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
if [[ "$OSTYPE" == "linux"* ]]; then
|
||||
WASI_SDK="https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-25/wasi-sdk-25.0-x86_64-linux.tar.gz"
|
||||
BINARYEN="https://github.com/WebAssembly/binaryen/releases/download/version_123/binaryen-version_123-x86_64-linux.tar.gz"
|
||||
elif [[ "$OSTYPE" == "darwin"* ]]; then
|
||||
WASI_SDK="https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-25/wasi-sdk-25.0-arm64-macos.tar.gz"
|
||||
BINARYEN="https://github.com/WebAssembly/binaryen/releases/download/version_123/binaryen-version_123-arm64-macos.tar.gz"
|
||||
elif [[ "$OSTYPE" == "msys" || "$OSTYPE" == "cygwin" ]]; then
|
||||
WASI_SDK="https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-25/wasi-sdk-25.0-x86_64-windows.tar.gz"
|
||||
BINARYEN="https://github.com/WebAssembly/binaryen/releases/download/version_123/binaryen-version_123-x86_64-windows.tar.gz"
|
||||
fi
|
||||
|
||||
# Download tools
|
||||
mkdir -p tools/
|
||||
[ -d "tools/wasi-sdk" ] || curl -#L "$WASI_SDK" | tar xzC tools &
|
||||
[ -d "tools/binaryen" ] || curl -#L "$BINARYEN" | tar xzC tools &
|
||||
wait
|
||||
|
||||
[ -d "tools/wasi-sdk" ] || mv "tools/wasi-sdk"* "tools/wasi-sdk"
|
||||
[ -d "tools/binaryen" ] || mv "tools/binaryen"* "tools/binaryen"
|
||||
|
||||
# Download and build SQLite
|
||||
sqlite3/download.sh
|
||||
sqlite3/tools.sh
|
||||
embed/build.sh
|
||||
embed/bcw2/build.sh
|
||||
|
||||
|
||||
9
.github/workflows/test.yml
vendored
9
.github/workflows/test.yml
vendored
@@ -17,12 +17,17 @@ on:
|
||||
- '**.yml'
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
test:
|
||||
strategy:
|
||||
matrix:
|
||||
os: [macos-latest, ubuntu-latest, windows-latest]
|
||||
runs-on: ${{ matrix.os }}
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
@@ -107,7 +112,7 @@ jobs:
|
||||
version: '10.1'
|
||||
flags: '-test.v -test.short'
|
||||
- name: openbsd
|
||||
version: '7.6'
|
||||
version: '7.7'
|
||||
flags: '-test.v -test.short'
|
||||
runs-on: ubuntu-latest
|
||||
needs: test
|
||||
@@ -123,7 +128,7 @@ jobs:
|
||||
run: .github/workflows/build-test.sh
|
||||
|
||||
- name: Test
|
||||
uses: cross-platform-actions/action@v0.27.0
|
||||
uses: cross-platform-actions/action@v0.28.0
|
||||
with:
|
||||
operating_system: ${{ matrix.os.name }}
|
||||
architecture: ${{ matrix.os.arch }}
|
||||
|
||||
31
README.md
31
README.md
@@ -30,10 +30,10 @@ db.QueryRow(`SELECT sqlite_version()`).Scan(&version)
|
||||
|
||||
- [`github.com/ncruces/go-sqlite3`](https://pkg.go.dev/github.com/ncruces/go-sqlite3)
|
||||
wraps the [C SQLite API](https://sqlite.org/cintro.html)
|
||||
([example usage](https://pkg.go.dev/github.com/ncruces/go-sqlite3#example-package)).
|
||||
([example](https://pkg.go.dev/github.com/ncruces/go-sqlite3#example-package)).
|
||||
- [`github.com/ncruces/go-sqlite3/driver`](https://pkg.go.dev/github.com/ncruces/go-sqlite3/driver)
|
||||
provides a [`database/sql`](https://pkg.go.dev/database/sql) driver
|
||||
([example usage](https://pkg.go.dev/github.com/ncruces/go-sqlite3/driver#example-package)).
|
||||
([example](https://pkg.go.dev/github.com/ncruces/go-sqlite3/driver#example-package)).
|
||||
- [`github.com/ncruces/go-sqlite3/embed`](https://pkg.go.dev/github.com/ncruces/go-sqlite3/embed)
|
||||
embeds a build of SQLite into your application.
|
||||
- [`github.com/ncruces/go-sqlite3/vfs`](https://pkg.go.dev/github.com/ncruces/go-sqlite3/vfs)
|
||||
@@ -44,12 +44,19 @@ db.QueryRow(`SELECT sqlite_version()`).Scan(&version)
|
||||
### Advanced features
|
||||
|
||||
- [incremental BLOB I/O](https://sqlite.org/c3ref/blob_open.html)
|
||||
([example](https://pkg.go.dev/github.com/ncruces/go-sqlite3/ext/blobio#example-package))
|
||||
- [nested transactions](https://sqlite.org/lang_savepoint.html)
|
||||
([example](https://pkg.go.dev/github.com/ncruces/go-sqlite3/driver#example-Savepoint))
|
||||
- [custom functions](https://sqlite.org/c3ref/create_function.html)
|
||||
([example](https://pkg.go.dev/github.com/ncruces/go-sqlite3#example-Conn.CreateFunction))
|
||||
- [virtual tables](https://sqlite.org/vtab.html)
|
||||
([example](https://pkg.go.dev/github.com/ncruces/go-sqlite3#example-CreateModule))
|
||||
- [custom VFSes](https://sqlite.org/vfs.html)
|
||||
([examples](vfs/README.md#custom-vfses))
|
||||
- [online backup](https://sqlite.org/backup.html)
|
||||
([example](https://pkg.go.dev/github.com/ncruces/go-sqlite3/driver#Conn))
|
||||
- [JSON support](https://sqlite.org/json1.html)
|
||||
([example](https://pkg.go.dev/github.com/ncruces/go-sqlite3/driver#example-package-Json))
|
||||
- [math functions](https://sqlite.org/lang_mathfunc.html)
|
||||
- [full-text search](https://sqlite.org/fts5.html)
|
||||
- [geospatial search](https://sqlite.org/geopoly.html)
|
||||
@@ -57,7 +64,6 @@ db.QueryRow(`SELECT sqlite_version()`).Scan(&version)
|
||||
- [statistics functions](https://pkg.go.dev/github.com/ncruces/go-sqlite3/ext/stats)
|
||||
- [encryption at rest](vfs/adiantum/README.md)
|
||||
- [many extensions](ext/README.md)
|
||||
- [custom VFSes](vfs/README.md#custom-vfses)
|
||||
- [and more…](embed/README.md)
|
||||
|
||||
### Caveats
|
||||
@@ -77,10 +83,19 @@ It also benefits greatly from [SQLite's](https://sqlite.org/testing.html) and
|
||||
[wazero's](https://tetrate.io/blog/introducing-wazero-from-tetrate/#:~:text=Rock%2Dsolid%20test%20approach)
|
||||
thorough testing.
|
||||
|
||||
Every commit is [tested](https://github.com/ncruces/go-sqlite3/wiki/Support-matrix) on
|
||||
Linux (amd64/arm64/386/riscv64/ppc64le/s390x), macOS (arm64/amd64),
|
||||
Windows (amd64), FreeBSD (amd64/arm64), OpenBSD (amd64), NetBSD (amd64/arm64),
|
||||
DragonFly BSD (amd64), illumos (amd64), and Solaris (amd64).
|
||||
Every commit is tested on:
|
||||
* Linux: amd64, arm64, 386, riscv64, ppc64le, s390x
|
||||
* macOS: amd64, arm64
|
||||
* Windows: amd64
|
||||
* BSD:
|
||||
* FreeBSD: amd64, arm64
|
||||
* OpenBSD: amd64
|
||||
* NetBSD: amd64, arm64
|
||||
* DragonFly BSD: amd64
|
||||
* illumos: amd64
|
||||
* Solaris: amd64
|
||||
|
||||
Certain operating system and CPU combinations have some limitations. See the [support matrix](https://github.com/ncruces/go-sqlite3/wiki/Support-matrix) for a complete overview.
|
||||
|
||||
The Go VFS is tested by running SQLite's
|
||||
[mptest](https://github.com/sqlite/sqlite/blob/master/mptest/mptest.c).
|
||||
@@ -118,4 +133,4 @@ and features we're working on, planning to work on, or asking for help with.
|
||||
- [`modernc.org/sqlite`](https://pkg.go.dev/modernc.org/sqlite)
|
||||
- [`crawshaw.io/sqlite`](https://pkg.go.dev/crawshaw.io/sqlite)
|
||||
- [`github.com/mattn/go-sqlite3`](https://pkg.go.dev/github.com/mattn/go-sqlite3)
|
||||
- [`github.com/zombiezen/go-sqlite`](https://pkg.go.dev/github.com/zombiezen/go-sqlite)
|
||||
- [`github.com/zombiezen/go-sqlite`](https://pkg.go.dev/github.com/zombiezen/go-sqlite)
|
||||
|
||||
@@ -109,7 +109,7 @@ func (c *Conn) FileControl(schema string, op FcntlOpcode, arg ...any) (any, erro
|
||||
default:
|
||||
return nil, MISUSE
|
||||
|
||||
case FCNTL_RESET_CACHE:
|
||||
case FCNTL_RESET_CACHE, FCNTL_NULL_IO:
|
||||
rc = res_t(c.call("sqlite3_file_control",
|
||||
stk_t(c.handle), stk_t(schemaPtr),
|
||||
stk_t(op), 0))
|
||||
|
||||
13
const.go
13
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.
|
||||
@@ -280,6 +280,7 @@ const (
|
||||
FCNTL_DATA_VERSION FcntlOpcode = 35
|
||||
FCNTL_RESERVE_BYTES FcntlOpcode = 38
|
||||
FCNTL_RESET_CACHE FcntlOpcode = 42
|
||||
FCNTL_NULL_IO FcntlOpcode = 43
|
||||
)
|
||||
|
||||
// LimitCategory are the available run-time limit categories.
|
||||
|
||||
15
context.go
15
context.go
@@ -177,12 +177,15 @@ func (ctx Context) ResultPointer(ptr any) {
|
||||
//
|
||||
// https://sqlite.org/c3ref/result_blob.html
|
||||
func (ctx Context) ResultJSON(value any) {
|
||||
data, err := json.Marshal(value)
|
||||
err := json.NewEncoder(callbackWriter(func(p []byte) (int, error) {
|
||||
ctx.ResultRawText(p[:len(p)-1]) // remove the newline
|
||||
return 0, nil
|
||||
})).Encode(value)
|
||||
|
||||
if err != nil {
|
||||
ctx.ResultError(err)
|
||||
return // notest
|
||||
}
|
||||
ctx.ResultRawText(data)
|
||||
}
|
||||
|
||||
// ResultValue sets the result of the function to a copy of [Value].
|
||||
@@ -224,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.
|
||||
//
|
||||
|
||||
@@ -241,8 +241,9 @@ func (n *connector) Connect(ctx context.Context) (ret driver.Conn, err error) {
|
||||
}
|
||||
}()
|
||||
|
||||
old := c.Conn.SetInterrupt(ctx)
|
||||
defer c.Conn.SetInterrupt(old)
|
||||
if old := c.Conn.SetInterrupt(ctx); old != ctx {
|
||||
defer c.Conn.SetInterrupt(old)
|
||||
}
|
||||
|
||||
if !n.pragmas {
|
||||
err = c.Conn.BusyTimeout(time.Minute)
|
||||
@@ -362,8 +363,9 @@ func (c *conn) BeginTx(ctx context.Context, opts driver.TxOptions) (driver.Tx, e
|
||||
c.txReset = `; PRAGMA query_only=` + string(c.readOnly)
|
||||
}
|
||||
|
||||
old := c.Conn.SetInterrupt(ctx)
|
||||
defer c.Conn.SetInterrupt(old)
|
||||
if old := c.Conn.SetInterrupt(ctx); old != ctx {
|
||||
defer c.Conn.SetInterrupt(old)
|
||||
}
|
||||
|
||||
err := c.Conn.Exec(txBegin)
|
||||
if err != nil {
|
||||
@@ -382,8 +384,10 @@ func (c *conn) Commit() error {
|
||||
|
||||
func (c *conn) Rollback() error {
|
||||
// ROLLBACK even if interrupted.
|
||||
old := c.Conn.SetInterrupt(context.Background())
|
||||
defer c.Conn.SetInterrupt(old)
|
||||
ctx := context.Background()
|
||||
if old := c.Conn.SetInterrupt(ctx); old != ctx {
|
||||
defer c.Conn.SetInterrupt(old)
|
||||
}
|
||||
return c.Conn.Exec(`ROLLBACK` + c.txReset)
|
||||
}
|
||||
|
||||
@@ -393,8 +397,9 @@ func (c *conn) Prepare(query string) (driver.Stmt, error) {
|
||||
}
|
||||
|
||||
func (c *conn) PrepareContext(ctx context.Context, query string) (driver.Stmt, error) {
|
||||
old := c.Conn.SetInterrupt(ctx)
|
||||
defer c.Conn.SetInterrupt(old)
|
||||
if old := c.Conn.SetInterrupt(ctx); old != ctx {
|
||||
defer c.Conn.SetInterrupt(old)
|
||||
}
|
||||
|
||||
s, tail, err := c.Conn.Prepare(query)
|
||||
if err != nil {
|
||||
@@ -419,8 +424,9 @@ func (c *conn) ExecContext(ctx context.Context, query string, args []driver.Name
|
||||
return resultRowsAffected(0), nil
|
||||
}
|
||||
|
||||
old := c.Conn.SetInterrupt(ctx)
|
||||
defer c.Conn.SetInterrupt(old)
|
||||
if old := c.Conn.SetInterrupt(ctx); old != ctx {
|
||||
defer c.Conn.SetInterrupt(old)
|
||||
}
|
||||
|
||||
err := c.Conn.Exec(query)
|
||||
if err != nil {
|
||||
@@ -483,8 +489,10 @@ func (s *stmt) ExecContext(ctx context.Context, args []driver.NamedValue) (drive
|
||||
return nil, err
|
||||
}
|
||||
|
||||
old := s.Stmt.Conn().SetInterrupt(ctx)
|
||||
defer s.Stmt.Conn().SetInterrupt(old)
|
||||
c := s.Stmt.Conn()
|
||||
if old := c.SetInterrupt(ctx); old != ctx {
|
||||
defer c.SetInterrupt(old)
|
||||
}
|
||||
|
||||
err = errors.Join(
|
||||
s.Stmt.Exec(),
|
||||
@@ -493,7 +501,7 @@ func (s *stmt) ExecContext(ctx context.Context, args []driver.NamedValue) (drive
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return newResult(s.Stmt.Conn()), nil
|
||||
return newResult(c), nil
|
||||
}
|
||||
|
||||
func (s *stmt) QueryContext(ctx context.Context, args []driver.NamedValue) (driver.Rows, error) {
|
||||
@@ -538,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:
|
||||
@@ -557,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:
|
||||
@@ -678,13 +686,14 @@ func (r *rows) scanType(index int) scantype {
|
||||
|
||||
func (r *rows) loadColumnMetadata() {
|
||||
if r.nulls == nil {
|
||||
c := r.Stmt.Conn()
|
||||
count := r.Stmt.ColumnCount()
|
||||
nulls := make([]bool, count)
|
||||
types := make([]string, count)
|
||||
scans := make([]scantype, count)
|
||||
for i := range nulls {
|
||||
if col := r.Stmt.ColumnOriginName(i); col != "" {
|
||||
types[i], _, nulls[i], _, _, _ = r.Stmt.Conn().TableColumnMetadata(
|
||||
types[i], _, nulls[i], _, _, _ = c.TableColumnMetadata(
|
||||
r.Stmt.ColumnDatabaseName(i),
|
||||
r.Stmt.ColumnTableName(i),
|
||||
col)
|
||||
@@ -762,8 +771,10 @@ func (r *rows) ColumnTypeScanType(index int) (typ reflect.Type) {
|
||||
}
|
||||
|
||||
func (r *rows) Next(dest []driver.Value) error {
|
||||
old := r.Stmt.Conn().SetInterrupt(r.ctx)
|
||||
defer r.Stmt.Conn().SetInterrupt(old)
|
||||
c := r.Stmt.Conn()
|
||||
if old := c.SetInterrupt(r.ctx); old != r.ctx {
|
||||
defer c.SetInterrupt(old)
|
||||
}
|
||||
|
||||
if !r.Stmt.Step() {
|
||||
if err := r.Stmt.Err(); err != nil {
|
||||
|
||||
@@ -248,8 +248,10 @@ func Test_nested_context(t *testing.T) {
|
||||
want(inner, 0)
|
||||
cancel()
|
||||
|
||||
if inner.Next() || !errors.Is(inner.Err(), sqlite3.INTERRUPT) {
|
||||
t.Fatal(inner.Err())
|
||||
var terr interface{ Temporary() bool }
|
||||
if inner.Next() || !errors.Is(inner.Err(), context.Canceled) &&
|
||||
(!errors.As(inner.Err(), &terr) || !terr.Temporary()) {
|
||||
t.Fatalf("got %v, want cancellation", inner.Err())
|
||||
}
|
||||
|
||||
want(outer, 1)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Embeddable Wasm build of SQLite
|
||||
|
||||
This folder includes an embeddable Wasm build of SQLite 3.49.1 for use with
|
||||
This folder includes an embeddable Wasm build of SQLite 3.50.2 for use with
|
||||
[`github.com/ncruces/go-sqlite3`](https://pkg.go.dev/github.com/ncruces/go-sqlite3).
|
||||
|
||||
The following optional features are compiled in:
|
||||
|
||||
Binary file not shown.
@@ -53,7 +53,7 @@ func Test_bcw2(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if version != "3.50.0" {
|
||||
if version != "3.51.0" {
|
||||
t.Error(version)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,11 +13,10 @@ mkdir -p build/ext/
|
||||
cp "$ROOT"/sqlite3/*.[ch] build/
|
||||
cp "$ROOT"/sqlite3/*.patch build/
|
||||
|
||||
# https://sqlite.org/src/info/c09656c62155a6e8
|
||||
curl -# https://sqlite.org/src/tarball/sqlite.tar.gz?r=c09656c6 | tar xz
|
||||
# https://sqlite.org/src/info/a6f6fbe6173de8a2
|
||||
curl -# https://sqlite.org/src/tarball/sqlite.tar.gz?r=a6f6fbe617 | tar xz
|
||||
|
||||
cd sqlite
|
||||
cat ../repro.patch | patch -p0 --no-backup-if-mismatch
|
||||
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
|
||||
@@ -44,12 +43,12 @@ cd ~-
|
||||
|
||||
"$WASI_SDK/clang" --target=wasm32-wasi -std=c23 -g0 -O2 \
|
||||
-Wall -Wextra -Wno-unused-parameter -Wno-unused-function \
|
||||
-o bcw2.wasm "build/main.c" \
|
||||
-I"build" \
|
||||
-o bcw2.wasm build/main.c \
|
||||
-I"$ROOT/sqlite3/libc" -I"build" \
|
||||
-mexec-model=reactor \
|
||||
-msimd128 -mmutable-globals -mmultivalue \
|
||||
-mbulk-memory -mreference-types \
|
||||
-mnontrapping-fptoint -msign-ext \
|
||||
-mmutable-globals -mnontrapping-fptoint \
|
||||
-msimd128 -mbulk-memory -msign-ext \
|
||||
-mreference-types -mmultivalue \
|
||||
-fno-stack-protector -fno-stack-clash-protection \
|
||||
-Wl,--stack-first \
|
||||
-Wl,--import-undefined \
|
||||
@@ -61,7 +60,7 @@ cd ~-
|
||||
|
||||
"$BINARYEN/wasm-ctor-eval" -g -c _initialize bcw2.wasm -o bcw2.tmp
|
||||
"$BINARYEN/wasm-opt" -g --strip --strip-producers -c -O3 \
|
||||
bcw2.tmp -o bcw2.wasm \
|
||||
--enable-simd --enable-mutable-globals --enable-multivalue \
|
||||
--enable-bulk-memory --enable-reference-types \
|
||||
--enable-nontrapping-float-to-int --enable-sign-ext
|
||||
bcw2.tmp -o bcw2.wasm --low-memory-unused \
|
||||
--enable-mutable-globals --enable-nontrapping-float-to-int \
|
||||
--enable-simd --enable-bulk-memory --enable-sign-ext \
|
||||
--enable-reference-types --enable-multivalue
|
||||
@@ -4,11 +4,11 @@ go 1.23.0
|
||||
|
||||
toolchain go1.24.0
|
||||
|
||||
require github.com/ncruces/go-sqlite3 v0.24.0
|
||||
require github.com/ncruces/go-sqlite3 v0.26.3
|
||||
|
||||
require (
|
||||
github.com/ncruces/julianday v1.0.0 // indirect
|
||||
github.com/ncruces/sort v0.1.5 // indirect
|
||||
github.com/tetratelabs/wazero v1.9.0 // indirect
|
||||
golang.org/x/sys v0.30.0 // indirect
|
||||
golang.org/x/sys v0.34.0 // indirect
|
||||
)
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
github.com/ncruces/go-sqlite3 v0.24.0 h1:Z4jfmzu2NCd4SmyFwLT2OmF3EnTZbqwATvdiuNHNhLA=
|
||||
github.com/ncruces/go-sqlite3 v0.24.0/go.mod h1:/Vs8ACZHjJ1SA6E9RZUn3EyB1OP3nDQ4z/ar+0fplTQ=
|
||||
github.com/ncruces/go-sqlite3 v0.26.3 h1:WFkQj4KNMhbqiBPGDrVpK74w1DzcxQu3wYpmdWAvfYM=
|
||||
github.com/ncruces/go-sqlite3 v0.26.3/go.mod h1:XFTPtFIo1DmGCh+XVP8KGn9b/o2f+z0WZuT09x2N6eo=
|
||||
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.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
|
||||
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM=
|
||||
golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=
|
||||
golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA=
|
||||
golang.org/x/sys v0.34.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=
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
# https://sqlite.org/src/vpatch?from=67809715977a5bad&to=3f57584710d61174
|
||||
--- tool/mkpragmatab.tcl
|
||||
+++ tool/mkpragmatab.tcl
|
||||
@@ -526,14 +526,17 @@
|
||||
puts $fd [format {#define PragFlg_%-10s 0x%02x /* %s */} \
|
||||
$f $fv $flagMeaning($f)]
|
||||
set fv [expr {$fv*2}]
|
||||
}
|
||||
|
||||
-# Sort the column lists so that longer column lists occur first
|
||||
+# Sort the column lists so that longer column lists occur first.
|
||||
+# In the event of a tie, sort column lists lexicographically.
|
||||
#
|
||||
proc colscmp {a b} {
|
||||
- return [expr {[llength $b] - [llength $a]}]
|
||||
+ set rc [expr {[llength $b] - [llength $a]}]
|
||||
+ if {$rc} {return $rc}
|
||||
+ return [string compare $a $b]
|
||||
}
|
||||
set cols_list [lsort -command colscmp $cols_list]
|
||||
|
||||
# Generate the array of column names used by pragmas that act like
|
||||
# queries.
|
||||
@@ -12,11 +12,11 @@ trap 'rm -f sqlite3.tmp' EXIT
|
||||
"$WASI_SDK/clang" --target=wasm32-wasi -std=c23 -g0 -O2 \
|
||||
-Wall -Wextra -Wno-unused-parameter -Wno-unused-function \
|
||||
-o sqlite3.wasm "$ROOT/sqlite3/main.c" \
|
||||
-I"$ROOT/sqlite3" \
|
||||
-I"$ROOT/sqlite3/libc" -I"$ROOT/sqlite3" \
|
||||
-mexec-model=reactor \
|
||||
-msimd128 -mmutable-globals -mmultivalue \
|
||||
-mbulk-memory -mreference-types \
|
||||
-mnontrapping-fptoint -msign-ext \
|
||||
-mmutable-globals -mnontrapping-fptoint \
|
||||
-msimd128 -mbulk-memory -msign-ext \
|
||||
-mreference-types -mmultivalue \
|
||||
-fno-stack-protector -fno-stack-clash-protection \
|
||||
-Wl,--stack-first \
|
||||
-Wl,--import-undefined \
|
||||
@@ -27,7 +27,7 @@ trap 'rm -f sqlite3.tmp' EXIT
|
||||
|
||||
"$BINARYEN/wasm-ctor-eval" -g -c _initialize sqlite3.wasm -o sqlite3.tmp
|
||||
"$BINARYEN/wasm-opt" -g --strip --strip-producers -c -O3 \
|
||||
sqlite3.tmp -o sqlite3.wasm \
|
||||
--enable-simd --enable-mutable-globals --enable-multivalue \
|
||||
--enable-bulk-memory --enable-reference-types \
|
||||
--enable-nontrapping-float-to-int --enable-sign-ext
|
||||
sqlite3.tmp -o sqlite3.wasm --low-memory-unused \
|
||||
--enable-mutable-globals --enable-nontrapping-float-to-int \
|
||||
--enable-simd --enable-bulk-memory --enable-sign-ext \
|
||||
--enable-reference-types --enable-multivalue
|
||||
@@ -66,6 +66,7 @@ sqlite3_errmsg
|
||||
sqlite3_error_offset
|
||||
sqlite3_errstr
|
||||
sqlite3_exec
|
||||
sqlite3_exec_go
|
||||
sqlite3_expanded_sql
|
||||
sqlite3_file_control
|
||||
sqlite3_filename_database
|
||||
@@ -97,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
|
||||
@@ -125,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.49.1" {
|
||||
if version != "3.50.2" {
|
||||
t.Error(version)
|
||||
}
|
||||
}
|
||||
|
||||
Binary file not shown.
2
error.go
2
error.go
@@ -75,7 +75,7 @@ func (e *Error) As(err any) bool {
|
||||
|
||||
// Temporary returns true for [BUSY] errors.
|
||||
func (e *Error) Temporary() bool {
|
||||
return e.Code() == BUSY
|
||||
return e.Code() == BUSY || e.Code() == INTERRUPT
|
||||
}
|
||||
|
||||
// Timeout returns true for [BUSY_TIMEOUT] errors.
|
||||
|
||||
@@ -30,7 +30,7 @@ you can load into your database connections.
|
||||
- [`github.com/ncruces/go-sqlite3/ext/statement`](https://pkg.go.dev/github.com/ncruces/go-sqlite3/ext/statement)
|
||||
creates [parameterized views](https://github.com/0x09/sqlite-statement-vtab).
|
||||
- [`github.com/ncruces/go-sqlite3/ext/stats`](https://pkg.go.dev/github.com/ncruces/go-sqlite3/ext/stats)
|
||||
provides [statistics](https://www.oreilly.com/library/view/sql-in-a/9780596155322/ch04s02.html) functions.
|
||||
provides [statistics](https://oreilly.com/library/view/sql-in-a/9780596155322/ch04s02.html) functions.
|
||||
- [`github.com/ncruces/go-sqlite3/ext/unicode`](https://pkg.go.dev/github.com/ncruces/go-sqlite3/ext/unicode)
|
||||
provides [Unicode aware](https://sqlite.org/src/dir/ext/icu) functions.
|
||||
- [`github.com/ncruces/go-sqlite3/ext/uuid`](https://pkg.go.dev/github.com/ncruces/go-sqlite3/ext/uuid)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package blobio_test
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
@@ -34,7 +35,8 @@ func Example() {
|
||||
const message = "Hello BLOB!"
|
||||
|
||||
// Create the BLOB.
|
||||
r, err := db.Exec(`INSERT INTO test VALUES (?)`, sqlite3.ZeroBlob(len(message)))
|
||||
r, err := db.Exec(`INSERT INTO test VALUES (:data)`,
|
||||
sql.Named("data", sqlite3.ZeroBlob(len(message))))
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
@@ -45,15 +47,19 @@ func Example() {
|
||||
}
|
||||
|
||||
// Write the BLOB.
|
||||
_, err = db.Exec(`SELECT writeblob('main', 'test', 'col', ?, 0, ?)`,
|
||||
id, message)
|
||||
_, err = db.Exec(`SELECT writeblob('main', 'test', 'col', :rowid, :offset, :message)`,
|
||||
sql.Named("rowid", id),
|
||||
sql.Named("offset", 0),
|
||||
sql.Named("message", message))
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// Read the BLOB.
|
||||
_, err = db.Exec(`SELECT readblob('main', 'test', 'col', ?, 0, ?)`,
|
||||
id, sqlite3.Pointer(os.Stdout))
|
||||
_, err = db.Exec(`SELECT readblob('main', 'test', 'col', :rowid, :offset, :writer)`,
|
||||
sql.Named("rowid", id),
|
||||
sql.Named("offset", 0),
|
||||
sql.Named("writer", sqlite3.Pointer(os.Stdout)))
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
@@ -64,7 +70,7 @@ func Example() {
|
||||
func TestMain(m *testing.M) {
|
||||
sqlite3.AutoExtension(blobio.Register)
|
||||
sqlite3.AutoExtension(array.Register)
|
||||
m.Run()
|
||||
os.Exit(m.Run())
|
||||
}
|
||||
|
||||
func Test_readblob(t *testing.T) {
|
||||
@@ -138,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()
|
||||
|
||||
@@ -14,7 +14,7 @@ import (
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
sqlite3.AutoExtension(bloom.Register)
|
||||
m.Run()
|
||||
os.Exit(m.Run())
|
||||
}
|
||||
|
||||
func TestRegister(t *testing.T) {
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
_ "embed"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/ncruces/go-sqlite3"
|
||||
@@ -14,7 +15,7 @@ import (
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
sqlite3.AutoExtension(closure.Register)
|
||||
m.Run()
|
||||
os.Exit(m.Run())
|
||||
}
|
||||
|
||||
func Example() {
|
||||
|
||||
@@ -3,6 +3,7 @@ package csv_test
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/ncruces/go-sqlite3"
|
||||
@@ -56,7 +57,7 @@ func Example() {
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
sqlite3.AutoExtension(csv.Register)
|
||||
m.Run()
|
||||
os.Exit(m.Run())
|
||||
}
|
||||
|
||||
func TestRegister(t *testing.T) {
|
||||
@@ -146,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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ package pivot_test
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
@@ -85,7 +86,7 @@ func Example() {
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
sqlite3.AutoExtension(pivot.Register)
|
||||
m.Run()
|
||||
os.Exit(m.Run())
|
||||
}
|
||||
|
||||
func TestRegister(t *testing.T) {
|
||||
@@ -140,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`)
|
||||
|
||||
@@ -38,7 +38,7 @@ func TestRegister(t *testing.T) {
|
||||
{`regexp_instr('Hello', '.', 6)`, ""},
|
||||
{`regexp_substr('Hello', 'el.')`, "ell"},
|
||||
{`regexp_replace('Hello', 'llo', 'll')`, "Hell"},
|
||||
// https://www.postgresql.org/docs/current/functions-matching.html
|
||||
// https://postgresql.org/docs/current/functions-matching.html
|
||||
{`regexp_count('ABCABCAXYaxy', 'A.')`, "3"},
|
||||
{`regexp_count('ABCABCAXYaxy', '(?i)A.', 1)`, "4"},
|
||||
{`regexp_instr('number of your street, town zip, FR', '[^,]+', 1, 2)`, "23"},
|
||||
|
||||
@@ -3,6 +3,7 @@ package statement_test
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/ncruces/go-sqlite3"
|
||||
@@ -50,7 +51,7 @@ func Example() {
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
sqlite3.AutoExtension(statement.Register)
|
||||
m.Run()
|
||||
os.Exit(m.Run())
|
||||
}
|
||||
|
||||
func TestRegister(t *testing.T) {
|
||||
@@ -91,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)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# ANSI SQL Aggregate Functions
|
||||
|
||||
https://www.oreilly.com/library/view/sql-in-a/9780596155322/ch04s02.html
|
||||
https://oreilly.com/library/view/sql-in-a/9780596155322/ch04s02.html
|
||||
|
||||
## Built in aggregates
|
||||
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
|
||||
@@ -47,7 +47,7 @@
|
||||
//
|
||||
// [Built-in Aggregate Functions]: https://sqlite.org/lang_aggfunc.html
|
||||
// [Built-in Window Functions]: https://sqlite.org/windowfunctions.html#builtins
|
||||
// [ANSI SQL Aggregate Functions]: https://www.oreilly.com/library/view/sql-in-a/9780596155322/ch04s02.html
|
||||
// [ANSI SQL Aggregate Functions]: https://oreilly.com/library/view/sql-in-a/9780596155322/ch04s02.html
|
||||
package stats
|
||||
|
||||
import (
|
||||
@@ -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)
|
||||
|
||||
@@ -2,6 +2,7 @@ package stats_test
|
||||
|
||||
import (
|
||||
"math"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/ncruces/go-sqlite3"
|
||||
@@ -12,7 +13,7 @@ import (
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
sqlite3.AutoExtension(stats.Register)
|
||||
m.Run()
|
||||
os.Exit(m.Run())
|
||||
}
|
||||
|
||||
func TestRegister_variance(t *testing.T) {
|
||||
@@ -33,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()
|
||||
|
||||
@@ -56,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)
|
||||
}
|
||||
@@ -130,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)
|
||||
}
|
||||
@@ -155,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()
|
||||
|
||||
@@ -247,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)
|
||||
@@ -281,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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
8
go.mod
8
go.mod
@@ -8,16 +8,16 @@ require (
|
||||
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.36.0
|
||||
golang.org/x/sys v0.31.0
|
||||
golang.org/x/crypto v0.40.0
|
||||
golang.org/x/sys v0.34.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.12.0 // test
|
||||
golang.org/x/text v0.23.0 // ext/unicode
|
||||
golang.org/x/sync v0.16.0 // test
|
||||
golang.org/x/text v0.27.0 // ext/unicode
|
||||
lukechampine.com/adiantum v1.1.1 // vfs/adiantum
|
||||
)
|
||||
|
||||
|
||||
16
go.sum
16
go.sum
@@ -10,13 +10,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.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34=
|
||||
golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc=
|
||||
golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw=
|
||||
golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||
golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
|
||||
golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY=
|
||||
golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4=
|
||||
golang.org/x/crypto v0.40.0 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM=
|
||||
golang.org/x/crypto v0.40.0/go.mod h1:Qr1vMER5WyS2dfPHAlsOj01wgLbsyWtFn/aY+5+ZdxY=
|
||||
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.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA=
|
||||
golang.org/x/sys v0.34.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=
|
||||
lukechampine.com/adiantum v1.1.1 h1:4fp6gTxWCqpEbLy40ExiYDDED3oUNWx5cTqBCtPdZqA=
|
||||
lukechampine.com/adiantum v1.1.1/go.mod h1:LrAYVnTYLnUtE/yMp5bQr0HstAf060YUF8nM0B6+rUw=
|
||||
|
||||
@@ -209,8 +209,12 @@ func (d *ddl) renameTable(dst, src string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func compileConstraintRegexp(name string) *regexp.Regexp {
|
||||
return regexp.MustCompile("^(?i:CONSTRAINT)\\s+[\"`]?" + regexp.QuoteMeta(name) + "[\"`\\s]")
|
||||
}
|
||||
|
||||
func (d *ddl) addConstraint(name string, sql string) {
|
||||
reg := regexp.MustCompile("^CONSTRAINT [\"`]?" + regexp.QuoteMeta(name) + "[\"` ]")
|
||||
reg := compileConstraintRegexp(name)
|
||||
|
||||
for i := 0; i < len(d.fields); i++ {
|
||||
if reg.MatchString(d.fields[i]) {
|
||||
@@ -223,7 +227,7 @@ func (d *ddl) addConstraint(name string, sql string) {
|
||||
}
|
||||
|
||||
func (d *ddl) removeConstraint(name string) bool {
|
||||
reg := regexp.MustCompile("^CONSTRAINT [\"`]?" + regexp.QuoteMeta(name) + "[\"` ]")
|
||||
reg := compileConstraintRegexp(name)
|
||||
|
||||
for i := 0; i < len(d.fields); i++ {
|
||||
if reg.MatchString(d.fields[i]) {
|
||||
@@ -236,7 +240,7 @@ func (d *ddl) removeConstraint(name string) bool {
|
||||
|
||||
//lint:ignore U1000 ignore unused code.
|
||||
func (d *ddl) hasConstraint(name string) bool {
|
||||
reg := regexp.MustCompile("^CONSTRAINT [\"`]?" + regexp.QuoteMeta(name) + "[\"` ]")
|
||||
reg := compileConstraintRegexp(name)
|
||||
|
||||
for _, f := range d.fields {
|
||||
if reg.MatchString(f) {
|
||||
|
||||
@@ -95,7 +95,7 @@ func parseAllColumns(in string) ([]string, error) {
|
||||
}
|
||||
return nil, fmt.Errorf("unexpected token: %s", string(s[i]))
|
||||
case parseAllColumnsState_State_End:
|
||||
break
|
||||
continue // avoid SA4011
|
||||
}
|
||||
}
|
||||
if state != parseAllColumnsState_State_End {
|
||||
|
||||
@@ -313,6 +313,41 @@ func TestRemoveConstraint(t *testing.T) {
|
||||
success: true,
|
||||
expect: []string{"`id` integer NOT NULL"},
|
||||
},
|
||||
{
|
||||
name: "lowercase",
|
||||
fields: []string{"`id` integer NOT NULL", "constraint `fk_users_notes` FOREIGN KEY (`user_id`) REFERENCES `users`(`id`))"},
|
||||
cName: "fk_users_notes",
|
||||
success: true,
|
||||
expect: []string{"`id` integer NOT NULL"},
|
||||
},
|
||||
{
|
||||
name: "mixed_case",
|
||||
fields: []string{"`id` integer NOT NULL", "cOnsTraiNT `fk_users_notes` FOREIGN KEY (`user_id`) REFERENCES `users`(`id`))"},
|
||||
cName: "fk_users_notes",
|
||||
success: true,
|
||||
expect: []string{"`id` integer NOT NULL"},
|
||||
},
|
||||
{
|
||||
name: "newline",
|
||||
fields: []string{"`id` integer NOT NULL", "CONSTRAINT `fk_users_notes`\nFOREIGN KEY (`user_id`) REFERENCES `users`(`id`))"},
|
||||
cName: "fk_users_notes",
|
||||
success: true,
|
||||
expect: []string{"`id` integer NOT NULL"},
|
||||
},
|
||||
{
|
||||
name: "lots_of_newlines",
|
||||
fields: []string{"`id` integer NOT NULL", "constraint \n fk_users_notes \n FOREIGN KEY (`user_id`) REFERENCES `users`(`id`))"},
|
||||
cName: "fk_users_notes",
|
||||
success: true,
|
||||
expect: []string{"`id` integer NOT NULL"},
|
||||
},
|
||||
{
|
||||
name: "no_backtick",
|
||||
fields: []string{"`id` integer NOT NULL", "CONSTRAINT fk_users_notes FOREIGN KEY (`user_id`) REFERENCES `users`(`id`))"},
|
||||
cName: "fk_users_notes",
|
||||
success: true,
|
||||
expect: []string{"`id` integer NOT NULL"},
|
||||
},
|
||||
{
|
||||
name: "check",
|
||||
fields: []string{"CONSTRAINT `name_checker` CHECK (`name` <> 'thetadev')", "`id` integer NOT NULL"},
|
||||
|
||||
@@ -5,8 +5,8 @@ go 1.23.0
|
||||
toolchain go1.24.0
|
||||
|
||||
require (
|
||||
github.com/ncruces/go-sqlite3 v0.24.0
|
||||
gorm.io/gorm v1.25.12
|
||||
github.com/ncruces/go-sqlite3 v0.26.3
|
||||
gorm.io/gorm v1.30.0
|
||||
)
|
||||
|
||||
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.30.0 // indirect
|
||||
golang.org/x/text v0.22.0 // indirect
|
||||
golang.org/x/sys v0.34.0 // indirect
|
||||
golang.org/x/text v0.27.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.24.0 h1:Z4jfmzu2NCd4SmyFwLT2OmF3EnTZbqwATvdiuNHNhLA=
|
||||
github.com/ncruces/go-sqlite3 v0.24.0/go.mod h1:/Vs8ACZHjJ1SA6E9RZUn3EyB1OP3nDQ4z/ar+0fplTQ=
|
||||
github.com/ncruces/go-sqlite3 v0.26.3 h1:WFkQj4KNMhbqiBPGDrVpK74w1DzcxQu3wYpmdWAvfYM=
|
||||
github.com/ncruces/go-sqlite3 v0.26.3/go.mod h1:XFTPtFIo1DmGCh+XVP8KGn9b/o2f+z0WZuT09x2N6eo=
|
||||
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.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
|
||||
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM=
|
||||
golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=
|
||||
gorm.io/gorm v1.25.12 h1:I0u8i2hWQItBq1WfE0o2+WuL9+8L21K9e2HHSTE/0f8=
|
||||
gorm.io/gorm v1.25.12/go.mod h1:xh7N7RHfYlNc5EmcI/El95gXusucDrQnHXe0+CgWcLQ=
|
||||
golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA=
|
||||
golang.org/x/sys v0.34.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=
|
||||
gorm.io/gorm v1.30.0 h1:qbT5aPv1UH8gI99OsRlvDToLxW5zR7FzS9acZDOZcgs=
|
||||
gorm.io/gorm v1.30.0/go.mod h1:8Z33v652h4//uMA76KjeDH8mJXPm1QNCYrMeatR0DOE=
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
// Package gormlite provides a GORM driver for SQLite.
|
||||
package gormlite
|
||||
|
||||
import (
|
||||
@@ -52,7 +51,9 @@ func (dialector _Dialector) Initialize(db *gorm.DB) (err error) {
|
||||
})
|
||||
|
||||
for k, v := range dialector.ClauseBuilders() {
|
||||
db.ClauseBuilders[k] = v
|
||||
if _, ok := db.ClauseBuilders[k]; !ok {
|
||||
db.ClauseBuilders[k] = v
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ rm -rf gorm/ tests/
|
||||
go work use -r .
|
||||
go test
|
||||
|
||||
git clone --branch v1.25.12 --filter=blob:none https://github.com/go-gorm/gorm.git
|
||||
git clone --branch v1.30.0 --filter=blob:none https://github.com/go-gorm/gorm.git
|
||||
mv gorm/tests tests
|
||||
rm -rf gorm/
|
||||
|
||||
|
||||
@@ -3,13 +3,12 @@ set -euo pipefail
|
||||
|
||||
cd -P -- "$(dirname -- "$0")"
|
||||
|
||||
curl -#OL "https://github.com/go-gorm/sqlite/raw/v1.5.7/ddlmod.go"
|
||||
curl -#OL "https://github.com/go-gorm/sqlite/raw/v1.5.7/ddlmod_test.go"
|
||||
curl -#OL "https://github.com/go-gorm/sqlite/raw/v1.5.7/ddlmod_parse_all_columns.go"
|
||||
curl -#OL "https://github.com/go-gorm/sqlite/raw/v1.5.7/ddlmod_parse_all_columns_test.go"
|
||||
curl -#OL "https://github.com/go-gorm/sqlite/raw/v1.5.7/error_translator.go"
|
||||
curl -#OL "https://github.com/go-gorm/sqlite/raw/v1.5.7/migrator.go"
|
||||
curl -#OL "https://github.com/go-gorm/sqlite/raw/v1.5.7/sqlite.go"
|
||||
curl -#OL "https://github.com/go-gorm/sqlite/raw/v1.5.7/sqlite_test.go"
|
||||
curl -#OL "https://github.com/go-gorm/sqlite/raw/v1.5.7/sqlite_test.go"
|
||||
curl -#OL "https://github.com/go-gorm/sqlite/raw/v1.6.0/ddlmod.go"
|
||||
curl -#OL "https://github.com/go-gorm/sqlite/raw/v1.6.0/ddlmod_test.go"
|
||||
curl -#OL "https://github.com/go-gorm/sqlite/raw/v1.6.0/ddlmod_parse_all_columns.go"
|
||||
curl -#OL "https://github.com/go-gorm/sqlite/raw/v1.6.0/ddlmod_parse_all_columns_test.go"
|
||||
curl -#OL "https://github.com/go-gorm/sqlite/raw/v1.6.0/error_translator.go"
|
||||
curl -#OL "https://github.com/go-gorm/sqlite/raw/v1.6.0/migrator.go"
|
||||
curl -#OL "https://github.com/go-gorm/sqlite/raw/v1.6.0/sqlite.go"
|
||||
curl -#OL "https://github.com/go-gorm/sqlite/raw/v1.6.0/sqlite_test.go"
|
||||
curl -#L "https://github.com/glebarez/sqlite/raw/v1.11.0/sqlite_error_translator_test.go" > error_translator_test.go
|
||||
@@ -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
|
||||
@@ -183355,7 +183355,7 @@
|
||||
@@ -184433,7 +184433,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-3490100.zip"
|
||||
curl -#OL "https://sqlite.org/2025/sqlite-amalgamation-3500200.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.49.1/ext/misc/anycollseq.c"
|
||||
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.49.1/ext/misc/base64.c"
|
||||
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.49.1/ext/misc/decimal.c"
|
||||
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.49.1/ext/misc/ieee754.c"
|
||||
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.49.1/ext/misc/regexp.c"
|
||||
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.49.1/ext/misc/series.c"
|
||||
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.49.1/ext/misc/spellfix.c"
|
||||
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.49.1/ext/misc/uint.c"
|
||||
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.50.2/ext/misc/anycollseq.c"
|
||||
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.50.2/ext/misc/base64.c"
|
||||
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.50.2/ext/misc/decimal.c"
|
||||
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.50.2/ext/misc/ieee754.c"
|
||||
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.50.2/ext/misc/regexp.c"
|
||||
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.50.2/ext/misc/series.c"
|
||||
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.50.2/ext/misc/spellfix.c"
|
||||
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.50.2/ext/misc/uint.c"
|
||||
cd ~-
|
||||
|
||||
cd ../vfs/tests/mptest/testdata/
|
||||
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.49.1/mptest/config01.test"
|
||||
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.49.1/mptest/config02.test"
|
||||
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.49.1/mptest/crash01.test"
|
||||
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.49.1/mptest/crash02.subtest"
|
||||
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.49.1/mptest/multiwrite01.test"
|
||||
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.50.2/mptest/config01.test"
|
||||
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.50.2/mptest/config02.test"
|
||||
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.50.2/mptest/crash01.test"
|
||||
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.50.2/mptest/crash02.subtest"
|
||||
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.50.2/mptest/multiwrite01.test"
|
||||
cd ~-
|
||||
|
||||
cd ../vfs/tests/mptest/wasm/
|
||||
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.49.1/mptest/mptest.c"
|
||||
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.50.2/mptest/mptest.c"
|
||||
cd ~-
|
||||
|
||||
cd ../vfs/tests/speedtest1/wasm/
|
||||
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.49.1/test/speedtest1.c"
|
||||
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.50.2/test/speedtest1.c"
|
||||
cd ~-
|
||||
|
||||
cat *.patch | patch -p0 --no-backup-if-mismatch
|
||||
12
sqlite3/libc/benchmark.sh
Executable file
12
sqlite3/libc/benchmark.sh
Executable file
@@ -0,0 +1,12 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
cd -P -- "$(dirname -- "$0")"
|
||||
|
||||
touch empty.S
|
||||
./build.sh empty.S
|
||||
go test -bench=.
|
||||
rm -f empty.S
|
||||
|
||||
./build.sh
|
||||
go test -bench=.
|
||||
51
sqlite3/libc/build.sh
Executable file
51
sqlite3/libc/build.sh
Executable file
@@ -0,0 +1,51 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
cd -P -- "$(dirname -- "$0")"
|
||||
|
||||
ROOT=../../
|
||||
BINARYEN="$ROOT/tools/binaryen/bin"
|
||||
WASI_SDK="$ROOT/tools/wasi-sdk/bin"
|
||||
SRCS="${1:-libc.c}"
|
||||
"../tools.sh"
|
||||
|
||||
trap 'rm -f libc.c libc.tmp' EXIT
|
||||
cat << EOF > libc.c
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
EOF
|
||||
|
||||
"$WASI_SDK/clang" --target=wasm32-wasi -std=c23 -g0 -O2 \
|
||||
-Wall -Wextra -Wno-unused-parameter -Wno-unused-function \
|
||||
-o libc.wasm -I. "$SRCS" \
|
||||
-mexec-model=reactor \
|
||||
-mmutable-globals -mnontrapping-fptoint \
|
||||
-msimd128 -mbulk-memory -msign-ext \
|
||||
-mreference-types -mmultivalue \
|
||||
-fno-stack-protector -fno-stack-clash-protection \
|
||||
-Wl,-z,stack-size=4096 \
|
||||
-Wl,--stack-first \
|
||||
-Wl,--import-undefined \
|
||||
-Wl,--initial-memory=16777216 \
|
||||
-Wl,--export=memchr \
|
||||
-Wl,--export=memcmp \
|
||||
-Wl,--export=memcpy \
|
||||
-Wl,--export=memmove \
|
||||
-Wl,--export=memrchr \
|
||||
-Wl,--export=memset \
|
||||
-Wl,--export=strchr \
|
||||
-Wl,--export=strchrnul \
|
||||
-Wl,--export=strcspn \
|
||||
-Wl,--export=strlen \
|
||||
-Wl,--export=strrchr \
|
||||
-Wl,--export=strspn \
|
||||
-Wl,--export=qsort
|
||||
|
||||
"$BINARYEN/wasm-ctor-eval" -g -c _initialize libc.wasm -o libc.tmp
|
||||
"$BINARYEN/wasm-opt" -g --strip --strip-producers -c -O3 \
|
||||
libc.tmp -o libc.wasm \
|
||||
--enable-mutable-globals --enable-nontrapping-float-to-int \
|
||||
--enable-simd --enable-bulk-memory --enable-sign-ext \
|
||||
--enable-reference-types --enable-multivalue
|
||||
|
||||
"$BINARYEN/wasm-dis" -o libc.wat libc.wasm
|
||||
BIN
sqlite3/libc/libc.wasm
Executable file
BIN
sqlite3/libc/libc.wasm
Executable file
Binary file not shown.
1859
sqlite3/libc/libc.wat
Normal file
1859
sqlite3/libc/libc.wat
Normal file
File diff suppressed because it is too large
Load Diff
840
sqlite3/libc/libc_test.go
Normal file
840
sqlite3/libc/libc_test.go
Normal file
@@ -0,0 +1,840 @@
|
||||
package libc
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
_ "embed"
|
||||
"math"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/tetratelabs/wazero"
|
||||
"github.com/tetratelabs/wazero/api"
|
||||
)
|
||||
|
||||
//go:embed libc.wasm
|
||||
var binary []byte
|
||||
|
||||
const (
|
||||
page = 64 * 1024
|
||||
size = 1024 * 1024 * 4
|
||||
ptr1 = 1024 * 1024
|
||||
ptr2 = ptr1 + size
|
||||
)
|
||||
|
||||
var (
|
||||
memory []byte
|
||||
module api.Module
|
||||
memset api.Function
|
||||
memcpy api.Function
|
||||
memchr api.Function
|
||||
memcmp api.Function
|
||||
strlen api.Function
|
||||
strchr api.Function
|
||||
strspn api.Function
|
||||
strrchr api.Function
|
||||
strcspn api.Function
|
||||
stack [8]uint64
|
||||
)
|
||||
|
||||
func call(fn api.Function, arg ...uint64) uint64 {
|
||||
copy(stack[:], arg)
|
||||
err := fn.CallWithStack(context.Background(), stack[:])
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return stack[0]
|
||||
}
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
ctx := context.Background()
|
||||
|
||||
runtime := wazero.NewRuntime(ctx)
|
||||
mod, err := runtime.Instantiate(ctx, binary)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
module = mod
|
||||
memset = mod.ExportedFunction("memset")
|
||||
memcpy = mod.ExportedFunction("memcpy")
|
||||
memchr = mod.ExportedFunction("memchr")
|
||||
memcmp = mod.ExportedFunction("memcmp")
|
||||
strlen = mod.ExportedFunction("strlen")
|
||||
strchr = mod.ExportedFunction("strchr")
|
||||
strspn = mod.ExportedFunction("strspn")
|
||||
strrchr = mod.ExportedFunction("strrchr")
|
||||
strcspn = mod.ExportedFunction("strcspn")
|
||||
memory, _ = mod.Memory().Read(0, mod.Memory().Size())
|
||||
|
||||
os.Exit(m.Run())
|
||||
}
|
||||
|
||||
func Benchmark_memset(b *testing.B) {
|
||||
clear(memory)
|
||||
|
||||
b.SetBytes(size)
|
||||
b.ResetTimer()
|
||||
for range b.N {
|
||||
call(memset, ptr1, 3, size)
|
||||
}
|
||||
}
|
||||
|
||||
func Benchmark_memcpy(b *testing.B) {
|
||||
clear(memory)
|
||||
fill(memory[ptr2:ptr2+size], 5)
|
||||
|
||||
b.SetBytes(size)
|
||||
b.ResetTimer()
|
||||
for range b.N {
|
||||
call(memcpy, ptr1, ptr2, size)
|
||||
}
|
||||
}
|
||||
|
||||
func Benchmark_strlen(b *testing.B) {
|
||||
clear(memory)
|
||||
fill(memory[ptr1:ptr1+size-1], 5)
|
||||
|
||||
b.SetBytes(size)
|
||||
b.ResetTimer()
|
||||
for range b.N {
|
||||
call(strlen, ptr1)
|
||||
}
|
||||
}
|
||||
|
||||
func Benchmark_memchr(b *testing.B) {
|
||||
clear(memory)
|
||||
fill(memory[ptr1:ptr1+size/2], 7)
|
||||
fill(memory[ptr1+size/2:ptr1+size], 5)
|
||||
|
||||
b.SetBytes(size/2 + 1)
|
||||
b.ResetTimer()
|
||||
for range b.N {
|
||||
call(memchr, ptr1, 5, size)
|
||||
}
|
||||
}
|
||||
|
||||
func Benchmark_strchr(b *testing.B) {
|
||||
clear(memory)
|
||||
fill(memory[ptr1:ptr1+size/2], 7)
|
||||
fill(memory[ptr1+size/2:ptr1+size-1], 5)
|
||||
|
||||
b.SetBytes(size/2 + 1)
|
||||
b.ResetTimer()
|
||||
for range b.N {
|
||||
call(strchr, ptr1, 5)
|
||||
}
|
||||
}
|
||||
|
||||
func Benchmark_strrchr(b *testing.B) {
|
||||
clear(memory)
|
||||
fill(memory[ptr1:ptr1+size/2], 5)
|
||||
fill(memory[ptr1+size/2:ptr1+size-1], 7)
|
||||
|
||||
b.SetBytes(size/2 + 1)
|
||||
b.ResetTimer()
|
||||
for range b.N {
|
||||
call(strrchr, ptr1, 5)
|
||||
}
|
||||
}
|
||||
|
||||
func Benchmark_memcmp(b *testing.B) {
|
||||
clear(memory)
|
||||
fill(memory[ptr1:ptr1+size], 7)
|
||||
fill(memory[ptr2:ptr2+size/2], 7)
|
||||
fill(memory[ptr2+size/2:ptr2+size], 5)
|
||||
|
||||
b.SetBytes(size/2 + 1)
|
||||
b.ResetTimer()
|
||||
for range b.N {
|
||||
call(memcmp, ptr1, ptr2, size)
|
||||
}
|
||||
}
|
||||
|
||||
func Benchmark_strspn(b *testing.B) {
|
||||
clear(memory)
|
||||
fill(memory[ptr1:ptr1+size/2], 7)
|
||||
fill(memory[ptr1+size/2:ptr1+size-1], 5)
|
||||
memory[ptr2+0] = 3
|
||||
memory[ptr2+1] = 5
|
||||
memory[ptr2+2] = 7
|
||||
memory[ptr2+3] = 9
|
||||
|
||||
b.SetBytes(size)
|
||||
b.ResetTimer()
|
||||
for range b.N {
|
||||
call(strspn, ptr1, ptr2)
|
||||
}
|
||||
}
|
||||
|
||||
func Benchmark_strcspn(b *testing.B) {
|
||||
clear(memory)
|
||||
fill(memory[ptr1:ptr1+size/2], 7)
|
||||
fill(memory[ptr1+size/2:ptr1+size-1], 5)
|
||||
memory[ptr2+0] = 3
|
||||
memory[ptr2+1] = 9
|
||||
|
||||
b.SetBytes(size)
|
||||
b.ResetTimer()
|
||||
for range b.N {
|
||||
call(strcspn, ptr1, ptr2)
|
||||
}
|
||||
}
|
||||
|
||||
func Test_strlen(t *testing.T) {
|
||||
for length := range 64 {
|
||||
for alignment := range 24 {
|
||||
ptr := (page - 8) + alignment
|
||||
|
||||
clear(memory[:2*page])
|
||||
fill(memory[ptr:ptr+length], 5)
|
||||
|
||||
got := call(strlen, uint64(ptr))
|
||||
if uint32(got) != uint32(length) {
|
||||
t.Errorf("strlen(%d) = %d, want %d",
|
||||
ptr, uint32(got), uint32(length))
|
||||
}
|
||||
|
||||
memory[ptr-1] = 5
|
||||
got = call(strlen, uint64(ptr))
|
||||
if uint32(got) != uint32(length) {
|
||||
t.Errorf("strlen(%d) = %d, want %d",
|
||||
ptr, uint32(got), uint32(length))
|
||||
}
|
||||
}
|
||||
|
||||
clear(memory)
|
||||
ptr := len(memory) - length - 1
|
||||
fill(memory[ptr:ptr+length], 5)
|
||||
|
||||
got := call(strlen, uint64(ptr))
|
||||
if uint32(got) != uint32(length) {
|
||||
t.Errorf("strlen(%d) = %d, want %d",
|
||||
ptr, uint32(got), uint32(length))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func Test_memchr(t *testing.T) {
|
||||
for length := range 64 {
|
||||
for pos := range length + 2 {
|
||||
for alignment := range 24 {
|
||||
ptr := (page - 8) + alignment
|
||||
want := 0
|
||||
if pos < length {
|
||||
want = ptr + pos
|
||||
}
|
||||
|
||||
clear(memory[:2*page])
|
||||
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",
|
||||
ptr, 7, uint64(length), uint32(got), uint32(want))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
clear(memory)
|
||||
ptr := len(memory) - length
|
||||
fill(memory[ptr:ptr+length], 5)
|
||||
memory[len(memory)-1] = 7
|
||||
|
||||
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))
|
||||
if uint32(got) != uint32(want) {
|
||||
t.Errorf("memchr(%d, %d, %d) = %d, want %d",
|
||||
ptr, 7, uint64(length), uint32(got), uint32(want))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func Test_strchr(t *testing.T) {
|
||||
for length := range 64 {
|
||||
for pos := range length + 2 {
|
||||
for alignment := range 24 {
|
||||
ptr := (page - 8) + alignment
|
||||
want := 0
|
||||
if pos < length {
|
||||
want = ptr + pos
|
||||
}
|
||||
|
||||
clear(memory[:2*page])
|
||||
fill(memory[ptr:ptr+max(pos, length)], 5)
|
||||
memory[ptr+pos] = 7
|
||||
memory[ptr+pos+1] = 7
|
||||
memory[ptr+length] = 0
|
||||
|
||||
got := call(strchr, uint64(ptr), 7)
|
||||
if uint32(got) != uint32(want) {
|
||||
t.Errorf("strchr(%d, %d) = %d, want %d",
|
||||
ptr, 7, uint32(got), uint32(want))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
clear(memory)
|
||||
ptr := len(memory) - length
|
||||
fill(memory[ptr:ptr+length], 5)
|
||||
memory[len(memory)-1] = 7
|
||||
|
||||
want := len(memory) - 1
|
||||
if length == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
got := call(strchr, uint64(ptr), 7)
|
||||
if uint32(got) != uint32(want) {
|
||||
t.Errorf("strchr(%d, %d) = %d, want %d",
|
||||
ptr, 7, uint32(got), uint32(want))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func Test_strrchr(t *testing.T) {
|
||||
for length := range 64 {
|
||||
for pos := range length + 2 {
|
||||
for alignment := range 24 {
|
||||
ptr := (page - 8) + alignment
|
||||
want := 0
|
||||
if pos < length {
|
||||
want = ptr + pos
|
||||
} else if length > 0 {
|
||||
want = ptr
|
||||
}
|
||||
|
||||
clear(memory[:2*page])
|
||||
fill(memory[ptr:ptr+max(pos, length)], 5)
|
||||
memory[ptr] = 7
|
||||
memory[ptr+pos] = 7
|
||||
memory[ptr+length] = 0
|
||||
|
||||
got := call(strrchr, uint64(ptr), 7)
|
||||
if uint32(got) != uint32(want) {
|
||||
t.Errorf("strrchr(%d, %d) = %d, want %d",
|
||||
ptr, 7, uint32(got), uint32(want))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ptr := len(memory) - length
|
||||
want := len(memory) - 2
|
||||
if length <= 1 {
|
||||
continue
|
||||
}
|
||||
|
||||
clear(memory)
|
||||
fill(memory[ptr:ptr+length], 5)
|
||||
memory[ptr] = 7
|
||||
memory[len(memory)-2] = 7
|
||||
memory[len(memory)-1] = 0
|
||||
|
||||
got := call(strrchr, uint64(ptr), 7)
|
||||
if uint32(got) != uint32(want) {
|
||||
t.Errorf("strrchr(%d, %d) = %d, want %d",
|
||||
ptr, 7, uint32(got), uint32(want))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const (
|
||||
compareTest1 = "" +
|
||||
"\x94\x63\x8f\x01\x74\x63\x8f\x01\x54\x63\x8f\x01\x34\x63\x8f\x01" +
|
||||
"\xb4\xf2\x93\x01\x94\xf2\x93\x01\x54\xf1\x93\x01\x34\xf1\x93\x01" +
|
||||
"\x14\xf1\x93\x01\x14\xf2\x93\x01\x34\xf2\x93\x01\x54\xf2\x93\x01" +
|
||||
"\x74\xf2\x93\x01\x74\xf1\x93\x01\xd4\xf2\x93\x01\x94\xf1\x93\x01" +
|
||||
"\xb4\xf1\x93\x01\xd4\xf1\x93\x01\xf4\xf1\x93\x01\xf4\xf2\x93\x01" +
|
||||
"\x14\xf4\x93\x01\xf4\xf3\x93\x01\xd4\xf3\x93\x01\xb4\xf3\x93\x01" +
|
||||
"\x94\xf3\x93\x01\x74\x80\x93\x01\x54\xf3\x93\x01\x34\xf3\x93\x01" +
|
||||
"\x7f\xf3\x93\x01\x00\x01"
|
||||
compareTest2 = "" +
|
||||
"\x94\x63\x8f\x01\x74\x63\x8f\x01\x54\x63\x8f\x01\x34\x63\x8f\x01" +
|
||||
"\xb4\xf2\x93\x01\x94\xf2\x93\x01\x54\xf1\x93\x01\x34\xf1\x93\x01" +
|
||||
"\x14\xf1\x93\x01\x14\xf2\x93\x01\x34\xf2\x93\x01\x54\xf2\x93\x01" +
|
||||
"\x74\xf2\x93\x01\x74\xf1\x93\x01\xd4\xf2\x93\x01\x94\xf1\x93\x01" +
|
||||
"\xb4\xf1\x93\x01\xd4\xf1\x93\x01\xf4\xf1\x93\x01\xf4\xf2\x93\x01" +
|
||||
"\xbc\x40\x96\x01\xf4\xf3\x93\x01\xd4\xf3\x93\x01\xb4\xf3\x93\x01" +
|
||||
"\x94\xf3\x93\x01\x74\x7f\x93\x01\x54\xf3\x93\x01\x34\xf3\x93\x01" +
|
||||
"\x80\xf3\x93\x01\x00\x02"
|
||||
)
|
||||
|
||||
func Test_memcmp(t *testing.T) {
|
||||
const s1 = compareTest1
|
||||
const s2 = compareTest2
|
||||
|
||||
ptr2 := len(memory) - len(s2)
|
||||
|
||||
clear(memory)
|
||||
copy(memory[ptr1:], s1)
|
||||
copy(memory[ptr2:], s2)
|
||||
|
||||
for i := range len(s1) + 1 {
|
||||
for j := range len(s1) - i {
|
||||
want := strings.Compare(s1[i:i+j], s2[i:i+j])
|
||||
got := call(memcmp, uint64(ptr1+i), uint64(ptr2+i), uint64(j))
|
||||
if sign(int32(got)) != want {
|
||||
t.Errorf("strcmp(%d, %d, %d) = %d, want %d",
|
||||
ptr1+i, ptr2+i, j, int32(got), want)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func Test_strspn(t *testing.T) {
|
||||
for length := range 64 {
|
||||
for pos := range length + 2 {
|
||||
for alignment := range 24 {
|
||||
ptr := (page - 8) + alignment
|
||||
want := min(pos, length)
|
||||
|
||||
clear(memory[:2*page])
|
||||
fill(memory[ptr:ptr+max(pos, length)], 5)
|
||||
memory[ptr+pos] = 7
|
||||
memory[ptr+length] = 0
|
||||
memory[128] = 7 | 128
|
||||
memory[129] = 5
|
||||
|
||||
got := call(strspn, uint64(ptr), 129)
|
||||
if uint32(got) != uint32(want) {
|
||||
t.Errorf("strspn(%d, %d) = %d, want %d",
|
||||
ptr, 129, uint32(got), uint32(want))
|
||||
}
|
||||
|
||||
got = call(strspn, uint64(ptr), 128)
|
||||
if uint32(got) != uint32(want) {
|
||||
t.Errorf("strspn(%d, %d) = %d, want %d",
|
||||
ptr, 128, uint32(got), uint32(want))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ptr := len(memory) - length
|
||||
want := length - 1
|
||||
if length == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
clear(memory)
|
||||
fill(memory[ptr:ptr+length], 5)
|
||||
memory[len(memory)-1] = 7
|
||||
memory[128] = 7 | 128
|
||||
memory[129] = 5
|
||||
|
||||
got := call(strspn, uint64(ptr), 129)
|
||||
if uint32(got) != uint32(want) {
|
||||
t.Errorf("strspn(%d, %d) = %d, want %d",
|
||||
ptr, 129, uint32(got), uint32(want))
|
||||
}
|
||||
|
||||
got = call(strspn, uint64(ptr), 128)
|
||||
if uint32(got) != uint32(want) {
|
||||
t.Errorf("strspn(%d, %d) = %d, want %d",
|
||||
ptr, 128, uint32(got), uint32(want))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func Test_strcspn(t *testing.T) {
|
||||
for length := range 64 {
|
||||
for pos := range length + 2 {
|
||||
for alignment := range 24 {
|
||||
ptr := (page - 8) + alignment
|
||||
want := min(pos, length)
|
||||
|
||||
clear(memory[:2*page])
|
||||
fill(memory[ptr:ptr+max(pos, length)], 5)
|
||||
memory[ptr+pos] = 7
|
||||
memory[ptr+length] = 0
|
||||
memory[128] = 5 | 128
|
||||
memory[129] = 7
|
||||
|
||||
got := call(strcspn, uint64(ptr), 129)
|
||||
if uint32(got) != uint32(want) {
|
||||
t.Errorf("strcspn(%d, %d) = %d, want %d",
|
||||
ptr, 129, uint32(got), uint32(want))
|
||||
}
|
||||
|
||||
got = call(strcspn, uint64(ptr), 128)
|
||||
if uint32(got) != uint32(want) {
|
||||
t.Errorf("strcspn(%d, %d) = %d, want %d",
|
||||
ptr, 128, uint32(got), uint32(want))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ptr := len(memory) - length
|
||||
want := length - 1
|
||||
if length == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
clear(memory)
|
||||
fill(memory[ptr:ptr+length], 5)
|
||||
memory[len(memory)-1] = 7
|
||||
memory[128] = 5 | 128
|
||||
memory[129] = 7
|
||||
|
||||
got := call(strcspn, uint64(ptr), 129)
|
||||
if uint32(got) != uint32(want) {
|
||||
t.Errorf("strcspn(%d, %d) = %d, want %d",
|
||||
ptr, 129, uint32(got), uint32(want))
|
||||
}
|
||||
|
||||
got = call(strcspn, uint64(ptr), 128)
|
||||
if uint32(got) != uint32(want) {
|
||||
t.Errorf("strcspn(%d, %d) = %d, want %d",
|
||||
ptr, 128, uint32(got), uint32(want))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type searchTest struct {
|
||||
haystk string
|
||||
needle string
|
||||
out int
|
||||
}
|
||||
|
||||
var searchTests = []searchTest{
|
||||
{"", "", 0},
|
||||
{"", "a", -1},
|
||||
{"", "foo", -1},
|
||||
{"fo", "foo", -1},
|
||||
{"foo", "baz", -1},
|
||||
{"foo", "foo", 0},
|
||||
{"oofofoofooo", "f", 2},
|
||||
{"oofofoofooo", "foo", 4},
|
||||
{"barfoobarfoo", "foo", 3},
|
||||
{"foo", "", 0},
|
||||
{"foo", "o", 1},
|
||||
{"jrzm6jjhorimglljrea4w3rlgosts0w2gia17hno2td4qd1jz", "jz", 47},
|
||||
{"ekkuk5oft4eq0ocpacknhwouic1uua46unx12l37nioq9wbpnocqks6", "ks6", 52},
|
||||
{"999f2xmimunbuyew5vrkla9cpwhmxan8o98ec", "98ec", 33},
|
||||
{"9lpt9r98i04k8bz6c6dsrthb96bhi", "96bhi", 24},
|
||||
{"55u558eqfaod2r2gu42xxsu631xf0zobs5840vl", "5840vl", 33},
|
||||
{"", "a", -1},
|
||||
{"x", "a", -1},
|
||||
{"x", "x", 0},
|
||||
{"abc", "a", 0},
|
||||
{"abc", "b", 1},
|
||||
{"abc", "c", 2},
|
||||
{"abc", "x", -1},
|
||||
{"", "ab", -1},
|
||||
{"bc", "ab", -1},
|
||||
{"ab", "ab", 0},
|
||||
{"xab", "ab", 1},
|
||||
{"xab"[:2], "ab", -1},
|
||||
{"", "abc", -1},
|
||||
{"xbc", "abc", -1},
|
||||
{"abc", "abc", 0},
|
||||
{"xabc", "abc", 1},
|
||||
{"xabc"[:3], "abc", -1},
|
||||
{"xabxc", "abc", -1},
|
||||
{"", "abcd", -1},
|
||||
{"xbcd", "abcd", -1},
|
||||
{"abcd", "abcd", 0},
|
||||
{"xabcd", "abcd", 1},
|
||||
{"xyabcd"[:5], "abcd", -1},
|
||||
{"xbcqq", "abcqq", -1},
|
||||
{"abcqq", "abcqq", 0},
|
||||
{"xabcqq", "abcqq", 1},
|
||||
{"xyabcqq"[:6], "abcqq", -1},
|
||||
{"xabxcqq", "abcqq", -1},
|
||||
{"xabcqxq", "abcqq", -1},
|
||||
{"", "01234567", -1},
|
||||
{"32145678", "01234567", -1},
|
||||
{"01234567", "01234567", 0},
|
||||
{"x01234567", "01234567", 1},
|
||||
{"x0123456x01234567", "01234567", 9},
|
||||
{"xx01234567"[:9], "01234567", -1},
|
||||
{"", "0123456789", -1},
|
||||
{"3214567844", "0123456789", -1},
|
||||
{"0123456789", "0123456789", 0},
|
||||
{"x0123456789", "0123456789", 1},
|
||||
{"x012345678x0123456789", "0123456789", 11},
|
||||
{"xyz0123456789"[:12], "0123456789", -1},
|
||||
{"x01234567x89", "0123456789", -1},
|
||||
{"", "0123456789012345", -1},
|
||||
{"3214567889012345", "0123456789012345", -1},
|
||||
{"0123456789012345", "0123456789012345", 0},
|
||||
{"x0123456789012345", "0123456789012345", 1},
|
||||
{"x012345678901234x0123456789012345", "0123456789012345", 17},
|
||||
{"", "01234567890123456789", -1},
|
||||
{"32145678890123456789", "01234567890123456789", -1},
|
||||
{"01234567890123456789", "01234567890123456789", 0},
|
||||
{"x01234567890123456789", "01234567890123456789", 1},
|
||||
{"x0123456789012345678x01234567890123456789", "01234567890123456789", 21},
|
||||
{"xyz01234567890123456789"[:22], "01234567890123456789", -1},
|
||||
{"", "0123456789012345678901234567890", -1},
|
||||
{"321456788901234567890123456789012345678911", "0123456789012345678901234567890", -1},
|
||||
{"0123456789012345678901234567890", "0123456789012345678901234567890", 0},
|
||||
{"x0123456789012345678901234567890", "0123456789012345678901234567890", 1},
|
||||
{"x012345678901234567890123456789x0123456789012345678901234567890", "0123456789012345678901234567890", 32},
|
||||
{"xyz0123456789012345678901234567890"[:33], "0123456789012345678901234567890", -1},
|
||||
{"", "01234567890123456789012345678901", -1},
|
||||
{"32145678890123456789012345678901234567890211", "01234567890123456789012345678901", -1},
|
||||
{"01234567890123456789012345678901", "01234567890123456789012345678901", 0},
|
||||
{"x01234567890123456789012345678901", "01234567890123456789012345678901", 1},
|
||||
{"x0123456789012345678901234567890x01234567890123456789012345678901", "01234567890123456789012345678901", 33},
|
||||
{"xyz01234567890123456789012345678901"[:34], "01234567890123456789012345678901", -1},
|
||||
{"xxxxxx012345678901234567890123456789012345678901234567890123456789012", "012345678901234567890123456789012345678901234567890123456789012", 6},
|
||||
{"", "0123456789012345678901234567890123456789", -1},
|
||||
{"xx012345678901234567890123456789012345678901234567890123456789012", "0123456789012345678901234567890123456789", 2},
|
||||
{"xx012345678901234567890123456789012345678901234567890123456789012"[:41], "0123456789012345678901234567890123456789", -1},
|
||||
{"xx012345678901234567890123456789012345678901234567890123456789012", "0123456789012345678901234567890123456xxx", -1},
|
||||
{"xx0123456789012345678901234567890123456789012345678901234567890120123456789012345678901234567890123456xxx", "0123456789012345678901234567890123456xxx", 65},
|
||||
{"barfoobarfooyyyzzzyyyzzzyyyzzzyyyxxxzzzyyy", "x", 33},
|
||||
{"fofofofooofoboo", "oo", 7},
|
||||
{"fofofofofofoboo", "ob", 11},
|
||||
{"fofofofofofoboo", "boo", 12},
|
||||
{"fofofofofofoboo", "oboo", 11},
|
||||
{"fofofofofoooboo", "fooo", 8},
|
||||
{"fofofofofofoboo", "foboo", 10},
|
||||
{"fofofofofofoboo", "fofob", 8},
|
||||
{"fofofofofofofoffofoobarfoo", "foffof", 12},
|
||||
{"fofofofofoofofoffofoobarfoo", "foffof", 13},
|
||||
{"fofofofofofofoffofoobarfoo", "foffofo", 12},
|
||||
{"fofofofofoofofoffofoobarfoo", "foffofo", 13},
|
||||
{"fofofofofoofofoffofoobarfoo", "foffofoo", 13},
|
||||
{"fofofofofofofoffofoobarfoo", "foffofoo", 12},
|
||||
{"fofofofofoofofoffofoobarfoo", "foffofoob", 13},
|
||||
{"fofofofofofofoffofoobarfoo", "foffofoob", 12},
|
||||
{"fofofofofoofofoffofoobarfoo", "foffofooba", 13},
|
||||
{"fofofofofofofoffofoobarfoo", "foffofooba", 12},
|
||||
{"fofofofofoofofoffofoobarfoo", "foffofoobar", 13},
|
||||
{"fofofofofofofoffofoobarfoo", "foffofoobar", 12},
|
||||
{"fofofofofoofofoffofoobarfoo", "foffofoobarf", 13},
|
||||
{"fofofofofofofoffofoobarfoo", "foffofoobarf", 12},
|
||||
{"fofofofofoofofoffofoobarfoo", "foffofoobarfo", 13},
|
||||
{"fofofofofofofoffofoobarfoo", "foffofoobarfo", 12},
|
||||
{"fofofofofoofofoffofoobarfoo", "foffofoobarfoo", 13},
|
||||
{"fofofofofofofoffofoobarfoo", "foffofoobarfoo", 12},
|
||||
{"fofofofofoofofoffofoobarfoo", "ofoffofoobarfoo", 12},
|
||||
{"fofofofofofofoffofoobarfoo", "ofoffofoobarfoo", 11},
|
||||
{"fofofofofoofofoffofoobarfoo", "fofoffofoobarfoo", 11},
|
||||
{"fofofofofofofoffofoobarfoo", "fofoffofoobarfoo", 10},
|
||||
{"fofofofofoofofoffofoobarfoo", "foobars", -1},
|
||||
{"foofyfoobarfoobar", "y", 4},
|
||||
{"oooooooooooooooooooooo", "r", -1},
|
||||
{"oxoxoxoxoxoxoxoxoxoxoxoy", "oy", 22},
|
||||
{"oxoxoxoxoxoxoxoxoxoxoxox", "oy", -1},
|
||||
{"oxoxoxoxoxoxoxoxoxoxox☺", "☺", 22},
|
||||
{"xx0123456789012345678901234567890123456789012345678901234567890120123456789012345678901234567890123456xxx\xed\x9f\xc0", "\xed\x9f\xc0", 105},
|
||||
{"000000000000000000000000000000000000000000000000000000000000000000000001", "0000000000000000000000000000000000000000000000000000000000000000001", 5},
|
||||
}
|
||||
|
||||
func Fuzz_memchr(f *testing.F) {
|
||||
f.Fuzz(func(t *testing.T, s string, c, i byte) {
|
||||
if len(s) > 128 || int(i) > len(s) {
|
||||
t.SkipNow()
|
||||
}
|
||||
copy(memory[ptr1:], s)
|
||||
|
||||
got := call(memchr, ptr1+uint64(i), uint64(c), uint64(len(s)-int(i)))
|
||||
want := strings.IndexByte(s[i:], c)
|
||||
if want >= 0 {
|
||||
want = ptr1 + int(i) + want
|
||||
} else {
|
||||
want = 0
|
||||
}
|
||||
|
||||
if uint32(got) != uint32(want) {
|
||||
t.Errorf("memchr(%q, %q) = %d, want %d",
|
||||
s[i:], c, uint32(got), uint32(want))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func Fuzz_strchr(f *testing.F) {
|
||||
f.Fuzz(func(t *testing.T, s string, c, i byte) {
|
||||
if len(s) > 128 || int(i) > len(s) {
|
||||
t.SkipNow()
|
||||
}
|
||||
copy(memory[ptr1:], s)
|
||||
memory[ptr1+len(s)] = 0
|
||||
|
||||
got := call(strchr, ptr1+uint64(i), uint64(c))
|
||||
want := bytes.IndexByte(term1(memory[ptr1+uint64(i):]), c)
|
||||
if want >= 0 {
|
||||
want = ptr1 + int(i) + want
|
||||
} else {
|
||||
want = 0
|
||||
}
|
||||
|
||||
if uint32(got) != uint32(want) {
|
||||
t.Errorf("strchr(%q, %q) = %d, want %d",
|
||||
s[i:], c, uint32(got), uint32(want))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func Fuzz_strrchr(f *testing.F) {
|
||||
f.Fuzz(func(t *testing.T, s string, c, i byte) {
|
||||
if len(s) > 128 || int(i) > len(s) {
|
||||
t.SkipNow()
|
||||
}
|
||||
copy(memory[ptr1:], s)
|
||||
memory[ptr1+len(s)] = 0
|
||||
|
||||
got := call(strrchr, ptr1+uint64(i), uint64(c))
|
||||
want := bytes.LastIndexByte(term1(memory[ptr1+uint64(i):]), c)
|
||||
if want >= 0 {
|
||||
want = ptr1 + int(i) + want
|
||||
} else {
|
||||
want = 0
|
||||
}
|
||||
|
||||
if uint32(got) != uint32(want) {
|
||||
t.Errorf("strrchr(%q, %q) = %d, want %d",
|
||||
s[i:], c, uint32(got), uint32(want))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func Fuzz_memcmp(f *testing.F) {
|
||||
const s1 = compareTest1
|
||||
const s2 = compareTest2
|
||||
|
||||
for i := range len(compareTest1) + 1 {
|
||||
f.Add(s1[i:], s2[i:])
|
||||
}
|
||||
|
||||
f.Fuzz(func(t *testing.T, s1, s2 string) {
|
||||
if len(s1) > 128 || len(s1) != len(s2) {
|
||||
t.SkipNow()
|
||||
}
|
||||
copy(memory[ptr1:], s1)
|
||||
copy(memory[ptr2:], s2)
|
||||
|
||||
got := call(memcmp, uint64(ptr1), uint64(ptr2), uint64(len(s1)))
|
||||
want := strings.Compare(s1, s2)
|
||||
|
||||
if sign(int32(got)) != want {
|
||||
t.Errorf("memcmp(%q, %q) = %d, want %d",
|
||||
s1, s2, uint32(got), uint32(want))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func Fuzz_strspn(f *testing.F) {
|
||||
for _, t := range searchTests {
|
||||
f.Add(t.haystk, t.needle)
|
||||
}
|
||||
|
||||
f.Fuzz(func(t *testing.T, s, chars string) {
|
||||
if len(s) > 128 || len(chars) > 128 {
|
||||
t.SkipNow()
|
||||
}
|
||||
copy(memory[ptr1:], s)
|
||||
copy(memory[ptr2:], chars)
|
||||
memory[ptr1+len(s)] = 0
|
||||
memory[ptr2+len(chars)] = 0
|
||||
|
||||
got := call(strspn, uint64(ptr1), uint64(ptr2))
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
if 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))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func Fuzz_strcspn(f *testing.F) {
|
||||
for _, t := range searchTests {
|
||||
f.Add(t.haystk, t.needle)
|
||||
}
|
||||
|
||||
f.Fuzz(func(t *testing.T, s, chars string) {
|
||||
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
|
||||
memory[ptr2+len(chars)] = 0
|
||||
|
||||
got := call(strcspn, uint64(ptr1), uint64(ptr2))
|
||||
|
||||
s = term(s)
|
||||
chars = term(chars)
|
||||
want := strings.IndexAny(s, chars)
|
||||
if want < 0 {
|
||||
want = len(s)
|
||||
}
|
||||
|
||||
if uint32(got) != uint32(want) {
|
||||
t.Errorf("strcspn(%q, %q) = %d, want %d",
|
||||
s, chars, uint32(got), uint32(want))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func sign(x int32) int {
|
||||
switch {
|
||||
case x > 0:
|
||||
return +1
|
||||
case x < 0:
|
||||
return -1
|
||||
default:
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
func fill(s []byte, v byte) {
|
||||
for i := range s {
|
||||
s[i] = v
|
||||
}
|
||||
}
|
||||
|
||||
func term[T interface{ []byte | string }](s T) T {
|
||||
for i, c := range []byte(s) {
|
||||
if c == 0 {
|
||||
return s[:i]
|
||||
}
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func term1[T interface{ []byte | string }](s T) T {
|
||||
for i, c := range []byte(s) {
|
||||
if c == 0 {
|
||||
return s[:i+1]
|
||||
}
|
||||
}
|
||||
return s
|
||||
}
|
||||
36
sqlite3/libc/math.h
Normal file
36
sqlite3/libc/math.h
Normal file
@@ -0,0 +1,36 @@
|
||||
#include_next <math.h> // the system math.h
|
||||
|
||||
#ifndef _WASM_SIMD128_MATH_H
|
||||
#define _WASM_SIMD128_MATH_H
|
||||
|
||||
#include <wasm_simd128.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#ifdef __wasm_relaxed_simd__
|
||||
|
||||
// This header assumes "relaxed fused multiply-add"
|
||||
// is both faster and more precise.
|
||||
|
||||
#define FP_FAST_FMA 1
|
||||
|
||||
__attribute__((weak))
|
||||
double fma(double x, double y, double z) {
|
||||
// If we get a software implementation from the host,
|
||||
// this is enough to short circuit it on the 2nd lane.
|
||||
const v128_t wx = wasm_f64x2_replace_lane(b, 0, x);
|
||||
const v128_t wy = wasm_f64x2_splat(y);
|
||||
const v128_t wz = wasm_f64x2_splat(z);
|
||||
const v128_t wr = wasm_f64x2_relaxed_madd(wx, wy, wz);
|
||||
return wasm_f64x2_extract_lane(wr, 0);
|
||||
}
|
||||
|
||||
#endif // __wasm_relaxed_simd__
|
||||
|
||||
#ifdef __cplusplus
|
||||
} // extern "C"
|
||||
#endif
|
||||
|
||||
#endif // _WASM_SIMD128_MATH_H
|
||||
57
sqlite3/libc/stdlib.h
Normal file
57
sqlite3/libc/stdlib.h
Normal file
@@ -0,0 +1,57 @@
|
||||
#include_next <stdlib.h> // the system stdlib.h
|
||||
|
||||
#ifndef _WASM_SIMD128_STDLIB_H
|
||||
#define _WASM_SIMD128_STDLIB_H
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
// Shellsort with Gonnet & Baeza-Yates gap sequence.
|
||||
// Simple, no recursion, doesn't use the C stack.
|
||||
// Clang auto-vectorizes the inner loop.
|
||||
|
||||
__attribute__((weak))
|
||||
void qsort(void *base, size_t nel, size_t width,
|
||||
int (*comp)(const void *, const void *)) {
|
||||
// If nel is zero, we're required to do nothing.
|
||||
// If it's one, the array is already sorted.
|
||||
size_t wnel = width * nel;
|
||||
size_t gap = nel;
|
||||
while (gap > 1) {
|
||||
// Use 64-bit unsigned arithmetic to avoid intermediate overflow.
|
||||
// Absent overflow, gap will be strictly less than its previous value.
|
||||
// Once it is one or zero, set it to one: do a final pass, and stop.
|
||||
gap = (5ull * gap - 1) / 11;
|
||||
if (gap == 0) gap = 1;
|
||||
|
||||
// It'd be undefined behavior for wnel to overflow a size_t;
|
||||
// or if width is zero: the base pointer would be invalid.
|
||||
// Since gap is stricly less than nel, we can assume
|
||||
// wgap is strictly less than wnel.
|
||||
size_t wgap = width * gap;
|
||||
__builtin_assume(wgap < wnel);
|
||||
for (size_t i = wgap; i < wnel; i += width) {
|
||||
// Even without overflow flags, the overflow builtin helps the compiler.
|
||||
for (size_t j = i; !__builtin_sub_overflow(j, wgap, &j);) {
|
||||
char *a = j + (char *)base;
|
||||
char *b = a + wgap;
|
||||
if (comp(a, b) <= 0) break;
|
||||
|
||||
// This well known loop is automatically vectorized.
|
||||
size_t s = width;
|
||||
do {
|
||||
char tmp = *a;
|
||||
*a++ = *b;
|
||||
*b++ = tmp;
|
||||
} while (--s);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef __cplusplus
|
||||
} // extern "C"
|
||||
#endif
|
||||
|
||||
#endif // _WASM_SIMD128_STDLIB_H
|
||||
476
sqlite3/libc/string.h
Normal file
476
sqlite3/libc/string.h
Normal file
@@ -0,0 +1,476 @@
|
||||
#include_next <string.h> // the system string.h
|
||||
|
||||
#ifndef _WASM_SIMD128_STRING_H
|
||||
#define _WASM_SIMD128_STRING_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <wasm_simd128.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#ifdef __wasm_bulk_memory__
|
||||
|
||||
// Use the builtins if compiled with bulk memory operations.
|
||||
// Clang will intrinsify using SIMD for small, constant N.
|
||||
|
||||
__attribute__((weak, always_inline))
|
||||
void *memset(void *dest, int c, size_t n) {
|
||||
return __builtin_memset(dest, c, n);
|
||||
}
|
||||
|
||||
__attribute__((weak, always_inline))
|
||||
void *memcpy(void *__restrict dest, const void *__restrict src, size_t n) {
|
||||
return __builtin_memcpy(dest, src, n);
|
||||
}
|
||||
|
||||
__attribute__((weak, always_inline))
|
||||
void *memmove(void *dest, const void *src, size_t n) {
|
||||
return __builtin_memmove(dest, src, n);
|
||||
}
|
||||
|
||||
#endif // __wasm_bulk_memory__
|
||||
|
||||
#ifdef __wasm_simd128__
|
||||
|
||||
__attribute__((weak))
|
||||
int memcmp(const void *vl, const void *vr, size_t n) {
|
||||
// Scalar algorithm.
|
||||
if (n < sizeof(v128_t)) {
|
||||
const unsigned char *u1 = (unsigned char *)vl;
|
||||
const unsigned char *u2 = (unsigned char *)vr;
|
||||
while (n--) {
|
||||
if (*u1 != *u2) return *u1 - *u2;
|
||||
u1++;
|
||||
u2++;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// memcmp is allowed to read up to n bytes from each object.
|
||||
// Find the first different character in the objects.
|
||||
// Unaligned loads handle the case where the objects
|
||||
// have mismatching alignments.
|
||||
const v128_t *v1 = (v128_t *)vl;
|
||||
const v128_t *v2 = (v128_t *)vr;
|
||||
while (n) {
|
||||
const v128_t cmp = wasm_i8x16_eq(wasm_v128_load(v1), wasm_v128_load(v2));
|
||||
// Bitmask is slow on AArch64, all_true is much faster.
|
||||
if (!wasm_i8x16_all_true(cmp)) {
|
||||
// Find the offset of the first zero bit (little-endian).
|
||||
size_t ctz = __builtin_ctz(~wasm_i8x16_bitmask(cmp));
|
||||
const unsigned char *u1 = (unsigned char *)v1 + ctz;
|
||||
const unsigned char *u2 = (unsigned char *)v2 + ctz;
|
||||
// This may help the compiler if the function is inlined.
|
||||
__builtin_assume(*u1 - *u2 != 0);
|
||||
return *u1 - *u2;
|
||||
}
|
||||
// This makes n a multiple of sizeof(v128_t)
|
||||
// for every iteration except the first.
|
||||
size_t align = (n - 1) % sizeof(v128_t) + 1;
|
||||
v1 = (v128_t *)((char *)v1 + align);
|
||||
v2 = (v128_t *)((char *)v2 + align);
|
||||
n -= align;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
__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
|
||||
// when n would go equal-to-or-below zero.
|
||||
if (!n--) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// memchr must behave as if it reads characters sequentially
|
||||
// and stops as soon as a match is found.
|
||||
// Aligning ensures out of bounds loads are safe.
|
||||
uintptr_t align = (uintptr_t)s % sizeof(v128_t);
|
||||
uintptr_t addr = (uintptr_t)s - align;
|
||||
v128_t vc = wasm_i8x16_splat(c);
|
||||
|
||||
for (;;) {
|
||||
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 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 align cleared them.
|
||||
// Knowing this helps the compiler if it unrolls the loop.
|
||||
__builtin_assume(mask || align);
|
||||
// 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 *)s + (addr - (uintptr_t)s + ctz) : NULL;
|
||||
}
|
||||
}
|
||||
// Decrement n; if it overflows we're done.
|
||||
if (__builtin_sub_overflow(n, sizeof(v128_t) - align, &n)) {
|
||||
return NULL;
|
||||
}
|
||||
align = 0;
|
||||
addr += sizeof(v128_t);
|
||||
}
|
||||
}
|
||||
|
||||
__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.
|
||||
const v128_t *v = (v128_t *)((char *)s + n);
|
||||
const v128_t vc = wasm_i8x16_splat(c);
|
||||
for (; n >= sizeof(v128_t); n -= sizeof(v128_t)) {
|
||||
const v128_t cmp = wasm_i8x16_eq(wasm_v128_load(--v), vc);
|
||||
// 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).
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
|
||||
// Scalar algorithm.
|
||||
const char *a = (char *)v;
|
||||
while (n--) {
|
||||
if (*(--a) == (char)c) return (char *)a;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
__attribute__((weak))
|
||||
size_t strlen(const char *s) {
|
||||
// strlen must stop as soon as it finds the terminator.
|
||||
// Aligning ensures out of bounds loads are safe.
|
||||
uintptr_t align = (uintptr_t)s % sizeof(v128_t);
|
||||
uintptr_t addr = (uintptr_t)s - align;
|
||||
|
||||
for (;;) {
|
||||
v128_t v = *(v128_t *)addr;
|
||||
// Bitmask is slow on AArch64, all_true is much faster.
|
||||
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 align cleared them.
|
||||
// Knowing this helps the compiler if it unrolls the loop.
|
||||
__builtin_assume(mask || align);
|
||||
// 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 addr - (uintptr_t)s + __builtin_ctz(mask);
|
||||
}
|
||||
}
|
||||
align = 0;
|
||||
addr += sizeof(v128_t);
|
||||
}
|
||||
}
|
||||
|
||||
static char *__strchrnul(const char *s, int c) {
|
||||
// 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);
|
||||
uintptr_t addr = (uintptr_t)s - align;
|
||||
v128_t vc = wasm_i8x16_splat(c);
|
||||
|
||||
for (;;) {
|
||||
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 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 align cleared them.
|
||||
// Knowing this helps the compiler if it unrolls the loop.
|
||||
__builtin_assume(mask || align);
|
||||
// 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 *)s + (addr - (uintptr_t)s + __builtin_ctz(mask));
|
||||
}
|
||||
}
|
||||
align = 0;
|
||||
addr += sizeof(v128_t);
|
||||
}
|
||||
}
|
||||
|
||||
__attribute__((weak, always_inline))
|
||||
char *strchrnul(const char *s, int c) {
|
||||
// For finding the terminator, strlen is faster.
|
||||
if (__builtin_constant_p(c) && (char)c == 0) {
|
||||
return (char *)s + strlen(s);
|
||||
}
|
||||
return __strchrnul(s, c);
|
||||
}
|
||||
|
||||
__attribute__((weak, always_inline))
|
||||
char *strchr(const char *s, int c) {
|
||||
// For finding the terminator, strlen is faster.
|
||||
if (__builtin_constant_p(c) && (char)c == 0) {
|
||||
return (char *)s + strlen(s);
|
||||
}
|
||||
char *r = __strchrnul(s, c);
|
||||
return *r == (char)c ? r : NULL;
|
||||
}
|
||||
|
||||
__attribute__((weak, always_inline))
|
||||
char *strrchr(const char *s, int c) {
|
||||
// For finding the terminator, strlen is faster.
|
||||
if (__builtin_constant_p(c) && (char)c == 0) {
|
||||
return (char *)s + strlen(s);
|
||||
}
|
||||
// This could also be implemented in a single pass using strchr,
|
||||
// advancing to the next match until no more matches are found.
|
||||
// That would be suboptimal with lots of consecutive matches.
|
||||
return (char *)memrchr(s, c, strlen(s) + 1);
|
||||
}
|
||||
|
||||
// SIMDized check which bytes are in a set (Geoff Langdale)
|
||||
// http://0x80.pl/notesen/2018-10-18-simd-byte-lookup.html
|
||||
|
||||
typedef struct {
|
||||
__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->lo[lo_nibble] |= (uint8_t)((uint32_t)1 << (hi_nibble - 0));
|
||||
bitmap->hi[lo_nibble] |= (uint8_t)((uint32_t)1 << (hi_nibble - 8));
|
||||
}
|
||||
|
||||
#ifndef __wasm_relaxed_simd__
|
||||
|
||||
#define wasm_i8x16_relaxed_swizzle wasm_i8x16_swizzle
|
||||
|
||||
#endif // __wasm_relaxed_simd__
|
||||
|
||||
__attribute__((always_inline))
|
||||
static v128_t __wasm_v128_chkbits(__wasm_v128_bitmap256_t bitmap, v128_t v) {
|
||||
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 = wasm_i8x16_relaxed_swizzle(bitmask_lookup, hi_nibbles);
|
||||
|
||||
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(bitmap.lo, indices_0_7);
|
||||
v128_t row_8_15 = wasm_i8x16_swizzle(bitmap.hi, indices_8_15);
|
||||
|
||||
v128_t bitsets = row_0_7 | row_8_15;
|
||||
return wasm_i8x16_eq(bitsets & bitmask, bitmask);
|
||||
}
|
||||
|
||||
#undef wasm_i8x16_relaxed_swizzle
|
||||
|
||||
__attribute__((weak))
|
||||
size_t strspn(const char *s, const char *c) {
|
||||
// strspn must stop as soon as it finds the terminator.
|
||||
// Aligning ensures out of bounds loads are safe.
|
||||
uintptr_t align = (uintptr_t)s % sizeof(v128_t);
|
||||
uintptr_t addr = (uintptr_t)s - align;
|
||||
|
||||
if (!c[0]) return 0;
|
||||
if (!c[1]) {
|
||||
v128_t vc = wasm_i8x16_splat(*c);
|
||||
for (;;) {
|
||||
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 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 align cleared them.
|
||||
// Knowing this helps the compiler if it unrolls the loop.
|
||||
__builtin_assume(mask || align);
|
||||
// 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 addr - (uintptr_t)s + __builtin_ctz(mask);
|
||||
}
|
||||
}
|
||||
align = 0;
|
||||
addr += sizeof(v128_t);
|
||||
}
|
||||
}
|
||||
|
||||
__wasm_v128_bitmap256_t bitmap = {};
|
||||
|
||||
for (; *c; c++) {
|
||||
// Terminator IS NOT on the bitmap.
|
||||
__wasm_v128_setbit(&bitmap, *c);
|
||||
}
|
||||
|
||||
for (;;) {
|
||||
v128_t v = *(v128_t *)addr;
|
||||
v128_t cmp = __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 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 align cleared them.
|
||||
// Knowing this helps the compiler if it unrolls the loop.
|
||||
__builtin_assume(mask || align);
|
||||
// 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 addr - (uintptr_t)s + __builtin_ctz(mask);
|
||||
}
|
||||
}
|
||||
align = 0;
|
||||
addr += sizeof(v128_t);
|
||||
}
|
||||
}
|
||||
|
||||
__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 out of bounds loads are safe.
|
||||
uintptr_t align = (uintptr_t)s % sizeof(v128_t);
|
||||
uintptr_t addr = (uintptr_t)s - align;
|
||||
|
||||
__wasm_v128_bitmap256_t bitmap = {};
|
||||
|
||||
do {
|
||||
// Terminator IS on the bitmap.
|
||||
__wasm_v128_setbit(&bitmap, *c);
|
||||
} while (*c++);
|
||||
|
||||
for (;;) {
|
||||
v128_t v = *(v128_t *)addr;
|
||||
v128_t cmp = __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 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 align cleared them.
|
||||
// Knowing this helps the compiler if it unrolls the loop.
|
||||
__builtin_assume(mask || align);
|
||||
// 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 addr - (uintptr_t)s + __builtin_ctz(mask);
|
||||
}
|
||||
}
|
||||
align = 0;
|
||||
addr += sizeof(v128_t);
|
||||
}
|
||||
}
|
||||
|
||||
// Given the above SIMD implementations,
|
||||
// these are best implemented as
|
||||
// small wrappers over those functions.
|
||||
|
||||
// Simple wrappers already in musl:
|
||||
// - mempcpy
|
||||
// - strcat
|
||||
// - strlcat
|
||||
// - strdup
|
||||
// - strndup
|
||||
// - strnlen
|
||||
// - strpbrk
|
||||
// - strsep
|
||||
// - strtok
|
||||
|
||||
__attribute__((weak))
|
||||
void *memccpy(void *__restrict dest, const void *__restrict src, int c,
|
||||
size_t n) {
|
||||
const void *m = memchr(src, c, n);
|
||||
if (m != NULL) {
|
||||
n = (char *)m - (char *)src + 1;
|
||||
m = (char *)dest + n;
|
||||
}
|
||||
memcpy(dest, src, n);
|
||||
return (void *)m;
|
||||
}
|
||||
|
||||
__attribute__((weak))
|
||||
size_t strlcpy(char *__restrict dest, const char *__restrict src, size_t n) {
|
||||
size_t slen = strlen(src);
|
||||
if (n--) {
|
||||
if (n > slen) n = slen;
|
||||
memcpy(dest, src, n);
|
||||
dest[n] = 0;
|
||||
}
|
||||
return slen;
|
||||
}
|
||||
|
||||
__attribute__((weak))
|
||||
char *strncat(char *__restrict dest, const char *__restrict src, size_t n) {
|
||||
size_t strnlen(const char *s, size_t n);
|
||||
size_t dlen = strlen(dest);
|
||||
size_t slen = strnlen(src, n);
|
||||
memcpy(dest + dlen, src, slen);
|
||||
dest[dlen + slen] = 0;
|
||||
return dest;
|
||||
}
|
||||
|
||||
static char *__stpcpy(char *__restrict dest, const char *__restrict src) {
|
||||
size_t slen = strlen(src);
|
||||
memcpy(dest, src, slen + 1);
|
||||
return dest + slen;
|
||||
}
|
||||
|
||||
static char *__stpncpy(char *__restrict dest, const char *__restrict src,
|
||||
size_t n) {
|
||||
size_t strnlen(const char *s, size_t n);
|
||||
size_t slen = strnlen(src, n);
|
||||
memcpy(dest, src, slen);
|
||||
memset(dest + slen, 0, n - slen);
|
||||
return dest + slen;
|
||||
}
|
||||
|
||||
__attribute__((weak, always_inline))
|
||||
char *stpcpy(char *__restrict dest, const char *__restrict src) {
|
||||
return __stpcpy(dest, src);
|
||||
}
|
||||
|
||||
__attribute__((weak, always_inline))
|
||||
char *strcpy(char *__restrict dest, const char *__restrict src) {
|
||||
__stpcpy(dest, src);
|
||||
return dest;
|
||||
}
|
||||
|
||||
__attribute__((weak, always_inline))
|
||||
char *stpncpy(char *__restrict dest, const char *__restrict src, size_t n) {
|
||||
return __stpncpy(dest, src, n);
|
||||
}
|
||||
|
||||
__attribute__((weak, always_inline))
|
||||
char *strncpy(char *__restrict dest, const char *__restrict src, size_t n) {
|
||||
__stpncpy(dest, src, n);
|
||||
return dest;
|
||||
}
|
||||
|
||||
#endif // __wasm_simd128__
|
||||
|
||||
#ifdef __cplusplus
|
||||
} // extern "C"
|
||||
#endif
|
||||
|
||||
#endif // _WASM_SIMD128_STRING_H
|
||||
@@ -10,12 +10,11 @@
|
||||
#include "ext/spellfix.c"
|
||||
#include "ext/uint.c"
|
||||
// Bindings
|
||||
#include "bind.c"
|
||||
#include "column.c"
|
||||
#include "func.c"
|
||||
#include "hooks.c"
|
||||
#include "pointer.c"
|
||||
#include "result.c"
|
||||
#include "stmt.c"
|
||||
#include "text.c"
|
||||
#include "time.c"
|
||||
#include "vfs.c"
|
||||
#include "vtab.c"
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
#include <math.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "sqlite3.h"
|
||||
|
||||
void sqlite3_result_text_go(sqlite3_context *ctx, const char *zData,
|
||||
sqlite3_uint64 nData) {
|
||||
sqlite3_result_text64(ctx, zData, nData, &sqlite3_free, SQLITE_UTF8);
|
||||
}
|
||||
|
||||
void sqlite3_result_blob_go(sqlite3_context *ctx, const void *zData,
|
||||
sqlite3_uint64 nData) {
|
||||
sqlite3_result_blob64(ctx, zData, nData, &sqlite3_free);
|
||||
}
|
||||
@@ -4,26 +4,24 @@
|
||||
#define SQLITE_THREADSAFE 0
|
||||
#define SQLITE_DEFAULT_WAL_SYNCHRONOUS 1
|
||||
#define SQLITE_LIKE_DOESNT_MATCH_BLOBS
|
||||
#define SQLITE_MAX_EXPR_DEPTH 0
|
||||
#define SQLITE_STRICT_SUBTYPE 1
|
||||
#define SQLITE_USE_ALLOCA
|
||||
#define SQLITE_OMIT_DEPRECATED
|
||||
#define SQLITE_OMIT_SHARED_CACHE
|
||||
#define SQLITE_OMIT_AUTOINIT
|
||||
|
||||
// We need these:
|
||||
// #define SQLITE_DEFAULT_MEMSTATUS 0
|
||||
// #define SQLITE_MAX_EXPR_DEPTH 0
|
||||
// #define SQLITE_USE_ALLOCA
|
||||
// #define SQLITE_OMIT_DECLTYPE
|
||||
// #define SQLITE_OMIT_PROGRESS_CALLBACK
|
||||
|
||||
// TODO add this:
|
||||
// #define SQLITE_ENABLE_API_ARMOR
|
||||
|
||||
// Other Options
|
||||
|
||||
#define SQLITE_ALLOW_URI_AUTHORITY
|
||||
#define SQLITE_TRUSTED_SCHEMA 0
|
||||
#define SQLITE_DEFAULT_FOREIGN_KEYS 1
|
||||
#define SQLITE_ENABLE_API_ARMOR
|
||||
#define SQLITE_ENABLE_ATOMIC_WRITE
|
||||
#define SQLITE_ENABLE_BATCH_ATOMIC_WRITE
|
||||
#define SQLITE_ENABLE_COLUMN_METADATA
|
||||
|
||||
@@ -2,6 +2,11 @@
|
||||
|
||||
#include "sqlite3.h"
|
||||
|
||||
int sqlite3_exec_go(sqlite3_stmt *stmt) {
|
||||
while (sqlite3_step(stmt) == SQLITE_ROW);
|
||||
return sqlite3_reset(stmt);
|
||||
}
|
||||
|
||||
union sqlite3_data {
|
||||
sqlite3_int64 i;
|
||||
double d;
|
||||
@@ -16,7 +21,7 @@ int sqlite3_columns_go(sqlite3_stmt *stmt, int nCol, char *aType,
|
||||
if (nCol != sqlite3_column_count(stmt)) {
|
||||
return SQLITE_MISUSE;
|
||||
}
|
||||
int rc = SQLITE_OK;
|
||||
bool check = false;
|
||||
for (int i = 0; i < nCol; ++i) {
|
||||
const void *ptr = NULL;
|
||||
switch (aType[i] = sqlite3_column_type(stmt, i)) {
|
||||
@@ -36,16 +41,14 @@ int sqlite3_columns_go(sqlite3_stmt *stmt, int nCol, char *aType,
|
||||
ptr = sqlite3_column_blob(stmt, i);
|
||||
break;
|
||||
}
|
||||
if (ptr == NULL && rc == SQLITE_OK) {
|
||||
rc = sqlite3_errcode(sqlite3_db_handle(stmt));
|
||||
if (rc == SQLITE_ROW || rc == SQLITE_DONE) {
|
||||
rc = SQLITE_OK;
|
||||
}
|
||||
}
|
||||
aData[i].ptr = ptr;
|
||||
aData[i].len = sqlite3_column_bytes(stmt, i);
|
||||
if (ptr == NULL) check = true;
|
||||
}
|
||||
return rc;
|
||||
if (check && SQLITE_NOMEM == sqlite3_errcode(sqlite3_db_handle(stmt))) {
|
||||
return SQLITE_NOMEM;
|
||||
}
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
static_assert(offsetof(union sqlite3_data, i) == 0, "Unexpected offset");
|
||||
@@ -10,4 +10,14 @@ int sqlite3_bind_text_go(sqlite3_stmt *stmt, int i, const char *zData,
|
||||
int sqlite3_bind_blob_go(sqlite3_stmt *stmt, int i, const char *zData,
|
||||
sqlite3_uint64 nData) {
|
||||
return sqlite3_bind_blob64(stmt, i, zData, nData, &sqlite3_free);
|
||||
}
|
||||
|
||||
void sqlite3_result_text_go(sqlite3_context *ctx, const char *zData,
|
||||
sqlite3_uint64 nData) {
|
||||
sqlite3_result_text64(ctx, zData, nData, &sqlite3_free, SQLITE_UTF8);
|
||||
}
|
||||
|
||||
void sqlite3_result_blob_go(sqlite3_context *ctx, const void *zData,
|
||||
sqlite3_uint64 nData) {
|
||||
sqlite3_result_blob64(ctx, zData, nData, &sqlite3_free);
|
||||
}
|
||||
39
sqlite3/tools.sh
Executable file
39
sqlite3/tools.sh
Executable file
@@ -0,0 +1,39 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
cd -P -- "$(dirname -- "$0")"
|
||||
|
||||
ROOT=../
|
||||
|
||||
if [[ "$OSTYPE" == "msys" || "$OSTYPE" == "cygwin" ]]; then
|
||||
WASI_SDK="x86_64-windows"
|
||||
BINARYEN="x86_64-windows"
|
||||
elif [[ "$OSTYPE" == "linux"* ]]; then
|
||||
if [[ "$(uname -m)" == "x86_64" ]]; then
|
||||
WASI_SDK="x86_64-linux"
|
||||
BINARYEN="x86_64-linux"
|
||||
else
|
||||
WASI_SDK="arm64-linux"
|
||||
BINARYEN="aarch64-linux"
|
||||
fi
|
||||
elif [[ "$OSTYPE" == "darwin"* ]]; then
|
||||
if [[ "$(uname -m)" == "x86_64" ]]; then
|
||||
WASI_SDK="x86_64-macos"
|
||||
BINARYEN="x86_64-macos"
|
||||
else
|
||||
WASI_SDK="arm64-macos"
|
||||
BINARYEN="arm64-macos"
|
||||
fi
|
||||
fi
|
||||
|
||||
WASI_SDK="https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-25/wasi-sdk-25.0-$WASI_SDK.tar.gz"
|
||||
BINARYEN="https://github.com/WebAssembly/binaryen/releases/download/version_123/binaryen-version_123-$BINARYEN.tar.gz"
|
||||
|
||||
# Download tools
|
||||
mkdir -p "$ROOT/tools"
|
||||
[ -d "$ROOT/tools/wasi-sdk" ] || curl -#L "$WASI_SDK" | tar xzC "$ROOT/tools" &
|
||||
[ -d "$ROOT/tools/binaryen" ] || curl -#L "$BINARYEN" | tar xzC "$ROOT/tools" &
|
||||
wait
|
||||
|
||||
[ -d "$ROOT/tools/wasi-sdk" ] || mv "$ROOT/tools/wasi-sdk"* "$ROOT/tools/wasi-sdk"
|
||||
[ -d "$ROOT/tools/binaryen" ] || mv "$ROOT/tools/binaryen"* "$ROOT/tools/binaryen"
|
||||
@@ -137,9 +137,10 @@ sqlite3_vfs *sqlite3_vfs_find(const char *zVfsName) {
|
||||
|
||||
// Create a new C wrapper.
|
||||
sqlite3_vfs *head = go_vfs_list;
|
||||
go_vfs_list = malloc(sizeof(sqlite3_vfs) + strlen(zVfsName) + 1);
|
||||
size_t vfsNameLen = strlen(zVfsName);
|
||||
go_vfs_list = malloc(sizeof(sqlite3_vfs) + vfsNameLen + 1);
|
||||
char *name = (char *)(go_vfs_list + 1);
|
||||
strcpy(name, zVfsName);
|
||||
memcpy(name, zVfsName, vfsNameLen + 1);
|
||||
*go_vfs_list = (sqlite3_vfs){
|
||||
.iVersion = 2,
|
||||
.szOsFile = sizeof(struct go_file),
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# Remove VFS registration. Go handles it.
|
||||
--- sqlite3.c.orig
|
||||
+++ sqlite3.c
|
||||
@@ -26725,7 +26725,7 @@
|
||||
@@ -26883,7 +26883,7 @@
|
||||
sqlite3_free(p);
|
||||
return sqlite3_os_init();
|
||||
}
|
||||
@@ -10,7 +10,7 @@
|
||||
/*
|
||||
** The list of all registered VFS implementations.
|
||||
*/
|
||||
@@ -26822,7 +26822,7 @@
|
||||
@@ -26980,7 +26980,7 @@
|
||||
sqlite3_mutex_leave(mutex);
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
26
stmt.go
26
stmt.go
@@ -110,10 +110,7 @@ func (s *Stmt) Step() bool {
|
||||
s.err = INTERRUPT
|
||||
return false
|
||||
}
|
||||
return s.step()
|
||||
}
|
||||
|
||||
func (s *Stmt) step() bool {
|
||||
rc := res_t(s.c.call("sqlite3_step", stk_t(s.handle)))
|
||||
switch rc {
|
||||
case _ROW:
|
||||
@@ -141,10 +138,9 @@ func (s *Stmt) Exec() error {
|
||||
if s.c.interrupt.Err() != nil {
|
||||
return INTERRUPT
|
||||
}
|
||||
// TODO: implement this in C.
|
||||
for s.step() {
|
||||
}
|
||||
return s.Reset()
|
||||
rc := res_t(s.c.call("sqlite3_exec_go", stk_t(s.handle)))
|
||||
s.err = nil
|
||||
return s.c.error(rc)
|
||||
}
|
||||
|
||||
// Status monitors the performance characteristics of prepared statements.
|
||||
@@ -371,11 +367,9 @@ func (s *Stmt) BindPointer(param int, ptr any) error {
|
||||
//
|
||||
// https://sqlite.org/c3ref/bind_blob.html
|
||||
func (s *Stmt) BindJSON(param int, value any) error {
|
||||
data, err := json.Marshal(value)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return s.BindRawText(param, data)
|
||||
return json.NewEncoder(callbackWriter(func(p []byte) (int, error) {
|
||||
return 0, s.BindRawText(param, p[:len(p)-1]) // remove the newline
|
||||
})).Encode(value)
|
||||
}
|
||||
|
||||
// BindValue binds a copy of value to the prepared statement.
|
||||
@@ -637,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,
|
||||
}
|
||||
}
|
||||
@@ -649,6 +642,7 @@ func (s *Stmt) ColumnValue(col int) Value {
|
||||
// [FLOAT] as float64, [NULL] as nil,
|
||||
// [TEXT] as string, and [BLOB] as []byte.
|
||||
func (s *Stmt) Columns(dest ...any) error {
|
||||
defer s.c.arena.mark()()
|
||||
types, ptr, err := s.columns(int64(len(dest)))
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -701,6 +695,7 @@ func (s *Stmt) Columns(dest ...any) error {
|
||||
// Any []byte are owned by SQLite and may be invalidated by
|
||||
// subsequent calls to [Stmt] methods.
|
||||
func (s *Stmt) ColumnsRaw(dest ...any) error {
|
||||
defer s.c.arena.mark()()
|
||||
types, ptr, err := s.columns(int64(len(dest)))
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -739,7 +734,6 @@ func (s *Stmt) ColumnsRaw(dest ...any) error {
|
||||
}
|
||||
|
||||
func (s *Stmt) columns(count int64) ([]byte, ptr_t, error) {
|
||||
defer s.c.arena.mark()()
|
||||
typePtr := s.c.arena.new(count)
|
||||
dataPtr := s.c.arena.new(count * 8)
|
||||
|
||||
@@ -754,3 +748,7 @@ func (s *Stmt) columns(count int64) ([]byte, ptr_t, error) {
|
||||
|
||||
return util.View(s.c.mod, typePtr, count), dataPtr, nil
|
||||
}
|
||||
|
||||
type callbackWriter func(p []byte) (int, error)
|
||||
|
||||
func (fn callbackWriter) Write(p []byte) (int, error) { return fn(p) }
|
||||
|
||||
@@ -114,6 +114,7 @@ func TestConn_FileControl(t *testing.T) {
|
||||
t.Errorf("got %v, want MISUSE", err)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("FCNTL_RESET_CACHE", func(t *testing.T) {
|
||||
o, err := db.FileControl("", sqlite3.FCNTL_RESET_CACHE)
|
||||
if err != nil {
|
||||
@@ -237,6 +238,16 @@ func TestConn_FileControl(t *testing.T) {
|
||||
t.Errorf("got %v, want LOCK_EXCLUSIVE", o)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("FCNTL_NULL_IO", func(t *testing.T) {
|
||||
o, err := db.FileControl("", sqlite3.FCNTL_NULL_IO)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if o != nil {
|
||||
t.Errorf("got %v, want nil", o)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestConn_Limit(t *testing.T) {
|
||||
|
||||
@@ -295,6 +295,17 @@ func TestConn_Filename(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
file := filepath.Join(t.TempDir(), "test.db")
|
||||
f, err := os.Create(file)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
f.Close()
|
||||
|
||||
file, err = filepath.EvalSymlinks(file)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
db, err := sqlite3.Open(file)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@ func TestMain(m *testing.M) {
|
||||
log.Printf("%v (%d): %s", code, code, msg)
|
||||
}
|
||||
})
|
||||
m.Run()
|
||||
os.Exit(m.Run())
|
||||
}
|
||||
|
||||
func Test_parallel(t *testing.T) {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -287,8 +279,9 @@ func TestConn_Transaction_busy(t *testing.T) {
|
||||
go cancel()
|
||||
|
||||
_, err = db2.BeginExclusive()
|
||||
if !errors.Is(err, sqlite3.BUSY) && !errors.Is(err, sqlite3.INTERRUPT) {
|
||||
t.Errorf("got %v, want sqlite3.BUSY or sqlite3.INTERRUPT", err)
|
||||
var terr interface{ Temporary() bool }
|
||||
if !errors.As(err, &terr) || !terr.Temporary() {
|
||||
t.Errorf("got %v, want temporary", err)
|
||||
}
|
||||
|
||||
err = nil
|
||||
@@ -332,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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -381,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) {
|
||||
@@ -468,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()
|
||||
@@ -552,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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -598,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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,10 +32,3 @@ func (FS) Stat(name string) (fs.FileInfo, error) {
|
||||
func (FS) ReadFile(name string) ([]byte, error) {
|
||||
return os.ReadFile(name)
|
||||
}
|
||||
|
||||
// OpenFile behaves the same as [os.OpenFile].
|
||||
//
|
||||
// Deprecated: use os.OpenFile instead.
|
||||
func OpenFile(name string, flag int, perm fs.FileMode) (*os.File, error) {
|
||||
return os.OpenFile(name, flag, perm)
|
||||
}
|
||||
|
||||
@@ -10,11 +10,13 @@ WASI_SDK="$ROOT/tools/wasi-sdk/bin"
|
||||
trap 'rm -f sql3parse_table.tmp' EXIT
|
||||
|
||||
"$WASI_SDK/clang" --target=wasm32-wasi -std=c23 -g0 -Oz \
|
||||
-Wall -Wextra -o sql3parse_table.wasm main.c \
|
||||
-Wall -Wextra -Wno-unused-parameter -Wno-unused-function \
|
||||
-o sql3parse_table.wasm main.c \
|
||||
-I"$ROOT/sqlite3/libc" -I"$ROOT/sqlite3" \
|
||||
-mexec-model=reactor \
|
||||
-msimd128 -mmutable-globals -mmultivalue \
|
||||
-mbulk-memory -mreference-types \
|
||||
-mnontrapping-fptoint -msign-ext \
|
||||
-mmutable-globals -mnontrapping-fptoint \
|
||||
-msimd128 -mbulk-memory -msign-ext \
|
||||
-mreference-types -mmultivalue \
|
||||
-fno-stack-protector -fno-stack-clash-protection \
|
||||
-Wl,--stack-first \
|
||||
-Wl,--import-undefined \
|
||||
@@ -22,7 +24,7 @@ trap 'rm -f sql3parse_table.tmp' EXIT
|
||||
|
||||
"$BINARYEN/wasm-ctor-eval" -c _initialize sql3parse_table.wasm -o sql3parse_table.tmp
|
||||
"$BINARYEN/wasm-opt" --strip --strip-debug --strip-producers -c -Oz \
|
||||
sql3parse_table.tmp -o sql3parse_table.wasm \
|
||||
--enable-simd --enable-mutable-globals --enable-multivalue \
|
||||
--enable-bulk-memory --enable-reference-types \
|
||||
--enable-nontrapping-float-to-int --enable-sign-ext
|
||||
sql3parse_table.tmp -o sql3parse_table.wasm --low-memory-unused \
|
||||
--enable-mutable-globals --enable-nontrapping-float-to-int \
|
||||
--enable-simd --enable-bulk-memory --enable-sign-ext \
|
||||
--enable-reference-types --enable-multivalue
|
||||
Binary file not shown.
42
value.go
42
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,
|
||||
}
|
||||
}
|
||||
@@ -42,9 +32,6 @@ func (v Value) Dup() *Value {
|
||||
//
|
||||
// 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
|
||||
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
|
||||
}
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ POSIX advisory locks,
|
||||
which SQLite uses on [Unix](https://github.com/sqlite/sqlite/blob/5d60f4/src/os_unix.c#L13-L14),
|
||||
are [broken by design](https://github.com/sqlite/sqlite/blob/5d60f4/src/os_unix.c#L1074-L1162).
|
||||
Instead, on Linux and macOS, this package uses
|
||||
[OFD locks](https://www.gnu.org/software/libc/manual/html_node/Open-File-Description-Locks.html)
|
||||
[OFD locks](https://gnu.org/software/libc/manual/html_node/Open-File-Description-Locks.html)
|
||||
to synchronize access to database files.
|
||||
|
||||
This package can also use
|
||||
|
||||
@@ -234,6 +234,7 @@ const (
|
||||
_FCNTL_CKSM_FILE _FcntlOpcode = 41
|
||||
_FCNTL_RESET_CACHE _FcntlOpcode = 42
|
||||
_FCNTL_NULL_IO _FcntlOpcode = 43
|
||||
_FCNTL_BLOCK_ON_CONNECT _FcntlOpcode = 44
|
||||
)
|
||||
|
||||
// https://sqlite.org/c3ref/c_shm_exclusive.html
|
||||
@@ -246,6 +247,6 @@ const (
|
||||
_SHM_EXCLUSIVE _ShmFlag = 8
|
||||
|
||||
_SHM_NLOCK = 8
|
||||
_SHM_BASE = 120
|
||||
_SHM_BASE = (22 + _SHM_NLOCK) * 4
|
||||
_SHM_DMS = _SHM_BASE + _SHM_NLOCK
|
||||
)
|
||||
|
||||
24
vfs/file.go
24
vfs/file.go
@@ -13,22 +13,28 @@ import (
|
||||
type vfsOS struct{}
|
||||
|
||||
func (vfsOS) FullPathname(path string) (string, error) {
|
||||
path, err := filepath.Abs(path)
|
||||
link, err := evalSymlinks(path)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return path, testSymlinks(filepath.Dir(path))
|
||||
full, err := filepath.Abs(link)
|
||||
if err == nil && link != path {
|
||||
err = _OK_SYMLINK
|
||||
}
|
||||
return full, err
|
||||
}
|
||||
|
||||
func testSymlinks(path string) error {
|
||||
p, err := filepath.EvalSymlinks(path)
|
||||
func evalSymlinks(path string) (string, error) {
|
||||
var file string
|
||||
_, err := os.Lstat(path)
|
||||
if errors.Is(err, fs.ErrNotExist) {
|
||||
path, file = filepath.Split(path)
|
||||
}
|
||||
path, err = filepath.EvalSymlinks(path)
|
||||
if err != nil {
|
||||
return err
|
||||
return "", err
|
||||
}
|
||||
if p != path {
|
||||
return _OK_SYMLINK
|
||||
}
|
||||
return nil
|
||||
return filepath.Join(path, file), nil
|
||||
}
|
||||
|
||||
func (vfsOS) Delete(path string, syncDir bool) error {
|
||||
|
||||
@@ -2,7 +2,6 @@ package memdb
|
||||
|
||||
import (
|
||||
"io"
|
||||
"runtime"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
@@ -28,9 +27,8 @@ func (memVFS) Open(name string, flags vfs.OpenFlag) (vfs.File, vfs.OpenFlag, err
|
||||
//
|
||||
// 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_TEMP_JOURNAL
|
||||
const types = vfs.OPEN_MAIN_DB | vfs.OPEN_TEMP_DB |
|
||||
vfs.OPEN_TRANSIENT_DB | vfs.OPEN_TEMP_JOURNAL
|
||||
if flags&types == 0 {
|
||||
// notest // OPEN_MEMORY
|
||||
return nil, flags, sqlite3.CANTOPEN
|
||||
@@ -86,9 +84,10 @@ type memDB struct {
|
||||
// +checklocks:memoryMtx
|
||||
refs int32
|
||||
|
||||
shared int32 // +checklocks:lockMtx
|
||||
pending bool // +checklocks:lockMtx
|
||||
reserved bool // +checklocks:lockMtx
|
||||
shared int32 // +checklocks:lockMtx
|
||||
pending bool // +checklocks:lockMtx
|
||||
reserved bool // +checklocks:lockMtx
|
||||
waiter *sync.Cond // +checklocks:lockMtx
|
||||
|
||||
lockMtx sync.Mutex
|
||||
dataMtx sync.RWMutex
|
||||
@@ -196,8 +195,6 @@ func (m *memFile) Size() (int64, error) {
|
||||
return m.size, nil
|
||||
}
|
||||
|
||||
const spinWait = 25 * time.Microsecond
|
||||
|
||||
func (m *memFile) Lock(lock vfs.LockLevel) error {
|
||||
if m.lock >= lock {
|
||||
return nil
|
||||
@@ -229,13 +226,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()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -258,7 +260,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
|
||||
|
||||
@@ -26,7 +26,6 @@ type vfsShm struct {
|
||||
ptrs []ptr_t
|
||||
stack [1]stk_t
|
||||
fileLock bool
|
||||
blocking bool
|
||||
sync.Mutex
|
||||
}
|
||||
|
||||
|
||||
@@ -9,10 +9,10 @@ WASI_SDK="$ROOT/tools/wasi-sdk/bin"
|
||||
|
||||
"$WASI_SDK/clang" --target=wasm32-wasi -std=c23 -g0 -O2 \
|
||||
-o mptest.wasm main.c \
|
||||
-I"$ROOT/sqlite3" \
|
||||
-msimd128 -mmutable-globals -mmultivalue \
|
||||
-mbulk-memory -mreference-types \
|
||||
-mnontrapping-fptoint -msign-ext \
|
||||
-I"$ROOT/sqlite3/libc" -I"$ROOT/sqlite3" \
|
||||
-mmutable-globals -mnontrapping-fptoint \
|
||||
-msimd128 -mbulk-memory -msign-ext \
|
||||
-mreference-types -mmultivalue \
|
||||
-fno-stack-protector -fno-stack-clash-protection \
|
||||
-Wl,--stack-first \
|
||||
-Wl,--import-undefined \
|
||||
@@ -26,8 +26,8 @@ WASI_SDK="$ROOT/tools/wasi-sdk/bin"
|
||||
$(awk '{print "-Wl,--export="$0}' exports.txt)
|
||||
|
||||
"$BINARYEN/wasm-opt" -g --strip --strip-producers -c -O3 \
|
||||
mptest.wasm -o mptest.tmp \
|
||||
--enable-simd --enable-mutable-globals --enable-multivalue \
|
||||
--enable-bulk-memory --enable-reference-types \
|
||||
--enable-nontrapping-float-to-int --enable-sign-ext
|
||||
mptest.wasm -o mptest.tmp --low-memory-unused \
|
||||
--enable-mutable-globals --enable-nontrapping-float-to-int \
|
||||
--enable-simd --enable-bulk-memory --enable-sign-ext \
|
||||
--enable-reference-types --enable-multivalue
|
||||
mv mptest.tmp mptest.wasm
|
||||
Binary file not shown.
@@ -9,10 +9,10 @@ WASI_SDK="$ROOT/tools/wasi-sdk/bin"
|
||||
|
||||
"$WASI_SDK/clang" --target=wasm32-wasi -std=c23 -g0 -O2 \
|
||||
-o speedtest1.wasm main.c \
|
||||
-I"$ROOT/sqlite3" \
|
||||
-msimd128 -mmutable-globals -mmultivalue \
|
||||
-mbulk-memory -mreference-types \
|
||||
-mnontrapping-fptoint -msign-ext \
|
||||
-I"$ROOT/sqlite3/libc" -I"$ROOT/sqlite3" \
|
||||
-mmutable-globals -mnontrapping-fptoint \
|
||||
-msimd128 -mbulk-memory -msign-ext \
|
||||
-mreference-types -mmultivalue \
|
||||
-fno-stack-protector -fno-stack-clash-protection \
|
||||
-Wl,--stack-first \
|
||||
-Wl,--import-undefined \
|
||||
@@ -21,8 +21,8 @@ WASI_SDK="$ROOT/tools/wasi-sdk/bin"
|
||||
$(awk '{print "-Wl,--export="$0}' exports.txt)
|
||||
|
||||
"$BINARYEN/wasm-opt" -g --strip --strip-producers -c -O3 \
|
||||
speedtest1.wasm -o speedtest1.tmp \
|
||||
--enable-simd --enable-mutable-globals --enable-multivalue \
|
||||
--enable-bulk-memory --enable-reference-types \
|
||||
--enable-nontrapping-float-to-int --enable-sign-ext
|
||||
speedtest1.wasm -o speedtest1.tmp --low-memory-unused \
|
||||
--enable-mutable-globals --enable-nontrapping-float-to-int \
|
||||
--enable-simd --enable-bulk-memory --enable-sign-ext \
|
||||
--enable-reference-types --enable-multivalue
|
||||
mv speedtest1.tmp speedtest1.wasm
|
||||
Binary file not shown.
@@ -381,6 +381,10 @@ func vfsFileControlImpl(ctx context.Context, mod api.Module, file File, op _Fcnt
|
||||
file.SetDB(ctx.Value(util.ConnKey{}))
|
||||
return _OK
|
||||
}
|
||||
|
||||
case _FCNTL_NULL_IO:
|
||||
file.Close()
|
||||
return _OK
|
||||
}
|
||||
|
||||
return _NOTFOUND
|
||||
|
||||
Reference in New Issue
Block a user