Compare commits

..
1 Commits
Author SHA1 Message Date
Nuno Cruces 5842ec9343 SQLite 3.51.3. 2026-03-13 15:22:36 +00:00
294 changed files with 16955 additions and 4582 deletions
+15
View File
@@ -0,0 +1,15 @@
#!/usr/bin/env bash
set -euo pipefail
echo 'set -eux' > test.sh
for p in $(go list ./...); do
dir=".${p#github.com/ncruces/go-sqlite3}"
name="$(basename "$p").test"
(cd ${dir}; go test -c ${BUILDFLAGS:-})
[ -f "${dir}/${name}" ] && echo "(cd ${dir}; ./${name} ${TESTFLAGS:-})" >> test.sh
done
if [[ -v VMACTIONS ]]; then
envsubst < .github/actions/vmactions/template.yml > .github/actions/vmactions/action.yml
fi
+10
View File
@@ -0,0 +1,10 @@
#!/usr/bin/env bash
set -euo pipefail
# Download and build SQLite
sqlite3/download.sh
sqlite3/tools.sh
embed/build.sh
# Check diffs
git diff --exit-code
+30
View File
@@ -0,0 +1,30 @@
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@v4
if: matrix.os == 'ubuntu-latest'
with:
subject-path: |
embed/sqlite3.wasm
+252
View File
@@ -0,0 +1,252 @@
name: Test
on:
push:
branches: [ 'main' ]
paths:
- '**.go'
- '**.mod'
- '**.wasm'
- '**.yml'
pull_request:
branches: [ 'main' ]
paths:
- '**.go'
- '**.mod'
- '**.wasm'
- '**.yml'
workflow_dispatch:
permissions:
contents: read
jobs:
test:
strategy:
matrix:
os: [macos-latest, ubuntu-latest, windows-latest]
runs-on: ${{ matrix.os }}
permissions:
contents: write
steps:
- uses: actions/checkout@v6
- uses: actions/setup-go@v6
with: { go-version: stable }
- name: Format
run: gofmt -s -w . && git diff --exit-code
if: matrix.os != 'windows-latest'
- name: Tidy
run: go mod tidy && git diff --exit-code
- name: Download
run: go mod download
- name: Verify
run: go mod verify
- name: Vet
run: go vet ./...
- name: Build
run: go build ./...
- name: Test
run: go test ./... -bench . -benchtime=1x
- name: Test BSD locks
run: go test -tags sqlite3_flock ./...
if: matrix.os != 'windows-latest'
- name: Test dot locks
run: go test -tags sqlite3_dotlk ./...
if: matrix.os != 'windows-latest'
- name: Test modules
shell: bash
run: |
go work init .
go work use -r gormlite
go test ./gormlite
- name: Test GORM
shell: bash
run: gormlite/test.sh
if: matrix.os == 'ubuntu-latest'
- name: Collect coverage
run: |
go get -tool github.com/dave/courtney@v0.4.4
go tool courtney
if: |
github.event_name == 'push' &&
matrix.os == 'ubuntu-latest'
- uses: ncruces/go-coverage-report@v0
with:
coverage-file: coverage.out
chart: true
amend: true
if: |
github.event_name == 'push' &&
matrix.os == 'ubuntu-latest'
test-cross:
strategy:
matrix:
os:
- name: freebsd
version: '15.0'
- name: netbsd
version: '10.1'
- name: illumos
action: omnios
version: 'r151056'
- name: openbsd
version: '7.8'
tflags: '-test.short'
- name: freebsd
arch: arm64
version: '15.0'
tflags: '-test.short'
- name: netbsd
arch: arm64
version: '10.1'
tflags: '-test.short'
runs-on: ubuntu-latest
needs: test
steps:
- uses: actions/checkout@v6
- name: Build
env:
GOOS: ${{ matrix.os.name }}
GOARCH: ${{ matrix.os.arch }}
TESTFLAGS: ${{ matrix.os.tflags }}
run: .github/workflows/build-test.sh
- name: Test
uses: cross-platform-actions/action@v0.32.0
with:
operating_system: ${{ matrix.os.action || matrix.os.name }}
architecture: ${{ matrix.os.arch }}
version: ${{ matrix.os.version }}
shell: bash
run: . ./test.sh
sync_files: runner-to-vm
test-vm:
strategy:
matrix:
os:
- name: dragonfly
action: 'vmactions/dragonflybsd-vm@v1'
- name: illumos
action: 'vmactions/openindiana-vm@v0'
- name: solaris
action: 'vmactions/solaris-vm@v1'
bflags: '-tags sqlite3_dotlk'
runs-on: ubuntu-latest
needs: test
steps:
- uses: actions/checkout@v6
- name: Build
env:
GOOS: ${{ matrix.os.name }}
BUILDFLAGS: ${{ matrix.os.bflags }}
TESTFLAGS: ${{ matrix.os.tflags }}
VMACTIONS: ${{ matrix.os.action }}
run: .github/workflows/build-test.sh
- 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@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 ./...
- name: Test riscv64 (interpreter)
run: GOARCH=riscv64 go test -short ./...
- name: Test ppc64le (interpreter)
run: GOARCH=ppc64le go test -short ./...
- name: Test loong64 (interpreter)
run: GOARCH=loong64 go test -short ./...
- name: Test s390x (big-endian)
run: GOARCH=s390x go test -short -tags sqlite3_dotlk ./...
test-linuxarm:
runs-on: ubuntu-24.04-arm
needs: test
steps:
- uses: actions/checkout@v6
- uses: actions/setup-go@v6
with: { go-version: stable }
- name: Test
run: go test ./...
- name: Test arm (32-bit)
run: GOARCH=arm GOARM=7 go test -short ./...
test-macintel:
runs-on: macos-15-intel
needs: test
steps:
- uses: actions/checkout@v6
- uses: actions/setup-go@v6
with: { go-version: stable }
- name: Test
run: go test ./...
test-winarm:
runs-on: windows-11-arm
needs: test
steps:
- uses: actions/checkout@v6
- uses: actions/setup-go@v6
with: { go-version: stable }
- name: Test
run: go test ./...
-20
View File
@@ -1,20 +0,0 @@
# Fork of ncruces/go-sqlite3. Allowlist UPSTREAM test fixtures + example crypto
# keys (SHA-256/MD5 of the empty string used in the adiantum/XTS VFS tests, and
# the SQLite C amalgamation). Hanzo additions (hanzovfs/, cmd/) are still scanned.
[extend]
useDefault = true
[allowlist]
description = "Upstream SQLite + crypto-VFS test fixtures (not real secrets)"
paths = [
'''sqlite3/sqlite3\.c''',
'''sqlite3/sqlite3ext\.h''',
'''vfs/adiantum/.*''',
'''vfs/xts/.*''',
'''vfs/tests/.*''',
'''tests/.*''',
]
regexes = [
'''e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855''',
'''d41d8cd98f00b204e9800998ecf8427e''',
]
-35
View File
@@ -1,35 +0,0 @@
# CI/CD for hanzoai/platform (the Hanzo PaaS) — NOT GitHub Actions.
# The platform runs `make ci` as a build step and/or a scheduled (cron) task.
# Works identically locally and anywhere: `make ci`.
SHELL := /usr/bin/env bash
export GOPRIVATE := github.com/hanzoai,github.com/luxfi
export GOFLAGS := -mod=mod
BIN := $(CURDIR)/.bin
export PATH := $(BIN):$(PATH)
GITLEAKS_VERSION ?= 8.21.2
ARCH := $(shell uname -m | sed 's/x86_64/x64/; s/aarch64/arm64/')
.PHONY: ci build test scan vuln secrets sbom tools clean
ci: build test scan ## full pipeline (build -> test -> scan)
build:
go build ./...
test:
go test ./...
scan: vuln secrets sbom
vuln:
go install golang.org/x/vuln/cmd/govulncheck@latest
govulncheck ./...
secrets: $(BIN)/gitleaks
gitleaks detect --source . --redact --no-banner --exit-code 1
sbom: $(BIN)/syft
syft . -o cyclonedx-json=sbom.cyclonedx.json -q
tools: $(BIN)/gitleaks $(BIN)/syft
clean:
rm -rf $(BIN) sbom.cyclonedx.json
$(BIN)/gitleaks:
@mkdir -p $(BIN)
curl -sSfL "https://github.com/gitleaks/gitleaks/releases/download/v$(GITLEAKS_VERSION)/gitleaks_$(GITLEAKS_VERSION)_linux_$(ARCH).tar.gz" | tar -xz -C $(BIN) gitleaks
$(BIN)/syft:
@mkdir -p $(BIN)
curl -sSfL https://raw.githubusercontent.com/anchore/syft/main/install.sh | sh -s -- -b $(BIN) >/dev/null
-7
View File
@@ -1,7 +0,0 @@
github.com/hanzoai/sqlite3 — canonical Hanzo SQLite.
Fork of github.com/ncruces/go-sqlite3 (MIT, Copyright (c) 2023 Nuno Cruces).
The upstream MIT LICENSE is retained in full. Hanzo additions (the hanzovfs
native encrypted VFS, module path, build pins) are likewise MIT-licensed.
Pure-Go, no cgo. WASM SQLite blob from github.com/ncruces/go-sqlite3-wasm (upstream).
-15
View File
@@ -1,15 +0,0 @@
# CI/CD on hanzoai/platform — no GitHub Actions
This repo is built, run, and scanned by **hanzoai/platform** (the Hanzo PaaS),
not GitHub Actions. `.github/workflows` is intentionally absent.
- **Build / run** — the platform builds via Dockerfile / Nixpacks / Railpack and
runs the result; auto-deploy on push.
- **Actions (CI)** — a platform **Scheduled Task** (and/or build step) runs
`make ci` = `build → test → scan` (govulncheck + gitleaks + CycloneDX SBOM),
on push and on a cron. SOC 2 CC7.x/CC8.x evidence (artifacts to hanzoai/audit).
- The platform build env carries Git credentials for `github.com/hanzoai` +
`github.com/luxfi`, so cross-org private modules resolve natively — no per-repo
token dance (the thing that made GitHub Actions painful).
Run anywhere: `make ci`.
+19 -26
View File
@@ -1,4 +1,4 @@
# Go bindings to SQLite using wasm2go
# Go bindings to SQLite using wazero
[![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 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.
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.
### Getting started
@@ -19,6 +19,7 @@ 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")
@@ -33,6 +34,8 @@ 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)
@@ -55,10 +58,13 @@ db.QueryRow(`SELECT sqlite_version()`).Scan(&version)
- [JSON support](https://sqlite.org/json1.html)
([example](https://pkg.go.dev/github.com/ncruces/go-sqlite3/driver#example-package-Json))
- [math functions](https://sqlite.org/lang_mathfunc.html)
- [full-text search](https://sqlite.org/fts5.html)
- [geospatial search](https://sqlite.org/geopoly.html)
- [Unicode support](https://pkg.go.dev/github.com/ncruces/go-sqlite3/ext/unicode)
- [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
@@ -73,7 +79,9 @@ 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) thorough testing.
It also benefits greatly from [SQLite's](https://sqlite.org/testing.html) and
[wazero's](https://tetrate.io/blog/introducing-wazero-from-tetrate/#:~:text=Rock%2Dsolid%20test%20approach)
thorough testing.
Every commit is tested on:
* Linux: amd64, arm64, 386, arm, riscv64, ppc64le, loong64, s390x
@@ -87,8 +95,7 @@ Every commit is tested on:
* illumos: amd64
* Solaris: amd64
Certain operating system and CPU combinations have some limitations.
See the [support matrix](https://github.com/ncruces/go-sqlite3/wiki/Support-matrix) for a complete overview.
Certain operating system and CPU combinations have some limitations. See the [support matrix](https://github.com/ncruces/go-sqlite3/wiki/Support-matrix) for a complete overview.
The Go VFS is tested by running SQLite's
[mptest](https://github.com/sqlite/sqlite/blob/master/mptest/mptest.c).
@@ -121,23 +128,9 @@ have an [idea](https://github.com/ncruces/go-sqlite3/discussions/categories/idea
The [Issue](https://github.com/ncruces/go-sqlite3/issues) tracker is for bugs,
and features we're working on, planning to work on, or asking for help with.
---
### Alternatives
## Hanzo fork
`github.com/hanzoai/sqlite3` is the **canonical Hanzo SQLite** — a fork of
`ncruces/go-sqlite3` (MIT) adding the **`hanzovfs`** native encrypted VFS for
per-tenant SQLite ⇒ `hanzoai/vfs` ⇒ S3 (HIP-0302 / HIP-0107), no FUSE, no cgo.
```go
import (
"github.com/hanzoai/sqlite3"
_ "github.com/hanzoai/sqlite3/hanzovfs" // registers vfs=hanzo
_ "github.com/hanzoai/sqlite3/embed"
)
db, _ := sqlite3.Open("file:/orgs/acme/app.db?vfs=hanzo")
```
**Deprecates** `modernc.org/sqlite` (no custom-VFS hook) and direct use of
`ncruces/go-sqlite3`. Benchmarked 3.6× faster writes / ~92× faster point-reads
than the FUSE-mounted path; per-page AEAD encryption costs ~0.
- [`modernc.org/sqlite`](https://pkg.go.dev/modernc.org/sqlite)
- [`crawshaw.io/sqlite`](https://pkg.go.dev/crawshaw.io/sqlite)
- [`github.com/mattn/go-sqlite3`](https://pkg.go.dev/github.com/mattn/go-sqlite3)
- [`github.com/zombiezen/go-sqlite`](https://pkg.go.dev/github.com/zombiezen/go-sqlite)
+14 -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.wrp.Xsqlite3_backup_init(
int32(dst), int32(dstPtr),
int32(src), int32(srcPtr)))
ptr := ptr_t(c.call("sqlite3_backup_init",
stk_t(dst), stk_t(dstPtr),
stk_t(src), stk_t(srcPtr)))
if ptr == 0 {
defer c.closeDB(other)
rc := res_t(c.wrp.Xsqlite3_errcode(int32(dst)))
return nil, c.errorFor(dst, rc)
rc := res_t(c.call("sqlite3_errcode", stk_t(dst)))
return nil, c.sqlite.error(rc, dst)
}
return &Backup{
@@ -97,7 +97,7 @@ func (b *Backup) Close() error {
return nil
}
rc := res_t(b.c.wrp.Xsqlite3_backup_finish(int32(b.handle)))
rc := res_t(b.c.call("sqlite3_backup_finish", stk_t(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.wrp.Xsqlite3_backup_step(int32(b.handle), int32(nPage)))
rc := res_t(b.c.call("sqlite3_backup_step", stk_t(b.handle), stk_t(nPage)))
if rc == _DONE {
return true, nil
}
@@ -120,7 +120,8 @@ func (b *Backup) Step(nPage int) (done bool, err error) {
//
// https://sqlite.org/c3ref/backup_finish.html#sqlite3backupremaining
func (b *Backup) Remaining() int {
return int(b.c.wrp.Xsqlite3_backup_remaining(int32(b.handle)))
n := int32(b.c.call("sqlite3_backup_remaining", stk_t(b.handle)))
return int(n)
}
// PageCount returns the total number of pages in the source database
@@ -128,5 +129,6 @@ func (b *Backup) Remaining() int {
//
// https://sqlite.org/c3ref/backup_finish.html#sqlite3backuppagecount
func (b *Backup) PageCount() int {
return int(b.c.wrp.Xsqlite3_backup_pagecount(int32(b.handle)))
n := int32(b.c.call("sqlite3_backup_pagecount", stk_t(b.handle)))
return int(n)
}
+33 -33
View File
@@ -3,7 +3,7 @@ package sqlite3
import (
"io"
"github.com/hanzoai/sqlite3/internal/errutil"
"github.com/ncruces/go-sqlite3/internal/util"
)
// 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.wrp.Xsqlite3_blob_open(int32(c.handle),
int32(dbPtr), int32(tablePtr), int32(columnPtr),
row, flags, int32(blobPtr)))
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)))
if err := c.error(rc); err != nil {
return nil, err
}
blob := Blob{c: c}
blob.handle = ptr_t(c.wrp.Read32(blobPtr))
blob.bytes = int64(c.wrp.Xsqlite3_blob_bytes(int32(blob.handle)))
blob.handle = util.Read32[ptr_t](c.mod, blobPtr)
blob.bytes = int64(int32(c.call("sqlite3_blob_bytes", stk_t(blob.handle))))
return &blob, nil
}
@@ -70,8 +70,8 @@ func (b *Blob) Close() error {
return nil
}
rc := res_t(b.c.wrp.Xsqlite3_blob_close(int32(b.handle)))
b.c.wrp.Free(b.bufptr)
rc := res_t(b.c.call("sqlite3_blob_close", stk_t(b.handle)))
b.c.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.wrp.Realloc(b.bufptr, want)
b.bufptr = b.c.realloc(b.bufptr, want)
b.buflen = want
}
rc := res_t(b.c.wrp.Xsqlite3_blob_read(int32(b.handle),
int32(b.bufptr), int32(want), int32(b.offset)))
rc := res_t(b.c.call("sqlite3_blob_read", stk_t(b.handle),
stk_t(b.bufptr), stk_t(want), stk_t(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, b.c.wrp.Bytes(b.bufptr, want))
copy(p, util.View(b.c.mod, 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.wrp.Realloc(b.bufptr, want)
b.bufptr = b.c.realloc(b.bufptr, want)
b.buflen = want
}
for want > 0 {
rc := res_t(b.c.wrp.Xsqlite3_blob_read(int32(b.handle),
int32(b.bufptr), int32(want), int32(b.offset)))
rc := res_t(b.c.call("sqlite3_blob_read", stk_t(b.handle),
stk_t(b.bufptr), stk_t(want), stk_t(b.offset)))
err = b.c.error(rc)
if err != nil {
return n, err
}
mem := b.c.wrp.Bytes(b.bufptr, want)
mem := util.View(b.c.mod, 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.wrp.Realloc(b.bufptr, want)
b.bufptr = b.c.realloc(b.bufptr, want)
b.buflen = want
}
b.c.wrp.WriteBytes(b.bufptr, p)
util.WriteBytes(b.c.mod, b.bufptr, p)
rc := res_t(b.c.wrp.Xsqlite3_blob_write(int32(b.handle),
int32(b.bufptr), int32(want), int32(b.offset)))
rc := res_t(b.c.call("sqlite3_blob_write", stk_t(b.handle),
stk_t(b.bufptr), stk_t(want), stk_t(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.wrp.Realloc(b.bufptr, want)
b.bufptr = b.c.realloc(b.bufptr, want)
b.buflen = want
}
for {
mem := b.c.wrp.Bytes(b.bufptr, want)
mem := util.View(b.c.mod, b.bufptr, want)
m, err := r.Read(mem[:want])
if m > 0 {
rc := res_t(b.c.wrp.Xsqlite3_blob_write(int32(b.handle),
int32(b.bufptr), int32(m), int32(b.offset)))
rc := res_t(b.c.call("sqlite3_blob_write", stk_t(b.handle),
stk_t(b.bufptr), stk_t(m), stk_t(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, errutil.WhenceErr
return 0, util.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, errutil.OffsetErr
return 0, util.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.wrp.Xsqlite3_blob_reopen(int32(b.handle), row)))
b.bytes = int64(b.c.wrp.Xsqlite3_blob_bytes(int32(b.handle)))
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))))
b.offset = 0
return err
}
-48
View File
@@ -1,48 +0,0 @@
// hanzovfs-bench: native PQ SQLite VFS throughput (in-process, no FUSE).
// go run github.com/hanzoai/sqlite3/cmd/hanzovfs-bench@latest
package main
import (
"fmt"
"os"
"runtime"
"time"
"github.com/hanzoai/sqlite3"
_ "github.com/hanzoai/sqlite3/embed"
"github.com/hanzoai/sqlite3/hanzovfs"
"github.com/luxfi/age"
)
func bench(label, dsn string) {
db, err := sqlite3.Open(dsn)
if err != nil { fmt.Printf("%-24s OPEN ERR %v\n", label, err); return }
defer db.Close()
db.Exec(`PRAGMA journal_mode=MEMORY; PRAGMA synchronous=OFF;`)
db.Exec(`CREATE TABLE t(id INTEGER PRIMARY KEY, email TEXT, body TEXT)`)
const n = 20000
t0 := time.Now()
db.Exec("BEGIN")
st, _, _ := db.Prepare("INSERT INTO t(email,body) VALUES(?,?)")
for i := 0; i < n; i++ {
st.BindText(1, fmt.Sprintf("u%d@hanzo.ai", i))
st.BindText(2, "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx")
st.Step(); st.Reset()
}
st.Close(); db.Exec("COMMIT")
ins := float64(n) / time.Since(t0).Seconds()
t0 = time.Now()
sel, _, _ := db.Prepare("SELECT body FROM t WHERE id=?")
for i := 0; i < 5000; i++ { sel.BindInt(1, 1+(i*7)%n); sel.Step(); sel.Reset() }
sel.Close()
fmt.Printf("%-24s insert=%9.0f rows/s point_sel=%9.0f q/s\n", label, ins, 5000/time.Since(t0).Seconds())
}
func main() {
fmt.Printf("hanzovfs-bench %s/%s (%d CPU)\n", runtime.GOOS, runtime.GOARCH, runtime.NumCPU())
id, rcpt, err := hanzovfs.GenerateIdentity()
if err != nil { panic(err) }
dir, _ := os.MkdirTemp("", "hvfs")
hanzovfs.Register("pq", hanzovfs.Config{Dir: dir, Recipients: []age.Recipient{rcpt}, Identities: []age.Identity{id}})
bench("native PQ VFS (ML-KEM)", "file:/orgs/acme/bench.db?vfs=pq")
}
+103 -99
View File
@@ -1,12 +1,15 @@
package sqlite3
import (
"context"
"fmt"
"strconv"
"sync/atomic"
"github.com/hanzoai/sqlite3/internal/errutil"
"github.com/hanzoai/sqlite3/vfs"
"github.com/tetratelabs/wazero/api"
"github.com/ncruces/go-sqlite3/internal/util"
"github.com/ncruces/go-sqlite3/vfs"
)
// Config makes configuration changes to a database connection.
@@ -27,8 +30,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 {
@@ -38,12 +41,12 @@ func (c *Conn) Config(op DBConfig, arg ...bool) (bool, error) {
flag = 1
}
c.wrp.Write32(argsPtr+0*ptrlen, uint32(flag))
c.wrp.Write32(argsPtr+1*ptrlen, uint32(argsPtr))
util.Write32(c.mod, argsPtr+0*ptrlen, flag)
util.Write32(c.mod, argsPtr+1*ptrlen, argsPtr)
rc := res_t(c.wrp.Xsqlite3_db_config(int32(c.handle),
int32(op), int32(argsPtr)))
return c.wrp.ReadBool(argsPtr), c.error(rc)
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)
}
var defaultLogger atomic.Pointer[func(code ExtendedErrorCode, msg string)]
@@ -63,7 +66,7 @@ func (c *Conn) ConfigLog(cb func(code ExtendedErrorCode, msg string)) error {
if cb != nil {
enable = 1
}
rc := res_t(c.wrp.Xsqlite3_config_log_go(enable))
rc := res_t(c.call("sqlite3_config_log_go", stk_t(enable)))
if err := c.error(rc); err != nil {
return err
}
@@ -71,9 +74,9 @@ func (c *Conn) ConfigLog(cb func(code ExtendedErrorCode, msg string)) error {
return nil
}
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)
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)
c.log(xErrorCode(iCode), msg)
}
}
@@ -92,12 +95,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
@@ -107,9 +110,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.wrp.Xsqlite3_file_control(
int32(c.handle), int32(schemaPtr),
int32(op), 0))
rc = res_t(c.call("sqlite3_file_control",
stk_t(c.handle), stk_t(schemaPtr),
stk_t(op), 0))
case FCNTL_PERSIST_WAL, FCNTL_POWERSAFE_OVERWRITE:
var flag int32
@@ -119,50 +122,50 @@ func (c *Conn) FileControl(schema string, op FcntlOpcode, arg ...any) (any, erro
case arg[0]:
flag = 1
}
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)
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)
case FCNTL_CHUNK_SIZE:
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)))
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)))
case FCNTL_RESERVE_BYTES:
bytes := -1
if len(arg) > 0 {
bytes = arg[0].(int)
}
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)))
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))
case FCNTL_DATA_VERSION:
rc = res_t(c.wrp.Xsqlite3_file_control(
int32(c.handle), int32(schemaPtr),
int32(op), int32(ptr)))
ret = c.wrp.Read32(ptr)
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)
case FCNTL_LOCKSTATE:
rc = res_t(c.wrp.Xsqlite3_file_control(
int32(c.handle), int32(schemaPtr),
int32(op), int32(ptr)))
ret = vfs.LockLevel(c.wrp.Read32(ptr))
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)
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)))
rc = res_t(c.call("sqlite3_file_control",
stk_t(c.handle), stk_t(schemaPtr),
stk_t(FCNTL_VFS_POINTER), stk_t(ptr)))
if rc == _OK {
const zNameOffset = 16
ptr = ptr_t(c.wrp.Read32(ptr))
ptr = ptr_t(c.wrp.Read32(ptr + zNameOffset))
name := c.wrp.ReadString(ptr, _MAX_NAME)
ptr = util.Read32[ptr_t](c.mod, ptr)
ptr = util.Read32[ptr_t](c.mod, ptr+zNameOffset)
name := util.ReadString(c.mod, ptr, _MAX_NAME)
if op == FCNTL_VFS_POINTER {
ret = vfs.Find(name)
} else {
@@ -171,14 +174,14 @@ func (c *Conn) FileControl(schema string, op FcntlOpcode, arg ...any) (any, erro
}
case FCNTL_FILE_POINTER, FCNTL_JOURNAL_POINTER:
rc = res_t(c.wrp.Xsqlite3_file_control(
int32(c.handle), int32(schemaPtr),
int32(op), int32(ptr)))
rc = res_t(c.call("sqlite3_file_control",
stk_t(c.handle), stk_t(schemaPtr),
stk_t(op), stk_t(ptr)))
if rc == _OK {
const fileHandleOffset = 4
ptr = ptr_t(c.wrp.Read32(ptr))
ptr = ptr_t(c.wrp.Read32(ptr + fileHandleOffset))
ret = c.wrp.GetHandle(ptr)
ptr = util.Read32[ptr_t](c.mod, ptr)
ptr = util.Read32[ptr_t](c.mod, ptr+fileHandleOffset)
ret = util.GetHandle(c.ctx, ptr)
}
}
@@ -193,7 +196,8 @@ 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 {
return int(c.wrp.Xsqlite3_limit(int32(c.handle), int32(id), int32(value)))
v := int32(c.call("sqlite3_limit", stk_t(c.handle), stk_t(id), stk_t(value)))
return int(v)
}
// SetAuthorizer registers an authorizer callback with the database connection.
@@ -204,39 +208,40 @@ func (c *Conn) SetAuthorizer(cb func(action AuthorizerActionCode, name3rd, name4
if cb != nil {
enable = 1
}
rc := res_t(c.wrp.Xsqlite3_set_authorizer_go(int32(c.handle), enable))
rc := res_t(c.call("sqlite3_set_authorizer_go", stk_t(c.handle), stk_t(enable)))
if err := c.error(rc); err != nil {
return err
}
c.authorizer = cb
return 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 {
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 {
var name3rd, name4th, schema, inner string
if zName3rd != 0 {
name3rd = e.ReadString(ptr_t(zName3rd), _MAX_NAME)
name3rd = util.ReadString(mod, zName3rd, _MAX_NAME)
}
if zName4th != 0 {
name4th = e.ReadString(ptr_t(zName4th), _MAX_NAME)
name4th = util.ReadString(mod, zName4th, _MAX_NAME)
}
if zSchema != 0 {
schema = e.ReadString(ptr_t(zSchema), _MAX_NAME)
schema = util.ReadString(mod, zSchema, _MAX_NAME)
}
if zInner != 0 {
inner = e.ReadString(ptr_t(zInner), _MAX_NAME)
inner = util.ReadString(mod, zInner, _MAX_NAME)
}
return int32(c.authorizer(AuthorizerActionCode(action), name3rd, name4th, schema, inner))
rc = c.authorizer(action, name3rd, name4th, schema, inner)
}
return _OK
return rc
}
// 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, arg2 any) error) error {
rc := res_t(c.wrp.Xsqlite3_trace_go(int32(c.handle), int32(mask)))
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)))
if err := c.error(rc); err != nil {
return err
}
@@ -244,30 +249,30 @@ func (c *Conn) Trace(mask TraceEvent, cb func(evt TraceEvent, arg1, arg2 any) er
return 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 {
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 {
var arg1, arg2 any
if TraceEvent(evt) == TRACE_CLOSE {
if evt == TRACE_CLOSE {
arg1 = c
} else {
for _, s := range c.stmts {
if ptr_t(pArg1) == s.handle {
if pArg1 == s.handle {
arg1 = s
switch TraceEvent(evt) {
switch evt {
case TRACE_STMT:
arg2 = s.SQL()
case TRACE_PROFILE:
arg2 = int64(e.Read64(ptr_t(pArg2)))
arg2 = util.Read64[int64](mod, pArg2)
}
break
}
}
}
if arg1 != nil {
_ = c.trace(TraceEvent(evt), arg1, arg2)
_ = c.trace(evt, arg1, arg2)
}
}
return _OK
return rc
}
// WALCheckpoint checkpoints a WAL database.
@@ -278,15 +283,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.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)))
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))
return nLog, nCkpt, c.error(rc)
}
@@ -294,7 +299,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.wrp.Xsqlite3_wal_autocheckpoint(int32(c.handle), int32(pages)))
rc := res_t(c.call("sqlite3_wal_autocheckpoint", stk_t(c.handle), stk_t(pages)))
return c.error(rc)
}
@@ -307,18 +312,17 @@ func (c *Conn) WALHook(cb func(db *Conn, schema string, pages int) error) {
if cb != nil {
enable = 1
}
c.wrp.Xsqlite3_wal_hook_go(int32(c.handle), enable)
c.call("sqlite3_wal_hook_go", stk_t(c.handle), stk_t(enable))
c.wal = cb
}
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)
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)
err := c.wal(c, schema, int(pages))
_, rc := errorCode(err, ERROR)
return int32(rc)
_, rc = errorCode(err, ERROR)
}
return _OK
return rc
}
// AutoVacuumPages registers a autovacuum compaction amount callback.
@@ -327,30 +331,30 @@ func (e *env) Xgo_wal_hook(_, pDB, zSchema, pages int32) int32 {
func (c *Conn) AutoVacuumPages(cb func(schema string, dbPages, freePages, bytesPerPage uint) uint) error {
var funcPtr ptr_t
if cb != nil {
funcPtr = c.wrp.AddHandle(cb)
funcPtr = util.AddHandle(c.ctx, cb)
}
rc := res_t(c.wrp.Xsqlite3_autovacuum_pages_go(int32(c.handle), int32(funcPtr)))
rc := res_t(c.call("sqlite3_autovacuum_pages_go", stk_t(c.handle), stk_t(funcPtr)))
return c.error(rc)
}
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))))
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)))
}
// SoftHeapLimit imposes a soft limit on heap size.
//
// https://sqlite.org/c3ref/hard_heap_limit64.html
func (c *Conn) SoftHeapLimit(n int64) int64 {
return c.wrp.Xsqlite3_soft_heap_limit64(n)
return int64(c.call("sqlite3_soft_heap_limit64", stk_t(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 c.wrp.Xsqlite3_hard_heap_limit64(n)
return int64(c.call("sqlite3_hard_heap_limit64", stk_t(n)))
}
// EnableChecksums enables checksums on a database.
@@ -378,7 +382,7 @@ func (c *Conn) EnableChecksums(schema string) error {
}
if r != 8 {
// Invalid value.
return errutil.ErrorString("sqlite3: reserve bytes must be 8, is: " + strconv.Itoa(r.(int)))
return util.ErrorString("sqlite3: reserve bytes must be 8, is: " + strconv.Itoa(r.(int)))
}
// VACUUM the database.
+92 -129
View File
@@ -11,9 +11,10 @@ import (
"strings"
"time"
"github.com/hanzoai/sqlite3/internal/errutil"
"github.com/hanzoai/sqlite3/internal/sqlite3_wrap"
"github.com/hanzoai/sqlite3/vfs"
"github.com/tetratelabs/wazero/api"
"github.com/ncruces/go-sqlite3/internal/util"
"github.com/ncruces/go-sqlite3/vfs"
)
// Conn is a database connection handle.
@@ -21,7 +22,7 @@ import (
//
// https://sqlite.org/c3ref/sqlite3.html
type Conn struct {
wrp *sqlite3_wrap.Wrapper
*sqlite
interrupt context.Context
stmts []*Stmt
@@ -34,11 +35,10 @@ type Conn struct {
update func(AuthorizerActionCode, string, string, int64)
commit func() bool
rollback func()
preupdate func(PreUpdateData)
busy1st time.Time
busylst time.Time
arena sqlite3_wrap.Arena
arena arena
handle ptr_t
gosched uint8
}
@@ -69,6 +69,8 @@ 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 {
@@ -76,23 +78,24 @@ func newConn(ctx context.Context, filename string, flags OpenFlag) (ret *Conn, _
}
c := &Conn{interrupt: ctx}
c.wrp, err = createWrapper(ctx)
c.sqlite, err = instantiateSQLite()
if err != nil {
return nil, err
}
defer func() {
if ret == nil {
c.Close()
c.sqlite.close()
} else {
c.interrupt = context.Background()
}
}()
c.wrp.DB = c
c.ctx = context.WithValue(c.ctx, connKey{}, c)
if logger := defaultLogger.Load(); logger != nil {
c.ConfigLog(*logger)
}
c.arena = c.wrp.NewArena()
c.arena = c.newArena()
c.handle, err = c.openDB(filename, flags)
if err == nil {
err = initExtensions(c)
@@ -104,24 +107,24 @@ 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.wrp.Xsqlite3_open_v2(int32(namePtr), int32(connPtr), int32(flags), 0))
rc := res_t(c.call("sqlite3_open_v2", stk_t(namePtr), stk_t(connPtr), stk_t(flags), 0))
handle := ptr_t(c.wrp.Read32(connPtr))
if err := c.errorFor(handle, rc); err != nil {
handle := util.Read32[ptr_t](c.mod, connPtr)
if err := c.sqlite.error(rc, handle); err != nil {
c.closeDB(handle)
return 0, err
}
c.wrp.Xsqlite3_progress_handler_go(int32(handle), 1000)
c.call("sqlite3_progress_handler_go", stk_t(handle), 1000)
if flags|OPEN_URI != 0 && strings.HasPrefix(filename, "file:") {
var pragmas strings.Builder
if u, err := url.Parse(filename); err == nil {
query := u.Query()
if _, after, ok := strings.Cut(filename, "?"); ok {
query, _ := url.ParseQuery(after)
for _, p := range query["_pragma"] {
pragmas.WriteString(`PRAGMA `)
pragmas.WriteString(p)
@@ -129,9 +132,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.wrp.Xsqlite3_exec(int32(handle), int32(pragmaPtr), 0, 0, 0))
if err := c.errorFor(handle, rc, pragmas.String()); err != nil {
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 {
err = fmt.Errorf("sqlite3: invalid _pragma: %w", err)
c.closeDB(handle)
return 0, err
@@ -142,8 +145,8 @@ func (c *Conn) openDB(filename string, flags OpenFlag) (ptr_t, error) {
}
func (c *Conn) closeDB(handle ptr_t) {
rc := res_t(c.wrp.Xsqlite3_close_v2(int32(handle)))
if err := c.errorFor(handle, rc); err != nil {
rc := res_t(c.call("sqlite3_close_v2", stk_t(handle)))
if err := c.sqlite.error(rc, handle); err != nil {
panic(err)
}
}
@@ -162,13 +165,13 @@ func (c *Conn) Close() error {
return nil
}
rc := res_t(c.wrp.Xsqlite3_close(int32(c.handle)))
rc := res_t(c.call("sqlite3_close", stk_t(c.handle)))
if err := c.error(rc); err != nil {
return err
}
c.handle = 0
return c.wrp.Close()
return c.close()
}
// Exec is a convenience function that allows an application to run
@@ -183,9 +186,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.wrp.Xsqlite3_exec(int32(c.handle), int32(textPtr), 0, 0, 0))
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))
return c.error(rc, sql)
}
@@ -208,18 +211,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.wrp.Xsqlite3_prepare_v3(int32(c.handle),
int32(textPtr), int32(len(sql)+1), int32(flags),
int32(stmtPtr), int32(tailPtr)))
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)))
stmt = &Stmt{c: c, sql: sql}
stmt.handle = ptr_t(c.wrp.Read32(stmtPtr))
if sql := sql[ptr_t(c.wrp.Read32(tailPtr))-textPtr:]; sql != "" {
stmt.handle = util.Read32[ptr_t](c.mod, stmtPtr)
if sql := sql[util.Read32[ptr_t](c.mod, tailPtr)-textPtr:]; sql != "" {
tail = sql
}
@@ -237,11 +240,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.wrp.Xsqlite3_db_name(int32(c.handle), int32(n)))
ptr := ptr_t(c.call("sqlite3_db_name", stk_t(c.handle), stk_t(n)))
if ptr == 0 {
return ""
}
return c.wrp.ReadString(ptr, _MAX_NAME)
return util.ReadString(c.mod, ptr, _MAX_NAME)
}
// Filename returns the filename for a database.
@@ -250,23 +253,23 @@ 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.wrp.Xsqlite3_db_filename(int32(c.handle), int32(ptr)))
return vfs.GetFilename(c.wrp, ptr, vfs.OPEN_MAIN_DB)
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)
}
// ReadOnly determines if a database is read-only.
//
// https://sqlite.org/c3ref/db_readonly.html
func (c *Conn) ReadOnly(schema string) (ro, ok bool) {
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 := c.wrp.Xsqlite3_db_readonly(int32(c.handle), int32(ptr))
b := int32(c.call("sqlite3_db_readonly", stk_t(c.handle), stk_t(ptr)))
return b > 0, b < 0
}
@@ -274,7 +277,7 @@ func (c *Conn) ReadOnly(schema string) (ro, ok bool) {
//
// https://sqlite.org/c3ref/get_autocommit.html
func (c *Conn) GetAutocommit() bool {
b := c.wrp.Xsqlite3_get_autocommit(int32(c.handle))
b := int32(c.call("sqlite3_get_autocommit", stk_t(c.handle)))
return b != 0
}
@@ -283,7 +286,7 @@ func (c *Conn) GetAutocommit() bool {
//
// https://sqlite.org/c3ref/last_insert_rowid.html
func (c *Conn) LastInsertRowID() int64 {
return c.wrp.Xsqlite3_last_insert_rowid(int32(c.handle))
return int64(c.call("sqlite3_last_insert_rowid", stk_t(c.handle)))
}
// SetLastInsertRowID allows the application to set the value returned by
@@ -291,7 +294,7 @@ func (c *Conn) LastInsertRowID() int64 {
//
// https://sqlite.org/c3ref/set_last_insert_rowid.html
func (c *Conn) SetLastInsertRowID(id int64) {
c.wrp.Xsqlite3_set_last_insert_rowid(int32(c.handle), id)
c.call("sqlite3_set_last_insert_rowid", stk_t(c.handle), stk_t(id))
}
// Changes returns the number of rows modified, inserted or deleted
@@ -300,7 +303,7 @@ func (c *Conn) SetLastInsertRowID(id int64) {
//
// https://sqlite.org/c3ref/changes.html
func (c *Conn) Changes() int64 {
return c.wrp.Xsqlite3_changes64(int32(c.handle))
return int64(c.call("sqlite3_changes64", stk_t(c.handle)))
}
// TotalChanges returns the number of rows modified, inserted or deleted
@@ -309,14 +312,14 @@ func (c *Conn) Changes() int64 {
//
// https://sqlite.org/c3ref/total_changes.html
func (c *Conn) TotalChanges() int64 {
return c.wrp.Xsqlite3_total_changes64(int32(c.handle))
return int64(c.call("sqlite3_total_changes64", stk_t(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.wrp.Xsqlite3_db_release_memory(int32(c.handle)))
rc := res_t(c.call("sqlite3_db_release_memory", stk_t(c.handle)))
return c.error(rc)
}
@@ -348,8 +351,8 @@ func (c *Conn) SetInterrupt(ctx context.Context) (old context.Context) {
return old
}
func (e *env) Xgo_progress_handler(_ int32) (interrupt int32) {
if c, ok := e.DB.(*Conn); ok {
func progressCallback(ctx context.Context, mod api.Module, _ ptr_t) (interrupt int32) {
if c, ok := ctx.Value(connKey{}).(*Conn); ok {
if c.gosched++; c.gosched%16 == 0 {
runtime.Gosched()
}
@@ -365,13 +368,13 @@ func (e *env) Xgo_progress_handler(_ int32) (interrupt int32) {
// 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.wrp.Xsqlite3_busy_timeout(int32(c.handle), int32(ms)))
rc := res_t(c.call("sqlite3_busy_timeout", stk_t(c.handle), stk_t(ms)))
return c.error(rc)
}
func (e *env) Xgo_busy_timeout(count, tmout int32) (retry int32) {
func timeoutCallback(ctx context.Context, mod api.Module, 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 := e.DB.(*Conn); ok && c.interrupt.Err() == nil {
if c, ok := ctx.Value(connKey{}).(*Conn); ok && c.interrupt.Err() == nil {
switch {
case count == 0:
c.busy1st = time.Now()
@@ -396,7 +399,7 @@ func (c *Conn) BusyHandler(cb func(ctx context.Context, count int) (retry bool))
if cb != nil {
enable = 1
}
rc := res_t(c.wrp.Xsqlite3_busy_handler_go(int32(c.handle), enable))
rc := res_t(c.call("sqlite3_busy_handler_go", stk_t(c.handle), stk_t(enable)))
if err := c.error(rc); err != nil {
return err
}
@@ -404,8 +407,8 @@ func (c *Conn) BusyHandler(cb func(ctx context.Context, count int) (retry bool))
return 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 {
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 {
if interrupt := c.interrupt; interrupt.Err() == nil &&
c.busy(interrupt, int(count)) {
retry = 1
@@ -418,20 +421,20 @@ func (e *env) Xgo_busy_handler(pDB, count int32) (retry 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.wrp.Xsqlite3_db_status64(int32(c.handle),
int32(op), int32(curPtr), int32(hiPtr), i))
rc := res_t(c.call("sqlite3_db_status64", stk_t(c.handle),
stk_t(op), stk_t(curPtr), stk_t(hiPtr), stk_t(i)))
if err = c.error(rc); err == nil {
current = int64(c.wrp.Read64(curPtr))
highwater = int64(c.wrp.Read64(hiPtr))
current = util.Read64[int64](c.mod, curPtr)
highwater = util.Read64[int64](c.mod, hiPtr)
}
return
}
@@ -440,7 +443,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
@@ -451,78 +454,38 @@ 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.wrp.Xsqlite3_table_column_metadata(int32(c.handle),
int32(schemaPtr), int32(tablePtr), int32(columnPtr),
int32(declTypePtr), int32(collSeqPtr),
int32(notNullPtr), int32(primaryKeyPtr), int32(autoIncPtr)))
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)))
if err = c.error(rc); err == nil && column != "" {
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, declTypePtr); ptr != 0 {
declType = util.ReadString(c.mod, ptr, _MAX_NAME)
}
if ptr := ptr_t(c.wrp.Read32(collSeqPtr)); ptr != 0 {
collSeq = 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)
}
notNull = c.wrp.ReadBool(notNullPtr)
autoInc = c.wrp.ReadBool(autoIncPtr)
primaryKey = c.wrp.ReadBool(primaryKeyPtr)
notNull = util.ReadBool(c.mod, notNullPtr)
autoInc = util.ReadBool(c.mod, autoIncPtr)
primaryKey = util.ReadBool(c.mod, primaryKeyPtr)
}
return
}
func (c *Conn) error(rc res_t, sql ...string) error {
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 := 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)
return c.sqlite.error(rc, c.handle, sql...)
}
// Stmts returns an iterator for the prepared statements
+10 -9
View File
@@ -3,7 +3,7 @@ package sqlite3
import (
"strconv"
"github.com/hanzoai/sqlite3/internal/sqlite3_wrap"
"github.com/ncruces/go-sqlite3/internal/util"
)
const (
@@ -15,13 +15,14 @@ const (
_MAX_LENGTH = 1e9
_MAX_SQL_LENGTH = 1e9
ptrlen = sqlite3_wrap.PtrLen
intlen = sqlite3_wrap.IntLen
ptrlen = util.PtrLen
intlen = util.IntLen
)
type (
ptr_t = sqlite3_wrap.Ptr_t
res_t = sqlite3_wrap.Res_t
stk_t = util.Stk_t
ptr_t = util.Ptr_t
res_t = util.Res_t
)
// ErrorCode is a result code that [Error.Code] might return.
@@ -180,7 +181,7 @@ const (
PREPARE_NORMALIZE PrepareFlag = 0x02
PREPARE_NO_VTAB PrepareFlag = 0x04
PREPARE_DONT_LOG PrepareFlag = 0x10
PREPARE_FROM_DDL PrepareFlag = 0x20
// PREPARE_FROM_DDL PrepareFlag = 0x20
)
// FunctionFlag is a flag that can be passed to
@@ -267,8 +268,8 @@ const (
DBCONFIG_ENABLE_ATTACH_CREATE DBConfig = 1020
DBCONFIG_ENABLE_ATTACH_WRITE DBConfig = 1021
DBCONFIG_ENABLE_COMMENTS DBConfig = 1022
DBCONFIG_FP_DIGITS DBConfig = 1023
// DBCONFIG_MAX DBConfig = 1023
// SQLITE_DBCONFIG_FP_DIGITS DBConfig = 1023
// DBCONFIG_MAX DBConfig = 1022
)
// FcntlOpcode are the available opcodes for [Conn.FileControl].
@@ -309,7 +310,7 @@ const (
LIMIT_VARIABLE_NUMBER LimitCategory = 9
LIMIT_TRIGGER_DEPTH LimitCategory = 10
LIMIT_WORKER_THREADS LimitCategory = 11
LIMIT_PARSER_DEPTH LimitCategory = 12
// LIMIT_PARSER_DEPTH LimitCategory = 12
)
// AuthorizerActionCode are the integer action codes
+41 -40
View File
@@ -2,9 +2,10 @@ package sqlite3
import (
"errors"
"math"
"time"
"github.com/hanzoai/sqlite3/internal/errutil"
"github.com/ncruces/go-sqlite3/internal/util"
)
// Context is the context in which an SQL function executes.
@@ -29,16 +30,16 @@ func (ctx Context) Conn() *Conn {
//
// https://sqlite.org/c3ref/get_auxdata.html
func (ctx Context) SetAuxData(n int, data any) {
ptr := ctx.c.wrp.AddHandle(data)
ctx.c.wrp.Xsqlite3_set_auxdata_go(int32(ctx.handle), int32(n), int32(ptr))
ptr := util.AddHandle(ctx.c.ctx, data)
ctx.c.call("sqlite3_set_auxdata_go", stk_t(ctx.handle), stk_t(n), stk_t(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.wrp.Xsqlite3_get_auxdata(int32(ctx.handle), int32(n)))
return ctx.c.wrp.GetHandle(ptr)
ptr := ptr_t(ctx.c.call("sqlite3_get_auxdata", stk_t(ctx.handle), stk_t(n)))
return util.GetHandle(ctx.c.ctx, ptr)
}
// ResultBool sets the result of the function to a bool.
@@ -65,25 +66,25 @@ func (ctx Context) ResultInt(value int) {
//
// https://sqlite.org/c3ref/result_blob.html
func (ctx Context) ResultInt64(value int64) {
ctx.c.wrp.Xsqlite3_result_int64(
int32(ctx.handle), value)
ctx.c.call("sqlite3_result_int64",
stk_t(ctx.handle), stk_t(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.wrp.Xsqlite3_result_double(
int32(ctx.handle), value)
ctx.c.call("sqlite3_result_double",
stk_t(ctx.handle), stk_t(math.Float64bits(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.wrp.NewString(value)
ctx.c.wrp.Xsqlite3_result_text_go(
int32(ctx.handle), int32(ptr), int64(len(value)))
ptr := ctx.c.newString(value)
ctx.c.call("sqlite3_result_text_go",
stk_t(ctx.handle), stk_t(ptr), stk_t(len(value)))
}
// ResultRawText sets the text result of the function to a []byte.
@@ -101,25 +102,25 @@ func (ctx Context) ResultBlob(value []byte) {
ctx.ResultZeroBlob(0)
return
}
ptr := ctx.c.wrp.NewBytes(value)
ctx.c.wrp.Xsqlite3_result_blob_go(
int32(ctx.handle), int32(ptr), int64(len(value)))
ptr := ctx.c.newBytes(value)
ctx.c.call("sqlite3_result_blob_go",
stk_t(ctx.handle), stk_t(ptr), stk_t(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.wrp.Xsqlite3_result_zeroblob64(
int32(ctx.handle), n)
ctx.c.call("sqlite3_result_zeroblob64",
stk_t(ctx.handle), stk_t(n))
}
// ResultNull sets the result of the function to NULL.
//
// https://sqlite.org/c3ref/result_blob.html
func (ctx Context) ResultNull() {
ctx.c.wrp.Xsqlite3_result_null(
int32(ctx.handle))
ctx.c.call("sqlite3_result_null",
stk_t(ctx.handle))
}
// ResultTime sets the result of the function to a [time.Time].
@@ -139,19 +140,19 @@ func (ctx Context) ResultTime(value time.Time, format TimeFormat) {
case float64:
ctx.ResultFloat(v)
default:
panic(errutil.AssertErr())
panic(util.AssertErr())
}
}
func (ctx Context) resultRFC3339Nano(value time.Time) {
const maxlen = 48
ptr := ctx.c.wrp.New(maxlen)
buf := ctx.c.wrp.Bytes(ptr, maxlen)
ptr := ctx.c.new(maxlen)
buf := util.View(ctx.c.mod, ptr, maxlen)
buf = value.AppendFormat(buf[:0], time.RFC3339Nano)
_ = append(buf, 0)
ctx.c.wrp.Xsqlite3_result_text_go(
int32(ctx.handle), int32(ptr), int64(len(buf)))
ctx.c.call("sqlite3_result_text_go",
stk_t(ctx.handle), stk_t(ptr), stk_t(len(buf)))
}
// ResultPointer sets the result of the function to NULL, just like [Context.ResultNull],
@@ -160,9 +161,9 @@ func (ctx Context) resultRFC3339Nano(value time.Time) {
//
// https://sqlite.org/c3ref/result_blob.html
func (ctx Context) ResultPointer(ptr any) {
valPtr := ctx.c.wrp.AddHandle(ptr)
ctx.c.wrp.Xsqlite3_result_pointer_go(
int32(ctx.handle), int32(valPtr))
valPtr := util.AddHandle(ctx.c.ctx, ptr)
ctx.c.call("sqlite3_result_pointer_go",
stk_t(ctx.handle), stk_t(valPtr))
}
// ResultValue sets the result of the function to a copy of [Value].
@@ -173,8 +174,8 @@ func (ctx Context) ResultValue(value Value) {
ctx.ResultError(MISUSE)
return
}
ctx.c.wrp.Xsqlite3_result_value(
int32(ctx.handle), int32(value.handle))
ctx.c.call("sqlite3_result_value",
stk_t(ctx.handle), stk_t(value.handle))
}
// ResultError sets the result of the function an error.
@@ -182,25 +183,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.wrp.Xsqlite3_result_error_nomem(int32(ctx.handle))
ctx.c.call("sqlite3_result_error_nomem", stk_t(ctx.handle))
return
}
if errors.Is(err, TOOBIG) {
ctx.c.wrp.Xsqlite3_result_error_toobig(int32(ctx.handle))
ctx.c.call("sqlite3_result_error_toobig", stk_t(ctx.handle))
return
}
msg, code := errorCode(err, ERROR)
if 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)))
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)))
}
if code != res_t(ERROR) {
ctx.c.wrp.Xsqlite3_result_error_code(
int32(ctx.handle), int32(code))
ctx.c.call("sqlite3_result_error_code",
stk_t(ctx.handle), stk_t(code))
}
}
@@ -208,8 +209,8 @@ func (ctx Context) ResultError(err error) {
//
// https://sqlite.org/c3ref/result_subtype.html
func (ctx Context) ResultSubtype(t uint) {
ctx.c.wrp.Xsqlite3_result_subtype(
int32(ctx.handle), int32(t))
ctx.c.call("sqlite3_result_subtype",
stk_t(ctx.handle), stk_t(uint32(t)))
}
// VTabNoChange may return true if a column is being fetched as part
@@ -217,6 +218,6 @@ func (ctx Context) ResultSubtype(t uint) {
//
// https://sqlite.org/c3ref/vtab_nochange.html
func (ctx Context) VTabNoChange() bool {
b := ctx.c.wrp.Xsqlite3_vtab_nochange(int32(ctx.handle))
b := int32(ctx.c.call("sqlite3_vtab_nochange", stk_t(ctx.handle)))
return b != 0
}
+54 -62
View File
@@ -1,8 +1,10 @@
// 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/hanzoai/sqlite3/driver"
// 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].
//
@@ -107,14 +109,13 @@ import (
"time"
"unsafe"
"github.com/hanzoai/sqlite3"
"github.com/hanzoai/sqlite3/internal/errutil"
"github.com/hanzoai/sqlite3/internal/util"
"github.com/ncruces/go-sqlite3"
"github.com/ncruces/go-sqlite3/internal/util"
)
// This variable can be replaced with -ldflags:
//
// go build -ldflags="-X github.com/hanzoai/sqlite3/driver.driverName=sqlite"
// go build -ldflags="-X github.com/ncruces/go-sqlite3/driver.driverName=sqlite"
var driverName = "sqlite3"
func init() {
@@ -174,17 +175,15 @@ func newConnector(name string, init, term func(*sqlite3.Conn) error) (*connector
var txlock, timefmt string
if strings.HasPrefix(name, "file:") {
u, err := url.Parse(name)
if err != nil {
return nil, err
if _, after, ok := strings.Cut(name, "?"); ok {
query, err := url.ParseQuery(after)
if err != nil {
return nil, err
}
txlock = query.Get("_txlock")
timefmt = query.Get("_timefmt")
c.pragmas = query.Has("_pragma")
}
query, err := url.ParseQuery(u.RawQuery)
if err != nil {
return nil, err
}
txlock = query.Get("_txlock")
timefmt = query.Get("_timefmt")
c.pragmas = query.Has("_pragma")
}
switch txlock {
@@ -344,7 +343,7 @@ func (c *conn) BeginTx(ctx context.Context, opts driver.TxOptions) (driver.Tx, e
var txLock string
switch opts.Isolation {
default:
return nil, errutil.IsolationErr
return nil, util.IsolationErr
case driver.IsolationLevel(sql.LevelLinearizable):
txLock = "exclusive"
case driver.IsolationLevel(sql.LevelSerializable):
@@ -406,7 +405,7 @@ func (c *conn) PrepareContext(ctx context.Context, query string) (driver.Stmt, e
}
if notWhitespace(tail) {
s.Close()
return nil, errutil.TailErr
return nil, util.TailErr
}
return &stmt{Stmt: s, tmRead: c.tmRead, tmWrite: c.tmWrite, inputs: -2}, nil
}
@@ -550,7 +549,7 @@ func (s *stmt) setupBindings(args []driver.NamedValue) (err error) {
case nil:
err = s.Stmt.BindNull(id)
default:
panic(errutil.AssertErr())
panic(util.AssertErr())
}
if err != nil {
return err
@@ -784,23 +783,17 @@ func (r *rows) ColumnTypeScanType(index int) (typ reflect.Type) {
}
}
func (r *rows) NextRow() error {
func (r *rows) Next(dest []driver.Value) error {
c := r.Stmt.Conn()
if old := c.SetInterrupt(r.ctx); old != r.ctx {
defer c.SetInterrupt(old)
}
if r.Stmt.Step() {
return nil
}
if err := r.Stmt.Err(); err != nil {
return err
}
return io.EOF
}
func (r *rows) Next(dest []driver.Value) error {
if err := r.NextRow(); err != nil {
return err
if !r.Stmt.Step() {
if err := r.Stmt.Err(); err != nil {
return err
}
return io.EOF
}
data := unsafe.Slice((*any)(unsafe.SliceData(dest)), len(dest))
@@ -808,38 +801,37 @@ func (r *rows) Next(dest []driver.Value) error {
return err
}
for i := range dest {
dest[i] = r.convert(i, dest[i])
scan := r.scanType(i)
if v, ok := dest[i].([]byte); ok {
if len(v) == cap(v) { // a BLOB
continue
}
if scan != _TEXT {
switch r.tmWrite {
case "", time.RFC3339, time.RFC3339Nano:
t, ok := maybeTime(v)
if ok {
dest[i] = t
continue
}
}
}
dest[i] = string(v)
}
switch scan {
case _TIME:
t, err := r.tmRead.Decode(dest[i])
if err == nil {
dest[i] = t
}
case _BOOL:
switch dest[i] {
case int64(0):
dest[i] = false
case int64(1):
dest[i] = true
}
}
}
return nil
}
func (r *rows) convert(i int, val driver.Value) driver.Value {
scan := r.scanType(i)
if v, ok := val.([]byte); ok {
if len(v) == cap(v) { // a BLOB
return val
}
if scan != _TEXT {
t, ok := r.maybeTime(v)
if ok {
return t
}
}
val = string(v)
}
switch scan {
case _TIME:
t, err := r.tmRead.Decode(val)
if err == nil {
return t
}
case _BOOL:
switch val {
case int64(0):
return false
case int64(1):
return true
}
}
return val
}
-146
View File
@@ -1,146 +0,0 @@
//go:build go1.27
package driver
import (
"database/sql"
"database/sql/driver"
"math"
"time"
"github.com/hanzoai/sqlite3"
)
func (r *rows) ScanColumn(ctx driver.ScanContext, i int, dest any) error {
typ := r.Stmt.ColumnType(i)
// Fast path.
var src any
switch typ {
case sqlite3.NULL:
// src = nil
case sqlite3.FLOAT:
f := r.stmt.ColumnFloat(i)
if r.scanFloat(f, dest) {
return nil
}
src = f
case sqlite3.INTEGER:
i := r.stmt.ColumnInt64(i)
if r.scanInt(i, dest) {
return nil
}
if r.scanFloat(float64(i), dest) {
return nil
}
src = i
default:
var b []byte
if typ == sqlite3.TEXT {
b = r.stmt.ColumnRawText(i)
} else {
b = r.stmt.ColumnRawBlob(i)
}
switch d := dest.(type) {
case *sql.RawBytes:
*d = b
return nil
case *string:
*d = string(b)
return nil
case *sql.NullString:
d.String = string(b)
d.Valid = true
return nil
case *[]byte:
*d = append((*d)[:0], b...)
return nil
}
src = b
}
// Time handling.
if typ != sqlite3.NULL && typ != sqlite3.BLOB {
var ok bool
switch d := dest.(type) {
case *time.Time:
*d, ok = r.scanTime(src)
case *sql.NullTime:
d.Time, ok = r.scanTime(src)
d.Valid = ok
}
if ok {
return nil
}
}
// Fallback.
return sql.ConvertAssign(ctx, dest, r.convert(i, src))
}
func (r *rows) scanTime(src any) (_ time.Time, _ bool) {
if s, ok := src.([]byte); ok {
if t, ok := r.maybeTime(s); ok {
return t, true
}
src = string(s)
}
t, err := r.tmRead.Decode(src)
return t, err == nil
}
func (r *rows) scanFloat(f float64, dest any) bool {
switch d := dest.(type) {
case *float64:
*d = f
case *float32:
*d = float32(f)
case *sql.NullFloat64:
d.Float64 = f
d.Valid = true
case *sql.Null[float32]:
d.V = float32(f)
d.Valid = true
default:
return false
}
return true
}
func (r *rows) scanInt(i int64, dest any) bool {
switch d := dest.(type) {
case *int64:
*d = i
case *sql.NullInt64:
d.Int64 = i
d.Valid = true
case *uint64:
*d = uint64(i)
return 0 <= i
case *sql.Null[uint64]:
d.V = uint64(i)
d.Valid = true
return 0 <= i
case *int:
*d = int(i)
return math.MinInt <= i && i <= math.MaxInt
case *sql.Null[int]:
d.V = int(i)
d.Valid = true
return math.MinInt <= i && i <= math.MaxInt
case *uint:
*d = uint(i)
return 0 <= i && uint64(i) <= math.MaxUint
case *sql.Null[uint]:
d.V = uint(i)
d.Valid = true
return 0 <= i && uint64(i) <= math.MaxUint
default:
return false
}
return true
}
+32 -87
View File
@@ -8,15 +8,14 @@ import (
"math"
"net/url"
"reflect"
"runtime"
"strings"
"testing"
"time"
"github.com/hanzoai/sqlite3"
"github.com/hanzoai/sqlite3/internal/errutil"
"github.com/hanzoai/sqlite3/internal/testcfg"
"github.com/hanzoai/sqlite3/vfs/memdb"
"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/vfs/memdb"
)
func Test_Open_error(t *testing.T) {
@@ -34,20 +33,16 @@ 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(ctx)
_, err = db.Conn(context.TODO())
if err == nil {
t.Fatal("want error")
}
if runtime.GOOS == "js" {
t.Skip("skipping on JS")
}
if !errors.Is(err, sqlite3.CANTOPEN_ISDIR) {
t.Errorf("got %v, want sqlite3.CANTOPEN_ISDIR", err)
}
@@ -59,7 +54,6 @@ 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)
@@ -67,7 +61,7 @@ func Test_Open_pragma(t *testing.T) {
defer db.Close()
var timeout int
err = db.QueryRowContext(ctx, `PRAGMA busy_timeout`).Scan(&timeout)
err = db.QueryRow(`PRAGMA busy_timeout`).Scan(&timeout)
if err != nil {
t.Fatal(err)
}
@@ -82,14 +76,13 @@ 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(ctx)
_, err = db.Conn(context.TODO())
if err == nil {
t.Fatal("want error")
}
@@ -112,19 +105,18 @@ 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.BeginTx(ctx, nil)
tx1, err := db.Begin()
if err != nil {
t.Fatal(err)
}
_, err = db.BeginTx(ctx, nil)
_, err = db.Begin()
if err == nil {
t.Error("want error")
}
@@ -164,24 +156,23 @@ 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(ctx, &sql.TxOptions{Isolation: sql.LevelReadCommitted})
if err.Error() != string(errutil.IsolationErr) {
_, err = db.BeginTx(t.Context(), &sql.TxOptions{Isolation: sql.LevelReadCommitted})
if err.Error() != string(util.IsolationErr) {
t.Error("want isolationErr")
}
tx1, err := db.BeginTx(ctx, &sql.TxOptions{ReadOnly: true})
tx1, err := db.BeginTx(t.Context(), &sql.TxOptions{ReadOnly: true})
if err != nil {
t.Fatal(err)
}
tx2, err := db.BeginTx(ctx, &sql.TxOptions{ReadOnly: true})
tx2, err := db.BeginTx(t.Context(), &sql.TxOptions{ReadOnly: true})
if err != nil {
t.Fatal(err)
}
@@ -209,14 +200,13 @@ 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.BeginTx(ctx, nil)
tx, err := db.Begin()
if err != nil {
t.Fatal(err)
}
@@ -243,7 +233,7 @@ func Test_nested_context(t *testing.T) {
want(outer, 0)
ctx, cancel := context.WithCancel(ctx)
ctx, cancel := context.WithCancel(t.Context())
defer cancel()
inner, err := tx.QueryContext(ctx, `SELECT value FROM generate_series(0)`)
@@ -268,7 +258,6 @@ 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)
@@ -276,7 +265,7 @@ func Test_Prepare(t *testing.T) {
defer db.Close()
var serr *sqlite3.Error
_, err = db.PrepareContext(ctx, `SELECT`)
_, err = db.Prepare(`SELECT`)
if err == nil {
t.Error("want error")
}
@@ -290,18 +279,18 @@ func Test_Prepare(t *testing.T) {
t.Error("got message:", got)
}
_, err = db.PrepareContext(ctx, `SELECT 1; `)
_, err = db.Prepare(`SELECT 1; `)
if err != nil {
t.Error(err)
}
_, err = db.PrepareContext(ctx, `SELECT 1; SELECT`)
if err.Error() != string(errutil.TailErr) {
_, err = db.Prepare(`SELECT 1; SELECT`)
if err.Error() != string(util.TailErr) {
t.Error("want tailErr")
}
_, err = db.PrepareContext(ctx, `SELECT 1; SELECT 2`)
if err.Error() != string(errutil.TailErr) {
_, err = db.Prepare(`SELECT 1; SELECT 2`)
if err.Error() != string(util.TailErr) {
t.Error("want tailErr")
}
}
@@ -310,27 +299,26 @@ 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(ctx)
conn, err := db.Conn(t.Context())
if err != nil {
t.Fatal(err)
}
defer conn.Close()
stmt, err := conn.PrepareContext(ctx, `SELECT ?, ?5, :AAA, @AAA, $AAA`)
stmt, err := conn.PrepareContext(t.Context(), `SELECT ?, ?5, :AAA, @AAA, $AAA`)
if err != nil {
t.Fatal(err)
}
defer stmt.Close()
date := time.Now()
row := stmt.QueryRowContext(ctx, true, sql.Named("AAA", math.Pi), nil /*3*/, nil /*4*/, date /*5*/)
row := stmt.QueryRow(true, sql.Named("AAA", math.Pi), nil /*3*/, nil /*4*/, date /*5*/)
var first bool
var fifth time.Time
@@ -361,14 +349,13 @@ 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.QueryContext(ctx, `
rows, err := db.Query(`
SELECT NULL UNION ALL
SELECT x'cafe' UNION ALL
SELECT x'babe' UNION ALL
@@ -401,7 +388,6 @@ func Test_time(t *testing.T) {
"_timefmt": {fmt},
})
ctx := testcfg.Context(t)
db, err := Open(dsn)
if err != nil {
t.Fatal(err)
@@ -410,18 +396,18 @@ func Test_time(t *testing.T) {
twosday := time.Date(2022, 2, 22, 22, 22, 22, 0, time.UTC)
_, err = db.ExecContext(ctx, `CREATE TABLE test (at DATETIME)`)
_, err = db.Exec(`CREATE TABLE test (at DATETIME)`)
if err != nil {
t.Fatal(err)
}
_, err = db.ExecContext(ctx, `INSERT INTO test VALUES (?)`, twosday)
_, err = db.Exec(`INSERT INTO test VALUES (?)`, twosday)
if err != nil {
t.Fatal(err)
}
var got time.Time
err = db.QueryRowContext(ctx, `SELECT * FROM test`).Scan(&got)
err = db.QueryRow(`SELECT * FROM test`).Scan(&got)
if err != nil {
t.Fatal(err)
}
@@ -447,14 +433,13 @@ 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.ExecContext(ctx, `
_, err = db.Exec(`
CREATE TABLE test (
col_int INTEGER,
col_real REAL,
@@ -479,7 +464,7 @@ func Test_ColumnType_ScanType(t *testing.T) {
t.Fatal(err)
}
rows, err := db.QueryContext(ctx, `SELECT * FROM test`)
rows, err := db.Query(`SELECT * FROM test`)
if err != nil {
t.Fatal(err)
}
@@ -535,47 +520,7 @@ 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.27
err = db.QueryRow(`SELECT datetime()`).Scan(&tm)
if err != nil && !strings.HasPrefix(err.Error(), "sql: Scan error") {
t.Error(err)
}
if err == nil && tm.IsZero() {
t.Error(tm)
}
var nt sql.NullTime
err = db.QueryRow(`SELECT NULL`).Scan(&nt)
if err != nil {
t.Error(err)
}
// Go 1.27
err = db.QueryRow(`SELECT datetime()`).Scan(&nt)
if err != nil && !strings.HasPrefix(err.Error(), "sql: Scan error") {
t.Error(err)
}
if err == nil && nt.Time.IsZero() {
t.Error(nt.Time)
}
}
func Benchmark_loop(b *testing.B) {
ctx := testcfg.Context(b)
db, err := Open(":memory:")
if err != nil {
b.Fatal(err)
@@ -583,7 +528,7 @@ func Benchmark_loop(b *testing.B) {
defer db.Close()
var version string
err = db.QueryRowContext(ctx, `SELECT sqlite_version();`).Scan(&version)
err = db.QueryRow(`SELECT sqlite_version();`).Scan(&version)
if err != nil {
b.Fatal(err)
}
+4 -3
View File
@@ -7,9 +7,10 @@ import (
"log"
"time"
"github.com/hanzoai/sqlite3"
_ "github.com/hanzoai/sqlite3/driver"
_ "github.com/hanzoai/sqlite3/vfs/memdb"
"github.com/ncruces/go-sqlite3"
_ "github.com/ncruces/go-sqlite3/driver"
_ "github.com/ncruces/go-sqlite3/embed"
_ "github.com/ncruces/go-sqlite3/vfs/memdb"
)
func Example_customTime() {
+3 -2
View File
@@ -10,8 +10,9 @@ import (
"log"
"os"
_ "github.com/hanzoai/sqlite3/driver"
_ "github.com/hanzoai/sqlite3/vfs/memdb"
_ "github.com/ncruces/go-sqlite3/driver"
_ "github.com/ncruces/go-sqlite3/embed"
_ "github.com/ncruces/go-sqlite3/vfs/memdb"
)
var db *sql.DB
+4 -3
View File
@@ -4,9 +4,10 @@ import (
"fmt"
"log"
"github.com/hanzoai/sqlite3"
"github.com/hanzoai/sqlite3/driver"
_ "github.com/hanzoai/sqlite3/vfs/memdb"
"github.com/ncruces/go-sqlite3"
"github.com/ncruces/go-sqlite3/driver"
_ "github.com/ncruces/go-sqlite3/embed"
_ "github.com/ncruces/go-sqlite3/vfs/memdb"
)
func Example_json() {
+1 -1
View File
@@ -4,7 +4,7 @@ import (
"database/sql"
"time"
"github.com/hanzoai/sqlite3"
"github.com/ncruces/go-sqlite3"
)
// Savepoint establishes a new transaction savepoint.
+4 -2
View File
@@ -4,8 +4,10 @@ import (
"fmt"
"log"
"github.com/hanzoai/sqlite3/driver"
_ "github.com/hanzoai/sqlite3/vfs/memdb"
"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"
)
func ExampleSavepoint() {
+3 -11
View File
@@ -6,18 +6,10 @@ import (
)
// Convert a string in [time.RFC3339Nano] format into a [time.Time]
// if that's the format we're using to persist them,
// and if it roundtrips back to the same string.
// if it roundtrips back to the same string.
// This way times can be persisted to, and recovered from, the database,
// but if a string is wanted, [database/sql] will recover the same string.
func (r *rows) maybeTime(text []byte) (_ time.Time, _ bool) {
switch r.tmWrite {
case "", time.RFC3339, time.RFC3339Nano:
break
default:
return
}
// but if a string is needed, [database/sql] will recover the same string.
func maybeTime(text []byte) (_ time.Time, _ bool) {
// Weed out (some) values that can't possibly be
// [time.RFC3339Nano] timestamps.
if len(text) < len("2006-01-02T15:04:05Z") {
+2 -4
View File
@@ -21,9 +21,8 @@ func Fuzz_stringOrTime_1(f *testing.F) {
f.Add("2006-01-02T15:04:05.9999999999Z")
f.Add("Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.")
r := rows{stmt: &stmt{}}
f.Fuzz(func(t *testing.T, str string) {
v, ok := r.maybeTime([]byte(str))
v, ok := maybeTime([]byte(str))
if ok {
// Make sure times round-trip to the same string:
// https://pkg.go.dev/database/sql#Rows.Scan
@@ -51,9 +50,8 @@ func Fuzz_stringOrTime_2(f *testing.F) {
f.Add(int64(639095955742), int64(222_222_222)) // twosday, year 22222AD
f.Add(int64(-763421161058), int64(222_222_222)) // twosday, year 22222BC
r := rows{stmt: &stmt{}}
checkTime := func(t testing.TB, date time.Time) {
v, ok := r.maybeTime(date.AppendFormat(nil, time.RFC3339Nano))
v, ok := maybeTime(date.AppendFormat(nil, time.RFC3339Nano))
if ok {
// Make sure times round-trip to the same time:
if !v.Equal(date) {
+3 -3
View File
@@ -5,7 +5,8 @@ import (
"slices"
"testing"
"github.com/hanzoai/sqlite3/internal/testcfg"
_ "github.com/ncruces/go-sqlite3/embed"
_ "github.com/ncruces/go-sqlite3/internal/testcfg"
)
func Test_namedValues(t *testing.T) {
@@ -54,8 +55,7 @@ func Fuzz_notWhitespace(f *testing.F) {
t.SkipNow()
}
ctx := testcfg.Context(t)
c, err := db.Conn(ctx)
c, err := db.Conn(t.Context())
if err != nil {
t.Fatal(err)
}
+38
View File
@@ -0,0 +1,38 @@
# Embeddable Wasm build of SQLite
This folder includes an embeddable Wasm build of SQLite 3.51.2 for use with
[`github.com/ncruces/go-sqlite3`](https://pkg.go.dev/github.com/ncruces/go-sqlite3).
The following optional features are compiled in:
- [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.
Example of a custom build of SQLite:
- [`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.
Executable
+37
View File
@@ -0,0 +1,37 @@
#!/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 -nostdlib -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_EXPERIMENTAL_PRAGMA_20251114 \
-DSQLITE_CUSTOM_INCLUDE=sqlite_opt.h \
$(awk '{print "-Wl,--export="$0}' exports.txt)
"$BINARYEN/wasm-ctor-eval" -g -c _initialize sqlite3.wasm -o sqlite3.tmp
"$BINARYEN/wasm-opt" -g sqlite3.tmp -o sqlite3.wasm \
--gufa --generate-global-effects --low-memory-unused --converge -O3 \
--enable-mutable-globals --enable-nontrapping-float-to-int \
--enable-simd --enable-bulk-memory --enable-sign-ext \
--enable-reference-types --enable-multivalue \
--disable-extended-const \
--strip --strip-producers
-8
View File
@@ -1,8 +0,0 @@
// Package embed is no longer needed.
//
// Deprecated: importing github.com/hanzoai/sqlite3/embed is unnecessary.
package embed
func init() {
println("If you're reading this, you're unnecessarily importing github.com/hanzoai/sqlite3/embed.")
}
+144
View File
@@ -0,0 +1,144 @@
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
@@ -0,0 +1,23 @@
// 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
@@ -0,0 +1,25 @@
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.3" {
t.Error(version)
}
}
BIN
View File
Binary file not shown.
+4 -4
View File
@@ -4,7 +4,7 @@ import (
"errors"
"strings"
"github.com/hanzoai/sqlite3/internal/sqlite3_wrap"
"github.com/ncruces/go-sqlite3/internal/util"
)
// 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(sqlite3_wrap.ErrorCodeString(e.code))
b.WriteString(util.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 sqlite3_wrap.ErrorCodeString(e)
return util.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 sqlite3_wrap.ErrorCodeString(e)
return util.ErrorCodeString(e)
}
// Is tests whether this error matches a given [ErrorCode].
+17 -18
View File
@@ -6,13 +6,12 @@ import (
"strings"
"testing"
"github.com/hanzoai/sqlite3/internal/errutil"
"github.com/hanzoai/sqlite3/internal/sqlite3_wrap"
"github.com/ncruces/go-sqlite3/internal/util"
)
func Test_assertErr(t *testing.T) {
err := errutil.AssertErr()
if s := err.Error(); !strings.HasPrefix(s, "sqlite3: assertion failed") || !strings.HasSuffix(s, "error_test.go:14)") {
err := util.AssertErr()
if s := err.Error(); !strings.HasPrefix(s, "sqlite3: assertion failed") || !strings.HasSuffix(s, "error_test.go:13)") {
t.Errorf("got %q", s)
}
}
@@ -128,7 +127,7 @@ func TestError_Timeout(t *testing.T) {
func Test_ErrorCode_Error(t *testing.T) {
t.Parallel()
db, err := OpenContext(testContext(t), ":memory:")
db, err := Open(":memory:")
if err != nil {
t.Fatal(err)
}
@@ -137,8 +136,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.wrp.Xsqlite3_errstr(int32(i)))
want += db.wrp.ReadString(ptr, _MAX_NAME)
ptr := ptr_t(db.call("sqlite3_errstr", stk_t(i)))
want += util.ReadString(db.mod, ptr, _MAX_NAME)
got := ErrorCode(i).Error()
if got != want {
@@ -150,7 +149,7 @@ func Test_ErrorCode_Error(t *testing.T) {
func Test_ExtendedErrorCode_Error(t *testing.T) {
t.Parallel()
db, err := OpenContext(testContext(t), ":memory:")
db, err := Open(":memory:")
if err != nil {
t.Fatal(err)
}
@@ -159,8 +158,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.wrp.Xsqlite3_errstr(int32(i)))
want += db.wrp.ReadString(ptr, _MAX_NAME)
ptr := ptr_t(db.call("sqlite3_errstr", stk_t(i)))
want += util.ReadString(db.mod, ptr, _MAX_NAME)
got := xErrorCode(i).Error()
if got != want {
@@ -176,14 +175,14 @@ func Test_errorCode(t *testing.T) {
wantCode res_t
}{
{nil, "", _OK},
{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},
{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},
}
for _, tt := range tests {
t.Run("", func(t *testing.T) {
+2 -1
View File
@@ -4,7 +4,8 @@ import (
"fmt"
"log"
"github.com/hanzoai/sqlite3"
"github.com/ncruces/go-sqlite3"
_ "github.com/ncruces/go-sqlite3/embed"
)
const memory = ":memory:"
-138
View File
@@ -1,138 +0,0 @@
package sqlite3
import (
"bytes"
"encoding/base64"
"sync"
"github.com/hanzoai/sqlite3/internal/errutil"
)
var (
// +checklocks:extRegistryMtx
extRegistry []func(*Conn) error
extRegistryMtx sync.RWMutex
)
// AutoExtension causes the entryPoint function to be invoked
// for each new database connection that is created.
//
// https://sqlite.org/c3ref/auto_extension.html
func AutoExtension(entryPoint func(*Conn) error) {
extRegistryMtx.Lock()
extRegistry = append(extRegistry, entryPoint)
extRegistryMtx.Unlock()
}
func initExtensions(c *Conn) error {
c.base64()
extRegistryMtx.RLock()
defer extRegistryMtx.RUnlock()
for _, f := range extRegistry {
if err := f(c); err != nil {
return err
}
}
return nil
}
func (c *Conn) base64() error {
return c.CreateFunction("base64", 1, DETERMINISTIC, func(ctx Context, arg ...Value) {
switch a := arg[0]; a.Type() {
case NULL:
case BLOB:
data := a.RawBlob()
code := base64.StdEncoding
size := int64(code.EncodedLen(len(data)))
if size > _MAX_LENGTH {
ctx.ResultError(TOOBIG)
return
}
ptr := c.wrp.New(size)
if size > 0 {
code.Encode(c.wrp.Bytes(ptr, size), data)
}
ctx.c.wrp.Xsqlite3_result_text_go(int32(ctx.handle), int32(ptr), size)
case TEXT:
data := a.RawText()
data = bytes.Trim(data, " \t\n\v\f\r")
data = bytes.TrimRight(data, "=")
code := base64.RawStdEncoding
size := int64(code.DecodedLen(len(data)))
if size > _MAX_LENGTH {
ctx.ResultError(TOOBIG)
return
}
ptr := c.wrp.New(size)
if size > 0 {
n, _ := code.Decode(c.wrp.Bytes(ptr, size), data)
size = int64(n)
}
ctx.c.wrp.Xsqlite3_result_blob_go(int32(ctx.handle), int32(ptr), size)
default:
ctx.ResultError(errutil.ErrorString("base64: accepts only blob or text"))
}
})
}
// ExtensionLibrary represents a dynamically linked SQLite extension.
type ExtensionLibrary interface {
Xsqlite3_extension_init(db, _, _ int32) int32
}
// ExtensionInfo returns values needed to load a dynamically linked SQLite extension.
type ExtensionInfo func() (memorySize, memoryAlignment, tableSize, tableAlignment int64)
type extEnv struct {
*env
memoryBase int32
tableBase int32
}
func (e *extEnv) X__memory_base() *int32 { return &e.memoryBase }
func (e *extEnv) X__table_base() *int32 { return &e.tableBase }
// ExtensionInit loads an SQLite extension library.
//
// https://sqlite.org/loadext.html
func ExtensionInit[Env any, Mod ExtensionLibrary](db *Conn, init func(env Env) Mod, info ExtensionInfo) error {
memSize, memAlign, tableSize, tableAlign := info()
var memBase int32
if memSize > 0 {
memBase = db.wrp.Xaligned_alloc(int32(memAlign), int32(memSize))
if memBase == 0 {
panic(errutil.OOMErr)
}
}
var tableBase int
if tableSize > 0 {
// Round up to the alignment.
rnd := int(tableAlign) - 1
tab := db.wrp.X__indirect_function_table()
tableBase = (len(*tab) + rnd) &^ rnd
if add := tableBase + int(tableSize) - len(*tab); add > 0 {
*tab = append(*tab, make([]any, add)...)
}
}
e := &extEnv{
env: &env{db.wrp},
memoryBase: memBase,
tableBase: int32(tableBase),
}
mod := init(any(e).(Env))
if opt, ok := any(mod).(interface{ X__wasm_apply_data_relocs() }); ok {
opt.X__wasm_apply_data_relocs()
}
if opt, ok := any(mod).(interface{ X__wasm_call_ctors() }); ok {
opt.X__wasm_call_ctors()
}
rc := mod.Xsqlite3_extension_init(int32(db.handle), 0, 0)
return db.error(res_t(rc))
}
+1 -11
View File
@@ -17,24 +17,16 @@ you can load into your database connections.
reads [comma-separated values](https://sqlite.org/csv.html).
- [`github.com/ncruces/go-sqlite3/ext/fileio`](https://pkg.go.dev/github.com/ncruces/go-sqlite3/ext/fileio)
reads, writes and lists files.
- [`github.com/ncruces/go-sqlite3/ext/fts5`](https://pkg.go.dev/github.com/ncruces/go-sqlite3/ext/fts5)
provides [full-text search](https://sqlite.org/fts5.html).
- [`github.com/ncruces/go-sqlite3/ext/hash`](https://pkg.go.dev/github.com/ncruces/go-sqlite3/ext/hash)
provides cryptographic hash functions.
- [`github.com/ncruces/go-sqlite3/ext/ipaddr`](https://pkg.go.dev/github.com/ncruces/go-sqlite3/ext/ipaddr)
provides functions to manipulate IPs and CIDRs.
- [`github.com/ncruces/go-sqlite3/ext/lines`](https://pkg.go.dev/github.com/ncruces/go-sqlite3/ext/lines)
reads data [line-by-line](https://github.com/asg017/sqlite-lines).
- [`github.com/ncruces/go-sqlite3/ext/pivot`](https://pkg.go.dev/github.com/ncruces/go-sqlite3/ext/pivot)
creates [pivot tables](https://github.com/jakethaw/pivot_vtab).
- [`github.com/ncruces/go-sqlite3/ext/regexp`](https://pkg.go.dev/github.com/ncruces/go-sqlite3/ext/regexp)
provides [regular expression](https://github.com/nalgeon/sqlean/blob/main/docs/regexp.md) functions.
- [`github.com/ncruces/go-sqlite3/ext/rtree`](https://pkg.go.dev/github.com/ncruces/go-sqlite3/ext/rtree)
provides [multi-dimensional](https://sqlite.org/rtree.html) and [geospatial](https://sqlite.org/geopoly.html) indexes.
provides regular expression functions.
- [`github.com/ncruces/go-sqlite3/ext/serdes`](https://pkg.go.dev/github.com/ncruces/go-sqlite3/ext/serdes)
(de)serializes databases.
- [`github.com/ncruces/go-sqlite3/ext/spellfix1`](https://pkg.go.dev/github.com/ncruces/go-sqlite3/ext/spellfix1)
searches a vocabulary for [close matches](https://sqlite.org/spellfix1.html).
- [`github.com/ncruces/go-sqlite3/ext/statement`](https://pkg.go.dev/github.com/ncruces/go-sqlite3/ext/statement)
creates [parameterized views](https://github.com/0x09/sqlite-statement-vtab).
- [`github.com/ncruces/go-sqlite3/ext/stats`](https://pkg.go.dev/github.com/ncruces/go-sqlite3/ext/stats)
@@ -43,8 +35,6 @@ you can load into your database connections.
provides [Unicode aware](https://sqlite.org/src/dir/ext/icu) functions.
- [`github.com/ncruces/go-sqlite3/ext/uuid`](https://pkg.go.dev/github.com/ncruces/go-sqlite3/ext/uuid)
generates [UUIDs](https://en.wikipedia.org/wiki/Universally_unique_identifier).
- [`github.com/ncruces/go-sqlite3/ext/vec1`](https://pkg.go.dev/github.com/ncruces/go-sqlite3/ext/vec1)
provides approximate nearest-neighbor [vector search](https://sqlite.org/vec1/doc/trunk/doc/vec1.md).
- [`github.com/ncruces/go-sqlite3/ext/zorder`](https://pkg.go.dev/github.com/ncruces/go-sqlite3/ext/zorder)
maps multidimensional data to one dimension.
+11 -28
View File
@@ -7,8 +7,8 @@ import (
"fmt"
"reflect"
"github.com/hanzoai/sqlite3"
"github.com/hanzoai/sqlite3/internal/util"
"github.com/ncruces/go-sqlite3"
"github.com/ncruces/go-sqlite3/internal/util"
)
// Register registers the array single-argument, table-valued SQL function.
@@ -45,14 +45,12 @@ func (array) Open() (sqlite3.VTabCursor, error) {
}
type cursor struct {
value reflect.Value
slice any
rowID int
length int
array reflect.Value
rowID int
}
func (c *cursor) EOF() bool {
return c.rowID >= c.length
return c.rowID >= c.array.Len()
}
func (c *cursor) Next() error {
@@ -70,22 +68,7 @@ func (c *cursor) Column(ctx sqlite3.Context, n int) error {
return nil
}
switch arr := c.slice.(type) {
case []int:
ctx.ResultInt(arr[c.rowID])
return nil
case []int64:
ctx.ResultInt64(arr[c.rowID])
return nil
case []float64:
ctx.ResultFloat(arr[c.rowID])
return nil
case []string:
ctx.ResultText(arr[c.rowID])
return nil
}
v := c.value.Index(c.rowID)
v := c.array.Index(c.rowID)
k := v.Kind()
if k == reflect.Interface {
@@ -127,14 +110,14 @@ func (c *cursor) Column(ctx sqlite3.Context, n int) error {
return nil
}
func (c *cursor) Filter(idxNum int, idxStr string, arg ...sqlite3.Value) (err error) {
c.slice = arg[0].Pointer()
c.value = reflect.ValueOf(c.slice)
c.value, err = indexable(c.value)
func (c *cursor) Filter(idxNum int, idxStr string, arg ...sqlite3.Value) error {
array := reflect.ValueOf(arg[0].Pointer())
array, err := indexable(array)
if err != nil {
return err
}
c.length = c.value.Len()
c.array = array
c.rowID = 0
return nil
}
+9 -16
View File
@@ -1,27 +1,22 @@
package array_test
import (
"errors"
"fmt"
"log"
"math"
"reflect"
"testing"
"github.com/hanzoai/sqlite3"
"github.com/hanzoai/sqlite3/driver"
"github.com/hanzoai/sqlite3/ext/array"
"github.com/hanzoai/sqlite3/ext/rtree"
"github.com/hanzoai/sqlite3/internal/testcfg"
"github.com/hanzoai/sqlite3/vfs/memdb"
"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/vfs/memdb"
)
func Example_driver() {
db, err := driver.Open("file:/test.db?vfs=memdb", func(c *sqlite3.Conn) error {
return errors.Join(
array.Register(c),
rtree.Register(c))
})
db, err := driver.Open("file:/test.db?vfs=memdb", array.Register)
if err != nil {
log.Fatal(err)
}
@@ -57,7 +52,6 @@ func Example_driver() {
func Example() {
sqlite3.AutoExtension(array.Register)
sqlite3.AutoExtension(rtree.Register)
db, err := sqlite3.Open(":memory:")
if err != nil {
@@ -96,14 +90,13 @@ 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.QueryContext(ctx, `
rows, err := db.Query(`
SELECT rowid, value FROM array(?)`,
sqlite3.Pointer(&[...]any{nil, true, 1, uint(2), math.Pi, "text", []byte{1, 2, 3}}))
if err != nil {
@@ -136,7 +129,7 @@ func Test_cursor_Column(t *testing.T) {
func Test_array_errors(t *testing.T) {
t.Parallel()
db, err := sqlite3.OpenContext(testcfg.Context(t), ":memory:")
db, err := sqlite3.Open(":memory:")
if err != nil {
t.Fatal(err)
}
+3 -3
View File
@@ -5,8 +5,8 @@ import (
"errors"
"io"
"github.com/hanzoai/sqlite3"
"github.com/hanzoai/sqlite3/internal/errutil"
"github.com/ncruces/go-sqlite3"
"github.com/ncruces/go-sqlite3/internal/util"
)
// 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(errutil.ErrorString("openblob: wrong number of arguments"))
ctx.ResultError(util.ErrorString("openblob: wrong number of arguments"))
return
}
+10 -9
View File
@@ -9,12 +9,13 @@ import (
"strings"
"testing"
"github.com/hanzoai/sqlite3"
"github.com/hanzoai/sqlite3/driver"
"github.com/hanzoai/sqlite3/ext/array"
"github.com/hanzoai/sqlite3/ext/blobio"
"github.com/hanzoai/sqlite3/internal/testcfg"
_ "github.com/hanzoai/sqlite3/vfs/memdb"
"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/vfs/memdb"
)
func Example() {
@@ -75,7 +76,7 @@ func TestMain(m *testing.M) {
func Test_readblob(t *testing.T) {
t.Parallel()
db, err := sqlite3.OpenContext(testcfg.Context(t), ":memory:")
db, err := sqlite3.Open(":memory:")
if err != nil {
t.Fatal(err)
}
@@ -166,7 +167,7 @@ func Test_readblob(t *testing.T) {
func Test_writeblob(t *testing.T) {
t.Parallel()
db, err := sqlite3.OpenContext(testcfg.Context(t), ":memory:")
db, err := sqlite3.Open(":memory:")
if err != nil {
t.Fatal(err)
}
@@ -221,7 +222,7 @@ func Test_writeblob(t *testing.T) {
func Test_openblob(t *testing.T) {
t.Parallel()
db, err := sqlite3.OpenContext(testcfg.Context(t), ":memory:")
db, err := sqlite3.Open(":memory:")
if err != nil {
t.Fatal(err)
}
+9 -9
View File
@@ -14,9 +14,9 @@ import (
"github.com/dchest/siphash"
"github.com/hanzoai/sqlite3"
"github.com/hanzoai/sqlite3/internal/errutil"
"github.com/hanzoai/sqlite3/util/sql3util"
"github.com/ncruces/go-sqlite3"
"github.com/ncruces/go-sqlite3/internal/util"
"github.com/ncruces/go-sqlite3/util/sql3util"
)
// Register registers the bloom_filter virtual table:
@@ -51,7 +51,7 @@ func create(db *sqlite3.Conn, _, schema, table string, arg ...string) (_ *bloom,
return nil, err
}
if nelem <= 0 {
return nil, errutil.ErrorString("bloom: number of elements in filter must be positive")
return nil, util.ErrorString("bloom: number of elements in filter must be positive")
}
} else {
nelem = 100
@@ -61,7 +61,7 @@ func create(db *sqlite3.Conn, _, schema, table string, arg ...string) (_ *bloom,
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)")
return nil, util.ErrorString("bloom: probability must be in the range (0,1)")
}
} else {
b.prob = 0.01
@@ -73,7 +73,7 @@ func create(db *sqlite3.Conn, _, schema, table string, arg ...string) (_ *bloom,
return nil, err
}
if b.hashes <= 0 {
return nil, errutil.ErrorString("bloom: number of hash functions must be positive")
return nil, util.ErrorString("bloom: number of hash functions must be positive")
}
} else {
b.hashes = max(1, numHashes(b.prob))
@@ -175,7 +175,7 @@ func (t *bloom) Integrity(schema, table string, flags int) error {
}
defer load.Close()
err = errutil.ErrorString("bloom: invalid parameters")
err = util.ErrorString("bloom: invalid parameters")
if !load.Step() {
return err
}
@@ -217,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, errutil.ErrorString("bloom: elements cannot be deleted")
return 0, util.ErrorString("bloom: elements cannot be deleted")
}
return 0, errutil.ErrorString("bloom: elements cannot be updated")
return 0, util.ErrorString("bloom: elements cannot be updated")
}
if arg[2].NoChange() {
+7 -6
View File
@@ -6,9 +6,10 @@ import (
"path/filepath"
"testing"
"github.com/hanzoai/sqlite3"
"github.com/hanzoai/sqlite3/ext/bloom"
"github.com/hanzoai/sqlite3/internal/testcfg"
"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"
)
func TestMain(m *testing.M) {
@@ -19,7 +20,7 @@ func TestMain(m *testing.M) {
func TestRegister(t *testing.T) {
t.Parallel()
db, err := sqlite3.OpenContext(testcfg.Context(t), ":memory:")
db, err := sqlite3.Open(":memory:")
if err != nil {
t.Fatal(err)
}
@@ -101,7 +102,7 @@ func Test_compatible(t *testing.T) {
t.Fatal(err)
}
db, err := sqlite3.OpenContext(testcfg.Context(t), "file:"+filepath.ToSlash(tmp)+"?nolock=1")
db, err := sqlite3.Open("file:" + filepath.ToSlash(tmp) + "?nolock=1")
if err != nil {
t.Fatal(err)
}
@@ -157,7 +158,7 @@ func Test_compatible(t *testing.T) {
func Test_errors(t *testing.T) {
t.Parallel()
db, err := sqlite3.OpenContext(testcfg.Context(t), ":memory:")
db, err := sqlite3.Open(":memory:")
if err != nil {
t.Fatal(err)
}
+3 -3
View File
@@ -10,9 +10,9 @@ import (
"fmt"
"math"
"github.com/hanzoai/sqlite3"
"github.com/hanzoai/sqlite3/internal/util"
"github.com/hanzoai/sqlite3/util/sql3util"
"github.com/ncruces/go-sqlite3"
"github.com/ncruces/go-sqlite3/internal/util"
"github.com/ncruces/go-sqlite3/util/sql3util"
)
const (
+6 -5
View File
@@ -7,9 +7,10 @@ import (
"os"
"testing"
"github.com/hanzoai/sqlite3"
"github.com/hanzoai/sqlite3/ext/closure"
"github.com/hanzoai/sqlite3/internal/testcfg"
"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"
)
func TestMain(m *testing.M) {
@@ -88,7 +89,7 @@ func Example() {
func TestRegister(t *testing.T) {
t.Parallel()
db, err := sqlite3.OpenContext(testcfg.Context(t), ":memory:")
db, err := sqlite3.Open(":memory:")
if err != nil {
t.Fatal(err)
}
@@ -156,7 +157,7 @@ func TestRegister(t *testing.T) {
func Test_errors(t *testing.T) {
t.Parallel()
db, err := sqlite3.OpenContext(testcfg.Context(t), ":memory:")
db, err := sqlite3.Open(":memory:")
if err != nil {
t.Fatal(err)
}
+1 -1
View File
@@ -4,7 +4,7 @@ import (
"fmt"
"strconv"
"github.com/hanzoai/sqlite3/util/sql3util"
"github.com/ncruces/go-sqlite3/util/sql3util"
)
func uintArg(key, val string) (int, error) {
+1 -1
View File
@@ -3,7 +3,7 @@ package csv
import (
"testing"
"github.com/hanzoai/sqlite3/util/sql3util"
"github.com/ncruces/go-sqlite3/util/sql3util"
)
func Test_uintArg(t *testing.T) {
+5 -6
View File
@@ -15,11 +15,10 @@ import (
"strconv"
"strings"
"github.com/hanzoai/sqlite3"
"github.com/hanzoai/sqlite3/internal/errutil"
"github.com/hanzoai/sqlite3/internal/util"
"github.com/hanzoai/sqlite3/util/osutil"
"github.com/hanzoai/sqlite3/util/sql3util"
"github.com/ncruces/go-sqlite3"
"github.com/ncruces/go-sqlite3/internal/util"
"github.com/ncruces/go-sqlite3/util/osutil"
"github.com/ncruces/go-sqlite3/util/sql3util"
)
// Register registers the CSV virtual table.
@@ -74,7 +73,7 @@ func RegisterFS(db *sqlite3.Conn, fsys fs.FS) error {
}
if (filename == "") == (data == "") {
return nil, errutil.ErrorString(`csv: must specify either "filename" or "data" but not both`)
return nil, util.ErrorString(`csv: must specify either "filename" or "data" but not both`)
}
t := &table{
+7 -6
View File
@@ -6,9 +6,10 @@ import (
"os"
"testing"
"github.com/hanzoai/sqlite3"
"github.com/hanzoai/sqlite3/ext/csv"
"github.com/hanzoai/sqlite3/internal/testcfg"
"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"
)
func Example() {
@@ -62,7 +63,7 @@ func TestMain(m *testing.M) {
func TestRegister(t *testing.T) {
t.Parallel()
db, err := sqlite3.OpenContext(testcfg.Context(t), ":memory:")
db, err := sqlite3.Open(":memory:")
if err != nil {
t.Fatal(err)
}
@@ -124,7 +125,7 @@ Robert "Griesemer" "gri"`
func TestAffinity(t *testing.T) {
t.Parallel()
db, err := sqlite3.OpenContext(testcfg.Context(t), ":memory:")
db, err := sqlite3.Open(":memory:")
if err != nil {
t.Fatal(err)
}
@@ -167,7 +168,7 @@ func TestAffinity(t *testing.T) {
func TestRegister_errors(t *testing.T) {
t.Parallel()
db, err := sqlite3.OpenContext(testcfg.Context(t), ":memory:")
db, err := sqlite3.Open(":memory:")
if err != nil {
t.Fatal(err)
}
+1 -1
View File
@@ -4,7 +4,7 @@ import (
"strconv"
"strings"
"github.com/hanzoai/sqlite3"
"github.com/ncruces/go-sqlite3"
)
func getSchema(header bool, columns int, row []string) string {
+1 -1
View File
@@ -1,6 +1,6 @@
package csv
import "github.com/hanzoai/sqlite3/util/sql3util"
import "github.com/ncruces/go-sqlite3/util/sql3util"
func getColumnAffinities(schema string) ([]sql3util.Affinity, error) {
tab, err := sql3util.ParseTable(schema)
+2 -2
View File
@@ -9,7 +9,7 @@ import (
"io/fs"
"os"
"github.com/hanzoai/sqlite3"
"github.com/ncruces/go-sqlite3"
)
// Register registers SQL functions readfile, writefile, lsmode,
@@ -30,7 +30,7 @@ func RegisterFS(db *sqlite3.Conn, fsys fs.FS) error {
db.CreateFunction("readfile", 1, sqlite3.DIRECTONLY, readfile(fsys)),
db.CreateFunction("lsmode", 1, sqlite3.DETERMINISTIC, lsmode),
sqlite3.CreateModule(db, "fsdir", nil, func(db *sqlite3.Conn, _, _, _ string, _ ...string) (fsdir, error) {
err := db.DeclareVTab(`CREATE TABLE x(name TEXT,mode INT,mtime TIMESTAMP,data BLOB,level INT,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)
}
+8 -9
View File
@@ -7,18 +7,18 @@ import (
"os"
"testing"
"github.com/hanzoai/sqlite3"
"github.com/hanzoai/sqlite3/driver"
"github.com/hanzoai/sqlite3/ext/fileio"
"github.com/hanzoai/sqlite3/internal/testcfg"
"github.com/hanzoai/sqlite3/vfs/memdb"
"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/vfs/memdb"
)
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.QueryRowContext(ctx, `SELECT lsmode(?)`, s.Mode()).Scan(&mode)
err = db.QueryRow(`SELECT lsmode(?)`, s.Mode()).Scan(&mode)
if err != nil {
t.Fatal(err)
}
@@ -55,7 +55,6 @@ 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
@@ -65,7 +64,7 @@ func Test_readfile(t *testing.T) {
}
defer db.Close()
rows, err := db.QueryContext(ctx, `SELECT readfile('fileio_test.go')`)
rows, err := db.Query(`SELECT readfile('fileio_test.go')`)
if err != nil {
t.Fatal(err)
}
+33 -96
View File
@@ -8,27 +8,22 @@ import (
"path/filepath"
"strings"
"github.com/hanzoai/sqlite3"
"github.com/ncruces/go-sqlite3"
)
const (
_COL_NAME = iota
_COL_MODE
_COL_MTIME
_COL_DATA
_COL_LEVEL
_COL_ROOT
_COL_BASE
_COL_NAME = 0
_COL_MODE = 1
_COL_TIME = 2
_COL_DATA = 3
_COL_ROOT = 4
_COL_BASE = 5
)
type fsdir struct{ fsys fs.FS }
func (d fsdir) BestIndex(idx *sqlite3.IndexInfo) error {
var levelOp sqlite3.IndexConstraintOp
var root bool
level := -1
base := -1
var root, base bool
for i, cst := range idx.Constraint {
switch cst.Column {
case _COL_ROOT:
@@ -44,44 +39,20 @@ func (d fsdir) BestIndex(idx *sqlite3.IndexInfo) error {
if !cst.Usable || cst.Op != sqlite3.INDEX_CONSTRAINT_EQ {
return sqlite3.CONSTRAINT
}
base = i
case _COL_LEVEL:
if !cst.Usable {
break
}
switch cst.Op {
case sqlite3.INDEX_CONSTRAINT_EQ, sqlite3.INDEX_CONSTRAINT_LE, sqlite3.INDEX_CONSTRAINT_LT:
levelOp = cst.Op
level = i
idx.ConstraintUsage[i] = sqlite3.IndexConstraintUsage{
Omit: true,
ArgvIndex: 2,
}
base = true
}
}
if !root {
return sqlite3.CONSTRAINT
}
args := 2
idx.IdxNum = 0
idx.EstimatedCost = 1e9
if base >= 0 {
idx.IdxNum |= 1
idx.EstimatedCost /= 1e4
idx.ConstraintUsage[base] = sqlite3.IndexConstraintUsage{
Omit: true,
ArgvIndex: args,
}
args++
}
if level >= 0 {
idx.EstimatedCost /= 1e4
idx.IdxNum |= (args - 1) << 1
if levelOp == sqlite3.INDEX_CONSTRAINT_LT {
idx.IdxNum |= 1 << 3
}
idx.ConstraintUsage[level] = sqlite3.IndexConstraintUsage{
Omit: levelOp != sqlite3.INDEX_CONSTRAINT_EQ,
ArgvIndex: args,
}
if base {
idx.EstimatedCost = 10
} else {
idx.EstimatedCost = 100
}
return nil
}
@@ -92,20 +63,18 @@ func (d fsdir) Open() (sqlite3.VTabCursor, error) {
type cursor struct {
fsdir
base string
next func() (entry, bool)
stop func()
curr entry
eof bool
rowID int64
maxLevel int
base string
next func() (entry, bool)
stop func()
curr entry
eof bool
rowID int64
}
type entry struct {
fs.DirEntry
err error
path string
level int
err error
path string
}
func (c *cursor) Close() error {
@@ -121,40 +90,24 @@ func (c *cursor) Filter(idxNum int, idxStr string, arg ...sqlite3.Value) error {
}
root := arg[0].Text()
if i := idxNum & 1; i > 0 {
base := arg[i].Text()
if len(arg) > 1 {
base := arg[1].Text()
if c.fsys != nil {
root = path.Join(base, root)
base = path.Clean(base) + "/"
} else {
root = filepath.Join(base, root)
base = filepath.Clean(base) + string(filepath.Separator)
}
root = base + root
c.base = base
}
if i := idxNum >> 1; i > 0 {
c.maxLevel = arg[i&3].Int() - i>>2
}
c.next, c.stop = iter.Pull(func(yield func(entry) bool) {
var stack []string
walkDir := func(p string, d fs.DirEntry, err error) error {
level := len(stack)
for level > 1 && !strings.HasPrefix(c.dir(p), stack[level-1]) {
level--
walkDir := func(path string, d fs.DirEntry, err error) error {
if yield(entry{d, err, path}) {
return nil
}
stack = stack[:level]
level++
if !yield(entry{d, err, p, level}) {
return fs.SkipAll
}
if d != nil && d.IsDir() {
if 0 < c.maxLevel && c.maxLevel <= level {
return fs.SkipDir
}
stack = append(stack, p)
}
return nil
return fs.SkipAll
}
if c.fsys != nil {
fs.WalkDir(c.fsys, root, walkDir)
@@ -196,7 +149,7 @@ func (c *cursor) Column(ctx sqlite3.Context, n int) error {
}
ctx.ResultInt64(int64(i.Mode()))
case _COL_MTIME:
case _COL_TIME:
i, err := c.curr.Info()
if err != nil {
return err
@@ -225,22 +178,6 @@ func (c *cursor) Column(ctx sqlite3.Context, n int) error {
}
ctx.ResultText(t)
}
case _COL_LEVEL:
ctx.ResultInt(c.curr.level)
}
return nil
}
func (c *cursor) dir(p string) string {
var dir string
if c.fsys != nil {
dir, _ = path.Split(p)
} else {
dir, _ = filepath.Split(p)
}
if dir == "" {
dir = "."
}
return dir
}
+9 -13
View File
@@ -8,11 +8,12 @@ import (
"testing"
"time"
"github.com/hanzoai/sqlite3"
"github.com/hanzoai/sqlite3/driver"
"github.com/hanzoai/sqlite3/ext/fileio"
"github.com/hanzoai/sqlite3/internal/testcfg"
"github.com/hanzoai/sqlite3/vfs/memdb"
"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/vfs/memdb"
)
func Test_fsdir(t *testing.T) {
@@ -22,7 +23,6 @@ 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.QueryContext(ctx, `SELECT * FROM fsdir('.') WHERE level <= 2`)
rows, err := db.Query(`SELECT * FROM fsdir('.', '.')`)
if err != nil {
t.Fatal(err)
}
@@ -42,8 +42,7 @@ func Test_fsdir(t *testing.T) {
var mode fs.FileMode
var mtime time.Time
var data sql.RawBytes
var level int
err := rows.Scan(&name, &mode, sqlite3.TimeFormatUnixFrac.Scanner(&mtime), &data, &level)
err := rows.Scan(&name, &mode, sqlite3.TimeFormatUnixFrac.Scanner(&mtime), &data)
if err != nil {
t.Fatal(err)
}
@@ -58,9 +57,6 @@ func Test_fsdir(t *testing.T) {
t.Errorf("got: %s", data[:min(64, len(data))])
}
}
if level > 2 {
t.Errorf("got: %v", level)
}
}
})
}
@@ -69,7 +65,7 @@ func Test_fsdir(t *testing.T) {
func Test_fsdir_errors(t *testing.T) {
t.Parallel()
db, err := sqlite3.OpenContext(testcfg.Context(t), ":memory:")
db, err := sqlite3.Open(":memory:")
if err != nil {
t.Fatal(err)
}
+4 -4
View File
@@ -8,14 +8,14 @@ import (
"path/filepath"
"time"
"github.com/hanzoai/sqlite3"
"github.com/hanzoai/sqlite3/internal/errutil"
"github.com/hanzoai/sqlite3/util/fsutil"
"github.com/ncruces/go-sqlite3"
"github.com/ncruces/go-sqlite3/internal/util"
"github.com/ncruces/go-sqlite3/util/fsutil"
)
func writefile(ctx sqlite3.Context, arg ...sqlite3.Value) {
if len(arg) < 2 || len(arg) > 4 {
ctx.ResultError(errutil.ErrorString("writefile: wrong number of arguments"))
ctx.ResultError(util.ErrorString("writefile: wrong number of arguments"))
return
}
+12 -15
View File
@@ -1,5 +1,3 @@
//go:build !js
package fileio
import (
@@ -9,16 +7,16 @@ import (
"testing"
"time"
"github.com/hanzoai/sqlite3/driver"
"github.com/hanzoai/sqlite3/internal/testcfg"
"github.com/hanzoai/sqlite3/vfs/memdb"
"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"
)
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)
@@ -32,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.ExecContext(ctx, `SELECT writefile(?, 'Hello world!')`, file)
_, err = db.Exec(`SELECT writefile(?, 'Hello world!')`, file)
if err != nil {
t.Fatal(err)
}
_, err = db.ExecContext(ctx, `SELECT writefile(?, ?, ?)`, link, "test.txt", fs.ModeSymlink)
_, err = db.Exec(`SELECT writefile(?, ?, ?)`, link, "test.txt", fs.ModeSymlink)
if err != nil {
t.Fatal(err)
}
_, err = db.ExecContext(ctx, `SELECT writefile(?, ?, ?, ?)`, dir, nil, 0040700, twosday.Unix())
_, err = db.Exec(`SELECT writefile(?, ?, ?, ?)`, dir, nil, 0040700, twosday.Unix())
if err != nil {
t.Fatal(err)
}
rows, err := db.QueryContext(ctx, `SELECT * FROM fsdir('.', ?)`, dir)
rows, err := db.Query(`SELECT * FROM fsdir('.', ?)`, dir)
if err != nil {
t.Fatal(err)
}
@@ -57,8 +55,7 @@ func Test_writefile(t *testing.T) {
var mode fs.FileMode
var mtime time.Time
var data sql.NullString
var level int
err := rows.Scan(&name, &mode, &mtime, &data, &level)
err := rows.Scan(&name, &mode, &mtime, &data)
if err != nil {
t.Fatal(err)
}
@@ -73,19 +70,19 @@ func Test_writefile(t *testing.T) {
}
}
_, err = db.ExecContext(ctx, `SELECT writefile(?, 'Hello world!')`, nest)
_, err = db.Exec(`SELECT writefile(?, 'Hello world!')`, nest)
if err != nil {
t.Fatal(err)
}
_, err = db.ExecContext(ctx, `SELECT writefile(?, ?, ?)`, sock, nil, fs.ModeSocket)
_, err = db.Exec(`SELECT writefile(?, ?, ?)`, sock, nil, fs.ModeSocket)
if err == nil {
t.Fatal("want error")
} else {
t.Log(err)
}
_, err = db.ExecContext(ctx, `SELECT writefile()`)
_, err = db.Exec(`SELECT writefile()`)
if err == nil {
t.Fatal("want error")
} else {
-14
View File
@@ -1,14 +0,0 @@
// Package fts5 provides the fts5 extension.
//
// https://sqlite.org/fts5.html
package fts5
import (
"github.com/hanzoai/sqlite3"
"github.com/ncruces/go-sqlite3-wasm/v3/fts5"
)
// Register registers the fts5 extension.
func Register(db *sqlite3.Conn) error {
return sqlite3.ExtensionInit(db, fts5.New, fts5.DylinkInfo)
}
-37
View File
@@ -1,37 +0,0 @@
package fts5_test
import (
"fmt"
"log"
"github.com/hanzoai/sqlite3/driver"
"github.com/hanzoai/sqlite3/ext/fts5"
_ "github.com/hanzoai/sqlite3/vfs/memdb"
)
func Example() {
db, err := driver.Open("file:/test.db?vfs=memdb", fts5.Register)
if err != nil {
log.Fatal(err)
}
defer db.Close()
_, err = db.Exec(`
CREATE VIRTUAL TABLE docs USING fts5(title, body);
INSERT INTO docs(title, body) VALUES
('Go Programming', 'An intensive guide to Go routines.'),
('SQLite Tutorial', 'Learn how to use virtual tables efficiently.');
`)
if err != nil {
log.Fatal(err)
}
var title string
err = db.QueryRow("SELECT title FROM docs WHERE docs MATCH 'Go AND routines'").Scan(&title)
if err != nil {
log.Fatal(err)
}
fmt.Println(title)
// Output: Go Programming
}
+3 -3
View File
@@ -3,8 +3,8 @@ package hash
import (
"crypto"
"github.com/hanzoai/sqlite3"
"github.com/hanzoai/sqlite3/internal/errutil"
"github.com/ncruces/go-sqlite3"
"github.com/ncruces/go-sqlite3/internal/util"
)
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(errutil.ErrorString("blake2b: size must be 256, 384, 512"))
ctx.ResultError(util.ErrorString("blake2b: size must be 256, 384, 512"))
}
}
+3 -3
View File
@@ -23,15 +23,15 @@ import (
"crypto"
"errors"
"github.com/hanzoai/sqlite3"
"github.com/hanzoai/sqlite3/internal/errutil"
"github.com/ncruces/go-sqlite3"
"github.com/ncruces/go-sqlite3/internal/util"
)
// Register registers cryptographic hash functions for a database connection.
func Register(db *sqlite3.Conn) error {
const flags = sqlite3.DETERMINISTIC | sqlite3.INNOCUOUS
var errs errutil.ErrorJoiner
var errs util.ErrorJoiner
if crypto.MD4.Available() {
errs.Join(
db.CreateFunction("md4", 1, flags, md4Func))
+9 -9
View File
@@ -13,9 +13,10 @@ import (
_ "golang.org/x/crypto/md4"
_ "golang.org/x/crypto/ripemd160"
"github.com/hanzoai/sqlite3/driver"
"github.com/hanzoai/sqlite3/internal/testcfg"
"github.com/hanzoai/sqlite3/vfs/memdb"
"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"
)
func TestRegister(t *testing.T) {
@@ -54,7 +55,6 @@ 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.QueryRowContext(ctx, `SELECT hex(`+tt.name+`)`).Scan(&hash)
err = db.QueryRow(`SELECT hex(` + tt.name + `)`).Scan(&hash)
if err != nil {
t.Fatal(err)
}
@@ -76,22 +76,22 @@ func TestRegister(t *testing.T) {
})
}
_, err = db.ExecContext(ctx, `SELECT sha256('', 255)`)
_, err = db.Exec(`SELECT sha256('', 255)`)
if err == nil {
t.Error("want error")
}
_, err = db.ExecContext(ctx, `SELECT sha512('', 255)`)
_, err = db.Exec(`SELECT sha512('', 255)`)
if err == nil {
t.Error("want error")
}
_, err = db.ExecContext(ctx, `SELECT sha3('', 255)`)
_, err = db.Exec(`SELECT sha3('', 255)`)
if err == nil {
t.Error("want error")
}
_, err = db.ExecContext(ctx, `SELECT blake2b('', 255)`)
_, err = db.Exec(`SELECT blake2b('', 255)`)
if err == nil {
t.Error("want error")
}
+4 -4
View File
@@ -3,8 +3,8 @@ package hash
import (
"crypto"
"github.com/hanzoai/sqlite3"
"github.com/hanzoai/sqlite3/internal/errutil"
"github.com/ncruces/go-sqlite3"
"github.com/ncruces/go-sqlite3/internal/util"
)
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(errutil.ErrorString("sha256: size must be 224, 256"))
ctx.ResultError(util.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(errutil.ErrorString("sha512: size must be 224, 256, 384, 512"))
ctx.ResultError(util.ErrorString("sha512: size must be 224, 256, 384, 512"))
}
}
+3 -3
View File
@@ -3,8 +3,8 @@ package hash
import (
"crypto"
"github.com/hanzoai/sqlite3"
"github.com/hanzoai/sqlite3/internal/errutil"
"github.com/ncruces/go-sqlite3"
"github.com/ncruces/go-sqlite3/internal/util"
)
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(errutil.ErrorString("sha3: size must be 224, 256, 384, 512"))
ctx.ResultError(util.ErrorString("sha3: size must be 224, 256, 384, 512"))
}
}
+1 -1
View File
@@ -13,7 +13,7 @@ import (
"errors"
"net/netip"
"github.com/hanzoai/sqlite3"
"github.com/ncruces/go-sqlite3"
)
// Register IP/CIDR functions for a database connection.
+13 -13
View File
@@ -3,17 +3,17 @@ package ipaddr_test
import (
"testing"
"github.com/hanzoai/sqlite3/driver"
"github.com/hanzoai/sqlite3/ext/ipaddr"
"github.com/hanzoai/sqlite3/internal/testcfg"
"github.com/hanzoai/sqlite3/vfs/memdb"
"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/vfs/memdb"
)
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.QueryRowContext(ctx, `SELECT ipfamily('::1')`).Scan(&got)
err = db.QueryRow(`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.QueryRowContext(ctx, `SELECT ipfamily('[::1]:80')`).Scan(&got)
err = db.QueryRow(`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.QueryRowContext(ctx, `SELECT ipfamily('192.168.1.5/24')`).Scan(&got)
err = db.QueryRow(`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.QueryRowContext(ctx, `SELECT iphost('192.168.1.5/24')`).Scan(&got)
err = db.QueryRow(`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.QueryRowContext(ctx, `SELECT ipmasklen('192.168.1.5/24')`).Scan(&got)
err = db.QueryRow(`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.QueryRowContext(ctx, `SELECT ipnetwork('192.168.1.5/24')`).Scan(&got)
err = db.QueryRow(`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.QueryRowContext(ctx, `SELECT ipcontains('192.168.1.0/24', '192.168.1.5')`).Scan(&got)
err = db.QueryRow(`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.QueryRowContext(ctx, `SELECT ipoverlaps('192.168.1.0/24', '192.168.1.5/32')`).Scan(&got)
err = db.QueryRow(`SELECT ipoverlaps('192.168.1.0/24', '192.168.1.5/32')`).Scan(&got)
if err != nil {
t.Fatal(err)
}
+2 -2
View File
@@ -18,8 +18,8 @@ import (
"io"
"io/fs"
"github.com/hanzoai/sqlite3"
"github.com/hanzoai/sqlite3/util/osutil"
"github.com/ncruces/go-sqlite3"
"github.com/ncruces/go-sqlite3/util/osutil"
)
// Register registers the lines and lines_read table-valued functions.
+11 -14
View File
@@ -10,11 +10,12 @@ import (
"strings"
"testing"
"github.com/hanzoai/sqlite3"
"github.com/hanzoai/sqlite3/driver"
"github.com/hanzoai/sqlite3/ext/lines"
"github.com/hanzoai/sqlite3/internal/testcfg"
"github.com/hanzoai/sqlite3/vfs/memdb"
"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/vfs/memdb"
)
func Example() {
@@ -68,7 +69,6 @@ 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.QueryContext(ctx, `SELECT rowid, line FROM lines(?)`, data)
rows, err := db.Query(`SELECT rowid, line FROM lines(?)`, data)
if err != nil {
t.Fatal(err)
}
@@ -100,21 +100,20 @@ 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.ExecContext(ctx, `SELECT rowid, line FROM lines(?)`, nil)
_, err = db.Exec(`SELECT rowid, line FROM lines(?)`, nil)
if err == nil {
t.Fatal("want error")
} else {
t.Log(err)
}
_, err = db.ExecContext(ctx, `SELECT rowid, line FROM lines_read(?)`, "xpto")
_, err = db.Exec(`SELECT rowid, line FROM lines_read(?)`, "xpto")
if err == nil {
t.Fatal("want error")
} else {
@@ -126,7 +125,6 @@ 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)
@@ -135,7 +133,7 @@ func Test_lines_read(t *testing.T) {
const data = "line 1\nline 2\r\nline 3\n"
rows, err := db.QueryContext(ctx, `SELECT rowid, line FROM lines_read(?)`,
rows, err := db.Query(`SELECT rowid, line FROM lines_read(?)`,
sqlite3.Pointer(strings.NewReader(data)))
if err != nil {
t.Fatal(err)
@@ -159,14 +157,13 @@ 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.QueryContext(ctx, `SELECT rowid, line FROM lines_read(?, '}')`, "lines_test.go")
rows, err := db.Query(`SELECT rowid, line FROM lines_read(?, '}')`, "lines_test.go")
if errors.Is(err, os.ErrNotExist) {
t.Skip(err)
}
+1 -1
View File
@@ -3,7 +3,7 @@ package pivot
import (
"testing"
"github.com/hanzoai/sqlite3"
"github.com/ncruces/go-sqlite3"
)
func Test_operator(t *testing.T) {
+11 -11
View File
@@ -8,8 +8,8 @@ import (
"fmt"
"strings"
"github.com/hanzoai/sqlite3"
"github.com/hanzoai/sqlite3/internal/errutil"
"github.com/ncruces/go-sqlite3"
"github.com/ncruces/go-sqlite3/internal/util"
)
// Register registers the pivot virtual table.
@@ -43,13 +43,13 @@ func declare(db *sqlite3.Conn, _, _, _ string, arg ...string) (ret *table, err e
// Row key query.
t.scan = "SELECT * FROM\n" + arg[0]
stmt, tail, err := db.PrepareFlags(t.scan, sqlite3.PREPARE_FROM_DDL)
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
return nil, util.TailErr
}
t.keys = make([]string, stmt.ColumnCount())
@@ -65,16 +65,16 @@ func declare(db *sqlite3.Conn, _, _, _ string, arg ...string) (ret *table, err e
stmt.Close()
// Column definition query.
stmt, tail, err = db.PrepareFlags("SELECT * FROM\n"+arg[1], sqlite3.PREPARE_FROM_DDL)
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
return nil, util.TailErr
}
if stmt.ColumnCount() != 2 {
return nil, errutil.ErrorString("pivot: column definition query expects 2 result columns")
return nil, util.ErrorString("pivot: column definition query expects 2 result columns")
}
for stmt.Step() {
name := sqlite3.QuoteIdentifier(stmt.ColumnText(1))
@@ -89,16 +89,16 @@ func declare(db *sqlite3.Conn, _, _, _ string, arg ...string) (ret *table, err e
// Pivot cell query.
t.cell = "SELECT * FROM\n" + arg[2]
stmt, tail, err = db.PrepareFlags(t.cell, sqlite3.PREPARE_FROM_DDL)
stmt, tail, err = db.PrepareFlags(t.cell, 0 /*PREPARE_FROM_DDL*/)
if err != nil {
return nil, err
}
if tail != "" {
return nil, errutil.TailErr
return nil, util.TailErr
}
if stmt.ColumnCount() != 1 {
return nil, errutil.ErrorString("pivot: cell query expects 1 result columns")
return nil, util.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)
@@ -196,7 +196,7 @@ func (c *cursor) Filter(idxNum int, idxStr string, arg ...sqlite3.Value) error {
return err
}
const prepflags = sqlite3.PREPARE_DONT_LOG | sqlite3.PREPARE_FROM_DDL
const prepflags = sqlite3.PREPARE_DONT_LOG /*PREPARE_FROM_DDL*/
c.scan, _, err = c.table.db.PrepareFlags(idxStr, prepflags)
if err != nil {
+6 -5
View File
@@ -7,9 +7,10 @@ import (
"strings"
"testing"
"github.com/hanzoai/sqlite3"
"github.com/hanzoai/sqlite3/ext/pivot"
"github.com/hanzoai/sqlite3/internal/testcfg"
"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"
)
// https://antonz.org/sqlite-pivot-table/
@@ -91,7 +92,7 @@ func TestMain(m *testing.M) {
func TestRegister(t *testing.T) {
t.Parallel()
db, err := sqlite3.OpenContext(testcfg.Context(t), ":memory:")
db, err := sqlite3.Open(":memory:")
if err != nil {
t.Fatal(err)
}
@@ -155,7 +156,7 @@ func TestRegister(t *testing.T) {
func TestRegister_errors(t *testing.T) {
t.Parallel()
db, err := sqlite3.OpenContext(testcfg.Context(t), ":memory:")
db, err := sqlite3.Open(":memory:")
if err != nil {
t.Fatal(err)
}
+1 -1
View File
@@ -20,7 +20,7 @@ import (
"strings"
"unicode/utf8"
"github.com/hanzoai/sqlite3"
"github.com/ncruces/go-sqlite3"
)
// Register registers Unicode aware functions for a database connection.
+8 -10
View File
@@ -6,17 +6,17 @@ import (
"strings"
"testing"
"github.com/hanzoai/sqlite3"
"github.com/hanzoai/sqlite3/driver"
"github.com/hanzoai/sqlite3/internal/testcfg"
"github.com/hanzoai/sqlite3/vfs/memdb"
"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"
)
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.QueryRowContext(ctx, `SELECT `+tt.test).Scan(&got)
err := db.QueryRow(`SELECT ` + tt.test).Scan(&got)
if err != nil {
t.Fatal(err)
}
@@ -82,7 +82,6 @@ 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)
@@ -99,7 +98,7 @@ func TestRegister_errors(t *testing.T) {
}
for _, tt := range tests {
err := db.QueryRowContext(ctx, `SELECT `+tt, `\`).Scan(nil)
err := db.QueryRow(`SELECT `+tt, `\`).Scan(nil)
if err == nil {
t.Fatal("want error")
}
@@ -110,7 +109,6 @@ 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)
@@ -118,7 +116,7 @@ func TestRegister_pointer(t *testing.T) {
defer db.Close()
var got int
err = db.QueryRowContext(ctx, `SELECT regexp_count('ABCABCAXYaxy', ?, 1)`,
err = db.QueryRow(`SELECT regexp_count('ABCABCAXYaxy', ?, 1)`,
sqlite3.Pointer(regexp.MustCompile(`(?i)A.`))).Scan(&got)
if err != nil {
t.Fatal(err)
-15
View File
@@ -1,15 +0,0 @@
// Package rtree provides the rtree and geopoly virtual tables.
//
// https://sqlite.org/rtree.html
// https://sqlite.org/geopoly.html
package rtree
import (
"github.com/hanzoai/sqlite3"
"github.com/ncruces/go-sqlite3-wasm/v3/rtree"
)
// Register registers the rtree and geopoly virtual tables.
func Register(db *sqlite3.Conn) error {
return sqlite3.ExtensionInit(db, rtree.New, rtree.DylinkInfo)
}
-63
View File
@@ -1,63 +0,0 @@
package rtree_test
import (
"fmt"
"log"
"github.com/hanzoai/sqlite3/driver"
"github.com/hanzoai/sqlite3/ext/rtree"
_ "github.com/hanzoai/sqlite3/vfs/memdb"
)
func Example_rtree() {
db, err := driver.Open("file:/test.db?vfs=memdb", rtree.Register)
if err != nil {
log.Fatal(err)
}
defer db.Close()
_, err = db.Exec(`
CREATE VIRTUAL TABLE locations USING rtree(id, minX, maxX, minY, maxY);
INSERT INTO locations VALUES (1, -10.0, -5.0, 30.0, 35.0);
INSERT INTO locations VALUES (2, 100.0, 105.0, 0.0, 5.0);
`)
if err != nil {
log.Fatal(err)
}
var id int
err = db.QueryRow(`SELECT id FROM locations WHERE
minX >= -12.0 AND maxX <= -4.0 AND
minY >= 28.0 AND maxY <= 36.0`).Scan(&id)
if err != nil {
log.Fatal(err)
}
fmt.Println(id)
// Output: 1
}
func Example_geopoly() {
db, err := driver.Open("file:/test.db?vfs=memdb", rtree.Register)
if err != nil {
log.Fatal(err)
}
defer db.Close()
_, err = db.Exec(`
CREATE VIRTUAL TABLE areas USING geopoly(name);
INSERT INTO areas(name, _shape) VALUES ('Triangle Area', '[[0,0],[2,0],[1,2],[0,0]]');
`)
if err != nil {
log.Fatal(err)
}
var name string
err = db.QueryRow("SELECT name FROM areas WHERE geopoly_contains_point(_shape, 1.0, 1.0)").Scan(&name)
if err != nil {
log.Fatal(err)
}
fmt.Println(name)
// Output: Triangle Area
}
+6 -6
View File
@@ -2,12 +2,12 @@
package serdes
import (
"github.com/hanzoai/sqlite3"
"github.com/hanzoai/sqlite3/util/vfsutil"
"github.com/hanzoai/sqlite3/vfs"
"github.com/ncruces/go-sqlite3"
"github.com/ncruces/go-sqlite3/util/vfsutil"
"github.com/ncruces/go-sqlite3/vfs"
)
const vfsName = "github.com/hanzoai/sqlite3/ext/serdes.sliceVFS"
const vfsName = "github.com/ncruces/go-sqlite3/ext/serdes.sliceVFS"
func init() {
vfs.Register(vfsName, sliceVFS{})
@@ -37,8 +37,8 @@ func Serialize(db *sqlite3.Conn, schema string) ([]byte, error) {
//
// https://sqlite.org/c3ref/deserialize.html
//
// ["memdb"]: https://pkg.go.dev/github.com/hanzoai/sqlite3/vfs/memdb
// ["reader"]: https://pkg.go.dev/github.com/hanzoai/sqlite3/vfs/readervfs
// ["memdb"]: https://pkg.go.dev/github.com/ncruces/go-sqlite3/vfs/memdb
// ["reader"]: https://pkg.go.dev/github.com/ncruces/go-sqlite3/vfs/readervfs
func Deserialize(db *sqlite3.Conn, schema string, data []byte) error {
fileToOpen <- &data
return db.Restore(schema, "file:serdes.db?immutable=1&vfs="+vfsName)
+7 -12
View File
@@ -7,21 +7,16 @@ import (
"net/http"
"testing"
"github.com/hanzoai/sqlite3"
"github.com/hanzoai/sqlite3/ext/serdes"
"github.com/hanzoai/sqlite3/internal/testcfg"
"github.com/hanzoai/sqlite3/vfs"
"github.com/ncruces/go-sqlite3"
_ "github.com/ncruces/go-sqlite3/embed"
"github.com/ncruces/go-sqlite3/ext/serdes"
)
//go:embed testdata/wal.db
var walDB []byte
func Test_wal(t *testing.T) {
if !vfs.SupportsFileLocking {
t.Skip("skipping without locks")
}
db, err := sqlite3.OpenContext(testcfg.Context(t), "testdata/wal.db")
db, err := sqlite3.Open("testdata/wal.db")
if err != nil {
t.Fatal(err)
}
@@ -50,7 +45,7 @@ func Test_northwind(t *testing.T) {
t.Fatal(err)
}
db, err := sqlite3.OpenContext(testcfg.Context(t), ":memory:")
db, err := sqlite3.Open(":memory:")
if err != nil {
t.Fatal(err)
}
@@ -102,7 +97,7 @@ func httpGet() ([]byte, error) {
}
func TestOpen_errors(t *testing.T) {
_, err := sqlite3.OpenContext(testcfg.Context(t), "file:test.db?vfs=github.com/hanzoai/sqlite3/ext/serdes.sliceVFS")
_, err := sqlite3.Open("file:test.db?vfs=github.com/ncruces/go-sqlite3/ext/serdes.sliceVFS")
if err == nil {
t.Error("want error")
}
@@ -110,7 +105,7 @@ func TestOpen_errors(t *testing.T) {
t.Errorf("got %v, want sqlite3.CANTOPEN", err)
}
_, err = sqlite3.OpenContext(testcfg.Context(t), "file:serdes.db?vfs=github.com/hanzoai/sqlite3/ext/serdes.sliceVFS")
_, err = sqlite3.Open("file:serdes.db?vfs=github.com/ncruces/go-sqlite3/ext/serdes.sliceVFS")
if err == nil {
t.Error("want error")
}
-14
View File
@@ -1,14 +0,0 @@
// Package spellfix1 provides the spellfix1 virtual table.
//
// https://sqlite.org/spellfix1.html
package spellfix1
import (
"github.com/hanzoai/sqlite3"
"github.com/ncruces/go-sqlite3-wasm/v3/spellfix"
)
// Register registers the spellfix1 virtual table.
func Register(db *sqlite3.Conn) error {
return sqlite3.ExtensionInit(db, spellfix.New, spellfix.DylinkInfo)
}
-65
View File
@@ -1,65 +0,0 @@
package spellfix1_test
import (
"testing"
"github.com/hanzoai/sqlite3/driver"
"github.com/hanzoai/sqlite3/ext/spellfix1"
"github.com/hanzoai/sqlite3/internal/testcfg"
"github.com/hanzoai/sqlite3/vfs/memdb"
)
func Test(t *testing.T) {
dsn := memdb.TestDB(t)
ctx := testcfg.Context(t)
db, err := driver.Open(dsn, spellfix1.Register)
if err != nil {
t.Fatal(err)
}
defer db.Close()
_, err = db.ExecContext(ctx, `
CREATE TABLE cost1(iLang, cFrom, cTo, iCost);
INSERT INTO cost1 VALUES
(0, '', '?', 97),
(0, '?', '', 98),
(0, '?', '?', 99),
(0, 'm', 'n', 50),
(0, 'n', 'm', 50);
SELECT editdist3('cost1');
`)
if err != nil {
t.Fatal(err)
}
tests := []struct {
s1 string
s2 string
want int
}{
{"anchor", "amchor", 50},
{"anchor", "anchoxr", 97},
{"anchor", "anchorx", 97},
{"anchor", "anchr", 98},
{"anchor", "ancho", 98},
{"anchor", "nchor", 98},
{"anchor", "anchur", 99},
{"anchor", "onchor", 99},
{"anchor", "anchot", 99},
{"anchor", "omchor", 149},
}
for _, tt := range tests {
t.Run("", func(t *testing.T) {
var got int
err := db.QueryRowContext(ctx, `SELECT editdist3(?, ?)`, tt.s1, tt.s2).Scan(&got)
if err != nil {
t.Fatal(err)
}
if got != tt.want {
t.Errorf("got %d, want %d", got, tt.want)
}
})
}
}
+6 -6
View File
@@ -13,8 +13,8 @@ import (
"strings"
"unsafe"
"github.com/hanzoai/sqlite3"
"github.com/hanzoai/sqlite3/internal/errutil"
"github.com/ncruces/go-sqlite3"
"github.com/ncruces/go-sqlite3/internal/util"
)
// Register registers the statement virtual table.
@@ -30,19 +30,19 @@ type table struct {
func declare(db *sqlite3.Conn, _, _, _ string, arg ...string) (*table, error) {
if len(arg) != 1 {
return nil, errutil.ErrorString("statement: wrong number of arguments")
return nil, util.ErrorString("statement: wrong number of arguments")
}
sql := "SELECT * FROM\n" + arg[0]
stmt, tail, err := db.PrepareFlags(sql,
sqlite3.PREPARE_PERSISTENT|sqlite3.PREPARE_FROM_DDL)
sqlite3.PREPARE_PERSISTENT /*PREPARE_FROM_DDL*/)
if err != nil {
return nil, err
}
if tail != "" {
stmt.Close()
return nil, errutil.TailErr
return nil, util.TailErr
}
var sep string
@@ -135,7 +135,7 @@ func (t *table) Open() (_ sqlite3.VTabCursor, err error) {
t.inuse = true
} else {
stmt, _, err = t.stmt.Conn().PrepareFlags(t.sql,
sqlite3.PREPARE_DONT_LOG|sqlite3.PREPARE_FROM_DDL)
sqlite3.PREPARE_DONT_LOG /*PREPARE_FROM_DDL*/)
if err != nil {
return nil, err
}
+6 -5
View File
@@ -6,9 +6,10 @@ import (
"os"
"testing"
"github.com/hanzoai/sqlite3"
"github.com/hanzoai/sqlite3/ext/statement"
"github.com/hanzoai/sqlite3/internal/testcfg"
"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"
)
func Example() {
@@ -56,7 +57,7 @@ func TestMain(m *testing.M) {
func TestRegister(t *testing.T) {
t.Parallel()
db, err := sqlite3.OpenContext(testcfg.Context(t), ":memory:")
db, err := sqlite3.Open(":memory:")
if err != nil {
t.Fatal(err)
}
@@ -106,7 +107,7 @@ func TestRegister(t *testing.T) {
func TestRegister_errors(t *testing.T) {
t.Parallel()
db, err := sqlite3.OpenContext(testcfg.Context(t), ":memory:")
db, err := sqlite3.Open(":memory:")
if err != nil {
t.Fatal(err)
}
+1 -1
View File
@@ -1,6 +1,6 @@
package stats
import "github.com/hanzoai/sqlite3"
import "github.com/ncruces/go-sqlite3"
const (
every = iota
+4 -3
View File
@@ -3,14 +3,15 @@ package stats_test
import (
"testing"
"github.com/hanzoai/sqlite3"
"github.com/hanzoai/sqlite3/internal/testcfg"
"github.com/ncruces/go-sqlite3"
_ "github.com/ncruces/go-sqlite3/embed"
_ "github.com/ncruces/go-sqlite3/internal/testcfg"
)
func TestRegister_boolean(t *testing.T) {
t.Parallel()
db, err := sqlite3.OpenContext(testcfg.Context(t), ":memory:")
db, err := sqlite3.Open(":memory:")
if err != nil {
t.Fatal(err)
}
+1 -1
View File
@@ -3,7 +3,7 @@ package stats
import (
"math"
"github.com/hanzoai/sqlite3"
"github.com/ncruces/go-sqlite3"
)
func cot(ctx sqlite3.Context, arg ...sqlite3.Value) {
+1 -1
View File
@@ -3,7 +3,7 @@ package stats
import (
"unsafe"
"github.com/hanzoai/sqlite3"
"github.com/ncruces/go-sqlite3"
)
func newMode() sqlite3.AggregateFunction {
+4 -3
View File
@@ -3,14 +3,15 @@ package stats_test
import (
"testing"
"github.com/hanzoai/sqlite3"
"github.com/hanzoai/sqlite3/internal/testcfg"
"github.com/ncruces/go-sqlite3"
_ "github.com/ncruces/go-sqlite3/embed"
_ "github.com/ncruces/go-sqlite3/internal/testcfg"
)
func TestRegister_mode(t *testing.T) {
t.Parallel()
db, err := sqlite3.OpenContext(testcfg.Context(t), ":memory:")
db, err := sqlite3.Open(":memory:")
if err != nil {
t.Fatal(err)
}
+3 -4
View File
@@ -6,9 +6,8 @@ import (
"math"
"slices"
"github.com/hanzoai/sqlite3"
"github.com/hanzoai/sqlite3/internal/errutil"
"github.com/hanzoai/sqlite3/internal/util"
"github.com/ncruces/go-sqlite3"
"github.com/ncruces/go-sqlite3/internal/util"
"github.com/ncruces/sort/quick"
)
@@ -84,7 +83,7 @@ func (q *percentile) at(pos float64) (float64, error) {
pos = pos / 100
}
if pos < 0 || pos > 1 {
return 0, errutil.ErrorString("invalid pos")
return 0, util.ErrorString("invalid pos")
}
i, f := math.Modf(pos * float64(len(q.nums)-1))
+4 -3
View File
@@ -4,14 +4,15 @@ import (
"slices"
"testing"
"github.com/hanzoai/sqlite3"
"github.com/hanzoai/sqlite3/internal/testcfg"
"github.com/ncruces/go-sqlite3"
_ "github.com/ncruces/go-sqlite3/embed"
_ "github.com/ncruces/go-sqlite3/internal/testcfg"
)
func TestRegister_percentile(t *testing.T) {
t.Parallel()
db, err := sqlite3.OpenContext(testcfg.Context(t), ":memory:")
db, err := sqlite3.Open(":memory:")
if err != nil {
t.Fatal(err)
}
-13
View File
@@ -1,13 +0,0 @@
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%
+1 -1
View File
@@ -53,7 +53,7 @@ package stats
import (
"errors"
"github.com/hanzoai/sqlite3"
"github.com/ncruces/go-sqlite3"
)
// Register registers statistics functions.
+9 -8
View File
@@ -5,9 +5,10 @@ import (
"os"
"testing"
"github.com/hanzoai/sqlite3"
"github.com/hanzoai/sqlite3/ext/stats"
"github.com/hanzoai/sqlite3/internal/testcfg"
"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"
)
func TestMain(m *testing.M) {
@@ -18,7 +19,7 @@ func TestMain(m *testing.M) {
func TestRegister_variance(t *testing.T) {
t.Parallel()
db, err := sqlite3.OpenContext(testcfg.Context(t), ":memory:")
db, err := sqlite3.Open(":memory:")
if err != nil {
t.Fatal(err)
}
@@ -117,7 +118,7 @@ func TestRegister_variance(t *testing.T) {
func TestRegister_covariance(t *testing.T) {
t.Parallel()
db, err := sqlite3.OpenContext(testcfg.Context(t), ":memory:")
db, err := sqlite3.Open(":memory:")
if err != nil {
t.Fatal(err)
}
@@ -232,7 +233,7 @@ func TestRegister_covariance(t *testing.T) {
}
func Benchmark_average(b *testing.B) {
db, err := sqlite3.OpenContext(b.Context(), ":memory:")
db, err := sqlite3.Open(":memory:")
if err != nil {
b.Fatal(err)
}
@@ -266,7 +267,7 @@ func Benchmark_average(b *testing.B) {
}
func Benchmark_variance(b *testing.B) {
db, err := sqlite3.OpenContext(b.Context(), ":memory:")
db, err := sqlite3.Open(":memory:")
if err != nil {
b.Fatal(err)
}
@@ -302,7 +303,7 @@ func Benchmark_variance(b *testing.B) {
func Benchmark_math(b *testing.B) {
benchmarks := []string{"sqrt", "tan", "cot", "cbrt"}
db, err := sqlite3.OpenContext(b.Context(), ":memory:")
db, err := sqlite3.Open(":memory:")
if err != nil {
b.Fatal(err)
}
+1 -1
View File
@@ -4,7 +4,7 @@ import (
"math"
"strconv"
"github.com/hanzoai/sqlite3/internal/util"
"github.com/ncruces/go-sqlite3/internal/util"
)
// Welford's algorithm with Kahan summation:
+4 -4
View File
@@ -39,8 +39,8 @@ import (
"golang.org/x/text/transform"
"golang.org/x/text/unicode/norm"
"github.com/hanzoai/sqlite3"
"github.com/hanzoai/sqlite3/internal/errutil"
"github.com/ncruces/go-sqlite3"
"github.com/ncruces/go-sqlite3/internal/util"
)
// 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(errutil.ErrorString("unicode: invalid form"))
ctx.ResultError(util.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(errutil.ErrorString("ESCAPE expression must be a single character"))
ctx.ResultError(util.ErrorString("ESCAPE expression must be a single character"))
return
}
}
+7 -6
View File
@@ -5,14 +5,15 @@ import (
"slices"
"testing"
"github.com/hanzoai/sqlite3"
"github.com/hanzoai/sqlite3/internal/testcfg"
"github.com/ncruces/go-sqlite3"
_ "github.com/ncruces/go-sqlite3/embed"
_ "github.com/ncruces/go-sqlite3/internal/testcfg"
)
func TestRegister(t *testing.T) {
t.Parallel()
db, err := sqlite3.OpenContext(testcfg.Context(t), ":memory:")
db, err := sqlite3.Open(":memory:")
if err != nil {
t.Fatal(err)
}
@@ -81,7 +82,7 @@ func TestRegister(t *testing.T) {
func TestRegister_collation(t *testing.T) {
t.Parallel()
db, err := sqlite3.OpenContext(testcfg.Context(t), ":memory:")
db, err := sqlite3.Open(":memory:")
if err != nil {
t.Fatal(err)
}
@@ -137,7 +138,7 @@ func TestRegister_collation(t *testing.T) {
func TestRegisterCollationsNeeded(t *testing.T) {
t.Parallel()
db, err := sqlite3.OpenContext(testcfg.Context(t), ":memory:")
db, err := sqlite3.Open(":memory:")
if err != nil {
t.Fatal(err)
}
@@ -188,7 +189,7 @@ func TestRegisterCollationsNeeded(t *testing.T) {
func TestRegister_error(t *testing.T) {
t.Parallel()
db, err := sqlite3.OpenContext(testcfg.Context(t), ":memory:")
db, err := sqlite3.Open(":memory:")
if err != nil {
t.Fatal(err)
}
+4 -4
View File
@@ -11,8 +11,8 @@ import (
"github.com/google/uuid"
"github.com/hanzoai/sqlite3"
"github.com/hanzoai/sqlite3/internal/errutil"
"github.com/ncruces/go-sqlite3"
"github.com/ncruces/go-sqlite3/internal/util"
)
// 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 = errutil.ErrorString("missing id")
err = util.ErrorString("missing id")
}
case 3, 5:
if len(arg) < 2 {
err = errutil.ErrorString("missing data")
err = util.ErrorString("missing data")
break
}
ns, err := fromValue(arg[1])
+19 -20
View File
@@ -6,16 +6,16 @@ import (
"github.com/google/uuid"
"github.com/hanzoai/sqlite3/driver"
"github.com/hanzoai/sqlite3/internal/testcfg"
"github.com/hanzoai/sqlite3/vfs/memdb"
"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"
)
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.QueryRowContext(ctx, `SELECT uuid()`).Scan(&u)
err = db.QueryRow(`SELECT uuid()`).Scan(&u)
if err != nil {
t.Fatal(err)
}
@@ -34,14 +34,14 @@ func Test_generate(t *testing.T) {
}
// Invalid version
err = db.QueryRowContext(ctx, `SELECT uuid(8)`).Scan(&u)
err = db.QueryRow(`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.QueryRowContext(ctx, `SELECT uuid(?)`, want).Scan(&u)
err = db.QueryRow(`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.QueryRowContext(ctx, `SELECT uuid(2, 4)`).Scan(&u)
err = db.QueryRow(`SELECT uuid(2, 4)`).Scan(&u)
if err == nil {
t.Error("want error")
}
err = db.QueryRowContext(ctx, `SELECT uuid(2, 'group')`).Scan(&u)
err = db.QueryRow(`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.QueryRowContext(ctx, `SELECT uuid(2, ?, ?)`, tt.in, tt.id).Scan(&u)
err = db.QueryRow(`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.QueryRowContext(ctx, `SELECT uuid(3)`).Scan(&u)
err = db.QueryRow(`SELECT uuid(3)`).Scan(&u)
if err == nil {
t.Error("want error")
}
err = db.QueryRowContext(ctx, `SELECT uuid(3, 0, '')`).Scan(&u)
err = db.QueryRow(`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.QueryRowContext(ctx, `SELECT uuid(?, ?, ?)`, tt.ver, tt.ns, tt.data).Scan(&u)
err = db.QueryRow(`SELECT uuid(?, ?, ?)`, tt.ver, tt.ns, tt.data).Scan(&u)
if err != nil {
t.Fatal(err)
}
@@ -155,7 +155,6 @@ 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)
@@ -171,7 +170,7 @@ func Test_convert(t *testing.T) {
}
for _, tt := range tests {
err = db.QueryRowContext(ctx, `SELECT uuid_str(`+tt+`)`).Scan(&u)
err = db.QueryRow(`SELECT uuid_str(` + tt + `)`).Scan(&u)
if err != nil {
t.Fatal(err)
}
@@ -181,7 +180,7 @@ func Test_convert(t *testing.T) {
}
for _, tt := range tests {
err = db.QueryRowContext(ctx, `SELECT uuid_blob(`+tt+`)`).Scan(&u)
err = db.QueryRow(`SELECT uuid_blob(` + tt + `)`).Scan(&u)
if err != nil {
t.Fatal(err)
}
@@ -190,22 +189,22 @@ func Test_convert(t *testing.T) {
}
}
err = db.QueryRowContext(ctx, `SELECT uuid_str(X'cafe')`).Scan(&u)
err = db.QueryRow(`SELECT uuid_str(X'cafe')`).Scan(&u)
if err == nil {
t.Fatal("want error")
}
err = db.QueryRowContext(ctx, `SELECT uuid_blob(X'cafe')`).Scan(&u)
err = db.QueryRow(`SELECT uuid_blob(X'cafe')`).Scan(&u)
if err == nil {
t.Fatal("want error")
}
err = db.QueryRowContext(ctx, `SELECT uuid_extract_version(X'cafe')`).Scan(&u)
err = db.QueryRow(`SELECT uuid_extract_version(X'cafe')`).Scan(&u)
if err == nil {
t.Fatal("want error")
}
err = db.QueryRowContext(ctx, `SELECT uuid_extract_timestamp(X'cafe')`).Scan(&u)
err = db.QueryRow(`SELECT uuid_extract_timestamp(X'cafe')`).Scan(&u)
if err == nil {
t.Fatal("want error")
}
-14
View File
@@ -1,14 +0,0 @@
// Package vec1 provides the vec1 vector extension.
//
// https://sqlite.org/vec1
package vec1
import (
"github.com/hanzoai/sqlite3"
"github.com/ncruces/go-sqlite3-wasm/v3/vec1"
)
// Register registers the vec1 vector extension.
func Register(db *sqlite3.Conn) error {
return sqlite3.ExtensionInit(db, vec1.New, vec1.DylinkInfo)
}
-51
View File
@@ -1,51 +0,0 @@
package vec1_test
import (
"testing"
"github.com/hanzoai/sqlite3/driver"
"github.com/hanzoai/sqlite3/ext/vec1"
"github.com/hanzoai/sqlite3/internal/testcfg"
"github.com/hanzoai/sqlite3/vfs/memdb"
)
func Test(t *testing.T) {
dsn := memdb.TestDB(t)
ctx := testcfg.Context(t)
db, err := driver.Open(dsn, vec1.Register)
if err != nil {
t.Fatal(err)
}
defer db.Close()
var n float64
err = db.QueryRowContext(ctx, `SELECT vec1_config('nthread')`).Scan(&n)
if err != nil {
t.Fatal(err)
}
if n != 1 {
t.Errorf("got %v, want 1", n)
}
err = db.QueryRowContext(ctx, `SELECT vec1_config('nprobe')`).Scan(&n)
if err != nil {
t.Fatal(err)
}
if n != 0.05 {
t.Errorf("got %v, want 0.05", n)
}
_, err = db.ExecContext(ctx, `
CREATE VIRTUAL TABLE v1 USING vec1;
INSERT INTO v1(cmd, vector) VALUES('rebuild', '{index:"flat"}');
INSERT INTO v1(rowid, vector) VALUES(
1, vec1_from_json('[1,2,3,4,5,6,7,8]')
);
PRAGMA integrity_check;
DROP TABLE v1;
`)
if err != nil {
t.Fatal(err)
}
}

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