Compare commits

...
51 Commits
Author SHA1 Message Date
Nuno Cruces 7daf908f50 Portable. 2026-03-29 12:47:10 +01:00
Nuno Cruces 13edc95f12 Regen. 2026-03-28 09:55:31 +00:00
Nuno Cruces b6e44cc408 Regen. 2026-03-25 15:09:35 +00:00
Nuno Cruces 2c6806ebbd Regen. 2026-03-21 10:37:12 +00:00
Nuno Cruces 361fdc52fa SQLite 3.51.3. 2026-03-18 17:56:27 +00:00
Nuno Cruces 1008ddc5cb JSON. 2026-03-15 11:10:12 +00:00
Nuno Cruces 1956c5a861 Cleanup. 2026-03-13 17:16:56 +00:00
Nuno CrucesandGitHub 1165211c3a Switch from wazero to wasm2go (#362) 2026-03-12 20:53:33 +00:00
Nuno Cruces 137e2694c0 Fix arena. 2026-03-09 21:12:30 +00:00
Nuno CrucesandGitHub d29aacb0b2 Compile SQLite with nostdlib. (#351) 2026-03-09 16:15:28 +00:00
Nuno Cruces 30649fa50a Noncombining. 2026-03-05 16:33:57 +00:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
ed9a77fa96 Bump docker/setup-qemu-action from 3 to 4 (#360)
Bumps [docker/setup-qemu-action](https://github.com/docker/setup-qemu-action) from 3 to 4.
- [Release notes](https://github.com/docker/setup-qemu-action/releases)
- [Commits](https://github.com/docker/setup-qemu-action/compare/v3...v4)

---
updated-dependencies:
- dependency-name: docker/setup-qemu-action
  dependency-version: '4'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-05 15:12:28 +00:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
4c0daafbf7 Bump actions/attest-build-provenance from 3 to 4 (#356)
Bumps [actions/attest-build-provenance](https://github.com/actions/attest-build-provenance) from 3 to 4.
- [Release notes](https://github.com/actions/attest-build-provenance/releases)
- [Changelog](https://github.com/actions/attest-build-provenance/blob/main/RELEASE.md)
- [Commits](https://github.com/actions/attest-build-provenance/compare/v3...v4)

---
updated-dependencies:
- dependency-name: actions/attest-build-provenance
  dependency-version: '4'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-02-27 09:08:07 +00:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
1ad454cfae Bump golang.org/x/crypto from 0.47.0 to 0.48.0 (#353)
Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.47.0 to 0.48.0.
- [Commits](https://github.com/golang/crypto/compare/v0.47.0...v0.48.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-02-10 07:40:07 +00:00
Nuno Cruces ed7dab3edd More sqlite-createtable-parser. 2026-02-06 11:18:52 +00:00
Nuno Cruces a68b5f4364 Math benchmarks. 2026-02-05 09:42:01 +00:00
Nuno Cruces 299c57d1b8 nostdlib. 2026-01-29 17:04:45 +00:00
Nuno Cruces 501f863d20 New sqlite-createtable-parser. 2026-01-26 14:23:44 +00:00
Nuno Cruces 33bea5277f Flags. 2026-01-24 13:35:09 +00:00
Nuno Cruces c263381f1f Fix #349. 2026-01-24 12:48:33 +00:00
Nuno Cruces c4f254bc37 SQLite 3.51.2. 2026-01-16 14:55:24 +00:00
Nuno Cruces c97c5a1f86 Type affinity. 2026-01-15 14:17:06 +00:00
Nuno Cruces 3e1b1313c1 Unquote identifiers. 2026-01-15 12:59:46 +00:00
Nuno Cruces 83744cb9c8 More sqlite-createtable-parser. 2026-01-14 18:11:59 +00:00
Nuno Cruces a36d72c2dc Stricter vtabs, sqlite-createtable-parser. 2026-01-13 14:23:55 +00:00
Nuno Cruces e7f5604199 Stricter vtabs, get VFS name. 2026-01-13 11:25:25 +00:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
e50083912c Bump golang.org/x/crypto from 0.46.0 to 0.47.0 (#348)
Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.46.0 to 0.47.0.
- [Commits](https://github.com/golang/crypto/compare/v0.46.0...v0.47.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-01-13 08:03:31 +00:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
d4764fb2fa Bump golang.org/x/sys from 0.39.0 to 0.40.0 (#346)
Bumps [golang.org/x/sys](https://github.com/golang/sys) from 0.39.0 to 0.40.0.
- [Commits](https://github.com/golang/sys/compare/v0.39.0...v0.40.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-01-08 23:12:26 +00:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
42df71f3ff Bump github.com/ncruces/wbt from 0.2.0 to 1.0.0 (#345)
Bumps [github.com/ncruces/wbt](https://github.com/ncruces/wbt) from 0.2.0 to 1.0.0.
- [Release notes](https://github.com/ncruces/wbt/releases)
- [Commits](https://github.com/ncruces/wbt/compare/v0.2.0...v1.0.0)

---
updated-dependencies:
- dependency-name: github.com/ncruces/wbt
  dependency-version: 1.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-01-06 23:55:44 +00:00
Nuno Cruces 91e969c06b Example. 2026-01-05 12:49:21 +00:00
Nuno Cruces 8ab0ddf53e More temp files. 2025-12-29 20:45:44 +00:00
Nuno Cruces 74d22ded0a Move Litestream. 2025-12-29 12:20:05 +00:00
Nuno Cruces d962611796 Bump cross-platform-actions/action from 0.31.0 to 0.32.0 2025-12-21 13:39:41 +00:00
Nuno Cruces 7df3814c34 Improved driver metadata. 2025-12-20 22:23:05 +00:00
Nuno Cruces c5f49b835a Reduce mutex scope, use temp files. 2025-12-19 16:37:47 +00:00
Nuno Cruces 0e55451a0b Deps. 2025-12-19 12:28:57 +00:00
Nuno Cruces ea9a58ab19 Remove singleflight. 2025-12-17 13:17:50 +00:00
Nuno Cruces 0b46e74ea6 Bump cross-platform-actions/action from 0.30.0 to 0.31.0 2025-12-16 12:34:55 +00:00
Nuno Cruces 8dca850bee Stricter floats. 2025-12-12 17:21:33 +00:00
Nuno Cruces 5b78823416 ScanColumn dropped. 2025-12-12 13:06:37 +00:00
Nuno Cruces 1764a571da Litestream v0.5.3. 2025-12-12 12:44:57 +00:00
Nuno Cruces 9837310af7 Fuzz time shift. 2025-12-10 16:15:44 +00:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
ec8961a621 Bump golang.org/x/crypto from 0.45.0 to 0.46.0 (#341)
Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.45.0 to 0.46.0.
- [Commits](https://github.com/golang/crypto/compare/v0.45.0...v0.46.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-09 00:49:00 +00:00
Nuno Cruces 8c37aa2d97 Remove inprocess primary. 2025-12-04 16:31:41 +00:00
Nuno Cruces ca93c498e7 Relative time, fixes. 2025-12-04 15:55:39 +00:00
Nuno Cruces 15e9087fa8 Time travel pragma. 2025-12-03 15:01:04 +00:00
Nuno Cruces 7028e3a5b9 SQLite 3.51.1. 2025-11-30 10:24:34 +00:00
Nuno Cruces 03bb20de6e Lock tweaks. 2025-11-29 16:12:29 +00:00
Nuno Cruces 20a51a344e Remove MinLevel. 2025-11-27 17:29:47 +00:00
Nuno Cruces 2dbcc480f7 Initial pragma support. 2025-11-27 16:28:16 +00:00
Nuno Cruces 0f0716c438 Inprocess Litestream primary. 2025-11-26 14:07:44 +00:00
257 changed files with 4023 additions and 10298 deletions
-23
View File
@@ -1,23 +0,0 @@
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
-15
View File
@@ -1,15 +0,0 @@
#!/usr/bin/env bash
set -euo pipefail
# Download and build SQLite
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
-32
View File
@@ -1,32 +0,0 @@
name: Reproducible build
on:
workflow_dispatch:
permissions:
contents: read
id-token: write
attestations: write
jobs:
build:
strategy:
matrix:
os: [macos-latest, ubuntu-latest, windows-latest]
runs-on: ${{ matrix.os }}
steps:
- uses: ilammy/msvc-dev-cmd@v1
- uses: actions/checkout@v6
- name: Build
shell: bash
run: .github/workflows/repro.sh
- uses: actions/attest-build-provenance@v3
if: matrix.os == 'ubuntu-latest'
with:
subject-path: |
embed/sqlite3.wasm
embed/bcw2/bcw2.wasm
util/sql3util/wasm/sql3parse_table.wasm
+27 -41
View File
@@ -68,8 +68,8 @@ jobs:
shell: bash
run: |
go work init .
go work use -r embed gormlite
go test ./embed/bcw2/...
go work use -r gormlite
go test ./gormlite
- name: Test GORM
shell: bash
@@ -93,25 +93,29 @@ jobs:
github.event_name == 'push' &&
matrix.os == 'ubuntu-latest'
test-bsd:
test-cross:
strategy:
matrix:
os:
- name: freebsd
version: '14.3'
version: '15.0'
- name: netbsd
version: '10.1'
- name: freebsd
arch: arm64
version: '14.3'
tflags: '-test.short'
- name: netbsd
arch: arm64
version: '10.1'
- name: illumos
action: omnios
version: 'r151056'
tflags: '-test.short'
- name: openbsd
version: '7.8'
tflags: '-test.short'
- name: freebsd
arch: arm64
version: '15.0'
tflags: '-test.short'
- name: netbsd
arch: arm64
version: '10.1'
tflags: '-test.short'
runs-on: ubuntu-latest
needs: test
@@ -126,9 +130,9 @@ jobs:
run: .github/workflows/build-test.sh
- name: Test
uses: cross-platform-actions/action@v0.30.0
uses: cross-platform-actions/action@v0.32.0
with:
operating_system: ${{ matrix.os.name }}
operating_system: ${{ matrix.os.action || matrix.os.name }}
architecture: ${{ matrix.os.arch }}
version: ${{ matrix.os.version }}
shell: bash
@@ -141,11 +145,14 @@ jobs:
os:
- name: dragonfly
action: 'vmactions/dragonflybsd-vm@v1'
tflags: '-test.short'
- name: illumos
action: 'vmactions/omnios-vm@v1'
action: 'vmactions/openindiana-vm@v0'
tflags: '-test.short'
- name: solaris
action: 'vmactions/solaris-vm@v1'
bflags: '-tags sqlite3_dotlk'
tflags: '-test.short'
runs-on: ubuntu-latest
needs: test
@@ -163,47 +170,26 @@ jobs:
- name: Test
uses: ./.github/actions/vmactions
test-wasip1:
runs-on: ubuntu-latest
needs: test
steps:
- uses: bytecodealliance/actions/wasmtime/setup@v1
- uses: actions/checkout@v6
- uses: actions/setup-go@v6
with: { go-version: stable }
- name: Set path
run: echo "$(go env GOROOT)/lib/wasm" >> "$GITHUB_PATH"
- name: Test wasmtime
env:
GOOS: wasip1
GOARCH: wasm
GOWASIRUNTIME: wasmtime
GOWASIRUNTIMEARGS: '--env CI=true'
run: go test -short -tags sqlite3_dotlk -skip Example ./...
test-qemu:
runs-on: ubuntu-latest
needs: test
steps:
- uses: docker/setup-qemu-action@v3
- uses: docker/setup-qemu-action@v4
- uses: actions/checkout@v6
- uses: actions/setup-go@v6
with: { go-version: stable }
- name: Test 386 (32-bit)
run: GOARCH=386 go test -short ./...
run: GOARCH=386 go test ./...
- name: Test riscv64 (interpreter)
- name: Test riscv64
run: GOARCH=riscv64 go test -short ./...
- name: Test ppc64le (interpreter)
- name: Test ppc64le
run: GOARCH=ppc64le go test -short ./...
- name: Test loong64 (interpreter)
- name: Test loong64
run: GOARCH=loong64 go test -short ./...
- name: Test s390x (big-endian)
@@ -222,7 +208,7 @@ jobs:
run: go test ./...
- name: Test arm (32-bit)
run: GOARCH=arm GOARM=7 go test -short ./...
run: GOARCH=arm GOARM=7 go test ./...
test-macintel:
runs-on: macos-15-intel
+5 -11
View File
@@ -1,4 +1,4 @@
# Go bindings to SQLite using wazero
# Go bindings to SQLite using wasm2go
[![Go Reference](https://pkg.go.dev/badge/image)](https://pkg.go.dev/github.com/ncruces/go-sqlite3)
[![Go Report](https://goreportcard.com/badge/github.com/ncruces/go-sqlite3)](https://goreportcard.com/report/github.com/ncruces/go-sqlite3)
@@ -8,9 +8,9 @@ Go module `github.com/ncruces/go-sqlite3` is a `cgo`-free [SQLite](https://sqlit
It provides a [`database/sql`](https://pkg.go.dev/database/sql) compatible driver,
as well as direct access to most of the [C SQLite API](https://sqlite.org/cintro.html).
It wraps a [Wasm](https://webassembly.org/) [build](embed/) of SQLite,
and uses [wazero](https://wazero.io/) as the runtime.\
Go, wazero and [`x/sys`](https://pkg.go.dev/golang.org/x/sys) are the _only_ direct dependencies.
It wraps a [Wasm](https://webassembly.org/) build of SQLite,
and uses [wasm2go](https://github.com/ncruces/wasm2go) to translate it to Go.\
Go and [`x/sys`](https://pkg.go.dev/golang.org/x/sys) are the _only_ direct dependencies.
### Getting started
@@ -19,7 +19,6 @@ Using the [`database/sql`](https://pkg.go.dev/database/sql) driver:
import "database/sql"
import _ "github.com/ncruces/go-sqlite3/driver"
import _ "github.com/ncruces/go-sqlite3/embed"
var version string
db, _ := sql.Open("sqlite3", "file:demo.db")
@@ -34,8 +33,6 @@ db.QueryRow(`SELECT sqlite_version()`).Scan(&version)
- [`github.com/ncruces/go-sqlite3/driver`](https://pkg.go.dev/github.com/ncruces/go-sqlite3/driver)
provides a [`database/sql`](https://pkg.go.dev/database/sql) driver
([example](https://pkg.go.dev/github.com/ncruces/go-sqlite3/driver#example-package)).
- [`github.com/ncruces/go-sqlite3/embed`](https://pkg.go.dev/github.com/ncruces/go-sqlite3/embed)
embeds a build of SQLite into your application.
- [`github.com/ncruces/go-sqlite3/vfs`](https://pkg.go.dev/github.com/ncruces/go-sqlite3/vfs)
wraps the [C SQLite VFS API](https://sqlite.org/vfs.html) and provides a pure Go implementation.
- [`github.com/ncruces/go-sqlite3/gormlite`](https://pkg.go.dev/github.com/ncruces/go-sqlite3/gormlite)
@@ -64,7 +61,6 @@ db.QueryRow(`SELECT sqlite_version()`).Scan(&version)
- [statistics functions](https://pkg.go.dev/github.com/ncruces/go-sqlite3/ext/stats)
- [encryption at rest](vfs/adiantum/README.md)
- [many extensions](ext/README.md)
- [and more…](embed/README.md)
### Caveats
@@ -79,9 +75,7 @@ memory usage will be higher than alternatives.
### Testing
This project aims for [high test coverage](https://github.com/ncruces/go-sqlite3/wiki/Test-coverage-report).
It also benefits greatly from [SQLite's](https://sqlite.org/testing.html) and
[wazero's](https://tetrate.io/blog/introducing-wazero-from-tetrate/#:~:text=Rock%2Dsolid%20test%20approach)
thorough testing.
It also benefits greatly from [SQLite's](https://sqlite.org/testing.html) thorough testing.
Every commit is tested on:
* Linux: amd64, arm64, 386, arm, riscv64, ppc64le, loong64, s390x
+12 -12
View File
@@ -62,22 +62,22 @@ func (src *Conn) BackupInit(srcDB, dstURI string) (*Backup, error) {
}
func (c *Conn) backupInit(dst ptr_t, dstName string, src ptr_t, srcName string) (*Backup, error) {
defer c.arena.mark()()
dstPtr := c.arena.string(dstName)
srcPtr := c.arena.string(srcName)
defer c.arena.Mark()()
dstPtr := c.arena.String(dstName)
srcPtr := c.arena.String(srcName)
other := dst
if c.handle == dst {
other = src
}
ptr := ptr_t(c.call("sqlite3_backup_init",
stk_t(dst), stk_t(dstPtr),
stk_t(src), stk_t(srcPtr)))
ptr := ptr_t(c.wrp.Xsqlite3_backup_init(
int32(dst), int32(dstPtr),
int32(src), int32(srcPtr)))
if ptr == 0 {
defer c.closeDB(other)
rc := res_t(c.call("sqlite3_errcode", stk_t(dst)))
return nil, c.sqlite.error(rc, dst)
rc := res_t(c.wrp.Xsqlite3_errcode(int32(dst)))
return nil, c.errorFor(dst, rc)
}
return &Backup{
@@ -97,7 +97,7 @@ func (b *Backup) Close() error {
return nil
}
rc := res_t(b.c.call("sqlite3_backup_finish", stk_t(b.handle)))
rc := res_t(b.c.wrp.Xsqlite3_backup_finish(int32(b.handle)))
b.c.closeDB(b.otherc)
b.handle = 0
return b.c.error(rc)
@@ -108,7 +108,7 @@ func (b *Backup) Close() error {
//
// https://sqlite.org/c3ref/backup_finish.html#sqlite3backupstep
func (b *Backup) Step(nPage int) (done bool, err error) {
rc := res_t(b.c.call("sqlite3_backup_step", stk_t(b.handle), stk_t(nPage)))
rc := res_t(b.c.wrp.Xsqlite3_backup_step(int32(b.handle), int32(nPage)))
if rc == _DONE {
return true, nil
}
@@ -120,7 +120,7 @@ func (b *Backup) Step(nPage int) (done bool, err error) {
//
// https://sqlite.org/c3ref/backup_finish.html#sqlite3backupremaining
func (b *Backup) Remaining() int {
n := int32(b.c.call("sqlite3_backup_remaining", stk_t(b.handle)))
n := int32(b.c.wrp.Xsqlite3_backup_remaining(int32(b.handle)))
return int(n)
}
@@ -129,6 +129,6 @@ func (b *Backup) Remaining() int {
//
// https://sqlite.org/c3ref/backup_finish.html#sqlite3backuppagecount
func (b *Backup) PageCount() int {
n := int32(b.c.call("sqlite3_backup_pagecount", stk_t(b.handle)))
n := int32(b.c.wrp.Xsqlite3_backup_pagecount(int32(b.handle)))
return int(n)
}
+33 -33
View File
@@ -3,7 +3,7 @@ package sqlite3
import (
"io"
"github.com/ncruces/go-sqlite3/internal/util"
"github.com/ncruces/go-sqlite3/internal/errutil"
)
// ZeroBlob represents a zero-filled, length n BLOB
@@ -35,28 +35,28 @@ func (c *Conn) OpenBlob(db, table, column string, row int64, write bool) (*Blob,
return nil, INTERRUPT
}
defer c.arena.mark()()
blobPtr := c.arena.new(ptrlen)
dbPtr := c.arena.string(db)
tablePtr := c.arena.string(table)
columnPtr := c.arena.string(column)
defer c.arena.Mark()()
blobPtr := c.arena.New(ptrlen)
dbPtr := c.arena.String(db)
tablePtr := c.arena.String(table)
columnPtr := c.arena.String(column)
var flags int32
if write {
flags = 1
}
rc := res_t(c.call("sqlite3_blob_open", stk_t(c.handle),
stk_t(dbPtr), stk_t(tablePtr), stk_t(columnPtr),
stk_t(row), stk_t(flags), stk_t(blobPtr)))
rc := res_t(c.wrp.Xsqlite3_blob_open(int32(c.handle),
int32(dbPtr), int32(tablePtr), int32(columnPtr),
row, flags, int32(blobPtr)))
if err := c.error(rc); err != nil {
return nil, err
}
blob := Blob{c: c}
blob.handle = util.Read32[ptr_t](c.mod, blobPtr)
blob.bytes = int64(int32(c.call("sqlite3_blob_bytes", stk_t(blob.handle))))
blob.handle = ptr_t(c.wrp.Read32(blobPtr))
blob.bytes = int64(c.wrp.Xsqlite3_blob_bytes(int32(blob.handle)))
return &blob, nil
}
@@ -70,8 +70,8 @@ func (b *Blob) Close() error {
return nil
}
rc := res_t(b.c.call("sqlite3_blob_close", stk_t(b.handle)))
b.c.free(b.bufptr)
rc := res_t(b.c.wrp.Xsqlite3_blob_close(int32(b.handle)))
b.c.wrp.Free(b.bufptr)
b.handle = 0
return b.c.error(rc)
}
@@ -97,12 +97,12 @@ func (b *Blob) Read(p []byte) (n int, err error) {
want = avail
}
if want > b.buflen {
b.bufptr = b.c.realloc(b.bufptr, want)
b.bufptr = b.c.wrp.Realloc(b.bufptr, want)
b.buflen = want
}
rc := res_t(b.c.call("sqlite3_blob_read", stk_t(b.handle),
stk_t(b.bufptr), stk_t(want), stk_t(b.offset)))
rc := res_t(b.c.wrp.Xsqlite3_blob_read(int32(b.handle),
int32(b.bufptr), int32(want), int32(b.offset)))
err = b.c.error(rc)
if err != nil {
return 0, err
@@ -112,7 +112,7 @@ func (b *Blob) Read(p []byte) (n int, err error) {
err = io.EOF
}
copy(p, util.View(b.c.mod, b.bufptr, want))
copy(p, b.c.wrp.Bytes(b.bufptr, want))
return int(want), err
}
@@ -130,19 +130,19 @@ func (b *Blob) WriteTo(w io.Writer) (n int64, err error) {
want = avail
}
if want > b.buflen {
b.bufptr = b.c.realloc(b.bufptr, want)
b.bufptr = b.c.wrp.Realloc(b.bufptr, want)
b.buflen = want
}
for want > 0 {
rc := res_t(b.c.call("sqlite3_blob_read", stk_t(b.handle),
stk_t(b.bufptr), stk_t(want), stk_t(b.offset)))
rc := res_t(b.c.wrp.Xsqlite3_blob_read(int32(b.handle),
int32(b.bufptr), int32(want), int32(b.offset)))
err = b.c.error(rc)
if err != nil {
return n, err
}
mem := util.View(b.c.mod, b.bufptr, want)
mem := b.c.wrp.Bytes(b.bufptr, want)
m, err := w.Write(mem[:want])
b.offset += int64(m)
n += int64(m)
@@ -168,13 +168,13 @@ func (b *Blob) WriteTo(w io.Writer) (n int64, err error) {
func (b *Blob) Write(p []byte) (n int, err error) {
want := int64(len(p))
if want > b.buflen {
b.bufptr = b.c.realloc(b.bufptr, want)
b.bufptr = b.c.wrp.Realloc(b.bufptr, want)
b.buflen = want
}
util.WriteBytes(b.c.mod, b.bufptr, p)
b.c.wrp.WriteBytes(b.bufptr, p)
rc := res_t(b.c.call("sqlite3_blob_write", stk_t(b.handle),
stk_t(b.bufptr), stk_t(want), stk_t(b.offset)))
rc := res_t(b.c.wrp.Xsqlite3_blob_write(int32(b.handle),
int32(b.bufptr), int32(want), int32(b.offset)))
err = b.c.error(rc)
if err != nil {
return 0, err
@@ -199,16 +199,16 @@ func (b *Blob) ReadFrom(r io.Reader) (n int64, err error) {
want = 1
}
if want > b.buflen {
b.bufptr = b.c.realloc(b.bufptr, want)
b.bufptr = b.c.wrp.Realloc(b.bufptr, want)
b.buflen = want
}
for {
mem := util.View(b.c.mod, b.bufptr, want)
mem := b.c.wrp.Bytes(b.bufptr, want)
m, err := r.Read(mem[:want])
if m > 0 {
rc := res_t(b.c.call("sqlite3_blob_write", stk_t(b.handle),
stk_t(b.bufptr), stk_t(m), stk_t(b.offset)))
rc := res_t(b.c.wrp.Xsqlite3_blob_write(int32(b.handle),
int32(b.bufptr), int32(m), int32(b.offset)))
err := b.c.error(rc)
if err != nil {
return n, err
@@ -237,7 +237,7 @@ func (b *Blob) ReadFrom(r io.Reader) (n int64, err error) {
func (b *Blob) Seek(offset int64, whence int) (int64, error) {
switch whence {
default:
return 0, util.WhenceErr
return 0, errutil.WhenceErr
case io.SeekStart:
break
case io.SeekCurrent:
@@ -246,7 +246,7 @@ func (b *Blob) Seek(offset int64, whence int) (int64, error) {
offset += b.bytes
}
if offset < 0 {
return 0, util.OffsetErr
return 0, errutil.OffsetErr
}
b.offset = offset
return offset, nil
@@ -259,8 +259,8 @@ func (b *Blob) Reopen(row int64) error {
if b.c.interrupt.Err() != nil {
return INTERRUPT
}
err := b.c.error(res_t(b.c.call("sqlite3_blob_reopen", stk_t(b.handle), stk_t(row))))
b.bytes = int64(int32(b.c.call("sqlite3_blob_bytes", stk_t(b.handle))))
err := b.c.error(res_t(b.c.wrp.Xsqlite3_blob_reopen(int32(b.handle), row)))
b.bytes = int64(b.c.wrp.Xsqlite3_blob_bytes(int32(b.handle)))
b.offset = 0
return err
}
+103 -102
View File
@@ -1,14 +1,11 @@
package sqlite3
import (
"context"
"fmt"
"strconv"
"sync/atomic"
"github.com/tetratelabs/wazero/api"
"github.com/ncruces/go-sqlite3/internal/util"
"github.com/ncruces/go-sqlite3/internal/errutil"
"github.com/ncruces/go-sqlite3/vfs"
)
@@ -30,8 +27,8 @@ func (c *Conn) Config(op DBConfig, arg ...bool) (bool, error) {
// The vararg is a pointer to an array containing these arguments:
// an int and an int* pointing to that int.
defer c.arena.mark()()
argsPtr := c.arena.new(intlen + ptrlen)
defer c.arena.Mark()()
argsPtr := c.arena.New(intlen + ptrlen)
var flag int32
switch {
@@ -41,12 +38,12 @@ func (c *Conn) Config(op DBConfig, arg ...bool) (bool, error) {
flag = 1
}
util.Write32(c.mod, argsPtr+0*ptrlen, flag)
util.Write32(c.mod, argsPtr+1*ptrlen, argsPtr)
c.wrp.Write32(argsPtr+0*ptrlen, uint32(flag))
c.wrp.Write32(argsPtr+1*ptrlen, uint32(argsPtr))
rc := res_t(c.call("sqlite3_db_config", stk_t(c.handle),
stk_t(op), stk_t(argsPtr)))
return util.ReadBool(c.mod, argsPtr), c.error(rc)
rc := res_t(c.wrp.Xsqlite3_db_config(int32(c.handle),
int32(op), int32(argsPtr)))
return c.wrp.ReadBool(argsPtr), c.error(rc)
}
var defaultLogger atomic.Pointer[func(code ExtendedErrorCode, msg string)]
@@ -66,7 +63,7 @@ func (c *Conn) ConfigLog(cb func(code ExtendedErrorCode, msg string)) error {
if cb != nil {
enable = 1
}
rc := res_t(c.call("sqlite3_config_log_go", stk_t(enable)))
rc := res_t(c.wrp.Xsqlite3_config_log_go(enable))
if err := c.error(rc); err != nil {
return err
}
@@ -74,9 +71,9 @@ func (c *Conn) ConfigLog(cb func(code ExtendedErrorCode, msg string)) error {
return nil
}
func logCallback(ctx context.Context, mod api.Module, _ ptr_t, iCode res_t, zMsg ptr_t) {
if c, ok := ctx.Value(connKey{}).(*Conn); ok && c.log != nil {
msg := util.ReadString(mod, zMsg, _MAX_LENGTH)
func (e *env) Xgo_log(_, iCode, zMsg int32) {
if c, ok := e.DB.(*Conn); ok && c.log != nil {
msg := e.ReadString(ptr_t(zMsg), _MAX_LENGTH)
c.log(xErrorCode(iCode), msg)
}
}
@@ -95,12 +92,12 @@ func (c *Conn) Log(code ExtendedErrorCode, format string, a ...any) {
//
// https://sqlite.org/c3ref/file_control.html
func (c *Conn) FileControl(schema string, op FcntlOpcode, arg ...any) (any, error) {
defer c.arena.mark()()
ptr := c.arena.new(max(ptrlen, intlen))
defer c.arena.Mark()()
ptr := c.arena.New(max(ptrlen, intlen))
var schemaPtr ptr_t
if schema != "" {
schemaPtr = c.arena.string(schema)
schemaPtr = c.arena.String(schema)
}
var rc res_t
@@ -110,9 +107,9 @@ func (c *Conn) FileControl(schema string, op FcntlOpcode, arg ...any) (any, erro
return nil, MISUSE
case FCNTL_RESET_CACHE, FCNTL_NULL_IO:
rc = res_t(c.call("sqlite3_file_control",
stk_t(c.handle), stk_t(schemaPtr),
stk_t(op), 0))
rc = res_t(c.wrp.Xsqlite3_file_control(
int32(c.handle), int32(schemaPtr),
int32(op), 0))
case FCNTL_PERSIST_WAL, FCNTL_POWERSAFE_OVERWRITE:
var flag int32
@@ -122,62 +119,66 @@ func (c *Conn) FileControl(schema string, op FcntlOpcode, arg ...any) (any, erro
case arg[0]:
flag = 1
}
util.Write32(c.mod, ptr, flag)
rc = res_t(c.call("sqlite3_file_control",
stk_t(c.handle), stk_t(schemaPtr),
stk_t(op), stk_t(ptr)))
ret = util.ReadBool(c.mod, ptr)
c.wrp.Write32(ptr, uint32(flag))
rc = res_t(c.wrp.Xsqlite3_file_control(
int32(c.handle), int32(schemaPtr),
int32(op), int32(ptr)))
ret = c.wrp.ReadBool(ptr)
case FCNTL_CHUNK_SIZE:
util.Write32(c.mod, ptr, int32(arg[0].(int)))
rc = res_t(c.call("sqlite3_file_control",
stk_t(c.handle), stk_t(schemaPtr),
stk_t(op), stk_t(ptr)))
c.wrp.Write32(ptr, uint32(arg[0].(int)))
rc = res_t(c.wrp.Xsqlite3_file_control(
int32(c.handle), int32(schemaPtr),
int32(op), int32(ptr)))
case FCNTL_RESERVE_BYTES:
bytes := -1
if len(arg) > 0 {
bytes = arg[0].(int)
}
util.Write32(c.mod, ptr, int32(bytes))
rc = res_t(c.call("sqlite3_file_control",
stk_t(c.handle), stk_t(schemaPtr),
stk_t(op), stk_t(ptr)))
ret = int(util.Read32[int32](c.mod, ptr))
c.wrp.Write32(ptr, uint32(bytes))
rc = res_t(c.wrp.Xsqlite3_file_control(
int32(c.handle), int32(schemaPtr),
int32(op), int32(ptr)))
ret = int(int32(c.wrp.Read32(ptr)))
case FCNTL_DATA_VERSION:
rc = res_t(c.call("sqlite3_file_control",
stk_t(c.handle), stk_t(schemaPtr),
stk_t(op), stk_t(ptr)))
ret = util.Read32[uint32](c.mod, ptr)
rc = res_t(c.wrp.Xsqlite3_file_control(
int32(c.handle), int32(schemaPtr),
int32(op), int32(ptr)))
ret = uint32(c.wrp.Read32(ptr))
case FCNTL_LOCKSTATE:
rc = res_t(c.call("sqlite3_file_control",
stk_t(c.handle), stk_t(schemaPtr),
stk_t(op), stk_t(ptr)))
ret = util.Read32[vfs.LockLevel](c.mod, ptr)
rc = res_t(c.wrp.Xsqlite3_file_control(
int32(c.handle), int32(schemaPtr),
int32(op), int32(ptr)))
ret = vfs.LockLevel(c.wrp.Read32(ptr))
case FCNTL_VFS_POINTER:
rc = res_t(c.call("sqlite3_file_control",
stk_t(c.handle), stk_t(schemaPtr),
stk_t(op), stk_t(ptr)))
case FCNTL_VFSNAME, FCNTL_VFS_POINTER:
rc = res_t(c.wrp.Xsqlite3_file_control(
int32(c.handle), int32(schemaPtr),
int32(FCNTL_VFS_POINTER), int32(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)
ret = vfs.Find(name)
ptr = ptr_t(c.wrp.Read32(ptr))
ptr = ptr_t(c.wrp.Read32(ptr + zNameOffset))
name := c.wrp.ReadString(ptr, _MAX_NAME)
if op == FCNTL_VFS_POINTER {
ret = vfs.Find(name)
} else {
ret = name
}
}
case FCNTL_FILE_POINTER, FCNTL_JOURNAL_POINTER:
rc = res_t(c.call("sqlite3_file_control",
stk_t(c.handle), stk_t(schemaPtr),
stk_t(op), stk_t(ptr)))
rc = res_t(c.wrp.Xsqlite3_file_control(
int32(c.handle), int32(schemaPtr),
int32(op), int32(ptr)))
if rc == _OK {
const fileHandleOffset = 4
ptr = util.Read32[ptr_t](c.mod, ptr)
ptr = util.Read32[ptr_t](c.mod, ptr+fileHandleOffset)
ret = util.GetHandle(c.ctx, ptr)
ptr = ptr_t(c.wrp.Read32(ptr))
ptr = ptr_t(c.wrp.Read32(ptr + fileHandleOffset))
ret = c.wrp.GetHandle(ptr)
}
}
@@ -192,7 +193,7 @@ func (c *Conn) FileControl(schema string, op FcntlOpcode, arg ...any) (any, erro
//
// https://sqlite.org/c3ref/limit.html
func (c *Conn) Limit(id LimitCategory, value int) int {
v := int32(c.call("sqlite3_limit", stk_t(c.handle), stk_t(id), stk_t(value)))
v := int32(c.wrp.Xsqlite3_limit(int32(c.handle), int32(id), int32(value)))
return int(v)
}
@@ -204,40 +205,39 @@ func (c *Conn) SetAuthorizer(cb func(action AuthorizerActionCode, name3rd, name4
if cb != nil {
enable = 1
}
rc := res_t(c.call("sqlite3_set_authorizer_go", stk_t(c.handle), stk_t(enable)))
rc := res_t(c.wrp.Xsqlite3_set_authorizer_go(int32(c.handle), enable))
if err := c.error(rc); err != nil {
return err
}
c.authorizer = cb
return nil
}
func authorizerCallback(ctx context.Context, mod api.Module, pDB ptr_t, action AuthorizerActionCode, zName3rd, zName4th, zSchema, zInner ptr_t) (rc AuthorizerReturnCode) {
if c, ok := ctx.Value(connKey{}).(*Conn); ok && c.handle == pDB && c.authorizer != nil {
func (e *env) Xgo_authorizer(pDB, action, zName3rd, zName4th, zSchema, zInner int32) (rc int32) {
if c, ok := e.DB.(*Conn); ok && c.handle == ptr_t(pDB) && c.authorizer != nil {
var name3rd, name4th, schema, inner string
if zName3rd != 0 {
name3rd = util.ReadString(mod, zName3rd, _MAX_NAME)
name3rd = e.ReadString(ptr_t(zName3rd), _MAX_NAME)
}
if zName4th != 0 {
name4th = util.ReadString(mod, zName4th, _MAX_NAME)
name4th = e.ReadString(ptr_t(zName4th), _MAX_NAME)
}
if zSchema != 0 {
schema = util.ReadString(mod, zSchema, _MAX_NAME)
schema = e.ReadString(ptr_t(zSchema), _MAX_NAME)
}
if zInner != 0 {
inner = util.ReadString(mod, zInner, _MAX_NAME)
inner = e.ReadString(ptr_t(zInner), _MAX_NAME)
}
rc = c.authorizer(action, name3rd, name4th, schema, inner)
return int32(c.authorizer(AuthorizerActionCode(action), name3rd, name4th, schema, inner))
}
return rc
return _OK
}
// Trace registers a trace callback function against the database connection.
//
// https://sqlite.org/c3ref/trace_v2.html
func (c *Conn) Trace(mask TraceEvent, cb func(evt TraceEvent, arg1 any, arg2 any) error) error {
rc := res_t(c.call("sqlite3_trace_go", stk_t(c.handle), stk_t(mask)))
rc := res_t(c.wrp.Xsqlite3_trace_go(int32(c.handle), int32(mask)))
if err := c.error(rc); err != nil {
return err
}
@@ -245,30 +245,30 @@ func (c *Conn) Trace(mask TraceEvent, cb func(evt TraceEvent, arg1 any, arg2 any
return nil
}
func traceCallback(ctx context.Context, mod api.Module, evt TraceEvent, pDB, pArg1, pArg2 ptr_t) (rc res_t) {
if c, ok := ctx.Value(connKey{}).(*Conn); ok && c.handle == pDB && c.trace != nil {
func (e *env) Xgo_trace(evt, pDB, pArg1, pArg2 int32) int32 {
if c, ok := e.DB.(*Conn); ok && c.handle == ptr_t(pDB) && c.trace != nil {
var arg1, arg2 any
if evt == TRACE_CLOSE {
if TraceEvent(evt) == TRACE_CLOSE {
arg1 = c
} else {
for _, s := range c.stmts {
if pArg1 == s.handle {
if ptr_t(pArg1) == s.handle {
arg1 = s
switch evt {
switch TraceEvent(evt) {
case TRACE_STMT:
arg2 = s.SQL()
case TRACE_PROFILE:
arg2 = util.Read64[int64](mod, pArg2)
arg2 = int64(e.Read64(ptr_t(pArg2)))
}
break
}
}
}
if arg1 != nil {
_ = c.trace(evt, arg1, arg2)
_ = c.trace(TraceEvent(evt), arg1, arg2)
}
}
return rc
return _OK
}
// WALCheckpoint checkpoints a WAL database.
@@ -279,15 +279,15 @@ func (c *Conn) WALCheckpoint(schema string, mode CheckpointMode) (nLog, nCkpt in
return 0, 0, INTERRUPT
}
defer c.arena.mark()()
nLogPtr := c.arena.new(ptrlen)
nCkptPtr := c.arena.new(ptrlen)
schemaPtr := c.arena.string(schema)
rc := res_t(c.call("sqlite3_wal_checkpoint_v2",
stk_t(c.handle), stk_t(schemaPtr), stk_t(mode),
stk_t(nLogPtr), stk_t(nCkptPtr)))
nLog = int(util.Read32[int32](c.mod, nLogPtr))
nCkpt = int(util.Read32[int32](c.mod, nCkptPtr))
defer c.arena.Mark()()
nLogPtr := c.arena.New(ptrlen)
nCkptPtr := c.arena.New(ptrlen)
schemaPtr := c.arena.String(schema)
rc := res_t(c.wrp.Xsqlite3_wal_checkpoint_v2(
int32(c.handle), int32(schemaPtr), int32(mode),
int32(nLogPtr), int32(nCkptPtr)))
nLog = int(int32(c.wrp.Read32(nLogPtr)))
nCkpt = int(int32(c.wrp.Read32(nCkptPtr)))
return nLog, nCkpt, c.error(rc)
}
@@ -295,7 +295,7 @@ func (c *Conn) WALCheckpoint(schema string, mode CheckpointMode) (nLog, nCkpt in
//
// https://sqlite.org/c3ref/wal_autocheckpoint.html
func (c *Conn) WALAutoCheckpoint(pages int) error {
rc := res_t(c.call("sqlite3_wal_autocheckpoint", stk_t(c.handle), stk_t(pages)))
rc := res_t(c.wrp.Xsqlite3_wal_autocheckpoint(int32(c.handle), int32(pages)))
return c.error(rc)
}
@@ -308,17 +308,18 @@ func (c *Conn) WALHook(cb func(db *Conn, schema string, pages int) error) {
if cb != nil {
enable = 1
}
c.call("sqlite3_wal_hook_go", stk_t(c.handle), stk_t(enable))
c.wrp.Xsqlite3_wal_hook_go(int32(c.handle), enable)
c.wal = cb
}
func walCallback(ctx context.Context, mod api.Module, _, pDB, zSchema ptr_t, pages int32) (rc res_t) {
if c, ok := ctx.Value(connKey{}).(*Conn); ok && c.handle == pDB && c.wal != nil {
schema := util.ReadString(mod, zSchema, _MAX_NAME)
func (e *env) Xgo_wal_hook(_, pDB, zSchema, pages int32) int32 {
if c, ok := e.DB.(*Conn); ok && c.handle == ptr_t(pDB) && c.wal != nil {
schema := e.ReadString(ptr_t(zSchema), _MAX_NAME)
err := c.wal(c, schema, int(pages))
_, rc = errorCode(err, ERROR)
_, rc := errorCode(err, ERROR)
return int32(rc)
}
return rc
return _OK
}
// AutoVacuumPages registers a autovacuum compaction amount callback.
@@ -327,30 +328,30 @@ func walCallback(ctx context.Context, mod api.Module, _, pDB, zSchema ptr_t, pag
func (c *Conn) AutoVacuumPages(cb func(schema string, dbPages, freePages, bytesPerPage uint) uint) error {
var funcPtr ptr_t
if cb != nil {
funcPtr = util.AddHandle(c.ctx, cb)
funcPtr = c.wrp.AddHandle(cb)
}
rc := res_t(c.call("sqlite3_autovacuum_pages_go", stk_t(c.handle), stk_t(funcPtr)))
rc := res_t(c.wrp.Xsqlite3_autovacuum_pages_go(int32(c.handle), int32(funcPtr)))
return c.error(rc)
}
func autoVacuumCallback(ctx context.Context, mod api.Module, pApp, zSchema ptr_t, nDbPage, nFreePage, nBytePerPage uint32) uint32 {
fn := util.GetHandle(ctx, pApp).(func(schema string, dbPages, freePages, bytesPerPage uint) uint)
schema := util.ReadString(mod, zSchema, _MAX_NAME)
return uint32(fn(schema, uint(nDbPage), uint(nFreePage), uint(nBytePerPage)))
func (e *env) Xgo_autovacuum_pages(pApp, zSchema, nDbPage, nFreePage, nBytePerPage int32) int32 {
fn := e.GetHandle(ptr_t(pApp)).(func(schema string, dbPages, freePages, bytesPerPage uint) uint)
schema := e.ReadString(ptr_t(zSchema), _MAX_NAME)
return int32(fn(schema, uint(uint32(nDbPage)), uint(uint32(nFreePage)), uint(uint32(nBytePerPage))))
}
// SoftHeapLimit imposes a soft limit on heap size.
//
// https://sqlite.org/c3ref/hard_heap_limit64.html
func (c *Conn) SoftHeapLimit(n int64) int64 {
return int64(c.call("sqlite3_soft_heap_limit64", stk_t(n)))
return c.wrp.Xsqlite3_soft_heap_limit64(n)
}
// HardHeapLimit imposes a hard limit on heap size.
//
// https://sqlite.org/c3ref/hard_heap_limit64.html
func (c *Conn) HardHeapLimit(n int64) int64 {
return int64(c.call("sqlite3_hard_heap_limit64", stk_t(n)))
return c.wrp.Xsqlite3_hard_heap_limit64(n)
}
// EnableChecksums enables checksums on a database.
@@ -378,7 +379,7 @@ func (c *Conn) EnableChecksums(schema string) error {
}
if r != 8 {
// Invalid value.
return util.ErrorString("sqlite3: reserve bytes must be 8, is: " + strconv.Itoa(r.(int)))
return errutil.ErrorString("sqlite3: reserve bytes must be 8, is: " + strconv.Itoa(r.(int)))
}
// VACUUM the database.
+124 -88
View File
@@ -11,9 +11,8 @@ import (
"strings"
"time"
"github.com/tetratelabs/wazero/api"
"github.com/ncruces/go-sqlite3/internal/util"
"github.com/ncruces/go-sqlite3/internal/errutil"
"github.com/ncruces/go-sqlite3/internal/sqlite3_wrap"
"github.com/ncruces/go-sqlite3/vfs"
)
@@ -22,7 +21,7 @@ import (
//
// https://sqlite.org/c3ref/sqlite3.html
type Conn struct {
*sqlite
wrp *sqlite3_wrap.Wrapper
interrupt context.Context
stmts []*Stmt
@@ -38,7 +37,7 @@ type Conn struct {
busy1st time.Time
busylst time.Time
arena arena
arena sqlite3_wrap.Arena
handle ptr_t
gosched uint8
}
@@ -69,8 +68,6 @@ func OpenFlags(filename string, flags OpenFlag) (*Conn, error) {
return newConn(context.Background(), filename, flags)
}
type connKey = util.ConnKey
func newConn(ctx context.Context, filename string, flags OpenFlag) (ret *Conn, _ error) {
err := ctx.Err()
if err != nil {
@@ -78,24 +75,23 @@ func newConn(ctx context.Context, filename string, flags OpenFlag) (ret *Conn, _
}
c := &Conn{interrupt: ctx}
c.sqlite, err = instantiateSQLite()
c.wrp, err = createWrapper(ctx)
if err != nil {
return nil, err
}
defer func() {
if ret == nil {
c.Close()
c.sqlite.close()
} else {
c.interrupt = context.Background()
}
}()
c.ctx = context.WithValue(c.ctx, connKey{}, c)
c.wrp.DB = c
if logger := defaultLogger.Load(); logger != nil {
c.ConfigLog(*logger)
}
c.arena = c.newArena()
c.arena = c.wrp.NewArena()
c.handle, err = c.openDB(filename, flags)
if err == nil {
err = initExtensions(c)
@@ -107,20 +103,20 @@ func newConn(ctx context.Context, filename string, flags OpenFlag) (ret *Conn, _
}
func (c *Conn) openDB(filename string, flags OpenFlag) (ptr_t, error) {
defer c.arena.mark()()
connPtr := c.arena.new(ptrlen)
namePtr := c.arena.string(filename)
defer c.arena.Mark()()
connPtr := c.arena.New(ptrlen)
namePtr := c.arena.String(filename)
flags |= OPEN_EXRESCODE
rc := res_t(c.call("sqlite3_open_v2", stk_t(namePtr), stk_t(connPtr), stk_t(flags), 0))
rc := res_t(c.wrp.Xsqlite3_open_v2(int32(namePtr), int32(connPtr), int32(flags), 0))
handle := util.Read32[ptr_t](c.mod, connPtr)
if err := c.sqlite.error(rc, handle); err != nil {
handle := ptr_t(c.wrp.Read32(connPtr))
if err := c.errorFor(handle, rc); err != nil {
c.closeDB(handle)
return 0, err
}
c.call("sqlite3_progress_handler_go", stk_t(handle), 1000)
c.wrp.Xsqlite3_progress_handler_go(int32(handle), 1000)
if flags|OPEN_URI != 0 && strings.HasPrefix(filename, "file:") {
var pragmas strings.Builder
if _, after, ok := strings.Cut(filename, "?"); ok {
@@ -132,9 +128,9 @@ func (c *Conn) openDB(filename string, flags OpenFlag) (ptr_t, error) {
}
}
if pragmas.Len() != 0 {
pragmaPtr := c.arena.string(pragmas.String())
rc := res_t(c.call("sqlite3_exec", stk_t(handle), stk_t(pragmaPtr), 0, 0, 0))
if err := c.sqlite.error(rc, handle, pragmas.String()); err != nil {
pragmaPtr := c.arena.String(pragmas.String())
rc := res_t(c.wrp.Xsqlite3_exec(int32(handle), int32(pragmaPtr), 0, 0, 0))
if err := c.errorFor(handle, rc, pragmas.String()); err != nil {
err = fmt.Errorf("sqlite3: invalid _pragma: %w", err)
c.closeDB(handle)
return 0, err
@@ -145,8 +141,8 @@ func (c *Conn) openDB(filename string, flags OpenFlag) (ptr_t, error) {
}
func (c *Conn) closeDB(handle ptr_t) {
rc := res_t(c.call("sqlite3_close_v2", stk_t(handle)))
if err := c.sqlite.error(rc, handle); err != nil {
rc := res_t(c.wrp.Xsqlite3_close_v2(int32(handle)))
if err := c.errorFor(handle, rc); err != nil {
panic(err)
}
}
@@ -165,13 +161,13 @@ func (c *Conn) Close() error {
return nil
}
rc := res_t(c.call("sqlite3_close", stk_t(c.handle)))
rc := res_t(c.wrp.Xsqlite3_close(int32(c.handle)))
if err := c.error(rc); err != nil {
return err
}
c.handle = 0
return c.close()
return c.wrp.Close()
}
// Exec is a convenience function that allows an application to run
@@ -186,9 +182,9 @@ func (c *Conn) Exec(sql string) error {
}
func (c *Conn) exec(sql string) error {
defer c.arena.mark()()
textPtr := c.arena.string(sql)
rc := res_t(c.call("sqlite3_exec", stk_t(c.handle), stk_t(textPtr), 0, 0, 0))
defer c.arena.Mark()()
textPtr := c.arena.String(sql)
rc := res_t(c.wrp.Xsqlite3_exec(int32(c.handle), int32(textPtr), 0, 0, 0))
return c.error(rc, sql)
}
@@ -211,18 +207,18 @@ func (c *Conn) PrepareFlags(sql string, flags PrepareFlag) (stmt *Stmt, tail str
return nil, "", INTERRUPT
}
defer c.arena.mark()()
stmtPtr := c.arena.new(ptrlen)
tailPtr := c.arena.new(ptrlen)
textPtr := c.arena.string(sql)
defer c.arena.Mark()()
stmtPtr := c.arena.New(ptrlen)
tailPtr := c.arena.New(ptrlen)
textPtr := c.arena.String(sql)
rc := res_t(c.call("sqlite3_prepare_v3", stk_t(c.handle),
stk_t(textPtr), stk_t(len(sql)+1), stk_t(flags),
stk_t(stmtPtr), stk_t(tailPtr)))
rc := res_t(c.wrp.Xsqlite3_prepare_v3(int32(c.handle),
int32(textPtr), int32(len(sql)+1), int32(flags),
int32(stmtPtr), int32(tailPtr)))
stmt = &Stmt{c: c, sql: sql}
stmt.handle = util.Read32[ptr_t](c.mod, stmtPtr)
if sql := sql[util.Read32[ptr_t](c.mod, tailPtr)-textPtr:]; sql != "" {
stmt.handle = ptr_t(c.wrp.Read32(stmtPtr))
if sql := sql[ptr_t(c.wrp.Read32(tailPtr))-textPtr:]; sql != "" {
tail = sql
}
@@ -240,11 +236,11 @@ func (c *Conn) PrepareFlags(sql string, flags PrepareFlag) (stmt *Stmt, tail str
//
// https://sqlite.org/c3ref/db_name.html
func (c *Conn) DBName(n int) string {
ptr := ptr_t(c.call("sqlite3_db_name", stk_t(c.handle), stk_t(n)))
ptr := ptr_t(c.wrp.Xsqlite3_db_name(int32(c.handle), int32(n)))
if ptr == 0 {
return ""
}
return util.ReadString(c.mod, ptr, _MAX_NAME)
return c.wrp.ReadString(ptr, _MAX_NAME)
}
// Filename returns the filename for a database.
@@ -253,11 +249,11 @@ func (c *Conn) DBName(n int) string {
func (c *Conn) Filename(schema string) *vfs.Filename {
var ptr ptr_t
if schema != "" {
defer c.arena.mark()()
ptr = c.arena.string(schema)
defer c.arena.Mark()()
ptr = c.arena.String(schema)
}
ptr = ptr_t(c.call("sqlite3_db_filename", stk_t(c.handle), stk_t(ptr)))
return vfs.GetFilename(c.ctx, c.mod, ptr, vfs.OPEN_MAIN_DB)
ptr = ptr_t(c.wrp.Xsqlite3_db_filename(int32(c.handle), int32(ptr)))
return vfs.GetFilename(c.wrp, ptr, vfs.OPEN_MAIN_DB)
}
// ReadOnly determines if a database is read-only.
@@ -266,10 +262,10 @@ func (c *Conn) Filename(schema string) *vfs.Filename {
func (c *Conn) ReadOnly(schema string) (ro bool, ok bool) {
var ptr ptr_t
if schema != "" {
defer c.arena.mark()()
ptr = c.arena.string(schema)
defer c.arena.Mark()()
ptr = c.arena.String(schema)
}
b := int32(c.call("sqlite3_db_readonly", stk_t(c.handle), stk_t(ptr)))
b := c.wrp.Xsqlite3_db_readonly(int32(c.handle), int32(ptr))
return b > 0, b < 0
}
@@ -277,7 +273,7 @@ func (c *Conn) ReadOnly(schema string) (ro bool, ok bool) {
//
// https://sqlite.org/c3ref/get_autocommit.html
func (c *Conn) GetAutocommit() bool {
b := int32(c.call("sqlite3_get_autocommit", stk_t(c.handle)))
b := c.wrp.Xsqlite3_get_autocommit(int32(c.handle))
return b != 0
}
@@ -286,7 +282,7 @@ func (c *Conn) GetAutocommit() bool {
//
// https://sqlite.org/c3ref/last_insert_rowid.html
func (c *Conn) LastInsertRowID() int64 {
return int64(c.call("sqlite3_last_insert_rowid", stk_t(c.handle)))
return c.wrp.Xsqlite3_last_insert_rowid(int32(c.handle))
}
// SetLastInsertRowID allows the application to set the value returned by
@@ -294,7 +290,7 @@ func (c *Conn) LastInsertRowID() int64 {
//
// https://sqlite.org/c3ref/set_last_insert_rowid.html
func (c *Conn) SetLastInsertRowID(id int64) {
c.call("sqlite3_set_last_insert_rowid", stk_t(c.handle), stk_t(id))
c.wrp.Xsqlite3_set_last_insert_rowid(int32(c.handle), id)
}
// Changes returns the number of rows modified, inserted or deleted
@@ -303,7 +299,7 @@ func (c *Conn) SetLastInsertRowID(id int64) {
//
// https://sqlite.org/c3ref/changes.html
func (c *Conn) Changes() int64 {
return int64(c.call("sqlite3_changes64", stk_t(c.handle)))
return c.wrp.Xsqlite3_changes64(int32(c.handle))
}
// TotalChanges returns the number of rows modified, inserted or deleted
@@ -312,14 +308,14 @@ func (c *Conn) Changes() int64 {
//
// https://sqlite.org/c3ref/total_changes.html
func (c *Conn) TotalChanges() int64 {
return int64(c.call("sqlite3_total_changes64", stk_t(c.handle)))
return c.wrp.Xsqlite3_total_changes64(int32(c.handle))
}
// ReleaseMemory frees memory used by a database connection.
//
// https://sqlite.org/c3ref/db_release_memory.html
func (c *Conn) ReleaseMemory() error {
rc := res_t(c.call("sqlite3_db_release_memory", stk_t(c.handle)))
rc := res_t(c.wrp.Xsqlite3_db_release_memory(int32(c.handle)))
return c.error(rc)
}
@@ -351,8 +347,8 @@ func (c *Conn) SetInterrupt(ctx context.Context) (old context.Context) {
return old
}
func progressCallback(ctx context.Context, mod api.Module, _ ptr_t) (interrupt int32) {
if c, ok := ctx.Value(connKey{}).(*Conn); ok {
func (e *env) Xgo_progress_handler(_ int32) (interrupt int32) {
if c, ok := e.DB.(*Conn); ok {
if c.gosched++; c.gosched%16 == 0 {
runtime.Gosched()
}
@@ -368,13 +364,13 @@ func progressCallback(ctx context.Context, mod api.Module, _ ptr_t) (interrupt i
// https://sqlite.org/c3ref/busy_timeout.html
func (c *Conn) BusyTimeout(timeout time.Duration) error {
ms := min((timeout+time.Millisecond-1)/time.Millisecond, math.MaxInt32)
rc := res_t(c.call("sqlite3_busy_timeout", stk_t(c.handle), stk_t(ms)))
rc := res_t(c.wrp.Xsqlite3_busy_timeout(int32(c.handle), int32(ms)))
return c.error(rc)
}
func timeoutCallback(ctx context.Context, mod api.Module, count, tmout int32) (retry int32) {
func (e *env) Xgo_busy_timeout(count, tmout int32) (retry int32) {
// https://fractaledmind.github.io/2024/04/15/sqlite-on-rails-the-how-and-why-of-optimal-performance/
if c, ok := ctx.Value(connKey{}).(*Conn); ok && c.interrupt.Err() == nil {
if c, ok := e.DB.(*Conn); ok && c.interrupt.Err() == nil {
switch {
case count == 0:
c.busy1st = time.Now()
@@ -399,7 +395,7 @@ func (c *Conn) BusyHandler(cb func(ctx context.Context, count int) (retry bool))
if cb != nil {
enable = 1
}
rc := res_t(c.call("sqlite3_busy_handler_go", stk_t(c.handle), stk_t(enable)))
rc := res_t(c.wrp.Xsqlite3_busy_handler_go(int32(c.handle), enable))
if err := c.error(rc); err != nil {
return err
}
@@ -407,8 +403,8 @@ func (c *Conn) BusyHandler(cb func(ctx context.Context, count int) (retry bool))
return nil
}
func busyCallback(ctx context.Context, mod api.Module, pDB ptr_t, count int32) (retry int32) {
if c, ok := ctx.Value(connKey{}).(*Conn); ok && c.handle == pDB && c.busy != nil {
func (e *env) Xgo_busy_handler(pDB, count int32) (retry int32) {
if c, ok := e.DB.(*Conn); ok && c.handle == ptr_t(pDB) && c.busy != nil {
if interrupt := c.interrupt; interrupt.Err() == nil &&
c.busy(interrupt, int(count)) {
retry = 1
@@ -421,20 +417,20 @@ func busyCallback(ctx context.Context, mod api.Module, pDB ptr_t, count int32) (
//
// https://sqlite.org/c3ref/db_status.html
func (c *Conn) Status(op DBStatus, reset bool) (current, highwater int64, err error) {
defer c.arena.mark()()
hiPtr := c.arena.new(8)
curPtr := c.arena.new(8)
defer c.arena.Mark()()
hiPtr := c.arena.New(8)
curPtr := c.arena.New(8)
var i int32
if reset {
i = 1
}
rc := res_t(c.call("sqlite3_db_status64", stk_t(c.handle),
stk_t(op), stk_t(curPtr), stk_t(hiPtr), stk_t(i)))
rc := res_t(c.wrp.Xsqlite3_db_status64(int32(c.handle),
int32(op), int32(curPtr), int32(hiPtr), i))
if err = c.error(rc); err == nil {
current = util.Read64[int64](c.mod, curPtr)
highwater = util.Read64[int64](c.mod, hiPtr)
current = int64(c.wrp.Read64(curPtr))
highwater = int64(c.wrp.Read64(hiPtr))
}
return
}
@@ -443,7 +439,7 @@ func (c *Conn) Status(op DBStatus, reset bool) (current, highwater int64, err er
//
// https://sqlite.org/c3ref/table_column_metadata.html
func (c *Conn) TableColumnMetadata(schema, table, column string) (declType, collSeq string, notNull, primaryKey, autoInc bool, err error) {
defer c.arena.mark()()
defer c.arena.Mark()()
var (
declTypePtr ptr_t
collSeqPtr ptr_t
@@ -454,38 +450,78 @@ func (c *Conn) TableColumnMetadata(schema, table, column string) (declType, coll
schemaPtr ptr_t
)
if column != "" {
declTypePtr = c.arena.new(ptrlen)
collSeqPtr = c.arena.new(ptrlen)
notNullPtr = c.arena.new(ptrlen)
primaryKeyPtr = c.arena.new(ptrlen)
autoIncPtr = c.arena.new(ptrlen)
columnPtr = c.arena.string(column)
declTypePtr = c.arena.New(ptrlen)
collSeqPtr = c.arena.New(ptrlen)
notNullPtr = c.arena.New(ptrlen)
primaryKeyPtr = c.arena.New(ptrlen)
autoIncPtr = c.arena.New(ptrlen)
columnPtr = c.arena.String(column)
}
if schema != "" {
schemaPtr = c.arena.string(schema)
schemaPtr = c.arena.String(schema)
}
tablePtr := c.arena.string(table)
tablePtr := c.arena.String(table)
rc := res_t(c.call("sqlite3_table_column_metadata", stk_t(c.handle),
stk_t(schemaPtr), stk_t(tablePtr), stk_t(columnPtr),
stk_t(declTypePtr), stk_t(collSeqPtr),
stk_t(notNullPtr), stk_t(primaryKeyPtr), stk_t(autoIncPtr)))
rc := res_t(c.wrp.Xsqlite3_table_column_metadata(int32(c.handle),
int32(schemaPtr), int32(tablePtr), int32(columnPtr),
int32(declTypePtr), int32(collSeqPtr),
int32(notNullPtr), int32(primaryKeyPtr), int32(autoIncPtr)))
if err = c.error(rc); err == nil && column != "" {
if ptr := util.Read32[ptr_t](c.mod, declTypePtr); ptr != 0 {
declType = util.ReadString(c.mod, ptr, _MAX_NAME)
if ptr := ptr_t(c.wrp.Read32(declTypePtr)); ptr != 0 {
declType = c.wrp.ReadString(ptr, _MAX_NAME)
}
if ptr := util.Read32[ptr_t](c.mod, collSeqPtr); ptr != 0 {
collSeq = util.ReadString(c.mod, ptr, _MAX_NAME)
if ptr := ptr_t(c.wrp.Read32(collSeqPtr)); ptr != 0 {
collSeq = c.wrp.ReadString(ptr, _MAX_NAME)
}
notNull = util.ReadBool(c.mod, notNullPtr)
autoInc = util.ReadBool(c.mod, autoIncPtr)
primaryKey = util.ReadBool(c.mod, primaryKeyPtr)
notNull = c.wrp.ReadBool(notNullPtr)
autoInc = c.wrp.ReadBool(autoIncPtr)
primaryKey = c.wrp.ReadBool(primaryKeyPtr)
}
return
}
func (c *Conn) error(rc res_t, sql ...string) error {
return c.sqlite.error(rc, c.handle, sql...)
return c.errorFor(c.handle, rc, sql...)
}
func (c *Conn) errorFor(handle ptr_t, rc res_t, sql ...string) error {
if rc == _OK {
return nil
}
if ErrorCode(rc) == NOMEM || xErrorCode(rc) == IOERR_NOMEM {
panic(errutil.OOMErr)
}
var msg, query string
if handle != 0 {
if ptr := ptr_t(c.wrp.Xsqlite3_errmsg(int32(handle))); ptr != 0 {
msg = c.wrp.ReadString(ptr, _MAX_LENGTH)
msg = strings.TrimPrefix(msg, "sqlite3: ")
msg = strings.TrimPrefix(msg, sqlite3_wrap.ErrorCodeString(rc)[len("sqlite3: "):])
msg = strings.TrimPrefix(msg, ": ")
if msg == "" || msg == "not an error" {
msg = ""
}
}
if len(sql) != 0 {
if i := int32(c.wrp.Xsqlite3_error_offset(int32(handle))); i != -1 {
query = sql[0][i:]
}
}
}
var sys error
switch ErrorCode(rc) {
case CANTOPEN, IOERR:
sys = c.wrp.SysError
}
if sys != nil || msg != "" || query != "" {
return &Error{code: rc, sys: sys, msg: msg, sql: query}
}
return xErrorCode(rc)
}
// Stmts returns an iterator for the prepared statements
+10 -7
View File
@@ -3,7 +3,7 @@ package sqlite3
import (
"strconv"
"github.com/ncruces/go-sqlite3/internal/util"
"github.com/ncruces/go-sqlite3/internal/sqlite3_wrap"
)
const (
@@ -15,14 +15,13 @@ const (
_MAX_LENGTH = 1e9
_MAX_SQL_LENGTH = 1e9
ptrlen = util.PtrLen
intlen = util.IntLen
ptrlen = sqlite3_wrap.PtrLen
intlen = sqlite3_wrap.IntLen
)
type (
stk_t = util.Stk_t
ptr_t = util.Ptr_t
res_t = util.Res_t
ptr_t = sqlite3_wrap.Ptr_t
res_t = sqlite3_wrap.Res_t
)
// ErrorCode is a result code that [Error.Code] might return.
@@ -173,7 +172,7 @@ const (
// PrepareFlag is a flag that can be passed to [Conn.PrepareFlags].
//
// https://sqlite.org/c3ref/c_prepare_normalize.html
// https://sqlite.org/c3ref/c_prepare_dont_log.html
type PrepareFlag uint32
const (
@@ -181,6 +180,7 @@ 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
@@ -267,6 +267,7 @@ 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
)
@@ -280,6 +281,7 @@ 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
@@ -307,6 +309,7 @@ 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
+43 -50
View File
@@ -2,10 +2,9 @@ package sqlite3
import (
"errors"
"math"
"time"
"github.com/ncruces/go-sqlite3/internal/util"
"github.com/ncruces/go-sqlite3/internal/errutil"
)
// Context is the context in which an SQL function executes.
@@ -30,16 +29,16 @@ func (ctx Context) Conn() *Conn {
//
// https://sqlite.org/c3ref/get_auxdata.html
func (ctx Context) SetAuxData(n int, data any) {
ptr := util.AddHandle(ctx.c.ctx, data)
ctx.c.call("sqlite3_set_auxdata_go", stk_t(ctx.handle), stk_t(n), stk_t(ptr))
ptr := ctx.c.wrp.AddHandle(data)
ctx.c.wrp.Xsqlite3_set_auxdata_go(int32(ctx.handle), int32(n), int32(ptr))
}
// GetAuxData returns metadata for argument n of the function.
//
// https://sqlite.org/c3ref/get_auxdata.html
func (ctx Context) GetAuxData(n int) any {
ptr := ptr_t(ctx.c.call("sqlite3_get_auxdata", stk_t(ctx.handle), stk_t(n)))
return util.GetHandle(ctx.c.ctx, ptr)
ptr := ptr_t(ctx.c.wrp.Xsqlite3_get_auxdata(int32(ctx.handle), int32(n)))
return ctx.c.wrp.GetHandle(ptr)
}
// ResultBool sets the result of the function to a bool.
@@ -66,38 +65,32 @@ func (ctx Context) ResultInt(value int) {
//
// https://sqlite.org/c3ref/result_blob.html
func (ctx Context) ResultInt64(value int64) {
ctx.c.call("sqlite3_result_int64",
stk_t(ctx.handle), stk_t(value))
ctx.c.wrp.Xsqlite3_result_int64(
int32(ctx.handle), value)
}
// ResultFloat sets the result of the function to a float64.
//
// https://sqlite.org/c3ref/result_blob.html
func (ctx Context) ResultFloat(value float64) {
ctx.c.call("sqlite3_result_double",
stk_t(ctx.handle), stk_t(math.Float64bits(value)))
ctx.c.wrp.Xsqlite3_result_double(
int32(ctx.handle), value)
}
// ResultText sets the result of the function to a string.
//
// https://sqlite.org/c3ref/result_blob.html
func (ctx Context) ResultText(value string) {
ptr := ctx.c.newString(value)
ctx.c.call("sqlite3_result_text_go",
stk_t(ctx.handle), stk_t(ptr), stk_t(len(value)))
ptr := ctx.c.wrp.NewString(value)
ctx.c.wrp.Xsqlite3_result_text_go(
int32(ctx.handle), int32(ptr), int64(len(value)))
}
// ResultRawText sets the text result of the function to a []byte.
//
// https://sqlite.org/c3ref/result_blob.html
func (ctx Context) ResultRawText(value []byte) {
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)))
ctx.ResultText(string(value)) // does not escape
}
// ResultBlob sets the result of the function to a []byte.
@@ -108,25 +101,25 @@ func (ctx Context) ResultBlob(value []byte) {
ctx.ResultZeroBlob(0)
return
}
ptr := ctx.c.newBytes(value)
ctx.c.call("sqlite3_result_blob_go",
stk_t(ctx.handle), stk_t(ptr), stk_t(len(value)))
ptr := ctx.c.wrp.NewBytes(value)
ctx.c.wrp.Xsqlite3_result_blob_go(
int32(ctx.handle), int32(ptr), int64(len(value)))
}
// ResultZeroBlob sets the result of the function to a zero-filled, length n BLOB.
//
// https://sqlite.org/c3ref/result_blob.html
func (ctx Context) ResultZeroBlob(n int64) {
ctx.c.call("sqlite3_result_zeroblob64",
stk_t(ctx.handle), stk_t(n))
ctx.c.wrp.Xsqlite3_result_zeroblob64(
int32(ctx.handle), n)
}
// ResultNull sets the result of the function to NULL.
//
// https://sqlite.org/c3ref/result_blob.html
func (ctx Context) ResultNull() {
ctx.c.call("sqlite3_result_null",
stk_t(ctx.handle))
ctx.c.wrp.Xsqlite3_result_null(
int32(ctx.handle))
}
// ResultTime sets the result of the function to a [time.Time].
@@ -146,19 +139,19 @@ func (ctx Context) ResultTime(value time.Time, format TimeFormat) {
case float64:
ctx.ResultFloat(v)
default:
panic(util.AssertErr())
panic(errutil.AssertErr())
}
}
func (ctx Context) resultRFC3339Nano(value time.Time) {
const maxlen = int64(len(time.RFC3339Nano)) + 5
ptr := ctx.c.new(maxlen)
buf := util.View(ctx.c.mod, ptr, maxlen)
const maxlen = 48
ptr := ctx.c.wrp.New(maxlen)
buf := ctx.c.wrp.Bytes(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)))
ctx.c.wrp.Xsqlite3_result_text_go(
int32(ctx.handle), int32(ptr), int64(len(buf)))
}
// ResultPointer sets the result of the function to NULL, just like [Context.ResultNull],
@@ -167,9 +160,9 @@ func (ctx Context) resultRFC3339Nano(value time.Time) {
//
// https://sqlite.org/c3ref/result_blob.html
func (ctx Context) ResultPointer(ptr any) {
valPtr := util.AddHandle(ctx.c.ctx, ptr)
ctx.c.call("sqlite3_result_pointer_go",
stk_t(ctx.handle), stk_t(valPtr))
valPtr := ctx.c.wrp.AddHandle(ptr)
ctx.c.wrp.Xsqlite3_result_pointer_go(
int32(ctx.handle), int32(valPtr))
}
// ResultValue sets the result of the function to a copy of [Value].
@@ -180,8 +173,8 @@ func (ctx Context) ResultValue(value Value) {
ctx.ResultError(MISUSE)
return
}
ctx.c.call("sqlite3_result_value",
stk_t(ctx.handle), stk_t(value.handle))
ctx.c.wrp.Xsqlite3_result_value(
int32(ctx.handle), int32(value.handle))
}
// ResultError sets the result of the function an error.
@@ -189,25 +182,25 @@ func (ctx Context) ResultValue(value Value) {
// https://sqlite.org/c3ref/result_blob.html
func (ctx Context) ResultError(err error) {
if errors.Is(err, NOMEM) {
ctx.c.call("sqlite3_result_error_nomem", stk_t(ctx.handle))
ctx.c.wrp.Xsqlite3_result_error_nomem(int32(ctx.handle))
return
}
if errors.Is(err, TOOBIG) {
ctx.c.call("sqlite3_result_error_toobig", stk_t(ctx.handle))
ctx.c.wrp.Xsqlite3_result_error_toobig(int32(ctx.handle))
return
}
msg, code := errorCode(err, ERROR)
if msg != "" {
defer ctx.c.arena.mark()()
ptr := ctx.c.arena.string(msg)
ctx.c.call("sqlite3_result_error",
stk_t(ctx.handle), stk_t(ptr), stk_t(len(msg)))
defer ctx.c.arena.Mark()()
ptr := ctx.c.arena.String(msg)
ctx.c.wrp.Xsqlite3_result_error(
int32(ctx.handle), int32(ptr), int32(len(msg)))
}
if code != res_t(ERROR) {
ctx.c.call("sqlite3_result_error_code",
stk_t(ctx.handle), stk_t(code))
ctx.c.wrp.Xsqlite3_result_error_code(
int32(ctx.handle), int32(code))
}
}
@@ -215,8 +208,8 @@ func (ctx Context) ResultError(err error) {
//
// https://sqlite.org/c3ref/result_subtype.html
func (ctx Context) ResultSubtype(t uint) {
ctx.c.call("sqlite3_result_subtype",
stk_t(ctx.handle), stk_t(uint32(t)))
ctx.c.wrp.Xsqlite3_result_subtype(
int32(ctx.handle), int32(t))
}
// VTabNoChange may return true if a column is being fetched as part
@@ -224,6 +217,6 @@ func (ctx Context) ResultSubtype(t uint) {
//
// https://sqlite.org/c3ref/vtab_nochange.html
func (ctx Context) VTabNoChange() bool {
b := int32(ctx.c.call("sqlite3_vtab_nochange", stk_t(ctx.handle)))
b := int32(ctx.c.wrp.Xsqlite3_vtab_nochange(int32(ctx.handle)))
return b != 0
}
+19 -61
View File
@@ -1,10 +1,8 @@
// Package driver provides a database/sql driver for SQLite.
//
// Importing package driver registers a [database/sql] driver named "sqlite3".
// You may also need to import package embed.
//
// import _ "github.com/ncruces/go-sqlite3/driver"
// import _ "github.com/ncruces/go-sqlite3/embed"
//
// The data source name for "sqlite3" databases can be a filename or a "file:" [URI].
//
@@ -110,6 +108,7 @@ import (
"unsafe"
"github.com/ncruces/go-sqlite3"
"github.com/ncruces/go-sqlite3/internal/errutil"
"github.com/ncruces/go-sqlite3/internal/util"
)
@@ -343,7 +342,7 @@ func (c *conn) BeginTx(ctx context.Context, opts driver.TxOptions) (driver.Tx, e
var txLock string
switch opts.Isolation {
default:
return nil, util.IsolationErr
return nil, errutil.IsolationErr
case driver.IsolationLevel(sql.LevelLinearizable):
txLock = "exclusive"
case driver.IsolationLevel(sql.LevelSerializable):
@@ -405,7 +404,7 @@ func (c *conn) PrepareContext(ctx context.Context, query string) (driver.Stmt, e
}
if notWhitespace(tail) {
s.Close()
return nil, util.TailErr
return nil, errutil.TailErr
}
return &stmt{Stmt: s, tmRead: c.tmRead, tmWrite: c.tmWrite, inputs: -2}, nil
}
@@ -440,22 +439,6 @@ 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
@@ -565,7 +548,7 @@ func (s *stmt) setupBindings(args []driver.NamedValue) (err error) {
case nil:
err = s.Stmt.BindNull(id)
default:
panic(util.AssertErr())
panic(errutil.AssertErr())
}
if err != nil {
return err
@@ -669,14 +652,12 @@ 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 {
@@ -716,17 +697,23 @@ func (r *rows) loadColumnMetadata() {
types := make([]string, count)
scans := make([]scantype, count)
for i := range types {
var notnull bool
if col := r.Stmt.ColumnOriginName(i); col != "" {
types[i], _, notnull, _, _, _ = c.TableColumnMetadata(
var declType string
var notNull, autoInc bool
if column := r.Stmt.ColumnOriginName(i); column != "" {
declType, _, notNull, _, autoInc, _ = c.TableColumnMetadata(
r.Stmt.ColumnDatabaseName(i),
r.Stmt.ColumnTableName(i),
col)
types[i] = strings.ToUpper(types[i])
scans[i] = scanFromDecl(types[i])
if notnull {
scans[i] |= _NOT_NULL
}
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
}
}
r.types = types
@@ -796,7 +783,6 @@ 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)
@@ -846,33 +832,5 @@ 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
}
+41 -64
View File
@@ -8,14 +8,12 @@ import (
"math"
"net/url"
"reflect"
"strings"
"testing"
"time"
"github.com/ncruces/go-sqlite3"
_ "github.com/ncruces/go-sqlite3/embed"
_ "github.com/ncruces/go-sqlite3/internal/testcfg"
"github.com/ncruces/go-sqlite3/internal/util"
"github.com/ncruces/go-sqlite3/internal/errutil"
"github.com/ncruces/go-sqlite3/internal/testcfg"
"github.com/ncruces/go-sqlite3/vfs/memdb"
)
@@ -34,13 +32,14 @@ func Test_Open_error(t *testing.T) {
func Test_Open_dir(t *testing.T) {
t.Parallel()
ctx := testcfg.Context(t)
db, err := Open(".")
if err != nil {
t.Fatal(err)
}
defer db.Close()
_, err = db.Conn(context.TODO())
_, err = db.Conn(ctx)
if err == nil {
t.Fatal("want error")
}
@@ -55,6 +54,7 @@ func Test_Open_pragma(t *testing.T) {
"_pragma": {"busy_timeout(1000)"},
})
ctx := testcfg.Context(t)
db, err := Open(dsn)
if err != nil {
t.Fatal(err)
@@ -62,7 +62,7 @@ func Test_Open_pragma(t *testing.T) {
defer db.Close()
var timeout int
err = db.QueryRow(`PRAGMA busy_timeout`).Scan(&timeout)
err = db.QueryRowContext(ctx, `PRAGMA busy_timeout`).Scan(&timeout)
if err != nil {
t.Fatal(err)
}
@@ -77,13 +77,14 @@ func Test_Open_pragma_invalid(t *testing.T) {
"_pragma": {"busy_timeout 1000"},
})
ctx := testcfg.Context(t)
db, err := Open(dsn)
if err != nil {
t.Fatal(err)
}
defer db.Close()
_, err = db.Conn(context.TODO())
_, err = db.Conn(ctx)
if err == nil {
t.Fatal("want error")
}
@@ -106,18 +107,19 @@ func Test_Open_txLock(t *testing.T) {
"_pragma": {"busy_timeout(1000)"},
})
ctx := testcfg.Context(t)
db, err := Open(dsn)
if err != nil {
t.Fatal(err)
}
defer db.Close()
tx1, err := db.Begin()
tx1, err := db.BeginTx(ctx, nil)
if err != nil {
t.Fatal(err)
}
_, err = db.Begin()
_, err = db.BeginTx(ctx, nil)
if err == nil {
t.Error("want error")
}
@@ -157,23 +159,24 @@ func Test_BeginTx(t *testing.T) {
"_pragma": {"busy_timeout(0)"},
})
ctx := testcfg.Context(t)
db, err := Open(dsn)
if err != nil {
t.Fatal(err)
}
defer db.Close()
_, err = db.BeginTx(t.Context(), &sql.TxOptions{Isolation: sql.LevelReadCommitted})
if err.Error() != string(util.IsolationErr) {
_, err = db.BeginTx(ctx, &sql.TxOptions{Isolation: sql.LevelReadCommitted})
if err.Error() != string(errutil.IsolationErr) {
t.Error("want isolationErr")
}
tx1, err := db.BeginTx(t.Context(), &sql.TxOptions{ReadOnly: true})
tx1, err := db.BeginTx(ctx, &sql.TxOptions{ReadOnly: true})
if err != nil {
t.Fatal(err)
}
tx2, err := db.BeginTx(t.Context(), &sql.TxOptions{ReadOnly: true})
tx2, err := db.BeginTx(ctx, &sql.TxOptions{ReadOnly: true})
if err != nil {
t.Fatal(err)
}
@@ -201,13 +204,14 @@ func Test_nested_context(t *testing.T) {
t.Parallel()
dsn := memdb.TestDB(t)
ctx := testcfg.Context(t)
db, err := Open(dsn)
if err != nil {
t.Fatal(err)
}
defer db.Close()
tx, err := db.Begin()
tx, err := db.BeginTx(ctx, nil)
if err != nil {
t.Fatal(err)
}
@@ -234,7 +238,7 @@ func Test_nested_context(t *testing.T) {
want(outer, 0)
ctx, cancel := context.WithCancel(t.Context())
ctx, cancel := context.WithCancel(ctx)
defer cancel()
inner, err := tx.QueryContext(ctx, `SELECT value FROM generate_series(0)`)
@@ -259,6 +263,7 @@ func Test_Prepare(t *testing.T) {
t.Parallel()
dsn := memdb.TestDB(t)
ctx := testcfg.Context(t)
db, err := Open(dsn)
if err != nil {
t.Fatal(err)
@@ -266,7 +271,7 @@ func Test_Prepare(t *testing.T) {
defer db.Close()
var serr *sqlite3.Error
_, err = db.Prepare(`SELECT`)
_, err = db.PrepareContext(ctx, `SELECT`)
if err == nil {
t.Error("want error")
}
@@ -280,18 +285,18 @@ func Test_Prepare(t *testing.T) {
t.Error("got message:", got)
}
_, err = db.Prepare(`SELECT 1; `)
_, err = db.PrepareContext(ctx, `SELECT 1; `)
if err != nil {
t.Error(err)
}
_, err = db.Prepare(`SELECT 1; SELECT`)
if err.Error() != string(util.TailErr) {
_, err = db.PrepareContext(ctx, `SELECT 1; SELECT`)
if err.Error() != string(errutil.TailErr) {
t.Error("want tailErr")
}
_, err = db.Prepare(`SELECT 1; SELECT 2`)
if err.Error() != string(util.TailErr) {
_, err = db.PrepareContext(ctx, `SELECT 1; SELECT 2`)
if err.Error() != string(errutil.TailErr) {
t.Error("want tailErr")
}
}
@@ -300,26 +305,27 @@ func Test_QueryRow_named(t *testing.T) {
t.Parallel()
dsn := memdb.TestDB(t)
ctx := testcfg.Context(t)
db, err := Open(dsn)
if err != nil {
t.Fatal(err)
}
defer db.Close()
conn, err := db.Conn(t.Context())
conn, err := db.Conn(ctx)
if err != nil {
t.Fatal(err)
}
defer conn.Close()
stmt, err := conn.PrepareContext(t.Context(), `SELECT ?, ?5, :AAA, @AAA, $AAA`)
stmt, err := conn.PrepareContext(ctx, `SELECT ?, ?5, :AAA, @AAA, $AAA`)
if err != nil {
t.Fatal(err)
}
defer stmt.Close()
date := time.Now()
row := stmt.QueryRow(true, sql.Named("AAA", math.Pi), nil /*3*/, nil /*4*/, date /*5*/)
row := stmt.QueryRowContext(ctx, true, sql.Named("AAA", math.Pi), nil /*3*/, nil /*4*/, date /*5*/)
var first bool
var fifth time.Time
@@ -350,13 +356,14 @@ func Test_QueryRow_blob_null(t *testing.T) {
t.Parallel()
dsn := memdb.TestDB(t)
ctx := testcfg.Context(t)
db, err := Open(dsn)
if err != nil {
t.Fatal(err)
}
defer db.Close()
rows, err := db.Query(`
rows, err := db.QueryContext(ctx, `
SELECT NULL UNION ALL
SELECT x'cafe' UNION ALL
SELECT x'babe' UNION ALL
@@ -389,6 +396,7 @@ func Test_time(t *testing.T) {
"_timefmt": {fmt},
})
ctx := testcfg.Context(t)
db, err := Open(dsn)
if err != nil {
t.Fatal(err)
@@ -397,18 +405,18 @@ func Test_time(t *testing.T) {
twosday := time.Date(2022, 2, 22, 22, 22, 22, 0, time.UTC)
_, err = db.Exec(`CREATE TABLE test (at DATETIME)`)
_, err = db.ExecContext(ctx, `CREATE TABLE test (at DATETIME)`)
if err != nil {
t.Fatal(err)
}
_, err = db.Exec(`INSERT INTO test VALUES (?)`, twosday)
_, err = db.ExecContext(ctx, `INSERT INTO test VALUES (?)`, twosday)
if err != nil {
t.Fatal(err)
}
var got time.Time
err = db.QueryRow(`SELECT * FROM test`).Scan(&got)
err = db.QueryRowContext(ctx, `SELECT * FROM test`).Scan(&got)
if err != nil {
t.Fatal(err)
}
@@ -434,13 +442,14 @@ func Test_ColumnType_ScanType(t *testing.T) {
t.Parallel()
dsn := memdb.TestDB(t)
ctx := testcfg.Context(t)
db, err := Open(dsn)
if err != nil {
t.Fatal(err)
}
defer db.Close()
_, err = db.Exec(`
_, err = db.ExecContext(ctx, `
CREATE TABLE test (
col_int INTEGER,
col_real REAL,
@@ -465,7 +474,7 @@ func Test_ColumnType_ScanType(t *testing.T) {
t.Fatal(err)
}
rows, err := db.Query(`SELECT * FROM test`)
rows, err := db.QueryContext(ctx, `SELECT * FROM test`)
if err != nil {
t.Fatal(err)
}
@@ -521,40 +530,8 @@ 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) {
ctx := testcfg.Context(b)
db, err := Open(":memory:")
if err != nil {
b.Fatal(err)
@@ -562,7 +539,7 @@ func Benchmark_loop(b *testing.B) {
defer db.Close()
var version string
err = db.QueryRow(`SELECT sqlite_version();`).Scan(&version)
err = db.QueryRowContext(ctx, `SELECT sqlite_version();`).Scan(&version)
if err != nil {
b.Fatal(err)
}
-1
View File
@@ -9,7 +9,6 @@ import (
"github.com/ncruces/go-sqlite3"
_ "github.com/ncruces/go-sqlite3/driver"
_ "github.com/ncruces/go-sqlite3/embed"
_ "github.com/ncruces/go-sqlite3/vfs/memdb"
)
-1
View File
@@ -11,7 +11,6 @@ import (
"os"
_ "github.com/ncruces/go-sqlite3/driver"
_ "github.com/ncruces/go-sqlite3/embed"
_ "github.com/ncruces/go-sqlite3/vfs/memdb"
)
-1
View File
@@ -6,7 +6,6 @@ import (
"github.com/ncruces/go-sqlite3"
"github.com/ncruces/go-sqlite3/driver"
_ "github.com/ncruces/go-sqlite3/embed"
_ "github.com/ncruces/go-sqlite3/vfs/memdb"
)
-2
View File
@@ -5,8 +5,6 @@ import (
"log"
"github.com/ncruces/go-sqlite3/driver"
_ "github.com/ncruces/go-sqlite3/embed"
_ "github.com/ncruces/go-sqlite3/internal/testcfg"
_ "github.com/ncruces/go-sqlite3/vfs/memdb"
)
+3 -3
View File
@@ -5,8 +5,7 @@ import (
"slices"
"testing"
_ "github.com/ncruces/go-sqlite3/embed"
_ "github.com/ncruces/go-sqlite3/internal/testcfg"
"github.com/ncruces/go-sqlite3/internal/testcfg"
)
func Test_namedValues(t *testing.T) {
@@ -55,7 +54,8 @@ func Fuzz_notWhitespace(f *testing.F) {
t.SkipNow()
}
c, err := db.Conn(t.Context())
ctx := testcfg.Context(t)
c, err := db.Conn(ctx)
if err != nil {
t.Fatal(err)
}
-41
View File
@@ -1,41 +0,0 @@
# Embeddable Wasm build of SQLite
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:
- [math functions](https://sqlite.org/lang_mathfunc.html)
- [FTS5](https://sqlite.org/fts5.html)
- [JSON](https://sqlite.org/json1.html)
- [R*Tree](https://sqlite.org/rtree.html)
- [GeoPoly](https://sqlite.org/geopoly.html)
- [Spellfix1](https://sqlite.org/spellfix1.html)
- [soundex](https://sqlite.org/lang_corefunc.html#soundex)
- [stat4](https://sqlite.org/compile.html#enable_stat4)
- [base64](https://github.com/sqlite/sqlite/blob/master/ext/misc/base64.c)
- [decimal](https://github.com/sqlite/sqlite/blob/master/ext/misc/decimal.c)
- [ieee754](https://github.com/sqlite/sqlite/blob/master/ext/misc/ieee754.c)
- [regexp](https://github.com/sqlite/sqlite/blob/master/ext/misc/regexp.c)
- [series](https://github.com/sqlite/sqlite/blob/master/ext/misc/series.c)
- [uint](https://github.com/sqlite/sqlite/blob/master/ext/misc/uint.c)
- [time](../sqlite3/time.c)
See the [configuration options](../sqlite3/sqlite_opt.h),
and [patches](../sqlite3) applied.
Built using [`wasi-sdk`](https://github.com/WebAssembly/wasi-sdk),
and [`binaryen`](https://github.com/WebAssembly/binaryen).
The build is easily reproducible, and verifiable, using
[Artifact Attestations](https://github.com/ncruces/go-sqlite3/attestations).
### Customizing the build
You can use your own 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
@@ -1,2 +0,0 @@
build/
sqlite/
-22
View File
@@ -1,22 +0,0 @@
# 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).
Binary file not shown.
-59
View File
@@ -1,59 +0,0 @@
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
@@ -1,72 +0,0 @@
#!/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
@@ -1,12 +0,0 @@
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
@@ -1,12 +0,0 @@
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
@@ -1,24 +0,0 @@
// 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))
}
-35
View File
@@ -1,35 +0,0 @@
#!/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 -f sqlite3.tmp' EXIT
"$WASI_SDK/clang" --target=wasm32-wasi -std=c23 -g0 -O2 \
-Wall -Wextra -Wno-unused-parameter -Wno-unused-function \
-o sqlite3.wasm "$ROOT/sqlite3/main.c" \
-I"$ROOT/sqlite3/libc" -I"$ROOT/sqlite3" \
-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_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 \
--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
-144
View File
@@ -1,144 +0,0 @@
aligned_alloc
sqlite3_anycollseq_init
sqlite3_autovacuum_pages_go
sqlite3_backup_finish
sqlite3_backup_init
sqlite3_backup_pagecount
sqlite3_backup_remaining
sqlite3_backup_step
sqlite3_bind_blob_go
sqlite3_bind_double
sqlite3_bind_int64
sqlite3_bind_null
sqlite3_bind_parameter_count
sqlite3_bind_parameter_index
sqlite3_bind_parameter_name
sqlite3_bind_pointer_go
sqlite3_bind_text_go
sqlite3_bind_value
sqlite3_bind_zeroblob64
sqlite3_blob_bytes
sqlite3_blob_close
sqlite3_blob_open
sqlite3_blob_read
sqlite3_blob_reopen
sqlite3_blob_write
sqlite3_busy_handler_go
sqlite3_busy_timeout
sqlite3_changes64
sqlite3_clear_bindings
sqlite3_close
sqlite3_close_v2
sqlite3_collation_needed_go
sqlite3_column_blob
sqlite3_column_bytes
sqlite3_column_count
sqlite3_column_database_name
sqlite3_column_decltype
sqlite3_column_double
sqlite3_column_int64
sqlite3_column_name
sqlite3_column_origin_name
sqlite3_column_table_name
sqlite3_column_text
sqlite3_column_type
sqlite3_column_value
sqlite3_columns_go
sqlite3_commit_hook_go
sqlite3_config_log_go
sqlite3_create_aggregate_function_go
sqlite3_create_collation_go
sqlite3_create_function_go
sqlite3_create_module_go
sqlite3_create_window_function_go
sqlite3_data_count
sqlite3_database_file_object
sqlite3_db_cacheflush
sqlite3_db_config
sqlite3_db_filename
sqlite3_db_name
sqlite3_db_readonly
sqlite3_db_release_memory
sqlite3_db_status64
sqlite3_declare_vtab
sqlite3_errcode
sqlite3_errmsg
sqlite3_error_offset
sqlite3_errstr
sqlite3_exec
sqlite3_exec_go
sqlite3_expanded_sql
sqlite3_file_control
sqlite3_filename_database
sqlite3_filename_journal
sqlite3_filename_wal
sqlite3_finalize
sqlite3_free
sqlite3_get_autocommit
sqlite3_get_auxdata
sqlite3_hard_heap_limit64
sqlite3_interrupt
sqlite3_invoke_busy_handler_go
sqlite3_last_insert_rowid
sqlite3_limit
sqlite3_log_go
sqlite3_malloc64
sqlite3_open_v2
sqlite3_overload_function
sqlite3_prepare_v3
sqlite3_progress_handler_go
sqlite3_realloc64
sqlite3_reset
sqlite3_result_blob_go
sqlite3_result_double
sqlite3_result_error
sqlite3_result_error_code
sqlite3_result_error_nomem
sqlite3_result_error_toobig
sqlite3_result_int64
sqlite3_result_null
sqlite3_result_pointer_go
sqlite3_result_subtype
sqlite3_result_text_go
sqlite3_result_value
sqlite3_result_zeroblob64
sqlite3_rollback_hook_go
sqlite3_set_authorizer_go
sqlite3_set_auxdata_go
sqlite3_set_last_insert_rowid
sqlite3_soft_heap_limit64
sqlite3_step
sqlite3_stmt_busy
sqlite3_stmt_readonly
sqlite3_stmt_status
sqlite3_table_column_metadata
sqlite3_total_changes64
sqlite3_trace_go
sqlite3_txn_state
sqlite3_update_hook_go
sqlite3_uri_key
sqlite3_value_blob
sqlite3_value_bytes
sqlite3_value_double
sqlite3_value_dup
sqlite3_value_free
sqlite3_value_frombind
sqlite3_value_int64
sqlite3_value_nochange
sqlite3_value_numeric_type
sqlite3_value_pointer_go
sqlite3_value_subtype
sqlite3_value_text
sqlite3_value_type
sqlite3_vtab_collation
sqlite3_vtab_config_go
sqlite3_vtab_distinct
sqlite3_vtab_in
sqlite3_vtab_in_first
sqlite3_vtab_in_next
sqlite3_vtab_nochange
sqlite3_vtab_on_conflict
sqlite3_vtab_rhs_value
sqlite3_wal_autocheckpoint
sqlite3_wal_checkpoint_v2
sqlite3_wal_hook_go
-23
View File
@@ -1,23 +0,0 @@
// Package embed embeds SQLite into your application.
//
// Importing package embed initializes the [sqlite3.Binary] variable
// with an appropriate build of SQLite:
//
// import _ "github.com/ncruces/go-sqlite3/embed"
package embed
import (
_ "embed"
"unsafe"
"github.com/ncruces/go-sqlite3"
)
//go:embed sqlite3.wasm
var binary string
func init() {
if sqlite3.Binary == nil {
sqlite3.Binary = unsafe.Slice(unsafe.StringData(binary), len(binary))
}
}
-25
View File
@@ -1,25 +0,0 @@
package embed
import (
"testing"
"github.com/ncruces/go-sqlite3/driver"
_ "github.com/ncruces/go-sqlite3/vfs/memdb"
)
func Test_init(t *testing.T) {
db, err := driver.Open("file:/test.db?vfs=memdb")
if err != nil {
t.Fatal(err)
}
defer db.Close()
var version string
err = db.QueryRow(`SELECT sqlite_version()`).Scan(&version)
if err != nil {
t.Fatal(err)
}
if version != "3.51.0" {
t.Error(version)
}
}
Binary file not shown.
+4 -4
View File
@@ -4,7 +4,7 @@ import (
"errors"
"strings"
"github.com/ncruces/go-sqlite3/internal/util"
"github.com/ncruces/go-sqlite3/internal/sqlite3_wrap"
)
// Error wraps an SQLite Error Code.
@@ -34,7 +34,7 @@ func (e *Error) ExtendedCode() ExtendedErrorCode {
// Error implements the error interface.
func (e *Error) Error() string {
var b strings.Builder
b.WriteString(util.ErrorCodeString(e.code))
b.WriteString(sqlite3_wrap.ErrorCodeString(e.code))
if e.msg != "" {
b.WriteString(": ")
@@ -103,7 +103,7 @@ func (e *Error) SQL() string {
// Error implements the error interface.
func (e ErrorCode) Error() string {
return util.ErrorCodeString(e)
return sqlite3_wrap.ErrorCodeString(e)
}
// As converts this error to an [ExtendedErrorCode].
@@ -127,7 +127,7 @@ func (e ErrorCode) ExtendedCode() ExtendedErrorCode {
// Error implements the error interface.
func (e ExtendedErrorCode) Error() string {
return util.ErrorCodeString(e)
return sqlite3_wrap.ErrorCodeString(e)
}
// Is tests whether this error matches a given [ErrorCode].
+18 -17
View File
@@ -6,12 +6,13 @@ import (
"strings"
"testing"
"github.com/ncruces/go-sqlite3/internal/util"
"github.com/ncruces/go-sqlite3/internal/errutil"
"github.com/ncruces/go-sqlite3/internal/sqlite3_wrap"
)
func Test_assertErr(t *testing.T) {
err := util.AssertErr()
if s := err.Error(); !strings.HasPrefix(s, "sqlite3: assertion failed") || !strings.HasSuffix(s, "error_test.go:13)") {
err := errutil.AssertErr()
if s := err.Error(); !strings.HasPrefix(s, "sqlite3: assertion failed") || !strings.HasSuffix(s, "error_test.go:14)") {
t.Errorf("got %q", s)
}
}
@@ -127,7 +128,7 @@ func TestError_Timeout(t *testing.T) {
func Test_ErrorCode_Error(t *testing.T) {
t.Parallel()
db, err := Open(":memory:")
db, err := OpenContext(testContext(t), ":memory:")
if err != nil {
t.Fatal(err)
}
@@ -136,8 +137,8 @@ func Test_ErrorCode_Error(t *testing.T) {
// Test all error codes.
for i := 0; i == int(ErrorCode(i)); i++ {
want := "sqlite3: "
ptr := ptr_t(db.call("sqlite3_errstr", stk_t(i)))
want += util.ReadString(db.mod, ptr, _MAX_NAME)
ptr := ptr_t(db.wrp.Xsqlite3_errstr(int32(i)))
want += db.wrp.ReadString(ptr, _MAX_NAME)
got := ErrorCode(i).Error()
if got != want {
@@ -149,7 +150,7 @@ func Test_ErrorCode_Error(t *testing.T) {
func Test_ExtendedErrorCode_Error(t *testing.T) {
t.Parallel()
db, err := Open(":memory:")
db, err := OpenContext(testContext(t), ":memory:")
if err != nil {
t.Fatal(err)
}
@@ -158,8 +159,8 @@ func Test_ExtendedErrorCode_Error(t *testing.T) {
// Test all extended error codes.
for i := 0; i == int(xErrorCode(i)); i++ {
want := "sqlite3: "
ptr := ptr_t(db.call("sqlite3_errstr", stk_t(i)))
want += util.ReadString(db.mod, ptr, _MAX_NAME)
ptr := ptr_t(db.wrp.Xsqlite3_errstr(int32(i)))
want += db.wrp.ReadString(ptr, _MAX_NAME)
got := xErrorCode(i).Error()
if got != want {
@@ -175,14 +176,14 @@ func Test_errorCode(t *testing.T) {
wantCode res_t
}{
{nil, "", _OK},
{ERROR, "", util.ERROR},
{IOERR, "", util.IOERR},
{IOERR_READ, "", util.IOERR_READ},
{&Error{code: util.ERROR}, "", util.ERROR},
{fmt.Errorf("%w", ERROR), ERROR.Error(), util.ERROR},
{fmt.Errorf("%w", IOERR), IOERR.Error(), util.IOERR},
{fmt.Errorf("%w", IOERR_READ), IOERR_READ.Error(), util.IOERR_READ},
{fmt.Errorf("error"), "error", util.ERROR},
{ERROR, "", sqlite3_wrap.ERROR},
{IOERR, "", sqlite3_wrap.IOERR},
{IOERR_READ, "", sqlite3_wrap.IOERR_READ},
{&Error{code: sqlite3_wrap.ERROR}, "", sqlite3_wrap.ERROR},
{fmt.Errorf("%w", ERROR), ERROR.Error(), sqlite3_wrap.ERROR},
{fmt.Errorf("%w", IOERR), IOERR.Error(), sqlite3_wrap.IOERR},
{fmt.Errorf("%w", IOERR_READ), IOERR_READ.Error(), sqlite3_wrap.IOERR_READ},
{fmt.Errorf("error"), "error", sqlite3_wrap.ERROR},
}
for _, tt := range tests {
t.Run("", func(t *testing.T) {
-1
View File
@@ -5,7 +5,6 @@ import (
"log"
"github.com/ncruces/go-sqlite3"
_ "github.com/ncruces/go-sqlite3/embed"
)
const memory = ":memory:"
+4 -4
View File
@@ -9,9 +9,8 @@ import (
"github.com/ncruces/go-sqlite3"
"github.com/ncruces/go-sqlite3/driver"
_ "github.com/ncruces/go-sqlite3/embed"
"github.com/ncruces/go-sqlite3/ext/array"
_ "github.com/ncruces/go-sqlite3/internal/testcfg"
"github.com/ncruces/go-sqlite3/internal/testcfg"
"github.com/ncruces/go-sqlite3/vfs/memdb"
)
@@ -90,13 +89,14 @@ func Test_cursor_Column(t *testing.T) {
t.Parallel()
dsn := memdb.TestDB(t)
ctx := testcfg.Context(t)
db, err := driver.Open(dsn, array.Register)
if err != nil {
t.Fatal(err)
}
defer db.Close()
rows, err := db.Query(`
rows, err := db.QueryContext(ctx, `
SELECT rowid, value FROM array(?)`,
sqlite3.Pointer(&[...]any{nil, true, 1, uint(2), math.Pi, "text", []byte{1, 2, 3}}))
if err != nil {
@@ -129,7 +129,7 @@ func Test_cursor_Column(t *testing.T) {
func Test_array_errors(t *testing.T) {
t.Parallel()
db, err := sqlite3.Open(":memory:")
db, err := sqlite3.OpenContext(testcfg.Context(t), ":memory:")
if err != nil {
t.Fatal(err)
}
+2 -2
View File
@@ -6,7 +6,7 @@ import (
"io"
"github.com/ncruces/go-sqlite3"
"github.com/ncruces/go-sqlite3/internal/util"
"github.com/ncruces/go-sqlite3/internal/errutil"
)
// Register registers the SQL functions:
@@ -111,7 +111,7 @@ func writeblob(ctx sqlite3.Context, arg ...sqlite3.Value) {
func openblob(ctx sqlite3.Context, arg ...sqlite3.Value) {
if len(arg) < 6 {
ctx.ResultError(util.ErrorString("openblob: wrong number of arguments"))
ctx.ResultError(errutil.ErrorString("openblob: wrong number of arguments"))
return
}
+4 -5
View File
@@ -11,10 +11,9 @@ import (
"github.com/ncruces/go-sqlite3"
"github.com/ncruces/go-sqlite3/driver"
_ "github.com/ncruces/go-sqlite3/embed"
"github.com/ncruces/go-sqlite3/ext/array"
"github.com/ncruces/go-sqlite3/ext/blobio"
_ "github.com/ncruces/go-sqlite3/internal/testcfg"
"github.com/ncruces/go-sqlite3/internal/testcfg"
_ "github.com/ncruces/go-sqlite3/vfs/memdb"
)
@@ -76,7 +75,7 @@ func TestMain(m *testing.M) {
func Test_readblob(t *testing.T) {
t.Parallel()
db, err := sqlite3.Open(":memory:")
db, err := sqlite3.OpenContext(testcfg.Context(t), ":memory:")
if err != nil {
t.Fatal(err)
}
@@ -167,7 +166,7 @@ func Test_readblob(t *testing.T) {
func Test_writeblob(t *testing.T) {
t.Parallel()
db, err := sqlite3.Open(":memory:")
db, err := sqlite3.OpenContext(testcfg.Context(t), ":memory:")
if err != nil {
t.Fatal(err)
}
@@ -222,7 +221,7 @@ func Test_writeblob(t *testing.T) {
func Test_openblob(t *testing.T) {
t.Parallel()
db, err := sqlite3.Open(":memory:")
db, err := sqlite3.OpenContext(testcfg.Context(t), ":memory:")
if err != nil {
t.Fatal(err)
}
+21 -20
View File
@@ -15,7 +15,8 @@ import (
"github.com/dchest/siphash"
"github.com/ncruces/go-sqlite3"
"github.com/ncruces/go-sqlite3/internal/util"
"github.com/ncruces/go-sqlite3/internal/errutil"
"github.com/ncruces/go-sqlite3/util/sql3util"
)
// Register registers the bloom_filter virtual table:
@@ -34,6 +35,8 @@ 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,
@@ -48,19 +51,17 @@ func create(db *sqlite3.Conn, _, schema, table string, arg ...string) (_ *bloom,
return nil, err
}
if nelem <= 0 {
return nil, util.ErrorString("bloom: number of elements in filter must be positive")
return nil, errutil.ErrorString("bloom: number of elements in filter must be positive")
}
} else {
nelem = 100
}
if len(arg) > 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)")
var ok bool
b.prob, ok = sql3util.ParseFloat(arg[1])
if !ok || b.prob <= 0 || b.prob >= 1 {
return nil, errutil.ErrorString("bloom: probability must be in the range (0,1)")
}
} else {
b.prob = 0.01
@@ -72,7 +73,7 @@ func create(db *sqlite3.Conn, _, schema, table string, arg ...string) (_ *bloom,
return nil, err
}
if b.hashes <= 0 {
return nil, util.ErrorString("bloom: number of hash functions must be positive")
return nil, errutil.ErrorString("bloom: number of hash functions must be positive")
}
} else {
b.hashes = max(1, numHashes(b.prob))
@@ -80,8 +81,7 @@ func create(db *sqlite3.Conn, _, schema, table string, arg ...string) (_ *bloom,
b.bytes = numBytes(nelem, b.prob)
err = db.DeclareVTab(
`CREATE TABLE x(present, word HIDDEN NOT NULL PRIMARY KEY) WITHOUT ROWID`)
err = db.DeclareVTab(vtab)
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(
`CREATE TABLE x(present, word HIDDEN NOT NULL PRIMARY KEY) WITHOUT ROWID`)
err = db.DeclareVTab(vtab)
if err != nil {
return nil, err
}
load, _, err := db.Prepare(fmt.Sprintf(
load, _, err := db.PrepareFlags(fmt.Sprintf(
`SELECT m/8, p, k FROM %s.%s WHERE rowid = 1`,
sqlite3.QuoteIdentifier(b.schema), sqlite3.QuoteIdentifier(b.storage)))
sqlite3.QuoteIdentifier(b.schema), sqlite3.QuoteIdentifier(b.storage)),
sqlite3.PREPARE_DONT_LOG)
if err != nil {
return nil, err
}
@@ -166,15 +166,16 @@ func (t *bloom) ShadowTables() {
}
func (t *bloom) Integrity(schema, table string, flags int) error {
load, _, err := t.db.Prepare(fmt.Sprintf(
load, _, err := t.db.PrepareFlags(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.QuoteIdentifier(t.schema), sqlite3.QuoteIdentifier(t.storage)),
sqlite3.PREPARE_DONT_LOG)
if err != nil {
return fmt.Errorf("bloom: %v", err) // can't wrap!
}
defer load.Close()
err = util.ErrorString("bloom: invalid parameters")
err = errutil.ErrorString("bloom: invalid parameters")
if !load.Step() {
return err
}
@@ -216,9 +217,9 @@ func (b *bloom) BestIndex(idx *sqlite3.IndexInfo) error {
func (b *bloom) Update(arg ...sqlite3.Value) (rowid int64, err error) {
if arg[0].Type() != sqlite3.NULL {
if len(arg) == 1 {
return 0, util.ErrorString("bloom: elements cannot be deleted")
return 0, errutil.ErrorString("bloom: elements cannot be deleted")
}
return 0, util.ErrorString("bloom: elements cannot be updated")
return 0, errutil.ErrorString("bloom: elements cannot be updated")
}
if arg[2].NoChange() {
+4 -5
View File
@@ -7,9 +7,8 @@ import (
"testing"
"github.com/ncruces/go-sqlite3"
_ "github.com/ncruces/go-sqlite3/embed"
"github.com/ncruces/go-sqlite3/ext/bloom"
_ "github.com/ncruces/go-sqlite3/internal/testcfg"
"github.com/ncruces/go-sqlite3/internal/testcfg"
)
func TestMain(m *testing.M) {
@@ -20,7 +19,7 @@ func TestMain(m *testing.M) {
func TestRegister(t *testing.T) {
t.Parallel()
db, err := sqlite3.Open(":memory:")
db, err := sqlite3.OpenContext(testcfg.Context(t), ":memory:")
if err != nil {
t.Fatal(err)
}
@@ -102,7 +101,7 @@ func Test_compatible(t *testing.T) {
t.Fatal(err)
}
db, err := sqlite3.Open("file:" + filepath.ToSlash(tmp) + "?nolock=1")
db, err := sqlite3.OpenContext(testcfg.Context(t), "file:"+filepath.ToSlash(tmp)+"?nolock=1")
if err != nil {
t.Fatal(err)
}
@@ -158,7 +157,7 @@ func Test_compatible(t *testing.T) {
func Test_errors(t *testing.T) {
t.Parallel()
db, err := sqlite3.Open(":memory:")
db, err := sqlite3.OpenContext(testcfg.Context(t), ":memory:")
if err != nil {
t.Fatal(err)
}
+2 -2
View File
@@ -56,7 +56,7 @@ func Register(db *sqlite3.Conn) error {
done.Add(key)
}
err := db.DeclareVTab(`CREATE TABLE x(id,depth,root HIDDEN,tablename HIDDEN,idcolumn HIDDEN,parentcolumn HIDDEN)`)
err := db.DeclareVTab(`CREATE TABLE x(id INT,depth INT,root HIDDEN,tablename TEXT HIDDEN,idcolumn TEXT HIDDEN,parentcolumn TEXT 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.Prepare(sql)
stmt, _, err := c.db.PrepareFlags(sql, sqlite3.PREPARE_DONT_LOG)
if err != nil {
return err
}
+3 -4
View File
@@ -8,9 +8,8 @@ import (
"testing"
"github.com/ncruces/go-sqlite3"
_ "github.com/ncruces/go-sqlite3/embed"
"github.com/ncruces/go-sqlite3/ext/closure"
_ "github.com/ncruces/go-sqlite3/internal/testcfg"
"github.com/ncruces/go-sqlite3/internal/testcfg"
)
func TestMain(m *testing.M) {
@@ -89,7 +88,7 @@ func Example() {
func TestRegister(t *testing.T) {
t.Parallel()
db, err := sqlite3.Open(":memory:")
db, err := sqlite3.OpenContext(testcfg.Context(t), ":memory:")
if err != nil {
t.Fatal(err)
}
@@ -157,7 +156,7 @@ func TestRegister(t *testing.T) {
func Test_errors(t *testing.T) {
t.Parallel()
db, err := sqlite3.Open(":memory:")
db, err := sqlite3.OpenContext(testcfg.Context(t), ":memory:")
if err != nil {
t.Fatal(err)
}
+18 -23
View File
@@ -16,6 +16,7 @@ import (
"strings"
"github.com/ncruces/go-sqlite3"
"github.com/ncruces/go-sqlite3/internal/errutil"
"github.com/ncruces/go-sqlite3/internal/util"
"github.com/ncruces/go-sqlite3/util/osutil"
"github.com/ncruces/go-sqlite3/util/sql3util"
@@ -73,7 +74,7 @@ func RegisterFS(db *sqlite3.Conn, fsys fs.FS) error {
}
if (filename == "") == (data == "") {
return nil, util.ErrorString(`csv: must specify either "filename" or "data" but not both`)
return nil, errutil.ErrorString(`csv: must specify either "filename" or "data" but not both`)
}
t := &table{
@@ -85,7 +86,8 @@ func RegisterFS(db *sqlite3.Conn, fsys fs.FS) error {
header: header,
}
if schema == "" {
hadSchema := schema != ""
if !hadSchema {
var row []string
if header || columns < 0 {
csv, c, err := t.newReader()
@@ -99,17 +101,14 @@ 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
}
@@ -123,7 +122,7 @@ type table struct {
fsys fs.FS
name string
data string
typs []affinity
typs []sql3util.Affinity
comma rune
comment rune
header bool
@@ -242,31 +241,27 @@ func (c *cursor) RowID() (int64, error) {
func (c *cursor) Column(ctx sqlite3.Context, col int) error {
if col < len(c.row) {
typ := text
typ := sql3util.TEXT
if col < len(c.table.typs) {
typ = c.table.typs[col]
}
txt := c.row[col]
if txt == "" && typ != text {
if txt == "" && typ != sql3util.TEXT {
return nil
}
switch typ {
case numeric, integer:
if strings.TrimLeft(txt, "+-0123456789") == "" {
if i, err := strconv.ParseInt(txt, 10, 64); err == nil {
ctx.ResultInt64(i)
return nil
}
case sql3util.NUMERIC, sql3util.INTEGER:
if i, err := strconv.ParseInt(txt, 10, 64); err == nil {
ctx.ResultInt64(i)
return nil
}
fallthrough
case real:
if strings.TrimLeft(txt, "+-.0123456789Ee") == "" {
if f, err := strconv.ParseFloat(txt, 64); err == nil {
ctx.ResultFloat(f)
return nil
}
case sql3util.REAL:
if f, ok := sql3util.ParseFloat(txt); ok {
ctx.ResultFloat(f)
return nil
}
fallthrough
default:
+4 -5
View File
@@ -7,9 +7,8 @@ import (
"testing"
"github.com/ncruces/go-sqlite3"
_ "github.com/ncruces/go-sqlite3/embed"
"github.com/ncruces/go-sqlite3/ext/csv"
_ "github.com/ncruces/go-sqlite3/internal/testcfg"
"github.com/ncruces/go-sqlite3/internal/testcfg"
)
func Example() {
@@ -63,7 +62,7 @@ func TestMain(m *testing.M) {
func TestRegister(t *testing.T) {
t.Parallel()
db, err := sqlite3.Open(":memory:")
db, err := sqlite3.OpenContext(testcfg.Context(t), ":memory:")
if err != nil {
t.Fatal(err)
}
@@ -125,7 +124,7 @@ Robert "Griesemer" "gri"`
func TestAffinity(t *testing.T) {
t.Parallel()
db, err := sqlite3.Open(":memory:")
db, err := sqlite3.OpenContext(testcfg.Context(t), ":memory:")
if err != nil {
t.Fatal(err)
}
@@ -168,7 +167,7 @@ func TestAffinity(t *testing.T) {
func TestRegister_errors(t *testing.T) {
t.Parallel()
db, err := sqlite3.Open(":memory:")
db, err := sqlite3.OpenContext(testcfg.Context(t), ":memory:")
if err != nil {
t.Fatal(err)
}
+13 -13
View File
@@ -9,31 +9,31 @@ import (
func getSchema(header bool, columns int, row []string) string {
var sep string
var str strings.Builder
str.WriteString("CREATE TABLE x(")
var buf strings.Builder
buf.WriteString("CREATE TABLE x(")
if 0 <= columns && columns < len(row) {
row = row[:columns]
}
for i, f := range row {
str.WriteString(sep)
buf.WriteString(sep)
if header && f != "" {
str.WriteString(sqlite3.QuoteIdentifier(f))
buf.WriteString(sqlite3.QuoteIdentifier(f))
} else {
str.WriteString("c")
str.WriteString(strconv.Itoa(i + 1))
buf.WriteString("c")
buf.WriteString(strconv.Itoa(i + 1))
}
str.WriteString(" TEXT")
buf.WriteString(" TEXT")
sep = ","
}
for i := len(row); i < columns; i++ {
str.WriteString(sep)
str.WriteString("c")
str.WriteString(strconv.Itoa(i + 1))
str.WriteString(" TEXT")
buf.WriteString(sep)
buf.WriteString("c")
buf.WriteString(strconv.Itoa(i + 1))
buf.WriteString(" TEXT")
sep = ","
}
str.WriteByte(')')
buf.WriteByte(')')
return str.String()
return buf.String()
}
+4 -39
View File
@@ -1,52 +1,17 @@
package csv
import (
"strings"
import "github.com/ncruces/go-sqlite3/util/sql3util"
"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) {
func getColumnAffinities(schema string) ([]sql3util.Affinity, error) {
tab, err := sql3util.ParseTable(schema)
if err != nil {
return nil, err
}
columns := tab.Columns
types := make([]affinity, len(columns))
types := make([]sql3util.Affinity, len(columns))
for i, col := range columns {
types[i] = getAffinity(col.Type)
types[i] = sql3util.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
@@ -1,32 +0,0 @@
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,mode,mtime TIMESTAMP,data,path HIDDEN,dir HIDDEN)`)
err := db.DeclareVTab(`CREATE TABLE x(name TEXT,mode INT,mtime TIMESTAMP,data BLOB,path HIDDEN,dir HIDDEN)`)
if err == nil {
err = db.VTabConfig(sqlite3.VTAB_DIRECTONLY)
}
+5 -4
View File
@@ -9,9 +9,8 @@ import (
"github.com/ncruces/go-sqlite3"
"github.com/ncruces/go-sqlite3/driver"
_ "github.com/ncruces/go-sqlite3/embed"
"github.com/ncruces/go-sqlite3/ext/fileio"
_ "github.com/ncruces/go-sqlite3/internal/testcfg"
"github.com/ncruces/go-sqlite3/internal/testcfg"
"github.com/ncruces/go-sqlite3/vfs/memdb"
)
@@ -19,6 +18,7 @@ func Test_lsmode(t *testing.T) {
t.Parallel()
dsn := memdb.TestDB(t)
ctx := testcfg.Context(t)
db, err := driver.Open(dsn, fileio.Register)
if err != nil {
t.Fatal(err)
@@ -36,7 +36,7 @@ func Test_lsmode(t *testing.T) {
}
var mode string
err = db.QueryRow(`SELECT lsmode(?)`, s.Mode()).Scan(&mode)
err = db.QueryRowContext(ctx, `SELECT lsmode(?)`, s.Mode()).Scan(&mode)
if err != nil {
t.Fatal(err)
}
@@ -55,6 +55,7 @@ func Test_readfile(t *testing.T) {
t.Run("", func(t *testing.T) {
dsn := memdb.TestDB(t)
ctx := testcfg.Context(t)
db, err := driver.Open(dsn, func(c *sqlite3.Conn) error {
fileio.RegisterFS(c, fsys)
return nil
@@ -64,7 +65,7 @@ func Test_readfile(t *testing.T) {
}
defer db.Close()
rows, err := db.Query(`SELECT readfile('fileio_test.go')`)
rows, err := db.QueryContext(ctx, `SELECT readfile('fileio_test.go')`)
if err != nil {
t.Fatal(err)
}
+4 -4
View File
@@ -10,9 +10,8 @@ import (
"github.com/ncruces/go-sqlite3"
"github.com/ncruces/go-sqlite3/driver"
_ "github.com/ncruces/go-sqlite3/embed"
"github.com/ncruces/go-sqlite3/ext/fileio"
_ "github.com/ncruces/go-sqlite3/internal/testcfg"
"github.com/ncruces/go-sqlite3/internal/testcfg"
"github.com/ncruces/go-sqlite3/vfs/memdb"
)
@@ -23,6 +22,7 @@ func Test_fsdir(t *testing.T) {
t.Run("", func(t *testing.T) {
dsn := memdb.TestDB(t)
ctx := testcfg.Context(t)
db, err := driver.Open(dsn, func(c *sqlite3.Conn) error {
fileio.RegisterFS(c, fsys)
return nil
@@ -32,7 +32,7 @@ func Test_fsdir(t *testing.T) {
}
defer db.Close()
rows, err := db.Query(`SELECT * FROM fsdir('.', '.')`)
rows, err := db.QueryContext(ctx, `SELECT * FROM fsdir('.', '.')`)
if err != nil {
t.Fatal(err)
}
@@ -65,7 +65,7 @@ func Test_fsdir(t *testing.T) {
func Test_fsdir_errors(t *testing.T) {
t.Parallel()
db, err := sqlite3.Open(":memory:")
db, err := sqlite3.OpenContext(testcfg.Context(t), ":memory:")
if err != nil {
t.Fatal(err)
}
+2 -2
View File
@@ -9,13 +9,13 @@ import (
"time"
"github.com/ncruces/go-sqlite3"
"github.com/ncruces/go-sqlite3/internal/util"
"github.com/ncruces/go-sqlite3/internal/errutil"
"github.com/ncruces/go-sqlite3/util/fsutil"
)
func writefile(ctx sqlite3.Context, arg ...sqlite3.Value) {
if len(arg) < 2 || len(arg) > 4 {
ctx.ResultError(util.ErrorString("writefile: wrong number of arguments"))
ctx.ResultError(errutil.ErrorString("writefile: wrong number of arguments"))
return
}
+9 -9
View File
@@ -8,8 +8,7 @@ import (
"time"
"github.com/ncruces/go-sqlite3/driver"
_ "github.com/ncruces/go-sqlite3/embed"
_ "github.com/ncruces/go-sqlite3/internal/testcfg"
"github.com/ncruces/go-sqlite3/internal/testcfg"
"github.com/ncruces/go-sqlite3/vfs/memdb"
)
@@ -17,6 +16,7 @@ func Test_writefile(t *testing.T) {
t.Parallel()
dsn := memdb.TestDB(t)
ctx := testcfg.Context(t)
db, err := driver.Open(dsn, Register)
if err != nil {
t.Fatal(err)
@@ -30,22 +30,22 @@ func Test_writefile(t *testing.T) {
sock := filepath.Join(dir, "sock")
twosday := time.Date(2022, 2, 22, 22, 22, 22, 0, time.UTC)
_, err = db.Exec(`SELECT writefile(?, 'Hello world!')`, file)
_, err = db.ExecContext(ctx, `SELECT writefile(?, 'Hello world!')`, file)
if err != nil {
t.Fatal(err)
}
_, err = db.Exec(`SELECT writefile(?, ?, ?)`, link, "test.txt", fs.ModeSymlink)
_, err = db.ExecContext(ctx, `SELECT writefile(?, ?, ?)`, link, "test.txt", fs.ModeSymlink)
if err != nil {
t.Fatal(err)
}
_, err = db.Exec(`SELECT writefile(?, ?, ?, ?)`, dir, nil, 0040700, twosday.Unix())
_, err = db.ExecContext(ctx, `SELECT writefile(?, ?, ?, ?)`, dir, nil, 0040700, twosday.Unix())
if err != nil {
t.Fatal(err)
}
rows, err := db.Query(`SELECT * FROM fsdir('.', ?)`, dir)
rows, err := db.QueryContext(ctx, `SELECT * FROM fsdir('.', ?)`, dir)
if err != nil {
t.Fatal(err)
}
@@ -70,19 +70,19 @@ func Test_writefile(t *testing.T) {
}
}
_, err = db.Exec(`SELECT writefile(?, 'Hello world!')`, nest)
_, err = db.ExecContext(ctx, `SELECT writefile(?, 'Hello world!')`, nest)
if err != nil {
t.Fatal(err)
}
_, err = db.Exec(`SELECT writefile(?, ?, ?)`, sock, nil, fs.ModeSocket)
_, err = db.ExecContext(ctx, `SELECT writefile(?, ?, ?)`, sock, nil, fs.ModeSocket)
if err == nil {
t.Fatal("want error")
} else {
t.Log(err)
}
_, err = db.Exec(`SELECT writefile()`)
_, err = db.ExecContext(ctx, `SELECT writefile()`)
if err == nil {
t.Fatal("want error")
} else {
+2 -2
View File
@@ -4,7 +4,7 @@ import (
"crypto"
"github.com/ncruces/go-sqlite3"
"github.com/ncruces/go-sqlite3/internal/util"
"github.com/ncruces/go-sqlite3/internal/errutil"
)
func blake2sFunc(ctx sqlite3.Context, arg ...sqlite3.Value) {
@@ -25,6 +25,6 @@ func blake2bFunc(ctx sqlite3.Context, arg ...sqlite3.Value) {
case 512:
hashFunc(ctx, arg[0], crypto.BLAKE2b_512)
default:
ctx.ResultError(util.ErrorString("blake2b: size must be 256, 384, 512"))
ctx.ResultError(errutil.ErrorString("blake2b: size must be 256, 384, 512"))
}
}
+2 -2
View File
@@ -24,14 +24,14 @@ import (
"errors"
"github.com/ncruces/go-sqlite3"
"github.com/ncruces/go-sqlite3/internal/util"
"github.com/ncruces/go-sqlite3/internal/errutil"
)
// Register registers cryptographic hash functions for a database connection.
func Register(db *sqlite3.Conn) error {
const flags = sqlite3.DETERMINISTIC | sqlite3.INNOCUOUS
var errs util.ErrorJoiner
var errs errutil.ErrorJoiner
if crypto.MD4.Available() {
errs.Join(
db.CreateFunction("md4", 1, flags, md4Func))
+7 -7
View File
@@ -14,8 +14,7 @@ import (
_ "golang.org/x/crypto/ripemd160"
"github.com/ncruces/go-sqlite3/driver"
_ "github.com/ncruces/go-sqlite3/embed"
_ "github.com/ncruces/go-sqlite3/internal/testcfg"
"github.com/ncruces/go-sqlite3/internal/testcfg"
"github.com/ncruces/go-sqlite3/vfs/memdb"
)
@@ -55,6 +54,7 @@ func TestRegister(t *testing.T) {
{"blake2b('', 256)", "0E5751C026E543B2E8AB2EB06099DAA1D1E5DF47778F7787FAAB45CDF12FE3A8"},
}
ctx := testcfg.Context(t)
db, err := driver.Open(dsn, Register)
if err != nil {
t.Fatal(err)
@@ -65,7 +65,7 @@ func TestRegister(t *testing.T) {
t.Run(tt.name, func(t *testing.T) {
var hash string
err = db.QueryRow(`SELECT hex(` + tt.name + `)`).Scan(&hash)
err = db.QueryRowContext(ctx, `SELECT hex(`+tt.name+`)`).Scan(&hash)
if err != nil {
t.Fatal(err)
}
@@ -76,22 +76,22 @@ func TestRegister(t *testing.T) {
})
}
_, err = db.Exec(`SELECT sha256('', 255)`)
_, err = db.ExecContext(ctx, `SELECT sha256('', 255)`)
if err == nil {
t.Error("want error")
}
_, err = db.Exec(`SELECT sha512('', 255)`)
_, err = db.ExecContext(ctx, `SELECT sha512('', 255)`)
if err == nil {
t.Error("want error")
}
_, err = db.Exec(`SELECT sha3('', 255)`)
_, err = db.ExecContext(ctx, `SELECT sha3('', 255)`)
if err == nil {
t.Error("want error")
}
_, err = db.Exec(`SELECT blake2b('', 255)`)
_, err = db.ExecContext(ctx, `SELECT blake2b('', 255)`)
if err == nil {
t.Error("want error")
}
+3 -3
View File
@@ -4,7 +4,7 @@ import (
"crypto"
"github.com/ncruces/go-sqlite3"
"github.com/ncruces/go-sqlite3/internal/util"
"github.com/ncruces/go-sqlite3/internal/errutil"
)
func sha224Func(ctx sqlite3.Context, arg ...sqlite3.Value) {
@@ -27,7 +27,7 @@ func sha256Func(ctx sqlite3.Context, arg ...sqlite3.Value) {
case 256:
hashFunc(ctx, arg[0], crypto.SHA256)
default:
ctx.ResultError(util.ErrorString("sha256: size must be 224, 256"))
ctx.ResultError(errutil.ErrorString("sha256: size must be 224, 256"))
}
}
@@ -47,7 +47,7 @@ func sha512Func(ctx sqlite3.Context, arg ...sqlite3.Value) {
case 512:
hashFunc(ctx, arg[0], crypto.SHA512)
default:
ctx.ResultError(util.ErrorString("sha512: size must be 224, 256, 384, 512"))
ctx.ResultError(errutil.ErrorString("sha512: size must be 224, 256, 384, 512"))
}
}
+2 -2
View File
@@ -4,7 +4,7 @@ import (
"crypto"
"github.com/ncruces/go-sqlite3"
"github.com/ncruces/go-sqlite3/internal/util"
"github.com/ncruces/go-sqlite3/internal/errutil"
)
func sha3Func(ctx sqlite3.Context, arg ...sqlite3.Value) {
@@ -23,6 +23,6 @@ func sha3Func(ctx sqlite3.Context, arg ...sqlite3.Value) {
case 512:
hashFunc(ctx, arg[0], crypto.SHA3_512)
default:
ctx.ResultError(util.ErrorString("sha3: size must be 224, 256, 384, 512"))
ctx.ResultError(errutil.ErrorString("sha3: size must be 224, 256, 384, 512"))
}
}
+10 -10
View File
@@ -4,9 +4,8 @@ import (
"testing"
"github.com/ncruces/go-sqlite3/driver"
_ "github.com/ncruces/go-sqlite3/embed"
"github.com/ncruces/go-sqlite3/ext/ipaddr"
_ "github.com/ncruces/go-sqlite3/internal/testcfg"
"github.com/ncruces/go-sqlite3/internal/testcfg"
"github.com/ncruces/go-sqlite3/vfs/memdb"
)
@@ -14,6 +13,7 @@ func TestRegister(t *testing.T) {
t.Parallel()
dsn := memdb.TestDB(t)
ctx := testcfg.Context(t)
db, err := driver.Open(dsn, ipaddr.Register)
if err != nil {
t.Fatal(err)
@@ -22,7 +22,7 @@ func TestRegister(t *testing.T) {
var got string
err = db.QueryRow(`SELECT ipfamily('::1')`).Scan(&got)
err = db.QueryRowContext(ctx, `SELECT ipfamily('::1')`).Scan(&got)
if err != nil {
t.Fatal(err)
}
@@ -30,7 +30,7 @@ func TestRegister(t *testing.T) {
t.Fatalf("got %s", got)
}
err = db.QueryRow(`SELECT ipfamily('[::1]:80')`).Scan(&got)
err = db.QueryRowContext(ctx, `SELECT ipfamily('[::1]:80')`).Scan(&got)
if err != nil {
t.Fatal(err)
}
@@ -38,7 +38,7 @@ func TestRegister(t *testing.T) {
t.Fatalf("got %s", got)
}
err = db.QueryRow(`SELECT ipfamily('192.168.1.5/24')`).Scan(&got)
err = db.QueryRowContext(ctx, `SELECT ipfamily('192.168.1.5/24')`).Scan(&got)
if err != nil {
t.Fatal(err)
}
@@ -46,7 +46,7 @@ func TestRegister(t *testing.T) {
t.Fatalf("got %s", got)
}
err = db.QueryRow(`SELECT iphost('192.168.1.5/24')`).Scan(&got)
err = db.QueryRowContext(ctx, `SELECT iphost('192.168.1.5/24')`).Scan(&got)
if err != nil {
t.Fatal(err)
}
@@ -54,7 +54,7 @@ func TestRegister(t *testing.T) {
t.Fatalf("got %s", got)
}
err = db.QueryRow(`SELECT ipmasklen('192.168.1.5/24')`).Scan(&got)
err = db.QueryRowContext(ctx, `SELECT ipmasklen('192.168.1.5/24')`).Scan(&got)
if err != nil {
t.Fatal(err)
}
@@ -62,7 +62,7 @@ func TestRegister(t *testing.T) {
t.Fatalf("got %s", got)
}
err = db.QueryRow(`SELECT ipnetwork('192.168.1.5/24')`).Scan(&got)
err = db.QueryRowContext(ctx, `SELECT ipnetwork('192.168.1.5/24')`).Scan(&got)
if err != nil {
t.Fatal(err)
}
@@ -70,7 +70,7 @@ func TestRegister(t *testing.T) {
t.Fatalf("got %s", got)
}
err = db.QueryRow(`SELECT ipcontains('192.168.1.0/24', '192.168.1.5')`).Scan(&got)
err = db.QueryRowContext(ctx, `SELECT ipcontains('192.168.1.0/24', '192.168.1.5')`).Scan(&got)
if err != nil {
t.Fatal(err)
}
@@ -78,7 +78,7 @@ func TestRegister(t *testing.T) {
t.Fatalf("got %s", got)
}
err = db.QueryRow(`SELECT ipoverlaps('192.168.1.0/24', '192.168.1.5/32')`).Scan(&got)
err = db.QueryRowContext(ctx, `SELECT ipoverlaps('192.168.1.0/24', '192.168.1.5/32')`).Scan(&got)
if err != nil {
t.Fatal(err)
}
+10 -7
View File
@@ -12,9 +12,8 @@ import (
"github.com/ncruces/go-sqlite3"
"github.com/ncruces/go-sqlite3/driver"
_ "github.com/ncruces/go-sqlite3/embed"
"github.com/ncruces/go-sqlite3/ext/lines"
_ "github.com/ncruces/go-sqlite3/internal/testcfg"
"github.com/ncruces/go-sqlite3/internal/testcfg"
"github.com/ncruces/go-sqlite3/vfs/memdb"
)
@@ -69,6 +68,7 @@ func Test_lines(t *testing.T) {
t.Parallel()
dsn := memdb.TestDB(t)
ctx := testcfg.Context(t)
db, err := driver.Open(dsn, lines.Register)
if err != nil {
log.Fatal(err)
@@ -77,7 +77,7 @@ func Test_lines(t *testing.T) {
const data = "line 1\nline 2\r\nline 3\n"
rows, err := db.Query(`SELECT rowid, line FROM lines(?)`, data)
rows, err := db.QueryContext(ctx, `SELECT rowid, line FROM lines(?)`, data)
if err != nil {
t.Fatal(err)
}
@@ -100,20 +100,21 @@ func Test_lines_error(t *testing.T) {
t.Parallel()
dsn := memdb.TestDB(t)
ctx := testcfg.Context(t)
db, err := driver.Open(dsn, lines.Register)
if err != nil {
log.Fatal(err)
}
defer db.Close()
_, err = db.Exec(`SELECT rowid, line FROM lines(?)`, nil)
_, err = db.ExecContext(ctx, `SELECT rowid, line FROM lines(?)`, nil)
if err == nil {
t.Fatal("want error")
} else {
t.Log(err)
}
_, err = db.Exec(`SELECT rowid, line FROM lines_read(?)`, "xpto")
_, err = db.ExecContext(ctx, `SELECT rowid, line FROM lines_read(?)`, "xpto")
if err == nil {
t.Fatal("want error")
} else {
@@ -125,6 +126,7 @@ func Test_lines_read(t *testing.T) {
t.Parallel()
dsn := memdb.TestDB(t)
ctx := testcfg.Context(t)
db, err := driver.Open(dsn, lines.Register)
if err != nil {
log.Fatal(err)
@@ -133,7 +135,7 @@ func Test_lines_read(t *testing.T) {
const data = "line 1\nline 2\r\nline 3\n"
rows, err := db.Query(`SELECT rowid, line FROM lines_read(?)`,
rows, err := db.QueryContext(ctx, `SELECT rowid, line FROM lines_read(?)`,
sqlite3.Pointer(strings.NewReader(data)))
if err != nil {
t.Fatal(err)
@@ -157,13 +159,14 @@ func Test_lines_test(t *testing.T) {
t.Parallel()
dsn := memdb.TestDB(t)
ctx := testcfg.Context(t)
db, err := driver.Open(dsn, lines.Register)
if err != nil {
log.Fatal(err)
}
defer db.Close()
rows, err := db.Query(`SELECT rowid, line FROM lines_read(?, '}')`, "lines_test.go")
rows, err := db.QueryContext(ctx, `SELECT rowid, line FROM lines_read(?, '}')`, "lines_test.go")
if errors.Is(err, os.ErrNotExist) {
t.Skip(err)
}
+25 -9
View File
@@ -9,7 +9,7 @@ import (
"strings"
"github.com/ncruces/go-sqlite3"
"github.com/ncruces/go-sqlite3/internal/util"
"github.com/ncruces/go-sqlite3/internal/errutil"
)
// Register registers the pivot virtual table.
@@ -43,11 +43,14 @@ func declare(db *sqlite3.Conn, _, _, _ string, arg ...string) (ret *table, err e
// Row key query.
t.scan = "SELECT * FROM\n" + arg[0]
stmt, _, err := db.Prepare(t.scan)
stmt, tail, err := db.PrepareFlags(t.scan, 0 /*PREPARE_FROM_DDL*/)
if err != nil {
return nil, err
}
defer stmt.Close()
if tail != "" {
return nil, errutil.TailErr
}
t.keys = make([]string, stmt.ColumnCount())
for i := range t.keys {
@@ -55,36 +58,47 @@ 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, _, err = db.Prepare("SELECT * FROM\n" + arg[1])
stmt, tail, err = db.PrepareFlags("SELECT * FROM\n"+arg[1], 0 /*PREPARE_FROM_DDL*/)
if err != nil {
return nil, err
}
if tail != "" {
return nil, errutil.TailErr
}
if stmt.ColumnCount() != 2 {
return nil, util.ErrorString("pivot: column definition query expects 2 result columns")
return nil, errutil.ErrorString("pivot: column definition query expects 2 result columns")
}
for stmt.Step() {
name := sqlite3.QuoteIdentifier(stmt.ColumnText(1))
t.cols = append(t.cols, stmt.ColumnValue(0).Dup())
create.WriteString(",")
create.WriteString(sep)
create.WriteString(name)
create.WriteString(" ")
create.WriteString(stmt.ColumnDeclType(1))
sep = ","
}
stmt.Close()
// Pivot cell query.
t.cell = "SELECT * FROM\n" + arg[2]
stmt, _, err = db.Prepare(t.cell)
stmt, tail, err = db.PrepareFlags(t.cell, 0 /*PREPARE_FROM_DDL*/)
if err != nil {
return nil, err
}
if tail != "" {
return nil, errutil.TailErr
}
if stmt.ColumnCount() != 1 {
return nil, util.ErrorString("pivot: cell query expects 1 result columns")
return nil, errutil.ErrorString("pivot: cell query expects 1 result columns")
}
if stmt.BindCount() != len(t.keys)+1 {
return nil, fmt.Errorf("pivot: cell query expects %d bound parameters", len(t.keys)+1)
@@ -182,7 +196,9 @@ func (c *cursor) Filter(idxNum int, idxStr string, arg ...sqlite3.Value) error {
return err
}
c.scan, _, err = c.table.db.Prepare(idxStr)
const prepflags = sqlite3.PREPARE_DONT_LOG /*PREPARE_FROM_DDL*/
c.scan, _, err = c.table.db.PrepareFlags(idxStr, prepflags)
if err != nil {
return err
}
@@ -194,7 +210,7 @@ func (c *cursor) Filter(idxNum int, idxStr string, arg ...sqlite3.Value) error {
}
if c.cell == nil {
c.cell, _, err = c.table.db.Prepare(c.table.cell)
c.cell, _, err = c.table.db.PrepareFlags(c.table.cell, prepflags)
if err != nil {
return err
}
+3 -4
View File
@@ -8,9 +8,8 @@ import (
"testing"
"github.com/ncruces/go-sqlite3"
_ "github.com/ncruces/go-sqlite3/embed"
"github.com/ncruces/go-sqlite3/ext/pivot"
_ "github.com/ncruces/go-sqlite3/internal/testcfg"
"github.com/ncruces/go-sqlite3/internal/testcfg"
)
// https://antonz.org/sqlite-pivot-table/
@@ -92,7 +91,7 @@ func TestMain(m *testing.M) {
func TestRegister(t *testing.T) {
t.Parallel()
db, err := sqlite3.Open(":memory:")
db, err := sqlite3.OpenContext(testcfg.Context(t), ":memory:")
if err != nil {
t.Fatal(err)
}
@@ -156,7 +155,7 @@ func TestRegister(t *testing.T) {
func TestRegister_errors(t *testing.T) {
t.Parallel()
db, err := sqlite3.Open(":memory:")
db, err := sqlite3.OpenContext(testcfg.Context(t), ":memory:")
if err != nil {
t.Fatal(err)
}
+7 -5
View File
@@ -8,8 +8,7 @@ import (
"github.com/ncruces/go-sqlite3"
"github.com/ncruces/go-sqlite3/driver"
_ "github.com/ncruces/go-sqlite3/embed"
_ "github.com/ncruces/go-sqlite3/internal/testcfg"
"github.com/ncruces/go-sqlite3/internal/testcfg"
"github.com/ncruces/go-sqlite3/vfs/memdb"
)
@@ -17,6 +16,7 @@ func TestRegister(t *testing.T) {
t.Parallel()
dsn := memdb.TestDB(t)
ctx := testcfg.Context(t)
db, err := driver.Open(dsn, Register)
if err != nil {
t.Fatal(err)
@@ -68,7 +68,7 @@ func TestRegister(t *testing.T) {
for _, tt := range tests {
var got sql.NullString
err := db.QueryRow(`SELECT ` + tt.test).Scan(&got)
err := db.QueryRowContext(ctx, `SELECT `+tt.test).Scan(&got)
if err != nil {
t.Fatal(err)
}
@@ -82,6 +82,7 @@ func TestRegister_errors(t *testing.T) {
t.Parallel()
dsn := memdb.TestDB(t)
ctx := testcfg.Context(t)
db, err := driver.Open(dsn, Register)
if err != nil {
t.Fatal(err)
@@ -98,7 +99,7 @@ func TestRegister_errors(t *testing.T) {
}
for _, tt := range tests {
err := db.QueryRow(`SELECT `+tt, `\`).Scan(nil)
err := db.QueryRowContext(ctx, `SELECT `+tt, `\`).Scan(nil)
if err == nil {
t.Fatal("want error")
}
@@ -109,6 +110,7 @@ func TestRegister_pointer(t *testing.T) {
t.Parallel()
dsn := memdb.TestDB(t)
ctx := testcfg.Context(t)
db, err := driver.Open(dsn, Register)
if err != nil {
t.Fatal(err)
@@ -116,7 +118,7 @@ func TestRegister_pointer(t *testing.T) {
defer db.Close()
var got int
err = db.QueryRow(`SELECT regexp_count('ABCABCAXYaxy', ?, 1)`,
err = db.QueryRowContext(ctx, `SELECT regexp_count('ABCABCAXYaxy', ?, 1)`,
sqlite3.Pointer(regexp.MustCompile(`(?i)A.`))).Scan(&got)
if err != nil {
t.Fatal(err)
+5 -5
View File
@@ -8,15 +8,15 @@ import (
"testing"
"github.com/ncruces/go-sqlite3"
_ "github.com/ncruces/go-sqlite3/embed"
"github.com/ncruces/go-sqlite3/ext/serdes"
"github.com/ncruces/go-sqlite3/internal/testcfg"
)
//go:embed testdata/wal.db
var walDB []byte
func Test_wal(t *testing.T) {
db, err := sqlite3.Open("testdata/wal.db")
db, err := sqlite3.OpenContext(testcfg.Context(t), "testdata/wal.db")
if err != nil {
t.Fatal(err)
}
@@ -45,7 +45,7 @@ func Test_northwind(t *testing.T) {
t.Fatal(err)
}
db, err := sqlite3.Open(":memory:")
db, err := sqlite3.OpenContext(testcfg.Context(t), ":memory:")
if err != nil {
t.Fatal(err)
}
@@ -97,7 +97,7 @@ func httpGet() ([]byte, error) {
}
func TestOpen_errors(t *testing.T) {
_, err := sqlite3.Open("file:test.db?vfs=github.com/ncruces/go-sqlite3/ext/serdes.sliceVFS")
_, err := sqlite3.OpenContext(testcfg.Context(t), "file:test.db?vfs=github.com/ncruces/go-sqlite3/ext/serdes.sliceVFS")
if err == nil {
t.Error("want error")
}
@@ -105,7 +105,7 @@ func TestOpen_errors(t *testing.T) {
t.Errorf("got %v, want sqlite3.CANTOPEN", err)
}
_, err = sqlite3.Open("file:serdes.db?vfs=github.com/ncruces/go-sqlite3/ext/serdes.sliceVFS")
_, err = sqlite3.OpenContext(testcfg.Context(t), "file:serdes.db?vfs=github.com/ncruces/go-sqlite3/ext/serdes.sliceVFS")
if err == nil {
t.Error("want error")
}
+24 -18
View File
@@ -14,7 +14,7 @@ import (
"unsafe"
"github.com/ncruces/go-sqlite3"
"github.com/ncruces/go-sqlite3/internal/util"
"github.com/ncruces/go-sqlite3/internal/errutil"
)
// Register registers the statement virtual table.
@@ -30,45 +30,50 @@ type table struct {
func declare(db *sqlite3.Conn, _, _, _ string, arg ...string) (*table, error) {
if len(arg) != 1 {
return nil, util.ErrorString("statement: wrong number of arguments")
return nil, errutil.ErrorString("statement: wrong number of arguments")
}
sql := "SELECT * FROM\n" + arg[0]
stmt, _, err := db.PrepareFlags(sql, sqlite3.PREPARE_PERSISTENT)
stmt, tail, err := db.PrepareFlags(sql,
sqlite3.PREPARE_PERSISTENT /*PREPARE_FROM_DDL*/)
if err != nil {
return nil, err
}
if tail != "" {
stmt.Close()
return nil, errutil.TailErr
}
var sep string
var str strings.Builder
str.WriteString("CREATE TABLE x(")
var buf strings.Builder
buf.WriteString("CREATE TABLE x(")
outputs := stmt.ColumnCount()
for i := range outputs {
name := sqlite3.QuoteIdentifier(stmt.ColumnName(i))
str.WriteString(sep)
str.WriteString(name)
str.WriteString(" ")
str.WriteString(stmt.ColumnDeclType(i))
buf.WriteString(sep)
buf.WriteString(name)
buf.WriteString(" ")
buf.WriteString(stmt.ColumnDeclType(i))
sep = ","
}
inputs := stmt.BindCount()
for i := 1; i <= inputs; i++ {
str.WriteString(sep)
buf.WriteString(sep)
name := stmt.BindName(i)
if name == "" {
str.WriteString("[")
str.WriteString(strconv.Itoa(i))
str.WriteString("] HIDDEN")
buf.WriteString("[")
buf.WriteString(strconv.Itoa(i))
buf.WriteString("] HIDDEN")
} else {
str.WriteString(sqlite3.QuoteIdentifier(name[1:]))
str.WriteString(" HIDDEN")
buf.WriteString(sqlite3.QuoteIdentifier(name[1:]))
buf.WriteString(" HIDDEN")
}
sep = ","
}
str.WriteByte(')')
buf.WriteByte(')')
err = db.DeclareVTab(str.String())
err = db.DeclareVTab(buf.String())
if err != nil {
stmt.Close()
return nil, err
@@ -129,7 +134,8 @@ func (t *table) Open() (_ sqlite3.VTabCursor, err error) {
if !t.inuse {
t.inuse = true
} else {
stmt, _, err = t.stmt.Conn().Prepare(t.sql)
stmt, _, err = t.stmt.Conn().PrepareFlags(t.sql,
sqlite3.PREPARE_DONT_LOG /*PREPARE_FROM_DDL*/)
if err != nil {
return nil, err
}
+3 -4
View File
@@ -7,9 +7,8 @@ import (
"testing"
"github.com/ncruces/go-sqlite3"
_ "github.com/ncruces/go-sqlite3/embed"
"github.com/ncruces/go-sqlite3/ext/statement"
_ "github.com/ncruces/go-sqlite3/internal/testcfg"
"github.com/ncruces/go-sqlite3/internal/testcfg"
)
func Example() {
@@ -57,7 +56,7 @@ func TestMain(m *testing.M) {
func TestRegister(t *testing.T) {
t.Parallel()
db, err := sqlite3.Open(":memory:")
db, err := sqlite3.OpenContext(testcfg.Context(t), ":memory:")
if err != nil {
t.Fatal(err)
}
@@ -107,7 +106,7 @@ func TestRegister(t *testing.T) {
func TestRegister_errors(t *testing.T) {
t.Parallel()
db, err := sqlite3.Open(":memory:")
db, err := sqlite3.OpenContext(testcfg.Context(t), ":memory:")
if err != nil {
t.Fatal(err)
}
+2 -3
View File
@@ -4,14 +4,13 @@ import (
"testing"
"github.com/ncruces/go-sqlite3"
_ "github.com/ncruces/go-sqlite3/embed"
_ "github.com/ncruces/go-sqlite3/internal/testcfg"
"github.com/ncruces/go-sqlite3/internal/testcfg"
)
func TestRegister_boolean(t *testing.T) {
t.Parallel()
db, err := sqlite3.Open(":memory:")
db, err := sqlite3.OpenContext(testcfg.Context(t), ":memory:")
if err != nil {
t.Fatal(err)
}
+6 -6
View File
@@ -5,15 +5,15 @@ package stats
type kahan struct{ hi, lo float64 }
func (k *kahan) add(x float64) {
y := k.lo + x
t := k.hi + y
k.lo = y - (t - k.hi)
y := float64(k.lo + x)
t := float64(k.hi + y)
k.lo = y - float64(t-k.hi)
k.hi = t
}
func (k *kahan) sub(x float64) {
y := k.lo - x
t := k.hi + y
k.lo = y - (t - k.hi)
y := float64(k.lo - x)
t := float64(k.hi + y)
k.lo = y - float64(t-k.hi)
k.hi = t
}
+21
View File
@@ -0,0 +1,21 @@
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))
}
}
+2 -3
View File
@@ -4,14 +4,13 @@ import (
"testing"
"github.com/ncruces/go-sqlite3"
_ "github.com/ncruces/go-sqlite3/embed"
_ "github.com/ncruces/go-sqlite3/internal/testcfg"
"github.com/ncruces/go-sqlite3/internal/testcfg"
)
func TestRegister_mode(t *testing.T) {
t.Parallel()
db, err := sqlite3.Open(":memory:")
db, err := sqlite3.OpenContext(testcfg.Context(t), ":memory:")
if err != nil {
t.Fatal(err)
}
+7 -6
View File
@@ -7,6 +7,7 @@ import (
"slices"
"github.com/ncruces/go-sqlite3"
"github.com/ncruces/go-sqlite3/internal/errutil"
"github.com/ncruces/go-sqlite3/internal/util"
"github.com/ncruces/sort/quick"
)
@@ -34,7 +35,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.NumericType() != sqlite3.NULL {
if f != 0.0 || a.Type() != sqlite3.NULL {
q.nums = append(q.nums, f)
}
if q.kind != median && q.arg1 == nil {
@@ -45,7 +46,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.NumericType() != sqlite3.NULL {
if f != 0.0 || a.Type() != sqlite3.NULL {
i := slices.Index(q.nums, f)
l := len(q.nums) - 1
q.nums[i] = q.nums[l]
@@ -66,12 +67,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, &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)
} else if err = json.Unmarshal(q.arg1, &float); err == nil {
float, err = q.at(float)
ctx.ResultFloat(float)
}
if err != nil {
ctx.ResultError(fmt.Errorf("percentile: %w", err)) // notest
@@ -83,7 +84,7 @@ func (q *percentile) at(pos float64) (float64, error) {
pos = pos / 100
}
if pos < 0 || pos > 1 {
return 0, util.ErrorString("invalid pos")
return 0, errutil.ErrorString("invalid pos")
}
i, f := math.Modf(pos * float64(len(q.nums)-1))
+2 -3
View File
@@ -5,14 +5,13 @@ import (
"testing"
"github.com/ncruces/go-sqlite3"
_ "github.com/ncruces/go-sqlite3/embed"
_ "github.com/ncruces/go-sqlite3/internal/testcfg"
"github.com/ncruces/go-sqlite3/internal/testcfg"
)
func TestRegister_percentile(t *testing.T) {
t.Parallel()
db, err := sqlite3.Open(":memory:")
db, err := sqlite3.OpenContext(testcfg.Context(t), ":memory:")
if err != nil {
t.Fatal(err)
}
+13
View File
@@ -0,0 +1,13 @@
goos: linux
goarch: amd64
pkg: github.com/ncruces/go-sqlite3/ext/stats
cpu: Intel(R) Xeon(R) W-2135 CPU @ 3.70GHz
│ before │ after │ after-unsafe │
│ sec/op │ sec/op vs base │ sec/op vs base │
_average-12 128.5n ± 12% 183.6n ± 9% +42.88% (p=0.000 n=20) 139.2n ± 8% ~ (p=0.056 n=20)
_variance-12 323.6n ± 11% 248.7n ± 9% -23.16% (p=0.000 n=20) 194.4n ± 12% -39.93% (p=0.000 n=20)
_math/sqrt-12 169.3n ± 17% 286.6n ± 6% +69.21% (p=0.000 n=20) 185.5n ± 5% ~ (p=0.072 n=20)
_math/tan-12 207.8n ± 6% 302.7n ± 4% +45.69% (p=0.000 n=20) 207.9n ± 4% ~ (p=0.533 n=20)
_math/cot-12 465.1n ± 9% 365.0n ± 3% -21.51% (p=0.000 n=20) 260.8n ± 5% -43.93% (p=0.000 n=20)
_math/cbrt-12 434.8n ± 10% 344.4n ± 4% -20.79% (p=0.000 n=20) 262.9n ± 6% -39.55% (p=0.000 n=20)
geomean 258.1n 281.5n +9.06% 203.7n -21.08%
+11 -9
View File
@@ -91,7 +91,9 @@ 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.CreateWindowFunction("mode", 1, order, newMode),
db.CreateFunction("cbrt", 1, flags, cbrt),
db.CreateFunction("cot", 1, flags, cot))
}
const (
@@ -168,7 +170,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.NumericType() != sqlite3.NULL {
if f != 0.0 || a.Type() != sqlite3.NULL {
fn.enqueue(f)
}
}
@@ -176,7 +178,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.NumericType() != sqlite3.NULL {
if f != 0.0 || a.Type() != sqlite3.NULL {
fn.dequeue(f)
}
}
@@ -241,8 +243,8 @@ func (fn *covariance) Step(ctx sqlite3.Context, arg ...sqlite3.Value) {
fa := a.Float()
fb := b.Float()
if true &&
(fa != 0.0 || a.NumericType() != sqlite3.NULL) &&
(fb != 0.0 || b.NumericType() != sqlite3.NULL) {
(fa != 0.0 || a.Type() != sqlite3.NULL) &&
(fb != 0.0 || b.Type() != sqlite3.NULL) {
fn.enqueue(fa, fb)
}
}
@@ -252,8 +254,8 @@ func (fn *covariance) Inverse(ctx sqlite3.Context, arg ...sqlite3.Value) {
fa := a.Float()
fb := b.Float()
if true &&
(fa != 0.0 || a.NumericType() != sqlite3.NULL) &&
(fb != 0.0 || b.NumericType() != sqlite3.NULL) {
(fa != 0.0 || a.Type() != sqlite3.NULL) &&
(fb != 0.0 || b.Type() != sqlite3.NULL) {
fn.dequeue(fa, fb)
}
}
@@ -293,7 +295,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.NumericType() != sqlite3.NULL {
if f != 0.0 || a.Type() != sqlite3.NULL {
fn.enqueue(f)
}
}
@@ -301,7 +303,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.NumericType() != sqlite3.NULL {
if f != 0.0 || a.Type() != sqlite3.NULL {
fn.dequeue(f)
}
}
+38 -12
View File
@@ -6,9 +6,8 @@ import (
"testing"
"github.com/ncruces/go-sqlite3"
_ "github.com/ncruces/go-sqlite3/embed"
"github.com/ncruces/go-sqlite3/ext/stats"
_ "github.com/ncruces/go-sqlite3/internal/testcfg"
"github.com/ncruces/go-sqlite3/internal/testcfg"
)
func TestMain(m *testing.M) {
@@ -19,7 +18,7 @@ func TestMain(m *testing.M) {
func TestRegister_variance(t *testing.T) {
t.Parallel()
db, err := sqlite3.Open(":memory:")
db, err := sqlite3.OpenContext(testcfg.Context(t), ":memory:")
if err != nil {
t.Fatal(err)
}
@@ -118,7 +117,7 @@ func TestRegister_variance(t *testing.T) {
func TestRegister_covariance(t *testing.T) {
t.Parallel()
db, err := sqlite3.Open(":memory:")
db, err := sqlite3.OpenContext(testcfg.Context(t), ":memory:")
if err != nil {
t.Fatal(err)
}
@@ -233,10 +232,7 @@ func TestRegister_covariance(t *testing.T) {
}
func Benchmark_average(b *testing.B) {
sqlite3.Initialize()
b.ResetTimer()
db, err := sqlite3.Open(":memory:")
db, err := sqlite3.OpenContext(b.Context(), ":memory:")
if err != nil {
b.Fatal(err)
}
@@ -253,6 +249,7 @@ func Benchmark_average(b *testing.B) {
b.Fatal(err)
}
b.ResetTimer()
if !stmt.Step() {
b.Fatal(stmt.Err())
} else {
@@ -269,10 +266,7 @@ func Benchmark_average(b *testing.B) {
}
func Benchmark_variance(b *testing.B) {
sqlite3.Initialize()
b.ResetTimer()
db, err := sqlite3.Open(":memory:")
db, err := sqlite3.OpenContext(b.Context(), ":memory:")
if err != nil {
b.Fatal(err)
}
@@ -289,6 +283,7 @@ func Benchmark_variance(b *testing.B) {
b.Fatal(err)
}
b.ResetTimer()
if !stmt.Step() {
b.Fatal(stmt.Err())
} else if b.N > 100 {
@@ -303,3 +298,34 @@ func Benchmark_variance(b *testing.B) {
b.Error(err)
}
}
func Benchmark_math(b *testing.B) {
benchmarks := []string{"sqrt", "tan", "cot", "cbrt"}
db, err := sqlite3.OpenContext(b.Context(), ":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)
}
})
}
}
+3 -3
View File
@@ -40,7 +40,7 @@ import (
"golang.org/x/text/unicode/norm"
"github.com/ncruces/go-sqlite3"
"github.com/ncruces/go-sqlite3/internal/util"
"github.com/ncruces/go-sqlite3/internal/errutil"
)
// RegisterLike must be set to false to not register a Unicode aware LIKE operator.
@@ -192,7 +192,7 @@ func normalize(ctx sqlite3.Context, arg ...sqlite3.Value) {
case "NFKD":
form = norm.NFKD
default:
ctx.ResultError(util.ErrorString("unicode: invalid form"))
ctx.ResultError(errutil.ErrorString("unicode: invalid form"))
return
}
}
@@ -228,7 +228,7 @@ func like(ctx sqlite3.Context, arg ...sqlite3.Value) {
b := arg[2].RawText()
escape, size = utf8.DecodeRune(b)
if size != len(b) {
ctx.ResultError(util.ErrorString("ESCAPE expression must be a single character"))
ctx.ResultError(errutil.ErrorString("ESCAPE expression must be a single character"))
return
}
}
+5 -6
View File
@@ -6,14 +6,13 @@ import (
"testing"
"github.com/ncruces/go-sqlite3"
_ "github.com/ncruces/go-sqlite3/embed"
_ "github.com/ncruces/go-sqlite3/internal/testcfg"
"github.com/ncruces/go-sqlite3/internal/testcfg"
)
func TestRegister(t *testing.T) {
t.Parallel()
db, err := sqlite3.Open(":memory:")
db, err := sqlite3.OpenContext(testcfg.Context(t), ":memory:")
if err != nil {
t.Fatal(err)
}
@@ -82,7 +81,7 @@ func TestRegister(t *testing.T) {
func TestRegister_collation(t *testing.T) {
t.Parallel()
db, err := sqlite3.Open(":memory:")
db, err := sqlite3.OpenContext(testcfg.Context(t), ":memory:")
if err != nil {
t.Fatal(err)
}
@@ -138,7 +137,7 @@ func TestRegister_collation(t *testing.T) {
func TestRegisterCollationsNeeded(t *testing.T) {
t.Parallel()
db, err := sqlite3.Open(":memory:")
db, err := sqlite3.OpenContext(testcfg.Context(t), ":memory:")
if err != nil {
t.Fatal(err)
}
@@ -189,7 +188,7 @@ func TestRegisterCollationsNeeded(t *testing.T) {
func TestRegister_error(t *testing.T) {
t.Parallel()
db, err := sqlite3.Open(":memory:")
db, err := sqlite3.OpenContext(testcfg.Context(t), ":memory:")
if err != nil {
t.Fatal(err)
}
+3 -3
View File
@@ -12,7 +12,7 @@ import (
"github.com/google/uuid"
"github.com/ncruces/go-sqlite3"
"github.com/ncruces/go-sqlite3/internal/util"
"github.com/ncruces/go-sqlite3/internal/errutil"
)
// Register registers the SQL functions:
@@ -89,12 +89,12 @@ func generate(ctx sqlite3.Context, arg ...sqlite3.Value) {
case domain == uuid.Group:
u, err = uuid.NewDCEGroup()
default:
err = util.ErrorString("missing id")
err = errutil.ErrorString("missing id")
}
case 3, 5:
if len(arg) < 2 {
err = util.ErrorString("missing data")
err = errutil.ErrorString("missing data")
break
}
ns, err := fromValue(arg[1])
+18 -17
View File
@@ -7,8 +7,7 @@ import (
"github.com/google/uuid"
"github.com/ncruces/go-sqlite3/driver"
_ "github.com/ncruces/go-sqlite3/embed"
_ "github.com/ncruces/go-sqlite3/internal/testcfg"
"github.com/ncruces/go-sqlite3/internal/testcfg"
"github.com/ncruces/go-sqlite3/vfs/memdb"
)
@@ -16,6 +15,7 @@ func Test_generate(t *testing.T) {
t.Parallel()
dsn := memdb.TestDB(t)
ctx := testcfg.Context(t)
db, err := driver.Open(dsn, Register)
if err != nil {
t.Fatal(err)
@@ -25,7 +25,7 @@ func Test_generate(t *testing.T) {
var u uuid.UUID
// Version 4, SQLite compatible
err = db.QueryRow(`SELECT uuid()`).Scan(&u)
err = db.QueryRowContext(ctx, `SELECT uuid()`).Scan(&u)
if err != nil {
t.Fatal(err)
}
@@ -34,14 +34,14 @@ func Test_generate(t *testing.T) {
}
// Invalid version
err = db.QueryRow(`SELECT uuid(8)`).Scan(&u)
err = db.QueryRowContext(ctx, `SELECT uuid(8)`).Scan(&u)
if err == nil {
t.Error("want error")
}
// Custom version, no arguments
for _, want := range []uuid.Version{1, 2, 4, 6, 7} {
err = db.QueryRow(`SELECT uuid(?)`, want).Scan(&u)
err = db.QueryRowContext(ctx, `SELECT uuid(?)`, want).Scan(&u)
if err != nil {
t.Fatal(err)
}
@@ -51,12 +51,12 @@ func Test_generate(t *testing.T) {
}
// Version 2, custom arguments
err = db.QueryRow(`SELECT uuid(2, 4)`).Scan(&u)
err = db.QueryRowContext(ctx, `SELECT uuid(2, 4)`).Scan(&u)
if err == nil {
t.Error("want error")
}
err = db.QueryRow(`SELECT uuid(2, 'group')`).Scan(&u)
err = db.QueryRowContext(ctx, `SELECT uuid(2, 'group')`).Scan(&u)
if err != nil {
t.Fatal(err)
}
@@ -81,7 +81,7 @@ func Test_generate(t *testing.T) {
{3, 3, 42},
}
for _, tt := range dce {
err = db.QueryRow(`SELECT uuid(2, ?, ?)`, tt.in, tt.id).Scan(&u)
err = db.QueryRowContext(ctx, `SELECT uuid(2, ?, ?)`, tt.in, tt.id).Scan(&u)
if err != nil {
t.Fatal(err)
}
@@ -97,12 +97,12 @@ func Test_generate(t *testing.T) {
}
// Versions 3 and 5
err = db.QueryRow(`SELECT uuid(3)`).Scan(&u)
err = db.QueryRowContext(ctx, `SELECT uuid(3)`).Scan(&u)
if err == nil {
t.Error("want error")
}
err = db.QueryRow(`SELECT uuid(3, 0, '')`).Scan(&u)
err = db.QueryRowContext(ctx, `SELECT uuid(3, 0, '')`).Scan(&u)
if err == nil {
t.Error("want error")
}
@@ -141,7 +141,7 @@ func Test_generate(t *testing.T) {
{5, "url", "https://www.php.net", uuid.MustParse("a8f6ae40-d8a7-58f0-be05-a22f94eca9ec")},
}
for _, tt := range tests {
err = db.QueryRow(`SELECT uuid(?, ?, ?)`, tt.ver, tt.ns, tt.data).Scan(&u)
err = db.QueryRowContext(ctx, `SELECT uuid(?, ?, ?)`, tt.ver, tt.ns, tt.data).Scan(&u)
if err != nil {
t.Fatal(err)
}
@@ -155,6 +155,7 @@ func Test_convert(t *testing.T) {
t.Parallel()
dsn := memdb.TestDB(t)
ctx := testcfg.Context(t)
db, err := driver.Open(dsn, Register)
if err != nil {
t.Fatal(err)
@@ -170,7 +171,7 @@ func Test_convert(t *testing.T) {
}
for _, tt := range tests {
err = db.QueryRow(`SELECT uuid_str(` + tt + `)`).Scan(&u)
err = db.QueryRowContext(ctx, `SELECT uuid_str(`+tt+`)`).Scan(&u)
if err != nil {
t.Fatal(err)
}
@@ -180,7 +181,7 @@ func Test_convert(t *testing.T) {
}
for _, tt := range tests {
err = db.QueryRow(`SELECT uuid_blob(` + tt + `)`).Scan(&u)
err = db.QueryRowContext(ctx, `SELECT uuid_blob(`+tt+`)`).Scan(&u)
if err != nil {
t.Fatal(err)
}
@@ -189,22 +190,22 @@ func Test_convert(t *testing.T) {
}
}
err = db.QueryRow(`SELECT uuid_str(X'cafe')`).Scan(&u)
err = db.QueryRowContext(ctx, `SELECT uuid_str(X'cafe')`).Scan(&u)
if err == nil {
t.Fatal("want error")
}
err = db.QueryRow(`SELECT uuid_blob(X'cafe')`).Scan(&u)
err = db.QueryRowContext(ctx, `SELECT uuid_blob(X'cafe')`).Scan(&u)
if err == nil {
t.Fatal("want error")
}
err = db.QueryRow(`SELECT uuid_extract_version(X'cafe')`).Scan(&u)
err = db.QueryRowContext(ctx, `SELECT uuid_extract_version(X'cafe')`).Scan(&u)
if err == nil {
t.Fatal("want error")
}
err = db.QueryRow(`SELECT uuid_extract_timestamp(X'cafe')`).Scan(&u)
err = db.QueryRowContext(ctx, `SELECT uuid_extract_timestamp(X'cafe')`).Scan(&u)
if err == nil {
t.Fatal("want error")
}
+6 -6
View File
@@ -7,7 +7,7 @@ import (
"errors"
"github.com/ncruces/go-sqlite3"
"github.com/ncruces/go-sqlite3/internal/util"
"github.com/ncruces/go-sqlite3/internal/errutil"
)
// Register registers the zorder and unzorder SQL functions.
@@ -21,7 +21,7 @@ func Register(db *sqlite3.Conn) error {
func zorder(ctx sqlite3.Context, arg ...sqlite3.Value) {
var x [24]int64
if n := len(arg); n < 2 || n > 24 {
ctx.ResultError(util.ErrorString("zorder: needs between 2 and 24 dimensions"))
ctx.ResultError(errutil.ErrorString("zorder: needs between 2 and 24 dimensions"))
return
}
for i := range arg {
@@ -37,7 +37,7 @@ func zorder(ctx sqlite3.Context, arg ...sqlite3.Value) {
for i := range arg {
if x[i] != 0 {
ctx.ResultError(util.ErrorString("zorder: argument out of range"))
ctx.ResultError(errutil.ErrorString("zorder: argument out of range"))
return
}
}
@@ -50,15 +50,15 @@ func unzorder(ctx sqlite3.Context, arg ...sqlite3.Value) {
z := arg[0].Int64()
if n < 2 || n > 24 {
ctx.ResultError(util.ErrorString("unzorder: needs between 2 and 24 dimensions"))
ctx.ResultError(errutil.ErrorString("unzorder: needs between 2 and 24 dimensions"))
return
}
if i < 0 || i >= n {
ctx.ResultError(util.ErrorString("unzorder: index out of range"))
ctx.ResultError(errutil.ErrorString("unzorder: index out of range"))
return
}
if z < 0 {
ctx.ResultError(util.ErrorString("unzorder: argument out of range"))
ctx.ResultError(errutil.ErrorString("unzorder: argument out of range"))
return
}
+16 -13
View File
@@ -6,9 +6,8 @@ import (
"testing"
"github.com/ncruces/go-sqlite3/driver"
_ "github.com/ncruces/go-sqlite3/embed"
"github.com/ncruces/go-sqlite3/ext/zorder"
_ "github.com/ncruces/go-sqlite3/internal/testcfg"
"github.com/ncruces/go-sqlite3/internal/testcfg"
"github.com/ncruces/go-sqlite3/vfs/memdb"
)
@@ -16,6 +15,7 @@ func Test_zorder(t *testing.T) {
t.Parallel()
dsn := memdb.TestDB(t)
ctx := testcfg.Context(t)
db, err := driver.Open(dsn, zorder.Register)
if err != nil {
t.Fatal(err)
@@ -23,7 +23,7 @@ func Test_zorder(t *testing.T) {
defer db.Close()
var got int64
err = db.QueryRow(`SELECT zorder(2, 3)`).Scan(&got)
err = db.QueryRowContext(ctx, `SELECT zorder(2, 3)`).Scan(&got)
if err != nil {
t.Fatal(err)
}
@@ -31,7 +31,7 @@ func Test_zorder(t *testing.T) {
t.Errorf("got %d, want 14", got)
}
err = db.QueryRow(`SELECT zorder(4, 5)`).Scan(&got)
err = db.QueryRowContext(ctx, `SELECT zorder(4, 5)`).Scan(&got)
if err != nil {
t.Fatal(err)
}
@@ -40,7 +40,7 @@ func Test_zorder(t *testing.T) {
}
var check bool
err = db.QueryRow(`SELECT zorder(3, 4) BETWEEN zorder(2, 3) AND zorder(4, 5)`).Scan(&check)
err = db.QueryRowContext(ctx, `SELECT zorder(3, 4) BETWEEN zorder(2, 3) AND zorder(4, 5)`).Scan(&check)
if err != nil {
t.Fatal(err)
}
@@ -48,7 +48,7 @@ func Test_zorder(t *testing.T) {
t.Error("want true")
}
err = db.QueryRow(`SELECT zorder(2, 2) NOT BETWEEN zorder(2, 3) AND zorder(4, 5)`).Scan(&check)
err = db.QueryRowContext(ctx, `SELECT zorder(2, 2) NOT BETWEEN zorder(2, 3) AND zorder(4, 5)`).Scan(&check)
if err != nil {
t.Fatal(err)
}
@@ -61,6 +61,7 @@ func Test_unzorder(t *testing.T) {
t.Parallel()
dsn := memdb.TestDB(t)
ctx := testcfg.Context(t)
db, err := driver.Open(dsn, zorder.Register)
if err != nil {
t.Fatal(err)
@@ -68,7 +69,7 @@ func Test_unzorder(t *testing.T) {
defer db.Close()
var got int64
err = db.QueryRow(`SELECT unzorder(zorder(3, 4), 2, 0)`).Scan(&got)
err = db.QueryRowContext(ctx, `SELECT unzorder(zorder(3, 4), 2, 0)`).Scan(&got)
if err != nil {
t.Fatal(err)
}
@@ -76,7 +77,7 @@ func Test_unzorder(t *testing.T) {
t.Errorf("got %d, want 3", got)
}
err = db.QueryRow(`SELECT unzorder(zorder(3, 4), 2, 1)`).Scan(&got)
err = db.QueryRowContext(ctx, `SELECT unzorder(zorder(3, 4), 2, 1)`).Scan(&got)
if err != nil {
t.Fatal(err)
}
@@ -89,6 +90,7 @@ func Test_zorder_error(t *testing.T) {
t.Parallel()
dsn := memdb.TestDB(t)
ctx := testcfg.Context(t)
db, err := driver.Open(dsn, zorder.Register)
if err != nil {
t.Fatal(err)
@@ -96,7 +98,7 @@ func Test_zorder_error(t *testing.T) {
defer db.Close()
var got int64
err = db.QueryRow(`SELECT zorder(1, 2, 3, 100000)`).Scan(&got)
err = db.QueryRowContext(ctx, `SELECT zorder(1, 2, 3, 100000)`).Scan(&got)
if err == nil {
t.Error("want error")
}
@@ -108,7 +110,7 @@ func Test_zorder_error(t *testing.T) {
buf.WriteString(strconv.Itoa(0))
}
buf.WriteByte(')')
err = db.QueryRow(buf.String()).Scan(&got)
err = db.QueryRowContext(ctx, buf.String()).Scan(&got)
if err == nil {
t.Error("want error")
}
@@ -118,6 +120,7 @@ func Test_unzorder_error(t *testing.T) {
t.Parallel()
dsn := memdb.TestDB(t)
ctx := testcfg.Context(t)
db, err := driver.Open(dsn, zorder.Register)
if err != nil {
t.Fatal(err)
@@ -125,17 +128,17 @@ func Test_unzorder_error(t *testing.T) {
defer db.Close()
var got int64
err = db.QueryRow(`SELECT unzorder(-1, 2, 0)`).Scan(&got)
err = db.QueryRowContext(ctx, `SELECT unzorder(-1, 2, 0)`).Scan(&got)
if err == nil {
t.Error("want error")
}
err = db.QueryRow(`SELECT unzorder(0, 2, 2)`).Scan(&got)
err = db.QueryRowContext(ctx, `SELECT unzorder(0, 2, 2)`).Scan(&got)
if err == nil {
t.Error("want error")
}
err = db.QueryRow(`SELECT unzorder(0, 25, 2)`).Scan(&got)
err = db.QueryRowContext(ctx, `SELECT unzorder(0, 25, 2)`).Scan(&got)
if err == nil {
t.Error("want error")
}
+66 -69
View File
@@ -1,15 +1,10 @@
package sqlite3
import (
"context"
"io"
"iter"
"sync"
"sync/atomic"
"github.com/tetratelabs/wazero/api"
"github.com/ncruces/go-sqlite3/internal/util"
)
// CollationNeeded registers a callback to be invoked
@@ -21,7 +16,7 @@ func (c *Conn) CollationNeeded(cb func(db *Conn, name string)) error {
if cb != nil {
enable = 1
}
rc := res_t(c.call("sqlite3_collation_needed_go", stk_t(c.handle), stk_t(enable)))
rc := res_t(c.wrp.Xsqlite3_collation_needed_go(int32(c.handle), enable))
if err := c.error(rc); err != nil {
return err
}
@@ -36,7 +31,7 @@ func (c *Conn) CollationNeeded(cb func(db *Conn, name string)) error {
// This can be used to load schemas that contain
// one or more unknown collating sequences.
func (c Conn) AnyCollationNeeded() error {
rc := res_t(c.call("sqlite3_anycollseq_init", stk_t(c.handle), 0, 0))
rc := res_t(c.wrp.Xsqlite3_anycollseq_init(int32(c.handle), 0, 0))
if err := c.error(rc); err != nil {
return err
}
@@ -49,13 +44,13 @@ func (c Conn) AnyCollationNeeded() error {
// https://sqlite.org/c3ref/create_collation.html
func (c *Conn) CreateCollation(name string, fn CollatingFunction) error {
var funcPtr ptr_t
defer c.arena.mark()()
namePtr := c.arena.string(name)
defer c.arena.Mark()()
namePtr := c.arena.String(name)
if fn != nil {
funcPtr = util.AddHandle(c.ctx, fn)
funcPtr = c.wrp.AddHandle(fn)
}
rc := res_t(c.call("sqlite3_create_collation_go",
stk_t(c.handle), stk_t(namePtr), stk_t(funcPtr)))
rc := res_t(c.wrp.Xsqlite3_create_collation_go(
int32(c.handle), int32(namePtr), int32(funcPtr)))
return c.error(rc)
}
@@ -68,14 +63,14 @@ type CollatingFunction func(a, b []byte) int
// https://sqlite.org/c3ref/create_function.html
func (c *Conn) CreateFunction(name string, nArg int, flag FunctionFlag, fn ScalarFunction) error {
var funcPtr ptr_t
defer c.arena.mark()()
namePtr := c.arena.string(name)
defer c.arena.Mark()()
namePtr := c.arena.String(name)
if fn != nil {
funcPtr = util.AddHandle(c.ctx, fn)
funcPtr = c.wrp.AddHandle(fn)
}
rc := res_t(c.call("sqlite3_create_function_go",
stk_t(c.handle), stk_t(namePtr), stk_t(nArg),
stk_t(flag), stk_t(funcPtr)))
rc := res_t(c.wrp.Xsqlite3_create_function_go(
int32(c.handle), int32(namePtr), int32(nArg),
int32(flag), int32(funcPtr)))
return c.error(rc)
}
@@ -88,10 +83,10 @@ type ScalarFunction func(ctx Context, arg ...Value)
// https://sqlite.org/c3ref/create_function.html
func (c *Conn) CreateAggregateFunction(name string, nArg int, flag FunctionFlag, fn AggregateSeqFunction) error {
var funcPtr ptr_t
defer c.arena.mark()()
namePtr := c.arena.string(name)
defer c.arena.Mark()()
namePtr := c.arena.String(name)
if fn != nil {
funcPtr = util.AddHandle(c.ctx, AggregateConstructor(func() AggregateFunction {
funcPtr = c.wrp.AddHandle(AggregateConstructor(func() AggregateFunction {
var a aggregateFunc
coro := func(yieldCoro func(struct{}) bool) {
seq := func(yieldSeq func([]Value) bool) {
@@ -107,9 +102,9 @@ func (c *Conn) CreateAggregateFunction(name string, nArg int, flag FunctionFlag,
return &a
}))
}
rc := res_t(c.call("sqlite3_create_aggregate_function_go",
stk_t(c.handle), stk_t(namePtr), stk_t(nArg),
stk_t(flag), stk_t(funcPtr)))
rc := res_t(c.wrp.Xsqlite3_create_aggregate_function_go(
int32(c.handle), int32(namePtr), int32(nArg),
int32(flag), int32(funcPtr)))
return c.error(rc)
}
@@ -124,10 +119,10 @@ type AggregateSeqFunction func(ctx *Context, seq iter.Seq[[]Value])
// https://sqlite.org/c3ref/create_function.html
func (c *Conn) CreateWindowFunction(name string, nArg int, flag FunctionFlag, fn AggregateConstructor) error {
var funcPtr ptr_t
defer c.arena.mark()()
namePtr := c.arena.string(name)
defer c.arena.Mark()()
namePtr := c.arena.String(name)
if fn != nil {
funcPtr = util.AddHandle(c.ctx, AggregateConstructor(func() AggregateFunction {
funcPtr = c.wrp.AddHandle(AggregateConstructor(func() AggregateFunction {
agg := fn()
if win, ok := agg.(WindowFunction); ok {
return win
@@ -135,9 +130,9 @@ func (c *Conn) CreateWindowFunction(name string, nArg int, flag FunctionFlag, fn
return agg
}))
}
rc := res_t(c.call("sqlite3_create_window_function_go",
stk_t(c.handle), stk_t(namePtr), stk_t(nArg),
stk_t(flag), stk_t(funcPtr)))
rc := res_t(c.wrp.Xsqlite3_create_window_function_go(
int32(c.handle), int32(namePtr), int32(nArg),
int32(flag), int32(funcPtr)))
return c.error(rc)
}
@@ -173,84 +168,86 @@ type WindowFunction interface {
//
// https://sqlite.org/c3ref/overload_function.html
func (c *Conn) OverloadFunction(name string, nArg int) error {
defer c.arena.mark()()
namePtr := c.arena.string(name)
rc := res_t(c.call("sqlite3_overload_function",
stk_t(c.handle), stk_t(namePtr), stk_t(nArg)))
defer c.arena.Mark()()
namePtr := c.arena.String(name)
rc := res_t(c.wrp.Xsqlite3_overload_function(
int32(c.handle), int32(namePtr), int32(nArg)))
return c.error(rc)
}
func destroyCallback(ctx context.Context, mod api.Module, pApp ptr_t) {
util.DelHandle(ctx, pApp)
func (e *env) Xgo_destroy(pApp int32) {
e.DelHandle(ptr_t(pApp))
}
func collationCallback(ctx context.Context, mod api.Module, pArg, pDB ptr_t, eTextRep uint32, zName ptr_t) {
if c, ok := ctx.Value(connKey{}).(*Conn); ok && c.handle == pDB && c.collation != nil {
name := util.ReadString(mod, zName, _MAX_NAME)
func (e *env) Xgo_collation_needed(pArg, pDB, eTextRep, zName int32) {
if c, ok := e.DB.(*Conn); ok && c.handle == ptr_t(pDB) && c.collation != nil {
name := e.ReadString(ptr_t(zName), _MAX_NAME)
c.collation(c, name)
}
}
func compareCallback(ctx context.Context, mod api.Module, pApp ptr_t, nKey1 int32, pKey1 ptr_t, nKey2 int32, pKey2 ptr_t) uint32 {
fn := util.GetHandle(ctx, pApp).(CollatingFunction)
return uint32(fn(util.View(mod, pKey1, int64(nKey1)), util.View(mod, pKey2, int64(nKey2))))
func (e *env) Xgo_compare(pApp, nKey1, pKey1, nKey2, pKey2 int32) int32 {
fn := e.GetHandle(ptr_t(pApp)).(CollatingFunction)
return int32(fn(
e.Bytes(ptr_t(pKey1), int64(nKey1)),
e.Bytes(ptr_t(pKey2), int64(nKey2))))
}
func funcCallback(ctx context.Context, mod api.Module, pCtx, pApp ptr_t, nArg int32, pArg ptr_t) {
db := ctx.Value(connKey{}).(*Conn)
args := callbackArgs(db, nArg, pArg)
func (e *env) Xgo_func(pCtx, pApp, nArg, pArg int32) {
db := e.DB.(*Conn)
args := callbackArgs(db, nArg, ptr_t(pArg))
defer returnArgs(args)
fn := util.GetHandle(db.ctx, pApp).(ScalarFunction)
fn(Context{db, pCtx}, *args...)
fn := db.wrp.GetHandle(ptr_t(pApp)).(ScalarFunction)
fn(Context{db, ptr_t(pCtx)}, *args...)
}
func stepCallback(ctx context.Context, mod api.Module, pCtx, pAgg, pApp ptr_t, nArg int32, pArg ptr_t) {
db := ctx.Value(connKey{}).(*Conn)
args := callbackArgs(db, nArg, pArg)
func (e *env) Xgo_step(pCtx, pAgg, pApp, nArg, pArg int32) {
db := e.DB.(*Conn)
args := callbackArgs(db, nArg, ptr_t(pArg))
defer returnArgs(args)
fn, _ := callbackAggregate(db, pAgg, pApp)
fn.Step(Context{db, pCtx}, *args...)
fn, _ := callbackAggregate(db, ptr_t(pAgg), ptr_t(pApp))
fn.Step(Context{db, ptr_t(pCtx)}, *args...)
}
func valueCallback(ctx context.Context, mod api.Module, pCtx, pAgg, pApp ptr_t, final int32) {
db := ctx.Value(connKey{}).(*Conn)
fn, handle := callbackAggregate(db, pAgg, pApp)
fn.Value(Context{db, pCtx})
func (e *env) Xgo_value(pCtx, pAgg, pApp, final int32) {
db := e.DB.(*Conn)
fn, handle := callbackAggregate(db, ptr_t(pAgg), ptr_t(pApp))
fn.Value(Context{db, ptr_t(pCtx)})
// Cleanup.
if final != 0 {
var err error
if handle != 0 {
err = util.DelHandle(ctx, handle)
err = e.DelHandle(handle)
} else if c, ok := fn.(io.Closer); ok {
err = c.Close()
}
if err != nil {
Context{db, pCtx}.ResultError(err)
Context{db, ptr_t(pCtx)}.ResultError(err)
return // notest
}
}
}
func inverseCallback(ctx context.Context, mod api.Module, pCtx, pAgg ptr_t, nArg int32, pArg ptr_t) {
db := ctx.Value(connKey{}).(*Conn)
args := callbackArgs(db, nArg, pArg)
func (e *env) Xgo_inverse(pCtx, pAgg, nArg, pArg int32) {
db := e.DB.(*Conn)
args := callbackArgs(db, nArg, ptr_t(pArg))
defer returnArgs(args)
fn := util.GetHandle(db.ctx, pAgg).(WindowFunction)
fn.Inverse(Context{db, pCtx}, *args...)
fn := db.wrp.GetHandle(ptr_t(pAgg)).(WindowFunction)
fn.Inverse(Context{db, ptr_t(pCtx)}, *args...)
}
func callbackAggregate(db *Conn, pAgg, pApp ptr_t) (AggregateFunction, ptr_t) {
if pApp == 0 {
handle := util.Read32[ptr_t](db.mod, pAgg)
return util.GetHandle(db.ctx, handle).(AggregateFunction), handle
handle := ptr_t(db.wrp.Read32(pAgg))
return db.wrp.GetHandle(handle).(AggregateFunction), handle
}
// We need to create the aggregate.
fn := util.GetHandle(db.ctx, pApp).(AggregateConstructor)()
fn := db.wrp.GetHandle(pApp).(AggregateConstructor)()
if pAgg != 0 {
handle := util.AddHandle(db.ctx, fn)
util.Write32(db.mod, pAgg, handle)
handle := db.wrp.AddHandle(fn)
db.wrp.Write32(pAgg, uint32(handle))
return fn, handle
}
return fn, 0
@@ -272,7 +269,7 @@ func callbackArgs(db *Conn, nArg int32, pArg ptr_t) *[]Value {
for i := range lst {
lst[i] = Value{
c: db,
handle: util.Read32[ptr_t](db.mod, pArg+ptr_t(i)*ptrlen),
handle: ptr_t(db.wrp.Read32(pArg + ptr_t(i)*ptrlen)),
}
}
*arg = lst
+5 -3
View File
@@ -6,7 +6,6 @@ import (
"log"
"github.com/ncruces/go-sqlite3"
_ "github.com/ncruces/go-sqlite3/embed"
)
func ExampleConn_CreateAggregateFunction() {
@@ -31,8 +30,11 @@ func ExampleConn_CreateAggregateFunction() {
count := 0
total := 0.0
for arg := range seq {
total += arg[0].Float()
count++
switch arg[0].NumericType() {
case sqlite3.FLOAT, sqlite3.INTEGER:
total += arg[0].Float()
count++
}
}
ctx.ResultFloat(total / float64(count))
})
-2
View File
@@ -7,9 +7,7 @@ import (
"regexp"
"github.com/ncruces/go-sqlite3"
_ "github.com/ncruces/go-sqlite3/embed"
"github.com/ncruces/go-sqlite3/ext/unicode"
_ "github.com/ncruces/go-sqlite3/internal/testcfg"
)
func ExampleConn_CreateCollation() {
-1
View File
@@ -6,7 +6,6 @@ import (
"unicode"
"github.com/ncruces/go-sqlite3"
_ "github.com/ncruces/go-sqlite3/embed"
)
func ExampleConn_CreateWindowFunction() {
+7 -7
View File
@@ -1,22 +1,22 @@
module github.com/ncruces/go-sqlite3
go 1.24.0
go 1.26.0
require (
github.com/ncruces/go-sqlite3-wasm v1.0.4-0.20260329114232-2491c387476c
github.com/ncruces/julianday v1.0.0
github.com/ncruces/sort v0.1.6
github.com/ncruces/wbt v0.2.0
github.com/tetratelabs/wazero v1.10.1
golang.org/x/sys v0.38.0
github.com/ncruces/wbt v1.0.0
golang.org/x/sys v0.41.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.45.0 // vfs/adiantum vfs/xts
golang.org/x/sync v0.18.0 // test
golang.org/x/text v0.31.0 // ext/unicode
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
lukechampine.com/adiantum v1.1.1 // vfs/adiantum
)
+12 -12
View File
@@ -2,23 +2,23 @@ github.com/dchest/siphash v1.2.3 h1:QXwFc8cFOR2dSa/gE6o/HokBMWtLUaNDVd+22aKHeEA=
github.com/dchest/siphash v1.2.3/go.mod h1:0NvQU092bT0ipiFN++/rXm69QG9tVxLAlQHIXMPAkHc=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/ncruces/go-sqlite3-wasm v1.0.4-0.20260329114232-2491c387476c h1:Fnw6J6r2trodoTlRk7mDo+ZXEnBSJFPmorGV7urtgvI=
github.com/ncruces/go-sqlite3-wasm v1.0.4-0.20260329114232-2491c387476c/go.mod h1:eewd1iQhpv/1qDXP83cfDk06Bpbhmt3EspN/0krcy70=
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/ncruces/wbt v0.2.0 h1:Q9zlKOBSZc7Yy/R2cGa35g6RKUUE3BjNIW3tfGC4F04=
github.com/ncruces/wbt v0.2.0/go.mod h1:DtF92amvMxH69EmBFUSFWRDAlo6hOEfoNQnClxj9C/c=
github.com/ncruces/wbt v1.0.0 h1:8iBE7UPjTLUpzu3/FCRjAmuQjWzgxo10RGBgt3ooLSc=
github.com/ncruces/wbt v1.0.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.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=
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=
lukechampine.com/adiantum v1.1.1 h1:4fp6gTxWCqpEbLy40ExiYDDED3oUNWx5cTqBCtPdZqA=
lukechampine.com/adiantum v1.1.1/go.mod h1:LrAYVnTYLnUtE/yMp5bQr0HstAf060YUF8nM0B6+rUw=
-1
View File
@@ -6,7 +6,6 @@
```go
import (
_ "github.com/ncruces/go-sqlite3/embed"
"github.com/ncruces/go-sqlite3/gormlite"
"gorm.io/gorm"
)
+6 -4
View File
@@ -3,15 +3,17 @@ module github.com/ncruces/go-sqlite3/gormlite
go 1.24.0
require (
github.com/ncruces/go-sqlite3 v0.30.1
github.com/ncruces/go-sqlite3 v0.31.0
gorm.io/gorm v1.31.1
)
require (
github.com/jinzhu/inflection v1.0.0 // indirect
github.com/jinzhu/now v1.1.5 // indirect
github.com/ncruces/go-sqlite3-wasm v0.0.0-20260309161257-7520fba710b6 // indirect
github.com/ncruces/julianday v1.0.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
golang.org/x/sys v0.41.0 // indirect
golang.org/x/text v0.34.0 // indirect
)
replace github.com/ncruces/go-sqlite3 => ../
+6 -8
View File
@@ -2,15 +2,13 @@ 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.1 h1:pHC3YsyRdJv4pCMB4MO1Q2BXw/CAa+Hoj7GSaKtVk+g=
github.com/ncruces/go-sqlite3 v0.30.1/go.mod h1:UVsWrQaq1qkcal5/vT5lOJnZCVlR5rsThKdwidjFsKc=
github.com/ncruces/go-sqlite3-wasm v0.0.0-20260309161257-7520fba710b6 h1:lhG+QcdqPaW1D3+FCE1bVOJmidrS80Uf/Rq1yw97iZU=
github.com/ncruces/go-sqlite3-wasm v0.0.0-20260309161257-7520fba710b6/go.mod h1:CLtq3B4C0mgdclnRUUkn4pLYYo1MsugQNXWjjAIxoPo=
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.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM=
golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM=
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=
gorm.io/gorm v1.31.1 h1:7CA8FTFz/gRfgqgpeKIBcervUn3xSyPUmr6B2WXJ7kg=
gorm.io/gorm v1.31.1/go.mod h1:XyQVbO2k6YkOis7C2437jSit3SsDK72s7n7rsSHd+Gs=
-2
View File
@@ -8,8 +8,6 @@ import (
"github.com/ncruces/go-sqlite3"
"github.com/ncruces/go-sqlite3/driver"
_ "github.com/ncruces/go-sqlite3/embed"
_ "github.com/ncruces/go-sqlite3/internal/testcfg"
"github.com/ncruces/go-sqlite3/vfs/memdb"
)
+1 -2
View File
@@ -7,11 +7,10 @@ diff --git a/tests/.gitignore b/tests/.gitignore
diff --git a/tests/tests_test.go b/tests/tests_test.go
--- a/tests/tests_test.go
+++ b/tests/tests_test.go
@@ -8,9 +8,11 @@ import (
@@ -8,9 +8,10 @@ import (
"path/filepath"
"time"
+ _ "github.com/ncruces/go-sqlite3/embed"
+ sqlite "github.com/ncruces/go-sqlite3/gormlite"
+
"gorm.io/driver/gaussdb"
-24
View File
@@ -1,24 +0,0 @@
//go:build !unix && !windows
package alloc
import "github.com/tetratelabs/wazero/experimental"
func NewMemory(cap, max uint64) experimental.LinearMemory {
return &sliceMemory{make([]byte, 0, cap)}
}
type sliceMemory struct {
buf []byte
}
func (b *sliceMemory) Free() {}
func (b *sliceMemory) Reallocate(size uint64) []byte {
if cap := uint64(cap(b.buf)); size > cap {
b.buf = append(b.buf[:cap], make([]byte, size-cap)...)
} else {
b.buf = b.buf[:size]
}
return b.buf
}
-14
View File
@@ -1,14 +0,0 @@
package alloc_test
import (
"math"
"testing"
"github.com/ncruces/go-sqlite3/internal/alloc"
)
func TestVirtual(t *testing.T) {
defer func() { _ = recover() }()
alloc.NewMemory(math.MaxInt+2, math.MaxInt+2)
t.Error("want panic")
}
-76
View File
@@ -1,76 +0,0 @@
//go:build unix
package alloc
import (
"math"
"golang.org/x/sys/unix"
"github.com/tetratelabs/wazero/experimental"
)
func NewMemory(cap, max uint64) experimental.LinearMemory {
// Round up to the page size.
rnd := uint64(unix.Getpagesize() - 1)
res := (max + rnd) &^ rnd
if res > math.MaxInt {
// This ensures int(res) overflows to a negative value,
// and unix.Mmap returns EINVAL.
res = math.MaxUint64
}
com := res
prot := unix.PROT_READ | unix.PROT_WRITE
if cap < max { // Commit memory only if cap=max.
com = 0
prot = unix.PROT_NONE
}
// Reserve res bytes of address space, to ensure we won't need to move it.
// A protected, private, anonymous mapping should not commit memory.
b, err := unix.Mmap(-1, 0, int(res), prot, unix.MAP_PRIVATE|unix.MAP_ANON)
if err != nil {
panic(err)
}
return &mmappedMemory{buf: b[:com]}
}
// The slice covers the entire mmapped memory:
// - len(buf) is the already committed memory,
// - cap(buf) is the reserved address space.
type mmappedMemory struct {
buf []byte
}
func (m *mmappedMemory) Reallocate(size uint64) []byte {
com := uint64(len(m.buf))
res := uint64(cap(m.buf))
if com < size && size <= res {
// Grow geometrically, round up to the page size.
rnd := uint64(unix.Getpagesize() - 1)
new := com + com>>3
new = min(max(size, new), res)
new = (new + rnd) &^ rnd
// Commit additional memory up to new bytes.
err := unix.Mprotect(m.buf[com:new], unix.PROT_READ|unix.PROT_WRITE)
if err != nil {
return nil
}
m.buf = m.buf[:new] // Update committed memory.
}
// Limit returned capacity because bytes beyond
// len(m.buf) have not yet been committed.
return m.buf[:size:len(m.buf)]
}
func (m *mmappedMemory) Free() {
err := unix.Munmap(m.buf[:cap(m.buf)])
if err != nil {
panic(err)
}
m.buf = nil
}
-83
View File
@@ -1,83 +0,0 @@
package alloc
import (
"math"
"reflect"
"unsafe"
"golang.org/x/sys/windows"
"github.com/tetratelabs/wazero/experimental"
)
func NewMemory(cap, max uint64) experimental.LinearMemory {
// Round up to the page size.
rnd := uint64(windows.Getpagesize() - 1)
res := (max + rnd) &^ rnd
if res > math.MaxInt {
// This ensures uintptr(res) overflows to a large value,
// and windows.VirtualAlloc returns an error.
res = math.MaxUint64
}
com := res
kind := windows.MEM_COMMIT
if cap < max { // Commit memory only if cap=max.
com = 0
kind = windows.MEM_RESERVE
}
// Reserve res bytes of address space, to ensure we won't need to move it.
r, err := windows.VirtualAlloc(0, uintptr(res), uint32(kind), windows.PAGE_READWRITE)
if err != nil {
panic(err)
}
mem := virtualMemory{addr: r}
// SliceHeader, although deprecated, avoids a go vet warning.
sh := (*reflect.SliceHeader)(unsafe.Pointer(&mem.buf))
sh.Data = r
sh.Len = int(com)
sh.Cap = int(res)
return &mem
}
// The slice covers the entire mmapped memory:
// - len(buf) is the already committed memory,
// - cap(buf) is the reserved address space.
type virtualMemory struct {
buf []byte
addr uintptr
}
func (m *virtualMemory) Reallocate(size uint64) []byte {
com := uint64(len(m.buf))
res := uint64(cap(m.buf))
if com < size && size <= res {
// Grow geometrically, round up to the page size.
rnd := uint64(windows.Getpagesize() - 1)
new := com + com>>3
new = min(max(size, new), res)
new = (new + rnd) &^ rnd
// Commit additional memory up to new bytes.
_, err := windows.VirtualAlloc(m.addr, uintptr(new), windows.MEM_COMMIT, windows.PAGE_READWRITE)
if err != nil {
return nil
}
m.buf = m.buf[:new] // Update committed memory.
}
// Limit returned capacity because bytes beyond
// len(m.buf) have not yet been committed.
return m.buf[:size:len(m.buf)]
}
func (m *virtualMemory) Free() {
err := windows.VirtualFree(m.addr, 0, windows.MEM_RELEASE)
if err != nil {
panic(err)
}
m.addr = 0
}
+41
View File
@@ -0,0 +1,41 @@
package errutil
import (
"runtime"
"strconv"
)
type ErrorString string
func (e ErrorString) Error() string { return string(e) }
const (
NilErr = ErrorString("sqlite3: invalid memory address or null pointer dereference")
OOMErr = ErrorString("sqlite3: out of memory")
NoNulErr = ErrorString("sqlite3: missing NUL terminator")
TimeErr = ErrorString("sqlite3: invalid time value")
WhenceErr = ErrorString("sqlite3: invalid whence")
OffsetErr = ErrorString("sqlite3: invalid offset")
TailErr = ErrorString("sqlite3: multiple statements")
IsolationErr = ErrorString("sqlite3: unsupported isolation level")
ValueErr = ErrorString("sqlite3: unsupported value")
NoVFSErr = ErrorString("sqlite3: no such vfs: ")
)
func AssertErr() ErrorString {
msg := "sqlite3: assertion failed"
if _, file, line, ok := runtime.Caller(1); ok {
msg += " (" + file + ":" + strconv.Itoa(line) + ")"
}
return ErrorString(msg)
}
type ErrorJoiner []error
func (j *ErrorJoiner) Join(errs ...error) {
for _, err := range errs {
if err != nil {
*j = append(*j, err)
}
}
}
@@ -1,4 +1,4 @@
package util
package errutil
import "testing"
+112
View File
@@ -0,0 +1,112 @@
package sqlite3_wrap
import "github.com/ncruces/go-sqlite3/internal/errutil"
func (w *Wrapper) Free(ptr Ptr_t) {
if ptr == 0 {
return
}
w.Xsqlite3_free(int32(ptr))
}
func (w *Wrapper) New(size int64) Ptr_t {
ptr := Ptr_t(w.Xsqlite3_malloc64(size))
if ptr == 0 && size != 0 {
panic(errutil.OOMErr)
}
return ptr
}
func (w *Wrapper) Realloc(ptr Ptr_t, size int64) Ptr_t {
ptr = Ptr_t(w.Xsqlite3_realloc64(int32(ptr), size))
if ptr == 0 && size != 0 {
panic(errutil.OOMErr)
}
return ptr
}
func (w *Wrapper) NewBytes(b []byte) Ptr_t {
if len(b) == 0 {
return 0
}
ptr := w.New(int64(len(b)))
w.WriteBytes(ptr, b)
return ptr
}
func (w *Wrapper) NewString(s string) Ptr_t {
ptr := w.New(int64(len(s)) + 1)
w.WriteString(ptr, s)
return ptr
}
const arenaSize = 4096
func (w *Wrapper) NewArena() Arena {
return Arena{
sqlt: w,
base: w.New(arenaSize),
}
}
type Arena struct {
sqlt *Wrapper
ptrs []Ptr_t
base Ptr_t
next int32
}
func (a *Arena) Free() {
if a.sqlt == nil {
return
}
for _, ptr := range a.ptrs {
a.sqlt.Free(ptr)
}
a.sqlt.Free(a.base)
a.sqlt = nil
}
func (a *Arena) Mark() (reset func()) {
ptrs := len(a.ptrs)
next := a.next
return func() {
for _, ptr := range a.ptrs[ptrs:] {
a.sqlt.Free(ptr)
}
a.ptrs = a.ptrs[:ptrs]
a.next = next
}
}
func (a *Arena) New(size int64) Ptr_t {
// Align the next address, to 4 or 8 bytes.
if size&7 != 0 {
a.next = (a.next + 3) &^ 3
} else {
a.next = (a.next + 7) &^ 7
}
if size <= arenaSize-int64(a.next) {
ptr := a.base + Ptr_t(a.next)
a.next += int32(size)
return Ptr_t(ptr)
}
ptr := a.sqlt.New(size)
a.ptrs = append(a.ptrs, ptr)
return Ptr_t(ptr)
}
func (a *Arena) Bytes(b []byte) Ptr_t {
if len(b) == 0 {
return 0
}
ptr := a.New(int64(len(b)))
a.sqlt.WriteBytes(ptr, b)
return ptr
}
func (a *Arena) String(s string) Ptr_t {
ptr := a.New(int64(len(s)) + 1)
a.sqlt.WriteString(ptr, s)
return ptr
}
@@ -1,4 +1,4 @@
package util
package sqlite3_wrap
// https://sqlite.com/rescode.html
const (
@@ -1,37 +1,4 @@
package util
import (
"runtime"
"strconv"
)
type ErrorString string
func (e ErrorString) Error() string { return string(e) }
const (
NilErr = ErrorString("sqlite3: invalid memory address or null pointer dereference")
OOMErr = ErrorString("sqlite3: out of memory")
RangeErr = ErrorString("sqlite3: index out of range")
NoNulErr = ErrorString("sqlite3: missing NUL terminator")
NoBinaryErr = ErrorString("sqlite3: no SQLite binary embed/set/loaded")
BadBinaryErr = ErrorString("sqlite3: invalid SQLite binary embed/set/loaded")
TimeErr = ErrorString("sqlite3: invalid time value")
WhenceErr = ErrorString("sqlite3: invalid whence")
OffsetErr = ErrorString("sqlite3: invalid offset")
TailErr = ErrorString("sqlite3: multiple statements")
IsolationErr = ErrorString("sqlite3: unsupported isolation level")
ValueErr = ErrorString("sqlite3: unsupported value")
NoVFSErr = ErrorString("sqlite3: no such vfs: ")
)
func AssertErr() ErrorString {
msg := "sqlite3: assertion failed"
if _, file, line, ok := runtime.Caller(1); ok {
msg += " (" + file + ":" + strconv.Itoa(line) + ")"
}
return ErrorString(msg)
}
package sqlite3_wrap
type errorCode interface {
~uint8 | ~uint16 | ~uint32 | ~int32
@@ -108,13 +75,3 @@ func ErrorCodeString[T errorCode](rc T) string {
}
return "sqlite3: unknown error"
}
type ErrorJoiner []error
func (j *ErrorJoiner) Join(errs ...error) {
for _, err := range errs {
if err != nil {
*j = append(*j, err)
}
}
}

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