Compare commits

..
1 Commits
Author SHA1 Message Date
Nuno Cruces e6b2d2aef5 Bulk copies. 2025-11-24 13:42:12 +00:00
139 changed files with 5439 additions and 12395 deletions
+23
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-15, macos-15-intel]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v6
- uses: actions/setup-go@v6
with: { go-version: stable }
- name: Benchmark
shell: bash
run: sqlite3/libc/benchmark.sh
+5
View File
@@ -5,6 +5,11 @@ set -euo pipefail
sqlite3/download.sh
sqlite3/tools.sh
embed/build.sh
embed/bcw2/build.sh
# Download and build sqlite-createtable-parser
util/sql3util/wasm/download.sh
util/sql3util/wasm/build.sh
# Check diffs
git diff --exit-code
+4 -2
View File
@@ -23,8 +23,10 @@ jobs:
shell: bash
run: .github/workflows/repro.sh
- uses: actions/attest-build-provenance@v4
- uses: actions/attest-build-provenance@v3
if: matrix.os == 'ubuntu-latest'
with:
subject-path: |
embed/sqlite3.wasm
embed/sqlite3.wasm
embed/bcw2/bcw2.wasm
util/sql3util/wasm/sql3parse_table.wasm
+12 -15
View File
@@ -68,8 +68,8 @@ jobs:
shell: bash
run: |
go work init .
go work use -r gormlite
go test ./gormlite
go work use -r embed gormlite
go test ./embed/bcw2/...
- name: Test GORM
shell: bash
@@ -93,28 +93,25 @@ jobs:
github.event_name == 'push' &&
matrix.os == 'ubuntu-latest'
test-cross:
test-bsd:
strategy:
matrix:
os:
- name: freebsd
version: '15.0'
version: '14.3'
- name: netbsd
version: '10.1'
- name: illumos
action: omnios
version: 'r151056'
- name: openbsd
version: '7.8'
tflags: '-test.short'
- name: freebsd
arch: arm64
version: '15.0'
version: '14.3'
tflags: '-test.short'
- name: netbsd
arch: arm64
version: '10.1'
tflags: '-test.short'
- name: openbsd
version: '7.8'
tflags: '-test.short'
runs-on: ubuntu-latest
needs: test
@@ -129,9 +126,9 @@ jobs:
run: .github/workflows/build-test.sh
- name: Test
uses: cross-platform-actions/action@v0.32.0
uses: cross-platform-actions/action@v0.30.0
with:
operating_system: ${{ matrix.os.action || matrix.os.name }}
operating_system: ${{ matrix.os.name }}
architecture: ${{ matrix.os.arch }}
version: ${{ matrix.os.version }}
shell: bash
@@ -145,7 +142,7 @@ jobs:
- name: dragonfly
action: 'vmactions/dragonflybsd-vm@v1'
- name: illumos
action: 'vmactions/openindiana-vm@v0'
action: 'vmactions/omnios-vm@v1'
- name: solaris
action: 'vmactions/solaris-vm@v1'
bflags: '-tags sqlite3_dotlk'
@@ -192,7 +189,7 @@ jobs:
needs: test
steps:
- uses: docker/setup-qemu-action@v4
- uses: docker/setup-qemu-action@v3
- uses: actions/checkout@v6
- uses: actions/setup-go@v6
with: { go-version: stable }
+3 -7
View File
@@ -157,20 +157,16 @@ func (c *Conn) FileControl(schema string, op FcntlOpcode, arg ...any) (any, erro
stk_t(op), stk_t(ptr)))
ret = util.Read32[vfs.LockLevel](c.mod, ptr)
case FCNTL_VFSNAME, FCNTL_VFS_POINTER:
case FCNTL_VFS_POINTER:
rc = res_t(c.call("sqlite3_file_control",
stk_t(c.handle), stk_t(schemaPtr),
stk_t(FCNTL_VFS_POINTER), stk_t(ptr)))
stk_t(op), stk_t(ptr)))
if rc == _OK {
const zNameOffset = 16
ptr = util.Read32[ptr_t](c.mod, ptr)
ptr = util.Read32[ptr_t](c.mod, ptr+zNameOffset)
name := util.ReadString(c.mod, ptr, _MAX_NAME)
if op == FCNTL_VFS_POINTER {
ret = vfs.Find(name)
} else {
ret = name
}
ret = vfs.Find(name)
}
case FCNTL_FILE_POINTER, FCNTL_JOURNAL_POINTER:
+1 -5
View File
@@ -173,7 +173,7 @@ const (
// PrepareFlag is a flag that can be passed to [Conn.PrepareFlags].
//
// https://sqlite.org/c3ref/c_prepare_dont_log.html
// https://sqlite.org/c3ref/c_prepare_normalize.html
type PrepareFlag uint32
const (
@@ -181,7 +181,6 @@ const (
PREPARE_NORMALIZE PrepareFlag = 0x02
PREPARE_NO_VTAB PrepareFlag = 0x04
PREPARE_DONT_LOG PrepareFlag = 0x10
// PREPARE_FROM_DDL PrepareFlag = 0x20
)
// FunctionFlag is a flag that can be passed to
@@ -268,7 +267,6 @@ const (
DBCONFIG_ENABLE_ATTACH_CREATE DBConfig = 1020
DBCONFIG_ENABLE_ATTACH_WRITE DBConfig = 1021
DBCONFIG_ENABLE_COMMENTS DBConfig = 1022
// SQLITE_DBCONFIG_FP_DIGITS DBConfig = 1023
// DBCONFIG_MAX DBConfig = 1022
)
@@ -282,7 +280,6 @@ const (
FCNTL_CHUNK_SIZE FcntlOpcode = 6
FCNTL_FILE_POINTER FcntlOpcode = 7
FCNTL_PERSIST_WAL FcntlOpcode = 10
FCNTL_VFSNAME FcntlOpcode = 12
FCNTL_POWERSAFE_OVERWRITE FcntlOpcode = 13
FCNTL_VFS_POINTER FcntlOpcode = 27
FCNTL_JOURNAL_POINTER FcntlOpcode = 28
@@ -310,7 +307,6 @@ const (
LIMIT_VARIABLE_NUMBER LimitCategory = 9
LIMIT_TRIGGER_DEPTH LimitCategory = 10
LIMIT_WORKER_THREADS LimitCategory = 11
// LIMIT_PARSER_DEPTH LimitCategory = 12
)
// AuthorizerActionCode are the integer action codes
+9 -3
View File
@@ -91,7 +91,13 @@ func (ctx Context) ResultText(value string) {
//
// https://sqlite.org/c3ref/result_blob.html
func (ctx Context) ResultRawText(value []byte) {
ctx.ResultText(string(value)) // does not escape
if len(value) == 0 {
ctx.ResultText("")
return
}
ptr := ctx.c.newBytes(value)
ctx.c.call("sqlite3_result_text_go",
stk_t(ctx.handle), stk_t(ptr), stk_t(len(value)))
}
// ResultBlob sets the result of the function to a []byte.
@@ -145,11 +151,11 @@ func (ctx Context) ResultTime(value time.Time, format TimeFormat) {
}
func (ctx Context) resultRFC3339Nano(value time.Time) {
const maxlen = 48
const maxlen = int64(len(time.RFC3339Nano)) + 5
ptr := ctx.c.new(maxlen)
buf := util.View(ctx.c.mod, ptr, maxlen)
buf = value.AppendFormat(buf[:0], time.RFC3339Nano)
_ = append(buf, 0)
ctx.c.call("sqlite3_result_text_go",
stk_t(ctx.handle), stk_t(ptr), stk_t(len(buf)))
+56 -15
View File
@@ -440,6 +440,22 @@ func (c *conn) CheckNamedValue(arg *driver.NamedValue) error {
return nil
}
// Deprecated: for Litestream use only; may be removed at any time.
func (c *conn) FileControlPersistWAL(schema string, mode int) (int, error) {
// notest
arg := make([]any, 1)
if mode >= 0 {
arg[0] = mode > 0
} else {
arg = arg[:0]
}
res, err := c.Conn.FileControl(schema, sqlite3.FCNTL_PERSIST_WAL, arg...)
if res == true {
return 1, err
}
return 0, err
}
type stmt struct {
*sqlite3.Stmt
tmWrite sqlite3.TimeFormat
@@ -653,12 +669,14 @@ type rows struct {
names []string
types []string
scans []scantype
dest []driver.Value
}
var (
// Ensure these interfaces are implemented:
_ driver.RowsColumnTypeDatabaseTypeName = &rows{}
_ driver.RowsColumnTypeNullable = &rows{}
// _ driver.RowsColumnScanner = &rows{}
)
func (r *rows) Close() error {
@@ -698,23 +716,17 @@ func (r *rows) loadColumnMetadata() {
types := make([]string, count)
scans := make([]scantype, count)
for i := range types {
var declType string
var notNull, autoInc bool
if column := r.Stmt.ColumnOriginName(i); column != "" {
declType, _, notNull, _, autoInc, _ = c.TableColumnMetadata(
var notnull bool
if col := r.Stmt.ColumnOriginName(i); col != "" {
types[i], _, notnull, _, _, _ = c.TableColumnMetadata(
r.Stmt.ColumnDatabaseName(i),
r.Stmt.ColumnTableName(i),
column)
} else {
declType = r.Stmt.ColumnDeclType(i)
}
if declType != "" {
declType = strings.ToUpper(declType)
scans[i] = scanFromDecl(declType)
types[i] = declType
}
if notNull || autoInc {
scans[i] |= _NOT_NULL
col)
types[i] = strings.ToUpper(types[i])
scans[i] = scanFromDecl(types[i])
if notnull {
scans[i] |= _NOT_NULL
}
}
}
r.types = types
@@ -784,6 +796,7 @@ func (r *rows) ColumnTypeScanType(index int) (typ reflect.Type) {
}
func (r *rows) Next(dest []driver.Value) error {
r.dest = nil
c := r.Stmt.Conn()
if old := c.SetInterrupt(r.ctx); old != r.ctx {
defer c.SetInterrupt(old)
@@ -833,5 +846,33 @@ func (r *rows) Next(dest []driver.Value) error {
}
}
}
r.dest = dest
return nil
}
func (r *rows) ScanColumn(dest any, index int) (err error) {
// notest // Go 1.26
var tm *time.Time
var ok *bool
switch d := dest.(type) {
case *time.Time:
tm = d
case *sql.NullTime:
tm = &d.Time
ok = &d.Valid
case *sql.Null[time.Time]:
tm = &d.V
ok = &d.Valid
default:
return driver.ErrSkip
}
value := r.dest[index]
*tm, err = r.tmRead.Decode(value)
if ok != nil {
*ok = err == nil
if value == nil {
return nil
}
}
return err
}
+34
View File
@@ -8,6 +8,7 @@ import (
"math"
"net/url"
"reflect"
"strings"
"testing"
"time"
@@ -520,6 +521,39 @@ func Test_ColumnType_ScanType(t *testing.T) {
}
}
func Test_rows_ScanColumn(t *testing.T) {
t.Parallel()
dsn := memdb.TestDB(t)
db, err := Open(dsn)
if err != nil {
t.Fatal(err)
}
defer db.Close()
var tm time.Time
err = db.QueryRow(`SELECT NULL`).Scan(&tm)
if err == nil {
t.Error("want error")
}
// Go 1.26
err = db.QueryRow(`SELECT datetime()`).Scan(&tm)
if err != nil && !strings.HasPrefix(err.Error(), "sql: Scan error") {
t.Error(err)
}
var nt sql.NullTime
err = db.QueryRow(`SELECT NULL`).Scan(&nt)
if err != nil {
t.Error(err)
}
// Go 1.26
err = db.QueryRow(`SELECT datetime()`).Scan(&nt)
if err != nil && !strings.HasPrefix(err.Error(), "sql: Scan error") {
t.Error(err)
}
}
func Benchmark_loop(b *testing.B) {
db, err := Open(":memory:")
if err != nil {
+5 -2
View File
@@ -1,6 +1,6 @@
# Embeddable Wasm build of SQLite
This folder includes an embeddable Wasm build of SQLite 3.51.2 for use with
This folder includes an embeddable Wasm build of SQLite 3.50.4 for use with
[`github.com/ncruces/go-sqlite3`](https://pkg.go.dev/github.com/ncruces/go-sqlite3).
The following optional features are compiled in:
@@ -33,6 +33,9 @@ The build is easily reproducible, and verifiable, using
You can use your own custom build of SQLite.
Example of a custom build of SQLite:
Examples of custom builds of SQLite are:
- [`github.com/ncruces/go-sqlite3/embed/bcw2`](https://github.com/ncruces/go-sqlite3/tree/main/embed/bcw2)
built from a branch supporting [`BEGIN CONCURRENT`](https://sqlite.org/src/doc/begin-concurrent/doc/begin_concurrent.md)
and [Wal2](https://sqlite.org/cgi/src/doc/wal2/doc/wal2.md).
- [`github.com/asg017/sqlite-vec-go-bindings/ncruces`](https://github.com/asg017/sqlite-vec-go-bindings)
which includes the [`sqlite-vec`](https://github.com/asg017/sqlite-vec) vector search extension.
+2
View File
@@ -0,0 +1,2 @@
build/
sqlite/
+22
View File
@@ -0,0 +1,22 @@
# Embeddable Wasm build of SQLite
This folder includes an alternative embeddable Wasm build of SQLite,
which includes the experimental
[`BEGIN CONCURRENT`](https://sqlite.org/src/doc/begin-concurrent/doc/begin_concurrent.md) and
[Wal2](https://sqlite.org/cgi/src/doc/wal2/doc/wal2.md) patches.
It also enables the optional
[`UPDATE … ORDER BY … LIMIT`](https://sqlite.org/lang_update.html#optional_limit_and_order_by_clauses) and
[`DELETE … ORDER BY … LIMIT`](https://sqlite.org/lang_delete.html#optional_limit_and_order_by_clauses) clauses,
and the [`WITHIN GROUP ORDER BY`](https://sqlite.org/compile.html#enable_ordered_set_aggregates) aggregate syntax.
> [!IMPORTANT]
> This package is experimental.
> It is built from the `bedrock` branch of SQLite,
> since that is _currently_ the most stable, maintained branch to include these features.
> [!CAUTION]
> The Wal2 journaling mode creates databases that other versions of SQLite cannot access.
The build is easily reproducible, and verifiable, using
[Artifact Attestations](https://github.com/ncruces/go-sqlite3/attestations).
BIN
View File
Binary file not shown.
+59
View File
@@ -0,0 +1,59 @@
package bcw2
import (
"path/filepath"
"testing"
"github.com/ncruces/go-sqlite3/driver"
"github.com/ncruces/go-sqlite3/ext/stats"
"github.com/ncruces/go-sqlite3/vfs"
)
func Test_bcw2(t *testing.T) {
if !vfs.SupportsSharedMemory {
t.Skip("skipping without shared memory")
}
tmp := filepath.ToSlash(filepath.Join(t.TempDir(), "test.db"))
db, err := driver.Open("file:"+tmp+"?_pragma=journal_mode(wal2)&_txlock=concurrent", stats.Register)
if err != nil {
t.Fatal(err)
}
defer db.Close()
tx, err := db.Begin()
if err != nil {
t.Fatal(err)
}
defer tx.Rollback()
_, err = tx.Exec(`CREATE TABLE test (col)`)
if err != nil {
t.Fatal(err)
}
_, err = tx.Exec(`DELETE FROM test LIMIT 1`)
if err != nil {
t.Fatal(err)
}
_, err = tx.Exec(`SELECT median() WITHIN GROUP (ORDER BY col) FROM test`)
if err != nil {
t.Fatal(err)
}
err = tx.Commit()
if err != nil {
t.Fatal(err)
}
var version string
err = db.QueryRow(`SELECT sqlite_version()`).Scan(&version)
if err != nil {
t.Fatal(err)
}
if version != "3.52.0" {
t.Error(version)
}
}
+72
View File
@@ -0,0 +1,72 @@
#!/usr/bin/env bash
set -euo pipefail
cd -P -- "$(dirname -- "$0")"
ROOT=../../
BINARYEN="$ROOT/tools/binaryen/bin"
WASI_SDK="$ROOT/tools/wasi-sdk/bin"
trap 'rm -rf sqlite/ build/ bcw2.tmp' EXIT
mkdir -p sqlite/
mkdir -p build/ext/
cp "$ROOT"/sqlite3/*.[ch] build/
cp "$ROOT"/sqlite3/*.patch build/
cd sqlite/
# https://sqlite.org/src/info/352b363a5d727047
curl -#L https://github.com/sqlite/sqlite/archive/dbd613c.tar.gz | tar xz --strip-components=1
# curl -#L https://sqlite.org/src/tarball/sqlite.tar.gz?r=352b363a5d | tar xz --strip-components=1
if [[ "$OSTYPE" == "msys" || "$OSTYPE" == "cygwin" ]]; then
MSYS_NO_PATHCONV=1 nmake /f makefile.msc sqlite3.c "OPTS=-DSQLITE_ENABLE_UPDATE_DELETE_LIMIT -DSQLITE_ENABLE_ORDERED_SET_AGGREGATES"
else
sh configure --enable-update-limit
OPTS=-DSQLITE_ENABLE_ORDERED_SET_AGGREGATES make sqlite3.c
fi
cd ~-
mv sqlite/sqlite3.c build/
mv sqlite/sqlite3.h build/
mv sqlite/sqlite3ext.h build/
mv sqlite/ext/misc/anycollseq.c build/ext/
mv sqlite/ext/misc/base64.c build/ext/
mv sqlite/ext/misc/decimal.c build/ext/
mv sqlite/ext/misc/ieee754.c build/ext/
mv sqlite/ext/misc/regexp.c build/ext/
mv sqlite/ext/misc/series.c build/ext/
mv sqlite/ext/misc/spellfix.c build/ext/
mv sqlite/ext/misc/uint.c build/ext/
cd build
cat *.patch | patch -p0 --no-backup-if-mismatch
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"$ROOT/sqlite3/libc" -I"build" \
-mexec-model=reactor \
-mmutable-globals -mnontrapping-fptoint \
-msimd128 -mbulk-memory -msign-ext \
-mreference-types -mmultivalue \
-mno-extended-const \
-fno-stack-protector \
-Wl,--stack-first \
-Wl,--import-undefined \
-Wl,--initial-memory=327680 \
-D_HAVE_SQLITE_CONFIG_H \
-DSQLITE_ENABLE_UPDATE_DELETE_LIMIT \
-DSQLITE_ENABLE_ORDERED_SET_AGGREGATES \
-DSQLITE_EXPERIMENTAL_PRAGMA_20251114 \
-DSQLITE_CUSTOM_INCLUDE=sqlite_opt.h \
$(awk '{print "-Wl,--export="$0}' ../exports.txt)
"$BINARYEN/wasm-ctor-eval" -g -c _initialize bcw2.wasm -o bcw2.tmp
"$BINARYEN/wasm-opt" -g bcw2.tmp -o bcw2.wasm \
--low-memory-unused --gufa --generate-global-effects --converge -O3 \
--enable-mutable-globals --enable-nontrapping-float-to-int \
--enable-simd --enable-bulk-memory --enable-sign-ext \
--enable-reference-types --enable-multivalue \
--strip --strip-producers
+12
View File
@@ -0,0 +1,12 @@
module github.com/ncruces/go-sqlite3/embed/bcw2
go 1.24.0
require github.com/ncruces/go-sqlite3 v0.30.1
require (
github.com/ncruces/julianday v1.0.0 // indirect
github.com/ncruces/sort v0.1.6 // indirect
github.com/tetratelabs/wazero v1.10.1 // indirect
golang.org/x/sys v0.38.0 // indirect
)
+12
View File
@@ -0,0 +1,12 @@
github.com/ncruces/go-sqlite3 v0.30.1 h1:pHC3YsyRdJv4pCMB4MO1Q2BXw/CAa+Hoj7GSaKtVk+g=
github.com/ncruces/go-sqlite3 v0.30.1/go.mod h1:UVsWrQaq1qkcal5/vT5lOJnZCVlR5rsThKdwidjFsKc=
github.com/ncruces/julianday v1.0.0 h1:fH0OKwa7NWvniGQtxdJRxAgkBMolni2BjDHaWTxqt7M=
github.com/ncruces/julianday v1.0.0/go.mod h1:Dusn2KvZrrovOMJuOt0TNXL6tB7U2E8kvza5fFc9G7g=
github.com/ncruces/sort v0.1.6 h1:TrsJfGRH1AoWoaeB4/+gCohot9+cA6u/INaH5agIhNk=
github.com/ncruces/sort v0.1.6/go.mod h1:obJToO4rYr6VWP0Uw5FYymgYGt3Br4RXcs/JdKaXAPk=
github.com/tetratelabs/wazero v1.10.1 h1:2DugeJf6VVk58KTPszlNfeeN8AhhpwcZqkJj2wwFuH8=
github.com/tetratelabs/wazero v1.10.1/go.mod h1:DRm5twOQ5Gr1AoEdSi0CLjDQF1J9ZAuyqFIjl1KKfQU=
golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k=
golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM=
+24
View File
@@ -0,0 +1,24 @@
// Package bcw2 embeds SQLite into your application.
//
// Importing package bcw2 initializes the [sqlite3.Binary] variable
// with a build of SQLite that includes the [BEGIN CONCURRENT] and [Wal2] patches:
//
// import _ "github.com/ncruces/go-sqlite3/embed/bcw2"
//
// [BEGIN CONCURRENT]: https://sqlite.org/src/doc/begin-concurrent/doc/begin_concurrent.md
// [Wal2]: https://sqlite.org/cgi/src/doc/wal2/doc/wal2.md
package bcw2
import (
_ "embed"
"unsafe"
"github.com/ncruces/go-sqlite3"
)
//go:embed bcw2.wasm
var binary string
func init() {
sqlite3.Binary = unsafe.Slice(unsafe.StringData(binary), len(binary))
}
+3 -5
View File
@@ -9,7 +9,7 @@ WASI_SDK="$ROOT/tools/wasi-sdk/bin"
trap 'rm -f sqlite3.tmp' EXIT
"$WASI_SDK/clang" --target=wasm32 -nostdlib -std=c23 -g0 -O2 \
"$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/libc" -I"$ROOT/sqlite3" \
@@ -23,15 +23,13 @@ trap 'rm -f sqlite3.tmp' EXIT
-Wl,--import-undefined \
-Wl,--initial-memory=327680 \
-D_HAVE_SQLITE_CONFIG_H \
-DSQLITE_EXPERIMENTAL_PRAGMA_20251114 \
-DSQLITE_CUSTOM_INCLUDE=sqlite_opt.h \
$(awk '{print "-Wl,--export="$0}' exports.txt)
"$BINARYEN/wasm-ctor-eval" -g -c _initialize sqlite3.wasm -o sqlite3.tmp
"$BINARYEN/wasm-opt" -g sqlite3.tmp -o sqlite3.wasm \
--gufa --generate-global-effects --low-memory-unused --converge -O3 \
--low-memory-unused --gufa --generate-global-effects --converge -O3 \
--enable-mutable-globals --enable-nontrapping-float-to-int \
--enable-simd --enable-bulk-memory --enable-sign-ext \
--enable-reference-types --enable-multivalue \
--disable-extended-const \
--strip --strip-producers
--strip --strip-producers
+1 -1
View File
@@ -19,7 +19,7 @@ func Test_init(t *testing.T) {
if err != nil {
t.Fatal(err)
}
if version != "3.51.3" {
if version != "3.51.0" {
t.Error(version)
}
}
Binary file not shown.
+13 -14
View File
@@ -16,7 +16,6 @@ import (
"github.com/ncruces/go-sqlite3"
"github.com/ncruces/go-sqlite3/internal/util"
"github.com/ncruces/go-sqlite3/util/sql3util"
)
// Register registers the bloom_filter virtual table:
@@ -35,8 +34,6 @@ type bloom struct {
hashes int
}
const vtab = `CREATE TABLE x(present, word TEXT HIDDEN NOT NULL PRIMARY KEY) WITHOUT ROWID`
func create(db *sqlite3.Conn, _, schema, table string, arg ...string) (_ *bloom, err error) {
b := bloom{
db: db,
@@ -58,9 +55,11 @@ func create(db *sqlite3.Conn, _, schema, table string, arg ...string) (_ *bloom,
}
if len(arg) > 1 {
var ok bool
b.prob, ok = sql3util.ParseFloat(arg[1])
if !ok || b.prob <= 0 || b.prob >= 1 {
b.prob, err = strconv.ParseFloat(arg[1], 64)
if err != nil {
return nil, err
}
if b.prob <= 0 || b.prob >= 1 {
return nil, util.ErrorString("bloom: probability must be in the range (0,1)")
}
} else {
@@ -81,7 +80,8 @@ func create(db *sqlite3.Conn, _, schema, table string, arg ...string) (_ *bloom,
b.bytes = numBytes(nelem, b.prob)
err = db.DeclareVTab(vtab)
err = db.DeclareVTab(
`CREATE TABLE x(present, word HIDDEN NOT NULL PRIMARY KEY) WITHOUT ROWID`)
if err != nil {
return nil, err
}
@@ -115,15 +115,15 @@ func connect(db *sqlite3.Conn, _, schema, table string, arg ...string) (_ *bloom
storage: table + "_storage",
}
err = db.DeclareVTab(vtab)
err = db.DeclareVTab(
`CREATE TABLE x(present, word HIDDEN NOT NULL PRIMARY KEY) WITHOUT ROWID`)
if err != nil {
return nil, err
}
load, _, err := db.PrepareFlags(fmt.Sprintf(
load, _, err := db.Prepare(fmt.Sprintf(
`SELECT m/8, p, k FROM %s.%s WHERE rowid = 1`,
sqlite3.QuoteIdentifier(b.schema), sqlite3.QuoteIdentifier(b.storage)),
sqlite3.PREPARE_DONT_LOG)
sqlite3.QuoteIdentifier(b.schema), sqlite3.QuoteIdentifier(b.storage)))
if err != nil {
return nil, err
}
@@ -166,10 +166,9 @@ func (t *bloom) ShadowTables() {
}
func (t *bloom) Integrity(schema, table string, flags int) error {
load, _, err := t.db.PrepareFlags(fmt.Sprintf(
load, _, err := t.db.Prepare(fmt.Sprintf(
`SELECT typeof(data), length(data), p, n, m, k FROM %s.%s WHERE rowid = 1`,
sqlite3.QuoteIdentifier(t.schema), sqlite3.QuoteIdentifier(t.storage)),
sqlite3.PREPARE_DONT_LOG)
sqlite3.QuoteIdentifier(t.schema), sqlite3.QuoteIdentifier(t.storage)))
if err != nil {
return fmt.Errorf("bloom: %v", err) // can't wrap!
}
+2 -2
View File
@@ -56,7 +56,7 @@ func Register(db *sqlite3.Conn) error {
done.Add(key)
}
err := db.DeclareVTab(`CREATE TABLE x(id INT,depth INT,root HIDDEN,tablename TEXT HIDDEN,idcolumn TEXT HIDDEN,parentcolumn TEXT HIDDEN)`)
err := db.DeclareVTab(`CREATE TABLE x(id,depth,root HIDDEN,tablename HIDDEN,idcolumn HIDDEN,parentcolumn HIDDEN)`)
if err != nil {
return nil, err
}
@@ -202,7 +202,7 @@ func (c *cursor) Filter(idxNum int, idxStr string, arg ...sqlite3.Value) error {
sqlite3.QuoteIdentifier(column),
sqlite3.QuoteIdentifier(parent),
)
stmt, _, err := c.db.PrepareFlags(sql, sqlite3.PREPARE_DONT_LOG)
stmt, _, err := c.db.Prepare(sql)
if err != nil {
return err
}
+22 -16
View File
@@ -85,8 +85,7 @@ func RegisterFS(db *sqlite3.Conn, fsys fs.FS) error {
header: header,
}
hadSchema := schema != ""
if !hadSchema {
if schema == "" {
var row []string
if header || columns < 0 {
csv, c, err := t.newReader()
@@ -100,14 +99,17 @@ func RegisterFS(db *sqlite3.Conn, fsys fs.FS) error {
}
}
schema = getSchema(header, columns, row)
} else {
t.typs, err = getColumnAffinities(schema)
if err != nil {
return nil, err
}
}
err = db.DeclareVTab(schema)
if err == nil {
err = db.VTabConfig(sqlite3.VTAB_DIRECTONLY)
}
if err == nil && hadSchema {
t.typs, err = getColumnAffinities(schema)
}
if err != nil {
return nil, err
}
@@ -121,7 +123,7 @@ type table struct {
fsys fs.FS
name string
data string
typs []sql3util.Affinity
typs []affinity
comma rune
comment rune
header bool
@@ -240,27 +242,31 @@ func (c *cursor) RowID() (int64, error) {
func (c *cursor) Column(ctx sqlite3.Context, col int) error {
if col < len(c.row) {
typ := sql3util.TEXT
typ := text
if col < len(c.table.typs) {
typ = c.table.typs[col]
}
txt := c.row[col]
if txt == "" && typ != sql3util.TEXT {
if txt == "" && typ != text {
return nil
}
switch typ {
case sql3util.NUMERIC, sql3util.INTEGER:
if i, err := strconv.ParseInt(txt, 10, 64); err == nil {
ctx.ResultInt64(i)
return nil
case numeric, integer:
if strings.TrimLeft(txt, "+-0123456789") == "" {
if i, err := strconv.ParseInt(txt, 10, 64); err == nil {
ctx.ResultInt64(i)
return nil
}
}
fallthrough
case sql3util.REAL:
if f, ok := sql3util.ParseFloat(txt); ok {
ctx.ResultFloat(f)
return nil
case real:
if strings.TrimLeft(txt, "+-.0123456789Ee") == "" {
if f, err := strconv.ParseFloat(txt, 64); err == nil {
ctx.ResultFloat(f)
return nil
}
}
fallthrough
default:
+13 -13
View File
@@ -9,31 +9,31 @@ import (
func getSchema(header bool, columns int, row []string) string {
var sep string
var buf strings.Builder
buf.WriteString("CREATE TABLE x(")
var str strings.Builder
str.WriteString("CREATE TABLE x(")
if 0 <= columns && columns < len(row) {
row = row[:columns]
}
for i, f := range row {
buf.WriteString(sep)
str.WriteString(sep)
if header && f != "" {
buf.WriteString(sqlite3.QuoteIdentifier(f))
str.WriteString(sqlite3.QuoteIdentifier(f))
} else {
buf.WriteString("c")
buf.WriteString(strconv.Itoa(i + 1))
str.WriteString("c")
str.WriteString(strconv.Itoa(i + 1))
}
buf.WriteString(" TEXT")
str.WriteString(" TEXT")
sep = ","
}
for i := len(row); i < columns; i++ {
buf.WriteString(sep)
buf.WriteString("c")
buf.WriteString(strconv.Itoa(i + 1))
buf.WriteString(" TEXT")
str.WriteString(sep)
str.WriteString("c")
str.WriteString(strconv.Itoa(i + 1))
str.WriteString(" TEXT")
sep = ","
}
buf.WriteByte(')')
str.WriteByte(')')
return buf.String()
return str.String()
}
+39 -4
View File
@@ -1,17 +1,52 @@
package csv
import "github.com/ncruces/go-sqlite3/util/sql3util"
import (
"strings"
func getColumnAffinities(schema string) ([]sql3util.Affinity, error) {
"github.com/ncruces/go-sqlite3/util/sql3util"
)
type affinity byte
const (
blob affinity = 0
text affinity = 1
numeric affinity = 2
integer affinity = 3
real affinity = 4
)
func getColumnAffinities(schema string) ([]affinity, error) {
tab, err := sql3util.ParseTable(schema)
if err != nil {
return nil, err
}
columns := tab.Columns
types := make([]sql3util.Affinity, len(columns))
types := make([]affinity, len(columns))
for i, col := range columns {
types[i] = sql3util.GetAffinity(col.Type)
types[i] = getAffinity(col.Type)
}
return types, nil
}
func getAffinity(declType string) affinity {
// https://sqlite.org/datatype3.html#determination_of_column_affinity
if declType == "" {
return blob
}
name := strings.ToUpper(declType)
if strings.Contains(name, "INT") {
return integer
}
if strings.Contains(name, "CHAR") || strings.Contains(name, "CLOB") || strings.Contains(name, "TEXT") {
return text
}
if strings.Contains(name, "BLOB") {
return blob
}
if strings.Contains(name, "REAL") || strings.Contains(name, "FLOA") || strings.Contains(name, "DOUB") {
return real
}
return numeric
}
+32
View File
@@ -0,0 +1,32 @@
package csv
import "testing"
func Test_getAffinity(t *testing.T) {
tests := []struct {
decl string
want affinity
}{
{"", blob},
{"INTEGER", integer},
{"TINYINT", integer},
{"TEXT", text},
{"CHAR", text},
{"CLOB", text},
{"BLOB", blob},
{"REAL", real},
{"FLOAT", real},
{"DOUBLE", real},
{"NUMERIC", numeric},
{"DECIMAL", numeric},
{"BOOLEAN", numeric},
{"DATETIME", numeric},
}
for _, tt := range tests {
t.Run(tt.decl, func(t *testing.T) {
if got := getAffinity(tt.decl); got != tt.want {
t.Errorf("getAffinity() = %v, want %v", got, tt.want)
}
})
}
}
+1 -1
View File
@@ -30,7 +30,7 @@ func RegisterFS(db *sqlite3.Conn, fsys fs.FS) error {
db.CreateFunction("readfile", 1, sqlite3.DIRECTONLY, readfile(fsys)),
db.CreateFunction("lsmode", 1, sqlite3.DETERMINISTIC, lsmode),
sqlite3.CreateModule(db, "fsdir", nil, func(db *sqlite3.Conn, _, _, _ string, _ ...string) (fsdir, error) {
err := db.DeclareVTab(`CREATE TABLE x(name TEXT,mode INT,mtime TIMESTAMP,data BLOB,path HIDDEN,dir HIDDEN)`)
err := db.DeclareVTab(`CREATE TABLE x(name,mode,mtime TIMESTAMP,data,path HIDDEN,dir HIDDEN)`)
if err == nil {
err = db.VTabConfig(sqlite3.VTAB_DIRECTONLY)
}
+6 -22
View File
@@ -43,14 +43,11 @@ func declare(db *sqlite3.Conn, _, _, _ string, arg ...string) (ret *table, err e
// Row key query.
t.scan = "SELECT * FROM\n" + arg[0]
stmt, tail, err := db.PrepareFlags(t.scan, 0 /*PREPARE_FROM_DDL*/)
stmt, _, err := db.Prepare(t.scan)
if err != nil {
return nil, err
}
defer stmt.Close()
if tail != "" {
return nil, util.TailErr
}
t.keys = make([]string, stmt.ColumnCount())
for i := range t.keys {
@@ -58,20 +55,15 @@ func declare(db *sqlite3.Conn, _, _, _ string, arg ...string) (ret *table, err e
t.keys[i] = name
create.WriteString(sep)
create.WriteString(name)
create.WriteString(" ")
create.WriteString(stmt.ColumnDeclType(i))
sep = ","
}
stmt.Close()
// Column definition query.
stmt, tail, err = db.PrepareFlags("SELECT * FROM\n"+arg[1], 0 /*PREPARE_FROM_DDL*/)
stmt, _, err = db.Prepare("SELECT * FROM\n" + arg[1])
if err != nil {
return nil, err
}
if tail != "" {
return nil, util.TailErr
}
if stmt.ColumnCount() != 2 {
return nil, util.ErrorString("pivot: column definition query expects 2 result columns")
@@ -79,23 +71,17 @@ func declare(db *sqlite3.Conn, _, _, _ string, arg ...string) (ret *table, err e
for stmt.Step() {
name := sqlite3.QuoteIdentifier(stmt.ColumnText(1))
t.cols = append(t.cols, stmt.ColumnValue(0).Dup())
create.WriteString(sep)
create.WriteString(",")
create.WriteString(name)
create.WriteString(" ")
create.WriteString(stmt.ColumnDeclType(1))
sep = ","
}
stmt.Close()
// Pivot cell query.
t.cell = "SELECT * FROM\n" + arg[2]
stmt, tail, err = db.PrepareFlags(t.cell, 0 /*PREPARE_FROM_DDL*/)
stmt, _, err = db.Prepare(t.cell)
if err != nil {
return nil, err
}
if tail != "" {
return nil, util.TailErr
}
if stmt.ColumnCount() != 1 {
return nil, util.ErrorString("pivot: cell query expects 1 result columns")
@@ -196,9 +182,7 @@ func (c *cursor) Filter(idxNum int, idxStr string, arg ...sqlite3.Value) error {
return err
}
const prepflags = sqlite3.PREPARE_DONT_LOG /*PREPARE_FROM_DDL*/
c.scan, _, err = c.table.db.PrepareFlags(idxStr, prepflags)
c.scan, _, err = c.table.db.Prepare(idxStr)
if err != nil {
return err
}
@@ -210,7 +194,7 @@ func (c *cursor) Filter(idxNum int, idxStr string, arg ...sqlite3.Value) error {
}
if c.cell == nil {
c.cell, _, err = c.table.db.PrepareFlags(c.table.cell, prepflags)
c.cell, _, err = c.table.db.Prepare(c.table.cell)
if err != nil {
return err
}
+16 -22
View File
@@ -35,45 +35,40 @@ func declare(db *sqlite3.Conn, _, _, _ string, arg ...string) (*table, error) {
sql := "SELECT * FROM\n" + arg[0]
stmt, tail, err := db.PrepareFlags(sql,
sqlite3.PREPARE_PERSISTENT /*PREPARE_FROM_DDL*/)
stmt, _, err := db.PrepareFlags(sql, sqlite3.PREPARE_PERSISTENT)
if err != nil {
return nil, err
}
if tail != "" {
stmt.Close()
return nil, util.TailErr
}
var sep string
var buf strings.Builder
buf.WriteString("CREATE TABLE x(")
var str strings.Builder
str.WriteString("CREATE TABLE x(")
outputs := stmt.ColumnCount()
for i := range outputs {
name := sqlite3.QuoteIdentifier(stmt.ColumnName(i))
buf.WriteString(sep)
buf.WriteString(name)
buf.WriteString(" ")
buf.WriteString(stmt.ColumnDeclType(i))
str.WriteString(sep)
str.WriteString(name)
str.WriteString(" ")
str.WriteString(stmt.ColumnDeclType(i))
sep = ","
}
inputs := stmt.BindCount()
for i := 1; i <= inputs; i++ {
buf.WriteString(sep)
str.WriteString(sep)
name := stmt.BindName(i)
if name == "" {
buf.WriteString("[")
buf.WriteString(strconv.Itoa(i))
buf.WriteString("] HIDDEN")
str.WriteString("[")
str.WriteString(strconv.Itoa(i))
str.WriteString("] HIDDEN")
} else {
buf.WriteString(sqlite3.QuoteIdentifier(name[1:]))
buf.WriteString(" HIDDEN")
str.WriteString(sqlite3.QuoteIdentifier(name[1:]))
str.WriteString(" HIDDEN")
}
sep = ","
}
buf.WriteByte(')')
str.WriteByte(')')
err = db.DeclareVTab(buf.String())
err = db.DeclareVTab(str.String())
if err != nil {
stmt.Close()
return nil, err
@@ -134,8 +129,7 @@ func (t *table) Open() (_ sqlite3.VTabCursor, err error) {
if !t.inuse {
t.inuse = true
} else {
stmt, _, err = t.stmt.Conn().PrepareFlags(t.sql,
sqlite3.PREPARE_DONT_LOG /*PREPARE_FROM_DDL*/)
stmt, _, err = t.stmt.Conn().Prepare(t.sql)
if err != nil {
return nil, err
}
+6 -6
View File
@@ -5,15 +5,15 @@ package stats
type kahan struct{ hi, lo float64 }
func (k *kahan) add(x float64) {
y := float64(k.lo + x)
t := float64(k.hi + y)
k.lo = y - float64(t-k.hi)
y := k.lo + x
t := k.hi + y
k.lo = y - (t - k.hi)
k.hi = t
}
func (k *kahan) sub(x float64) {
y := float64(k.lo - x)
t := float64(k.hi + y)
k.lo = y - float64(t-k.hi)
y := k.lo - x
t := k.hi + y
k.lo = y - (t - k.hi)
k.hi = t
}
-21
View File
@@ -1,21 +0,0 @@
package stats
import (
"math"
"github.com/ncruces/go-sqlite3"
)
func cot(ctx sqlite3.Context, arg ...sqlite3.Value) {
if f := arg[0].Float(); f != 0.0 {
ctx.ResultFloat(1 / math.Tan(f))
}
}
func cbrt(ctx sqlite3.Context, arg ...sqlite3.Value) {
a := arg[0]
f := a.Float()
if f != 0.0 || a.Type() != sqlite3.NULL {
ctx.ResultFloat(math.Cbrt(f))
}
}
+5 -5
View File
@@ -34,7 +34,7 @@ type percentile struct {
func (q *percentile) Step(ctx sqlite3.Context, arg ...sqlite3.Value) {
a := arg[0]
f := a.Float()
if f != 0.0 || a.Type() != sqlite3.NULL {
if f != 0.0 || a.NumericType() != sqlite3.NULL {
q.nums = append(q.nums, f)
}
if q.kind != median && q.arg1 == nil {
@@ -45,7 +45,7 @@ func (q *percentile) Step(ctx sqlite3.Context, arg ...sqlite3.Value) {
func (q *percentile) Inverse(ctx sqlite3.Context, arg ...sqlite3.Value) {
a := arg[0]
f := a.Float()
if f != 0.0 || a.Type() != sqlite3.NULL {
if f != 0.0 || a.NumericType() != sqlite3.NULL {
i := slices.Index(q.nums, f)
l := len(q.nums) - 1
q.nums[i] = q.nums[l]
@@ -66,12 +66,12 @@ func (q *percentile) Value(ctx sqlite3.Context) {
if q.kind == median {
float, err = q.at(0.5)
ctx.ResultFloat(float)
} else if err = json.Unmarshal(q.arg1, &floats); err == nil {
err = q.atMore(floats)
ctx.ResultJSON(floats)
} else if err = json.Unmarshal(q.arg1, &float); err == nil {
float, err = q.at(float)
ctx.ResultFloat(float)
} else if err = json.Unmarshal(q.arg1, &floats); err == nil {
err = q.atMore(floats)
ctx.ResultJSON(floats)
}
if err != nil {
ctx.ResultError(fmt.Errorf("percentile: %w", err)) // notest
+9 -11
View File
@@ -91,9 +91,7 @@ func Register(db *sqlite3.Conn) error {
db.CreateWindowFunction("percentile_disc", 2, order, newPercentile(percentile_disc)),
db.CreateWindowFunction("every", 1, flags, newBoolean(every)),
db.CreateWindowFunction("some", 1, flags, newBoolean(some)),
db.CreateWindowFunction("mode", 1, order, newMode),
db.CreateFunction("cbrt", 1, flags, cbrt),
db.CreateFunction("cot", 1, flags, cot))
db.CreateWindowFunction("mode", 1, order, newMode))
}
const (
@@ -170,7 +168,7 @@ func (fn *variance) Value(ctx sqlite3.Context) {
func (fn *variance) Step(ctx sqlite3.Context, arg ...sqlite3.Value) {
a := arg[0]
f := a.Float()
if f != 0.0 || a.Type() != sqlite3.NULL {
if f != 0.0 || a.NumericType() != sqlite3.NULL {
fn.enqueue(f)
}
}
@@ -178,7 +176,7 @@ func (fn *variance) Step(ctx sqlite3.Context, arg ...sqlite3.Value) {
func (fn *variance) Inverse(ctx sqlite3.Context, arg ...sqlite3.Value) {
a := arg[0]
f := a.Float()
if f != 0.0 || a.Type() != sqlite3.NULL {
if f != 0.0 || a.NumericType() != sqlite3.NULL {
fn.dequeue(f)
}
}
@@ -243,8 +241,8 @@ func (fn *covariance) Step(ctx sqlite3.Context, arg ...sqlite3.Value) {
fa := a.Float()
fb := b.Float()
if true &&
(fa != 0.0 || a.Type() != sqlite3.NULL) &&
(fb != 0.0 || b.Type() != sqlite3.NULL) {
(fa != 0.0 || a.NumericType() != sqlite3.NULL) &&
(fb != 0.0 || b.NumericType() != sqlite3.NULL) {
fn.enqueue(fa, fb)
}
}
@@ -254,8 +252,8 @@ func (fn *covariance) Inverse(ctx sqlite3.Context, arg ...sqlite3.Value) {
fa := a.Float()
fb := b.Float()
if true &&
(fa != 0.0 || a.Type() != sqlite3.NULL) &&
(fb != 0.0 || b.Type() != sqlite3.NULL) {
(fa != 0.0 || a.NumericType() != sqlite3.NULL) &&
(fb != 0.0 || b.NumericType() != sqlite3.NULL) {
fn.dequeue(fa, fb)
}
}
@@ -295,7 +293,7 @@ func (fn *momentfn) Value(ctx sqlite3.Context) {
func (fn *momentfn) Step(ctx sqlite3.Context, arg ...sqlite3.Value) {
a := arg[0]
f := a.Float()
if f != 0.0 || a.Type() != sqlite3.NULL {
if f != 0.0 || a.NumericType() != sqlite3.NULL {
fn.enqueue(f)
}
}
@@ -303,7 +301,7 @@ func (fn *momentfn) Step(ctx sqlite3.Context, arg ...sqlite3.Value) {
func (fn *momentfn) Inverse(ctx sqlite3.Context, arg ...sqlite3.Value) {
a := arg[0]
f := a.Float()
if f != 0.0 || a.Type() != sqlite3.NULL {
if f != 0.0 || a.NumericType() != sqlite3.NULL {
fn.dequeue(f)
}
}
+6 -33
View File
@@ -233,6 +233,9 @@ func TestRegister_covariance(t *testing.T) {
}
func Benchmark_average(b *testing.B) {
sqlite3.Initialize()
b.ResetTimer()
db, err := sqlite3.Open(":memory:")
if err != nil {
b.Fatal(err)
@@ -250,7 +253,6 @@ func Benchmark_average(b *testing.B) {
b.Fatal(err)
}
b.ResetTimer()
if !stmt.Step() {
b.Fatal(stmt.Err())
} else {
@@ -267,6 +269,9 @@ func Benchmark_average(b *testing.B) {
}
func Benchmark_variance(b *testing.B) {
sqlite3.Initialize()
b.ResetTimer()
db, err := sqlite3.Open(":memory:")
if err != nil {
b.Fatal(err)
@@ -284,7 +289,6 @@ func Benchmark_variance(b *testing.B) {
b.Fatal(err)
}
b.ResetTimer()
if !stmt.Step() {
b.Fatal(stmt.Err())
} else if b.N > 100 {
@@ -299,34 +303,3 @@ func Benchmark_variance(b *testing.B) {
b.Error(err)
}
}
func Benchmark_math(b *testing.B) {
benchmarks := []string{"sqrt", "tan", "cot", "cbrt"}
db, err := sqlite3.Open(":memory:")
if err != nil {
b.Fatal(err)
}
defer db.Close()
for _, bm := range benchmarks {
b.Run(bm, func(b *testing.B) {
stmt, _, err := db.Prepare(`SELECT ` + bm + `(value) FROM generate_series(0, ?)`)
if err != nil {
b.Fatal(err)
}
defer stmt.Close()
err = stmt.BindInt(1, b.N)
if err != nil {
b.Fatal(err)
}
b.ResetTimer()
err = stmt.Exec()
if err != nil {
b.Fatal(err)
}
})
}
}
+2 -5
View File
@@ -31,11 +31,8 @@ func ExampleConn_CreateAggregateFunction() {
count := 0
total := 0.0
for arg := range seq {
switch arg[0].NumericType() {
case sqlite3.FLOAT, sqlite3.INTEGER:
total += arg[0].Float()
count++
}
total += arg[0].Float()
count++
}
ctx.ResultFloat(total / float64(count))
})
+6 -8
View File
@@ -5,24 +5,22 @@ go 1.24.0
require (
github.com/ncruces/julianday v1.0.0
github.com/ncruces/sort v0.1.6
github.com/ncruces/wbt v1.0.0
github.com/tetratelabs/wazero v1.11.0
golang.org/x/sys v0.41.0
github.com/ncruces/wbt v0.2.0
github.com/tetratelabs/wazero v1.10.1
golang.org/x/sys v0.38.0
)
require (
github.com/dchest/siphash v1.2.3 // ext/bloom
github.com/google/uuid v1.6.0 // ext/uuid
github.com/psanford/httpreadat v0.1.0 // example
golang.org/x/crypto v0.48.0 // vfs/adiantum vfs/xts
golang.org/x/sync v0.19.0 // test
golang.org/x/text v0.34.0 // ext/unicode
golang.org/x/crypto v0.45.0 // vfs/adiantum vfs/xts
golang.org/x/sync v0.18.0 // test
golang.org/x/text v0.31.0 // ext/unicode
lukechampine.com/adiantum v1.1.1 // vfs/adiantum
)
retract (
v0.31.1 // SQLite 3.52.0 withdrawn
v0.31.0 // SQLite 3.52.0 withdrawn
v0.23.2 // tagged from the wrong branch
v0.4.0 // tagged from the wrong branch
)
+12 -12
View File
@@ -6,19 +6,19 @@ github.com/ncruces/julianday v1.0.0 h1:fH0OKwa7NWvniGQtxdJRxAgkBMolni2BjDHaWTxqt
github.com/ncruces/julianday v1.0.0/go.mod h1:Dusn2KvZrrovOMJuOt0TNXL6tB7U2E8kvza5fFc9G7g=
github.com/ncruces/sort v0.1.6 h1:TrsJfGRH1AoWoaeB4/+gCohot9+cA6u/INaH5agIhNk=
github.com/ncruces/sort v0.1.6/go.mod h1:obJToO4rYr6VWP0Uw5FYymgYGt3Br4RXcs/JdKaXAPk=
github.com/ncruces/wbt v1.0.0 h1:8iBE7UPjTLUpzu3/FCRjAmuQjWzgxo10RGBgt3ooLSc=
github.com/ncruces/wbt v1.0.0/go.mod h1:DtF92amvMxH69EmBFUSFWRDAlo6hOEfoNQnClxj9C/c=
github.com/ncruces/wbt v0.2.0 h1:Q9zlKOBSZc7Yy/R2cGa35g6RKUUE3BjNIW3tfGC4F04=
github.com/ncruces/wbt v0.2.0/go.mod h1:DtF92amvMxH69EmBFUSFWRDAlo6hOEfoNQnClxj9C/c=
github.com/psanford/httpreadat v0.1.0 h1:VleW1HS2zO7/4c7c7zNl33fO6oYACSagjJIyMIwZLUE=
github.com/psanford/httpreadat v0.1.0/go.mod h1:Zg7P+TlBm3bYbyHTKv/EdtSJZn3qwbPwpfZ/I9GKCRE=
github.com/tetratelabs/wazero v1.11.0 h1:+gKemEuKCTevU4d7ZTzlsvgd1uaToIDtlQlmNbwqYhA=
github.com/tetratelabs/wazero v1.11.0/go.mod h1:eV28rsN8Q+xwjogd7f4/Pp4xFxO7uOGbLcD/LzB1wiU=
golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts=
golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos=
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=
golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA=
github.com/tetratelabs/wazero v1.10.1 h1:2DugeJf6VVk58KTPszlNfeeN8AhhpwcZqkJj2wwFuH8=
github.com/tetratelabs/wazero v1.10.1/go.mod h1:DRm5twOQ5Gr1AoEdSi0CLjDQF1J9ZAuyqFIjl1KKfQU=
golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q=
golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4=
golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I=
golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM=
golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM=
lukechampine.com/adiantum v1.1.1 h1:4fp6gTxWCqpEbLy40ExiYDDED3oUNWx5cTqBCtPdZqA=
lukechampine.com/adiantum v1.1.1/go.mod h1:LrAYVnTYLnUtE/yMp5bQr0HstAf060YUF8nM0B6+rUw=
+4 -4
View File
@@ -3,7 +3,7 @@ module github.com/ncruces/go-sqlite3/gormlite
go 1.24.0
require (
github.com/ncruces/go-sqlite3 v0.30.4
github.com/ncruces/go-sqlite3 v0.30.1
gorm.io/gorm v1.31.1
)
@@ -11,7 +11,7 @@ require (
github.com/jinzhu/inflection v1.0.0 // indirect
github.com/jinzhu/now v1.1.5 // indirect
github.com/ncruces/julianday v1.0.0 // indirect
github.com/tetratelabs/wazero v1.11.0 // indirect
golang.org/x/sys v0.40.0 // indirect
golang.org/x/text v0.33.0 // indirect
github.com/tetratelabs/wazero v1.10.1 // indirect
golang.org/x/sys v0.38.0 // indirect
golang.org/x/text v0.31.0 // indirect
)
+8 -8
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.30.4 h1:j9hEoOL7f9ZoXl8uqXVniaq1VNwlWAXihZbTvhqPPjA=
github.com/ncruces/go-sqlite3 v0.30.4/go.mod h1:7WR20VSC5IZusKhUdiR9y1NsUqnZgqIYCmKKoMEYg68=
github.com/ncruces/go-sqlite3 v0.30.1 h1:pHC3YsyRdJv4pCMB4MO1Q2BXw/CAa+Hoj7GSaKtVk+g=
github.com/ncruces/go-sqlite3 v0.30.1/go.mod h1:UVsWrQaq1qkcal5/vT5lOJnZCVlR5rsThKdwidjFsKc=
github.com/ncruces/julianday v1.0.0 h1:fH0OKwa7NWvniGQtxdJRxAgkBMolni2BjDHaWTxqt7M=
github.com/ncruces/julianday v1.0.0/go.mod h1:Dusn2KvZrrovOMJuOt0TNXL6tB7U2E8kvza5fFc9G7g=
github.com/tetratelabs/wazero v1.11.0 h1:+gKemEuKCTevU4d7ZTzlsvgd1uaToIDtlQlmNbwqYhA=
github.com/tetratelabs/wazero v1.11.0/go.mod h1:eV28rsN8Q+xwjogd7f4/Pp4xFxO7uOGbLcD/LzB1wiU=
golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ=
golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE=
golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8=
github.com/tetratelabs/wazero v1.10.1 h1:2DugeJf6VVk58KTPszlNfeeN8AhhpwcZqkJj2wwFuH8=
github.com/tetratelabs/wazero v1.10.1/go.mod h1:DRm5twOQ5Gr1AoEdSi0CLjDQF1J9ZAuyqFIjl1KKfQU=
golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM=
golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM=
gorm.io/gorm v1.31.1 h1:7CA8FTFz/gRfgqgpeKIBcervUn3xSyPUmr6B2WXJ7kg=
gorm.io/gorm v1.31.1/go.mod h1:XyQVbO2k6YkOis7C2437jSit3SsDK72s7n7rsSHd+Gs=
+47 -71
View File
@@ -2,27 +2,21 @@ package util
import (
"context"
"math"
"github.com/tetratelabs/wazero"
"github.com/tetratelabs/wazero/api"
)
const (
i32s = "\x7f\x7f\x7f\x7f\x7f\x7f"
i64s = "\x7e"
f64s = "\x7c\x7c"
)
type funcVI[T0 i32] func(context.Context, api.Module, T0)
func (fn funcVI[T0]) Call(ctx context.Context, mod api.Module, stack []uint64) {
fn(ctx, mod, T0(stack[0]))
}
func ExportFuncVI[T0 i32](mod wazero.HostModuleBuilder, name string, fn funcVI[T0]) {
func ExportFuncVI[T0 i32](mod wazero.HostModuleBuilder, name string, fn func(context.Context, api.Module, T0)) {
mod.NewFunctionBuilder().
WithGoModuleFunction(fn, []api.ValueType(i32s[:1]), nil).
WithGoModuleFunction(funcVI[T0](fn),
[]api.ValueType{api.ValueTypeI32}, nil).
Export(name)
}
@@ -33,9 +27,10 @@ func (fn funcVIII[T0, T1, T2]) Call(ctx context.Context, mod api.Module, stack [
fn(ctx, mod, T0(stack[0]), T1(stack[1]), T2(stack[2]))
}
func ExportFuncVIII[T0, T1, T2 i32](mod wazero.HostModuleBuilder, name string, fn funcVIII[T0, T1, T2]) {
func ExportFuncVIII[T0, T1, T2 i32](mod wazero.HostModuleBuilder, name string, fn func(context.Context, api.Module, T0, T1, T2)) {
mod.NewFunctionBuilder().
WithGoModuleFunction(fn, []api.ValueType(i32s[:3]), nil).
WithGoModuleFunction(funcVIII[T0, T1, T2](fn),
[]api.ValueType{api.ValueTypeI32, api.ValueTypeI32, api.ValueTypeI32}, nil).
Export(name)
}
@@ -46,9 +41,10 @@ func (fn funcVIIII[T0, T1, T2, T3]) Call(ctx context.Context, mod api.Module, st
fn(ctx, mod, T0(stack[0]), T1(stack[1]), T2(stack[2]), T3(stack[3]))
}
func ExportFuncVIIII[T0, T1, T2, T3 i32](mod wazero.HostModuleBuilder, name string, fn funcVIIII[T0, T1, T2, T3]) {
func ExportFuncVIIII[T0, T1, T2, T3 i32](mod wazero.HostModuleBuilder, name string, fn func(context.Context, api.Module, T0, T1, T2, T3)) {
mod.NewFunctionBuilder().
WithGoModuleFunction(fn, []api.ValueType(i32s[:4]), nil).
WithGoModuleFunction(funcVIIII[T0, T1, T2, T3](fn),
[]api.ValueType{api.ValueTypeI32, api.ValueTypeI32, api.ValueTypeI32, api.ValueTypeI32}, nil).
Export(name)
}
@@ -59,9 +55,10 @@ func (fn funcVIIIII[T0, T1, T2, T3, T4]) Call(ctx context.Context, mod api.Modul
fn(ctx, mod, T0(stack[0]), T1(stack[1]), T2(stack[2]), T3(stack[3]), T4(stack[4]))
}
func ExportFuncVIIIII[T0, T1, T2, T3, T4 i32](mod wazero.HostModuleBuilder, name string, fn funcVIIIII[T0, T1, T2, T3, T4]) {
func ExportFuncVIIIII[T0, T1, T2, T3, T4 i32](mod wazero.HostModuleBuilder, name string, fn func(context.Context, api.Module, T0, T1, T2, T3, T4)) {
mod.NewFunctionBuilder().
WithGoModuleFunction(fn, []api.ValueType(i32s[:5]), nil).
WithGoModuleFunction(funcVIIIII[T0, T1, T2, T3, T4](fn),
[]api.ValueType{api.ValueTypeI32, api.ValueTypeI32, api.ValueTypeI32, api.ValueTypeI32, api.ValueTypeI32}, nil).
Export(name)
}
@@ -72,21 +69,23 @@ func (fn funcVIIIIJ[T0, T1, T2, T3, T4]) Call(ctx context.Context, mod api.Modul
fn(ctx, mod, T0(stack[0]), T1(stack[1]), T2(stack[2]), T3(stack[3]), T4(stack[4]))
}
func ExportFuncVIIIIJ[T0, T1, T2, T3 i32, T4 i64](mod wazero.HostModuleBuilder, name string, fn funcVIIIIJ[T0, T1, T2, T3, T4]) {
func ExportFuncVIIIIJ[T0, T1, T2, T3 i32, T4 i64](mod wazero.HostModuleBuilder, name string, fn func(context.Context, api.Module, T0, T1, T2, T3, T4)) {
mod.NewFunctionBuilder().
WithGoModuleFunction(fn, []api.ValueType(i32s[:4]+i64s[:1]), nil).
WithGoModuleFunction(funcVIIIIJ[T0, T1, T2, T3, T4](fn),
[]api.ValueType{api.ValueTypeI32, api.ValueTypeI32, api.ValueTypeI32, api.ValueTypeI32, api.ValueTypeI64}, nil).
Export(name)
}
type funcII[TR, T0 i32] func(context.Context, api.Module, T0) TR
func (fn funcII[TR, T0]) Call(ctx context.Context, mod api.Module, stack []uint64) {
stack[0] = uint64(uint32(fn(ctx, mod, T0(stack[0]))))
stack[0] = uint64(fn(ctx, mod, T0(stack[0])))
}
func ExportFuncII[TR, T0 i32](mod wazero.HostModuleBuilder, name string, fn funcII[TR, T0]) {
func ExportFuncII[TR, T0 i32](mod wazero.HostModuleBuilder, name string, fn func(context.Context, api.Module, T0) TR) {
mod.NewFunctionBuilder().
WithGoModuleFunction(fn, []api.ValueType(i32s[:1]), []api.ValueType(i32s[:1])).
WithGoModuleFunction(funcII[TR, T0](fn),
[]api.ValueType{api.ValueTypeI32}, []api.ValueType{api.ValueTypeI32}).
Export(name)
}
@@ -94,12 +93,13 @@ type funcIII[TR, T0, T1 i32] func(context.Context, api.Module, T0, T1) TR
func (fn funcIII[TR, T0, T1]) Call(ctx context.Context, mod api.Module, stack []uint64) {
_ = stack[1] // prevent bounds check on every slice access
stack[0] = uint64(uint32(fn(ctx, mod, T0(stack[0]), T1(stack[1]))))
stack[0] = uint64(fn(ctx, mod, T0(stack[0]), T1(stack[1])))
}
func ExportFuncIII[TR, T0, T1 i32](mod wazero.HostModuleBuilder, name string, fn funcIII[TR, T0, T1]) {
func ExportFuncIII[TR, T0, T1 i32](mod wazero.HostModuleBuilder, name string, fn func(context.Context, api.Module, T0, T1) TR) {
mod.NewFunctionBuilder().
WithGoModuleFunction(fn, []api.ValueType(i32s[:2]), []api.ValueType(i32s[:1])).
WithGoModuleFunction(funcIII[TR, T0, T1](fn),
[]api.ValueType{api.ValueTypeI32, api.ValueTypeI32}, []api.ValueType{api.ValueTypeI32}).
Export(name)
}
@@ -107,12 +107,13 @@ type funcIIII[TR, T0, T1, T2 i32] func(context.Context, api.Module, T0, T1, T2)
func (fn funcIIII[TR, T0, T1, T2]) Call(ctx context.Context, mod api.Module, stack []uint64) {
_ = stack[2] // prevent bounds check on every slice access
stack[0] = uint64(uint32(fn(ctx, mod, T0(stack[0]), T1(stack[1]), T2(stack[2]))))
stack[0] = uint64(fn(ctx, mod, T0(stack[0]), T1(stack[1]), T2(stack[2])))
}
func ExportFuncIIII[TR, T0, T1, T2 i32](mod wazero.HostModuleBuilder, name string, fn funcIIII[TR, T0, T1, T2]) {
func ExportFuncIIII[TR, T0, T1, T2 i32](mod wazero.HostModuleBuilder, name string, fn func(context.Context, api.Module, T0, T1, T2) TR) {
mod.NewFunctionBuilder().
WithGoModuleFunction(fn, []api.ValueType(i32s[:3]), []api.ValueType(i32s[:1])).
WithGoModuleFunction(funcIIII[TR, T0, T1, T2](fn),
[]api.ValueType{api.ValueTypeI32, api.ValueTypeI32, api.ValueTypeI32}, []api.ValueType{api.ValueTypeI32}).
Export(name)
}
@@ -120,12 +121,13 @@ type funcIIIII[TR, T0, T1, T2, T3 i32] func(context.Context, api.Module, T0, T1,
func (fn funcIIIII[TR, T0, T1, T2, T3]) Call(ctx context.Context, mod api.Module, stack []uint64) {
_ = stack[3] // prevent bounds check on every slice access
stack[0] = uint64(uint32(fn(ctx, mod, T0(stack[0]), T1(stack[1]), T2(stack[2]), T3(stack[3]))))
stack[0] = uint64(fn(ctx, mod, T0(stack[0]), T1(stack[1]), T2(stack[2]), T3(stack[3])))
}
func ExportFuncIIIII[TR, T0, T1, T2, T3 i32](mod wazero.HostModuleBuilder, name string, fn funcIIIII[TR, T0, T1, T2, T3]) {
func ExportFuncIIIII[TR, T0, T1, T2, T3 i32](mod wazero.HostModuleBuilder, name string, fn func(context.Context, api.Module, T0, T1, T2, T3) TR) {
mod.NewFunctionBuilder().
WithGoModuleFunction(fn, []api.ValueType(i32s[:4]), []api.ValueType(i32s[:1])).
WithGoModuleFunction(funcIIIII[TR, T0, T1, T2, T3](fn),
[]api.ValueType{api.ValueTypeI32, api.ValueTypeI32, api.ValueTypeI32, api.ValueTypeI32}, []api.ValueType{api.ValueTypeI32}).
Export(name)
}
@@ -133,12 +135,13 @@ type funcIIIIII[TR, T0, T1, T2, T3, T4 i32] func(context.Context, api.Module, T0
func (fn funcIIIIII[TR, T0, T1, T2, T3, T4]) Call(ctx context.Context, mod api.Module, stack []uint64) {
_ = stack[4] // prevent bounds check on every slice access
stack[0] = uint64(uint32(fn(ctx, mod, T0(stack[0]), T1(stack[1]), T2(stack[2]), T3(stack[3]), T4(stack[4]))))
stack[0] = uint64(fn(ctx, mod, T0(stack[0]), T1(stack[1]), T2(stack[2]), T3(stack[3]), T4(stack[4])))
}
func ExportFuncIIIIII[TR, T0, T1, T2, T3, T4 i32](mod wazero.HostModuleBuilder, name string, fn funcIIIIII[TR, T0, T1, T2, T3, T4]) {
func ExportFuncIIIIII[TR, T0, T1, T2, T3, T4 i32](mod wazero.HostModuleBuilder, name string, fn func(context.Context, api.Module, T0, T1, T2, T3, T4) TR) {
mod.NewFunctionBuilder().
WithGoModuleFunction(fn, []api.ValueType(i32s[:5]), []api.ValueType(i32s[:1])).
WithGoModuleFunction(funcIIIIII[TR, T0, T1, T2, T3, T4](fn),
[]api.ValueType{api.ValueTypeI32, api.ValueTypeI32, api.ValueTypeI32, api.ValueTypeI32, api.ValueTypeI32}, []api.ValueType{api.ValueTypeI32}).
Export(name)
}
@@ -146,12 +149,13 @@ type funcIIIIIII[TR, T0, T1, T2, T3, T4, T5 i32] func(context.Context, api.Modul
func (fn funcIIIIIII[TR, T0, T1, T2, T3, T4, T5]) Call(ctx context.Context, mod api.Module, stack []uint64) {
_ = stack[5] // prevent bounds check on every slice access
stack[0] = uint64(uint32(fn(ctx, mod, T0(stack[0]), T1(stack[1]), T2(stack[2]), T3(stack[3]), T4(stack[4]), T5(stack[5]))))
stack[0] = uint64(fn(ctx, mod, T0(stack[0]), T1(stack[1]), T2(stack[2]), T3(stack[3]), T4(stack[4]), T5(stack[5])))
}
func ExportFuncIIIIIII[TR, T0, T1, T2, T3, T4, T5 i32](mod wazero.HostModuleBuilder, name string, fn funcIIIIIII[TR, T0, T1, T2, T3, T4, T5]) {
func ExportFuncIIIIIII[TR, T0, T1, T2, T3, T4, T5 i32](mod wazero.HostModuleBuilder, name string, fn func(context.Context, api.Module, T0, T1, T2, T3, T4, T5) TR) {
mod.NewFunctionBuilder().
WithGoModuleFunction(fn, []api.ValueType(i32s[:6]), []api.ValueType(i32s[:1])).
WithGoModuleFunction(funcIIIIIII[TR, T0, T1, T2, T3, T4, T5](fn),
[]api.ValueType{api.ValueTypeI32, api.ValueTypeI32, api.ValueTypeI32, api.ValueTypeI32, api.ValueTypeI32, api.ValueTypeI32}, []api.ValueType{api.ValueTypeI32}).
Export(name)
}
@@ -159,12 +163,13 @@ type funcIIIIJ[TR, T0, T1, T2 i32, T3 i64] func(context.Context, api.Module, T0,
func (fn funcIIIIJ[TR, T0, T1, T2, T3]) Call(ctx context.Context, mod api.Module, stack []uint64) {
_ = stack[3] // prevent bounds check on every slice access
stack[0] = uint64(uint32(fn(ctx, mod, T0(stack[0]), T1(stack[1]), T2(stack[2]), T3(stack[3]))))
stack[0] = uint64(fn(ctx, mod, T0(stack[0]), T1(stack[1]), T2(stack[2]), T3(stack[3])))
}
func ExportFuncIIIIJ[TR, T0, T1, T2 i32, T3 i64](mod wazero.HostModuleBuilder, name string, fn funcIIIIJ[TR, T0, T1, T2, T3]) {
func ExportFuncIIIIJ[TR, T0, T1, T2 i32, T3 i64](mod wazero.HostModuleBuilder, name string, fn func(context.Context, api.Module, T0, T1, T2, T3) TR) {
mod.NewFunctionBuilder().
WithGoModuleFunction(fn, []api.ValueType(i32s[:3]+i64s[:1]), []api.ValueType(i32s[:1])).
WithGoModuleFunction(funcIIIIJ[TR, T0, T1, T2, T3](fn),
[]api.ValueType{api.ValueTypeI32, api.ValueTypeI32, api.ValueTypeI32, api.ValueTypeI64}, []api.ValueType{api.ValueTypeI32}).
Export(name)
}
@@ -172,41 +177,12 @@ type funcIIJ[TR, T0 i32, T1 i64] func(context.Context, api.Module, T0, T1) TR
func (fn funcIIJ[TR, T0, T1]) Call(ctx context.Context, mod api.Module, stack []uint64) {
_ = stack[1] // prevent bounds check on every slice access
stack[0] = uint64(uint32(fn(ctx, mod, T0(stack[0]), T1(stack[1]))))
stack[0] = uint64(fn(ctx, mod, T0(stack[0]), T1(stack[1])))
}
func ExportFuncIIJ[TR, T0 i32, T1 i64](mod wazero.HostModuleBuilder, name string, fn funcIIJ[TR, T0, T1]) {
func ExportFuncIIJ[TR, T0 i32, T1 i64](mod wazero.HostModuleBuilder, name string, fn func(context.Context, api.Module, T0, T1) TR) {
mod.NewFunctionBuilder().
WithGoModuleFunction(fn, []api.ValueType(i32s[:1]+i64s[:1]), []api.ValueType(i32s[:1])).
Export(name)
}
type flt = interface{ ~float32 | ~float64 }
type funcDD[TR, T0 flt] func(T0) TR
func (fn funcDD[TR, T0]) Call(ctx context.Context, mod api.Module, stack []uint64) {
stack[0] = math.Float64bits(float64(fn(
T0(math.Float64frombits(stack[0])))))
}
func ExportFuncDD[TR, T0 flt](mod wazero.HostModuleBuilder, name string, fn funcDD[TR, T0]) {
mod.NewFunctionBuilder().
WithGoModuleFunction(fn, []api.ValueType(f64s[:1]), []api.ValueType(f64s[:1])).
Export(name)
}
type funcDDD[TR, T0, T1 flt] func(T0, T1) TR
func (fn funcDDD[TR, T0, T1]) Call(ctx context.Context, mod api.Module, stack []uint64) {
_ = stack[1] // prevent bounds check on every slice access
stack[0] = math.Float64bits(float64(fn(
T0(math.Float64frombits(stack[0])),
T1(math.Float64frombits(stack[1])))))
}
func ExportFuncDDD[TR, T0, T1 flt](mod wazero.HostModuleBuilder, name string, fn funcDDD[TR, T0, T1]) {
mod.NewFunctionBuilder().
WithGoModuleFunction(fn, []api.ValueType(f64s[:2]), []api.ValueType(f64s[:1])).
WithGoModuleFunction(funcIIJ[TR, T0, T1](fn),
[]api.ValueType{api.ValueTypeI32, api.ValueTypeI64}, []api.ValueType{api.ValueTypeI32}).
Export(name)
}
+3 -3
View File
@@ -13,9 +13,9 @@ const (
)
type (
i8 = interface{ ~int8 | ~uint8 }
i32 = interface{ ~int32 | ~uint32 }
i64 = interface{ ~int64 | ~uint64 }
i8 interface{ ~int8 | ~uint8 }
i32 interface{ ~int32 | ~uint32 }
i64 interface{ ~int64 | ~uint64 }
Stk_t = uint64
Ptr_t uint32
-5
View File
@@ -1,5 +0,0 @@
package util
func ValidPageSize(s int) bool {
return s&(s-1) == 0 && 512 <= s && s <= 65536
}
-142
View File
@@ -1,142 +0,0 @@
package util
import (
"strconv"
"strings"
"time"
)
func ParseBool(s string) (b, ok bool) {
if len(s) == 0 {
return false, false
}
if s[0] == '0' {
return false, true
}
if '1' <= s[0] && s[0] <= '9' {
return true, true
}
switch strings.ToLower(s) {
case "true", "yes", "on":
return true, true
case "false", "no", "off":
return false, true
}
return false, false
}
func ParseFloat(s string) (f float64, ok bool) {
if strings.TrimLeft(s, "+-.0123456789Ee") != "" {
return
}
f, err := strconv.ParseFloat(s, 64)
return f, err == nil
}
func ParseTimeShift(s string) (years, months, days int, duration time.Duration, ok bool) {
// Sign part: ±
neg := strings.HasPrefix(s, "-")
sign := neg || strings.HasPrefix(s, "+")
if sign {
s = s[1:]
}
if ok = len(s) >= 5; !ok {
return // !ok
}
defer func() {
if neg {
years = -years
months = -months
days = -days
duration = -duration
}
}()
// Date part: YYYY-MM-DD
if s[4] == '-' {
if ok = sign && len(s) >= 10 && s[7] == '-'; !ok {
return // !ok
}
if years, ok = parseInt(s[0:4], 0); !ok {
return // !ok
}
if months, ok = parseInt(s[5:7], 12); !ok {
return // !ok
}
if days, ok = parseInt(s[8:10], 31); !ok {
return // !ok
}
if len(s) == 10 {
return
}
if ok = s[10] == ' '; !ok {
return // !ok
}
s = s[11:]
}
// Time part: HH:MM
if ok = len(s) >= 5 && s[2] == ':'; !ok {
return // !ok
}
var hours, minutes int
if hours, ok = parseInt(s[0:2], 24); !ok {
return
}
if minutes, ok = parseInt(s[3:5], 60); !ok {
return
}
duration = time.Duration(hours)*time.Hour + time.Duration(minutes)*time.Minute
if len(s) == 5 {
return
}
if ok = len(s) >= 8 && s[5] == ':'; !ok {
return // !ok
}
// Seconds part: HH:MM:SS
var seconds int
if seconds, ok = parseInt(s[6:8], 60); !ok {
return
}
duration += time.Duration(seconds) * time.Second
if len(s) == 8 {
return
}
if ok = len(s) >= 10 && s[8] == '.'; !ok {
return // !ok
}
s = s[9:]
// Nanosecond part: HH:MM:SS.SSS
var nanos int
if nanos, ok = parseInt(s[0:min(9, len(s))], 0); !ok {
return
}
for i := len(s); i < 9; i++ {
nanos *= 10
}
duration += time.Duration(nanos)
// Subnanosecond part.
if len(s) > 9 {
_, ok = parseInt(s[9:], 0)
}
return
}
func parseInt(s string, max int) (i int, _ bool) {
for _, r := range []byte(s) {
r -= '0'
if r > 9 {
return
}
i = i*10 + int(r)
}
return i, max == 0 || i < max
}
-144
View File
@@ -1,144 +0,0 @@
package util_test
import (
"testing"
"time"
"github.com/ncruces/go-sqlite3"
_ "github.com/ncruces/go-sqlite3/embed"
"github.com/ncruces/go-sqlite3/internal/util"
)
func TestParseBool(t *testing.T) {
tests := []struct {
str string
val bool
ok bool
}{
{"", false, false},
{"0", false, true},
{"1", true, true},
{"9", true, true},
{"T", false, false},
{"true", true, true},
{"FALSE", false, true},
{"false?", false, false},
}
for _, tt := range tests {
t.Run(tt.str, func(t *testing.T) {
gotVal, gotOK := util.ParseBool(tt.str)
if gotVal != tt.val || gotOK != tt.ok {
t.Errorf("ParseBool(%q) = (%v, %v) want (%v, %v)", tt.str, gotVal, gotOK, tt.val, tt.ok)
}
})
}
}
func TestParseTimeShift(t *testing.T) {
epoch := time.Unix(0, 0)
tests := []struct {
str string
val time.Time
ok bool
}{
{"", epoch, false},
{"0001-11-30", epoch, false},
{"+_001-11-30", epoch, false},
{"+0001-_1-30", epoch.AddDate(1, 0, 0), false},
{"+0001-11-_0", epoch.AddDate(1, 11, 0), false},
{"+0001-11-30", epoch.AddDate(1, 11, 30), true},
{"-0001-11-30", epoch.AddDate(-1, -11, -30), true},
{"+0001-11-30T", epoch.AddDate(1, 11, 30), false},
{"+0001-11-30 12", epoch.AddDate(1, 11, 30), false},
{"+0001-11-30 _2:30", epoch.AddDate(1, 11, 30), false},
{"+0001-11-30 12:_0", epoch.AddDate(1, 11, 30), false},
{"+0001-11-30 12:30", epoch.AddDate(1, 11, 30).Add(12*time.Hour + 30*time.Minute), true},
{"+0001-11-30 12:30:", epoch.AddDate(1, 11, 30).Add(12*time.Hour + 30*time.Minute), false},
{"+0001-11-30 12:30:_0", epoch.AddDate(1, 11, 30).Add(12*time.Hour + 30*time.Minute), false},
{"+0001-11-30 12:30:59", epoch.AddDate(1, 11, 30).Add(12*time.Hour + 30*time.Minute + 59*time.Second), true},
{"+0001-11-30 12:30:59.", epoch.AddDate(1, 11, 30).Add(12*time.Hour + 30*time.Minute + 59*time.Second), false},
{"+0001-11-30 12:30:59._", epoch.AddDate(1, 11, 30).Add(12*time.Hour + 30*time.Minute + 59*time.Second), false},
{"+0001-11-30 12:30:59.1", epoch.AddDate(1, 11, 30).Add(12*time.Hour + 30*time.Minute + 59*time.Second + 100*time.Millisecond), true},
{"+0001-11-30 12:30:59.123456789_", epoch.AddDate(1, 11, 30).Add(12*time.Hour + 30*time.Minute + 59*time.Second + 123456789), false},
{"+0001-11-30 12:30:59.1234567890", epoch.AddDate(1, 11, 30).Add(12*time.Hour + 30*time.Minute + 59*time.Second + 123456789), true},
{"-12:30:59.1234567890", epoch.Add(-12*time.Hour - 30*time.Minute - 59*time.Second - 123456789), true},
}
for _, tt := range tests {
t.Run(tt.str, func(t *testing.T) {
years, months, days, duration, gotOK := util.ParseTimeShift(tt.str)
gotVal := epoch.AddDate(years, months, days).Add(duration)
if !gotVal.Equal(tt.val) || gotOK != tt.ok {
t.Errorf("ParseTimeShift(%q) = (%v, %v) want (%v, %v)", tt.str, gotVal, gotOK, tt.val, tt.ok)
}
})
}
}
func FuzzParseTimeShift(f *testing.F) {
f.Add("")
f.Add("0001-12-30")
f.Add("+_001-12-30")
f.Add("+0001-_2-30")
f.Add("+0001-12-_0")
f.Add("+0001-12-30")
f.Add("-0001-12-30")
f.Add("+0001-12-30T")
f.Add("+0001-12-30 12")
f.Add("+0001-12-30 _2:30")
f.Add("+0001-12-30 12:_0")
f.Add("+0001-12-30 12:30")
f.Add("+0001-12-30 12:30:")
f.Add("+0001-12-30 12:30:_0")
f.Add("+0001-12-30 12:30:60")
f.Add("+0001-12-30 12:30:60.")
f.Add("+0001-12-30 12:30:60._")
f.Add("+0001-12-30 12:30:60.1")
f.Add("+0001-12-30 12:30:60.123456789_")
f.Add("+0001-12-30 12:30:60.1234567890")
f.Add("-12:30:60.1234567890")
c, err := sqlite3.Open(":memory:")
if err != nil {
f.Fatal(err)
}
defer c.Close()
s, _, err := c.Prepare(`SELECT julianday('00:00', ?)`)
if err != nil {
f.Fatal(err)
}
defer s.Close()
// Default SQLite date.
epoch := time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC)
f.Fuzz(func(t *testing.T, str string) {
years, months, days, duration, ok := util.ParseTimeShift(str)
// Account for a full 400 year cycle.
if years < -200 || years > +200 {
t.Skip()
}
// SQLite only tracks milliseconds.
if duration != duration.Truncate(time.Millisecond) {
t.Skip()
}
if ok {
s.Reset()
s.BindText(1, str)
if !s.Step() {
t.Fail()
}
got := epoch.AddDate(years, months, days).Add(duration)
// Julian day introduces floating point inaccuracy.
want := s.ColumnTime(0, sqlite3.TimeFormatJulianDay)
want = want.Round(time.Millisecond)
if !got.Equal(want) {
t.Fatalf("with %q, got %v, want %v", str, got, want)
}
}
})
}
-4
View File
@@ -20,10 +20,6 @@ func JSON(value any) any {
// ResultJSON sets the result of the function to the JSON encoding of value.
//
// If you return JSON from application-defined functions,
// consider registering those functions with [RESULT_SUBTYPE]
// and calling [Context.ResultSubtype] with 'J'.
//
// https://sqlite.org/c3ref/result_blob.html
func (ctx Context) ResultJSON(value any) {
err := json.NewEncoder(callbackWriter(func(p []byte) (int, error) {
+3 -4
View File
@@ -40,7 +40,7 @@ func (s *Stmt) BindJSON(param int, value any) error {
w := bytesWriter{sqlite: s.c.sqlite}
if err := json.MarshalWrite(&w, value); err != nil {
s.c.free(w.ptr)
return err // notest
return err
}
rc := res_t(s.c.call("sqlite3_bind_text_go",
stk_t(s.handle), stk_t(param),
@@ -100,8 +100,8 @@ type bytesWriter struct {
}
func (b *bytesWriter) Write(p []byte) (n int, err error) {
if len(p) > cap(b.buf)-len(b.buf)-1 {
want := int64(len(b.buf)) + int64(len(p)) + 1
if len(p) > cap(b.buf)-len(b.buf) {
want := int64(len(b.buf)) + int64(len(p))
grow := int64(cap(b.buf))
grow += grow >> 1
want = max(want, grow)
@@ -109,6 +109,5 @@ func (b *bytesWriter) Write(p []byte) (n int, err error) {
b.buf = util.View(b.mod, b.ptr, want)[:len(b.buf)]
}
b.buf = append(b.buf, p...)
_ = append(b.buf, 0)
return len(p), nil
}
+11
View File
@@ -0,0 +1,11 @@
# Litestream lightweight read-replicas
This package implements the **EXPERIMENTAL** `"litestream"` SQLite VFS
that offers Litestream [lightweight read-replicas](https://fly.io/blog/litestream-revamped/#lightweight-read-replicas).
See the [example](vfs_test.go) for how to use.
To improve performance,
increase `PollInterval` (and `MinLevel`) as much as you can,
and set [`PRAGMA cache_size=N`](https://www.sqlite.org/pragma.html#pragma_cache_size)
(or use `_pragma=cache_size(N)`).
+78
View File
@@ -0,0 +1,78 @@
// Package litestream implements a Litestream lightweight read-replica VFS.
package litestream
import (
"log/slog"
"sync"
"time"
"github.com/benbjohnson/litestream"
"github.com/ncruces/go-sqlite3/vfs"
)
const (
// The default poll interval.
DefaultPollInterval = 1 * time.Second
// The default cache size: 10 MiB.
DefaultCacheSize = 10 * 1024 * 1024
)
func init() {
vfs.Register("litestream", liteVFS{})
}
var (
liteMtx sync.RWMutex
// +checklocks:liteMtx
liteDBs = map[string]*liteDB{}
)
// ReplicaOptions represents options for [NewReplica].
type ReplicaOptions struct {
// Where to log error messages. May be nil.
Logger *slog.Logger
// Replica poll interval.
// Should be less than the compaction interval
// used by the replica at MinLevel+1.
PollInterval time.Duration
// Minimum compaction level to track.
MinLevel int
// CacheSize is the maximum size of the page cache in bytes.
// Zero means DefaultCacheSize, negative disables caching.
CacheSize int
}
// NewReplica creates a read-replica from a Litestream client.
func NewReplica(name string, client litestream.ReplicaClient, options ReplicaOptions) {
if options.Logger != nil {
options.Logger = options.Logger.With("name", name)
} else {
options.Logger = slog.New(slog.DiscardHandler)
}
if options.PollInterval <= 0 {
options.PollInterval = DefaultPollInterval
}
if options.CacheSize == 0 {
options.CacheSize = DefaultCacheSize
}
liteMtx.Lock()
defer liteMtx.Unlock()
liteDBs[name] = &liteDB{
client: client,
opts: options,
cache: pageCache{size: options.CacheSize},
}
}
// RemoveReplica removes a replica by name.
func RemoveReplica(name string) {
liteMtx.Lock()
defer liteMtx.Unlock()
delete(liteDBs, name)
}
+72
View File
@@ -0,0 +1,72 @@
package litestream
import (
"encoding/binary"
"sync"
"golang.org/x/sync/singleflight"
"github.com/superfly/ltx"
)
type pageCache struct {
single singleflight.Group
pages map[uint32]cachedPage // +checklocks:mtx
size int
mtx sync.Mutex
}
type cachedPage struct {
data []byte
txid ltx.TXID
}
func (c *pageCache) getOrFetch(pgno uint32, maxTXID ltx.TXID, fetch func() (any, error)) ([]byte, error) {
if c.size >= 0 {
c.mtx.Lock()
if c.pages == nil {
c.pages = map[uint32]cachedPage{}
}
page := c.pages[pgno]
c.mtx.Unlock()
if page.txid == maxTXID {
return page.data, nil
}
}
var key [12]byte
binary.LittleEndian.PutUint32(key[0:], pgno)
binary.LittleEndian.PutUint64(key[4:], uint64(maxTXID))
v, err, _ := c.single.Do(string(key[:]), fetch)
if err != nil {
return nil, err
}
page := cachedPage{v.([]byte), maxTXID}
if c.size >= 0 {
c.mtx.Lock()
c.evict(len(page.data))
c.pages[pgno] = page
c.mtx.Unlock()
}
return page.data, nil
}
// +checklocks:c.mtx
func (c *pageCache) evict(pageSize int) {
// Evict random keys until we're under the maximum size.
// SQLite has its own page cache, which it will use for each connection.
// Since this is a second layer of shared cache,
// random eviction is probably good enough.
if pageSize*len(c.pages) < c.size {
return
}
for key := range c.pages {
delete(c.pages, key)
if pageSize*len(c.pages) < c.size {
return
}
}
}
+48
View File
@@ -0,0 +1,48 @@
package litestream_test
import (
"log"
"time"
"github.com/benbjohnson/litestream/s3"
"github.com/ncruces/go-sqlite3/driver"
_ "github.com/ncruces/go-sqlite3/embed"
"github.com/ncruces/go-sqlite3/litestream"
)
func ExampleNewReplica() {
client := s3.NewReplicaClient()
client.Bucket = "test-bucket"
client.Path = "fruits.db"
litestream.NewReplica("fruits.db", client, litestream.ReplicaOptions{
PollInterval: 5 * time.Second,
})
db, err := driver.Open("file:fruits.db?vfs=litestream")
if err != nil {
log.Fatalln(err)
}
defer db.Close()
for {
time.Sleep(time.Second)
rows, err := db.Query("SELECT * FROM fruits")
if err != nil {
log.Fatalln(err)
}
for rows.Next() {
var name, color string
err := rows.Scan(&name, &color)
if err != nil {
log.Fatalln(err)
}
log.Println(name, color)
}
log.Println("===")
rows.Close()
}
}
+63
View File
@@ -0,0 +1,63 @@
module github.com/ncruces/go-sqlite3/litestream
go 1.24.4
require (
github.com/benbjohnson/litestream v0.5.2
github.com/ncruces/go-sqlite3 v0.30.1
github.com/ncruces/wbt v0.2.0
github.com/superfly/ltx v0.5.0
)
// github.com/ncruces/go-sqlite3
require (
github.com/ncruces/julianday v1.0.0 // indirect
github.com/tetratelabs/wazero v1.10.1 // indirect
golang.org/x/sys v0.38.0 // indirect
)
// github.com/superfly/ltx
require github.com/pierrec/lz4/v4 v4.1.22 // indirect
// github.com/benbjohnson/litestream
require (
filippo.io/age v1.2.1 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/kr/text v0.2.0 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/prometheus/client_golang v1.23.2 // indirect
github.com/prometheus/client_model v0.6.2 // indirect
github.com/prometheus/common v0.67.3 // indirect
github.com/prometheus/procfs v0.19.2 // indirect
github.com/psanford/sqlite3vfs v0.0.0-20240315230605-24e1d98cf361 // indirect
go.yaml.in/yaml/v2 v2.4.3 // indirect
golang.org/x/crypto v0.45.0 // indirect
google.golang.org/protobuf v1.36.10 // indirect
modernc.org/sqlite v1.40.1 // indirect
)
// github.com/benbjohnson/litestream/s3
require (
github.com/aws/aws-sdk-go-v2 v1.37.1 // indirect
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.0 // indirect
github.com/aws/aws-sdk-go-v2/config v1.30.2 // indirect
github.com/aws/aws-sdk-go-v2/credentials v1.18.2 // indirect
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.1 // indirect
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.18.2 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.1 // indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.1 // indirect
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3 // indirect
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.1 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.0 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.8.1 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.1 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.1 // indirect
github.com/aws/aws-sdk-go-v2/service/s3 v1.85.1 // indirect
github.com/aws/aws-sdk-go-v2/service/sso v1.26.1 // indirect
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.31.1 // indirect
github.com/aws/aws-sdk-go-v2/service/sts v1.35.1 // indirect
github.com/aws/smithy-go v1.22.5 // indirect
)
replace modernc.org/sqlite => github.com/ncruces/go-sqlite3/litestream/modernc v0.0.0-20251109124432-99b097de3b79
+197
View File
@@ -0,0 +1,197 @@
c2sp.org/CCTV/age v0.0.0-20240306222714-3ec4d716e805 h1:u2qwJeEvnypw+OCPUHmoZE3IqwfuN5kgDfo5MLzpNM0=
c2sp.org/CCTV/age v0.0.0-20240306222714-3ec4d716e805/go.mod h1:FomMrUJ2Lxt5jCLmZkG3FHa72zUprnhd3v/Z18Snm4w=
cloud.google.com/go v0.111.0 h1:YHLKNupSD1KqjDbQ3+LVdQ81h/UJbJyZG203cEfnQgM=
cloud.google.com/go v0.111.0/go.mod h1:0mibmpKP1TyOOFYQY5izo0LnT+ecvOQ0Sg3OdmMiNRU=
cloud.google.com/go/compute v1.23.3 h1:6sVlXXBmbd7jNX0Ipq0trII3e4n1/MsADLK6a+aiVlk=
cloud.google.com/go/compute v1.23.3/go.mod h1:VCgBUoMnIVIR0CscqQiPJLAG25E3ZRZMzcFZeQ+h8CI=
cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY=
cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA=
cloud.google.com/go/iam v1.1.5 h1:1jTsCu4bcsNsE4iiqNT5SHwrDRCfRmIaaaVFhRveTJI=
cloud.google.com/go/iam v1.1.5/go.mod h1:rB6P/Ic3mykPbFio+vo7403drjlgvoWfYpJhMXEbzv8=
cloud.google.com/go/storage v1.36.0 h1:P0mOkAcaJxhCTvAkMhxMfrTKiNcub4YmmPBtlhAyTr8=
cloud.google.com/go/storage v1.36.0/go.mod h1:M6M/3V/D3KpzMTJyPOR/HU6n2Si5QdaXYEsng2xgOs8=
filippo.io/age v1.2.1 h1:X0TZjehAZylOIj4DubWYU1vWQxv9bJpo+Uu2/LGhi1o=
filippo.io/age v1.2.1/go.mod h1:JL9ew2lTN+Pyft4RiNGguFfOpewKwSHm5ayKD/A4004=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.2 h1:Hr5FTipp7SL07o2FvoVOX9HRiRH3CR3Mj8pxqCcdD5A=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.2/go.mod h1:QyVsSSN64v5TGltphKLQ2sQxe4OBQg0J1eKRcVBnfgE=
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.11.0 h1:MhRfI58HblXzCtWEZCO0feHs8LweePB3s90r7WaR1KU=
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.11.0/go.mod h1:okZ+ZURbArNdlJ+ptXoyHNuOETzOl1Oww19rm8I2WLA=
github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2 h1:9iefClla7iYpfYWdzPCRDozdmndjTm8DXdpCzPajMgA=
github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2/go.mod h1:XtLgD3ZD34DAaVIIAyG3objl5DynM3CQ/vMcbBNJZGI=
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.6.2 h1:FwladfywkNirM+FZYLBR2kBz5C8Tg0fw5w5Y7meRXWI=
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.6.2/go.mod h1:vv5Ad0RrIoT1lJFdWBZwt4mB1+j+V8DUroixmKDTCdk=
github.com/AzureAD/microsoft-authentication-library-for-go v1.4.2 h1:oygO0locgZJe7PpYPXT5A29ZkwJaPqcva7BVeemZOZs=
github.com/AzureAD/microsoft-authentication-library-for-go v1.4.2/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI=
github.com/aws/aws-sdk-go-v2 v1.37.1 h1:SMUxeNz3Z6nqGsXv0JuJXc8w5YMtrQMuIBmDx//bBDY=
github.com/aws/aws-sdk-go-v2 v1.37.1/go.mod h1:9Q0OoGQoboYIAJyslFyF1f5K1Ryddop8gqMhWx/n4Wg=
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.0 h1:6GMWV6CNpA/6fbFHnoAjrv4+LGfyTqZz2LtCHnspgDg=
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.0/go.mod h1:/mXlTIVG9jbxkqDnr5UQNQxW1HRYxeGklkM9vAFeabg=
github.com/aws/aws-sdk-go-v2/config v1.30.2 h1:YE1BmSc4fFYqFgN1mN8uzrtc7R9x+7oSWeX8ckoltAw=
github.com/aws/aws-sdk-go-v2/config v1.30.2/go.mod h1:UNrLGZ6jfAVjgVJpkIxjLufRJqTXCVYOpkeVf83kwBo=
github.com/aws/aws-sdk-go-v2/credentials v1.18.2 h1:mfm0GKY/PHLhs7KO0sUaOtFnIQ15Qqxt+wXbO/5fIfs=
github.com/aws/aws-sdk-go-v2/credentials v1.18.2/go.mod h1:v0SdJX6ayPeZFQxgXUKw5RhLpAoZUuynxWDfh8+Eknc=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.1 h1:owmNBboeA0kHKDcdF8KiSXmrIuXZustfMGGytv6OMkM=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.1/go.mod h1:Bg1miN59SGxrZqlP8vJZSmXW+1N8Y1MjQDq1OfuNod8=
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.18.2 h1:YFX4DvH1CPQXgQR8935b46Om+L7+6jus4aTdKqyDR84=
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.18.2/go.mod h1:DgMPy7GqxcV0RSyaITnI3rw8HC3lIHB87U3KPQKDxHg=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.1 h1:ksZXBYv80EFTcgc8OJO48aQ8XDWXIQL7gGasPeCoTzI=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.1/go.mod h1:HSksQyyJETVZS7uM54cir0IgxttTD+8aEoJMPGepHBI=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.1 h1:+dn/xF/05utS7tUhjIcndbuaPjfll2LhbH1cCDGLYUQ=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.1/go.mod h1:hyAGz30LHdm5KBZDI58MXx5lDVZ5CUfvfTZvMu4HCZo=
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3 h1:bIqFDwgGXXN1Kpp99pDOdKMTTb5d2KyU5X/BZxjOkRo=
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3/go.mod h1:H5O/EsxDWyU+LP/V8i5sm8cxoZgc2fdNR9bxlOFrQTo=
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.1 h1:4HbnOGE9491a9zYJ9VpPh1ApgEq6ZlD4Kuv1PJenFpc=
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.1/go.mod h1:Z6QnHC6TmpJWUxAy8FI4JzA7rTwl6EIANkyK9OR5z5w=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.0 h1:6+lZi2JeGKtCraAj1rpoZfKqnQ9SptseRZioejfUOLM=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.0/go.mod h1:eb3gfbVIxIoGgJsi9pGne19dhCBpK6opTYpQqAmdy44=
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.8.1 h1:ps3nrmBWdWwakZBydGX1CxeYFK80HsQ79JLMwm7Y4/c=
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.8.1/go.mod h1:bAdfrfxENre68Hh2swNaGEVuFYE74o0SaSCAlaG9E74=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.1 h1:ky79ysLMxhwk5rxJtS+ILd3Mc8kC5fhsLBrP27r6h4I=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.1/go.mod h1:+2MmkvFvPYM1vsozBWduoLJUi5maxFk5B7KJFECujhY=
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.1 h1:MdVYlN5pcQu1t1OYx4Ajo3fKl1IEhzgdPQbYFCRjYS8=
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.1/go.mod h1:iikmNLrvHm2p4a3/4BPeix2S9P+nW8yM1IZW73x8bFA=
github.com/aws/aws-sdk-go-v2/service/s3 v1.85.1 h1:Hsqo8+dFxSdDvv9B2PgIx1AJAnDpqgS0znVI+R+MoGY=
github.com/aws/aws-sdk-go-v2/service/s3 v1.85.1/go.mod h1:8Q0TAPXD68Z8YqlcIGHs/UNIDHsxErV9H4dl4vJEpgw=
github.com/aws/aws-sdk-go-v2/service/sso v1.26.1 h1:uWaz3DoNK9MNhm7i6UGxqufwu3BEuJZm72WlpGwyVtY=
github.com/aws/aws-sdk-go-v2/service/sso v1.26.1/go.mod h1:ILpVNjL0BO+Z3Mm0SbEeUoYS9e0eJWV1BxNppp0fcb8=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.31.1 h1:XdG6/o1/ZDmn3wJU5SRAejHaWgKS4zHv0jBamuKuS2k=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.31.1/go.mod h1:oiotGTKadCOCl3vg/tYh4k45JlDF81Ka8rdumNhEnIQ=
github.com/aws/aws-sdk-go-v2/service/sts v1.35.1 h1:iF4Xxkc0H9c/K2dS0zZw3SCkj0Z7n6AMnUiiyoJND+I=
github.com/aws/aws-sdk-go-v2/service/sts v1.35.1/go.mod h1:0bxIatfN0aLq4mjoLDeBpOjOke68OsFlXPDFJ7V0MYw=
github.com/aws/smithy-go v1.22.5 h1:P9ATCXPMb2mPjYBgueqJNCA5S9UfktsW0tTxi+a7eqw=
github.com/aws/smithy-go v1.22.5/go.mod h1:t1ufH5HMublsJYulve2RKmHDC15xu1f26kHCp/HgceI=
github.com/benbjohnson/litestream v0.5.2 h1:uD9I17n6RgUgyCwPM/Sw2YXNmMGixecUB5kmJ4FL08o=
github.com/benbjohnson/litestream v0.5.2/go.mod h1:jSW6AGqbxmJnEXGjMHchlZclGphzbJ6jGrGo5fYIDhU=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/go-logr/logr v1.3.0 h1:2y3SDp0ZXuc6/cjLSZ+Q3ir+QB9T/iG5yYRXqsagWSY=
github.com/go-logr/logr v1.3.0/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo=
github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE=
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/s2a-go v0.1.7 h1:60BLSyTrOV4/haCDW4zb1guZItoSq8foHCXrAnjBo/o=
github.com/google/s2a-go v0.1.7/go.mod h1:50CgR4k1jNlWBu4UfS4AcfhVe1r6pdZPygJ3R8F0Qdw=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/enterprise-certificate-proxy v0.3.2 h1:Vie5ybvEvT75RniqhfFxPRy3Bf7vr3h0cechB90XaQs=
github.com/googleapis/enterprise-certificate-proxy v0.3.2/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0=
github.com/googleapis/gax-go/v2 v2.12.0 h1:A+gCJKdRfqXkr+BIRGtZLibNXf0m1f9E4HG56etFpas=
github.com/googleapis/gax-go/v2 v2.12.0/go.mod h1:y+aIqrI5eb1YGMVJfuV3185Ts/D7qKpsEkdD5+I6QGU=
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
github.com/kr/fs v0.1.0 h1:Jskdu9ieNAYnjxsi0LbQp1ulIKZV1LAFgK1tWhpZgl8=
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
github.com/mattn/go-sqlite3 v1.14.8/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
github.com/mattn/go-sqlite3 v1.14.19 h1:fhGleo2h1p8tVChob4I9HpmVFIAkKGpiukdrgQbWfGI=
github.com/mattn/go-sqlite3 v1.14.19/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
github.com/nats-io/nats.go v1.44.0 h1:ECKVrDLdh/kDPV1g0gAQ+2+m2KprqZK5O/eJAyAnH2M=
github.com/nats-io/nats.go v1.44.0/go.mod h1:iRWIPokVIFbVijxuMQq4y9ttaBTMe0SFdlZfMDd+33g=
github.com/nats-io/nkeys v0.4.11 h1:q44qGV008kYd9W1b1nEBkNzvnWxtRSQ7A8BoqRrcfa0=
github.com/nats-io/nkeys v0.4.11/go.mod h1:szDimtgmfOi9n25JpfIdGw12tZFYXqhGxjhVxsatHVE=
github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw=
github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
github.com/ncruces/go-sqlite3 v0.30.1 h1:pHC3YsyRdJv4pCMB4MO1Q2BXw/CAa+Hoj7GSaKtVk+g=
github.com/ncruces/go-sqlite3 v0.30.1/go.mod h1:UVsWrQaq1qkcal5/vT5lOJnZCVlR5rsThKdwidjFsKc=
github.com/ncruces/go-sqlite3/litestream/modernc v0.0.0-20251109124432-99b097de3b79 h1:evpQceUV2vRbOe84U/QhBBchfqFERRHTx1JOadFFMLE=
github.com/ncruces/go-sqlite3/litestream/modernc v0.0.0-20251109124432-99b097de3b79/go.mod h1:GSM2gXEOb9HIFFtsl0IUtnpvpDmVi7Kbp8z5GzwA0Tw=
github.com/ncruces/julianday v1.0.0 h1:fH0OKwa7NWvniGQtxdJRxAgkBMolni2BjDHaWTxqt7M=
github.com/ncruces/julianday v1.0.0/go.mod h1:Dusn2KvZrrovOMJuOt0TNXL6tB7U2E8kvza5fFc9G7g=
github.com/ncruces/wbt v0.2.0 h1:Q9zlKOBSZc7Yy/R2cGa35g6RKUUE3BjNIW3tfGC4F04=
github.com/ncruces/wbt v0.2.0/go.mod h1:DtF92amvMxH69EmBFUSFWRDAlo6hOEfoNQnClxj9C/c=
github.com/pierrec/lz4/v4 v4.1.22 h1:cKFw6uJDK+/gfw5BcDL0JL5aBsAFdsIT18eRtLj7VIU=
github.com/pierrec/lz4/v4 v4.1.22/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ=
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU=
github.com/pkg/sftp v1.13.6 h1:JFZT4XbOU7l77xGSpOdW+pwIMqP044IyjXX6FGyEKFo=
github.com/pkg/sftp v1.13.6/go.mod h1:tz1ryNURKu77RL+GuCzmoJYxQczL3wLNNpPWagdg4Qk=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o=
github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg=
github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
github.com/prometheus/common v0.67.3 h1:shd26MlnwTw5jksTDhC7rTQIteBxy+ZZDr3t7F2xN2Q=
github.com/prometheus/common v0.67.3/go.mod h1:gP0fq6YjjNCLssJCQp0yk4M8W6ikLURwkdd/YKtTbyI=
github.com/prometheus/procfs v0.19.2 h1:zUMhqEW66Ex7OXIiDkll3tl9a1ZdilUOd/F6ZXw4Vws=
github.com/prometheus/procfs v0.19.2/go.mod h1:M0aotyiemPhBCM0z5w87kL22CxfcH05ZpYlu+b4J7mw=
github.com/psanford/sqlite3vfs v0.0.0-20240315230605-24e1d98cf361 h1:vAKifIJuYY306ZJSrwDgKonWcJGELijdaenABqbV03E=
github.com/psanford/sqlite3vfs v0.0.0-20240315230605-24e1d98cf361/go.mod h1:iW4cSew5PAb1sMZiTEkVJAIBNrepaB6jTYjeP47WtI0=
github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/superfly/ltx v0.5.0 h1:dXNrcT3ZtMb6iKZopIV7z5UBscnapg0b0F02loQsk5o=
github.com/superfly/ltx v0.5.0/go.mod h1:Nf50QAIXU/ET4ua3AuQ2fh31MbgNQZA7r/DYx6Os77s=
github.com/tetratelabs/wazero v1.10.1 h1:2DugeJf6VVk58KTPszlNfeeN8AhhpwcZqkJj2wwFuH8=
github.com/tetratelabs/wazero v1.10.1/go.mod h1:DRm5twOQ5Gr1AoEdSi0CLjDQF1J9ZAuyqFIjl1KKfQU=
go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.46.1 h1:SpGay3w+nEwMpfVnbqOLH5gY52/foP8RE8UzTZ1pdSE=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.46.1/go.mod h1:4UoMYEZOC0yN/sPGH76KPkkU7zgiEWYWL9vwmbnTJPE=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.46.1 h1:aFJWCqJMNjENlcleuuOkGAPH82y0yULBScfXcIEdS24=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.46.1/go.mod h1:sEGXWArGqc3tVa+ekntsN65DmVbVeW+7lTKTjZF3/Fo=
go.opentelemetry.io/otel v1.21.0 h1:hzLeKBZEL7Okw2mGzZ0cc4k/A7Fta0uoPgaJCr8fsFc=
go.opentelemetry.io/otel v1.21.0/go.mod h1:QZzNPQPm1zLX4gZK4cMi+71eaorMSGT3A4znnUvNNEo=
go.opentelemetry.io/otel/metric v1.21.0 h1:tlYWfeo+Bocx5kLEloTjbcDwBuELRrIFxwdQ36PlJu4=
go.opentelemetry.io/otel/metric v1.21.0/go.mod h1:o1p3CA8nNHW8j5yuQLdc1eeqEaPfzug24uvsyIEJRWM=
go.opentelemetry.io/otel/trace v1.21.0 h1:WD9i5gzvoUPuXIXH24ZNBudiarZDKuekPqi/E8fpfLc=
go.opentelemetry.io/otel/trace v1.21.0/go.mod h1:LGbsEB0f9LGjN+OZaQQ26sohbOmiMR+BaslueVtS/qQ=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0=
go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8=
golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q=
golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4=
golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY=
golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU=
golang.org/x/oauth2 v0.32.0 h1:jsCblLleRMDrxMN29H3z/k1KliIvpLgCkE6R8FXXNgY=
golang.org/x/oauth2 v0.32.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=
golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug=
golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM=
golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM=
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/api v0.154.0 h1:X7QkVKZBskztmpPKWQXgjJRPA2dJYrL6r+sYPRLj050=
google.golang.org/api v0.154.0/go.mod h1:qhSMkM85hgqiokIYsrRyKxrjfBeIhgl4Z2JmeRkYylc=
google.golang.org/genproto v0.0.0-20231212172506-995d672761c0 h1:YJ5pD9rF8o9Qtta0Cmy9rdBwkSjrTCT6XTiUQVOtIos=
google.golang.org/genproto v0.0.0-20231212172506-995d672761c0/go.mod h1:l/k7rMz0vFTBPy+tFSGvXEd3z+BcoG1k7EHbqm+YBsY=
google.golang.org/genproto/googleapis/api v0.0.0-20231212172506-995d672761c0 h1:s1w3X6gQxwrLEpxnLd/qXTVLgQE2yXwaOaoa6IlY/+o=
google.golang.org/genproto/googleapis/api v0.0.0-20231212172506-995d672761c0/go.mod h1:CAny0tYF+0/9rmDB9fahA9YLzX3+AEVl1qXbv5hhj6c=
google.golang.org/genproto/googleapis/rpc v0.0.0-20231212172506-995d672761c0 h1:/jFB8jK5R3Sq3i/lmeZO0cATSzFfZaJq1J2Euan3XKU=
google.golang.org/genproto/googleapis/rpc v0.0.0-20231212172506-995d672761c0/go.mod h1:FUoWkonphQm3RhTS+kOEhF8h0iDpm4tdXolVCeZ9KKA=
google.golang.org/grpc v1.60.1 h1:26+wFr+cNqSGFcOXcabYC0lUVJVRa2Sb2ortSK7VrEU=
google.golang.org/grpc v1.60.1/go.mod h1:OlCHIeLYqSSsLi6i49B5QGdzaMZK9+M7LXN2FKz4eGM=
google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE=
google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+11
View File
@@ -0,0 +1,11 @@
module modernc.org/sqlite
go 1.24.0
require github.com/ncruces/go-sqlite3 v0.30.1
require (
github.com/ncruces/julianday v1.0.0 // indirect
github.com/tetratelabs/wazero v1.10.1 // indirect
golang.org/x/sys v0.38.0 // indirect
)
+10
View File
@@ -0,0 +1,10 @@
github.com/ncruces/go-sqlite3 v0.30.1 h1:pHC3YsyRdJv4pCMB4MO1Q2BXw/CAa+Hoj7GSaKtVk+g=
github.com/ncruces/go-sqlite3 v0.30.1/go.mod h1:UVsWrQaq1qkcal5/vT5lOJnZCVlR5rsThKdwidjFsKc=
github.com/ncruces/julianday v1.0.0 h1:fH0OKwa7NWvniGQtxdJRxAgkBMolni2BjDHaWTxqt7M=
github.com/ncruces/julianday v1.0.0/go.mod h1:Dusn2KvZrrovOMJuOt0TNXL6tB7U2E8kvza5fFc9G7g=
github.com/tetratelabs/wazero v1.10.1 h1:2DugeJf6VVk58KTPszlNfeeN8AhhpwcZqkJj2wwFuH8=
github.com/tetratelabs/wazero v1.10.1/go.mod h1:DRm5twOQ5Gr1AoEdSi0CLjDQF1J9ZAuyqFIjl1KKfQU=
golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k=
golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM=
+20
View File
@@ -0,0 +1,20 @@
// Package sqlite provides a shim that allows Litestream to work with the ncruces SQLite driver.
package sqlite
import (
"database/sql"
"slices"
"github.com/ncruces/go-sqlite3/driver"
_ "github.com/ncruces/go-sqlite3/embed"
)
func init() {
if !slices.Contains(sql.Drivers(), "sqlite") {
sql.Register("sqlite", &driver.SQLite{})
}
}
type FileControl interface {
FileControlPersistWAL(string, int) (int, error)
}
+309
View File
@@ -0,0 +1,309 @@
package litestream
import (
"context"
"encoding/binary"
"errors"
"fmt"
"io"
"sync"
"time"
"github.com/benbjohnson/litestream"
"github.com/superfly/ltx"
"github.com/ncruces/go-sqlite3"
"github.com/ncruces/go-sqlite3/util/vfsutil"
"github.com/ncruces/go-sqlite3/vfs"
"github.com/ncruces/wbt"
)
type liteVFS struct{}
func (liteVFS) Open(name string, flags vfs.OpenFlag) (vfs.File, vfs.OpenFlag, error) {
// Temp journals, as used by the sorter, use SliceFile.
if flags&vfs.OPEN_TEMP_JOURNAL != 0 {
return &vfsutil.SliceFile{}, flags | vfs.OPEN_MEMORY, nil
}
// Refuse to open all other file types.
if flags&vfs.OPEN_MAIN_DB == 0 {
return nil, flags, sqlite3.CANTOPEN
}
liteMtx.RLock()
defer liteMtx.RUnlock()
if db, ok := liteDBs[name]; ok {
// Build the page index so we can lookup individual pages.
if err := db.buildIndex(context.Background()); err != nil {
db.opts.Logger.Error("build index", "error", err)
return nil, 0, err
}
return &liteFile{db: db}, flags | vfs.OPEN_READONLY, nil
}
return nil, flags, sqlite3.CANTOPEN
}
func (liteVFS) Delete(name string, dirSync bool) error {
// notest // used to delete journals
return sqlite3.IOERR_DELETE_NOENT
}
func (liteVFS) Access(name string, flag vfs.AccessFlag) (bool, error) {
// notest // used to check for journals
return false, nil
}
func (liteVFS) FullPathname(name string) (string, error) {
return name, nil
}
type liteFile struct {
db *liteDB
conn *sqlite3.Conn
pages *pageIndex
txid ltx.TXID
pageSize uint32
}
func (f *liteFile) Close() error { return nil }
func (f *liteFile) ReadAt(p []byte, off int64) (n int, err error) {
ctx := f.context()
pages, txid := f.pages, f.txid
if pages == nil {
pages, txid, err = f.db.pollReplica(ctx)
}
if err != nil {
return 0, err
}
pgno := uint32(1)
if off >= 512 {
pgno += uint32(off / int64(f.pageSize))
}
elem, ok := pages.Get(pgno)
if !ok {
return 0, io.EOF
}
data, err := f.db.cache.getOrFetch(pgno, elem.MaxTXID, func() (any, error) {
_, data, err := litestream.FetchPage(ctx, f.db.client, elem.Level, elem.MinTXID, elem.MaxTXID, elem.Offset, elem.Size)
return data, err
})
if err != nil {
f.db.opts.Logger.Error("fetch page", "error", err)
return 0, err
}
// Update the first page to pretend we are in journal mode,
// load the page size and track changes to the database.
if pgno == 1 && len(data) >= 100 &&
data[18] >= 1 && data[19] >= 1 &&
data[18] <= 3 && data[19] <= 3 {
data[18], data[19] = 0x01, 0x01
binary.BigEndian.PutUint32(data[24:28], uint32(txid))
f.pageSize = uint32(256 * binary.LittleEndian.Uint16(data[16:18]))
}
n = copy(p, data[off%int64(len(data)):])
return n, nil
}
func (f *liteFile) WriteAt(b []byte, off int64) (n int, err error) {
// notest // OPEN_READONLY
return 0, sqlite3.IOERR_WRITE
}
func (f *liteFile) Truncate(size int64) error {
// notest // OPEN_READONLY
return sqlite3.IOERR_TRUNCATE
}
func (f *liteFile) Sync(flag vfs.SyncFlag) error {
// notest // OPEN_READONLY
return sqlite3.IOERR_FSYNC
}
func (f *liteFile) Size() (size int64, err error) {
if max := f.pages.Max(); max != nil {
size = int64(max.Key()) * int64(f.pageSize)
}
return
}
func (f *liteFile) Lock(lock vfs.LockLevel) (err error) {
if lock >= vfs.LOCK_RESERVED {
return sqlite3.IOERR_LOCK
}
f.pages, f.txid, err = f.db.pollReplica(f.context())
return err
}
func (f *liteFile) Unlock(lock vfs.LockLevel) error {
f.pages, f.txid = nil, 0
return nil
}
func (f *liteFile) CheckReservedLock() (bool, error) {
// notest // used to check for hot journals
return false, nil
}
func (f *liteFile) SectorSize() int {
// notest // safe default
return 0
}
func (f *liteFile) DeviceCharacteristics() vfs.DeviceCharacteristic {
// notest // safe default
return 0
}
func (f *liteFile) SetDB(conn any) {
f.conn = conn.(*sqlite3.Conn)
}
func (f *liteFile) context() context.Context {
if f.conn != nil {
return f.conn.GetInterrupt()
}
return context.Background()
}
type liteDB struct {
client litestream.ReplicaClient
opts ReplicaOptions
cache pageCache
pages *pageIndex // +checklocks:mtx
lastPoll time.Time // +checklocks:mtx
txids levelTXIDs // +checklocks:mtx
mtx sync.Mutex
}
func (f *liteDB) buildIndex(ctx context.Context) error {
f.mtx.Lock()
defer f.mtx.Unlock()
// Skip if we already have an index.
if f.pages != nil {
return nil
}
// Build the index from scratch from a Litestream restore plan.
infos, err := litestream.CalcRestorePlan(ctx, f.client, 0, time.Time{}, f.opts.Logger)
if err != nil {
if !errors.Is(err, litestream.ErrTxNotAvailable) {
return fmt.Errorf("calc restore plan: %w", err)
}
return nil
}
for _, info := range infos {
err := f.updateInfo(ctx, info)
if err != nil {
return err
}
}
f.lastPoll = time.Now()
return nil
}
func (f *liteDB) pollReplica(ctx context.Context) (*pageIndex, ltx.TXID, error) {
f.mtx.Lock()
defer f.mtx.Unlock()
// Limit polling interval.
if time.Since(f.lastPoll) < f.opts.PollInterval {
return f.pages, f.txids[0], nil
}
for level := range pollLevels(f.opts.MinLevel) {
if err := f.updateLevel(ctx, level); err != nil {
f.opts.Logger.Error("cannot poll replica", "error", err)
return nil, 0, err
}
}
f.lastPoll = time.Now()
return f.pages, f.txids[0], nil
}
// +checklocks:f.mtx
func (f *liteDB) updateLevel(ctx context.Context, level int) error {
var nextTXID ltx.TXID
// Snapshots must start from scratch,
// other levels can start from where they were left.
if level != litestream.SnapshotLevel {
nextTXID = f.txids[level] + 1
}
// Start reading from the next LTX file after the current position.
itr, err := f.client.LTXFiles(ctx, level, nextTXID, false)
if err != nil {
return fmt.Errorf("ltx files: %w", err)
}
defer itr.Close()
// Build an update across all new LTX files.
for itr.Next() {
info := itr.Item()
// Skip LTX files already fully loaded into the index.
if info.MaxTXID <= f.txids[level] {
continue
}
err := f.updateInfo(ctx, info)
if err != nil {
return err
}
}
if err := itr.Err(); err != nil {
return err
}
return itr.Close()
}
// +checklocks:f.mtx
func (f *liteDB) updateInfo(ctx context.Context, info *ltx.FileInfo) error {
idx, err := litestream.FetchPageIndex(ctx, f.client, info)
if err != nil {
return fmt.Errorf("fetch page index: %w", err)
}
// Replace pages in the index with new pages.
for k, v := range idx {
// Patch avoids mutating the index for an unmodified page.
f.pages = f.pages.Patch(k, func(node *pageIndex) (ltx.PageIndexElem, bool) {
return v, node == nil || v != node.Value()
})
}
// Track the MaxTXID for each level.
maxTXID := &f.txids[info.Level]
*maxTXID = max(*maxTXID, info.MaxTXID)
return nil
}
func pollLevels(minLevel int) (r []int) {
// Updating from lower to upper levels is non-racy,
// since LTX files are compacted into higher levels
// before the lower level LTX files are deleted.
// Also, only level 0 compactions and snapshots delete files,
// so the intermediate levels never need to be updated.
if minLevel <= 0 {
return append(r, 0, 1, litestream.SnapshotLevel)
}
if minLevel >= litestream.SnapshotLevel {
return append(r, litestream.SnapshotLevel)
}
return append(r, minLevel, litestream.SnapshotLevel)
}
// Type aliases; these are a mouthful.
type pageIndex = wbt.Tree[uint32, ltx.PageIndexElem]
type levelTXIDs = [litestream.SnapshotLevel + 1]ltx.TXID
+34
View File
@@ -0,0 +1,34 @@
package litestream
import (
"slices"
"strconv"
"testing"
"github.com/benbjohnson/litestream"
_ "github.com/ncruces/go-sqlite3/embed"
)
func Test_pollLevels(t *testing.T) {
tests := []struct {
minLevel int
want []int
}{
{minLevel: -1, want: []int{0, 1, litestream.SnapshotLevel}},
{minLevel: 0, want: []int{0, 1, litestream.SnapshotLevel}},
{minLevel: 1, want: []int{1, litestream.SnapshotLevel}},
{minLevel: 2, want: []int{2, litestream.SnapshotLevel}},
{minLevel: 3, want: []int{3, litestream.SnapshotLevel}},
{minLevel: litestream.SnapshotLevel, want: []int{litestream.SnapshotLevel}},
{minLevel: litestream.SnapshotLevel + 1, want: []int{litestream.SnapshotLevel}},
}
for _, tt := range tests {
t.Run(strconv.Itoa(tt.minLevel), func(t *testing.T) {
got := pollLevels(tt.minLevel)
if !slices.Equal(got, tt.want) {
t.Errorf("pollLevels() = %v, want %v", got, tt.want)
}
})
}
}
+1 -1
View File
@@ -14,8 +14,8 @@ var (
// https://sqlite.org/c3ref/auto_extension.html
func AutoExtension(entryPoint func(*Conn) error) {
extRegistryMtx.Lock()
defer extRegistryMtx.Unlock()
extRegistry = append(extRegistry, entryPoint)
extRegistryMtx.Unlock()
}
func initExtensions(c *Conn) error {
+3 -23
View File
@@ -3,7 +3,6 @@ package sqlite3
import (
"context"
"math"
"math/bits"
"os"
"runtime"
@@ -269,10 +268,11 @@ func (a *arena) mark() (reset func()) {
ptrs := len(a.ptrs)
next := a.next
return func() {
for _, ptr := range a.ptrs[ptrs:] {
rest := a.ptrs[ptrs:]
for _, ptr := range a.ptrs[:ptrs] {
a.sqlt.free(ptr)
}
a.ptrs = a.ptrs[:ptrs]
a.ptrs = rest
a.next = next
}
}
@@ -351,25 +351,5 @@ func exportCallbacks(env wazero.HostModuleBuilder) wazero.HostModuleBuilder {
util.ExportFuncII(env, "go_cur_eof", cursorEOFCallback)
util.ExportFuncIIII(env, "go_cur_column", cursorColumnCallback)
util.ExportFuncIII(env, "go_cur_rowid", cursorRowIDCallback)
// Math functions.
util.ExportFuncDD(env, "exp", math.Exp)
util.ExportFuncDD(env, "log", math.Log)
util.ExportFuncDD(env, "log2", math.Log2)
util.ExportFuncDD(env, "log10", math.Log10)
util.ExportFuncDD(env, "cos", math.Cos)
util.ExportFuncDD(env, "cosh", math.Cosh)
util.ExportFuncDD(env, "sin", math.Sin)
util.ExportFuncDD(env, "sinh", math.Sinh)
util.ExportFuncDD(env, "tan", math.Tan)
util.ExportFuncDD(env, "tanh", math.Tanh)
util.ExportFuncDD(env, "acos", math.Acos)
util.ExportFuncDD(env, "acosh", math.Acosh)
util.ExportFuncDD(env, "asin", math.Asin)
util.ExportFuncDD(env, "asinh", math.Asinh)
util.ExportFuncDD(env, "atan", math.Atan)
util.ExportFuncDD(env, "atanh", math.Atanh)
util.ExportFuncDDD(env, "atan2", math.Atan2)
util.ExportFuncDDD(env, "fmod", math.Mod)
util.ExportFuncDDD(env, "pow", math.Pow)
return env
}
+1 -1
View File
@@ -2,7 +2,7 @@
# handle, and interrupt, sqlite3_busy_timeout.
--- sqlite3.c.orig
+++ sqlite3.c
@@ -188066,7 +188066,7 @@
@@ -186667,7 +186667,7 @@
if( !sqlite3SafetyCheckOk(db) ) return SQLITE_MISUSE_BKPT;
#endif
if( ms>0 ){
+6 -3
View File
@@ -3,11 +3,11 @@ set -euo pipefail
cd -P -- "$(dirname -- "$0")"
curl -#OL "https://sqlite.org/2026/sqlite-autoconf-3510300.tar.gz"
curl -#OL "https://sqlite.org/2025/sqlite-autoconf-3510000.tar.gz"
# Verify download.
if hash=$(openssl dgst -sha3-256 sqlite-autoconf-*.tar.gz); then
if ! [[ $hash =~ 581215771b32ea4c4062e6fb9842c4aa43d0a7fb2b6670ff6fa4ebb807781204 ]]; then
if ! [[ $hash =~ fa52f9cc74dbca004aa650ae698036a3350611f672649e165078f4eae21d6a2e ]]; then
echo $hash
exit 1
fi
@@ -15,12 +15,15 @@ fi 2> /dev/null
tar xzf sqlite-autoconf-*.tar.gz
# To test a snapshot instead:
# curl -# https://sqlite.org/snapshot/sqlite-snapshot-202410081727.tar.gz | tar xz
mv sqlite-*/sqlite3.c .
mv sqlite-*/sqlite3.h .
mv sqlite-*/sqlite3ext.h .
rm -r sqlite-*
GITHUB_TAG="https://github.com/sqlite/sqlite/raw/version-3.51.3"
GITHUB_TAG="https://github.com/sqlite/sqlite/raw/version-3.51.0"
mkdir -p ext/
cd ext/
+1 -4
View File
@@ -3,8 +3,5 @@ set -euo pipefail
cd -P -- "$(dirname -- "$0")"
ROOT=../
WASI_SDK="$ROOT/tools/wasi-sdk/bin"
shopt -s extglob
"$WASI_SDK/clang-format" --style=Google -i !(sqlite3*).@(c|h)
clang-format -i !(sqlite3*).@(c|h)
+22 -22
View File
@@ -4,22 +4,22 @@
#include "include.h"
#include "sqlite3.h"
void go_collation_needed(void*, sqlite3*, int, const char*);
void go_collation_needed(void *, sqlite3 *, int, const char *);
int go_compare(go_handle, int, const void*, int, const void*);
int go_compare(go_handle, int, const void *, int, const void *);
void go_func(sqlite3_context*, go_handle, int, sqlite3_value**);
void go_func(sqlite3_context *, go_handle, int, sqlite3_value **);
void go_step(sqlite3_context*, go_handle*, go_handle, int, sqlite3_value**);
void go_value(sqlite3_context*, go_handle*, go_handle, bool);
void go_inverse(sqlite3_context*, go_handle, int, sqlite3_value**);
void go_step(sqlite3_context *, go_handle *, go_handle, int, sqlite3_value **);
void go_value(sqlite3_context *, go_handle *, go_handle, bool);
void go_inverse(sqlite3_context *, go_handle, int, sqlite3_value **);
void go_func_wrapper(sqlite3_context* ctx, int nArg, sqlite3_value** pArg) {
void go_func_wrapper(sqlite3_context *ctx, int nArg, sqlite3_value **pArg) {
go_func(ctx, sqlite3_user_data(ctx), nArg, pArg);
}
void go_step_wrapper(sqlite3_context* ctx, int nArg, sqlite3_value** pArg) {
go_handle* agg = sqlite3_aggregate_context(ctx, 4);
void go_step_wrapper(sqlite3_context *ctx, int nArg, sqlite3_value **pArg) {
go_handle *agg = sqlite3_aggregate_context(ctx, 4);
go_handle data = NULL;
if (agg == NULL || *agg == NULL) {
data = sqlite3_user_data(ctx);
@@ -27,8 +27,8 @@ void go_step_wrapper(sqlite3_context* ctx, int nArg, sqlite3_value** pArg) {
go_step(ctx, agg, data, nArg, pArg);
}
void go_value_wrapper(sqlite3_context* ctx) {
go_handle* agg = sqlite3_aggregate_context(ctx, 4);
void go_value_wrapper(sqlite3_context *ctx) {
go_handle *agg = sqlite3_aggregate_context(ctx, 4);
go_handle data = NULL;
if (agg == NULL || *agg == NULL) {
data = sqlite3_user_data(ctx);
@@ -36,8 +36,8 @@ void go_value_wrapper(sqlite3_context* ctx) {
go_value(ctx, agg, data, /*final=*/false);
}
void go_final_wrapper(sqlite3_context* ctx) {
go_handle* agg = sqlite3_aggregate_context(ctx, 0);
void go_final_wrapper(sqlite3_context *ctx) {
go_handle *agg = sqlite3_aggregate_context(ctx, 0);
go_handle data = NULL;
if (agg == NULL || *agg == NULL) {
data = sqlite3_user_data(ctx);
@@ -45,17 +45,17 @@ void go_final_wrapper(sqlite3_context* ctx) {
go_value(ctx, agg, data, /*final=*/true);
}
void go_inverse_wrapper(sqlite3_context* ctx, int nArg, sqlite3_value** pArg) {
go_handle* agg = sqlite3_aggregate_context(ctx, 0);
void go_inverse_wrapper(sqlite3_context *ctx, int nArg, sqlite3_value **pArg) {
go_handle *agg = sqlite3_aggregate_context(ctx, 0);
go_inverse(ctx, *agg, nArg, pArg);
}
int sqlite3_collation_needed_go(sqlite3* db, bool enable) {
int sqlite3_collation_needed_go(sqlite3 *db, bool enable) {
return sqlite3_collation_needed(db, /*arg=*/NULL,
enable ? go_collation_needed : NULL);
}
int sqlite3_create_collation_go(sqlite3* db, const char* name, go_handle app) {
int sqlite3_create_collation_go(sqlite3 *db, const char *name, go_handle app) {
if (app == NULL) {
return sqlite3_create_collation_v2(db, name, SQLITE_UTF8, NULL, NULL, NULL);
}
@@ -65,7 +65,7 @@ int sqlite3_create_collation_go(sqlite3* db, const char* name, go_handle app) {
return rc;
}
int sqlite3_create_function_go(sqlite3* db, const char* name, int argc,
int sqlite3_create_function_go(sqlite3 *db, const char *name, int argc,
int flags, go_handle app) {
if (app == NULL) {
return sqlite3_create_function_v2(db, name, argc, SQLITE_UTF8 | flags, NULL,
@@ -76,7 +76,7 @@ int sqlite3_create_function_go(sqlite3* db, const char* name, int argc,
/*final=*/NULL, go_destroy);
}
int sqlite3_create_aggregate_function_go(sqlite3* db, const char* name,
int sqlite3_create_aggregate_function_go(sqlite3 *db, const char *name,
int argc, int flags, go_handle app) {
if (app == NULL) {
return sqlite3_create_function_v2(db, name, argc, SQLITE_UTF8 | flags, NULL,
@@ -87,7 +87,7 @@ int sqlite3_create_aggregate_function_go(sqlite3* db, const char* name,
go_final_wrapper, go_destroy);
}
int sqlite3_create_window_function_go(sqlite3* db, const char* name, int argc,
int sqlite3_create_window_function_go(sqlite3 *db, const char *name, int argc,
int flags, go_handle app) {
if (app == NULL) {
return sqlite3_create_window_function(db, name, argc, SQLITE_UTF8 | flags,
@@ -98,6 +98,6 @@ int sqlite3_create_window_function_go(sqlite3* db, const char* name, int argc,
go_final_wrapper, go_value_wrapper, go_inverse_wrapper, go_destroy);
}
void sqlite3_set_auxdata_go(sqlite3_context* ctx, int i, go_handle aux) {
void sqlite3_set_auxdata_go(sqlite3_context *ctx, int i, go_handle aux) {
sqlite3_set_auxdata(ctx, i, aux, go_destroy);
}
}
+25 -25
View File
@@ -2,56 +2,56 @@
#include "sqlite3.h"
int go_progress_handler(void*);
int go_busy_handler(void*, int);
int go_progress_handler(void *);
int go_busy_handler(void *, int);
int go_busy_timeout(int count, int tmout);
int go_commit_hook(void*);
void go_rollback_hook(void*);
void go_update_hook(void*, int, char const*, char const*, sqlite3_int64);
int go_wal_hook(void*, sqlite3*, const char*, int);
int go_trace(unsigned, void*, void*, void*);
int go_authorizer(void*, int, const char*, const char*, const char*,
const char*);
int go_commit_hook(void *);
void go_rollback_hook(void *);
void go_update_hook(void *, int, char const *, char const *, sqlite3_int64);
int go_wal_hook(void *, sqlite3 *, const char *, int);
int go_trace(unsigned, void *, void *, void *);
int go_authorizer(void *, int, const char *, const char *, const char *,
const char *);
void go_log(void*, int, const char*);
void go_log(void *, int, const char *);
unsigned int go_autovacuum_pages(void*, const char*, unsigned int, unsigned int,
unsigned int);
unsigned int go_autovacuum_pages(void *, const char *, unsigned int,
unsigned int, unsigned int);
void sqlite3_log_go(int iErrCode, const char* zMsg) {
void sqlite3_log_go(int iErrCode, const char *zMsg) {
sqlite3_log(iErrCode, "%s", zMsg);
}
void sqlite3_progress_handler_go(sqlite3* db, int n) {
void sqlite3_progress_handler_go(sqlite3 *db, int n) {
sqlite3_progress_handler(db, n, go_progress_handler, /*arg=*/NULL);
}
int sqlite3_busy_handler_go(sqlite3* db, bool enable) {
int sqlite3_busy_handler_go(sqlite3 *db, bool enable) {
return sqlite3_busy_handler(db, enable ? go_busy_handler : NULL, /*arg=*/db);
}
void sqlite3_commit_hook_go(sqlite3* db, bool enable) {
void sqlite3_commit_hook_go(sqlite3 *db, bool enable) {
sqlite3_commit_hook(db, enable ? go_commit_hook : NULL, /*arg=*/db);
}
void sqlite3_rollback_hook_go(sqlite3* db, bool enable) {
void sqlite3_rollback_hook_go(sqlite3 *db, bool enable) {
sqlite3_rollback_hook(db, enable ? go_rollback_hook : NULL, /*arg=*/db);
}
void sqlite3_update_hook_go(sqlite3* db, bool enable) {
void sqlite3_update_hook_go(sqlite3 *db, bool enable) {
sqlite3_update_hook(db, enable ? go_update_hook : NULL, /*arg=*/db);
}
void sqlite3_wal_hook_go(sqlite3* db, bool enable) {
void sqlite3_wal_hook_go(sqlite3 *db, bool enable) {
sqlite3_wal_hook(db, enable ? go_wal_hook : NULL, /*arg=*/NULL);
}
int sqlite3_set_authorizer_go(sqlite3* db, bool enable) {
int sqlite3_set_authorizer_go(sqlite3 *db, bool enable) {
return sqlite3_set_authorizer(db, enable ? go_authorizer : NULL, /*arg=*/db);
}
int sqlite3_trace_go(sqlite3* db, unsigned mask) {
int sqlite3_trace_go(sqlite3 *db, unsigned mask) {
return sqlite3_trace_v2(db, mask, go_trace, /*arg=*/db);
}
@@ -60,7 +60,7 @@ int sqlite3_config_log_go(bool enable) {
/*arg=*/NULL);
}
int sqlite3_autovacuum_pages_go(sqlite3* db, go_handle app) {
int sqlite3_autovacuum_pages_go(sqlite3 *db, go_handle app) {
if (app == NULL) {
return sqlite3_autovacuum_pages(db, NULL, NULL, NULL);
}
@@ -69,8 +69,8 @@ int sqlite3_autovacuum_pages_go(sqlite3* db, go_handle app) {
#ifndef sqliteBusyCallback
static int sqliteBusyCallback(void* ptr, int count) {
return go_busy_timeout(count, ((sqlite3*)ptr)->busyTimeout);
static int sqliteBusyCallback(void *ptr, int count) {
return go_busy_timeout(count, ((sqlite3 *)ptr)->busyTimeout);
}
#endif
#endif
+3 -3
View File
@@ -5,8 +5,8 @@
// https://github.com/JuliaLang/julia/blob/v1.9.4/src/julia.h#L67-L68
#define container_of(ptr, type, member) \
((type*)((char*)(ptr) - offsetof(type, member)))
((type *)((char *)(ptr) - offsetof(type, member)))
typedef void* go_handle;
typedef void *go_handle;
void go_destroy(go_handle);
static_assert(sizeof(go_handle) == 4, "Unexpected size");
static_assert(sizeof(go_handle) == 4, "Unexpected size");
-7
View File
@@ -1,7 +0,0 @@
# SQLite Libc
This is a minimal libc that offers just enough
to compile SQLite for wasm32 with nostdlib.
The allocator is either Doug Lea's malloc or a bump allocator,
and math is provided by the host side.
-3
View File
@@ -1,3 +0,0 @@
#ifndef assert
#define assert(ignore) ((void)0)
#endif
+12
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=.
+53
View File
@@ -0,0 +1,53 @@
#!/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 \
-mno-extended-const \
-fno-stack-protector \
-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 libc.tmp -o libc.wasm \
--low-memory-unused --generate-global-effects --converge -O3 \
--enable-mutable-globals --enable-nontrapping-float-to-int \
--enable-simd --enable-bulk-memory --enable-sign-ext \
--enable-reference-types --enable-multivalue \
--strip --strip-debug --strip-producers
"$BINARYEN/wasm-dis" -o libc.wat libc.wasm
-18
View File
@@ -1,18 +0,0 @@
#include <ctype.h>
int(isalnum)(int c) { return isalpha(c) || isdigit(c); }
int(isalpha)(int c) { return ((unsigned)c | 32) - 'a' < 26; }
int(isascii)(int c) { return !(c & ~0x7f); }
int(isblank)(int c) { return (c == ' ' || c == '\t'); }
int(iscntrl)(int c) { return (unsigned)c < 0x20 || c == 0x7f; }
int(isdigit)(int c) { return (unsigned)c - '0' < 10; }
int(isgraph)(int c) { return (unsigned)c - 0x21 < 0x5e; }
int(islower)(int c) { return (unsigned)c - 'a' < 26; }
int(isprint)(int c) { return (unsigned)c - 0x20 < 0x5f; }
int(ispunct)(int c) { return isgraph(c) && !isalnum(c); }
int(isspace)(int c) { return c == ' ' || (unsigned)c - '\t' < 5; }
int(isupper)(int c) { return (unsigned)c - 'A' < 26; }
int(isxdigit)(int c) { return isdigit(c) || ((unsigned)c | 32) - 'a' < 6; }
int(tolower)(int c) { return isupper(c) ? c | 32 : c; }
int(toupper)(int c) { return islower(c) ? c & 0x5f : c; }
-25
View File
@@ -1,25 +0,0 @@
#pragma once
int isalnum(int);
int isalpha(int);
int isascii(int);
int isblank(int);
int iscntrl(int);
int isdigit(int);
int isgraph(int);
int islower(int);
int isprint(int);
int ispunct(int);
int isspace(int);
int isupper(int);
int isxdigit(int);
int tolower(int);
int toupper(int);
#define isalpha(a) (0 ? isalpha(a) : (((unsigned)(a) | 32) - 'a') < 26)
#define isdigit(a) (0 ? isdigit(a) : ((unsigned)(a) - '0') < 10)
#define isgraph(a) (0 ? isgraph(a) : ((unsigned)(a) - 0x21) < 0x5e)
#define islower(a) (0 ? islower(a) : ((unsigned)(a) - 'a') < 26)
#define isprint(a) (0 ? isprint(a) : ((unsigned)(a) - 0x20) < 0x5f)
#define isupper(a) (0 ? isupper(a) : ((unsigned)(a) - 'A') < 26)
-6
View File
@@ -1,6 +0,0 @@
#!/usr/bin/env bash
set -euo pipefail
cd -P -- "$(dirname -- "$0")"
curl -#OL "https://gee.cs.oswego.edu/pub/misc/malloc.c"
-10
View File
@@ -1,10 +0,0 @@
#!/usr/bin/env bash
set -euo pipefail
cd -P -- "$(dirname -- "$0")"
ROOT=../../
WASI_SDK="$ROOT/tools/wasi-sdk/bin"
shopt -s extglob
"$WASI_SDK/clang-format" --style=Google -i !(malloc).@(c|h)
-4
View File
@@ -1,4 +0,0 @@
#include "ctype.c"
#include "math.c"
#include "stdlib.c"
#include "string.c"
BIN
View File
Binary file not shown.
File diff suppressed because it is too large Load Diff
+832
View File
@@ -0,0 +1,832 @@
package libc
import (
"bytes"
"context"
_ "embed"
"math"
"os"
"strings"
"testing"
"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)
for b.Loop() {
call(memset, ptr1, 3, size)
}
}
func Benchmark_memcpy(b *testing.B) {
clear(memory)
fill(memory[ptr2:ptr2+size], 5)
b.SetBytes(size)
for b.Loop() {
call(memcpy, ptr1, ptr2, size)
}
}
func Benchmark_strlen(b *testing.B) {
clear(memory)
fill(memory[ptr1:ptr1+size-1], 5)
b.SetBytes(size)
for b.Loop() {
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)
for b.Loop() {
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)
for b.Loop() {
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)
for b.Loop() {
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)
for b.Loop() {
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)
for b.Loop() {
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)
for b.Loop() {
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 := indexNotByte(s, chars)
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()
}
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 := indexAnyByte(s, chars)
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
}
func indexNotByte(s, chars string) int {
for i, c := range []byte(s) {
if strings.IndexByte(chars, c) < 0 {
return i
}
}
return len(s)
}
func indexAnyByte(s, chars string) int {
for i, c := range []byte(s) {
if strings.IndexByte(chars, c) >= 0 {
return i
}
}
return len(s)
}
File diff suppressed because it is too large Load Diff
-56
View File
@@ -1,56 +0,0 @@
// A simple bump allocator that never frees memory.
// Takes over the initial heap, then grows it as needed.
// Assumes that new memory is zero-initialized,
// and that the heap base is 16 byte aligned.
// It allocates in 16 byte chunks and keeps no size metadata.
#include <stdint.h>
#include <stdlib.h>
#define PAGESIZE 65536
extern char __heap_base[];
extern char __heap_end[];
static void* __arena_beg = __heap_base;
static void* __arena_end = __heap_end;
__attribute__((always_inline)) void free(void*) {}
void* malloc(size_t size) {
if (size == 0) return NULL;
size = __builtin_align_up(size, 16);
size_t avail = __arena_end - __arena_beg;
if (size > avail) {
size_t npages = (size - avail + PAGESIZE - 1) / PAGESIZE;
size_t old = __builtin_wasm_memory_grow(0, npages);
if (old == SIZE_MAX) return NULL;
__arena_end += npages * PAGESIZE;
}
void* res = __arena_beg;
__arena_beg += size;
return res;
}
void* calloc(size_t nelem, size_t elsize) { return malloc(nelem * elsize); }
void* aligned_alloc(size_t align, size_t size) {
// Ensure non-zero power-of-two.
if (align <= 0 || (align & (align - 1))) return NULL;
align = (align - (size_t)__arena_beg) & (align - 1);
return malloc(size + align) + align;
}
void* realloc(void* ptr, size_t size) {
if (ptr == NULL) return malloc(size);
// No need to move the first chunk.
if (size <= 16) return ptr;
size_t copy = __arena_beg - ptr;
void* res = malloc(size);
if (copy > size) copy = size;
__builtin_memcpy(res, ptr, copy);
return res;
}
-65
View File
@@ -1,65 +0,0 @@
#define PAGESIZE 65536
void* sbrk(intptr_t increment) {
if (increment == 0) return (void*)(__builtin_wasm_memory_size(0) * PAGESIZE);
if (increment < 0 || increment % PAGESIZE != 0) abort();
size_t old = __builtin_wasm_memory_grow(0, (size_t)increment / PAGESIZE);
if (old == SIZE_MAX) return (void*)old;
return (void*)(old * PAGESIZE);
}
#define LACKS_ERRNO_H
#define LACKS_FCNTL_H
#define LACKS_SCHED_H
#define LACKS_STRINGS_H
#define LACKS_SYS_MMAN_H
#define LACKS_SYS_PARAM_H
#define LACKS_SYS_TYPES_H
#define LACKS_TIME_H
#define LACKS_UNISTD_H
#define HAVE_MMAP 0
#define MALLOC_ALIGNMENT 16
#define MALLOC_FAILURE_ACTION
#define MORECORE_CANNOT_TRIM 1
#define NO_MALLINFO 1
#define NO_MALLOC_STATS 1
#define USE_BUILTIN_FFS 1
#define USE_LOCKS 0
#define ffs(i) (__builtin_ffs(i))
#define ENOMEM 7 // SQLITE_NOMEM
#define EINVAL 21 // SQLITE_MISUSE
#pragma clang diagnostic ignored "-Weverything"
#include "malloc.c"
extern char __heap_base[];
extern char __heap_end[];
// Initialize dlmalloc to be able to use the memory between
// __heap_base and __heap_end.
static void try_init_allocator(void) {
if (is_initialized(gm)) __builtin_trap();
ensure_initialization();
size_t heap_size = __heap_end - __heap_base;
if (heap_size <= MIN_CHUNK_SIZE + TOP_FOOT_SIZE + MALLOC_ALIGNMENT) return;
gm->least_addr = __heap_base;
gm->seg.base = __heap_base;
gm->seg.size = heap_size;
gm->footprint = heap_size;
gm->max_footprint = heap_size;
gm->magic = mparams.magic;
gm->release_checks = MAX_RELEASE_CHECK_RATE;
init_bins(gm);
init_top(gm, (mchunkptr)__heap_base, heap_size - TOP_FOOT_SIZE);
}
__attribute__((alias("memalign"))) void* aligned_alloc(size_t align,
size_t size);
-17
View File
@@ -1,17 +0,0 @@
#include <math.h>
__attribute__((always_inline)) double(ceil)(double x) {
return __builtin_ceil(x);
}
__attribute__((always_inline)) double(floor)(double x) {
return __builtin_floor(x);
}
__attribute__((always_inline)) double(sqrt)(double x) {
return __builtin_sqrt(x);
}
__attribute__((always_inline)) double(trunc)(double x) {
return __builtin_trunc(x);
}
+32 -34
View File
@@ -1,38 +1,36 @@
#pragma once
#include_next <math.h> // the system math.h
double acos(double);
double acosh(double);
double asin(double);
double asinh(double);
double atan(double);
double atan2(double, double);
double atanh(double);
double ceil(double);
double cos(double);
double cosh(double);
double exp(double);
double fabs(double);
double floor(double);
double fmod(double, double);
double log(double);
double log10(double);
double log2(double);
double pow(double, double);
double sin(double);
double sinh(double);
double tan(double);
double tanh(double);
double trunc(double);
double sqrt(double);
#ifndef _WASM_SIMD128_MATH_H
#define _WASM_SIMD128_MATH_H
#define ceil(x) (__builtin_ceil(x))
#define floor(x) (__builtin_floor(x))
#define sqrt(x) (__builtin_sqrt(x))
#define trunc(x) (__builtin_trunc(x))
#include <wasm_simd128.h>
#define INFINITY __builtin_inf()
#ifdef __cplusplus
extern "C" {
#endif
#define isfinite(x) (__builtin_isfinite(x))
#define isinf(x) (__builtin_isinf(x))
#define isnan(x) (__builtin_isnan(x))
#define isnormal(x) (__builtin_isnormal(x))
#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
View File
-45
View File
@@ -1,45 +0,0 @@
#include <stdlib.h>
void abort(void) { __builtin_trap(); }
// Shellsort with Gonnet & Baeza-Yates gap sequence.
// Simple, no recursion, doesn't use the C stack.
// Clang auto-vectorizes the inner loop.
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);
}
}
}
}
+53 -9
View File
@@ -1,13 +1,57 @@
#pragma once
#include_next <stdlib.h> // the system stdlib.h
#include <stddef.h>
#ifndef _WASM_SIMD128_STDLIB_H
#define _WASM_SIMD128_STDLIB_H
__attribute__((noreturn)) void abort(void);
#ifdef __cplusplus
extern "C" {
#endif
void free(void*);
__attribute__((malloc)) void* malloc(size_t);
__attribute__((malloc)) void* calloc(size_t, size_t);
__attribute__((malloc)) void* aligned_alloc(size_t, size_t);
void* realloc(void*, size_t);
// Shellsort with Gonnet & Baeza-Yates gap sequence.
// Simple, no recursion, doesn't use the C stack.
// Clang auto-vectorizes the inner loop.
void qsort(void*, size_t, size_t, int (*)(const void*, const void*));
__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
-350
View File
@@ -1,350 +0,0 @@
#ifdef __wasm_simd128__
#include <stdint.h>
#include <string.h>
#include <wasm_simd128.h>
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;
}
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 = *__builtin_launder((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);
}
}
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;
}
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 = *__builtin_launder((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);
}
}
char* strchr(const char* s, int c) {
char* r = strchrnul(s, c);
return *r == (char)c ? r : NULL;
}
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 = *__builtin_launder((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);
}
}
char* strrchr(const char* s, int c) {
return (char*)memrchr(s, c, strlen(s) + 1);
}
int strcmp(const char* l, const char* r) {
for (; *l == *r && *l; l++, r++);
return *(unsigned char*)l - *(unsigned char*)r;
}
int strncmp(const char* _l, const char* _r, size_t n) {
const unsigned char *l = (void*)_l, *r = (void*)_r;
if (!n--) return 0;
for (; *l && *r && n && *l == *r; l++, r++, n--);
return *l - *r;
}
// SIMDized check which bytes are in a set (Geoff Langdale)
// http://0x80.pl/notesen/2018-10-18-simd-byte-lookup.html
// This is the same algorithm as truffle from Hyperscan:
// https://github.com/intel/hyperscan/blob/v5.4.2/src/nfa/truffle.c#L64-L81
// https://github.com/intel/hyperscan/blob/v5.4.2/src/nfa/trufflecompile.cpp
typedef struct {
__u8x16 lo;
__u8x16 hi;
} __wasm_v128_bitmap256_t;
__attribute__((always_inline)) static void __wasm_v128_setbit(
__wasm_v128_bitmap256_t* bitmap, uint8_t i) {
uint8_t hi_nibble = i >> 4;
uint8_t lo_nibble = i & 0xf;
bitmap->lo[lo_nibble] |= (uint8_t)(1u << (hi_nibble - 0));
bitmap->hi[lo_nibble] |= (uint8_t)(1u << (hi_nibble - 8));
}
#ifndef __wasm_relaxed_simd__
#define wasm_i8x16_relaxed_swizzle wasm_i8x16_swizzle
#endif
__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_u64x2_const_splat(0x8040201008040201);
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((v128_t)bitmap.lo, indices_0_7);
v128_t row_8_15 = wasm_i8x16_swizzle((v128_t)bitmap.hi, indices_8_15);
v128_t bitsets = row_0_7 | row_8_15;
return bitsets & bitmask;
}
#undef wasm_i8x16_relaxed_swizzle
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 = *__builtin_launder((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, (uint8_t)*c);
}
for (;;) {
v128_t v = *__builtin_launder((v128_t*)addr);
v128_t found = __wasm_v128_chkbits(bitmap, v);
// Bitmask is slow on AArch64, all_true is much faster.
if (!wasm_i8x16_all_true(found)) {
v128_t cmp = wasm_i8x16_eq(found, (v128_t){});
// Clear the bits corresponding to align (little-endian)
// so we can count trailing zeros.
int mask = wasm_i8x16_bitmask(cmp) >> align << align;
// At least one bit will be set, unless 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);
}
}
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, (uint8_t)*c);
} while (*c++);
for (;;) {
v128_t v = *__builtin_launder((v128_t*)addr);
v128_t found = __wasm_v128_chkbits(bitmap, v);
// Bitmask is slow on AArch64, any_true is much faster.
if (wasm_v128_any_true(found)) {
v128_t cmp = wasm_i8x16_eq(found, (v128_t){});
// Clear the bits corresponding to align (little-endian)
// so we can count trailing zeros.
int mask = (uint16_t)~wasm_i8x16_bitmask(cmp) >> align << align;
// At least one bit will be set, unless 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);
}
}
#else
size_t strlen(const char* s) {
const char* a = s;
for (; *s; s++);
return s - a;
}
#endif
+477 -16
View File
@@ -1,20 +1,481 @@
#pragma once
#include_next <string.h> // the system string.h
#include <stddef.h>
#ifndef _WASM_SIMD128_STRING_H
#define _WASM_SIMD128_STRING_H
void* memset(void*, int, size_t);
void* memcpy(void* restrict, const void* restrict, size_t);
void* memmove(void*, const void*, size_t);
#include <stdint.h>
#include <wasm_simd128.h>
void* memchr(const void*, int, size_t);
void* memrchr(const void*, int, size_t);
int memcmp(const void*, const void*, size_t);
#ifdef __cplusplus
extern "C" {
#endif
size_t strlen(const char*);
size_t strspn(const char*, const char*);
size_t strcspn(const char*, const char*);
char* strchr(const char*, int);
char* strrchr(const char*, int);
char* strchrnul(const char*, int);
int strcmp(const char*, const char*);
int strncmp(const char*, const char*, size_t);
#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
// This is the same algorithm as truffle from Hyperscan:
// https://github.com/intel/hyperscan/blob/v5.4.2/src/nfa/truffle.c#L64-L81
// https://github.com/intel/hyperscan/blob/v5.4.2/src/nfa/trufflecompile.cpp
typedef struct {
__u8x16 lo;
__u8x16 hi;
} __wasm_v128_bitmap256_t;
__attribute__((always_inline))
static void __wasm_v128_setbit(__wasm_v128_bitmap256_t *bitmap, uint8_t i) {
uint8_t hi_nibble = i >> 4;
uint8_t lo_nibble = i & 0xf;
bitmap->lo[lo_nibble] |= (uint8_t)(1u << (hi_nibble - 0));
bitmap->hi[lo_nibble] |= (uint8_t)(1u << (hi_nibble - 8));
}
#ifndef __wasm_relaxed_simd__
#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_u64x2_const_splat(0x8040201008040201);
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((v128_t)bitmap.lo, indices_0_7);
v128_t row_8_15 = wasm_i8x16_swizzle((v128_t)bitmap.hi, indices_8_15);
v128_t bitsets = row_0_7 | row_8_15;
return bitsets & bitmask;
}
#undef wasm_i8x16_relaxed_swizzle
__attribute__((weak))
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, (uint8_t)*c);
}
for (;;) {
v128_t v = *(v128_t *)addr;
v128_t found = __wasm_v128_chkbits(bitmap, v);
// Bitmask is slow on AArch64, all_true is much faster.
if (!wasm_i8x16_all_true(found)) {
v128_t cmp = wasm_i8x16_eq(found, (v128_t){});
// Clear the bits corresponding to align (little-endian)
// so we can count trailing zeros.
int mask = wasm_i8x16_bitmask(cmp) >> align << align;
// At least one bit will be set, unless 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, (uint8_t)*c);
} while (*c++);
for (;;) {
v128_t v = *(v128_t *)addr;
v128_t found = __wasm_v128_chkbits(bitmap, v);
// Bitmask is slow on AArch64, any_true is much faster.
if (wasm_v128_any_true(found)) {
v128_t cmp = wasm_i8x16_eq(found, (v128_t){});
// Clear the bits corresponding to align (little-endian)
// so we can count trailing zeros.
int mask = (uint16_t)~wasm_i8x16_bitmask(cmp) >> align << align;
// At least one bit will be set, unless 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
-15
View File
@@ -1,15 +0,0 @@
#pragma once
typedef long long time_t;
struct tm {
int tm_sec;
int tm_min;
int tm_hour;
int tm_mday;
int tm_mon;
int tm_year;
int tm_wday;
int tm_yday;
int tm_isdst;
};
+2 -6
View File
@@ -18,12 +18,8 @@
#include "time.c"
#include "vfs.c"
#include "vtab.c"
// Libc
#include "libc.c"
#include "malloc_sbrk.c"
void _initialize() {
try_init_allocator();
__attribute__((constructor)) void init() {
sqlite3_initialize();
sqlite3_auto_extension((void (*)(void))sqlite3_base_init);
sqlite3_auto_extension((void (*)(void))sqlite3_decimal_init);
@@ -33,4 +29,4 @@ void _initialize() {
sqlite3_auto_extension((void (*)(void))sqlite3_spellfix_init);
sqlite3_auto_extension((void (*)(void))sqlite3_uint_init);
sqlite3_auto_extension((void (*)(void))sqlite3_time_init);
}
}
+4 -4
View File
@@ -4,14 +4,14 @@
#define GO_POINTER_TYPE "github.com/ncruces/go-sqlite3.Pointer"
int sqlite3_bind_pointer_go(sqlite3_stmt* stmt, int i, go_handle app) {
int sqlite3_bind_pointer_go(sqlite3_stmt *stmt, int i, go_handle app) {
return sqlite3_bind_pointer(stmt, i, app, GO_POINTER_TYPE, go_destroy);
}
void sqlite3_result_pointer_go(sqlite3_context* ctx, go_handle app) {
void sqlite3_result_pointer_go(sqlite3_context *ctx, go_handle app) {
sqlite3_result_pointer(ctx, app, GO_POINTER_TYPE, go_destroy);
}
go_handle sqlite3_value_pointer_go(sqlite3_value* val) {
go_handle sqlite3_value_pointer_go(sqlite3_value *val) {
return sqlite3_value_pointer(val, GO_POINTER_TYPE);
}
}
+9 -7
View File
@@ -1,12 +1,9 @@
#include <stddef.h>
#include <time.h>
// Platform Configuration
#define SQLITE_OS_OTHER 1
#define SQLITE_BYTEORDER 1234
#define SQLITE_MAX_PATHLEN 4096
#define SQLITE_MALLOCSIZE(x) malloc_usable_size(x)
#define HAVE_INT8_T 1
#define HAVE_INT16_T 1
@@ -19,20 +16,25 @@
#define HAVE_UINT64_T 1
#define HAVE_UINTPTR_T 1
#define HAVE_STDINT_H 1
#define HAVE_INTTYPES_H 1
#define HAVE_LOG2 1
#define HAVE_LOG10 1
#define HAVE_ISNAN 1
#define HAVE_STRCHRNUL 1
#define HAVE_USLEEP 1
#define HAVE_NANOSLEEP 1
#define HAVE_GMTIME_R 1
#define HAVE_LOCALTIME_S 1
// Implemented in malloc.c.
size_t malloc_usable_size(void*);
#define HAVE_MALLOC_H 1
#define HAVE_MALLOC_USABLE_SIZE 1
// Implemented in hooks.c.
static int sqliteBusyCallback(void*, int);
static int sqliteBusyCallback(void *, int);
// Implemented in vfs.c.
int localtime_s(struct tm* const pTm, time_t const* const pTime);
int localtime_s(struct tm *const pTm, time_t const *const pTime);
+6 -2
View File
@@ -9,6 +9,7 @@
#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
@@ -27,8 +28,11 @@
#define SQLITE_ENABLE_SETLK_TIMEOUT 2
#define SQLITE_ENABLE_STAT4 1
// We have our own memdb VFS.
// To avoid interactions between the two,
// omit sqlite3_serialize/sqlite3_deserialize,
// which we also don't wrap.
#define SQLITE_OMIT_DESERIALIZE
#define SQLITE_OMIT_LOAD_EXTENSION
// Amalgamated Extensions
@@ -39,4 +43,4 @@
#define SQLITE_ENABLE_GEOPOLY 1
#define SQLITE_SOUNDEX
#define SQLITE_UNTESTABLE
#define SQLITE_UNTESTABLE
+6 -6
View File
@@ -2,7 +2,7 @@
#include "sqlite3.h"
int sqlite3_exec_go(sqlite3_stmt* stmt) {
int sqlite3_exec_go(sqlite3_stmt *stmt) {
while (sqlite3_step(stmt) == SQLITE_ROW);
return sqlite3_reset(stmt);
}
@@ -11,19 +11,19 @@ union sqlite3_data {
sqlite3_int64 i;
double d;
struct {
const void* ptr;
const void *ptr;
int len;
};
};
int sqlite3_columns_go(sqlite3_stmt* stmt, int nCol, char* aType,
union sqlite3_data* aData) {
int sqlite3_columns_go(sqlite3_stmt *stmt, int nCol, char *aType,
union sqlite3_data *aData) {
if (nCol != sqlite3_column_count(stmt)) {
return SQLITE_MISUSE;
}
bool check = false;
for (int i = 0; i < nCol; ++i) {
const void* ptr = NULL;
const void *ptr = NULL;
switch (aType[i] = sqlite3_column_type(stmt, i)) {
default: // SQLITE_NULL
aData[i] = (union sqlite3_data){};
@@ -55,4 +55,4 @@ static_assert(offsetof(union sqlite3_data, i) == 0, "Unexpected offset");
static_assert(offsetof(union sqlite3_data, d) == 0, "Unexpected offset");
static_assert(offsetof(union sqlite3_data, ptr) == 0, "Unexpected offset");
static_assert(offsetof(union sqlite3_data, len) == 4, "Unexpected offset");
static_assert(sizeof(union sqlite3_data) == 8, "Unexpected size");
static_assert(sizeof(union sqlite3_data) == 8, "Unexpected size");
+7 -13
View File
@@ -2,28 +2,22 @@
#include "sqlite3.h"
#ifdef SQLITE_UTF8_ZT
#define ENCODING SQLITE_UTF8_ZT
#else
#define ENCODING SQLITE_UTF8
#endif
int sqlite3_bind_text_go(sqlite3_stmt* stmt, int i, const char* zData,
int sqlite3_bind_text_go(sqlite3_stmt *stmt, int i, const char *zData,
sqlite3_uint64 nData) {
return sqlite3_bind_text64(stmt, i, zData, nData, &sqlite3_free, ENCODING);
return sqlite3_bind_text64(stmt, i, zData, nData, &sqlite3_free, SQLITE_UTF8);
}
int sqlite3_bind_blob_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,
void sqlite3_result_text_go(sqlite3_context *ctx, const char *zData,
sqlite3_uint64 nData) {
sqlite3_result_text64(ctx, zData, nData, &sqlite3_free, ENCODING);
sqlite3_result_text64(ctx, zData, nData, &sqlite3_free, SQLITE_UTF8);
}
void sqlite3_result_blob_go(sqlite3_context* ctx, const void* zData,
void sqlite3_result_blob_go(sqlite3_context *ctx, const void *zData,
sqlite3_uint64 nData) {
sqlite3_result_blob64(ctx, zData, nData, &sqlite3_free);
}
}
+7 -7
View File
@@ -3,8 +3,8 @@
#include "sqlite3.h"
static int time_collation(void* pArg, int nKey1, const void* pKey1, int nKey2,
const void* pKey2) {
static int time_collation(void *pArg, int nKey1, const void *pKey1, int nKey2,
const void *pKey2) {
UNUSED_PARAMETER(pArg);
// Remove a Z suffix if one key is no longer than the other.
@@ -12,8 +12,8 @@ static int time_collation(void* pArg, int nKey1, const void* pKey1, int nKey2,
// This avoids making different keys equal.
const int nK1 = nKey1;
const int nK2 = nKey2;
const char* pK1 = (const char*)pKey1;
const char* pK2 = (const char*)pKey2;
const char *pK1 = (const char *)pKey1;
const char *pK2 = (const char *)pKey2;
if (nK1 && nK1 <= nK2 && pK1[nK1 - 1] == 'Z') {
nKey1--;
}
@@ -29,10 +29,10 @@ static int time_collation(void* pArg, int nKey1, const void* pKey1, int nKey2,
return rc;
}
int sqlite3_time_init(sqlite3* db, char** pzErrMsg,
const sqlite3_api_routines* pApi) {
int sqlite3_time_init(sqlite3 *db, char **pzErrMsg,
const sqlite3_api_routines *pApi) {
UNUSED_PARAMETER2(pzErrMsg, pApi);
sqlite3_create_collation_v2(db, "time", SQLITE_UTF8, /*arg=*/NULL,
time_collation, /*destroy=*/NULL);
return SQLITE_OK;
}
}
+6 -7
View File
@@ -26,15 +26,14 @@ elif [[ "$OSTYPE" == "darwin"* ]]; then
fi
fi
WASI_SDK="https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-32/wasi-sdk-32.0-$WASI_SDK.tar.gz"
BINARYEN="https://github.com/WebAssembly/binaryen/releases/download/version_127/binaryen-version_127-$BINARYEN.tar.gz"
WASI_SDK="https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-29/wasi-sdk-29.0-$WASI_SDK.tar.gz"
BINARYEN="https://github.com/WebAssembly/binaryen/releases/download/version_125/binaryen-version_125-$BINARYEN.tar.gz"
# Download tools
rm -rf "$ROOT/tools"
mkdir -p "$ROOT/tools"
curl -#L "$WASI_SDK" | tar xzC "$ROOT/tools" &
curl -#L "$BINARYEN" | tar xzC "$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
mv "$ROOT/tools/wasi-sdk"* "$ROOT/tools/wasi-sdk"
mv "$ROOT/tools/binaryen"* "$ROOT/tools/binaryen"
[ -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"
+39 -39
View File
@@ -5,40 +5,40 @@
#include "include.h"
#include "sqlite3.h"
int go_vfs_find(const char* zVfsName);
int go_localtime(struct tm*, sqlite3_int64);
int go_vfs_find(const char *zVfsName);
int go_localtime(struct tm *, sqlite3_int64);
int go_randomness(sqlite3_vfs*, int nByte, char* zOut);
int go_sleep(sqlite3_vfs*, int microseconds);
int go_current_time_64(sqlite3_vfs*, sqlite3_int64*);
int go_randomness(sqlite3_vfs *, int nByte, char *zOut);
int go_sleep(sqlite3_vfs *, int microseconds);
int go_current_time_64(sqlite3_vfs *, sqlite3_int64 *);
int go_open(sqlite3_vfs*, sqlite3_filename zName, sqlite3_file*, int flags,
int* pOutFlags, int* pOutVFS);
int go_delete(sqlite3_vfs*, const char* zName, int syncDir);
int go_access(sqlite3_vfs*, const char* zName, int flags, int* pResOut);
int go_full_pathname(sqlite3_vfs*, const char* zName, int nOut, char* zOut);
int go_open(sqlite3_vfs *, sqlite3_filename zName, sqlite3_file *, int flags,
int *pOutFlags, int *pOutVFS);
int go_delete(sqlite3_vfs *, const char *zName, int syncDir);
int go_access(sqlite3_vfs *, const char *zName, int flags, int *pResOut);
int go_full_pathname(sqlite3_vfs *, const char *zName, int nOut, char *zOut);
int go_close(sqlite3_file*);
int go_read(sqlite3_file*, void*, int iAmt, sqlite3_int64 iOfst);
int go_write(sqlite3_file*, const void*, int iAmt, sqlite3_int64 iOfst);
int go_truncate(sqlite3_file*, sqlite3_int64 size);
int go_sync(sqlite3_file*, int flags);
int go_file_size(sqlite3_file*, sqlite3_int64* pSize);
int go_file_control(sqlite3_file*, int op, void* pArg);
int go_sector_size(sqlite3_file* file);
int go_device_characteristics(sqlite3_file* file);
int go_close(sqlite3_file *);
int go_read(sqlite3_file *, void *, int iAmt, sqlite3_int64 iOfst);
int go_write(sqlite3_file *, const void *, int iAmt, sqlite3_int64 iOfst);
int go_truncate(sqlite3_file *, sqlite3_int64 size);
int go_sync(sqlite3_file *, int flags);
int go_file_size(sqlite3_file *, sqlite3_int64 *pSize);
int go_file_control(sqlite3_file *, int op, void *pArg);
int go_sector_size(sqlite3_file *file);
int go_device_characteristics(sqlite3_file *file);
int go_lock(sqlite3_file*, int eLock);
int go_unlock(sqlite3_file*, int eLock);
int go_check_reserved_lock(sqlite3_file*, int* pResOut);
int go_lock(sqlite3_file *, int eLock);
int go_unlock(sqlite3_file *, int eLock);
int go_check_reserved_lock(sqlite3_file *, int *pResOut);
int go_shm_map(sqlite3_file*, int iPg, int pgsz, int, void volatile**);
int go_shm_lock(sqlite3_file*, int offset, int n, int flags);
int go_shm_unmap(sqlite3_file*, int deleteFlag);
void go_shm_barrier(sqlite3_file*);
int go_shm_map(sqlite3_file *, int iPg, int pgsz, int, void volatile **);
int go_shm_lock(sqlite3_file *, int offset, int n, int flags);
int go_shm_unmap(sqlite3_file *, int deleteFlag);
void go_shm_barrier(sqlite3_file *);
static int go_open_wrapper(sqlite3_vfs* vfs, sqlite3_filename zName,
sqlite3_file* file, int flags, int* pOutFlags) {
static int go_open_wrapper(sqlite3_vfs *vfs, sqlite3_filename zName,
sqlite3_file *file, int flags, int *pOutFlags) {
static const sqlite3_io_methods go_io[2] = {
{
.iVersion = 1,
@@ -89,7 +89,7 @@ struct go_file {
go_handle handle;
};
sqlite3_vfs* sqlite3_vfs_find(const char* zVfsName) {
sqlite3_vfs *sqlite3_vfs_find(const char *zVfsName) {
// The default VFS.
if (!zVfsName || !strcmp(zVfsName, "os")) {
static sqlite3_vfs os_vfs = {
@@ -115,18 +115,18 @@ sqlite3_vfs* sqlite3_vfs_find(const char* zVfsName) {
return NULL;
}
static sqlite3_vfs* go_vfs_list;
static sqlite3_vfs *go_vfs_list;
// Do we already have a C wrapper for the Go VFS?
for (sqlite3_vfs* it = go_vfs_list; it; it = it->pNext) {
for (sqlite3_vfs *it = go_vfs_list; it; it = it->pNext) {
if (!strcmp(zVfsName, it->zName)) {
return it;
}
}
// Delete C wrappers that are no longer needed.
for (sqlite3_vfs** ptr = &go_vfs_list; *ptr;) {
sqlite3_vfs* it = *ptr;
for (sqlite3_vfs **ptr = &go_vfs_list; *ptr;) {
sqlite3_vfs *it = *ptr;
if (go_vfs_find(it->zName)) {
ptr = &it->pNext;
} else {
@@ -136,10 +136,10 @@ sqlite3_vfs* sqlite3_vfs_find(const char* zVfsName) {
}
// Create a new C wrapper.
sqlite3_vfs* head = go_vfs_list;
sqlite3_vfs *head = go_vfs_list;
size_t vfsNameLen = strlen(zVfsName);
go_vfs_list = malloc(sizeof(sqlite3_vfs) + vfsNameLen + 1);
char* name = (char*)(go_vfs_list + 1);
char *name = (char *)(go_vfs_list + 1);
memcpy(name, zVfsName, vfsNameLen + 1);
*go_vfs_list = (sqlite3_vfs){
.iVersion = 2,
@@ -160,16 +160,16 @@ sqlite3_vfs* sqlite3_vfs_find(const char* zVfsName) {
return go_vfs_list;
}
int localtime_s(struct tm* const pTm, time_t const* const pTime) {
int localtime_s(struct tm *const pTm, time_t const *const pTime) {
return go_localtime(pTm, (sqlite3_int64)*pTime);
}
int sqlite3_os_init() { return SQLITE_OK; }
int sqlite3_invoke_busy_handler_go(sqlite3_int64 token) {
void** ap = (void**)&token;
return ((int (*)(void*))(ap[0]))(ap[1]);
void **ap = (void **)&token;
return ((int (*)(void *))(ap[0]))(ap[1]);
}
static_assert(offsetof(sqlite3_vfs, zName) == 16, "Unexpected offset");
static_assert(offsetof(struct go_file, handle) == 4, "Unexpected offset");
static_assert(offsetof(struct go_file, handle) == 4, "Unexpected offset");
+2 -2
View File
@@ -1,7 +1,7 @@
# Remove VFS registration. Go handles it.
--- sqlite3.c.orig
+++ sqlite3.c
@@ -27363,7 +27363,7 @@
@@ -27176,7 +27176,7 @@
sqlite3_free(p);
return sqlite3_os_init();
}
@@ -10,7 +10,7 @@
/*
** The list of all registered VFS implementations.
*/
@@ -27460,7 +27460,7 @@
@@ -27273,7 +27273,7 @@
sqlite3_mutex_leave(mutex);
return SQLITE_OK;
}
+60 -60
View File
@@ -14,40 +14,40 @@
#define SQLITE_VTAB_SAVEPOINTER_GO /**/ 0x080
#define SQLITE_VTAB_SHADOWTABS_GO /***/ 0x100
int go_vtab_create(sqlite3_module*, int argc, const char* const* argv,
sqlite3_vtab**, char** pzErr);
int go_vtab_connect(sqlite3_module*, int argc, const char* const* argv,
sqlite3_vtab**, char** pzErr);
int go_vtab_create(sqlite3_module *, int argc, const char *const *argv,
sqlite3_vtab **, char **pzErr);
int go_vtab_connect(sqlite3_module *, int argc, const char *const *argv,
sqlite3_vtab **, char **pzErr);
int go_vtab_disconnect(sqlite3_vtab*);
int go_vtab_destroy(sqlite3_vtab*);
int go_vtab_best_index(sqlite3_vtab*, sqlite3_index_info*);
int go_cur_open(sqlite3_vtab*, sqlite3_vtab_cursor**);
int go_vtab_disconnect(sqlite3_vtab *);
int go_vtab_destroy(sqlite3_vtab *);
int go_vtab_best_index(sqlite3_vtab *, sqlite3_index_info *);
int go_cur_open(sqlite3_vtab *, sqlite3_vtab_cursor **);
int go_cur_close(sqlite3_vtab_cursor*);
int go_cur_filter(sqlite3_vtab_cursor*, int idxNum, const char* idxStr,
int argc, sqlite3_value** argv);
int go_cur_next(sqlite3_vtab_cursor*);
int go_cur_eof(sqlite3_vtab_cursor*);
int go_cur_column(sqlite3_vtab_cursor*, sqlite3_context*, int);
int go_cur_rowid(sqlite3_vtab_cursor*, sqlite3_int64* pRowid);
int go_cur_close(sqlite3_vtab_cursor *);
int go_cur_filter(sqlite3_vtab_cursor *, int idxNum, const char *idxStr,
int argc, sqlite3_value **argv);
int go_cur_next(sqlite3_vtab_cursor *);
int go_cur_eof(sqlite3_vtab_cursor *);
int go_cur_column(sqlite3_vtab_cursor *, sqlite3_context *, int);
int go_cur_rowid(sqlite3_vtab_cursor *, sqlite3_int64 *pRowid);
int go_vtab_update(sqlite3_vtab*, int, sqlite3_value**, sqlite3_int64*);
int go_vtab_rename(sqlite3_vtab*, const char* zNew);
int go_vtab_find_function(sqlite3_vtab*, int nArg, const char* zName,
go_handle* pxFunc);
int go_vtab_update(sqlite3_vtab *, int, sqlite3_value **, sqlite3_int64 *);
int go_vtab_rename(sqlite3_vtab *, const char *zNew);
int go_vtab_find_function(sqlite3_vtab *, int nArg, const char *zName,
go_handle *pxFunc);
int go_vtab_begin(sqlite3_vtab*);
int go_vtab_sync(sqlite3_vtab*);
int go_vtab_commit(sqlite3_vtab*);
int go_vtab_rollback(sqlite3_vtab*);
int go_vtab_begin(sqlite3_vtab *);
int go_vtab_sync(sqlite3_vtab *);
int go_vtab_commit(sqlite3_vtab *);
int go_vtab_rollback(sqlite3_vtab *);
int go_vtab_savepoint(sqlite3_vtab*, int);
int go_vtab_release(sqlite3_vtab*, int);
int go_vtab_rollback_to(sqlite3_vtab*, int);
int go_vtab_savepoint(sqlite3_vtab *, int);
int go_vtab_release(sqlite3_vtab *, int);
int go_vtab_rollback_to(sqlite3_vtab *, int);
int go_vtab_integrity(sqlite3_vtab*, const char* zSchema, const char* zTabName,
int mFlags, char** pzErr);
int go_vtab_integrity(sqlite3_vtab *, const char *zSchema, const char *zTabName,
int mFlags, char **pzErr);
struct go_module {
go_handle handle;
@@ -64,23 +64,23 @@ struct go_cursor {
sqlite3_vtab_cursor base;
};
static void go_mod_destroy(void* pAux) {
struct go_module* mod = pAux;
void* handle = mod->handle;
static void go_mod_destroy(void *pAux) {
struct go_module *mod = pAux;
void *handle = mod->handle;
free(mod);
go_destroy(handle);
}
static int go_vtab_create_wrapper(sqlite3* db, void* pAux, int argc,
const char* const* argv,
sqlite3_vtab** ppVTab, char** pzErr) {
static int go_vtab_create_wrapper(sqlite3 *db, void *pAux, int argc,
const char *const *argv,
sqlite3_vtab **ppVTab, char **pzErr) {
UNUSED_PARAMETER(db);
struct go_vtab* vtab = calloc(1, sizeof(struct go_vtab));
struct go_vtab *vtab = calloc(1, sizeof(struct go_vtab));
if (vtab == NULL) return SQLITE_NOMEM;
*ppVTab = &vtab->base;
struct go_module* mod = pAux;
struct go_module *mod = pAux;
int rc = go_vtab_create(&mod->base, argc, argv, ppVTab, pzErr);
if (rc) {
if (*pzErr) *pzErr = sqlite3_mprintf("%s", *pzErr);
@@ -89,16 +89,16 @@ static int go_vtab_create_wrapper(sqlite3* db, void* pAux, int argc,
return rc;
}
static int go_vtab_connect_wrapper(sqlite3* db, void* pAux, int argc,
const char* const* argv,
sqlite3_vtab** ppVTab, char** pzErr) {
static int go_vtab_connect_wrapper(sqlite3 *db, void *pAux, int argc,
const char *const *argv,
sqlite3_vtab **ppVTab, char **pzErr) {
UNUSED_PARAMETER(db);
struct go_vtab* vtab = calloc(1, sizeof(struct go_vtab));
struct go_vtab *vtab = calloc(1, sizeof(struct go_vtab));
if (vtab == NULL) return SQLITE_NOMEM;
*ppVTab = &vtab->base;
struct go_module* mod = pAux;
struct go_module *mod = pAux;
int rc = go_vtab_connect(&mod->base, argc, argv, ppVTab, pzErr);
if (rc) {
free(vtab);
@@ -107,23 +107,23 @@ static int go_vtab_connect_wrapper(sqlite3* db, void* pAux, int argc,
return rc;
}
static int go_vtab_disconnect_wrapper(sqlite3_vtab* pVTab) {
struct go_vtab* vtab = container_of(pVTab, struct go_vtab, base);
static int go_vtab_disconnect_wrapper(sqlite3_vtab *pVTab) {
struct go_vtab *vtab = container_of(pVTab, struct go_vtab, base);
int rc = go_vtab_disconnect(pVTab);
free(vtab);
return rc;
}
static int go_vtab_destroy_wrapper(sqlite3_vtab* pVTab) {
struct go_vtab* vtab = container_of(pVTab, struct go_vtab, base);
static int go_vtab_destroy_wrapper(sqlite3_vtab *pVTab) {
struct go_vtab *vtab = container_of(pVTab, struct go_vtab, base);
int rc = go_vtab_destroy(pVTab);
free(vtab);
return rc;
}
static int go_cur_open_wrapper(sqlite3_vtab* pVTab,
sqlite3_vtab_cursor** ppCursor) {
struct go_cursor* cur = calloc(1, sizeof(struct go_cursor));
static int go_cur_open_wrapper(sqlite3_vtab *pVTab,
sqlite3_vtab_cursor **ppCursor) {
struct go_cursor *cur = calloc(1, sizeof(struct go_cursor));
if (cur == NULL) return SQLITE_NOMEM;
*ppCursor = &cur->base;
@@ -132,16 +132,16 @@ static int go_cur_open_wrapper(sqlite3_vtab* pVTab,
return rc;
}
static int go_cur_close_wrapper(sqlite3_vtab_cursor* pCursor) {
struct go_cursor* cur = container_of(pCursor, struct go_cursor, base);
static int go_cur_close_wrapper(sqlite3_vtab_cursor *pCursor) {
struct go_cursor *cur = container_of(pCursor, struct go_cursor, base);
int rc = go_cur_close(pCursor);
free(cur);
return rc;
}
static int go_vtab_find_function_wrapper(
sqlite3_vtab* pVTab, int nArg, const char* zName,
void (**pxFunc)(sqlite3_context*, int, sqlite3_value**), void** ppArg) {
sqlite3_vtab *pVTab, int nArg, const char *zName,
void (**pxFunc)(sqlite3_context *, int, sqlite3_value **), void **ppArg) {
go_handle handle;
int rc = go_vtab_find_function(pVTab, nArg, zName, &handle);
if (rc) {
@@ -151,23 +151,23 @@ static int go_vtab_find_function_wrapper(
return rc;
}
static int go_vtab_integrity_wrapper(sqlite3_vtab* pVTab, const char* zSchema,
const char* zTabName, int mFlags,
char** pzErr) {
static int go_vtab_integrity_wrapper(sqlite3_vtab *pVTab, const char *zSchema,
const char *zTabName, int mFlags,
char **pzErr) {
int rc = go_vtab_integrity(pVTab, zSchema, zTabName, mFlags, pzErr);
if (rc && *pzErr) *pzErr = sqlite3_mprintf("%s", *pzErr);
return rc;
}
static int go_vtab_shadown_name_wrapper(const char* zName) { return true; }
static int go_vtab_shadown_name_wrapper(const char *zName) { return true; }
int sqlite3_create_module_go(sqlite3* db, const char* zName, int flags,
int sqlite3_create_module_go(sqlite3 *db, const char *zName, int flags,
go_handle handle) {
if (handle == NULL) {
return sqlite3_create_module_v2(db, zName, NULL, NULL, NULL);
}
struct go_module* mod = malloc(sizeof(struct go_module));
struct go_module *mod = malloc(sizeof(struct go_module));
if (mod == NULL) {
go_destroy(handle);
return SQLITE_NOMEM;
@@ -229,7 +229,7 @@ int sqlite3_create_module_go(sqlite3* db, const char* zName, int flags,
return sqlite3_create_module_v2(db, zName, &mod->base, mod, go_mod_destroy);
}
int sqlite3_vtab_config_go(sqlite3* db, int op, int constraint) {
int sqlite3_vtab_config_go(sqlite3 *db, int op, int constraint) {
return sqlite3_vtab_config(db, op, constraint);
}
@@ -241,4 +241,4 @@ static_assert(sizeof(struct sqlite3_index_info) == 72, "Unexpected size");
static_assert(sizeof(struct sqlite3_index_orderby) == 8, "Unexpected size");
static_assert(sizeof(struct sqlite3_index_constraint) == 12, "Unexpected size");
static_assert(sizeof(struct sqlite3_index_constraint_usage) == 8,
"Unexpected size");
"Unexpected size");

Some files were not shown because too many files have changed in this diff Show More