Compare commits

...

80 Commits

Author SHA1 Message Date
Nuno Cruces
9719d4b0e3 Better tests. 2025-07-17 01:11:16 +01:00
Nuno Cruces
b21c69dc1f Fix mode. 2025-07-17 00:50:39 +01:00
Nuno Cruces
b0f8ff44a5 Support subtype. 2025-07-17 00:47:04 +01:00
Nuno Cruces
f37bca6a80 Speedup memdb. 2025-07-15 23:08:41 +01:00
Nuno Cruces
b4e8fcb752 Avoid UB. 2025-07-15 15:52:39 +01:00
dependabot[bot]
14b98a5d05 Bump golang.org/x/crypto from 0.39.0 to 0.40.0 (#295)
Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.39.0 to 0.40.0.
- [Commits](https://github.com/golang/crypto/compare/v0.39.0...v0.40.0)

---
updated-dependencies:
- dependency-name: golang.org/x/crypto
  dependency-version: 0.40.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-07-11 00:30:01 +01:00
Nuno Cruces
36a62264f9 Remove unneeded generics. 2025-07-10 13:05:55 +01:00
Nuno Cruces
33ea564f38 Deps. 2025-07-10 00:50:14 +01:00
Nuno Cruces
5c55d8692f Fix bitset. 2025-07-04 17:10:40 +01:00
Nuno Cruces
be2f3036b4 SQLite 3.50.2. 2025-06-30 12:29:54 +01:00
Nuno Cruces
784f82f42f Avoid UB. 2025-06-25 15:27:11 +01:00
Nuno Cruces
cd6ba43e77 Less SIMD. 2025-06-24 02:23:54 +01:00
Nuno Cruces
d7aef63844 Naming, volatile. 2025-06-20 12:45:42 +01:00
Nuno Cruces
64e5046f10 Improved byteset search. 2025-06-17 11:36:53 +01:00
Nuno Cruces
0bdce8aa68 Avoid overflow. 2025-06-12 15:12:20 +01:00
Nuno Cruces
69a2881a10 SQLite 3.50.1. 2025-06-08 00:38:01 +01:00
dependabot[bot]
24ad4445f1 Bump golang.org/x/crypto from 0.38.0 to 0.39.0 (#285)
Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.38.0 to 0.39.0.
- [Commits](https://github.com/golang/crypto/compare/v0.38.0...v0.39.0)

---
updated-dependencies:
- dependency-name: golang.org/x/crypto
  dependency-version: 0.39.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-06-05 23:49:59 +01:00
Nuno Cruces
c159bbd88f Docs, tweaks. 2025-06-04 12:19:01 +01:00
Nuno Cruces
c90f8205f7 Remove. 2025-06-03 12:54:56 +01:00
Nuno Cruces
b64b9b0415 Better strcasestr. 2025-06-02 10:25:10 +01:00
Nuno Cruces
9142e19d61 SQLite 3.50.0. 2025-05-31 01:14:16 +01:00
Daenney
4a76f2b064 README: Make the testing section simpler to read (#282) 2025-05-30 14:29:34 +01:00
Nuno Cruces
c9b364507e Avoid copy, alloc. 2025-05-27 11:35:43 +01:00
Nuno Cruces
2204b96ff6 Gorm. 2025-05-27 10:51:42 +01:00
Nuno Cruces
b46f480d79 FCNTL_NULL_IO. 2025-05-26 16:32:52 +01:00
Nuno Cruces
040a026925 More examples. 2025-05-26 12:26:14 +01:00
Michael Lynch
e678040a4e Remove duplicate link to 'custom VFSes' in README (#279) 2025-05-26 11:49:03 +01:00
Michael Lynch
f1cc12569c Link to example of incremental BLOB I/O usage (#280) 2025-05-26 11:48:53 +01:00
Nuno Cruces
721a987e0e Line numbers. 2025-05-26 11:48:02 +01:00
Michael Lynch
f3d65142cc Use sql.Named to clarify the blobio example (#281)
I find the example SQL queries a bit difficult to read at the callsite with the magic numbers and ? placeholders. I think sql.Named makes it more obvious to the reader what the different parts of the SQL query represent.
2025-05-25 19:02:19 +01:00
Nuno Cruces
93f711c77b More fuzzing. 2025-05-21 11:36:56 +01:00
Nuno Cruces
341bd063e8 More fuzzing. 2025-05-21 08:16:52 +01:00
Nuno Cruces
f765882670 Sunday's Quick Search. 2025-05-17 13:20:11 +01:00
Nuno Cruces
ff3676ff4a Case insensitive search, fixes. 2025-05-16 13:10:03 +01:00
Nuno Cruces
54877a53cd Differential fuzzing. 2025-05-15 14:55:34 +01:00
Nuno Cruces
fccc6c10a7 Issue #277. 2025-05-14 13:46:01 +01:00
Nuno Cruces
fc21ffcc71 Tweaks. 2025-05-14 01:10:44 +01:00
Nuno Cruces
687e643d7a Case insensitive compare. 2025-05-13 18:12:14 +01:00
Nuno Cruces
fc5ced209c Fix bcmp. 2025-05-13 14:28:21 +01:00
Nuno Cruces
c1bed07e3a Issue #276. 2025-05-13 14:27:04 +01:00
Nuno Cruces
a0771f2363 Relaxed SIMD. 2025-05-12 17:30:32 +01:00
Nuno Cruces
6bad547d3d Unknown haystack length. 2025-05-12 12:00:06 +01:00
Nuno Cruces
c2c1aea578 Handle repetitive needles. 2025-05-10 01:44:25 +01:00
Nuno Cruces
60ab485b29 Comments. 2025-05-09 14:21:23 +01:00
Nuno Cruces
e17a432fde Adds strstr and memmem. (#275) 2025-05-09 00:59:39 +01:00
Nuno Cruces
c780ef16e2 SQLite 3.49.2. 2025-05-07 14:08:18 +01:00
Nuno Cruces
b609930142 Refactor #274. 2025-05-07 12:46:13 +01:00
Nuno Cruces
fd165ce724 Issue #274. 2025-05-07 01:37:52 +01:00
Nuno Cruces
d3973b23e3 More memcmp. 2025-05-06 15:48:58 +01:00
Nuno Cruces
320b68e74f More tests. 2025-05-05 14:47:43 +01:00
Nuno Cruces
2c3850e5d1 Reuse fast funcs. 2025-05-03 01:18:10 +01:00
Nuno Cruces
db7aacff9f Add strrchr. 2025-05-02 14:35:14 +01:00
Nuno Cruces
d748d98e39 Fix. 2025-05-01 12:49:38 +01:00
Nuno Cruces
13b8642384 Compile as C++. 2025-04-29 14:03:59 +01:00
Nuno Cruces
29c5c816cb More libc. 2025-04-27 23:35:13 +01:00
Nuno Cruces
b32db76da6 Fix flake. 2025-04-25 00:40:52 +01:00
Nuno Cruces
383f620a1e Action permissions. 2025-04-25 00:28:03 +01:00
Nuno Cruces
a3c3515e96 Update binaries. 2025-04-25 00:20:26 +01:00
Nuno Cruces
e580f080b9 Test libc. 2025-04-24 23:59:53 +01:00
Nuno Cruces
9ea7099c24 Size optimized versions. 2025-04-24 23:17:30 +01:00
Nuno Cruces
29aa365806 Fix. 2025-04-24 00:41:48 +01:00
Nuno Cruces
bb87a920f7 Fix memchr. 2025-04-22 02:27:50 +01:00
Nuno Cruces
48379336dc Improve strspn. 2025-04-21 18:59:20 +01:00
Nuno Cruces
251a92fa1a Weak symbols. 2025-04-18 14:50:57 +01:00
Nuno Cruces
f5206ea8da Shellsort. 2025-04-18 09:56:18 +01:00
Nuno Cruces
68ef4593d6 Make libc easier to use. 2025-04-17 13:55:44 +01:00
Nuno Cruces
79bf171210 Fix #263. 2025-04-16 16:39:36 +01:00
Nuno Cruces
ad16d329ea Optimize strlen and strchr on ARM (#262) 2025-04-15 00:44:31 +01:00
Nuno Cruces
9706fa9607 Benchmark more CPUs 2025-04-13 13:28:36 +01:00
Nuno Cruces
45494f5fb6 Redundant defers. 2025-04-09 10:21:54 +01:00
Nuno Cruces
1b0bf3495e Fix flake. 2025-04-09 10:21:54 +01:00
Nuno Cruces
73ac7e06f6 Use SIMD libc. 2025-04-09 10:21:44 +01:00
Nuno Cruces
a3ce8f9de5 More. 2025-04-07 01:32:15 +01:00
Nuno Cruces
2043d5fca4 Benchmark. 2025-04-06 23:24:34 +01:00
Nuno Cruces
3bd11a0a86 More SIMD. 2025-04-05 11:16:47 +01:00
Nuno Cruces
39f3fa64eb More SIMD. 2025-04-05 02:03:31 +01:00
Nuno Cruces
4c19387535 SIMD. 2025-04-04 17:47:20 +01:00
Nuno Cruces
e6c9f18934 Benchmark libc. 2025-04-04 10:56:12 +01:00
Nuno Cruces
970eb6a2f9 Fix. 2025-04-02 15:33:21 +01:00
Nuno Cruces
fac27b8bab libc. 2025-04-02 11:55:20 +01:00
91 changed files with 4105 additions and 584 deletions

23
.github/workflows/libc.yml vendored Normal file
View 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

View File

@@ -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

View File

@@ -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 }}

View File

@@ -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)

View File

@@ -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))

View File

@@ -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.

View File

@@ -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.
//

View File

@@ -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 {

View File

@@ -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)

View File

@@ -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.

View File

@@ -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)
}
}

View File

@@ -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

View File

@@ -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
)

View File

@@ -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=

View File

@@ -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.

View File

@@ -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

View File

@@ -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

View File

@@ -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.

View File

@@ -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.

View File

@@ -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)

View File

@@ -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()

View File

@@ -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) {

View File

@@ -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() {

View File

@@ -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)
}
}

View File

@@ -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`)

View File

@@ -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"},

View File

@@ -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)

View File

@@ -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

View File

@@ -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)
}

View File

@@ -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
}
}

View File

@@ -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()
}

View File

@@ -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")
}

View File

@@ -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)

View File

@@ -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)

View File

@@ -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
View File

@@ -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
View File

@@ -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=

View File

@@ -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) {

View File

@@ -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 {

View File

@@ -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"},

View File

@@ -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
)

View File

@@ -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=

View File

@@ -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
}

View File

@@ -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/

View File

@@ -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

View File

@@ -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 }

View File

@@ -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)
}
}

View File

@@ -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}
}

View File

@@ -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

View File

@@ -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
View 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
View 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

Binary file not shown.

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
View 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
View 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
View 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
View 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

View File

@@ -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"

View File

@@ -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);
}

View File

@@ -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

View File

@@ -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");

View File

@@ -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
View 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"

View File

@@ -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),

View 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
View File

@@ -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) }

View File

@@ -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) {

View File

@@ -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)

View File

@@ -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)
}
}
}

View File

@@ -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)
}

View File

@@ -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) {

View File

@@ -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)

View File

@@ -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 {

View File

@@ -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)
}
}

View File

@@ -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)
}

View File

@@ -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

View File

@@ -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
}

View File

@@ -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

View File

@@ -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
)

View File

@@ -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 {

View File

@@ -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

View File

@@ -26,7 +26,6 @@ type vfsShm struct {
ptrs []ptr_t
stack [1]stk_t
fileLock bool
blocking bool
sync.Mutex
}

View File

@@ -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.

View File

@@ -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

View File

@@ -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