Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dd529b4e1c | ||
|
|
5333f203e0 | ||
|
|
5db4440521 | ||
|
|
ca962ab55b | ||
|
|
fab434685b | ||
|
|
aeb79a1e8f | ||
|
|
764539773a | ||
|
|
2900c3e2a1 | ||
|
|
d581398704 | ||
|
|
f063720e7f | ||
|
|
cc974a6e22 | ||
|
|
2f00389562 | ||
|
|
25266ab966 | ||
|
|
dc662cff13 | ||
|
|
174151ad8f | ||
|
|
312b416f2a | ||
|
|
b7dd234638 | ||
|
|
c5396f115d | ||
|
|
89efb65dbb | ||
|
|
a2be335b4f | ||
|
|
ce78ca9e71 | ||
|
|
79143838cd | ||
|
|
3bd7753abc | ||
|
|
57d9e999b5 | ||
|
|
7c278c53d1 | ||
|
|
61d8d97579 | ||
|
|
892bb6a5b1 | ||
|
|
74832300cf | ||
|
|
edf4e07234 | ||
|
|
f99d5fa541 | ||
|
|
3f5df10c4b | ||
|
|
9dba3c7490 | ||
|
|
4ba802708b | ||
|
|
10ce179087 | ||
|
|
064bd0131f | ||
|
|
0985a110f6 | ||
|
|
85405c4f38 | ||
|
|
a2af69a0fe | ||
|
|
692e090377 | ||
|
|
02af344355 | ||
|
|
61d2c859c5 | ||
|
|
7daf908f50 | ||
|
|
13edc95f12 | ||
|
|
b6e44cc408 | ||
|
|
2c6806ebbd | ||
|
|
361fdc52fa | ||
|
|
1008ddc5cb | ||
|
|
1956c5a861 | ||
|
|
1165211c3a | ||
|
|
137e2694c0 | ||
|
|
d29aacb0b2 | ||
|
|
30649fa50a | ||
|
|
ed9a77fa96 | ||
|
|
4c0daafbf7 | ||
|
|
1ad454cfae | ||
|
|
ed7dab3edd | ||
|
|
a68b5f4364 | ||
|
|
299c57d1b8 | ||
|
|
501f863d20 | ||
|
|
33bea5277f | ||
|
|
c263381f1f | ||
|
|
c4f254bc37 | ||
|
|
c97c5a1f86 | ||
|
|
3e1b1313c1 | ||
|
|
83744cb9c8 | ||
|
|
a36d72c2dc | ||
|
|
e7f5604199 | ||
|
|
e50083912c | ||
|
|
d4764fb2fa | ||
|
|
42df71f3ff | ||
|
|
91e969c06b | ||
|
|
8ab0ddf53e | ||
|
|
74d22ded0a | ||
|
|
d962611796 | ||
|
|
7df3814c34 | ||
|
|
c5f49b835a | ||
|
|
0e55451a0b | ||
|
|
ea9a58ab19 | ||
|
|
0b46e74ea6 | ||
|
|
8dca850bee | ||
|
|
5b78823416 | ||
|
|
1764a571da | ||
|
|
9837310af7 | ||
|
|
ec8961a621 | ||
|
|
8c37aa2d97 | ||
|
|
ca93c498e7 | ||
|
|
15e9087fa8 | ||
|
|
7028e3a5b9 | ||
|
|
03bb20de6e | ||
|
|
20a51a344e | ||
|
|
2dbcc480f7 | ||
|
|
0f0716c438 | ||
|
|
0286e50e25 | ||
|
|
8ac10eb8b4 | ||
|
|
0ff41bb966 | ||
|
|
ba9caf0405 | ||
|
|
2c167dd116 | ||
|
|
ce0da893b4 | ||
|
|
9bbbab77f6 | ||
|
|
bab2d26652 | ||
|
|
3132b272de | ||
|
|
8f9a6ca4c1 | ||
|
|
99b097de3b | ||
|
|
4a956e80a2 | ||
|
|
5f4ff03f6f | ||
|
|
5890049488 | ||
|
|
5e73c5d714 | ||
|
|
6d92aa16ef | ||
|
|
191d1337e7 | ||
|
|
b65e894849 | ||
|
|
0b040d3f09 | ||
|
|
1db4366226 | ||
|
|
9e1cbfb5bb | ||
|
|
7f2d70a0f3 | ||
|
|
ea860e407d | ||
|
|
d4561d08f9 | ||
|
|
14c1e490b4 | ||
|
|
23aad5f62f | ||
|
|
e5bd10a1ff | ||
|
|
5cf06c45f7 | ||
|
|
08f9fc758a | ||
|
|
b588d5f991 | ||
|
|
4c24bd0cb6 | ||
|
|
cc353e4848 | ||
|
|
c3ebb04045 | ||
|
|
11e064574c | ||
|
|
770420289a | ||
|
|
62f69011f1 | ||
|
|
4f9e3f900b | ||
|
|
4e90618350 | ||
|
|
54bb94ce58 | ||
|
|
07fec784e1 | ||
|
|
da4638cbff | ||
|
|
085872c2f3 | ||
|
|
de49aa2b06 | ||
|
|
1f3ad0165e | ||
|
|
0bda48d1d9 | ||
|
|
0026bc91aa | ||
|
|
d84ca9d627 | ||
|
|
5d14e01f94 | ||
|
|
342df983d4 | ||
|
|
00476fb1e2 | ||
|
|
8a64ee6eaa | ||
|
|
8f9a8e2752 | ||
|
|
d8880e4cee | ||
|
|
4b154a842c | ||
|
|
758a53e9bf | ||
|
|
1a42b4c590 | ||
|
|
7e4ec1df1c | ||
|
|
2c582a1d66 | ||
|
|
20a67ca669 | ||
|
|
789e2dc136 | ||
|
|
0399f10c06 | ||
|
|
75c6744b5b | ||
|
|
754e806164 | ||
|
|
2640c9fb54 | ||
|
|
9719d4b0e3 | ||
|
|
b21c69dc1f | ||
|
|
b0f8ff44a5 | ||
|
|
f37bca6a80 | ||
|
|
b4e8fcb752 | ||
|
|
14b98a5d05 | ||
|
|
36a62264f9 | ||
|
|
33ea564f38 | ||
|
|
5c55d8692f | ||
|
|
be2f3036b4 | ||
|
|
784f82f42f | ||
|
|
cd6ba43e77 | ||
|
|
d7aef63844 | ||
|
|
64e5046f10 | ||
|
|
0bdce8aa68 |
@@ -1,15 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
echo 'set -eu' > 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
|
||||
@@ -1,23 +0,0 @@
|
||||
name: Benchmark libc
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
test:
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-24.04, ubuntu-24.04-arm, macos-13, macos-15]
|
||||
runs-on: ${{ matrix.os }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-go@v5
|
||||
with: { go-version: stable }
|
||||
|
||||
- name: Benchmark
|
||||
shell: bash
|
||||
run: sqlite3/libc/benchmark.sh
|
||||
@@ -1,15 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# Download and build SQLite
|
||||
sqlite3/download.sh
|
||||
sqlite3/tools.sh
|
||||
embed/build.sh
|
||||
embed/bcw2/build.sh
|
||||
|
||||
# Download and build sqlite-createtable-parser
|
||||
util/sql3util/wasm/download.sh
|
||||
util/sql3util/wasm/build.sh
|
||||
|
||||
# Check diffs
|
||||
git diff --exit-code
|
||||
@@ -1,32 +0,0 @@
|
||||
name: Reproducible build
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
id-token: write
|
||||
attestations: write
|
||||
|
||||
jobs:
|
||||
build:
|
||||
strategy:
|
||||
matrix:
|
||||
os: [macos-latest, ubuntu-latest, windows-latest]
|
||||
runs-on: ${{ matrix.os }}
|
||||
|
||||
steps:
|
||||
- uses: ilammy/msvc-dev-cmd@v1
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Build
|
||||
shell: bash
|
||||
run: .github/workflows/repro.sh
|
||||
|
||||
- uses: actions/attest-build-provenance@v2
|
||||
if: matrix.os == 'ubuntu-latest'
|
||||
with:
|
||||
subject-path: |
|
||||
embed/sqlite3.wasm
|
||||
embed/bcw2/bcw2.wasm
|
||||
util/sql3util/wasm/sql3parse_table.wasm
|
||||
@@ -1,236 +0,0 @@
|
||||
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@v4
|
||||
- uses: actions/setup-go@v5
|
||||
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 -v ./...
|
||||
|
||||
- name: Test
|
||||
run: go test -v ./... -bench . -benchtime=1x
|
||||
|
||||
- name: Test BSD locks
|
||||
run: go test -v -tags sqlite3_flock ./...
|
||||
if: matrix.os != 'windows-latest'
|
||||
|
||||
- name: Test dot locks
|
||||
run: go test -v -tags sqlite3_dotlk ./...
|
||||
if: matrix.os != 'windows-latest'
|
||||
|
||||
- name: Test modules
|
||||
shell: bash
|
||||
run: |
|
||||
go work init .
|
||||
go work use -r embed gormlite
|
||||
go test -v ./embed/bcw2/...
|
||||
|
||||
- name: Test GORM
|
||||
shell: bash
|
||||
run: gormlite/test.sh
|
||||
if: matrix.os != 'windows-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-bsd:
|
||||
strategy:
|
||||
matrix:
|
||||
os:
|
||||
- name: freebsd
|
||||
version: '14.2'
|
||||
flags: '-test.v'
|
||||
- name: netbsd
|
||||
version: '10.1'
|
||||
flags: '-test.v'
|
||||
- name: freebsd
|
||||
arch: arm64
|
||||
version: '14.2'
|
||||
flags: '-test.v -test.short'
|
||||
- name: netbsd
|
||||
arch: arm64
|
||||
version: '10.1'
|
||||
flags: '-test.v -test.short'
|
||||
- name: openbsd
|
||||
version: '7.7'
|
||||
flags: '-test.v -test.short'
|
||||
runs-on: ubuntu-latest
|
||||
needs: test
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Build
|
||||
env:
|
||||
GOOS: ${{ matrix.os.name }}
|
||||
GOARCH: ${{ matrix.os.arch }}
|
||||
TESTFLAGS: ${{ matrix.os.flags }}
|
||||
run: .github/workflows/build-test.sh
|
||||
|
||||
- name: Test
|
||||
uses: cross-platform-actions/action@v0.28.0
|
||||
with:
|
||||
operating_system: ${{ 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'
|
||||
tflags: '-test.v'
|
||||
- name: illumos
|
||||
action: 'vmactions/omnios-vm@v1'
|
||||
tflags: '-test.v'
|
||||
- name: solaris
|
||||
action: 'vmactions/solaris-vm@v1'
|
||||
bflags: '-tags sqlite3_dotlk'
|
||||
tflags: '-test.v'
|
||||
runs-on: ubuntu-latest
|
||||
needs: test
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- 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@v4
|
||||
- uses: actions/setup-go@v5
|
||||
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 -v -short -tags sqlite3_dotlk -skip Example ./...
|
||||
|
||||
test-qemu:
|
||||
runs-on: ubuntu-latest
|
||||
needs: test
|
||||
|
||||
steps:
|
||||
- uses: docker/setup-qemu-action@v3
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-go@v5
|
||||
with: { go-version: stable }
|
||||
|
||||
- name: Test 386 (32-bit)
|
||||
run: GOARCH=386 go test -v -short ./...
|
||||
|
||||
- name: Test riscv64 (interpreter)
|
||||
run: GOARCH=riscv64 go test -v -short ./...
|
||||
|
||||
- name: Test ppc64le (interpreter)
|
||||
run: GOARCH=ppc64le go test -v -short ./...
|
||||
|
||||
- name: Test s390x (big-endian)
|
||||
run: GOARCH=s390x go test -v -short -tags sqlite3_dotlk ./...
|
||||
|
||||
test-linuxarm:
|
||||
runs-on: ubuntu-24.04-arm
|
||||
needs: test
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-go@v5
|
||||
with: { go-version: stable }
|
||||
|
||||
- name: Test
|
||||
run: go test -v ./...
|
||||
|
||||
test-macintel:
|
||||
runs-on: macos-13
|
||||
needs: test
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-go@v5
|
||||
with: { go-version: stable }
|
||||
|
||||
- name: Test
|
||||
run: go test -v ./...
|
||||
@@ -0,0 +1,20 @@
|
||||
# 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''',
|
||||
]
|
||||
@@ -0,0 +1,35 @@
|
||||
# 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
|
||||
@@ -0,0 +1,7 @@
|
||||
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
@@ -0,0 +1,15 @@
|
||||
# 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`.
|
||||
@@ -1,4 +1,4 @@
|
||||
# Go bindings to SQLite using wazero
|
||||
# Go bindings to SQLite using wasm2go
|
||||
|
||||
[](https://pkg.go.dev/github.com/ncruces/go-sqlite3)
|
||||
[](https://goreportcard.com/report/github.com/ncruces/go-sqlite3)
|
||||
@@ -8,9 +8,9 @@ Go module `github.com/ncruces/go-sqlite3` is a `cgo`-free [SQLite](https://sqlit
|
||||
It provides a [`database/sql`](https://pkg.go.dev/database/sql) compatible driver,
|
||||
as well as direct access to most of the [C SQLite API](https://sqlite.org/cintro.html).
|
||||
|
||||
It wraps a [Wasm](https://webassembly.org/) [build](embed/) of SQLite,
|
||||
and uses [wazero](https://wazero.io/) as the runtime.\
|
||||
Go, wazero and [`x/sys`](https://pkg.go.dev/golang.org/x/sys) are the _only_ direct dependencies.
|
||||
It wraps a [Wasm](https://webassembly.org/) build of SQLite,
|
||||
and uses [wasm2go](https://github.com/ncruces/wasm2go) to translate it to Go.\
|
||||
Go and [`x/sys`](https://pkg.go.dev/golang.org/x/sys) are the _only_ direct dependencies.
|
||||
|
||||
### Getting started
|
||||
|
||||
@@ -19,7 +19,6 @@ Using the [`database/sql`](https://pkg.go.dev/database/sql) driver:
|
||||
|
||||
import "database/sql"
|
||||
import _ "github.com/ncruces/go-sqlite3/driver"
|
||||
import _ "github.com/ncruces/go-sqlite3/embed"
|
||||
|
||||
var version string
|
||||
db, _ := sql.Open("sqlite3", "file:demo.db")
|
||||
@@ -34,8 +33,6 @@ db.QueryRow(`SELECT sqlite_version()`).Scan(&version)
|
||||
- [`github.com/ncruces/go-sqlite3/driver`](https://pkg.go.dev/github.com/ncruces/go-sqlite3/driver)
|
||||
provides a [`database/sql`](https://pkg.go.dev/database/sql) driver
|
||||
([example](https://pkg.go.dev/github.com/ncruces/go-sqlite3/driver#example-package)).
|
||||
- [`github.com/ncruces/go-sqlite3/embed`](https://pkg.go.dev/github.com/ncruces/go-sqlite3/embed)
|
||||
embeds a build of SQLite into your application.
|
||||
- [`github.com/ncruces/go-sqlite3/vfs`](https://pkg.go.dev/github.com/ncruces/go-sqlite3/vfs)
|
||||
wraps the [C SQLite VFS API](https://sqlite.org/vfs.html) and provides a pure Go implementation.
|
||||
- [`github.com/ncruces/go-sqlite3/gormlite`](https://pkg.go.dev/github.com/ncruces/go-sqlite3/gormlite)
|
||||
@@ -58,13 +55,10 @@ 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
|
||||
|
||||
@@ -79,23 +73,22 @@ memory usage will be higher than alternatives.
|
||||
### Testing
|
||||
|
||||
This project aims for [high test coverage](https://github.com/ncruces/go-sqlite3/wiki/Test-coverage-report).
|
||||
It also benefits greatly from [SQLite's](https://sqlite.org/testing.html) and
|
||||
[wazero's](https://tetrate.io/blog/introducing-wazero-from-tetrate/#:~:text=Rock%2Dsolid%20test%20approach)
|
||||
thorough testing.
|
||||
It also benefits greatly from [SQLite's](https://sqlite.org/testing.html) thorough testing.
|
||||
|
||||
Every commit is tested on:
|
||||
* Linux: amd64, arm64, 386, riscv64, ppc64le, s390x
|
||||
* Linux: amd64, arm64, 386, arm, riscv64, ppc64le, loong64, s390x
|
||||
* macOS: amd64, arm64
|
||||
* Windows: amd64
|
||||
* Windows: amd64, arm64
|
||||
* BSD:
|
||||
* FreeBSD: amd64, arm64
|
||||
* OpenBSD: amd64
|
||||
* NetBSD: amd64, arm64
|
||||
* DragonFly BSD: amd64
|
||||
* OpenBSD: amd64
|
||||
* illumos: amd64
|
||||
* Solaris: amd64
|
||||
|
||||
Certain operating system and CPU combinations have some limitations. See the [support matrix](https://github.com/ncruces/go-sqlite3/wiki/Support-matrix) for a complete overview.
|
||||
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).
|
||||
@@ -128,9 +121,23 @@ 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
|
||||
---
|
||||
|
||||
- [`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)
|
||||
## 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.
|
||||
|
||||
@@ -62,22 +62,22 @@ func (src *Conn) BackupInit(srcDB, dstURI string) (*Backup, error) {
|
||||
}
|
||||
|
||||
func (c *Conn) backupInit(dst ptr_t, dstName string, src ptr_t, srcName string) (*Backup, error) {
|
||||
defer c.arena.mark()()
|
||||
dstPtr := c.arena.string(dstName)
|
||||
srcPtr := c.arena.string(srcName)
|
||||
defer c.arena.Mark()()
|
||||
dstPtr := c.arena.String(dstName)
|
||||
srcPtr := c.arena.String(srcName)
|
||||
|
||||
other := dst
|
||||
if c.handle == dst {
|
||||
other = src
|
||||
}
|
||||
|
||||
ptr := ptr_t(c.call("sqlite3_backup_init",
|
||||
stk_t(dst), stk_t(dstPtr),
|
||||
stk_t(src), stk_t(srcPtr)))
|
||||
ptr := ptr_t(c.wrp.Xsqlite3_backup_init(
|
||||
int32(dst), int32(dstPtr),
|
||||
int32(src), int32(srcPtr)))
|
||||
if ptr == 0 {
|
||||
defer c.closeDB(other)
|
||||
rc := res_t(c.call("sqlite3_errcode", stk_t(dst)))
|
||||
return nil, c.sqlite.error(rc, dst)
|
||||
rc := res_t(c.wrp.Xsqlite3_errcode(int32(dst)))
|
||||
return nil, c.errorFor(dst, rc)
|
||||
}
|
||||
|
||||
return &Backup{
|
||||
@@ -97,7 +97,7 @@ func (b *Backup) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
rc := res_t(b.c.call("sqlite3_backup_finish", stk_t(b.handle)))
|
||||
rc := res_t(b.c.wrp.Xsqlite3_backup_finish(int32(b.handle)))
|
||||
b.c.closeDB(b.otherc)
|
||||
b.handle = 0
|
||||
return b.c.error(rc)
|
||||
@@ -108,7 +108,7 @@ func (b *Backup) Close() error {
|
||||
//
|
||||
// https://sqlite.org/c3ref/backup_finish.html#sqlite3backupstep
|
||||
func (b *Backup) Step(nPage int) (done bool, err error) {
|
||||
rc := res_t(b.c.call("sqlite3_backup_step", stk_t(b.handle), stk_t(nPage)))
|
||||
rc := res_t(b.c.wrp.Xsqlite3_backup_step(int32(b.handle), int32(nPage)))
|
||||
if rc == _DONE {
|
||||
return true, nil
|
||||
}
|
||||
@@ -120,8 +120,7 @@ func (b *Backup) Step(nPage int) (done bool, err error) {
|
||||
//
|
||||
// https://sqlite.org/c3ref/backup_finish.html#sqlite3backupremaining
|
||||
func (b *Backup) Remaining() int {
|
||||
n := int32(b.c.call("sqlite3_backup_remaining", stk_t(b.handle)))
|
||||
return int(n)
|
||||
return int(b.c.wrp.Xsqlite3_backup_remaining(int32(b.handle)))
|
||||
}
|
||||
|
||||
// PageCount returns the total number of pages in the source database
|
||||
@@ -129,6 +128,5 @@ func (b *Backup) Remaining() int {
|
||||
//
|
||||
// https://sqlite.org/c3ref/backup_finish.html#sqlite3backuppagecount
|
||||
func (b *Backup) PageCount() int {
|
||||
n := int32(b.c.call("sqlite3_backup_pagecount", stk_t(b.handle)))
|
||||
return int(n)
|
||||
return int(b.c.wrp.Xsqlite3_backup_pagecount(int32(b.handle)))
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ package sqlite3
|
||||
import (
|
||||
"io"
|
||||
|
||||
"github.com/ncruces/go-sqlite3/internal/util"
|
||||
"github.com/hanzoai/sqlite3/internal/errutil"
|
||||
)
|
||||
|
||||
// ZeroBlob represents a zero-filled, length n BLOB
|
||||
@@ -35,28 +35,28 @@ func (c *Conn) OpenBlob(db, table, column string, row int64, write bool) (*Blob,
|
||||
return nil, INTERRUPT
|
||||
}
|
||||
|
||||
defer c.arena.mark()()
|
||||
blobPtr := c.arena.new(ptrlen)
|
||||
dbPtr := c.arena.string(db)
|
||||
tablePtr := c.arena.string(table)
|
||||
columnPtr := c.arena.string(column)
|
||||
defer c.arena.Mark()()
|
||||
blobPtr := c.arena.New(ptrlen)
|
||||
dbPtr := c.arena.String(db)
|
||||
tablePtr := c.arena.String(table)
|
||||
columnPtr := c.arena.String(column)
|
||||
|
||||
var flags int32
|
||||
if write {
|
||||
flags = 1
|
||||
}
|
||||
|
||||
rc := res_t(c.call("sqlite3_blob_open", stk_t(c.handle),
|
||||
stk_t(dbPtr), stk_t(tablePtr), stk_t(columnPtr),
|
||||
stk_t(row), stk_t(flags), stk_t(blobPtr)))
|
||||
rc := res_t(c.wrp.Xsqlite3_blob_open(int32(c.handle),
|
||||
int32(dbPtr), int32(tablePtr), int32(columnPtr),
|
||||
row, flags, int32(blobPtr)))
|
||||
|
||||
if err := c.error(rc); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
blob := Blob{c: c}
|
||||
blob.handle = util.Read32[ptr_t](c.mod, blobPtr)
|
||||
blob.bytes = int64(int32(c.call("sqlite3_blob_bytes", stk_t(blob.handle))))
|
||||
blob.handle = ptr_t(c.wrp.Read32(blobPtr))
|
||||
blob.bytes = int64(c.wrp.Xsqlite3_blob_bytes(int32(blob.handle)))
|
||||
return &blob, nil
|
||||
}
|
||||
|
||||
@@ -70,8 +70,8 @@ func (b *Blob) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
rc := res_t(b.c.call("sqlite3_blob_close", stk_t(b.handle)))
|
||||
b.c.free(b.bufptr)
|
||||
rc := res_t(b.c.wrp.Xsqlite3_blob_close(int32(b.handle)))
|
||||
b.c.wrp.Free(b.bufptr)
|
||||
b.handle = 0
|
||||
return b.c.error(rc)
|
||||
}
|
||||
@@ -97,12 +97,12 @@ func (b *Blob) Read(p []byte) (n int, err error) {
|
||||
want = avail
|
||||
}
|
||||
if want > b.buflen {
|
||||
b.bufptr = b.c.realloc(b.bufptr, want)
|
||||
b.bufptr = b.c.wrp.Realloc(b.bufptr, want)
|
||||
b.buflen = want
|
||||
}
|
||||
|
||||
rc := res_t(b.c.call("sqlite3_blob_read", stk_t(b.handle),
|
||||
stk_t(b.bufptr), stk_t(want), stk_t(b.offset)))
|
||||
rc := res_t(b.c.wrp.Xsqlite3_blob_read(int32(b.handle),
|
||||
int32(b.bufptr), int32(want), int32(b.offset)))
|
||||
err = b.c.error(rc)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
@@ -112,7 +112,7 @@ func (b *Blob) Read(p []byte) (n int, err error) {
|
||||
err = io.EOF
|
||||
}
|
||||
|
||||
copy(p, util.View(b.c.mod, b.bufptr, want))
|
||||
copy(p, b.c.wrp.Bytes(b.bufptr, want))
|
||||
return int(want), err
|
||||
}
|
||||
|
||||
@@ -130,19 +130,19 @@ func (b *Blob) WriteTo(w io.Writer) (n int64, err error) {
|
||||
want = avail
|
||||
}
|
||||
if want > b.buflen {
|
||||
b.bufptr = b.c.realloc(b.bufptr, want)
|
||||
b.bufptr = b.c.wrp.Realloc(b.bufptr, want)
|
||||
b.buflen = want
|
||||
}
|
||||
|
||||
for want > 0 {
|
||||
rc := res_t(b.c.call("sqlite3_blob_read", stk_t(b.handle),
|
||||
stk_t(b.bufptr), stk_t(want), stk_t(b.offset)))
|
||||
rc := res_t(b.c.wrp.Xsqlite3_blob_read(int32(b.handle),
|
||||
int32(b.bufptr), int32(want), int32(b.offset)))
|
||||
err = b.c.error(rc)
|
||||
if err != nil {
|
||||
return n, err
|
||||
}
|
||||
|
||||
mem := util.View(b.c.mod, b.bufptr, want)
|
||||
mem := b.c.wrp.Bytes(b.bufptr, want)
|
||||
m, err := w.Write(mem[:want])
|
||||
b.offset += int64(m)
|
||||
n += int64(m)
|
||||
@@ -168,13 +168,13 @@ func (b *Blob) WriteTo(w io.Writer) (n int64, err error) {
|
||||
func (b *Blob) Write(p []byte) (n int, err error) {
|
||||
want := int64(len(p))
|
||||
if want > b.buflen {
|
||||
b.bufptr = b.c.realloc(b.bufptr, want)
|
||||
b.bufptr = b.c.wrp.Realloc(b.bufptr, want)
|
||||
b.buflen = want
|
||||
}
|
||||
util.WriteBytes(b.c.mod, b.bufptr, p)
|
||||
b.c.wrp.WriteBytes(b.bufptr, p)
|
||||
|
||||
rc := res_t(b.c.call("sqlite3_blob_write", stk_t(b.handle),
|
||||
stk_t(b.bufptr), stk_t(want), stk_t(b.offset)))
|
||||
rc := res_t(b.c.wrp.Xsqlite3_blob_write(int32(b.handle),
|
||||
int32(b.bufptr), int32(want), int32(b.offset)))
|
||||
err = b.c.error(rc)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
@@ -199,16 +199,16 @@ func (b *Blob) ReadFrom(r io.Reader) (n int64, err error) {
|
||||
want = 1
|
||||
}
|
||||
if want > b.buflen {
|
||||
b.bufptr = b.c.realloc(b.bufptr, want)
|
||||
b.bufptr = b.c.wrp.Realloc(b.bufptr, want)
|
||||
b.buflen = want
|
||||
}
|
||||
|
||||
for {
|
||||
mem := util.View(b.c.mod, b.bufptr, want)
|
||||
mem := b.c.wrp.Bytes(b.bufptr, want)
|
||||
m, err := r.Read(mem[:want])
|
||||
if m > 0 {
|
||||
rc := res_t(b.c.call("sqlite3_blob_write", stk_t(b.handle),
|
||||
stk_t(b.bufptr), stk_t(m), stk_t(b.offset)))
|
||||
rc := res_t(b.c.wrp.Xsqlite3_blob_write(int32(b.handle),
|
||||
int32(b.bufptr), int32(m), int32(b.offset)))
|
||||
err := b.c.error(rc)
|
||||
if err != nil {
|
||||
return n, err
|
||||
@@ -237,7 +237,7 @@ func (b *Blob) ReadFrom(r io.Reader) (n int64, err error) {
|
||||
func (b *Blob) Seek(offset int64, whence int) (int64, error) {
|
||||
switch whence {
|
||||
default:
|
||||
return 0, util.WhenceErr
|
||||
return 0, errutil.WhenceErr
|
||||
case io.SeekStart:
|
||||
break
|
||||
case io.SeekCurrent:
|
||||
@@ -246,7 +246,7 @@ func (b *Blob) Seek(offset int64, whence int) (int64, error) {
|
||||
offset += b.bytes
|
||||
}
|
||||
if offset < 0 {
|
||||
return 0, util.OffsetErr
|
||||
return 0, errutil.OffsetErr
|
||||
}
|
||||
b.offset = offset
|
||||
return offset, nil
|
||||
@@ -259,8 +259,8 @@ func (b *Blob) Reopen(row int64) error {
|
||||
if b.c.interrupt.Err() != nil {
|
||||
return INTERRUPT
|
||||
}
|
||||
err := b.c.error(res_t(b.c.call("sqlite3_blob_reopen", stk_t(b.handle), stk_t(row))))
|
||||
b.bytes = int64(int32(b.c.call("sqlite3_blob_bytes", stk_t(b.handle))))
|
||||
err := b.c.error(res_t(b.c.wrp.Xsqlite3_blob_reopen(int32(b.handle), row)))
|
||||
b.bytes = int64(b.c.wrp.Xsqlite3_blob_bytes(int32(b.handle)))
|
||||
b.offset = 0
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
// 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")
|
||||
}
|
||||
@@ -1,15 +1,12 @@
|
||||
package sqlite3
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/tetratelabs/wazero/api"
|
||||
|
||||
"github.com/ncruces/go-sqlite3/internal/util"
|
||||
"github.com/ncruces/go-sqlite3/vfs"
|
||||
"github.com/hanzoai/sqlite3/internal/errutil"
|
||||
"github.com/hanzoai/sqlite3/vfs"
|
||||
)
|
||||
|
||||
// Config makes configuration changes to a database connection.
|
||||
@@ -30,8 +27,8 @@ func (c *Conn) Config(op DBConfig, arg ...bool) (bool, error) {
|
||||
// The vararg is a pointer to an array containing these arguments:
|
||||
// an int and an int* pointing to that int.
|
||||
|
||||
defer c.arena.mark()()
|
||||
argsPtr := c.arena.new(intlen + ptrlen)
|
||||
defer c.arena.Mark()()
|
||||
argsPtr := c.arena.New(intlen + ptrlen)
|
||||
|
||||
var flag int32
|
||||
switch {
|
||||
@@ -41,12 +38,12 @@ func (c *Conn) Config(op DBConfig, arg ...bool) (bool, error) {
|
||||
flag = 1
|
||||
}
|
||||
|
||||
util.Write32(c.mod, argsPtr+0*ptrlen, flag)
|
||||
util.Write32(c.mod, argsPtr+1*ptrlen, argsPtr)
|
||||
c.wrp.Write32(argsPtr+0*ptrlen, uint32(flag))
|
||||
c.wrp.Write32(argsPtr+1*ptrlen, uint32(argsPtr))
|
||||
|
||||
rc := res_t(c.call("sqlite3_db_config", stk_t(c.handle),
|
||||
stk_t(op), stk_t(argsPtr)))
|
||||
return util.ReadBool(c.mod, argsPtr), c.error(rc)
|
||||
rc := res_t(c.wrp.Xsqlite3_db_config(int32(c.handle),
|
||||
int32(op), int32(argsPtr)))
|
||||
return c.wrp.ReadBool(argsPtr), c.error(rc)
|
||||
}
|
||||
|
||||
var defaultLogger atomic.Pointer[func(code ExtendedErrorCode, msg string)]
|
||||
@@ -66,7 +63,7 @@ func (c *Conn) ConfigLog(cb func(code ExtendedErrorCode, msg string)) error {
|
||||
if cb != nil {
|
||||
enable = 1
|
||||
}
|
||||
rc := res_t(c.call("sqlite3_config_log_go", stk_t(enable)))
|
||||
rc := res_t(c.wrp.Xsqlite3_config_log_go(enable))
|
||||
if err := c.error(rc); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -74,9 +71,9 @@ func (c *Conn) ConfigLog(cb func(code ExtendedErrorCode, msg string)) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func logCallback(ctx context.Context, mod api.Module, _ ptr_t, iCode res_t, zMsg ptr_t) {
|
||||
if c, ok := ctx.Value(connKey{}).(*Conn); ok && c.log != nil {
|
||||
msg := util.ReadString(mod, zMsg, _MAX_LENGTH)
|
||||
func (e *env) Xgo_log(_, iCode, zMsg int32) {
|
||||
if c, ok := e.DB.(*Conn); ok && c.log != nil {
|
||||
msg := e.ReadString(ptr_t(zMsg), _MAX_LENGTH)
|
||||
c.log(xErrorCode(iCode), msg)
|
||||
}
|
||||
}
|
||||
@@ -95,12 +92,12 @@ func (c *Conn) Log(code ExtendedErrorCode, format string, a ...any) {
|
||||
//
|
||||
// https://sqlite.org/c3ref/file_control.html
|
||||
func (c *Conn) FileControl(schema string, op FcntlOpcode, arg ...any) (any, error) {
|
||||
defer c.arena.mark()()
|
||||
ptr := c.arena.new(max(ptrlen, intlen))
|
||||
defer c.arena.Mark()()
|
||||
ptr := c.arena.New(max(ptrlen, intlen))
|
||||
|
||||
var schemaPtr ptr_t
|
||||
if schema != "" {
|
||||
schemaPtr = c.arena.string(schema)
|
||||
schemaPtr = c.arena.String(schema)
|
||||
}
|
||||
|
||||
var rc res_t
|
||||
@@ -110,9 +107,9 @@ func (c *Conn) FileControl(schema string, op FcntlOpcode, arg ...any) (any, erro
|
||||
return nil, MISUSE
|
||||
|
||||
case FCNTL_RESET_CACHE, FCNTL_NULL_IO:
|
||||
rc = res_t(c.call("sqlite3_file_control",
|
||||
stk_t(c.handle), stk_t(schemaPtr),
|
||||
stk_t(op), 0))
|
||||
rc = res_t(c.wrp.Xsqlite3_file_control(
|
||||
int32(c.handle), int32(schemaPtr),
|
||||
int32(op), 0))
|
||||
|
||||
case FCNTL_PERSIST_WAL, FCNTL_POWERSAFE_OVERWRITE:
|
||||
var flag int32
|
||||
@@ -122,62 +119,66 @@ func (c *Conn) FileControl(schema string, op FcntlOpcode, arg ...any) (any, erro
|
||||
case arg[0]:
|
||||
flag = 1
|
||||
}
|
||||
util.Write32(c.mod, ptr, flag)
|
||||
rc = res_t(c.call("sqlite3_file_control",
|
||||
stk_t(c.handle), stk_t(schemaPtr),
|
||||
stk_t(op), stk_t(ptr)))
|
||||
ret = util.ReadBool(c.mod, ptr)
|
||||
c.wrp.Write32(ptr, uint32(flag))
|
||||
rc = res_t(c.wrp.Xsqlite3_file_control(
|
||||
int32(c.handle), int32(schemaPtr),
|
||||
int32(op), int32(ptr)))
|
||||
ret = c.wrp.ReadBool(ptr)
|
||||
|
||||
case FCNTL_CHUNK_SIZE:
|
||||
util.Write32(c.mod, ptr, int32(arg[0].(int)))
|
||||
rc = res_t(c.call("sqlite3_file_control",
|
||||
stk_t(c.handle), stk_t(schemaPtr),
|
||||
stk_t(op), stk_t(ptr)))
|
||||
c.wrp.Write32(ptr, uint32(arg[0].(int)))
|
||||
rc = res_t(c.wrp.Xsqlite3_file_control(
|
||||
int32(c.handle), int32(schemaPtr),
|
||||
int32(op), int32(ptr)))
|
||||
|
||||
case FCNTL_RESERVE_BYTES:
|
||||
bytes := -1
|
||||
if len(arg) > 0 {
|
||||
bytes = arg[0].(int)
|
||||
}
|
||||
util.Write32(c.mod, ptr, int32(bytes))
|
||||
rc = res_t(c.call("sqlite3_file_control",
|
||||
stk_t(c.handle), stk_t(schemaPtr),
|
||||
stk_t(op), stk_t(ptr)))
|
||||
ret = int(util.Read32[int32](c.mod, ptr))
|
||||
c.wrp.Write32(ptr, uint32(bytes))
|
||||
rc = res_t(c.wrp.Xsqlite3_file_control(
|
||||
int32(c.handle), int32(schemaPtr),
|
||||
int32(op), int32(ptr)))
|
||||
ret = int(int32(c.wrp.Read32(ptr)))
|
||||
|
||||
case FCNTL_DATA_VERSION:
|
||||
rc = res_t(c.call("sqlite3_file_control",
|
||||
stk_t(c.handle), stk_t(schemaPtr),
|
||||
stk_t(op), stk_t(ptr)))
|
||||
ret = util.Read32[uint32](c.mod, ptr)
|
||||
rc = res_t(c.wrp.Xsqlite3_file_control(
|
||||
int32(c.handle), int32(schemaPtr),
|
||||
int32(op), int32(ptr)))
|
||||
ret = c.wrp.Read32(ptr)
|
||||
|
||||
case FCNTL_LOCKSTATE:
|
||||
rc = res_t(c.call("sqlite3_file_control",
|
||||
stk_t(c.handle), stk_t(schemaPtr),
|
||||
stk_t(op), stk_t(ptr)))
|
||||
ret = util.Read32[vfs.LockLevel](c.mod, ptr)
|
||||
rc = res_t(c.wrp.Xsqlite3_file_control(
|
||||
int32(c.handle), int32(schemaPtr),
|
||||
int32(op), int32(ptr)))
|
||||
ret = vfs.LockLevel(c.wrp.Read32(ptr))
|
||||
|
||||
case FCNTL_VFS_POINTER:
|
||||
rc = res_t(c.call("sqlite3_file_control",
|
||||
stk_t(c.handle), stk_t(schemaPtr),
|
||||
stk_t(op), stk_t(ptr)))
|
||||
case FCNTL_VFSNAME, FCNTL_VFS_POINTER:
|
||||
rc = res_t(c.wrp.Xsqlite3_file_control(
|
||||
int32(c.handle), int32(schemaPtr),
|
||||
int32(FCNTL_VFS_POINTER), int32(ptr)))
|
||||
if rc == _OK {
|
||||
const zNameOffset = 16
|
||||
ptr = util.Read32[ptr_t](c.mod, ptr)
|
||||
ptr = util.Read32[ptr_t](c.mod, ptr+zNameOffset)
|
||||
name := util.ReadString(c.mod, ptr, _MAX_NAME)
|
||||
ret = vfs.Find(name)
|
||||
ptr = ptr_t(c.wrp.Read32(ptr))
|
||||
ptr = ptr_t(c.wrp.Read32(ptr + zNameOffset))
|
||||
name := c.wrp.ReadString(ptr, _MAX_NAME)
|
||||
if op == FCNTL_VFS_POINTER {
|
||||
ret = vfs.Find(name)
|
||||
} else {
|
||||
ret = name
|
||||
}
|
||||
}
|
||||
|
||||
case FCNTL_FILE_POINTER, FCNTL_JOURNAL_POINTER:
|
||||
rc = res_t(c.call("sqlite3_file_control",
|
||||
stk_t(c.handle), stk_t(schemaPtr),
|
||||
stk_t(op), stk_t(ptr)))
|
||||
rc = res_t(c.wrp.Xsqlite3_file_control(
|
||||
int32(c.handle), int32(schemaPtr),
|
||||
int32(op), int32(ptr)))
|
||||
if rc == _OK {
|
||||
const fileHandleOffset = 4
|
||||
ptr = util.Read32[ptr_t](c.mod, ptr)
|
||||
ptr = util.Read32[ptr_t](c.mod, ptr+fileHandleOffset)
|
||||
ret = util.GetHandle(c.ctx, ptr)
|
||||
ptr = ptr_t(c.wrp.Read32(ptr))
|
||||
ptr = ptr_t(c.wrp.Read32(ptr + fileHandleOffset))
|
||||
ret = c.wrp.GetHandle(ptr)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -192,8 +193,7 @@ func (c *Conn) FileControl(schema string, op FcntlOpcode, arg ...any) (any, erro
|
||||
//
|
||||
// https://sqlite.org/c3ref/limit.html
|
||||
func (c *Conn) Limit(id LimitCategory, value int) int {
|
||||
v := int32(c.call("sqlite3_limit", stk_t(c.handle), stk_t(id), stk_t(value)))
|
||||
return int(v)
|
||||
return int(c.wrp.Xsqlite3_limit(int32(c.handle), int32(id), int32(value)))
|
||||
}
|
||||
|
||||
// SetAuthorizer registers an authorizer callback with the database connection.
|
||||
@@ -204,40 +204,39 @@ func (c *Conn) SetAuthorizer(cb func(action AuthorizerActionCode, name3rd, name4
|
||||
if cb != nil {
|
||||
enable = 1
|
||||
}
|
||||
rc := res_t(c.call("sqlite3_set_authorizer_go", stk_t(c.handle), stk_t(enable)))
|
||||
rc := res_t(c.wrp.Xsqlite3_set_authorizer_go(int32(c.handle), enable))
|
||||
if err := c.error(rc); err != nil {
|
||||
return err
|
||||
}
|
||||
c.authorizer = cb
|
||||
return nil
|
||||
|
||||
}
|
||||
|
||||
func authorizerCallback(ctx context.Context, mod api.Module, pDB ptr_t, action AuthorizerActionCode, zName3rd, zName4th, zSchema, zInner ptr_t) (rc AuthorizerReturnCode) {
|
||||
if c, ok := ctx.Value(connKey{}).(*Conn); ok && c.handle == pDB && c.authorizer != nil {
|
||||
func (e *env) Xgo_authorizer(pDB, action, zName3rd, zName4th, zSchema, zInner int32) (rc int32) {
|
||||
if c, ok := e.DB.(*Conn); ok && c.handle == ptr_t(pDB) && c.authorizer != nil {
|
||||
var name3rd, name4th, schema, inner string
|
||||
if zName3rd != 0 {
|
||||
name3rd = util.ReadString(mod, zName3rd, _MAX_NAME)
|
||||
name3rd = e.ReadString(ptr_t(zName3rd), _MAX_NAME)
|
||||
}
|
||||
if zName4th != 0 {
|
||||
name4th = util.ReadString(mod, zName4th, _MAX_NAME)
|
||||
name4th = e.ReadString(ptr_t(zName4th), _MAX_NAME)
|
||||
}
|
||||
if zSchema != 0 {
|
||||
schema = util.ReadString(mod, zSchema, _MAX_NAME)
|
||||
schema = e.ReadString(ptr_t(zSchema), _MAX_NAME)
|
||||
}
|
||||
if zInner != 0 {
|
||||
inner = util.ReadString(mod, zInner, _MAX_NAME)
|
||||
inner = e.ReadString(ptr_t(zInner), _MAX_NAME)
|
||||
}
|
||||
rc = c.authorizer(action, name3rd, name4th, schema, inner)
|
||||
return int32(c.authorizer(AuthorizerActionCode(action), name3rd, name4th, schema, inner))
|
||||
}
|
||||
return rc
|
||||
return _OK
|
||||
}
|
||||
|
||||
// Trace registers a trace callback function against the database connection.
|
||||
//
|
||||
// https://sqlite.org/c3ref/trace_v2.html
|
||||
func (c *Conn) Trace(mask TraceEvent, cb func(evt TraceEvent, arg1 any, arg2 any) error) error {
|
||||
rc := res_t(c.call("sqlite3_trace_go", stk_t(c.handle), stk_t(mask)))
|
||||
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)))
|
||||
if err := c.error(rc); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -245,30 +244,30 @@ func (c *Conn) Trace(mask TraceEvent, cb func(evt TraceEvent, arg1 any, arg2 any
|
||||
return nil
|
||||
}
|
||||
|
||||
func traceCallback(ctx context.Context, mod api.Module, evt TraceEvent, pDB, pArg1, pArg2 ptr_t) (rc res_t) {
|
||||
if c, ok := ctx.Value(connKey{}).(*Conn); ok && c.handle == pDB && c.trace != nil {
|
||||
func (e *env) Xgo_trace(evt, pDB, pArg1, pArg2 int32) int32 {
|
||||
if c, ok := e.DB.(*Conn); ok && c.handle == ptr_t(pDB) && c.trace != nil {
|
||||
var arg1, arg2 any
|
||||
if evt == TRACE_CLOSE {
|
||||
if TraceEvent(evt) == TRACE_CLOSE {
|
||||
arg1 = c
|
||||
} else {
|
||||
for _, s := range c.stmts {
|
||||
if pArg1 == s.handle {
|
||||
if ptr_t(pArg1) == s.handle {
|
||||
arg1 = s
|
||||
switch evt {
|
||||
switch TraceEvent(evt) {
|
||||
case TRACE_STMT:
|
||||
arg2 = s.SQL()
|
||||
case TRACE_PROFILE:
|
||||
arg2 = util.Read64[int64](mod, pArg2)
|
||||
arg2 = int64(e.Read64(ptr_t(pArg2)))
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if arg1 != nil {
|
||||
_, rc = errorCode(c.trace(evt, arg1, arg2), ERROR)
|
||||
_ = c.trace(TraceEvent(evt), arg1, arg2)
|
||||
}
|
||||
}
|
||||
return rc
|
||||
return _OK
|
||||
}
|
||||
|
||||
// WALCheckpoint checkpoints a WAL database.
|
||||
@@ -279,15 +278,15 @@ func (c *Conn) WALCheckpoint(schema string, mode CheckpointMode) (nLog, nCkpt in
|
||||
return 0, 0, INTERRUPT
|
||||
}
|
||||
|
||||
defer c.arena.mark()()
|
||||
nLogPtr := c.arena.new(ptrlen)
|
||||
nCkptPtr := c.arena.new(ptrlen)
|
||||
schemaPtr := c.arena.string(schema)
|
||||
rc := res_t(c.call("sqlite3_wal_checkpoint_v2",
|
||||
stk_t(c.handle), stk_t(schemaPtr), stk_t(mode),
|
||||
stk_t(nLogPtr), stk_t(nCkptPtr)))
|
||||
nLog = int(util.Read32[int32](c.mod, nLogPtr))
|
||||
nCkpt = int(util.Read32[int32](c.mod, nCkptPtr))
|
||||
defer c.arena.Mark()()
|
||||
nLogPtr := c.arena.New(ptrlen)
|
||||
nCkptPtr := c.arena.New(ptrlen)
|
||||
schemaPtr := c.arena.String(schema)
|
||||
rc := res_t(c.wrp.Xsqlite3_wal_checkpoint_v2(
|
||||
int32(c.handle), int32(schemaPtr), int32(mode),
|
||||
int32(nLogPtr), int32(nCkptPtr)))
|
||||
nLog = int(int32(c.wrp.Read32(nLogPtr)))
|
||||
nCkpt = int(int32(c.wrp.Read32(nCkptPtr)))
|
||||
return nLog, nCkpt, c.error(rc)
|
||||
}
|
||||
|
||||
@@ -295,7 +294,7 @@ func (c *Conn) WALCheckpoint(schema string, mode CheckpointMode) (nLog, nCkpt in
|
||||
//
|
||||
// https://sqlite.org/c3ref/wal_autocheckpoint.html
|
||||
func (c *Conn) WALAutoCheckpoint(pages int) error {
|
||||
rc := res_t(c.call("sqlite3_wal_autocheckpoint", stk_t(c.handle), stk_t(pages)))
|
||||
rc := res_t(c.wrp.Xsqlite3_wal_autocheckpoint(int32(c.handle), int32(pages)))
|
||||
return c.error(rc)
|
||||
}
|
||||
|
||||
@@ -308,17 +307,18 @@ func (c *Conn) WALHook(cb func(db *Conn, schema string, pages int) error) {
|
||||
if cb != nil {
|
||||
enable = 1
|
||||
}
|
||||
c.call("sqlite3_wal_hook_go", stk_t(c.handle), stk_t(enable))
|
||||
c.wrp.Xsqlite3_wal_hook_go(int32(c.handle), enable)
|
||||
c.wal = cb
|
||||
}
|
||||
|
||||
func walCallback(ctx context.Context, mod api.Module, _, pDB, zSchema ptr_t, pages int32) (rc res_t) {
|
||||
if c, ok := ctx.Value(connKey{}).(*Conn); ok && c.handle == pDB && c.wal != nil {
|
||||
schema := util.ReadString(mod, zSchema, _MAX_NAME)
|
||||
func (e *env) Xgo_wal_hook(_, pDB, zSchema, pages int32) int32 {
|
||||
if c, ok := e.DB.(*Conn); ok && c.handle == ptr_t(pDB) && c.wal != nil {
|
||||
schema := e.ReadString(ptr_t(zSchema), _MAX_NAME)
|
||||
err := c.wal(c, schema, int(pages))
|
||||
_, rc = errorCode(err, ERROR)
|
||||
_, rc := errorCode(err, ERROR)
|
||||
return int32(rc)
|
||||
}
|
||||
return rc
|
||||
return _OK
|
||||
}
|
||||
|
||||
// AutoVacuumPages registers a autovacuum compaction amount callback.
|
||||
@@ -327,30 +327,30 @@ func walCallback(ctx context.Context, mod api.Module, _, pDB, zSchema ptr_t, pag
|
||||
func (c *Conn) AutoVacuumPages(cb func(schema string, dbPages, freePages, bytesPerPage uint) uint) error {
|
||||
var funcPtr ptr_t
|
||||
if cb != nil {
|
||||
funcPtr = util.AddHandle(c.ctx, cb)
|
||||
funcPtr = c.wrp.AddHandle(cb)
|
||||
}
|
||||
rc := res_t(c.call("sqlite3_autovacuum_pages_go", stk_t(c.handle), stk_t(funcPtr)))
|
||||
rc := res_t(c.wrp.Xsqlite3_autovacuum_pages_go(int32(c.handle), int32(funcPtr)))
|
||||
return c.error(rc)
|
||||
}
|
||||
|
||||
func autoVacuumCallback(ctx context.Context, mod api.Module, pApp, zSchema ptr_t, nDbPage, nFreePage, nBytePerPage uint32) uint32 {
|
||||
fn := util.GetHandle(ctx, pApp).(func(schema string, dbPages, freePages, bytesPerPage uint) uint)
|
||||
schema := util.ReadString(mod, zSchema, _MAX_NAME)
|
||||
return uint32(fn(schema, uint(nDbPage), uint(nFreePage), uint(nBytePerPage)))
|
||||
func (e *env) Xgo_autovacuum_pages(pApp, zSchema, nDbPage, nFreePage, nBytePerPage int32) int32 {
|
||||
fn := e.GetHandle(ptr_t(pApp)).(func(schema string, dbPages, freePages, bytesPerPage uint) uint)
|
||||
schema := e.ReadString(ptr_t(zSchema), _MAX_NAME)
|
||||
return int32(fn(schema, uint(uint32(nDbPage)), uint(uint32(nFreePage)), uint(uint32(nBytePerPage))))
|
||||
}
|
||||
|
||||
// SoftHeapLimit imposes a soft limit on heap size.
|
||||
//
|
||||
// https://sqlite.org/c3ref/hard_heap_limit64.html
|
||||
func (c *Conn) SoftHeapLimit(n int64) int64 {
|
||||
return int64(c.call("sqlite3_soft_heap_limit64", stk_t(n)))
|
||||
return c.wrp.Xsqlite3_soft_heap_limit64(n)
|
||||
}
|
||||
|
||||
// HardHeapLimit imposes a hard limit on heap size.
|
||||
//
|
||||
// https://sqlite.org/c3ref/hard_heap_limit64.html
|
||||
func (c *Conn) HardHeapLimit(n int64) int64 {
|
||||
return int64(c.call("sqlite3_hard_heap_limit64", stk_t(n)))
|
||||
return c.wrp.Xsqlite3_hard_heap_limit64(n)
|
||||
}
|
||||
|
||||
// EnableChecksums enables checksums on a database.
|
||||
@@ -378,7 +378,7 @@ func (c *Conn) EnableChecksums(schema string) error {
|
||||
}
|
||||
if r != 8 {
|
||||
// Invalid value.
|
||||
return util.ErrorString("sqlite3: reserve bytes must be 8, is: " + strconv.Itoa(r.(int)))
|
||||
return errutil.ErrorString("sqlite3: reserve bytes must be 8, is: " + strconv.Itoa(r.(int)))
|
||||
}
|
||||
|
||||
// VACUUM the database.
|
||||
|
||||
@@ -11,10 +11,9 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/tetratelabs/wazero/api"
|
||||
|
||||
"github.com/ncruces/go-sqlite3/internal/util"
|
||||
"github.com/ncruces/go-sqlite3/vfs"
|
||||
"github.com/hanzoai/sqlite3/internal/errutil"
|
||||
"github.com/hanzoai/sqlite3/internal/sqlite3_wrap"
|
||||
"github.com/hanzoai/sqlite3/vfs"
|
||||
)
|
||||
|
||||
// Conn is a database connection handle.
|
||||
@@ -22,7 +21,7 @@ import (
|
||||
//
|
||||
// https://sqlite.org/c3ref/sqlite3.html
|
||||
type Conn struct {
|
||||
*sqlite
|
||||
wrp *sqlite3_wrap.Wrapper
|
||||
|
||||
interrupt context.Context
|
||||
stmts []*Stmt
|
||||
@@ -35,10 +34,11 @@ type Conn struct {
|
||||
update func(AuthorizerActionCode, string, string, int64)
|
||||
commit func() bool
|
||||
rollback func()
|
||||
preupdate func(PreUpdateData)
|
||||
|
||||
busy1st time.Time
|
||||
busylst time.Time
|
||||
arena arena
|
||||
arena sqlite3_wrap.Arena
|
||||
handle ptr_t
|
||||
gosched uint8
|
||||
}
|
||||
@@ -69,8 +69,6 @@ func OpenFlags(filename string, flags OpenFlag) (*Conn, error) {
|
||||
return newConn(context.Background(), filename, flags)
|
||||
}
|
||||
|
||||
type connKey = util.ConnKey
|
||||
|
||||
func newConn(ctx context.Context, filename string, flags OpenFlag) (ret *Conn, _ error) {
|
||||
err := ctx.Err()
|
||||
if err != nil {
|
||||
@@ -78,24 +76,23 @@ func newConn(ctx context.Context, filename string, flags OpenFlag) (ret *Conn, _
|
||||
}
|
||||
|
||||
c := &Conn{interrupt: ctx}
|
||||
c.sqlite, err = instantiateSQLite()
|
||||
c.wrp, err = createWrapper(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer func() {
|
||||
if ret == nil {
|
||||
c.Close()
|
||||
c.sqlite.close()
|
||||
} else {
|
||||
c.interrupt = context.Background()
|
||||
}
|
||||
}()
|
||||
|
||||
c.ctx = context.WithValue(c.ctx, connKey{}, c)
|
||||
c.wrp.DB = c
|
||||
if logger := defaultLogger.Load(); logger != nil {
|
||||
c.ConfigLog(*logger)
|
||||
}
|
||||
c.arena = c.newArena()
|
||||
c.arena = c.wrp.NewArena()
|
||||
c.handle, err = c.openDB(filename, flags)
|
||||
if err == nil {
|
||||
err = initExtensions(c)
|
||||
@@ -107,24 +104,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.call("sqlite3_open_v2", stk_t(namePtr), stk_t(connPtr), stk_t(flags), 0))
|
||||
rc := res_t(c.wrp.Xsqlite3_open_v2(int32(namePtr), int32(connPtr), int32(flags), 0))
|
||||
|
||||
handle := util.Read32[ptr_t](c.mod, connPtr)
|
||||
if err := c.sqlite.error(rc, handle); err != nil {
|
||||
handle := ptr_t(c.wrp.Read32(connPtr))
|
||||
if err := c.errorFor(handle, rc); err != nil {
|
||||
c.closeDB(handle)
|
||||
return 0, err
|
||||
}
|
||||
|
||||
c.call("sqlite3_progress_handler_go", stk_t(handle), 1000)
|
||||
c.wrp.Xsqlite3_progress_handler_go(int32(handle), 1000)
|
||||
if flags|OPEN_URI != 0 && strings.HasPrefix(filename, "file:") {
|
||||
var pragmas strings.Builder
|
||||
if _, after, ok := strings.Cut(filename, "?"); ok {
|
||||
query, _ := url.ParseQuery(after)
|
||||
if u, err := url.Parse(filename); err == nil {
|
||||
query := u.Query()
|
||||
for _, p := range query["_pragma"] {
|
||||
pragmas.WriteString(`PRAGMA `)
|
||||
pragmas.WriteString(p)
|
||||
@@ -132,9 +129,9 @@ func (c *Conn) openDB(filename string, flags OpenFlag) (ptr_t, error) {
|
||||
}
|
||||
}
|
||||
if pragmas.Len() != 0 {
|
||||
pragmaPtr := c.arena.string(pragmas.String())
|
||||
rc := res_t(c.call("sqlite3_exec", stk_t(handle), stk_t(pragmaPtr), 0, 0, 0))
|
||||
if err := c.sqlite.error(rc, handle, pragmas.String()); err != nil {
|
||||
pragmaPtr := c.arena.String(pragmas.String())
|
||||
rc := res_t(c.wrp.Xsqlite3_exec(int32(handle), int32(pragmaPtr), 0, 0, 0))
|
||||
if err := c.errorFor(handle, rc, pragmas.String()); err != nil {
|
||||
err = fmt.Errorf("sqlite3: invalid _pragma: %w", err)
|
||||
c.closeDB(handle)
|
||||
return 0, err
|
||||
@@ -145,8 +142,8 @@ func (c *Conn) openDB(filename string, flags OpenFlag) (ptr_t, error) {
|
||||
}
|
||||
|
||||
func (c *Conn) closeDB(handle ptr_t) {
|
||||
rc := res_t(c.call("sqlite3_close_v2", stk_t(handle)))
|
||||
if err := c.sqlite.error(rc, handle); err != nil {
|
||||
rc := res_t(c.wrp.Xsqlite3_close_v2(int32(handle)))
|
||||
if err := c.errorFor(handle, rc); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
@@ -165,13 +162,13 @@ func (c *Conn) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
rc := res_t(c.call("sqlite3_close", stk_t(c.handle)))
|
||||
rc := res_t(c.wrp.Xsqlite3_close(int32(c.handle)))
|
||||
if err := c.error(rc); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c.handle = 0
|
||||
return c.close()
|
||||
return c.wrp.Close()
|
||||
}
|
||||
|
||||
// Exec is a convenience function that allows an application to run
|
||||
@@ -186,9 +183,9 @@ func (c *Conn) Exec(sql string) error {
|
||||
}
|
||||
|
||||
func (c *Conn) exec(sql string) error {
|
||||
defer c.arena.mark()()
|
||||
textPtr := c.arena.string(sql)
|
||||
rc := res_t(c.call("sqlite3_exec", stk_t(c.handle), stk_t(textPtr), 0, 0, 0))
|
||||
defer c.arena.Mark()()
|
||||
textPtr := c.arena.String(sql)
|
||||
rc := res_t(c.wrp.Xsqlite3_exec(int32(c.handle), int32(textPtr), 0, 0, 0))
|
||||
return c.error(rc, sql)
|
||||
}
|
||||
|
||||
@@ -211,18 +208,18 @@ func (c *Conn) PrepareFlags(sql string, flags PrepareFlag) (stmt *Stmt, tail str
|
||||
return nil, "", INTERRUPT
|
||||
}
|
||||
|
||||
defer c.arena.mark()()
|
||||
stmtPtr := c.arena.new(ptrlen)
|
||||
tailPtr := c.arena.new(ptrlen)
|
||||
textPtr := c.arena.string(sql)
|
||||
defer c.arena.Mark()()
|
||||
stmtPtr := c.arena.New(ptrlen)
|
||||
tailPtr := c.arena.New(ptrlen)
|
||||
textPtr := c.arena.String(sql)
|
||||
|
||||
rc := res_t(c.call("sqlite3_prepare_v3", stk_t(c.handle),
|
||||
stk_t(textPtr), stk_t(len(sql)+1), stk_t(flags),
|
||||
stk_t(stmtPtr), stk_t(tailPtr)))
|
||||
rc := res_t(c.wrp.Xsqlite3_prepare_v3(int32(c.handle),
|
||||
int32(textPtr), int32(len(sql)+1), int32(flags),
|
||||
int32(stmtPtr), int32(tailPtr)))
|
||||
|
||||
stmt = &Stmt{c: c, sql: sql}
|
||||
stmt.handle = util.Read32[ptr_t](c.mod, stmtPtr)
|
||||
if sql := sql[util.Read32[ptr_t](c.mod, tailPtr)-textPtr:]; sql != "" {
|
||||
stmt.handle = ptr_t(c.wrp.Read32(stmtPtr))
|
||||
if sql := sql[ptr_t(c.wrp.Read32(tailPtr))-textPtr:]; sql != "" {
|
||||
tail = sql
|
||||
}
|
||||
|
||||
@@ -240,11 +237,11 @@ func (c *Conn) PrepareFlags(sql string, flags PrepareFlag) (stmt *Stmt, tail str
|
||||
//
|
||||
// https://sqlite.org/c3ref/db_name.html
|
||||
func (c *Conn) DBName(n int) string {
|
||||
ptr := ptr_t(c.call("sqlite3_db_name", stk_t(c.handle), stk_t(n)))
|
||||
ptr := ptr_t(c.wrp.Xsqlite3_db_name(int32(c.handle), int32(n)))
|
||||
if ptr == 0 {
|
||||
return ""
|
||||
}
|
||||
return util.ReadString(c.mod, ptr, _MAX_NAME)
|
||||
return c.wrp.ReadString(ptr, _MAX_NAME)
|
||||
}
|
||||
|
||||
// Filename returns the filename for a database.
|
||||
@@ -253,23 +250,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.call("sqlite3_db_filename", stk_t(c.handle), stk_t(ptr)))
|
||||
return vfs.GetFilename(c.ctx, c.mod, ptr, vfs.OPEN_MAIN_DB)
|
||||
ptr = ptr_t(c.wrp.Xsqlite3_db_filename(int32(c.handle), int32(ptr)))
|
||||
return vfs.GetFilename(c.wrp, ptr, vfs.OPEN_MAIN_DB)
|
||||
}
|
||||
|
||||
// ReadOnly determines if a database is read-only.
|
||||
//
|
||||
// https://sqlite.org/c3ref/db_readonly.html
|
||||
func (c *Conn) ReadOnly(schema string) (ro bool, ok bool) {
|
||||
func (c *Conn) ReadOnly(schema string) (ro, ok bool) {
|
||||
var ptr ptr_t
|
||||
if schema != "" {
|
||||
defer c.arena.mark()()
|
||||
ptr = c.arena.string(schema)
|
||||
defer c.arena.Mark()()
|
||||
ptr = c.arena.String(schema)
|
||||
}
|
||||
b := int32(c.call("sqlite3_db_readonly", stk_t(c.handle), stk_t(ptr)))
|
||||
b := c.wrp.Xsqlite3_db_readonly(int32(c.handle), int32(ptr))
|
||||
return b > 0, b < 0
|
||||
}
|
||||
|
||||
@@ -277,7 +274,7 @@ func (c *Conn) ReadOnly(schema string) (ro bool, ok bool) {
|
||||
//
|
||||
// https://sqlite.org/c3ref/get_autocommit.html
|
||||
func (c *Conn) GetAutocommit() bool {
|
||||
b := int32(c.call("sqlite3_get_autocommit", stk_t(c.handle)))
|
||||
b := c.wrp.Xsqlite3_get_autocommit(int32(c.handle))
|
||||
return b != 0
|
||||
}
|
||||
|
||||
@@ -286,7 +283,7 @@ func (c *Conn) GetAutocommit() bool {
|
||||
//
|
||||
// https://sqlite.org/c3ref/last_insert_rowid.html
|
||||
func (c *Conn) LastInsertRowID() int64 {
|
||||
return int64(c.call("sqlite3_last_insert_rowid", stk_t(c.handle)))
|
||||
return c.wrp.Xsqlite3_last_insert_rowid(int32(c.handle))
|
||||
}
|
||||
|
||||
// SetLastInsertRowID allows the application to set the value returned by
|
||||
@@ -294,7 +291,7 @@ func (c *Conn) LastInsertRowID() int64 {
|
||||
//
|
||||
// https://sqlite.org/c3ref/set_last_insert_rowid.html
|
||||
func (c *Conn) SetLastInsertRowID(id int64) {
|
||||
c.call("sqlite3_set_last_insert_rowid", stk_t(c.handle), stk_t(id))
|
||||
c.wrp.Xsqlite3_set_last_insert_rowid(int32(c.handle), id)
|
||||
}
|
||||
|
||||
// Changes returns the number of rows modified, inserted or deleted
|
||||
@@ -303,7 +300,7 @@ func (c *Conn) SetLastInsertRowID(id int64) {
|
||||
//
|
||||
// https://sqlite.org/c3ref/changes.html
|
||||
func (c *Conn) Changes() int64 {
|
||||
return int64(c.call("sqlite3_changes64", stk_t(c.handle)))
|
||||
return c.wrp.Xsqlite3_changes64(int32(c.handle))
|
||||
}
|
||||
|
||||
// TotalChanges returns the number of rows modified, inserted or deleted
|
||||
@@ -312,14 +309,14 @@ func (c *Conn) Changes() int64 {
|
||||
//
|
||||
// https://sqlite.org/c3ref/total_changes.html
|
||||
func (c *Conn) TotalChanges() int64 {
|
||||
return int64(c.call("sqlite3_total_changes64", stk_t(c.handle)))
|
||||
return c.wrp.Xsqlite3_total_changes64(int32(c.handle))
|
||||
}
|
||||
|
||||
// ReleaseMemory frees memory used by a database connection.
|
||||
//
|
||||
// https://sqlite.org/c3ref/db_release_memory.html
|
||||
func (c *Conn) ReleaseMemory() error {
|
||||
rc := res_t(c.call("sqlite3_db_release_memory", stk_t(c.handle)))
|
||||
rc := res_t(c.wrp.Xsqlite3_db_release_memory(int32(c.handle)))
|
||||
return c.error(rc)
|
||||
}
|
||||
|
||||
@@ -351,8 +348,8 @@ func (c *Conn) SetInterrupt(ctx context.Context) (old context.Context) {
|
||||
return old
|
||||
}
|
||||
|
||||
func progressCallback(ctx context.Context, mod api.Module, _ ptr_t) (interrupt int32) {
|
||||
if c, ok := ctx.Value(connKey{}).(*Conn); ok {
|
||||
func (e *env) Xgo_progress_handler(_ int32) (interrupt int32) {
|
||||
if c, ok := e.DB.(*Conn); ok {
|
||||
if c.gosched++; c.gosched%16 == 0 {
|
||||
runtime.Gosched()
|
||||
}
|
||||
@@ -368,13 +365,13 @@ func progressCallback(ctx context.Context, mod api.Module, _ ptr_t) (interrupt i
|
||||
// https://sqlite.org/c3ref/busy_timeout.html
|
||||
func (c *Conn) BusyTimeout(timeout time.Duration) error {
|
||||
ms := min((timeout+time.Millisecond-1)/time.Millisecond, math.MaxInt32)
|
||||
rc := res_t(c.call("sqlite3_busy_timeout", stk_t(c.handle), stk_t(ms)))
|
||||
rc := res_t(c.wrp.Xsqlite3_busy_timeout(int32(c.handle), int32(ms)))
|
||||
return c.error(rc)
|
||||
}
|
||||
|
||||
func timeoutCallback(ctx context.Context, mod api.Module, count, tmout int32) (retry int32) {
|
||||
func (e *env) Xgo_busy_timeout(count, tmout int32) (retry int32) {
|
||||
// https://fractaledmind.github.io/2024/04/15/sqlite-on-rails-the-how-and-why-of-optimal-performance/
|
||||
if c, ok := ctx.Value(connKey{}).(*Conn); ok && c.interrupt.Err() == nil {
|
||||
if c, ok := e.DB.(*Conn); ok && c.interrupt.Err() == nil {
|
||||
switch {
|
||||
case count == 0:
|
||||
c.busy1st = time.Now()
|
||||
@@ -399,7 +396,7 @@ func (c *Conn) BusyHandler(cb func(ctx context.Context, count int) (retry bool))
|
||||
if cb != nil {
|
||||
enable = 1
|
||||
}
|
||||
rc := res_t(c.call("sqlite3_busy_handler_go", stk_t(c.handle), stk_t(enable)))
|
||||
rc := res_t(c.wrp.Xsqlite3_busy_handler_go(int32(c.handle), enable))
|
||||
if err := c.error(rc); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -407,8 +404,8 @@ func (c *Conn) BusyHandler(cb func(ctx context.Context, count int) (retry bool))
|
||||
return nil
|
||||
}
|
||||
|
||||
func busyCallback(ctx context.Context, mod api.Module, pDB ptr_t, count int32) (retry int32) {
|
||||
if c, ok := ctx.Value(connKey{}).(*Conn); ok && c.handle == pDB && c.busy != nil {
|
||||
func (e *env) Xgo_busy_handler(pDB, count int32) (retry int32) {
|
||||
if c, ok := e.DB.(*Conn); ok && c.handle == ptr_t(pDB) && c.busy != nil {
|
||||
if interrupt := c.interrupt; interrupt.Err() == nil &&
|
||||
c.busy(interrupt, int(count)) {
|
||||
retry = 1
|
||||
@@ -420,21 +417,21 @@ func busyCallback(ctx context.Context, mod api.Module, pDB ptr_t, count int32) (
|
||||
// Status retrieves runtime status information about a database connection.
|
||||
//
|
||||
// https://sqlite.org/c3ref/db_status.html
|
||||
func (c *Conn) Status(op DBStatus, reset bool) (current, highwater int, err error) {
|
||||
defer c.arena.mark()()
|
||||
hiPtr := c.arena.new(intlen)
|
||||
curPtr := c.arena.new(intlen)
|
||||
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)
|
||||
|
||||
var i int32
|
||||
if reset {
|
||||
i = 1
|
||||
}
|
||||
|
||||
rc := res_t(c.call("sqlite3_db_status", stk_t(c.handle),
|
||||
stk_t(op), stk_t(curPtr), stk_t(hiPtr), stk_t(i)))
|
||||
rc := res_t(c.wrp.Xsqlite3_db_status64(int32(c.handle),
|
||||
int32(op), int32(curPtr), int32(hiPtr), i))
|
||||
if err = c.error(rc); err == nil {
|
||||
current = int(util.Read32[int32](c.mod, curPtr))
|
||||
highwater = int(util.Read32[int32](c.mod, hiPtr))
|
||||
current = int64(c.wrp.Read64(curPtr))
|
||||
highwater = int64(c.wrp.Read64(hiPtr))
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -443,42 +440,89 @@ func (c *Conn) Status(op DBStatus, reset bool) (current, highwater int, err erro
|
||||
//
|
||||
// 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()()
|
||||
|
||||
var schemaPtr, columnPtr ptr_t
|
||||
declTypePtr := c.arena.new(ptrlen)
|
||||
collSeqPtr := c.arena.new(ptrlen)
|
||||
notNullPtr := c.arena.new(ptrlen)
|
||||
autoIncPtr := c.arena.new(ptrlen)
|
||||
primaryKeyPtr := c.arena.new(ptrlen)
|
||||
if schema != "" {
|
||||
schemaPtr = c.arena.string(schema)
|
||||
}
|
||||
tablePtr := c.arena.string(table)
|
||||
defer c.arena.Mark()()
|
||||
var (
|
||||
declTypePtr ptr_t
|
||||
collSeqPtr ptr_t
|
||||
notNullPtr ptr_t
|
||||
primaryKeyPtr ptr_t
|
||||
autoIncPtr ptr_t
|
||||
columnPtr ptr_t
|
||||
schemaPtr ptr_t
|
||||
)
|
||||
if column != "" {
|
||||
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)
|
||||
}
|
||||
tablePtr := c.arena.String(table)
|
||||
|
||||
rc := res_t(c.call("sqlite3_table_column_metadata", stk_t(c.handle),
|
||||
stk_t(schemaPtr), stk_t(tablePtr), stk_t(columnPtr),
|
||||
stk_t(declTypePtr), stk_t(collSeqPtr),
|
||||
stk_t(notNullPtr), stk_t(primaryKeyPtr), stk_t(autoIncPtr)))
|
||||
rc := res_t(c.wrp.Xsqlite3_table_column_metadata(int32(c.handle),
|
||||
int32(schemaPtr), int32(tablePtr), int32(columnPtr),
|
||||
int32(declTypePtr), int32(collSeqPtr),
|
||||
int32(notNullPtr), int32(primaryKeyPtr), int32(autoIncPtr)))
|
||||
if err = c.error(rc); err == nil && column != "" {
|
||||
if ptr := util.Read32[ptr_t](c.mod, declTypePtr); ptr != 0 {
|
||||
declType = util.ReadString(c.mod, ptr, _MAX_NAME)
|
||||
if ptr := ptr_t(c.wrp.Read32(declTypePtr)); ptr != 0 {
|
||||
declType = c.wrp.ReadString(ptr, _MAX_NAME)
|
||||
}
|
||||
if ptr := util.Read32[ptr_t](c.mod, collSeqPtr); ptr != 0 {
|
||||
collSeq = util.ReadString(c.mod, ptr, _MAX_NAME)
|
||||
if ptr := ptr_t(c.wrp.Read32(collSeqPtr)); ptr != 0 {
|
||||
collSeq = c.wrp.ReadString(ptr, _MAX_NAME)
|
||||
}
|
||||
notNull = util.ReadBool(c.mod, notNullPtr)
|
||||
autoInc = util.ReadBool(c.mod, autoIncPtr)
|
||||
primaryKey = util.ReadBool(c.mod, primaryKeyPtr)
|
||||
notNull = c.wrp.ReadBool(notNullPtr)
|
||||
autoInc = c.wrp.ReadBool(autoIncPtr)
|
||||
primaryKey = c.wrp.ReadBool(primaryKeyPtr)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (c *Conn) error(rc res_t, sql ...string) error {
|
||||
return c.sqlite.error(rc, c.handle, sql...)
|
||||
return c.errorFor(c.handle, rc, sql...)
|
||||
}
|
||||
|
||||
func (c *Conn) errorFor(handle ptr_t, rc res_t, sql ...string) error {
|
||||
if rc == _OK {
|
||||
return nil
|
||||
}
|
||||
|
||||
if ErrorCode(rc) == NOMEM || xErrorCode(rc) == IOERR_NOMEM {
|
||||
panic(errutil.OOMErr)
|
||||
}
|
||||
|
||||
var msg, query string
|
||||
if handle != 0 {
|
||||
if ptr := ptr_t(c.wrp.Xsqlite3_errmsg(int32(handle))); ptr != 0 {
|
||||
msg = c.wrp.ReadString(ptr, _MAX_LENGTH)
|
||||
msg = strings.TrimPrefix(msg, "sqlite3: ")
|
||||
msg = strings.TrimPrefix(msg, sqlite3_wrap.ErrorCodeString(rc)[len("sqlite3: "):])
|
||||
msg = strings.TrimPrefix(msg, ": ")
|
||||
if msg == "" || msg == "not an error" {
|
||||
msg = ""
|
||||
}
|
||||
}
|
||||
|
||||
if len(sql) != 0 {
|
||||
if i := c.wrp.Xsqlite3_error_offset(int32(handle)); i != -1 {
|
||||
query = sql[0][i:]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var sys error
|
||||
switch ErrorCode(rc) {
|
||||
case CANTOPEN, IOERR:
|
||||
sys = c.wrp.SysError
|
||||
}
|
||||
|
||||
if sys != nil || msg != "" || query != "" {
|
||||
return &Error{code: rc, sys: sys, msg: msg, sql: query}
|
||||
}
|
||||
return xErrorCode(rc)
|
||||
}
|
||||
|
||||
// Stmts returns an iterator for the prepared statements
|
||||
|
||||
@@ -3,7 +3,7 @@ package sqlite3
|
||||
import (
|
||||
"strconv"
|
||||
|
||||
"github.com/ncruces/go-sqlite3/internal/util"
|
||||
"github.com/hanzoai/sqlite3/internal/sqlite3_wrap"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -15,14 +15,13 @@ const (
|
||||
_MAX_LENGTH = 1e9
|
||||
_MAX_SQL_LENGTH = 1e9
|
||||
|
||||
ptrlen = util.PtrLen
|
||||
intlen = util.IntLen
|
||||
ptrlen = sqlite3_wrap.PtrLen
|
||||
intlen = sqlite3_wrap.IntLen
|
||||
)
|
||||
|
||||
type (
|
||||
stk_t = util.Stk_t
|
||||
ptr_t = util.Ptr_t
|
||||
res_t = util.Res_t
|
||||
ptr_t = sqlite3_wrap.Ptr_t
|
||||
res_t = sqlite3_wrap.Res_t
|
||||
)
|
||||
|
||||
// ErrorCode is a result code that [Error.Code] might return.
|
||||
@@ -73,6 +72,9 @@ const (
|
||||
ERROR_MISSING_COLLSEQ ExtendedErrorCode = xErrorCode(ERROR) | (1 << 8)
|
||||
ERROR_RETRY ExtendedErrorCode = xErrorCode(ERROR) | (2 << 8)
|
||||
ERROR_SNAPSHOT ExtendedErrorCode = xErrorCode(ERROR) | (3 << 8)
|
||||
ERROR_RESERVESIZE ExtendedErrorCode = xErrorCode(ERROR) | (4 << 8)
|
||||
ERROR_KEY ExtendedErrorCode = xErrorCode(ERROR) | (5 << 8)
|
||||
ERROR_UNABLE ExtendedErrorCode = xErrorCode(ERROR) | (6 << 8)
|
||||
IOERR_READ ExtendedErrorCode = xErrorCode(IOERR) | (1 << 8)
|
||||
IOERR_SHORT_READ ExtendedErrorCode = xErrorCode(IOERR) | (2 << 8)
|
||||
IOERR_WRITE ExtendedErrorCode = xErrorCode(IOERR) | (3 << 8)
|
||||
@@ -107,6 +109,8 @@ const (
|
||||
IOERR_DATA ExtendedErrorCode = xErrorCode(IOERR) | (32 << 8)
|
||||
IOERR_CORRUPTFS ExtendedErrorCode = xErrorCode(IOERR) | (33 << 8)
|
||||
IOERR_IN_PAGE ExtendedErrorCode = xErrorCode(IOERR) | (34 << 8)
|
||||
IOERR_BADKEY ExtendedErrorCode = xErrorCode(IOERR) | (35 << 8)
|
||||
IOERR_CODEC ExtendedErrorCode = xErrorCode(IOERR) | (36 << 8)
|
||||
LOCKED_SHAREDCACHE ExtendedErrorCode = xErrorCode(LOCKED) | (1 << 8)
|
||||
LOCKED_VTAB ExtendedErrorCode = xErrorCode(LOCKED) | (2 << 8)
|
||||
BUSY_RECOVERY ExtendedErrorCode = xErrorCode(BUSY) | (1 << 8)
|
||||
@@ -168,7 +172,7 @@ const (
|
||||
|
||||
// PrepareFlag is a flag that can be passed to [Conn.PrepareFlags].
|
||||
//
|
||||
// https://sqlite.org/c3ref/c_prepare_normalize.html
|
||||
// https://sqlite.org/c3ref/c_prepare_dont_log.html
|
||||
type PrepareFlag uint32
|
||||
|
||||
const (
|
||||
@@ -176,6 +180,7 @@ const (
|
||||
PREPARE_NORMALIZE PrepareFlag = 0x02
|
||||
PREPARE_NO_VTAB PrepareFlag = 0x04
|
||||
PREPARE_DONT_LOG PrepareFlag = 0x10
|
||||
PREPARE_FROM_DDL PrepareFlag = 0x20
|
||||
)
|
||||
|
||||
// FunctionFlag is a flag that can be passed to
|
||||
@@ -185,12 +190,12 @@ const (
|
||||
type FunctionFlag uint32
|
||||
|
||||
const (
|
||||
DETERMINISTIC FunctionFlag = 0x000000800
|
||||
DIRECTONLY FunctionFlag = 0x000080000
|
||||
INNOCUOUS FunctionFlag = 0x000200000
|
||||
SELFORDER1 FunctionFlag = 0x002000000
|
||||
// SUBTYPE FunctionFlag = 0x000100000
|
||||
// RESULT_SUBTYPE FunctionFlag = 0x001000000
|
||||
DETERMINISTIC FunctionFlag = 0x000000800
|
||||
DIRECTONLY FunctionFlag = 0x000080000
|
||||
SUBTYPE FunctionFlag = 0x000100000
|
||||
INNOCUOUS FunctionFlag = 0x000200000
|
||||
RESULT_SUBTYPE FunctionFlag = 0x001000000
|
||||
SELFORDER1 FunctionFlag = 0x002000000
|
||||
)
|
||||
|
||||
// StmtStatus name counter values associated with the [Stmt.Status] method.
|
||||
@@ -229,7 +234,8 @@ const (
|
||||
DBSTATUS_DEFERRED_FKS DBStatus = 10
|
||||
DBSTATUS_CACHE_USED_SHARED DBStatus = 11
|
||||
DBSTATUS_CACHE_SPILL DBStatus = 12
|
||||
// DBSTATUS_MAX DBStatus = 12
|
||||
DBSTATUS_TEMPBUF_SPILL DBStatus = 13
|
||||
// DBSTATUS_MAX DBStatus = 13
|
||||
)
|
||||
|
||||
// DBConfig are the available database connection configuration options.
|
||||
@@ -261,7 +267,8 @@ const (
|
||||
DBCONFIG_ENABLE_ATTACH_CREATE DBConfig = 1020
|
||||
DBCONFIG_ENABLE_ATTACH_WRITE DBConfig = 1021
|
||||
DBCONFIG_ENABLE_COMMENTS DBConfig = 1022
|
||||
// DBCONFIG_MAX DBConfig = 1022
|
||||
DBCONFIG_FP_DIGITS DBConfig = 1023
|
||||
// DBCONFIG_MAX DBConfig = 1023
|
||||
)
|
||||
|
||||
// FcntlOpcode are the available opcodes for [Conn.FileControl].
|
||||
@@ -274,6 +281,7 @@ const (
|
||||
FCNTL_CHUNK_SIZE FcntlOpcode = 6
|
||||
FCNTL_FILE_POINTER FcntlOpcode = 7
|
||||
FCNTL_PERSIST_WAL FcntlOpcode = 10
|
||||
FCNTL_VFSNAME FcntlOpcode = 12
|
||||
FCNTL_POWERSAFE_OVERWRITE FcntlOpcode = 13
|
||||
FCNTL_VFS_POINTER FcntlOpcode = 27
|
||||
FCNTL_JOURNAL_POINTER FcntlOpcode = 28
|
||||
@@ -301,6 +309,7 @@ const (
|
||||
LIMIT_VARIABLE_NUMBER LimitCategory = 9
|
||||
LIMIT_TRIGGER_DEPTH LimitCategory = 10
|
||||
LIMIT_WORKER_THREADS LimitCategory = 11
|
||||
LIMIT_PARSER_DEPTH LimitCategory = 12
|
||||
)
|
||||
|
||||
// AuthorizerActionCode are the integer action codes
|
||||
@@ -362,13 +371,14 @@ const (
|
||||
// CheckpointMode are all the checkpoint mode values.
|
||||
//
|
||||
// https://sqlite.org/c3ref/c_checkpoint_full.html
|
||||
type CheckpointMode uint32
|
||||
type CheckpointMode int32
|
||||
|
||||
const (
|
||||
CHECKPOINT_PASSIVE CheckpointMode = 0 /* Do as much as possible w/o blocking */
|
||||
CHECKPOINT_FULL CheckpointMode = 1 /* Wait for writers, then checkpoint */
|
||||
CHECKPOINT_RESTART CheckpointMode = 2 /* Like FULL but wait for readers */
|
||||
CHECKPOINT_TRUNCATE CheckpointMode = 3 /* Like RESTART but also truncate WAL */
|
||||
CHECKPOINT_NOOP CheckpointMode = -1 /* Do no work at all */
|
||||
CHECKPOINT_PASSIVE CheckpointMode = 0 /* Do as much as possible w/o blocking */
|
||||
CHECKPOINT_FULL CheckpointMode = 1 /* Wait for writers, then checkpoint */
|
||||
CHECKPOINT_RESTART CheckpointMode = 2 /* Like FULL but wait for readers */
|
||||
CHECKPOINT_TRUNCATE CheckpointMode = 3 /* Like RESTART but also truncate WAL */
|
||||
)
|
||||
|
||||
// TxnState are the allowed return values from [Conn.TxnState].
|
||||
|
||||
+51
-66
@@ -1,12 +1,10 @@
|
||||
package sqlite3
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"math"
|
||||
"time"
|
||||
|
||||
"github.com/ncruces/go-sqlite3/internal/util"
|
||||
"github.com/hanzoai/sqlite3/internal/errutil"
|
||||
)
|
||||
|
||||
// Context is the context in which an SQL function executes.
|
||||
@@ -31,16 +29,16 @@ func (ctx Context) Conn() *Conn {
|
||||
//
|
||||
// https://sqlite.org/c3ref/get_auxdata.html
|
||||
func (ctx Context) SetAuxData(n int, data any) {
|
||||
ptr := util.AddHandle(ctx.c.ctx, data)
|
||||
ctx.c.call("sqlite3_set_auxdata_go", stk_t(ctx.handle), stk_t(n), stk_t(ptr))
|
||||
ptr := ctx.c.wrp.AddHandle(data)
|
||||
ctx.c.wrp.Xsqlite3_set_auxdata_go(int32(ctx.handle), int32(n), int32(ptr))
|
||||
}
|
||||
|
||||
// GetAuxData returns metadata for argument n of the function.
|
||||
//
|
||||
// https://sqlite.org/c3ref/get_auxdata.html
|
||||
func (ctx Context) GetAuxData(n int) any {
|
||||
ptr := ptr_t(ctx.c.call("sqlite3_get_auxdata", stk_t(ctx.handle), stk_t(n)))
|
||||
return util.GetHandle(ctx.c.ctx, ptr)
|
||||
ptr := ptr_t(ctx.c.wrp.Xsqlite3_get_auxdata(int32(ctx.handle), int32(n)))
|
||||
return ctx.c.wrp.GetHandle(ptr)
|
||||
}
|
||||
|
||||
// ResultBool sets the result of the function to a bool.
|
||||
@@ -67,38 +65,32 @@ func (ctx Context) ResultInt(value int) {
|
||||
//
|
||||
// https://sqlite.org/c3ref/result_blob.html
|
||||
func (ctx Context) ResultInt64(value int64) {
|
||||
ctx.c.call("sqlite3_result_int64",
|
||||
stk_t(ctx.handle), stk_t(value))
|
||||
ctx.c.wrp.Xsqlite3_result_int64(
|
||||
int32(ctx.handle), value)
|
||||
}
|
||||
|
||||
// ResultFloat sets the result of the function to a float64.
|
||||
//
|
||||
// https://sqlite.org/c3ref/result_blob.html
|
||||
func (ctx Context) ResultFloat(value float64) {
|
||||
ctx.c.call("sqlite3_result_double",
|
||||
stk_t(ctx.handle), stk_t(math.Float64bits(value)))
|
||||
ctx.c.wrp.Xsqlite3_result_double(
|
||||
int32(ctx.handle), value)
|
||||
}
|
||||
|
||||
// ResultText sets the result of the function to a string.
|
||||
//
|
||||
// https://sqlite.org/c3ref/result_blob.html
|
||||
func (ctx Context) ResultText(value string) {
|
||||
ptr := ctx.c.newString(value)
|
||||
ctx.c.call("sqlite3_result_text_go",
|
||||
stk_t(ctx.handle), stk_t(ptr), stk_t(len(value)))
|
||||
ptr := ctx.c.wrp.NewString(value)
|
||||
ctx.c.wrp.Xsqlite3_result_text_go(
|
||||
int32(ctx.handle), int32(ptr), int64(len(value)))
|
||||
}
|
||||
|
||||
// ResultRawText sets the text result of the function to a []byte.
|
||||
//
|
||||
// https://sqlite.org/c3ref/result_blob.html
|
||||
func (ctx Context) ResultRawText(value []byte) {
|
||||
if len(value) == 0 {
|
||||
ctx.ResultText("")
|
||||
return
|
||||
}
|
||||
ptr := ctx.c.newBytes(value)
|
||||
ctx.c.call("sqlite3_result_text_go",
|
||||
stk_t(ctx.handle), stk_t(ptr), stk_t(len(value)))
|
||||
ctx.ResultText(string(value)) // does not escape
|
||||
}
|
||||
|
||||
// ResultBlob sets the result of the function to a []byte.
|
||||
@@ -109,25 +101,25 @@ func (ctx Context) ResultBlob(value []byte) {
|
||||
ctx.ResultZeroBlob(0)
|
||||
return
|
||||
}
|
||||
ptr := ctx.c.newBytes(value)
|
||||
ctx.c.call("sqlite3_result_blob_go",
|
||||
stk_t(ctx.handle), stk_t(ptr), stk_t(len(value)))
|
||||
ptr := ctx.c.wrp.NewBytes(value)
|
||||
ctx.c.wrp.Xsqlite3_result_blob_go(
|
||||
int32(ctx.handle), int32(ptr), int64(len(value)))
|
||||
}
|
||||
|
||||
// ResultZeroBlob sets the result of the function to a zero-filled, length n BLOB.
|
||||
//
|
||||
// https://sqlite.org/c3ref/result_blob.html
|
||||
func (ctx Context) ResultZeroBlob(n int64) {
|
||||
ctx.c.call("sqlite3_result_zeroblob64",
|
||||
stk_t(ctx.handle), stk_t(n))
|
||||
ctx.c.wrp.Xsqlite3_result_zeroblob64(
|
||||
int32(ctx.handle), n)
|
||||
}
|
||||
|
||||
// ResultNull sets the result of the function to NULL.
|
||||
//
|
||||
// https://sqlite.org/c3ref/result_blob.html
|
||||
func (ctx Context) ResultNull() {
|
||||
ctx.c.call("sqlite3_result_null",
|
||||
stk_t(ctx.handle))
|
||||
ctx.c.wrp.Xsqlite3_result_null(
|
||||
int32(ctx.handle))
|
||||
}
|
||||
|
||||
// ResultTime sets the result of the function to a [time.Time].
|
||||
@@ -147,19 +139,19 @@ func (ctx Context) ResultTime(value time.Time, format TimeFormat) {
|
||||
case float64:
|
||||
ctx.ResultFloat(v)
|
||||
default:
|
||||
panic(util.AssertErr())
|
||||
panic(errutil.AssertErr())
|
||||
}
|
||||
}
|
||||
|
||||
func (ctx Context) resultRFC3339Nano(value time.Time) {
|
||||
const maxlen = int64(len(time.RFC3339Nano)) + 5
|
||||
|
||||
ptr := ctx.c.new(maxlen)
|
||||
buf := util.View(ctx.c.mod, ptr, maxlen)
|
||||
const maxlen = 48
|
||||
ptr := ctx.c.wrp.New(maxlen)
|
||||
buf := ctx.c.wrp.Bytes(ptr, maxlen)
|
||||
buf = value.AppendFormat(buf[:0], time.RFC3339Nano)
|
||||
_ = append(buf, 0)
|
||||
|
||||
ctx.c.call("sqlite3_result_text_go",
|
||||
stk_t(ctx.handle), stk_t(ptr), stk_t(len(buf)))
|
||||
ctx.c.wrp.Xsqlite3_result_text_go(
|
||||
int32(ctx.handle), int32(ptr), int64(len(buf)))
|
||||
}
|
||||
|
||||
// ResultPointer sets the result of the function to NULL, just like [Context.ResultNull],
|
||||
@@ -168,24 +160,9 @@ func (ctx Context) resultRFC3339Nano(value time.Time) {
|
||||
//
|
||||
// https://sqlite.org/c3ref/result_blob.html
|
||||
func (ctx Context) ResultPointer(ptr any) {
|
||||
valPtr := util.AddHandle(ctx.c.ctx, ptr)
|
||||
ctx.c.call("sqlite3_result_pointer_go",
|
||||
stk_t(ctx.handle), stk_t(valPtr))
|
||||
}
|
||||
|
||||
// ResultJSON sets the result of the function to the JSON encoding of value.
|
||||
//
|
||||
// https://sqlite.org/c3ref/result_blob.html
|
||||
func (ctx Context) ResultJSON(value any) {
|
||||
err := json.NewEncoder(callbackWriter(func(p []byte) (int, error) {
|
||||
ctx.ResultRawText(p[:len(p)-1]) // remove the newline
|
||||
return 0, nil
|
||||
})).Encode(value)
|
||||
|
||||
if err != nil {
|
||||
ctx.ResultError(err)
|
||||
return // notest
|
||||
}
|
||||
valPtr := ctx.c.wrp.AddHandle(ptr)
|
||||
ctx.c.wrp.Xsqlite3_result_pointer_go(
|
||||
int32(ctx.handle), int32(valPtr))
|
||||
}
|
||||
|
||||
// ResultValue sets the result of the function to a copy of [Value].
|
||||
@@ -196,8 +173,8 @@ func (ctx Context) ResultValue(value Value) {
|
||||
ctx.ResultError(MISUSE)
|
||||
return
|
||||
}
|
||||
ctx.c.call("sqlite3_result_value",
|
||||
stk_t(ctx.handle), stk_t(value.handle))
|
||||
ctx.c.wrp.Xsqlite3_result_value(
|
||||
int32(ctx.handle), int32(value.handle))
|
||||
}
|
||||
|
||||
// ResultError sets the result of the function an error.
|
||||
@@ -205,33 +182,41 @@ func (ctx Context) ResultValue(value Value) {
|
||||
// https://sqlite.org/c3ref/result_blob.html
|
||||
func (ctx Context) ResultError(err error) {
|
||||
if errors.Is(err, NOMEM) {
|
||||
ctx.c.call("sqlite3_result_error_nomem", stk_t(ctx.handle))
|
||||
ctx.c.wrp.Xsqlite3_result_error_nomem(int32(ctx.handle))
|
||||
return
|
||||
}
|
||||
|
||||
if errors.Is(err, TOOBIG) {
|
||||
ctx.c.call("sqlite3_result_error_toobig", stk_t(ctx.handle))
|
||||
ctx.c.wrp.Xsqlite3_result_error_toobig(int32(ctx.handle))
|
||||
return
|
||||
}
|
||||
|
||||
msg, code := errorCode(err, _OK)
|
||||
msg, code := errorCode(err, ERROR)
|
||||
if msg != "" {
|
||||
defer ctx.c.arena.mark()()
|
||||
ptr := ctx.c.arena.string(msg)
|
||||
ctx.c.call("sqlite3_result_error",
|
||||
stk_t(ctx.handle), stk_t(ptr), stk_t(len(msg)))
|
||||
defer ctx.c.arena.Mark()()
|
||||
ptr := ctx.c.arena.String(msg)
|
||||
ctx.c.wrp.Xsqlite3_result_error(
|
||||
int32(ctx.handle), int32(ptr), int32(len(msg)))
|
||||
}
|
||||
if code != _OK {
|
||||
ctx.c.call("sqlite3_result_error_code",
|
||||
stk_t(ctx.handle), stk_t(code))
|
||||
if code != res_t(ERROR) {
|
||||
ctx.c.wrp.Xsqlite3_result_error_code(
|
||||
int32(ctx.handle), int32(code))
|
||||
}
|
||||
}
|
||||
|
||||
// ResultSubtype sets the subtype of the result of the function.
|
||||
//
|
||||
// https://sqlite.org/c3ref/result_subtype.html
|
||||
func (ctx Context) ResultSubtype(t uint) {
|
||||
ctx.c.wrp.Xsqlite3_result_subtype(
|
||||
int32(ctx.handle), int32(t))
|
||||
}
|
||||
|
||||
// VTabNoChange may return true if a column is being fetched as part
|
||||
// of an update during which the column value will not change.
|
||||
//
|
||||
// https://sqlite.org/c3ref/vtab_nochange.html
|
||||
func (ctx Context) VTabNoChange() bool {
|
||||
b := int32(ctx.c.call("sqlite3_vtab_nochange", stk_t(ctx.handle)))
|
||||
b := ctx.c.wrp.Xsqlite3_vtab_nochange(int32(ctx.handle))
|
||||
return b != 0
|
||||
}
|
||||
|
||||
+119
-106
@@ -1,10 +1,8 @@
|
||||
// Package driver provides a database/sql driver for SQLite.
|
||||
//
|
||||
// Importing package driver registers a [database/sql] driver named "sqlite3".
|
||||
// You may also need to import package embed.
|
||||
//
|
||||
// import _ "github.com/ncruces/go-sqlite3/driver"
|
||||
// import _ "github.com/ncruces/go-sqlite3/embed"
|
||||
// import _ "github.com/hanzoai/sqlite3/driver"
|
||||
//
|
||||
// The data source name for "sqlite3" databases can be a filename or a "file:" [URI].
|
||||
//
|
||||
@@ -109,13 +107,14 @@ import (
|
||||
"time"
|
||||
"unsafe"
|
||||
|
||||
"github.com/ncruces/go-sqlite3"
|
||||
"github.com/ncruces/go-sqlite3/internal/util"
|
||||
"github.com/hanzoai/sqlite3"
|
||||
"github.com/hanzoai/sqlite3/internal/errutil"
|
||||
"github.com/hanzoai/sqlite3/internal/util"
|
||||
)
|
||||
|
||||
// This variable can be replaced with -ldflags:
|
||||
//
|
||||
// go build -ldflags="-X github.com/ncruces/go-sqlite3/driver.driverName=sqlite"
|
||||
// go build -ldflags="-X github.com/hanzoai/sqlite3/driver.driverName=sqlite"
|
||||
var driverName = "sqlite3"
|
||||
|
||||
func init() {
|
||||
@@ -175,15 +174,17 @@ func newConnector(name string, init, term func(*sqlite3.Conn) error) (*connector
|
||||
|
||||
var txlock, timefmt string
|
||||
if strings.HasPrefix(name, "file:") {
|
||||
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")
|
||||
u, err := url.Parse(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
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 {
|
||||
@@ -263,10 +264,8 @@ func (n *connector) Connect(ctx context.Context) (ret driver.Conn, err error) {
|
||||
return nil, err
|
||||
}
|
||||
defer s.Close()
|
||||
if s.Step() && s.ColumnBool(0) {
|
||||
c.readOnly = '1'
|
||||
} else {
|
||||
c.readOnly = '0'
|
||||
if s.Step() {
|
||||
c.readOnly = s.ColumnBool(0)
|
||||
}
|
||||
err = s.Close()
|
||||
if err != nil {
|
||||
@@ -322,7 +321,7 @@ type conn struct {
|
||||
txReset string
|
||||
tmRead sqlite3.TimeFormat
|
||||
tmWrite sqlite3.TimeFormat
|
||||
readOnly byte
|
||||
readOnly bool
|
||||
}
|
||||
|
||||
var (
|
||||
@@ -345,7 +344,7 @@ func (c *conn) BeginTx(ctx context.Context, opts driver.TxOptions) (driver.Tx, e
|
||||
var txLock string
|
||||
switch opts.Isolation {
|
||||
default:
|
||||
return nil, util.IsolationErr
|
||||
return nil, errutil.IsolationErr
|
||||
case driver.IsolationLevel(sql.LevelLinearizable):
|
||||
txLock = "exclusive"
|
||||
case driver.IsolationLevel(sql.LevelSerializable):
|
||||
@@ -358,9 +357,9 @@ func (c *conn) BeginTx(ctx context.Context, opts driver.TxOptions) (driver.Tx, e
|
||||
|
||||
c.txReset = ``
|
||||
txBegin := `BEGIN ` + txLock
|
||||
if opts.ReadOnly {
|
||||
if opts.ReadOnly && !c.readOnly {
|
||||
txBegin += ` ; PRAGMA query_only=on`
|
||||
c.txReset = `; PRAGMA query_only=` + string(c.readOnly)
|
||||
c.txReset = `; PRAGMA query_only=off`
|
||||
}
|
||||
|
||||
if old := c.Conn.SetInterrupt(ctx); old != ctx {
|
||||
@@ -407,7 +406,7 @@ func (c *conn) PrepareContext(ctx context.Context, query string) (driver.Stmt, e
|
||||
}
|
||||
if notWhitespace(tail) {
|
||||
s.Close()
|
||||
return nil, util.TailErr
|
||||
return nil, errutil.TailErr
|
||||
}
|
||||
return &stmt{Stmt: s, tmRead: c.tmRead, tmWrite: c.tmWrite, inputs: -2}, nil
|
||||
}
|
||||
@@ -546,12 +545,12 @@ func (s *stmt) setupBindings(args []driver.NamedValue) (err error) {
|
||||
err = s.Stmt.BindTime(id, a, s.tmWrite)
|
||||
case util.JSON:
|
||||
err = s.Stmt.BindJSON(id, a.Value)
|
||||
case util.PointerUnwrap:
|
||||
err = s.Stmt.BindPointer(id, util.UnwrapPointer(a))
|
||||
case util.Pointer:
|
||||
err = s.Stmt.BindPointer(id, a.Value)
|
||||
case nil:
|
||||
err = s.Stmt.BindNull(id)
|
||||
default:
|
||||
panic(util.AssertErr())
|
||||
panic(errutil.AssertErr())
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -565,7 +564,7 @@ func (s *stmt) CheckNamedValue(arg *driver.NamedValue) error {
|
||||
switch arg.Value.(type) {
|
||||
case bool, int, int64, float64, string, []byte,
|
||||
time.Time, sqlite3.ZeroBlob,
|
||||
util.JSON, util.PointerUnwrap,
|
||||
util.JSON, util.Pointer,
|
||||
nil:
|
||||
return nil
|
||||
default:
|
||||
@@ -604,26 +603,27 @@ func (r resultRowsAffected) RowsAffected() (int64, error) {
|
||||
return int64(r), nil
|
||||
}
|
||||
|
||||
type rows struct {
|
||||
ctx context.Context
|
||||
*stmt
|
||||
names []string
|
||||
types []string
|
||||
nulls []bool
|
||||
scans []scantype
|
||||
}
|
||||
|
||||
type scantype byte
|
||||
|
||||
const (
|
||||
_ANY scantype = iota
|
||||
_INT scantype = scantype(sqlite3.INTEGER)
|
||||
_REAL scantype = scantype(sqlite3.FLOAT)
|
||||
_TEXT scantype = scantype(sqlite3.TEXT)
|
||||
_BLOB scantype = scantype(sqlite3.BLOB)
|
||||
_NULL scantype = scantype(sqlite3.NULL)
|
||||
_BOOL scantype = iota
|
||||
_ANY scantype = iota
|
||||
_INT
|
||||
_REAL
|
||||
_TEXT
|
||||
_BLOB
|
||||
_NULL
|
||||
_BOOL
|
||||
_TIME
|
||||
_NOT_NULL
|
||||
)
|
||||
|
||||
var (
|
||||
_ [0]struct{} = [scantype(sqlite3.INTEGER) - _INT]struct{}{}
|
||||
_ [0]struct{} = [scantype(sqlite3.FLOAT) - _REAL]struct{}{}
|
||||
_ [0]struct{} = [scantype(sqlite3.TEXT) - _TEXT]struct{}{}
|
||||
_ [0]struct{} = [scantype(sqlite3.BLOB) - _BLOB]struct{}{}
|
||||
_ [0]struct{} = [scantype(sqlite3.NULL) - _NULL]struct{}{}
|
||||
_ [0]struct{} = [_NOT_NULL & (_NOT_NULL - 1)]struct{}{}
|
||||
)
|
||||
|
||||
func scanFromDecl(decl string) scantype {
|
||||
@@ -648,6 +648,14 @@ func scanFromDecl(decl string) scantype {
|
||||
return _ANY
|
||||
}
|
||||
|
||||
type rows struct {
|
||||
ctx context.Context
|
||||
*stmt
|
||||
names []string
|
||||
types []string
|
||||
scans []scantype
|
||||
}
|
||||
|
||||
var (
|
||||
// Ensure these interfaces are implemented:
|
||||
_ driver.RowsColumnTypeDatabaseTypeName = &rows{}
|
||||
@@ -674,34 +682,42 @@ func (r *rows) Columns() []string {
|
||||
|
||||
func (r *rows) scanType(index int) scantype {
|
||||
if r.scans == nil {
|
||||
count := r.Stmt.ColumnCount()
|
||||
count := len(r.names)
|
||||
scans := make([]scantype, count)
|
||||
for i := range scans {
|
||||
scans[i] = scanFromDecl(strings.ToUpper(r.Stmt.ColumnDeclType(i)))
|
||||
}
|
||||
r.scans = scans
|
||||
}
|
||||
return r.scans[index]
|
||||
return r.scans[index] &^ _NOT_NULL
|
||||
}
|
||||
|
||||
func (r *rows) loadColumnMetadata() {
|
||||
if r.nulls == nil {
|
||||
if r.types == nil {
|
||||
c := r.Stmt.Conn()
|
||||
count := r.Stmt.ColumnCount()
|
||||
nulls := make([]bool, count)
|
||||
count := len(r.names)
|
||||
types := make([]string, count)
|
||||
scans := make([]scantype, count)
|
||||
for i := range nulls {
|
||||
if col := r.Stmt.ColumnOriginName(i); col != "" {
|
||||
types[i], _, nulls[i], _, _, _ = c.TableColumnMetadata(
|
||||
for i := range types {
|
||||
var declType string
|
||||
var notNull, autoInc bool
|
||||
if column := r.Stmt.ColumnOriginName(i); column != "" {
|
||||
declType, _, notNull, _, autoInc, _ = c.TableColumnMetadata(
|
||||
r.Stmt.ColumnDatabaseName(i),
|
||||
r.Stmt.ColumnTableName(i),
|
||||
col)
|
||||
types[i] = strings.ToUpper(types[i])
|
||||
scans[i] = scanFromDecl(types[i])
|
||||
column)
|
||||
} else {
|
||||
declType = r.Stmt.ColumnDeclType(i)
|
||||
}
|
||||
if declType != "" {
|
||||
declType = strings.ToUpper(declType)
|
||||
scans[i] = scanFromDecl(declType)
|
||||
types[i] = declType
|
||||
}
|
||||
if notNull || autoInc {
|
||||
scans[i] |= _NOT_NULL
|
||||
}
|
||||
}
|
||||
r.nulls = nulls
|
||||
r.types = types
|
||||
r.scans = scans
|
||||
}
|
||||
@@ -720,15 +736,13 @@ func (r *rows) ColumnTypeDatabaseTypeName(index int) string {
|
||||
|
||||
func (r *rows) ColumnTypeNullable(index int) (nullable, ok bool) {
|
||||
r.loadColumnMetadata()
|
||||
if r.nulls[index] {
|
||||
return false, true
|
||||
}
|
||||
return true, false
|
||||
nullable = r.scans[index]&^_NOT_NULL == 0
|
||||
return nullable, !nullable
|
||||
}
|
||||
|
||||
func (r *rows) ColumnTypeScanType(index int) (typ reflect.Type) {
|
||||
r.loadColumnMetadata()
|
||||
scan := r.scans[index]
|
||||
scan := r.scans[index] &^ _NOT_NULL
|
||||
|
||||
if r.Stmt.Busy() {
|
||||
// SQLite is dynamically typed and we now have a row.
|
||||
@@ -740,7 +754,7 @@ func (r *rows) ColumnTypeScanType(index int) (typ reflect.Type) {
|
||||
switch {
|
||||
case scan == _TIME && val != _BLOB && val != _NULL:
|
||||
t := r.Stmt.ColumnTime(index, r.tmRead)
|
||||
useValType = t == time.Time{}
|
||||
useValType = t.IsZero()
|
||||
case scan == _BOOL && val == _INT:
|
||||
i := r.Stmt.ColumnInt64(index)
|
||||
useValType = i != 0 && i != 1
|
||||
@@ -770,17 +784,23 @@ func (r *rows) ColumnTypeScanType(index int) (typ reflect.Type) {
|
||||
}
|
||||
}
|
||||
|
||||
func (r *rows) Next(dest []driver.Value) error {
|
||||
func (r *rows) NextRow() 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
|
||||
}
|
||||
|
||||
if !r.Stmt.Step() {
|
||||
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
|
||||
}
|
||||
|
||||
data := unsafe.Slice((*any)(unsafe.SliceData(dest)), len(dest))
|
||||
@@ -788,45 +808,38 @@ func (r *rows) Next(dest []driver.Value) error {
|
||||
return err
|
||||
}
|
||||
for i := range dest {
|
||||
scan := r.scanType(i)
|
||||
switch v := dest[i].(type) {
|
||||
case int64:
|
||||
if scan == _BOOL {
|
||||
switch v {
|
||||
case 1:
|
||||
dest[i] = true
|
||||
case 0:
|
||||
dest[i] = false
|
||||
}
|
||||
continue
|
||||
}
|
||||
case []byte:
|
||||
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)
|
||||
case float64:
|
||||
break
|
||||
default:
|
||||
continue
|
||||
}
|
||||
if scan == _TIME {
|
||||
t, err := r.tmRead.Decode(dest[i])
|
||||
if err == nil {
|
||||
dest[i] = t
|
||||
continue
|
||||
}
|
||||
}
|
||||
dest[i] = r.convert(i, dest[i])
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
@@ -0,0 +1,146 @@
|
||||
//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
|
||||
}
|
||||
+109
-64
@@ -8,14 +8,15 @@ import (
|
||||
"math"
|
||||
"net/url"
|
||||
"reflect"
|
||||
"runtime"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/ncruces/go-sqlite3"
|
||||
_ "github.com/ncruces/go-sqlite3/embed"
|
||||
_ "github.com/ncruces/go-sqlite3/internal/testcfg"
|
||||
"github.com/ncruces/go-sqlite3/internal/util"
|
||||
"github.com/ncruces/go-sqlite3/vfs/memdb"
|
||||
"github.com/hanzoai/sqlite3"
|
||||
"github.com/hanzoai/sqlite3/internal/errutil"
|
||||
"github.com/hanzoai/sqlite3/internal/testcfg"
|
||||
"github.com/hanzoai/sqlite3/vfs/memdb"
|
||||
)
|
||||
|
||||
func Test_Open_error(t *testing.T) {
|
||||
@@ -33,35 +34,40 @@ func Test_Open_error(t *testing.T) {
|
||||
func Test_Open_dir(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
db, err := sql.Open("sqlite3", ".")
|
||||
ctx := testcfg.Context(t)
|
||||
db, err := Open(".")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
_, err = db.Conn(context.TODO())
|
||||
_, err = db.Conn(ctx)
|
||||
if err == nil {
|
||||
t.Fatal("want error")
|
||||
}
|
||||
if !errors.Is(err, sqlite3.CANTOPEN) {
|
||||
t.Errorf("got %v, want sqlite3.CANTOPEN", err)
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
func Test_Open_pragma(t *testing.T) {
|
||||
t.Parallel()
|
||||
tmp := memdb.TestDB(t, url.Values{
|
||||
dsn := memdb.TestDB(t, url.Values{
|
||||
"_pragma": {"busy_timeout(1000)"},
|
||||
})
|
||||
|
||||
db, err := sql.Open("sqlite3", tmp)
|
||||
ctx := testcfg.Context(t)
|
||||
db, err := Open(dsn)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
var timeout int
|
||||
err = db.QueryRow(`PRAGMA busy_timeout`).Scan(&timeout)
|
||||
err = db.QueryRowContext(ctx, `PRAGMA busy_timeout`).Scan(&timeout)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -72,17 +78,18 @@ func Test_Open_pragma(t *testing.T) {
|
||||
|
||||
func Test_Open_pragma_invalid(t *testing.T) {
|
||||
t.Parallel()
|
||||
tmp := memdb.TestDB(t, url.Values{
|
||||
dsn := memdb.TestDB(t, url.Values{
|
||||
"_pragma": {"busy_timeout 1000"},
|
||||
})
|
||||
|
||||
db, err := sql.Open("sqlite3", tmp)
|
||||
ctx := testcfg.Context(t)
|
||||
db, err := Open(dsn)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
_, err = db.Conn(context.TODO())
|
||||
_, err = db.Conn(ctx)
|
||||
if err == nil {
|
||||
t.Fatal("want error")
|
||||
}
|
||||
@@ -100,23 +107,24 @@ func Test_Open_pragma_invalid(t *testing.T) {
|
||||
|
||||
func Test_Open_txLock(t *testing.T) {
|
||||
t.Parallel()
|
||||
tmp := memdb.TestDB(t, url.Values{
|
||||
dsn := memdb.TestDB(t, url.Values{
|
||||
"_txlock": {"exclusive"},
|
||||
"_pragma": {"busy_timeout(1000)"},
|
||||
})
|
||||
|
||||
db, err := sql.Open("sqlite3", tmp)
|
||||
ctx := testcfg.Context(t)
|
||||
db, err := Open(dsn)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
tx1, err := db.Begin()
|
||||
tx1, err := db.BeginTx(ctx, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
_, err = db.Begin()
|
||||
_, err = db.BeginTx(ctx, nil)
|
||||
if err == nil {
|
||||
t.Error("want error")
|
||||
}
|
||||
@@ -136,11 +144,11 @@ func Test_Open_txLock(t *testing.T) {
|
||||
|
||||
func Test_Open_txLock_invalid(t *testing.T) {
|
||||
t.Parallel()
|
||||
tmp := memdb.TestDB(t, url.Values{
|
||||
dsn := memdb.TestDB(t, url.Values{
|
||||
"_txlock": {"xclusive"},
|
||||
})
|
||||
|
||||
_, err := sql.Open("sqlite3", tmp+"_txlock=xclusive")
|
||||
_, err := Open(dsn)
|
||||
if err == nil {
|
||||
t.Fatal("want error")
|
||||
}
|
||||
@@ -151,22 +159,20 @@ func Test_Open_txLock_invalid(t *testing.T) {
|
||||
|
||||
func Test_BeginTx(t *testing.T) {
|
||||
t.Parallel()
|
||||
tmp := memdb.TestDB(t, url.Values{
|
||||
dsn := memdb.TestDB(t, url.Values{
|
||||
"_txlock": {"exclusive"},
|
||||
"_pragma": {"busy_timeout(0)"},
|
||||
})
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
db, err := sql.Open("sqlite3", tmp)
|
||||
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(util.IsolationErr) {
|
||||
if err.Error() != string(errutil.IsolationErr) {
|
||||
t.Error("want isolationErr")
|
||||
}
|
||||
|
||||
@@ -201,15 +207,16 @@ func Test_BeginTx(t *testing.T) {
|
||||
|
||||
func Test_nested_context(t *testing.T) {
|
||||
t.Parallel()
|
||||
tmp := memdb.TestDB(t)
|
||||
dsn := memdb.TestDB(t)
|
||||
|
||||
db, err := sql.Open("sqlite3", tmp)
|
||||
ctx := testcfg.Context(t)
|
||||
db, err := Open(dsn)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
tx, err := db.Begin()
|
||||
tx, err := db.BeginTx(ctx, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -236,7 +243,7 @@ func Test_nested_context(t *testing.T) {
|
||||
|
||||
want(outer, 0)
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
defer cancel()
|
||||
|
||||
inner, err := tx.QueryContext(ctx, `SELECT value FROM generate_series(0)`)
|
||||
@@ -259,16 +266,17 @@ func Test_nested_context(t *testing.T) {
|
||||
|
||||
func Test_Prepare(t *testing.T) {
|
||||
t.Parallel()
|
||||
tmp := memdb.TestDB(t)
|
||||
dsn := memdb.TestDB(t)
|
||||
|
||||
db, err := sql.Open("sqlite3", tmp)
|
||||
ctx := testcfg.Context(t)
|
||||
db, err := Open(dsn)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
var serr *sqlite3.Error
|
||||
_, err = db.Prepare(`SELECT`)
|
||||
_, err = db.PrepareContext(ctx, `SELECT`)
|
||||
if err == nil {
|
||||
t.Error("want error")
|
||||
}
|
||||
@@ -282,30 +290,28 @@ func Test_Prepare(t *testing.T) {
|
||||
t.Error("got message:", got)
|
||||
}
|
||||
|
||||
_, err = db.Prepare(`SELECT 1; `)
|
||||
_, err = db.PrepareContext(ctx, `SELECT 1; `)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
_, err = db.Prepare(`SELECT 1; SELECT`)
|
||||
if err.Error() != string(util.TailErr) {
|
||||
_, err = db.PrepareContext(ctx, `SELECT 1; SELECT`)
|
||||
if err.Error() != string(errutil.TailErr) {
|
||||
t.Error("want tailErr")
|
||||
}
|
||||
|
||||
_, err = db.Prepare(`SELECT 1; SELECT 2`)
|
||||
if err.Error() != string(util.TailErr) {
|
||||
_, err = db.PrepareContext(ctx, `SELECT 1; SELECT 2`)
|
||||
if err.Error() != string(errutil.TailErr) {
|
||||
t.Error("want tailErr")
|
||||
}
|
||||
}
|
||||
|
||||
func Test_QueryRow_named(t *testing.T) {
|
||||
t.Parallel()
|
||||
tmp := memdb.TestDB(t)
|
||||
dsn := memdb.TestDB(t)
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
db, err := sql.Open("sqlite3", tmp)
|
||||
ctx := testcfg.Context(t)
|
||||
db, err := Open(dsn)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -324,7 +330,7 @@ func Test_QueryRow_named(t *testing.T) {
|
||||
defer stmt.Close()
|
||||
|
||||
date := time.Now()
|
||||
row := stmt.QueryRow(true, sql.Named("AAA", math.Pi), nil /*3*/, nil /*4*/, date /*5*/)
|
||||
row := stmt.QueryRowContext(ctx, true, sql.Named("AAA", math.Pi), nil /*3*/, nil /*4*/, date /*5*/)
|
||||
|
||||
var first bool
|
||||
var fifth time.Time
|
||||
@@ -353,15 +359,16 @@ func Test_QueryRow_named(t *testing.T) {
|
||||
|
||||
func Test_QueryRow_blob_null(t *testing.T) {
|
||||
t.Parallel()
|
||||
tmp := memdb.TestDB(t)
|
||||
dsn := memdb.TestDB(t)
|
||||
|
||||
db, err := sql.Open("sqlite3", tmp)
|
||||
ctx := testcfg.Context(t)
|
||||
db, err := Open(dsn)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
rows, err := db.Query(`
|
||||
rows, err := db.QueryContext(ctx, `
|
||||
SELECT NULL UNION ALL
|
||||
SELECT x'cafe' UNION ALL
|
||||
SELECT x'babe' UNION ALL
|
||||
@@ -390,11 +397,12 @@ func Test_time(t *testing.T) {
|
||||
|
||||
for _, fmt := range []string{"auto", "sqlite", "rfc3339", time.ANSIC} {
|
||||
t.Run(fmt, func(t *testing.T) {
|
||||
tmp := memdb.TestDB(t, url.Values{
|
||||
dsn := memdb.TestDB(t, url.Values{
|
||||
"_timefmt": {fmt},
|
||||
})
|
||||
|
||||
db, err := sql.Open("sqlite3", tmp)
|
||||
ctx := testcfg.Context(t)
|
||||
db, err := Open(dsn)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -402,18 +410,18 @@ func Test_time(t *testing.T) {
|
||||
|
||||
twosday := time.Date(2022, 2, 22, 22, 22, 22, 0, time.UTC)
|
||||
|
||||
_, err = db.Exec(`CREATE TABLE test (at DATETIME)`)
|
||||
_, err = db.ExecContext(ctx, `CREATE TABLE test (at DATETIME)`)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
_, err = db.Exec(`INSERT INTO test VALUES (?)`, twosday)
|
||||
_, err = db.ExecContext(ctx, `INSERT INTO test VALUES (?)`, twosday)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
var got time.Time
|
||||
err = db.QueryRow(`SELECT * FROM test`).Scan(&got)
|
||||
err = db.QueryRowContext(ctx, `SELECT * FROM test`).Scan(&got)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -437,15 +445,16 @@ func Test_ColumnType_ScanType(t *testing.T) {
|
||||
)
|
||||
|
||||
t.Parallel()
|
||||
tmp := memdb.TestDB(t)
|
||||
dsn := memdb.TestDB(t)
|
||||
|
||||
db, err := sql.Open("sqlite3", tmp)
|
||||
ctx := testcfg.Context(t)
|
||||
db, err := Open(dsn)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
_, err = db.Exec(`
|
||||
_, err = db.ExecContext(ctx, `
|
||||
CREATE TABLE test (
|
||||
col_int INTEGER,
|
||||
col_real REAL,
|
||||
@@ -470,7 +479,7 @@ func Test_ColumnType_ScanType(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
rows, err := db.Query(`SELECT * FROM test`)
|
||||
rows, err := db.QueryContext(ctx, `SELECT * FROM test`)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -526,7 +535,47 @@ 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)
|
||||
@@ -534,17 +583,13 @@ func Benchmark_loop(b *testing.B) {
|
||||
defer db.Close()
|
||||
|
||||
var version string
|
||||
err = db.QueryRow(`SELECT sqlite_version();`).Scan(&version)
|
||||
err = db.QueryRowContext(ctx, `SELECT sqlite_version();`).Scan(&version)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
b.Cleanup(cancel)
|
||||
|
||||
b.ResetTimer()
|
||||
for range b.N {
|
||||
_, err := db.ExecContext(ctx,
|
||||
for b.Loop() {
|
||||
_, err := db.ExecContext(b.Context(),
|
||||
`WITH RECURSIVE c(x) AS (VALUES(1) UNION ALL SELECT x+1 FROM c WHERE x < 1000000) SELECT x FROM c;`)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
|
||||
@@ -7,10 +7,9 @@ import (
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"github.com/ncruces/go-sqlite3"
|
||||
_ "github.com/ncruces/go-sqlite3/driver"
|
||||
_ "github.com/ncruces/go-sqlite3/embed"
|
||||
_ "github.com/ncruces/go-sqlite3/vfs/memdb"
|
||||
"github.com/hanzoai/sqlite3"
|
||||
_ "github.com/hanzoai/sqlite3/driver"
|
||||
_ "github.com/hanzoai/sqlite3/vfs/memdb"
|
||||
)
|
||||
|
||||
func Example_customTime() {
|
||||
|
||||
@@ -10,9 +10,8 @@ import (
|
||||
"log"
|
||||
"os"
|
||||
|
||||
_ "github.com/ncruces/go-sqlite3/driver"
|
||||
_ "github.com/ncruces/go-sqlite3/embed"
|
||||
_ "github.com/ncruces/go-sqlite3/vfs/memdb"
|
||||
_ "github.com/hanzoai/sqlite3/driver"
|
||||
_ "github.com/hanzoai/sqlite3/vfs/memdb"
|
||||
)
|
||||
|
||||
var db *sql.DB
|
||||
|
||||
+3
-4
@@ -4,10 +4,9 @@ import (
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"github.com/ncruces/go-sqlite3"
|
||||
"github.com/ncruces/go-sqlite3/driver"
|
||||
_ "github.com/ncruces/go-sqlite3/embed"
|
||||
_ "github.com/ncruces/go-sqlite3/vfs/memdb"
|
||||
"github.com/hanzoai/sqlite3"
|
||||
"github.com/hanzoai/sqlite3/driver"
|
||||
_ "github.com/hanzoai/sqlite3/vfs/memdb"
|
||||
)
|
||||
|
||||
func Example_json() {
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@ import (
|
||||
"database/sql"
|
||||
"time"
|
||||
|
||||
"github.com/ncruces/go-sqlite3"
|
||||
"github.com/hanzoai/sqlite3"
|
||||
)
|
||||
|
||||
// Savepoint establishes a new transaction savepoint.
|
||||
|
||||
@@ -4,10 +4,8 @@ import (
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"github.com/ncruces/go-sqlite3/driver"
|
||||
_ "github.com/ncruces/go-sqlite3/embed"
|
||||
_ "github.com/ncruces/go-sqlite3/internal/testcfg"
|
||||
_ "github.com/ncruces/go-sqlite3/vfs/memdb"
|
||||
"github.com/hanzoai/sqlite3/driver"
|
||||
_ "github.com/hanzoai/sqlite3/vfs/memdb"
|
||||
)
|
||||
|
||||
func ExampleSavepoint() {
|
||||
|
||||
+11
-3
@@ -6,10 +6,18 @@ import (
|
||||
)
|
||||
|
||||
// Convert a string in [time.RFC3339Nano] format into a [time.Time]
|
||||
// if it roundtrips back to the same string.
|
||||
// if that's the format we're using to persist them,
|
||||
// and 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 needed, [database/sql] will recover the same string.
|
||||
func maybeTime(text []byte) (_ time.Time, _ bool) {
|
||||
// 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
|
||||
}
|
||||
|
||||
// Weed out (some) values that can't possibly be
|
||||
// [time.RFC3339Nano] timestamps.
|
||||
if len(text) < len("2006-01-02T15:04:05Z") {
|
||||
|
||||
+4
-2
@@ -21,8 +21,9 @@ 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 := maybeTime([]byte(str))
|
||||
v, ok := r.maybeTime([]byte(str))
|
||||
if ok {
|
||||
// Make sure times round-trip to the same string:
|
||||
// https://pkg.go.dev/database/sql#Rows.Scan
|
||||
@@ -50,8 +51,9 @@ 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 := maybeTime(date.AppendFormat(nil, time.RFC3339Nano))
|
||||
v, ok := r.maybeTime(date.AppendFormat(nil, time.RFC3339Nano))
|
||||
if ok {
|
||||
// Make sure times round-trip to the same time:
|
||||
if !v.Equal(date) {
|
||||
|
||||
+3
-4
@@ -1,13 +1,11 @@
|
||||
package driver
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql/driver"
|
||||
"slices"
|
||||
"testing"
|
||||
|
||||
_ "github.com/ncruces/go-sqlite3/embed"
|
||||
_ "github.com/ncruces/go-sqlite3/internal/testcfg"
|
||||
"github.com/hanzoai/sqlite3/internal/testcfg"
|
||||
)
|
||||
|
||||
func Test_namedValues(t *testing.T) {
|
||||
@@ -56,7 +54,8 @@ func Fuzz_notWhitespace(f *testing.F) {
|
||||
t.SkipNow()
|
||||
}
|
||||
|
||||
c, err := db.Conn(context.Background())
|
||||
ctx := testcfg.Context(t)
|
||||
c, err := db.Conn(ctx)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
@@ -1,41 +0,0 @@
|
||||
# Embeddable Wasm build of SQLite
|
||||
|
||||
This folder includes an embeddable Wasm build of SQLite 3.50.1 for use with
|
||||
[`github.com/ncruces/go-sqlite3`](https://pkg.go.dev/github.com/ncruces/go-sqlite3).
|
||||
|
||||
The following optional features are compiled in:
|
||||
- [math functions](https://sqlite.org/lang_mathfunc.html)
|
||||
- [FTS5](https://sqlite.org/fts5.html)
|
||||
- [JSON](https://sqlite.org/json1.html)
|
||||
- [R*Tree](https://sqlite.org/rtree.html)
|
||||
- [GeoPoly](https://sqlite.org/geopoly.html)
|
||||
- [Spellfix1](https://sqlite.org/spellfix1.html)
|
||||
- [soundex](https://sqlite.org/lang_corefunc.html#soundex)
|
||||
- [stat4](https://sqlite.org/compile.html#enable_stat4)
|
||||
- [base64](https://github.com/sqlite/sqlite/blob/master/ext/misc/base64.c)
|
||||
- [decimal](https://github.com/sqlite/sqlite/blob/master/ext/misc/decimal.c)
|
||||
- [ieee754](https://github.com/sqlite/sqlite/blob/master/ext/misc/ieee754.c)
|
||||
- [regexp](https://github.com/sqlite/sqlite/blob/master/ext/misc/regexp.c)
|
||||
- [series](https://github.com/sqlite/sqlite/blob/master/ext/misc/series.c)
|
||||
- [uint](https://github.com/sqlite/sqlite/blob/master/ext/misc/uint.c)
|
||||
- [time](../sqlite3/time.c)
|
||||
|
||||
See the [configuration options](../sqlite3/sqlite_opt.h),
|
||||
and [patches](../sqlite3) applied.
|
||||
|
||||
Built using [`wasi-sdk`](https://github.com/WebAssembly/wasi-sdk),
|
||||
and [`binaryen`](https://github.com/WebAssembly/binaryen).
|
||||
|
||||
The build is easily reproducible, and verifiable, using
|
||||
[Artifact Attestations](https://github.com/ncruces/go-sqlite3/attestations).
|
||||
|
||||
### Customizing the build
|
||||
|
||||
You can use your own custom build of SQLite.
|
||||
|
||||
Examples of custom builds of SQLite are:
|
||||
- [`github.com/ncruces/go-sqlite3/embed/bcw2`](https://github.com/ncruces/go-sqlite3/tree/main/embed/bcw2)
|
||||
built from a branch supporting [`BEGIN CONCURRENT`](https://sqlite.org/src/doc/begin-concurrent/doc/begin_concurrent.md)
|
||||
and [Wal2](https://sqlite.org/cgi/src/doc/wal2/doc/wal2.md).
|
||||
- [`github.com/asg017/sqlite-vec-go-bindings/ncruces`](https://github.com/asg017/sqlite-vec-go-bindings)
|
||||
which includes the [`sqlite-vec`](https://github.com/asg017/sqlite-vec) vector search extension.
|
||||
@@ -1,2 +0,0 @@
|
||||
build/
|
||||
sqlite/
|
||||
@@ -1,22 +0,0 @@
|
||||
# Embeddable Wasm build of SQLite
|
||||
|
||||
This folder includes an alternative embeddable Wasm build of SQLite,
|
||||
which includes the experimental
|
||||
[`BEGIN CONCURRENT`](https://sqlite.org/src/doc/begin-concurrent/doc/begin_concurrent.md) and
|
||||
[Wal2](https://sqlite.org/cgi/src/doc/wal2/doc/wal2.md) patches.
|
||||
|
||||
It also enables the optional
|
||||
[`UPDATE … ORDER BY … LIMIT`](https://sqlite.org/lang_update.html#optional_limit_and_order_by_clauses) and
|
||||
[`DELETE … ORDER BY … LIMIT`](https://sqlite.org/lang_delete.html#optional_limit_and_order_by_clauses) clauses,
|
||||
and the [`WITHIN GROUP ORDER BY`](https://sqlite.org/compile.html#enable_ordered_set_aggregates) aggregate syntax.
|
||||
|
||||
> [!IMPORTANT]
|
||||
> This package is experimental.
|
||||
> It is built from the `bedrock` branch of SQLite,
|
||||
> since that is _currently_ the most stable, maintained branch to include these features.
|
||||
|
||||
> [!CAUTION]
|
||||
> The Wal2 journaling mode creates databases that other versions of SQLite cannot access.
|
||||
|
||||
The build is easily reproducible, and verifiable, using
|
||||
[Artifact Attestations](https://github.com/ncruces/go-sqlite3/attestations).
|
||||
Binary file not shown.
@@ -1,59 +0,0 @@
|
||||
package bcw2
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/ncruces/go-sqlite3/driver"
|
||||
"github.com/ncruces/go-sqlite3/ext/stats"
|
||||
"github.com/ncruces/go-sqlite3/vfs"
|
||||
)
|
||||
|
||||
func Test_bcw2(t *testing.T) {
|
||||
if !vfs.SupportsSharedMemory {
|
||||
t.Skip("skipping without shared memory")
|
||||
}
|
||||
|
||||
tmp := filepath.ToSlash(filepath.Join(t.TempDir(), "test.db"))
|
||||
|
||||
db, err := driver.Open("file:"+tmp+"?_pragma=journal_mode(wal2)&_txlock=concurrent", stats.Register)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
tx, err := db.Begin()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer tx.Rollback()
|
||||
|
||||
_, err = tx.Exec(`CREATE TABLE test (col)`)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
_, err = tx.Exec(`DELETE FROM test LIMIT 1`)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
_, err = tx.Exec(`SELECT median() WITHIN GROUP (ORDER BY col) FROM test`)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = tx.Commit()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
var version string
|
||||
err = db.QueryRow(`SELECT sqlite_version()`).Scan(&version)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if version != "3.51.0" {
|
||||
t.Error(version)
|
||||
}
|
||||
}
|
||||
@@ -1,66 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
cd -P -- "$(dirname -- "$0")"
|
||||
|
||||
ROOT=../../
|
||||
BINARYEN="$ROOT/tools/binaryen/bin"
|
||||
WASI_SDK="$ROOT/tools/wasi-sdk/bin"
|
||||
|
||||
trap 'rm -rf build/ sqlite/ bcw2.tmp' EXIT
|
||||
|
||||
mkdir -p build/ext/
|
||||
cp "$ROOT"/sqlite3/*.[ch] build/
|
||||
cp "$ROOT"/sqlite3/*.patch build/
|
||||
|
||||
# https://sqlite.org/src/info/93740658c8c6f531
|
||||
curl -# https://sqlite.org/src/tarball/sqlite.tar.gz?r=93740658c8 | tar xz
|
||||
|
||||
cd sqlite
|
||||
if [[ "$OSTYPE" == "msys" || "$OSTYPE" == "cygwin" ]]; then
|
||||
MSYS_NO_PATHCONV=1 nmake /f makefile.msc sqlite3.c "OPTS=-DSQLITE_ENABLE_UPDATE_DELETE_LIMIT -DSQLITE_ENABLE_ORDERED_SET_AGGREGATES"
|
||||
else
|
||||
sh configure --enable-update-limit
|
||||
OPTS=-DSQLITE_ENABLE_ORDERED_SET_AGGREGATES make sqlite3.c
|
||||
fi
|
||||
cd ~-
|
||||
|
||||
mv sqlite/sqlite3.c build/
|
||||
mv sqlite/sqlite3.h build/
|
||||
mv sqlite/sqlite3ext.h build/
|
||||
mv sqlite/ext/misc/anycollseq.c build/ext/
|
||||
mv sqlite/ext/misc/base64.c build/ext/
|
||||
mv sqlite/ext/misc/decimal.c build/ext/
|
||||
mv sqlite/ext/misc/ieee754.c build/ext/
|
||||
mv sqlite/ext/misc/regexp.c build/ext/
|
||||
mv sqlite/ext/misc/series.c build/ext/
|
||||
mv sqlite/ext/misc/spellfix.c build/ext/
|
||||
mv sqlite/ext/misc/uint.c build/ext/
|
||||
|
||||
cd build
|
||||
cat *.patch | patch -p0 --no-backup-if-mismatch
|
||||
cd ~-
|
||||
|
||||
"$WASI_SDK/clang" --target=wasm32-wasi -std=c23 -g0 -O2 \
|
||||
-Wall -Wextra -Wno-unused-parameter -Wno-unused-function \
|
||||
-o bcw2.wasm build/main.c \
|
||||
-I"$ROOT/sqlite3/libc" -I"build" \
|
||||
-mexec-model=reactor \
|
||||
-mmutable-globals -mnontrapping-fptoint \
|
||||
-msimd128 -mbulk-memory -msign-ext \
|
||||
-mreference-types -mmultivalue \
|
||||
-fno-stack-protector -fno-stack-clash-protection \
|
||||
-Wl,--stack-first \
|
||||
-Wl,--import-undefined \
|
||||
-Wl,--initial-memory=327680 \
|
||||
-D_HAVE_SQLITE_CONFIG_H \
|
||||
-DSQLITE_ENABLE_UPDATE_DELETE_LIMIT \
|
||||
-DSQLITE_CUSTOM_INCLUDE=sqlite_opt.h \
|
||||
$(awk '{print "-Wl,--export="$0}' ../exports.txt)
|
||||
|
||||
"$BINARYEN/wasm-ctor-eval" -g -c _initialize bcw2.wasm -o bcw2.tmp
|
||||
"$BINARYEN/wasm-opt" -g --strip --strip-producers -c -O3 \
|
||||
bcw2.tmp -o bcw2.wasm --low-memory-unused \
|
||||
--enable-mutable-globals --enable-nontrapping-float-to-int \
|
||||
--enable-simd --enable-bulk-memory --enable-sign-ext \
|
||||
--enable-reference-types --enable-multivalue
|
||||
@@ -1,14 +0,0 @@
|
||||
module github.com/ncruces/go-sqlite3/embed/bcw2
|
||||
|
||||
go 1.23.0
|
||||
|
||||
toolchain go1.24.0
|
||||
|
||||
require github.com/ncruces/go-sqlite3 v0.26.0
|
||||
|
||||
require (
|
||||
github.com/ncruces/julianday v1.0.0 // indirect
|
||||
github.com/ncruces/sort v0.1.5 // indirect
|
||||
github.com/tetratelabs/wazero v1.9.0 // indirect
|
||||
golang.org/x/sys v0.33.0 // indirect
|
||||
)
|
||||
@@ -1,12 +0,0 @@
|
||||
github.com/ncruces/go-sqlite3 v0.26.0 h1:dY6ASfuhSEbtSge6kJwjyJVC7bXCpgEVOycmdboKJek=
|
||||
github.com/ncruces/go-sqlite3 v0.26.0/go.mod h1:46HIzeCQQ+aNleAxCli+vpA2tfh7ttSnw24kQahBc1o=
|
||||
github.com/ncruces/julianday v1.0.0 h1:fH0OKwa7NWvniGQtxdJRxAgkBMolni2BjDHaWTxqt7M=
|
||||
github.com/ncruces/julianday v1.0.0/go.mod h1:Dusn2KvZrrovOMJuOt0TNXL6tB7U2E8kvza5fFc9G7g=
|
||||
github.com/ncruces/sort v0.1.5 h1:fiFWXXAqKI8QckPf/6hu/bGFwcEPrirIOFaJqWujs4k=
|
||||
github.com/ncruces/sort v0.1.5/go.mod h1:obJToO4rYr6VWP0Uw5FYymgYGt3Br4RXcs/JdKaXAPk=
|
||||
github.com/tetratelabs/wazero v1.9.0 h1:IcZ56OuxrtaEz8UYNRHBrUa9bYeX9oVY93KspZZBf/I=
|
||||
github.com/tetratelabs/wazero v1.9.0/go.mod h1:TSbcXCfFP0L2FGkRPxHphadXPjo1T6W+CseNNY7EkjM=
|
||||
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
|
||||
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4=
|
||||
golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA=
|
||||
@@ -1,24 +0,0 @@
|
||||
// Package bcw2 embeds SQLite into your application.
|
||||
//
|
||||
// Importing package bcw2 initializes the [sqlite3.Binary] variable
|
||||
// with a build of SQLite that includes the [BEGIN CONCURRENT] and [Wal2] patches:
|
||||
//
|
||||
// import _ "github.com/ncruces/go-sqlite3/embed/bcw2"
|
||||
//
|
||||
// [BEGIN CONCURRENT]: https://sqlite.org/src/doc/begin-concurrent/doc/begin_concurrent.md
|
||||
// [Wal2]: https://sqlite.org/cgi/src/doc/wal2/doc/wal2.md
|
||||
package bcw2
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
"unsafe"
|
||||
|
||||
"github.com/ncruces/go-sqlite3"
|
||||
)
|
||||
|
||||
//go:embed bcw2.wasm
|
||||
var binary string
|
||||
|
||||
func init() {
|
||||
sqlite3.Binary = unsafe.Slice(unsafe.StringData(binary), len(binary))
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
cd -P -- "$(dirname -- "$0")"
|
||||
|
||||
ROOT=../
|
||||
BINARYEN="$ROOT/tools/binaryen/bin"
|
||||
WASI_SDK="$ROOT/tools/wasi-sdk/bin"
|
||||
|
||||
trap 'rm -f sqlite3.tmp' EXIT
|
||||
|
||||
"$WASI_SDK/clang" --target=wasm32-wasi -std=c23 -g0 -O2 \
|
||||
-Wall -Wextra -Wno-unused-parameter -Wno-unused-function \
|
||||
-o sqlite3.wasm "$ROOT/sqlite3/main.c" \
|
||||
-I"$ROOT/sqlite3/libc" -I"$ROOT/sqlite3" \
|
||||
-mexec-model=reactor \
|
||||
-mmutable-globals -mnontrapping-fptoint \
|
||||
-msimd128 -mbulk-memory -msign-ext \
|
||||
-mreference-types -mmultivalue \
|
||||
-fno-stack-protector -fno-stack-clash-protection \
|
||||
-Wl,--stack-first \
|
||||
-Wl,--import-undefined \
|
||||
-Wl,--initial-memory=327680 \
|
||||
-D_HAVE_SQLITE_CONFIG_H \
|
||||
-DSQLITE_CUSTOM_INCLUDE=sqlite_opt.h \
|
||||
$(awk '{print "-Wl,--export="$0}' exports.txt)
|
||||
|
||||
"$BINARYEN/wasm-ctor-eval" -g -c _initialize sqlite3.wasm -o sqlite3.tmp
|
||||
"$BINARYEN/wasm-opt" -g --strip --strip-producers -c -O3 \
|
||||
sqlite3.tmp -o sqlite3.wasm --low-memory-unused \
|
||||
--enable-mutable-globals --enable-nontrapping-float-to-int \
|
||||
--enable-simd --enable-bulk-memory --enable-sign-ext \
|
||||
--enable-reference-types --enable-multivalue
|
||||
@@ -0,0 +1,8 @@
|
||||
// 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.")
|
||||
}
|
||||
@@ -1,142 +0,0 @@
|
||||
aligned_alloc
|
||||
sqlite3_anycollseq_init
|
||||
sqlite3_autovacuum_pages_go
|
||||
sqlite3_backup_finish
|
||||
sqlite3_backup_init
|
||||
sqlite3_backup_pagecount
|
||||
sqlite3_backup_remaining
|
||||
sqlite3_backup_step
|
||||
sqlite3_bind_blob_go
|
||||
sqlite3_bind_double
|
||||
sqlite3_bind_int64
|
||||
sqlite3_bind_null
|
||||
sqlite3_bind_parameter_count
|
||||
sqlite3_bind_parameter_index
|
||||
sqlite3_bind_parameter_name
|
||||
sqlite3_bind_pointer_go
|
||||
sqlite3_bind_text_go
|
||||
sqlite3_bind_value
|
||||
sqlite3_bind_zeroblob64
|
||||
sqlite3_blob_bytes
|
||||
sqlite3_blob_close
|
||||
sqlite3_blob_open
|
||||
sqlite3_blob_read
|
||||
sqlite3_blob_reopen
|
||||
sqlite3_blob_write
|
||||
sqlite3_busy_handler_go
|
||||
sqlite3_busy_timeout
|
||||
sqlite3_changes64
|
||||
sqlite3_clear_bindings
|
||||
sqlite3_close
|
||||
sqlite3_close_v2
|
||||
sqlite3_collation_needed_go
|
||||
sqlite3_column_blob
|
||||
sqlite3_column_bytes
|
||||
sqlite3_column_count
|
||||
sqlite3_column_database_name
|
||||
sqlite3_column_decltype
|
||||
sqlite3_column_double
|
||||
sqlite3_column_int64
|
||||
sqlite3_column_name
|
||||
sqlite3_column_origin_name
|
||||
sqlite3_column_table_name
|
||||
sqlite3_column_text
|
||||
sqlite3_column_type
|
||||
sqlite3_column_value
|
||||
sqlite3_columns_go
|
||||
sqlite3_commit_hook_go
|
||||
sqlite3_config_log_go
|
||||
sqlite3_create_aggregate_function_go
|
||||
sqlite3_create_collation_go
|
||||
sqlite3_create_function_go
|
||||
sqlite3_create_module_go
|
||||
sqlite3_create_window_function_go
|
||||
sqlite3_data_count
|
||||
sqlite3_database_file_object
|
||||
sqlite3_db_cacheflush
|
||||
sqlite3_db_config
|
||||
sqlite3_db_filename
|
||||
sqlite3_db_name
|
||||
sqlite3_db_readonly
|
||||
sqlite3_db_release_memory
|
||||
sqlite3_db_status
|
||||
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_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_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
|
||||
@@ -1,21 +0,0 @@
|
||||
// Package embed embeds SQLite into your application.
|
||||
//
|
||||
// Importing package embed initializes the [sqlite3.Binary] variable
|
||||
// with an appropriate build of SQLite:
|
||||
//
|
||||
// import _ "github.com/ncruces/go-sqlite3/embed"
|
||||
package embed
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
"unsafe"
|
||||
|
||||
"github.com/ncruces/go-sqlite3"
|
||||
)
|
||||
|
||||
//go:embed sqlite3.wasm
|
||||
var binary string
|
||||
|
||||
func init() {
|
||||
sqlite3.Binary = unsafe.Slice(unsafe.StringData(binary), len(binary))
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
package embed
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/ncruces/go-sqlite3/driver"
|
||||
_ "github.com/ncruces/go-sqlite3/vfs/memdb"
|
||||
)
|
||||
|
||||
func Test_init(t *testing.T) {
|
||||
db, err := driver.Open("file:/test.db?vfs=memdb")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
var version string
|
||||
err = db.QueryRow(`SELECT sqlite_version()`).Scan(&version)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if version != "3.50.1" {
|
||||
t.Error(version)
|
||||
}
|
||||
}
|
||||
Binary file not shown.
@@ -4,13 +4,14 @@ import (
|
||||
"errors"
|
||||
"strings"
|
||||
|
||||
"github.com/ncruces/go-sqlite3/internal/util"
|
||||
"github.com/hanzoai/sqlite3/internal/sqlite3_wrap"
|
||||
)
|
||||
|
||||
// Error wraps an SQLite Error Code.
|
||||
//
|
||||
// https://sqlite.org/c3ref/errcode.html
|
||||
type Error struct {
|
||||
sys error
|
||||
msg string
|
||||
sql string
|
||||
code res_t
|
||||
@@ -33,16 +34,28 @@ func (e *Error) ExtendedCode() ExtendedErrorCode {
|
||||
// Error implements the error interface.
|
||||
func (e *Error) Error() string {
|
||||
var b strings.Builder
|
||||
b.WriteString(util.ErrorCodeString(uint32(e.code)))
|
||||
b.WriteString(sqlite3_wrap.ErrorCodeString(e.code))
|
||||
|
||||
if e.msg != "" {
|
||||
b.WriteString(": ")
|
||||
b.WriteString(e.msg)
|
||||
}
|
||||
if e.sys != nil {
|
||||
b.WriteString(": ")
|
||||
b.WriteString(e.sys.Error())
|
||||
}
|
||||
|
||||
return b.String()
|
||||
}
|
||||
|
||||
// Unwrap returns the underlying operating system error
|
||||
// that caused the I/O error or failure to open a file.
|
||||
//
|
||||
// https://sqlite.org/c3ref/system_errno.html
|
||||
func (e *Error) Unwrap() error {
|
||||
return e.sys
|
||||
}
|
||||
|
||||
// Is tests whether this error matches a given [ErrorCode] or [ExtendedErrorCode].
|
||||
//
|
||||
// It makes it possible to do:
|
||||
@@ -90,7 +103,16 @@ func (e *Error) SQL() string {
|
||||
|
||||
// Error implements the error interface.
|
||||
func (e ErrorCode) Error() string {
|
||||
return util.ErrorCodeString(uint32(e))
|
||||
return sqlite3_wrap.ErrorCodeString(e)
|
||||
}
|
||||
|
||||
// As converts this error to an [ExtendedErrorCode].
|
||||
func (e ErrorCode) As(err any) bool {
|
||||
c, ok := err.(*xErrorCode)
|
||||
if ok {
|
||||
*c = xErrorCode(e)
|
||||
}
|
||||
return ok
|
||||
}
|
||||
|
||||
// Temporary returns true for [BUSY] errors.
|
||||
@@ -105,7 +127,7 @@ func (e ErrorCode) ExtendedCode() ExtendedErrorCode {
|
||||
|
||||
// Error implements the error interface.
|
||||
func (e ExtendedErrorCode) Error() string {
|
||||
return util.ErrorCodeString(uint32(e))
|
||||
return sqlite3_wrap.ErrorCodeString(e)
|
||||
}
|
||||
|
||||
// Is tests whether this error matches a given [ErrorCode].
|
||||
@@ -150,14 +172,10 @@ func errorCode(err error, def ErrorCode) (msg string, code res_t) {
|
||||
return code.msg, res_t(code.code)
|
||||
}
|
||||
|
||||
var ecode ErrorCode
|
||||
var xcode xErrorCode
|
||||
switch {
|
||||
case errors.As(err, &xcode):
|
||||
if errors.As(err, &xcode) {
|
||||
code = res_t(xcode)
|
||||
case errors.As(err, &ecode):
|
||||
code = res_t(ecode)
|
||||
default:
|
||||
} else {
|
||||
code = res_t(def)
|
||||
}
|
||||
return err.Error(), code
|
||||
|
||||
+18
-17
@@ -6,12 +6,13 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/ncruces/go-sqlite3/internal/util"
|
||||
"github.com/hanzoai/sqlite3/internal/errutil"
|
||||
"github.com/hanzoai/sqlite3/internal/sqlite3_wrap"
|
||||
)
|
||||
|
||||
func Test_assertErr(t *testing.T) {
|
||||
err := util.AssertErr()
|
||||
if s := err.Error(); !strings.HasPrefix(s, "sqlite3: assertion failed") || !strings.HasSuffix(s, "error_test.go:13)") {
|
||||
err := errutil.AssertErr()
|
||||
if s := err.Error(); !strings.HasPrefix(s, "sqlite3: assertion failed") || !strings.HasSuffix(s, "error_test.go:14)") {
|
||||
t.Errorf("got %q", s)
|
||||
}
|
||||
}
|
||||
@@ -127,7 +128,7 @@ func TestError_Timeout(t *testing.T) {
|
||||
func Test_ErrorCode_Error(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
db, err := Open(":memory:")
|
||||
db, err := OpenContext(testContext(t), ":memory:")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -136,8 +137,8 @@ func Test_ErrorCode_Error(t *testing.T) {
|
||||
// Test all error codes.
|
||||
for i := 0; i == int(ErrorCode(i)); i++ {
|
||||
want := "sqlite3: "
|
||||
ptr := ptr_t(db.call("sqlite3_errstr", stk_t(i)))
|
||||
want += util.ReadString(db.mod, ptr, _MAX_NAME)
|
||||
ptr := ptr_t(db.wrp.Xsqlite3_errstr(int32(i)))
|
||||
want += db.wrp.ReadString(ptr, _MAX_NAME)
|
||||
|
||||
got := ErrorCode(i).Error()
|
||||
if got != want {
|
||||
@@ -149,7 +150,7 @@ func Test_ErrorCode_Error(t *testing.T) {
|
||||
func Test_ExtendedErrorCode_Error(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
db, err := Open(":memory:")
|
||||
db, err := OpenContext(testContext(t), ":memory:")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -158,8 +159,8 @@ func Test_ExtendedErrorCode_Error(t *testing.T) {
|
||||
// Test all extended error codes.
|
||||
for i := 0; i == int(xErrorCode(i)); i++ {
|
||||
want := "sqlite3: "
|
||||
ptr := ptr_t(db.call("sqlite3_errstr", stk_t(i)))
|
||||
want += util.ReadString(db.mod, ptr, _MAX_NAME)
|
||||
ptr := ptr_t(db.wrp.Xsqlite3_errstr(int32(i)))
|
||||
want += db.wrp.ReadString(ptr, _MAX_NAME)
|
||||
|
||||
got := xErrorCode(i).Error()
|
||||
if got != want {
|
||||
@@ -175,14 +176,14 @@ func Test_errorCode(t *testing.T) {
|
||||
wantCode res_t
|
||||
}{
|
||||
{nil, "", _OK},
|
||||
{ERROR, "", util.ERROR},
|
||||
{IOERR, "", util.IOERR},
|
||||
{IOERR_READ, "", util.IOERR_READ},
|
||||
{&Error{code: util.ERROR}, "", util.ERROR},
|
||||
{fmt.Errorf("%w", ERROR), ERROR.Error(), util.ERROR},
|
||||
{fmt.Errorf("%w", IOERR), IOERR.Error(), util.IOERR},
|
||||
{fmt.Errorf("%w", IOERR_READ), IOERR_READ.Error(), util.IOERR_READ},
|
||||
{fmt.Errorf("error"), "error", util.ERROR},
|
||||
{ERROR, "", sqlite3_wrap.ERROR},
|
||||
{IOERR, "", sqlite3_wrap.IOERR},
|
||||
{IOERR_READ, "", sqlite3_wrap.IOERR_READ},
|
||||
{&Error{code: sqlite3_wrap.ERROR}, "", sqlite3_wrap.ERROR},
|
||||
{fmt.Errorf("%w", ERROR), ERROR.Error(), sqlite3_wrap.ERROR},
|
||||
{fmt.Errorf("%w", IOERR), IOERR.Error(), sqlite3_wrap.IOERR},
|
||||
{fmt.Errorf("%w", IOERR_READ), IOERR_READ.Error(), sqlite3_wrap.IOERR_READ},
|
||||
{fmt.Errorf("error"), "error", sqlite3_wrap.ERROR},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run("", func(t *testing.T) {
|
||||
|
||||
+1
-2
@@ -4,8 +4,7 @@ import (
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"github.com/ncruces/go-sqlite3"
|
||||
_ "github.com/ncruces/go-sqlite3/embed"
|
||||
"github.com/hanzoai/sqlite3"
|
||||
)
|
||||
|
||||
const memory = ":memory:"
|
||||
|
||||
@@ -0,0 +1,138 @@
|
||||
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))
|
||||
}
|
||||
+13
-3
@@ -17,16 +17,24 @@ 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 functions.
|
||||
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.
|
||||
- [`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)
|
||||
@@ -35,14 +43,16 @@ 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.
|
||||
|
||||
### Pakages
|
||||
### Packages
|
||||
|
||||
These packages may also be useful to work with SQLite:
|
||||
|
||||
- [`github.com/ncruces/decimal`](https://pkg.go.dev/github.com/ncruces/decimal)
|
||||
decimal arithmetic.
|
||||
- [`github.com/ncruces/julianday`](https://pkg.go.dev/github.com/ncruces/julianday)
|
||||
Julian day math.
|
||||
Julian day math.
|
||||
|
||||
+30
-12
@@ -7,8 +7,8 @@ import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
|
||||
"github.com/ncruces/go-sqlite3"
|
||||
"github.com/ncruces/go-sqlite3/internal/util"
|
||||
"github.com/hanzoai/sqlite3"
|
||||
"github.com/hanzoai/sqlite3/internal/util"
|
||||
)
|
||||
|
||||
// Register registers the array single-argument, table-valued SQL function.
|
||||
@@ -45,12 +45,14 @@ func (array) Open() (sqlite3.VTabCursor, error) {
|
||||
}
|
||||
|
||||
type cursor struct {
|
||||
array reflect.Value
|
||||
rowID int
|
||||
value reflect.Value
|
||||
slice any
|
||||
rowID int
|
||||
length int
|
||||
}
|
||||
|
||||
func (c *cursor) EOF() bool {
|
||||
return c.rowID >= c.array.Len()
|
||||
return c.rowID >= c.length
|
||||
}
|
||||
|
||||
func (c *cursor) Next() error {
|
||||
@@ -59,7 +61,8 @@ func (c *cursor) Next() error {
|
||||
}
|
||||
|
||||
func (c *cursor) RowID() (int64, error) {
|
||||
return int64(c.rowID), nil
|
||||
// One-based RowID for consistency with carray and other tables.
|
||||
return int64(c.rowID) + 1, nil
|
||||
}
|
||||
|
||||
func (c *cursor) Column(ctx sqlite3.Context, n int) error {
|
||||
@@ -67,7 +70,22 @@ func (c *cursor) Column(ctx sqlite3.Context, n int) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
v := c.array.Index(c.rowID)
|
||||
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)
|
||||
k := v.Kind()
|
||||
|
||||
if k == reflect.Interface {
|
||||
@@ -109,14 +127,14 @@ func (c *cursor) Column(ctx sqlite3.Context, n int) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *cursor) Filter(idxNum int, idxStr string, arg ...sqlite3.Value) error {
|
||||
array := reflect.ValueOf(arg[0].Pointer())
|
||||
array, err := indexable(array)
|
||||
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)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c.array = array
|
||||
c.length = c.value.Len()
|
||||
c.rowID = 0
|
||||
return nil
|
||||
}
|
||||
|
||||
+18
-11
@@ -1,22 +1,27 @@
|
||||
package array_test
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"math"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"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"
|
||||
"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"
|
||||
)
|
||||
|
||||
func Example_driver() {
|
||||
db, err := driver.Open("file:/test.db?vfs=memdb", array.Register)
|
||||
db, err := driver.Open("file:/test.db?vfs=memdb", func(c *sqlite3.Conn) error {
|
||||
return errors.Join(
|
||||
array.Register(c),
|
||||
rtree.Register(c))
|
||||
})
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
@@ -52,6 +57,7 @@ func Example_driver() {
|
||||
|
||||
func Example() {
|
||||
sqlite3.AutoExtension(array.Register)
|
||||
sqlite3.AutoExtension(rtree.Register)
|
||||
|
||||
db, err := sqlite3.Open(":memory:")
|
||||
if err != nil {
|
||||
@@ -88,15 +94,16 @@ func Example() {
|
||||
|
||||
func Test_cursor_Column(t *testing.T) {
|
||||
t.Parallel()
|
||||
tmp := memdb.TestDB(t)
|
||||
dsn := memdb.TestDB(t)
|
||||
|
||||
db, err := driver.Open(tmp, array.Register)
|
||||
ctx := testcfg.Context(t)
|
||||
db, err := driver.Open(dsn, array.Register)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
rows, err := db.Query(`
|
||||
rows, err := db.QueryContext(ctx, `
|
||||
SELECT rowid, value FROM array(?)`,
|
||||
sqlite3.Pointer(&[...]any{nil, true, 1, uint(2), math.Pi, "text", []byte{1, 2, 3}}))
|
||||
if err != nil {
|
||||
@@ -129,7 +136,7 @@ func Test_cursor_Column(t *testing.T) {
|
||||
func Test_array_errors(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
db, err := sqlite3.Open(":memory:")
|
||||
db, err := sqlite3.OpenContext(testcfg.Context(t), ":memory:")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
+3
-3
@@ -5,8 +5,8 @@ import (
|
||||
"errors"
|
||||
"io"
|
||||
|
||||
"github.com/ncruces/go-sqlite3"
|
||||
"github.com/ncruces/go-sqlite3/internal/util"
|
||||
"github.com/hanzoai/sqlite3"
|
||||
"github.com/hanzoai/sqlite3/internal/errutil"
|
||||
)
|
||||
|
||||
// Register registers the SQL functions:
|
||||
@@ -111,7 +111,7 @@ func writeblob(ctx sqlite3.Context, arg ...sqlite3.Value) {
|
||||
|
||||
func openblob(ctx sqlite3.Context, arg ...sqlite3.Value) {
|
||||
if len(arg) < 6 {
|
||||
ctx.ResultError(util.ErrorString("openblob: wrong number of arguments"))
|
||||
ctx.ResultError(errutil.ErrorString("openblob: wrong number of arguments"))
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
+17
-20
@@ -9,13 +9,12 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"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"
|
||||
"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"
|
||||
)
|
||||
|
||||
func Example() {
|
||||
@@ -76,7 +75,7 @@ func TestMain(m *testing.M) {
|
||||
func Test_readblob(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
db, err := sqlite3.Open(":memory:")
|
||||
db, err := sqlite3.OpenContext(testcfg.Context(t), ":memory:")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -144,18 +143,16 @@ func Test_readblob(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
if stmt.Step() {
|
||||
got := stmt.ColumnText(0)
|
||||
if got != tt.want1 {
|
||||
t.Errorf("got %q", got)
|
||||
}
|
||||
if !stmt.Step() {
|
||||
t.Fatal(stmt.Err())
|
||||
} else if got := stmt.ColumnText(0); got != tt.want1 {
|
||||
t.Errorf("got %q", got)
|
||||
}
|
||||
|
||||
if stmt.Step() {
|
||||
got := stmt.ColumnText(0)
|
||||
if got != tt.want2 {
|
||||
t.Errorf("got %q", got)
|
||||
}
|
||||
if !stmt.Step() {
|
||||
t.Fatal(stmt.Err())
|
||||
} else if got := stmt.ColumnText(0); got != tt.want2 {
|
||||
t.Errorf("got %q", got)
|
||||
}
|
||||
|
||||
err = stmt.Err()
|
||||
@@ -169,7 +166,7 @@ func Test_readblob(t *testing.T) {
|
||||
func Test_writeblob(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
db, err := sqlite3.Open(":memory:")
|
||||
db, err := sqlite3.OpenContext(testcfg.Context(t), ":memory:")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -224,7 +221,7 @@ func Test_writeblob(t *testing.T) {
|
||||
func Test_openblob(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
db, err := sqlite3.Open(":memory:")
|
||||
db, err := sqlite3.OpenContext(testcfg.Context(t), ":memory:")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
+22
-21
@@ -14,8 +14,9 @@ import (
|
||||
|
||||
"github.com/dchest/siphash"
|
||||
|
||||
"github.com/ncruces/go-sqlite3"
|
||||
"github.com/ncruces/go-sqlite3/internal/util"
|
||||
"github.com/hanzoai/sqlite3"
|
||||
"github.com/hanzoai/sqlite3/internal/errutil"
|
||||
"github.com/hanzoai/sqlite3/util/sql3util"
|
||||
)
|
||||
|
||||
// Register registers the bloom_filter virtual table:
|
||||
@@ -34,6 +35,8 @@ type bloom struct {
|
||||
hashes int
|
||||
}
|
||||
|
||||
const vtab = `CREATE TABLE x(present, word TEXT HIDDEN NOT NULL PRIMARY KEY) WITHOUT ROWID`
|
||||
|
||||
func create(db *sqlite3.Conn, _, schema, table string, arg ...string) (_ *bloom, err error) {
|
||||
b := bloom{
|
||||
db: db,
|
||||
@@ -48,19 +51,17 @@ func create(db *sqlite3.Conn, _, schema, table string, arg ...string) (_ *bloom,
|
||||
return nil, err
|
||||
}
|
||||
if nelem <= 0 {
|
||||
return nil, util.ErrorString("bloom: number of elements in filter must be positive")
|
||||
return nil, errutil.ErrorString("bloom: number of elements in filter must be positive")
|
||||
}
|
||||
} else {
|
||||
nelem = 100
|
||||
}
|
||||
|
||||
if len(arg) > 1 {
|
||||
b.prob, err = strconv.ParseFloat(arg[1], 64)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if b.prob <= 0 || b.prob >= 1 {
|
||||
return nil, util.ErrorString("bloom: probability must be in the range (0,1)")
|
||||
var ok bool
|
||||
b.prob, ok = sql3util.ParseFloat(arg[1])
|
||||
if !ok || b.prob <= 0 || b.prob >= 1 {
|
||||
return nil, errutil.ErrorString("bloom: probability must be in the range (0,1)")
|
||||
}
|
||||
} else {
|
||||
b.prob = 0.01
|
||||
@@ -72,7 +73,7 @@ func create(db *sqlite3.Conn, _, schema, table string, arg ...string) (_ *bloom,
|
||||
return nil, err
|
||||
}
|
||||
if b.hashes <= 0 {
|
||||
return nil, util.ErrorString("bloom: number of hash functions must be positive")
|
||||
return nil, errutil.ErrorString("bloom: number of hash functions must be positive")
|
||||
}
|
||||
} else {
|
||||
b.hashes = max(1, numHashes(b.prob))
|
||||
@@ -80,8 +81,7 @@ func create(db *sqlite3.Conn, _, schema, table string, arg ...string) (_ *bloom,
|
||||
|
||||
b.bytes = numBytes(nelem, b.prob)
|
||||
|
||||
err = db.DeclareVTab(
|
||||
`CREATE TABLE x(present, word HIDDEN NOT NULL PRIMARY KEY) WITHOUT ROWID`)
|
||||
err = db.DeclareVTab(vtab)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -115,15 +115,15 @@ func connect(db *sqlite3.Conn, _, schema, table string, arg ...string) (_ *bloom
|
||||
storage: table + "_storage",
|
||||
}
|
||||
|
||||
err = db.DeclareVTab(
|
||||
`CREATE TABLE x(present, word HIDDEN NOT NULL PRIMARY KEY) WITHOUT ROWID`)
|
||||
err = db.DeclareVTab(vtab)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
load, _, err := db.Prepare(fmt.Sprintf(
|
||||
load, _, err := db.PrepareFlags(fmt.Sprintf(
|
||||
`SELECT m/8, p, k FROM %s.%s WHERE rowid = 1`,
|
||||
sqlite3.QuoteIdentifier(b.schema), sqlite3.QuoteIdentifier(b.storage)))
|
||||
sqlite3.QuoteIdentifier(b.schema), sqlite3.QuoteIdentifier(b.storage)),
|
||||
sqlite3.PREPARE_DONT_LOG)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -166,15 +166,16 @@ func (t *bloom) ShadowTables() {
|
||||
}
|
||||
|
||||
func (t *bloom) Integrity(schema, table string, flags int) error {
|
||||
load, _, err := t.db.Prepare(fmt.Sprintf(
|
||||
load, _, err := t.db.PrepareFlags(fmt.Sprintf(
|
||||
`SELECT typeof(data), length(data), p, n, m, k FROM %s.%s WHERE rowid = 1`,
|
||||
sqlite3.QuoteIdentifier(t.schema), sqlite3.QuoteIdentifier(t.storage)))
|
||||
sqlite3.QuoteIdentifier(t.schema), sqlite3.QuoteIdentifier(t.storage)),
|
||||
sqlite3.PREPARE_DONT_LOG)
|
||||
if err != nil {
|
||||
return fmt.Errorf("bloom: %v", err) // can't wrap!
|
||||
}
|
||||
defer load.Close()
|
||||
|
||||
err = util.ErrorString("bloom: invalid parameters")
|
||||
err = errutil.ErrorString("bloom: invalid parameters")
|
||||
if !load.Step() {
|
||||
return err
|
||||
}
|
||||
@@ -216,9 +217,9 @@ func (b *bloom) BestIndex(idx *sqlite3.IndexInfo) error {
|
||||
func (b *bloom) Update(arg ...sqlite3.Value) (rowid int64, err error) {
|
||||
if arg[0].Type() != sqlite3.NULL {
|
||||
if len(arg) == 1 {
|
||||
return 0, util.ErrorString("bloom: elements cannot be deleted")
|
||||
return 0, errutil.ErrorString("bloom: elements cannot be deleted")
|
||||
}
|
||||
return 0, util.ErrorString("bloom: elements cannot be updated")
|
||||
return 0, errutil.ErrorString("bloom: elements cannot be updated")
|
||||
}
|
||||
|
||||
if arg[2].NoChange() {
|
||||
|
||||
@@ -6,10 +6,9 @@ import (
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/ncruces/go-sqlite3"
|
||||
_ "github.com/ncruces/go-sqlite3/embed"
|
||||
"github.com/ncruces/go-sqlite3/ext/bloom"
|
||||
_ "github.com/ncruces/go-sqlite3/internal/testcfg"
|
||||
"github.com/hanzoai/sqlite3"
|
||||
"github.com/hanzoai/sqlite3/ext/bloom"
|
||||
"github.com/hanzoai/sqlite3/internal/testcfg"
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
@@ -20,7 +19,7 @@ func TestMain(m *testing.M) {
|
||||
func TestRegister(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
db, err := sqlite3.Open(":memory:")
|
||||
db, err := sqlite3.OpenContext(testcfg.Context(t), ":memory:")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -102,7 +101,7 @@ func Test_compatible(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
db, err := sqlite3.Open("file:" + filepath.ToSlash(tmp) + "?nolock=1")
|
||||
db, err := sqlite3.OpenContext(testcfg.Context(t), "file:"+filepath.ToSlash(tmp)+"?nolock=1")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -158,7 +157,7 @@ func Test_compatible(t *testing.T) {
|
||||
func Test_errors(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
db, err := sqlite3.Open(":memory:")
|
||||
db, err := sqlite3.OpenContext(testcfg.Context(t), ":memory:")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
@@ -10,9 +10,9 @@ import (
|
||||
"fmt"
|
||||
"math"
|
||||
|
||||
"github.com/ncruces/go-sqlite3"
|
||||
"github.com/ncruces/go-sqlite3/internal/util"
|
||||
"github.com/ncruces/go-sqlite3/util/sql3util"
|
||||
"github.com/hanzoai/sqlite3"
|
||||
"github.com/hanzoai/sqlite3/internal/util"
|
||||
"github.com/hanzoai/sqlite3/util/sql3util"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -56,7 +56,7 @@ func Register(db *sqlite3.Conn) error {
|
||||
done.Add(key)
|
||||
}
|
||||
|
||||
err := db.DeclareVTab(`CREATE TABLE x(id,depth,root HIDDEN,tablename HIDDEN,idcolumn HIDDEN,parentcolumn HIDDEN)`)
|
||||
err := db.DeclareVTab(`CREATE TABLE x(id INT,depth INT,root HIDDEN,tablename TEXT HIDDEN,idcolumn TEXT HIDDEN,parentcolumn TEXT HIDDEN)`)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -154,6 +154,7 @@ func (c *closure) BestIndex(idx *sqlite3.IndexInfo) error {
|
||||
return sqlite3.CONSTRAINT
|
||||
}
|
||||
|
||||
idx.IdxFlags = sqlite3.INDEX_SCAN_HEX
|
||||
idx.EstimatedCost = cost
|
||||
idx.IdxNum = plan
|
||||
return nil
|
||||
@@ -201,7 +202,7 @@ func (c *cursor) Filter(idxNum int, idxStr string, arg ...sqlite3.Value) error {
|
||||
sqlite3.QuoteIdentifier(column),
|
||||
sqlite3.QuoteIdentifier(parent),
|
||||
)
|
||||
stmt, _, err := c.db.Prepare(sql)
|
||||
stmt, _, err := c.db.PrepareFlags(sql, sqlite3.PREPARE_DONT_LOG)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -7,10 +7,9 @@ import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/ncruces/go-sqlite3"
|
||||
_ "github.com/ncruces/go-sqlite3/embed"
|
||||
"github.com/ncruces/go-sqlite3/ext/closure"
|
||||
_ "github.com/ncruces/go-sqlite3/internal/testcfg"
|
||||
"github.com/hanzoai/sqlite3"
|
||||
"github.com/hanzoai/sqlite3/ext/closure"
|
||||
"github.com/hanzoai/sqlite3/internal/testcfg"
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
@@ -89,7 +88,7 @@ func Example() {
|
||||
func TestRegister(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
db, err := sqlite3.Open(":memory:")
|
||||
db, err := sqlite3.OpenContext(testcfg.Context(t), ":memory:")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -157,7 +156,7 @@ func TestRegister(t *testing.T) {
|
||||
func Test_errors(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
db, err := sqlite3.Open(":memory:")
|
||||
db, err := sqlite3.OpenContext(testcfg.Context(t), ":memory:")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@ import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"github.com/ncruces/go-sqlite3/util/sql3util"
|
||||
"github.com/hanzoai/sqlite3/util/sql3util"
|
||||
)
|
||||
|
||||
func uintArg(key, val string) (int, error) {
|
||||
|
||||
+1
-1
@@ -3,7 +3,7 @@ package csv
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/ncruces/go-sqlite3/util/sql3util"
|
||||
"github.com/hanzoai/sqlite3/util/sql3util"
|
||||
)
|
||||
|
||||
func Test_uintArg(t *testing.T) {
|
||||
|
||||
+22
-27
@@ -15,10 +15,11 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"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"
|
||||
"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"
|
||||
)
|
||||
|
||||
// Register registers the CSV virtual table.
|
||||
@@ -73,7 +74,7 @@ func RegisterFS(db *sqlite3.Conn, fsys fs.FS) error {
|
||||
}
|
||||
|
||||
if (filename == "") == (data == "") {
|
||||
return nil, util.ErrorString(`csv: must specify either "filename" or "data" but not both`)
|
||||
return nil, errutil.ErrorString(`csv: must specify either "filename" or "data" but not both`)
|
||||
}
|
||||
|
||||
t := &table{
|
||||
@@ -85,7 +86,8 @@ func RegisterFS(db *sqlite3.Conn, fsys fs.FS) error {
|
||||
header: header,
|
||||
}
|
||||
|
||||
if schema == "" {
|
||||
hadSchema := schema != ""
|
||||
if !hadSchema {
|
||||
var row []string
|
||||
if header || columns < 0 {
|
||||
csv, c, err := t.newReader()
|
||||
@@ -99,17 +101,14 @@ func RegisterFS(db *sqlite3.Conn, fsys fs.FS) error {
|
||||
}
|
||||
}
|
||||
schema = getSchema(header, columns, row)
|
||||
} else {
|
||||
t.typs, err = getColumnAffinities(schema)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
err = db.DeclareVTab(schema)
|
||||
if err == nil {
|
||||
err = db.VTabConfig(sqlite3.VTAB_DIRECTONLY)
|
||||
}
|
||||
if err == nil && hadSchema {
|
||||
t.typs, err = getColumnAffinities(schema)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -123,7 +122,7 @@ type table struct {
|
||||
fsys fs.FS
|
||||
name string
|
||||
data string
|
||||
typs []affinity
|
||||
typs []sql3util.Affinity
|
||||
comma rune
|
||||
comment rune
|
||||
header bool
|
||||
@@ -242,31 +241,27 @@ func (c *cursor) RowID() (int64, error) {
|
||||
|
||||
func (c *cursor) Column(ctx sqlite3.Context, col int) error {
|
||||
if col < len(c.row) {
|
||||
typ := text
|
||||
typ := sql3util.TEXT
|
||||
if col < len(c.table.typs) {
|
||||
typ = c.table.typs[col]
|
||||
}
|
||||
|
||||
txt := c.row[col]
|
||||
if txt == "" && typ != text {
|
||||
if txt == "" && typ != sql3util.TEXT {
|
||||
return nil
|
||||
}
|
||||
|
||||
switch typ {
|
||||
case numeric, integer:
|
||||
if strings.TrimLeft(txt, "+-0123456789") == "" {
|
||||
if i, err := strconv.ParseInt(txt, 10, 64); err == nil {
|
||||
ctx.ResultInt64(i)
|
||||
return nil
|
||||
}
|
||||
case sql3util.NUMERIC, sql3util.INTEGER:
|
||||
if i, err := strconv.ParseInt(txt, 10, 64); err == nil {
|
||||
ctx.ResultInt64(i)
|
||||
return nil
|
||||
}
|
||||
fallthrough
|
||||
case real:
|
||||
if strings.TrimLeft(txt, "+-.0123456789Ee") == "" {
|
||||
if f, err := strconv.ParseFloat(txt, 64); err == nil {
|
||||
ctx.ResultFloat(f)
|
||||
return nil
|
||||
}
|
||||
case sql3util.REAL:
|
||||
if f, ok := sql3util.ParseFloat(txt); ok {
|
||||
ctx.ResultFloat(f)
|
||||
return nil
|
||||
}
|
||||
fallthrough
|
||||
default:
|
||||
|
||||
+19
-19
@@ -6,10 +6,9 @@ import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/ncruces/go-sqlite3"
|
||||
_ "github.com/ncruces/go-sqlite3/embed"
|
||||
"github.com/ncruces/go-sqlite3/ext/csv"
|
||||
_ "github.com/ncruces/go-sqlite3/internal/testcfg"
|
||||
"github.com/hanzoai/sqlite3"
|
||||
"github.com/hanzoai/sqlite3/ext/csv"
|
||||
"github.com/hanzoai/sqlite3/internal/testcfg"
|
||||
)
|
||||
|
||||
func Example() {
|
||||
@@ -63,7 +62,7 @@ func TestMain(m *testing.M) {
|
||||
func TestRegister(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
db, err := sqlite3.Open(":memory:")
|
||||
db, err := sqlite3.OpenContext(testcfg.Context(t), ":memory:")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -125,7 +124,7 @@ Robert "Griesemer" "gri"`
|
||||
func TestAffinity(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
db, err := sqlite3.Open(":memory:")
|
||||
db, err := sqlite3.OpenContext(testcfg.Context(t), ":memory:")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -147,27 +146,28 @@ func TestAffinity(t *testing.T) {
|
||||
}
|
||||
defer stmt.Close()
|
||||
|
||||
if stmt.Step() {
|
||||
if got := stmt.ColumnText(0); got != "1" {
|
||||
t.Errorf("got %q want 1", got)
|
||||
}
|
||||
if !stmt.Step() {
|
||||
t.Fatal(stmt.Err())
|
||||
} else if got := stmt.ColumnText(0); got != "1" {
|
||||
t.Errorf("got %q want 1", got)
|
||||
}
|
||||
if stmt.Step() {
|
||||
if got := stmt.ColumnText(0); got != "0.1" {
|
||||
t.Errorf("got %q want 0.1", got)
|
||||
}
|
||||
if !stmt.Step() {
|
||||
t.Fatal(stmt.Err())
|
||||
} else if got := stmt.ColumnText(0); got != "0.1" {
|
||||
t.Errorf("got %q want 0.1", got)
|
||||
}
|
||||
if stmt.Step() {
|
||||
if got := stmt.ColumnText(0); got != "e" {
|
||||
t.Errorf("got %q want e", got)
|
||||
}
|
||||
|
||||
if !stmt.Step() {
|
||||
t.Fatal(stmt.Err())
|
||||
} else if got := stmt.ColumnText(0); got != "e" {
|
||||
t.Errorf("got %q want e", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRegister_errors(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
db, err := sqlite3.Open(":memory:")
|
||||
db, err := sqlite3.OpenContext(testcfg.Context(t), ":memory:")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
+14
-14
@@ -4,36 +4,36 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/ncruces/go-sqlite3"
|
||||
"github.com/hanzoai/sqlite3"
|
||||
)
|
||||
|
||||
func getSchema(header bool, columns int, row []string) string {
|
||||
var sep string
|
||||
var str strings.Builder
|
||||
str.WriteString("CREATE TABLE x(")
|
||||
var buf strings.Builder
|
||||
buf.WriteString("CREATE TABLE x(")
|
||||
|
||||
if 0 <= columns && columns < len(row) {
|
||||
row = row[:columns]
|
||||
}
|
||||
for i, f := range row {
|
||||
str.WriteString(sep)
|
||||
buf.WriteString(sep)
|
||||
if header && f != "" {
|
||||
str.WriteString(sqlite3.QuoteIdentifier(f))
|
||||
buf.WriteString(sqlite3.QuoteIdentifier(f))
|
||||
} else {
|
||||
str.WriteString("c")
|
||||
str.WriteString(strconv.Itoa(i + 1))
|
||||
buf.WriteString("c")
|
||||
buf.WriteString(strconv.Itoa(i + 1))
|
||||
}
|
||||
str.WriteString(" TEXT")
|
||||
buf.WriteString(" TEXT")
|
||||
sep = ","
|
||||
}
|
||||
for i := len(row); i < columns; i++ {
|
||||
str.WriteString(sep)
|
||||
str.WriteString("c")
|
||||
str.WriteString(strconv.Itoa(i + 1))
|
||||
str.WriteString(" TEXT")
|
||||
buf.WriteString(sep)
|
||||
buf.WriteString("c")
|
||||
buf.WriteString(strconv.Itoa(i + 1))
|
||||
buf.WriteString(" TEXT")
|
||||
sep = ","
|
||||
}
|
||||
str.WriteByte(')')
|
||||
buf.WriteByte(')')
|
||||
|
||||
return str.String()
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
+4
-39
@@ -1,52 +1,17 @@
|
||||
package csv
|
||||
|
||||
import (
|
||||
"strings"
|
||||
import "github.com/hanzoai/sqlite3/util/sql3util"
|
||||
|
||||
"github.com/ncruces/go-sqlite3/util/sql3util"
|
||||
)
|
||||
|
||||
type affinity byte
|
||||
|
||||
const (
|
||||
blob affinity = 0
|
||||
text affinity = 1
|
||||
numeric affinity = 2
|
||||
integer affinity = 3
|
||||
real affinity = 4
|
||||
)
|
||||
|
||||
func getColumnAffinities(schema string) ([]affinity, error) {
|
||||
func getColumnAffinities(schema string) ([]sql3util.Affinity, error) {
|
||||
tab, err := sql3util.ParseTable(schema)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
columns := tab.Columns
|
||||
types := make([]affinity, len(columns))
|
||||
types := make([]sql3util.Affinity, len(columns))
|
||||
for i, col := range columns {
|
||||
types[i] = getAffinity(col.Type)
|
||||
types[i] = sql3util.GetAffinity(col.Type)
|
||||
}
|
||||
return types, nil
|
||||
}
|
||||
|
||||
func getAffinity(declType string) affinity {
|
||||
// https://sqlite.org/datatype3.html#determination_of_column_affinity
|
||||
if declType == "" {
|
||||
return blob
|
||||
}
|
||||
name := strings.ToUpper(declType)
|
||||
if strings.Contains(name, "INT") {
|
||||
return integer
|
||||
}
|
||||
if strings.Contains(name, "CHAR") || strings.Contains(name, "CLOB") || strings.Contains(name, "TEXT") {
|
||||
return text
|
||||
}
|
||||
if strings.Contains(name, "BLOB") {
|
||||
return blob
|
||||
}
|
||||
if strings.Contains(name, "REAL") || strings.Contains(name, "FLOA") || strings.Contains(name, "DOUB") {
|
||||
return real
|
||||
}
|
||||
return numeric
|
||||
}
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
package csv
|
||||
|
||||
import "testing"
|
||||
|
||||
func Test_getAffinity(t *testing.T) {
|
||||
tests := []struct {
|
||||
decl string
|
||||
want affinity
|
||||
}{
|
||||
{"", blob},
|
||||
{"INTEGER", integer},
|
||||
{"TINYINT", integer},
|
||||
{"TEXT", text},
|
||||
{"CHAR", text},
|
||||
{"CLOB", text},
|
||||
{"BLOB", blob},
|
||||
{"REAL", real},
|
||||
{"FLOAT", real},
|
||||
{"DOUBLE", real},
|
||||
{"NUMERIC", numeric},
|
||||
{"DECIMAL", numeric},
|
||||
{"BOOLEAN", numeric},
|
||||
{"DATETIME", numeric},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.decl, func(t *testing.T) {
|
||||
if got := getAffinity(tt.decl); got != tt.want {
|
||||
t.Errorf("getAffinity() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -9,7 +9,7 @@ import (
|
||||
"io/fs"
|
||||
"os"
|
||||
|
||||
"github.com/ncruces/go-sqlite3"
|
||||
"github.com/hanzoai/sqlite3"
|
||||
)
|
||||
|
||||
// Register registers SQL functions readfile, writefile, lsmode,
|
||||
@@ -18,7 +18,7 @@ func Register(db *sqlite3.Conn) error {
|
||||
return RegisterFS(db, nil)
|
||||
}
|
||||
|
||||
// Register registers SQL functions readfile, lsmode,
|
||||
// RegisterFS registers SQL functions readfile, lsmode,
|
||||
// and the table-valued function fsdir;
|
||||
// fsys will be used to read files and list directories.
|
||||
func RegisterFS(db *sqlite3.Conn, fsys fs.FS) error {
|
||||
@@ -30,7 +30,7 @@ func RegisterFS(db *sqlite3.Conn, fsys fs.FS) error {
|
||||
db.CreateFunction("readfile", 1, sqlite3.DIRECTONLY, readfile(fsys)),
|
||||
db.CreateFunction("lsmode", 1, sqlite3.DETERMINISTIC, lsmode),
|
||||
sqlite3.CreateModule(db, "fsdir", nil, func(db *sqlite3.Conn, _, _, _ string, _ ...string) (fsdir, error) {
|
||||
err := db.DeclareVTab(`CREATE TABLE x(name,mode,mtime TIMESTAMP,data,path HIDDEN,dir HIDDEN)`)
|
||||
err := db.DeclareVTab(`CREATE TABLE x(name TEXT,mode INT,mtime TIMESTAMP,data BLOB,level INT,path HIDDEN,dir HIDDEN)`)
|
||||
if err == nil {
|
||||
err = db.VTabConfig(sqlite3.VTAB_DIRECTONLY)
|
||||
}
|
||||
|
||||
+13
-12
@@ -7,19 +7,19 @@ import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"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"
|
||||
"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"
|
||||
)
|
||||
|
||||
func Test_lsmode(t *testing.T) {
|
||||
t.Parallel()
|
||||
tmp := memdb.TestDB(t)
|
||||
dsn := memdb.TestDB(t)
|
||||
|
||||
db, err := driver.Open(tmp, fileio.Register)
|
||||
ctx := testcfg.Context(t)
|
||||
db, err := driver.Open(dsn, fileio.Register)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -36,7 +36,7 @@ func Test_lsmode(t *testing.T) {
|
||||
}
|
||||
|
||||
var mode string
|
||||
err = db.QueryRow(`SELECT lsmode(?)`, s.Mode()).Scan(&mode)
|
||||
err = db.QueryRowContext(ctx, `SELECT lsmode(?)`, s.Mode()).Scan(&mode)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -53,9 +53,10 @@ func Test_readfile(t *testing.T) {
|
||||
|
||||
for _, fsys := range []fs.FS{nil, os.DirFS(".")} {
|
||||
t.Run("", func(t *testing.T) {
|
||||
tmp := memdb.TestDB(t)
|
||||
dsn := memdb.TestDB(t)
|
||||
|
||||
db, err := driver.Open(tmp, func(c *sqlite3.Conn) error {
|
||||
ctx := testcfg.Context(t)
|
||||
db, err := driver.Open(dsn, func(c *sqlite3.Conn) error {
|
||||
fileio.RegisterFS(c, fsys)
|
||||
return nil
|
||||
})
|
||||
@@ -64,7 +65,7 @@ func Test_readfile(t *testing.T) {
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
rows, err := db.Query(`SELECT readfile('fileio_test.go')`)
|
||||
rows, err := db.QueryContext(ctx, `SELECT readfile('fileio_test.go')`)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
+96
-33
@@ -8,22 +8,27 @@ import (
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/ncruces/go-sqlite3"
|
||||
"github.com/hanzoai/sqlite3"
|
||||
)
|
||||
|
||||
const (
|
||||
_COL_NAME = 0
|
||||
_COL_MODE = 1
|
||||
_COL_TIME = 2
|
||||
_COL_DATA = 3
|
||||
_COL_ROOT = 4
|
||||
_COL_BASE = 5
|
||||
_COL_NAME = iota
|
||||
_COL_MODE
|
||||
_COL_MTIME
|
||||
_COL_DATA
|
||||
_COL_LEVEL
|
||||
_COL_ROOT
|
||||
_COL_BASE
|
||||
)
|
||||
|
||||
type fsdir struct{ fsys fs.FS }
|
||||
|
||||
func (d fsdir) BestIndex(idx *sqlite3.IndexInfo) error {
|
||||
var root, base bool
|
||||
var levelOp sqlite3.IndexConstraintOp
|
||||
var root bool
|
||||
level := -1
|
||||
base := -1
|
||||
|
||||
for i, cst := range idx.Constraint {
|
||||
switch cst.Column {
|
||||
case _COL_ROOT:
|
||||
@@ -39,20 +44,44 @@ func (d fsdir) BestIndex(idx *sqlite3.IndexInfo) error {
|
||||
if !cst.Usable || cst.Op != sqlite3.INDEX_CONSTRAINT_EQ {
|
||||
return sqlite3.CONSTRAINT
|
||||
}
|
||||
idx.ConstraintUsage[i] = sqlite3.IndexConstraintUsage{
|
||||
Omit: true,
|
||||
ArgvIndex: 2,
|
||||
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
|
||||
}
|
||||
base = true
|
||||
}
|
||||
}
|
||||
if !root {
|
||||
return sqlite3.CONSTRAINT
|
||||
}
|
||||
if base {
|
||||
idx.EstimatedCost = 10
|
||||
} else {
|
||||
idx.EstimatedCost = 100
|
||||
|
||||
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,
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -63,18 +92,20 @@ 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
|
||||
base string
|
||||
next func() (entry, bool)
|
||||
stop func()
|
||||
curr entry
|
||||
eof bool
|
||||
rowID int64
|
||||
maxLevel int
|
||||
}
|
||||
|
||||
type entry struct {
|
||||
fs.DirEntry
|
||||
err error
|
||||
path string
|
||||
err error
|
||||
path string
|
||||
level int
|
||||
}
|
||||
|
||||
func (c *cursor) Close() error {
|
||||
@@ -90,24 +121,40 @@ func (c *cursor) Filter(idxNum int, idxStr string, arg ...sqlite3.Value) error {
|
||||
}
|
||||
|
||||
root := arg[0].Text()
|
||||
if len(arg) > 1 {
|
||||
base := arg[1].Text()
|
||||
if i := idxNum & 1; i > 0 {
|
||||
base := arg[i].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) {
|
||||
walkDir := func(path string, d fs.DirEntry, err error) error {
|
||||
if yield(entry{d, err, path}) {
|
||||
return nil
|
||||
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--
|
||||
}
|
||||
return fs.SkipAll
|
||||
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
|
||||
}
|
||||
if c.fsys != nil {
|
||||
fs.WalkDir(c.fsys, root, walkDir)
|
||||
@@ -149,7 +196,7 @@ func (c *cursor) Column(ctx sqlite3.Context, n int) error {
|
||||
}
|
||||
ctx.ResultInt64(int64(i.Mode()))
|
||||
|
||||
case _COL_TIME:
|
||||
case _COL_MTIME:
|
||||
i, err := c.curr.Info()
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -178,6 +225,22 @@ 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
|
||||
}
|
||||
|
||||
+15
-11
@@ -8,12 +8,11 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"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"
|
||||
"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"
|
||||
)
|
||||
|
||||
func Test_fsdir(t *testing.T) {
|
||||
@@ -21,9 +20,10 @@ func Test_fsdir(t *testing.T) {
|
||||
|
||||
for _, fsys := range []fs.FS{nil, os.DirFS(".")} {
|
||||
t.Run("", func(t *testing.T) {
|
||||
tmp := memdb.TestDB(t)
|
||||
dsn := memdb.TestDB(t)
|
||||
|
||||
db, err := driver.Open(tmp, func(c *sqlite3.Conn) error {
|
||||
ctx := testcfg.Context(t)
|
||||
db, err := driver.Open(dsn, func(c *sqlite3.Conn) error {
|
||||
fileio.RegisterFS(c, fsys)
|
||||
return nil
|
||||
})
|
||||
@@ -32,7 +32,7 @@ func Test_fsdir(t *testing.T) {
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
rows, err := db.Query(`SELECT * FROM fsdir('.', '.')`)
|
||||
rows, err := db.QueryContext(ctx, `SELECT * FROM fsdir('.') WHERE level <= 2`)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -42,7 +42,8 @@ func Test_fsdir(t *testing.T) {
|
||||
var mode fs.FileMode
|
||||
var mtime time.Time
|
||||
var data sql.RawBytes
|
||||
err := rows.Scan(&name, &mode, sqlite3.TimeFormatUnixFrac.Scanner(&mtime), &data)
|
||||
var level int
|
||||
err := rows.Scan(&name, &mode, sqlite3.TimeFormatUnixFrac.Scanner(&mtime), &data, &level)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -57,6 +58,9 @@ func Test_fsdir(t *testing.T) {
|
||||
t.Errorf("got: %s", data[:min(64, len(data))])
|
||||
}
|
||||
}
|
||||
if level > 2 {
|
||||
t.Errorf("got: %v", level)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -65,7 +69,7 @@ func Test_fsdir(t *testing.T) {
|
||||
func Test_fsdir_errors(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
db, err := sqlite3.Open(":memory:")
|
||||
db, err := sqlite3.OpenContext(testcfg.Context(t), ":memory:")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
+4
-4
@@ -8,14 +8,14 @@ import (
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"github.com/ncruces/go-sqlite3"
|
||||
"github.com/ncruces/go-sqlite3/internal/util"
|
||||
"github.com/ncruces/go-sqlite3/util/fsutil"
|
||||
"github.com/hanzoai/sqlite3"
|
||||
"github.com/hanzoai/sqlite3/internal/errutil"
|
||||
"github.com/hanzoai/sqlite3/util/fsutil"
|
||||
)
|
||||
|
||||
func writefile(ctx sqlite3.Context, arg ...sqlite3.Value) {
|
||||
if len(arg) < 2 || len(arg) > 4 {
|
||||
ctx.ResultError(util.ErrorString("writefile: wrong number of arguments"))
|
||||
ctx.ResultError(errutil.ErrorString("writefile: wrong number of arguments"))
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
+17
-14
@@ -1,3 +1,5 @@
|
||||
//go:build !js
|
||||
|
||||
package fileio
|
||||
|
||||
import (
|
||||
@@ -7,17 +9,17 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/ncruces/go-sqlite3/driver"
|
||||
_ "github.com/ncruces/go-sqlite3/embed"
|
||||
_ "github.com/ncruces/go-sqlite3/internal/testcfg"
|
||||
"github.com/ncruces/go-sqlite3/vfs/memdb"
|
||||
"github.com/hanzoai/sqlite3/driver"
|
||||
"github.com/hanzoai/sqlite3/internal/testcfg"
|
||||
"github.com/hanzoai/sqlite3/vfs/memdb"
|
||||
)
|
||||
|
||||
func Test_writefile(t *testing.T) {
|
||||
t.Parallel()
|
||||
tmp := memdb.TestDB(t)
|
||||
dsn := memdb.TestDB(t)
|
||||
|
||||
db, err := driver.Open(tmp, Register)
|
||||
ctx := testcfg.Context(t)
|
||||
db, err := driver.Open(dsn, Register)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -30,22 +32,22 @@ func Test_writefile(t *testing.T) {
|
||||
sock := filepath.Join(dir, "sock")
|
||||
twosday := time.Date(2022, 2, 22, 22, 22, 22, 0, time.UTC)
|
||||
|
||||
_, err = db.Exec(`SELECT writefile(?, 'Hello world!')`, file)
|
||||
_, err = db.ExecContext(ctx, `SELECT writefile(?, 'Hello world!')`, file)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
_, err = db.Exec(`SELECT writefile(?, ?, ?)`, link, "test.txt", fs.ModeSymlink)
|
||||
_, err = db.ExecContext(ctx, `SELECT writefile(?, ?, ?)`, link, "test.txt", fs.ModeSymlink)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
_, err = db.Exec(`SELECT writefile(?, ?, ?, ?)`, dir, nil, 0040700, twosday.Unix())
|
||||
_, err = db.ExecContext(ctx, `SELECT writefile(?, ?, ?, ?)`, dir, nil, 0040700, twosday.Unix())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
rows, err := db.Query(`SELECT * FROM fsdir('.', ?)`, dir)
|
||||
rows, err := db.QueryContext(ctx, `SELECT * FROM fsdir('.', ?)`, dir)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -55,7 +57,8 @@ func Test_writefile(t *testing.T) {
|
||||
var mode fs.FileMode
|
||||
var mtime time.Time
|
||||
var data sql.NullString
|
||||
err := rows.Scan(&name, &mode, &mtime, &data)
|
||||
var level int
|
||||
err := rows.Scan(&name, &mode, &mtime, &data, &level)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -70,19 +73,19 @@ func Test_writefile(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
_, err = db.Exec(`SELECT writefile(?, 'Hello world!')`, nest)
|
||||
_, err = db.ExecContext(ctx, `SELECT writefile(?, 'Hello world!')`, nest)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
_, err = db.Exec(`SELECT writefile(?, ?, ?)`, sock, nil, fs.ModeSocket)
|
||||
_, err = db.ExecContext(ctx, `SELECT writefile(?, ?, ?)`, sock, nil, fs.ModeSocket)
|
||||
if err == nil {
|
||||
t.Fatal("want error")
|
||||
} else {
|
||||
t.Log(err)
|
||||
}
|
||||
|
||||
_, err = db.Exec(`SELECT writefile()`)
|
||||
_, err = db.ExecContext(ctx, `SELECT writefile()`)
|
||||
if err == nil {
|
||||
t.Fatal("want error")
|
||||
} else {
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
// 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)
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
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
@@ -3,8 +3,8 @@ package hash
|
||||
import (
|
||||
"crypto"
|
||||
|
||||
"github.com/ncruces/go-sqlite3"
|
||||
"github.com/ncruces/go-sqlite3/internal/util"
|
||||
"github.com/hanzoai/sqlite3"
|
||||
"github.com/hanzoai/sqlite3/internal/errutil"
|
||||
)
|
||||
|
||||
func blake2sFunc(ctx sqlite3.Context, arg ...sqlite3.Value) {
|
||||
@@ -25,6 +25,6 @@ func blake2bFunc(ctx sqlite3.Context, arg ...sqlite3.Value) {
|
||||
case 512:
|
||||
hashFunc(ctx, arg[0], crypto.BLAKE2b_512)
|
||||
default:
|
||||
ctx.ResultError(util.ErrorString("blake2b: size must be 256, 384, 512"))
|
||||
ctx.ResultError(errutil.ErrorString("blake2b: size must be 256, 384, 512"))
|
||||
}
|
||||
}
|
||||
|
||||
+3
-3
@@ -23,15 +23,15 @@ import (
|
||||
"crypto"
|
||||
"errors"
|
||||
|
||||
"github.com/ncruces/go-sqlite3"
|
||||
"github.com/ncruces/go-sqlite3/internal/util"
|
||||
"github.com/hanzoai/sqlite3"
|
||||
"github.com/hanzoai/sqlite3/internal/errutil"
|
||||
)
|
||||
|
||||
// Register registers cryptographic hash functions for a database connection.
|
||||
func Register(db *sqlite3.Conn) error {
|
||||
const flags = sqlite3.DETERMINISTIC | sqlite3.INNOCUOUS
|
||||
|
||||
var errs util.ErrorJoiner
|
||||
var errs errutil.ErrorJoiner
|
||||
if crypto.MD4.Available() {
|
||||
errs.Join(
|
||||
db.CreateFunction("md4", 1, flags, md4Func))
|
||||
|
||||
+12
-12
@@ -4,6 +4,7 @@ import (
|
||||
_ "crypto/md5"
|
||||
_ "crypto/sha1"
|
||||
_ "crypto/sha256"
|
||||
_ "crypto/sha3"
|
||||
_ "crypto/sha512"
|
||||
"testing"
|
||||
|
||||
@@ -11,17 +12,15 @@ import (
|
||||
_ "golang.org/x/crypto/blake2s"
|
||||
_ "golang.org/x/crypto/md4"
|
||||
_ "golang.org/x/crypto/ripemd160"
|
||||
_ "golang.org/x/crypto/sha3"
|
||||
|
||||
"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"
|
||||
"github.com/hanzoai/sqlite3/driver"
|
||||
"github.com/hanzoai/sqlite3/internal/testcfg"
|
||||
"github.com/hanzoai/sqlite3/vfs/memdb"
|
||||
)
|
||||
|
||||
func TestRegister(t *testing.T) {
|
||||
t.Parallel()
|
||||
tmp := memdb.TestDB(t)
|
||||
dsn := memdb.TestDB(t)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
@@ -55,7 +54,8 @@ func TestRegister(t *testing.T) {
|
||||
{"blake2b('', 256)", "0E5751C026E543B2E8AB2EB06099DAA1D1E5DF47778F7787FAAB45CDF12FE3A8"},
|
||||
}
|
||||
|
||||
db, err := driver.Open(tmp, Register)
|
||||
ctx := testcfg.Context(t)
|
||||
db, err := driver.Open(dsn, Register)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -65,7 +65,7 @@ func TestRegister(t *testing.T) {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
var hash string
|
||||
|
||||
err = db.QueryRow(`SELECT hex(` + tt.name + `)`).Scan(&hash)
|
||||
err = db.QueryRowContext(ctx, `SELECT hex(`+tt.name+`)`).Scan(&hash)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -76,22 +76,22 @@ func TestRegister(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
_, err = db.Exec(`SELECT sha256('', 255)`)
|
||||
_, err = db.ExecContext(ctx, `SELECT sha256('', 255)`)
|
||||
if err == nil {
|
||||
t.Error("want error")
|
||||
}
|
||||
|
||||
_, err = db.Exec(`SELECT sha512('', 255)`)
|
||||
_, err = db.ExecContext(ctx, `SELECT sha512('', 255)`)
|
||||
if err == nil {
|
||||
t.Error("want error")
|
||||
}
|
||||
|
||||
_, err = db.Exec(`SELECT sha3('', 255)`)
|
||||
_, err = db.ExecContext(ctx, `SELECT sha3('', 255)`)
|
||||
if err == nil {
|
||||
t.Error("want error")
|
||||
}
|
||||
|
||||
_, err = db.Exec(`SELECT blake2b('', 255)`)
|
||||
_, err = db.ExecContext(ctx, `SELECT blake2b('', 255)`)
|
||||
if err == nil {
|
||||
t.Error("want error")
|
||||
}
|
||||
|
||||
+4
-4
@@ -3,8 +3,8 @@ package hash
|
||||
import (
|
||||
"crypto"
|
||||
|
||||
"github.com/ncruces/go-sqlite3"
|
||||
"github.com/ncruces/go-sqlite3/internal/util"
|
||||
"github.com/hanzoai/sqlite3"
|
||||
"github.com/hanzoai/sqlite3/internal/errutil"
|
||||
)
|
||||
|
||||
func sha224Func(ctx sqlite3.Context, arg ...sqlite3.Value) {
|
||||
@@ -27,7 +27,7 @@ func sha256Func(ctx sqlite3.Context, arg ...sqlite3.Value) {
|
||||
case 256:
|
||||
hashFunc(ctx, arg[0], crypto.SHA256)
|
||||
default:
|
||||
ctx.ResultError(util.ErrorString("sha256: size must be 224, 256"))
|
||||
ctx.ResultError(errutil.ErrorString("sha256: size must be 224, 256"))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,7 +47,7 @@ func sha512Func(ctx sqlite3.Context, arg ...sqlite3.Value) {
|
||||
case 512:
|
||||
hashFunc(ctx, arg[0], crypto.SHA512)
|
||||
default:
|
||||
ctx.ResultError(util.ErrorString("sha512: size must be 224, 256, 384, 512"))
|
||||
ctx.ResultError(errutil.ErrorString("sha512: size must be 224, 256, 384, 512"))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+3
-3
@@ -3,8 +3,8 @@ package hash
|
||||
import (
|
||||
"crypto"
|
||||
|
||||
"github.com/ncruces/go-sqlite3"
|
||||
"github.com/ncruces/go-sqlite3/internal/util"
|
||||
"github.com/hanzoai/sqlite3"
|
||||
"github.com/hanzoai/sqlite3/internal/errutil"
|
||||
)
|
||||
|
||||
func sha3Func(ctx sqlite3.Context, arg ...sqlite3.Value) {
|
||||
@@ -23,6 +23,6 @@ func sha3Func(ctx sqlite3.Context, arg ...sqlite3.Value) {
|
||||
case 512:
|
||||
hashFunc(ctx, arg[0], crypto.SHA3_512)
|
||||
default:
|
||||
ctx.ResultError(util.ErrorString("sha3: size must be 224, 256, 384, 512"))
|
||||
ctx.ResultError(errutil.ErrorString("sha3: size must be 224, 256, 384, 512"))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ import (
|
||||
"errors"
|
||||
"net/netip"
|
||||
|
||||
"github.com/ncruces/go-sqlite3"
|
||||
"github.com/hanzoai/sqlite3"
|
||||
)
|
||||
|
||||
// Register IP/CIDR functions for a database connection.
|
||||
|
||||
+15
-15
@@ -3,18 +3,18 @@ package ipaddr_test
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/ncruces/go-sqlite3/driver"
|
||||
_ "github.com/ncruces/go-sqlite3/embed"
|
||||
"github.com/ncruces/go-sqlite3/ext/ipaddr"
|
||||
_ "github.com/ncruces/go-sqlite3/internal/testcfg"
|
||||
"github.com/ncruces/go-sqlite3/vfs/memdb"
|
||||
"github.com/hanzoai/sqlite3/driver"
|
||||
"github.com/hanzoai/sqlite3/ext/ipaddr"
|
||||
"github.com/hanzoai/sqlite3/internal/testcfg"
|
||||
"github.com/hanzoai/sqlite3/vfs/memdb"
|
||||
)
|
||||
|
||||
func TestRegister(t *testing.T) {
|
||||
t.Parallel()
|
||||
tmp := memdb.TestDB(t)
|
||||
dsn := memdb.TestDB(t)
|
||||
|
||||
db, err := driver.Open(tmp, ipaddr.Register)
|
||||
ctx := testcfg.Context(t)
|
||||
db, err := driver.Open(dsn, ipaddr.Register)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -22,7 +22,7 @@ func TestRegister(t *testing.T) {
|
||||
|
||||
var got string
|
||||
|
||||
err = db.QueryRow(`SELECT ipfamily('::1')`).Scan(&got)
|
||||
err = db.QueryRowContext(ctx, `SELECT ipfamily('::1')`).Scan(&got)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -30,7 +30,7 @@ func TestRegister(t *testing.T) {
|
||||
t.Fatalf("got %s", got)
|
||||
}
|
||||
|
||||
err = db.QueryRow(`SELECT ipfamily('[::1]:80')`).Scan(&got)
|
||||
err = db.QueryRowContext(ctx, `SELECT ipfamily('[::1]:80')`).Scan(&got)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -38,7 +38,7 @@ func TestRegister(t *testing.T) {
|
||||
t.Fatalf("got %s", got)
|
||||
}
|
||||
|
||||
err = db.QueryRow(`SELECT ipfamily('192.168.1.5/24')`).Scan(&got)
|
||||
err = db.QueryRowContext(ctx, `SELECT ipfamily('192.168.1.5/24')`).Scan(&got)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -46,7 +46,7 @@ func TestRegister(t *testing.T) {
|
||||
t.Fatalf("got %s", got)
|
||||
}
|
||||
|
||||
err = db.QueryRow(`SELECT iphost('192.168.1.5/24')`).Scan(&got)
|
||||
err = db.QueryRowContext(ctx, `SELECT iphost('192.168.1.5/24')`).Scan(&got)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -54,7 +54,7 @@ func TestRegister(t *testing.T) {
|
||||
t.Fatalf("got %s", got)
|
||||
}
|
||||
|
||||
err = db.QueryRow(`SELECT ipmasklen('192.168.1.5/24')`).Scan(&got)
|
||||
err = db.QueryRowContext(ctx, `SELECT ipmasklen('192.168.1.5/24')`).Scan(&got)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -62,7 +62,7 @@ func TestRegister(t *testing.T) {
|
||||
t.Fatalf("got %s", got)
|
||||
}
|
||||
|
||||
err = db.QueryRow(`SELECT ipnetwork('192.168.1.5/24')`).Scan(&got)
|
||||
err = db.QueryRowContext(ctx, `SELECT ipnetwork('192.168.1.5/24')`).Scan(&got)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -70,7 +70,7 @@ func TestRegister(t *testing.T) {
|
||||
t.Fatalf("got %s", got)
|
||||
}
|
||||
|
||||
err = db.QueryRow(`SELECT ipcontains('192.168.1.0/24', '192.168.1.5')`).Scan(&got)
|
||||
err = db.QueryRowContext(ctx, `SELECT ipcontains('192.168.1.0/24', '192.168.1.5')`).Scan(&got)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -78,7 +78,7 @@ func TestRegister(t *testing.T) {
|
||||
t.Fatalf("got %s", got)
|
||||
}
|
||||
|
||||
err = db.QueryRow(`SELECT ipoverlaps('192.168.1.0/24', '192.168.1.5/32')`).Scan(&got)
|
||||
err = db.QueryRowContext(ctx, `SELECT ipoverlaps('192.168.1.0/24', '192.168.1.5/32')`).Scan(&got)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
+2
-2
@@ -18,8 +18,8 @@ import (
|
||||
"io"
|
||||
"io/fs"
|
||||
|
||||
"github.com/ncruces/go-sqlite3"
|
||||
"github.com/ncruces/go-sqlite3/util/osutil"
|
||||
"github.com/hanzoai/sqlite3"
|
||||
"github.com/hanzoai/sqlite3/util/osutil"
|
||||
)
|
||||
|
||||
// Register registers the lines and lines_read table-valued functions.
|
||||
|
||||
+22
-19
@@ -10,12 +10,11 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"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"
|
||||
"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"
|
||||
)
|
||||
|
||||
func Example() {
|
||||
@@ -67,9 +66,10 @@ func Example() {
|
||||
|
||||
func Test_lines(t *testing.T) {
|
||||
t.Parallel()
|
||||
tmp := memdb.TestDB(t)
|
||||
dsn := memdb.TestDB(t)
|
||||
|
||||
db, err := driver.Open(tmp, lines.Register)
|
||||
ctx := testcfg.Context(t)
|
||||
db, err := driver.Open(dsn, lines.Register)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
@@ -77,7 +77,7 @@ func Test_lines(t *testing.T) {
|
||||
|
||||
const data = "line 1\nline 2\r\nline 3\n"
|
||||
|
||||
rows, err := db.Query(`SELECT rowid, line FROM lines(?)`, data)
|
||||
rows, err := db.QueryContext(ctx, `SELECT rowid, line FROM lines(?)`, data)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -98,22 +98,23 @@ func Test_lines(t *testing.T) {
|
||||
|
||||
func Test_lines_error(t *testing.T) {
|
||||
t.Parallel()
|
||||
tmp := memdb.TestDB(t)
|
||||
dsn := memdb.TestDB(t)
|
||||
|
||||
db, err := driver.Open(tmp, lines.Register)
|
||||
ctx := testcfg.Context(t)
|
||||
db, err := driver.Open(dsn, lines.Register)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
_, err = db.Exec(`SELECT rowid, line FROM lines(?)`, nil)
|
||||
_, err = db.ExecContext(ctx, `SELECT rowid, line FROM lines(?)`, nil)
|
||||
if err == nil {
|
||||
t.Fatal("want error")
|
||||
} else {
|
||||
t.Log(err)
|
||||
}
|
||||
|
||||
_, err = db.Exec(`SELECT rowid, line FROM lines_read(?)`, "xpto")
|
||||
_, err = db.ExecContext(ctx, `SELECT rowid, line FROM lines_read(?)`, "xpto")
|
||||
if err == nil {
|
||||
t.Fatal("want error")
|
||||
} else {
|
||||
@@ -123,9 +124,10 @@ func Test_lines_error(t *testing.T) {
|
||||
|
||||
func Test_lines_read(t *testing.T) {
|
||||
t.Parallel()
|
||||
tmp := memdb.TestDB(t)
|
||||
dsn := memdb.TestDB(t)
|
||||
|
||||
db, err := driver.Open(tmp, lines.Register)
|
||||
ctx := testcfg.Context(t)
|
||||
db, err := driver.Open(dsn, lines.Register)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
@@ -133,7 +135,7 @@ func Test_lines_read(t *testing.T) {
|
||||
|
||||
const data = "line 1\nline 2\r\nline 3\n"
|
||||
|
||||
rows, err := db.Query(`SELECT rowid, line FROM lines_read(?)`,
|
||||
rows, err := db.QueryContext(ctx, `SELECT rowid, line FROM lines_read(?)`,
|
||||
sqlite3.Pointer(strings.NewReader(data)))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -155,15 +157,16 @@ func Test_lines_read(t *testing.T) {
|
||||
|
||||
func Test_lines_test(t *testing.T) {
|
||||
t.Parallel()
|
||||
tmp := memdb.TestDB(t)
|
||||
dsn := memdb.TestDB(t)
|
||||
|
||||
db, err := driver.Open(tmp, lines.Register)
|
||||
ctx := testcfg.Context(t)
|
||||
db, err := driver.Open(dsn, lines.Register)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
rows, err := db.Query(`SELECT rowid, line FROM lines_read(?, '}')`, "lines_test.go")
|
||||
rows, err := db.QueryContext(ctx, `SELECT rowid, line FROM lines_read(?, '}')`, "lines_test.go")
|
||||
if errors.Is(err, os.ErrNotExist) {
|
||||
t.Skip(err)
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ package pivot
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/ncruces/go-sqlite3"
|
||||
"github.com/hanzoai/sqlite3"
|
||||
)
|
||||
|
||||
func Test_operator(t *testing.T) {
|
||||
|
||||
+26
-10
@@ -8,8 +8,8 @@ import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/ncruces/go-sqlite3"
|
||||
"github.com/ncruces/go-sqlite3/internal/util"
|
||||
"github.com/hanzoai/sqlite3"
|
||||
"github.com/hanzoai/sqlite3/internal/errutil"
|
||||
)
|
||||
|
||||
// Register registers the pivot virtual table.
|
||||
@@ -43,11 +43,14 @@ func declare(db *sqlite3.Conn, _, _, _ string, arg ...string) (ret *table, err e
|
||||
|
||||
// Row key query.
|
||||
t.scan = "SELECT * FROM\n" + arg[0]
|
||||
stmt, _, err := db.Prepare(t.scan)
|
||||
stmt, tail, err := db.PrepareFlags(t.scan, sqlite3.PREPARE_FROM_DDL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer stmt.Close()
|
||||
if tail != "" {
|
||||
return nil, errutil.TailErr
|
||||
}
|
||||
|
||||
t.keys = make([]string, stmt.ColumnCount())
|
||||
for i := range t.keys {
|
||||
@@ -55,36 +58,47 @@ func declare(db *sqlite3.Conn, _, _, _ string, arg ...string) (ret *table, err e
|
||||
t.keys[i] = name
|
||||
create.WriteString(sep)
|
||||
create.WriteString(name)
|
||||
create.WriteString(" ")
|
||||
create.WriteString(stmt.ColumnDeclType(i))
|
||||
sep = ","
|
||||
}
|
||||
stmt.Close()
|
||||
|
||||
// Column definition query.
|
||||
stmt, _, err = db.Prepare("SELECT * FROM\n" + arg[1])
|
||||
stmt, tail, err = db.PrepareFlags("SELECT * FROM\n"+arg[1], sqlite3.PREPARE_FROM_DDL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if tail != "" {
|
||||
return nil, errutil.TailErr
|
||||
}
|
||||
|
||||
if stmt.ColumnCount() != 2 {
|
||||
return nil, util.ErrorString("pivot: column definition query expects 2 result columns")
|
||||
return nil, errutil.ErrorString("pivot: column definition query expects 2 result columns")
|
||||
}
|
||||
for stmt.Step() {
|
||||
name := sqlite3.QuoteIdentifier(stmt.ColumnText(1))
|
||||
t.cols = append(t.cols, stmt.ColumnValue(0).Dup())
|
||||
create.WriteString(",")
|
||||
create.WriteString(sep)
|
||||
create.WriteString(name)
|
||||
create.WriteString(" ")
|
||||
create.WriteString(stmt.ColumnDeclType(1))
|
||||
sep = ","
|
||||
}
|
||||
stmt.Close()
|
||||
|
||||
// Pivot cell query.
|
||||
t.cell = "SELECT * FROM\n" + arg[2]
|
||||
stmt, _, err = db.Prepare(t.cell)
|
||||
stmt, tail, err = db.PrepareFlags(t.cell, sqlite3.PREPARE_FROM_DDL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if tail != "" {
|
||||
return nil, errutil.TailErr
|
||||
}
|
||||
|
||||
if stmt.ColumnCount() != 1 {
|
||||
return nil, util.ErrorString("pivot: cell query expects 1 result columns")
|
||||
return nil, errutil.ErrorString("pivot: cell query expects 1 result columns")
|
||||
}
|
||||
if stmt.BindCount() != len(t.keys)+1 {
|
||||
return nil, fmt.Errorf("pivot: cell query expects %d bound parameters", len(t.keys)+1)
|
||||
@@ -182,7 +196,9 @@ func (c *cursor) Filter(idxNum int, idxStr string, arg ...sqlite3.Value) error {
|
||||
return err
|
||||
}
|
||||
|
||||
c.scan, _, err = c.table.db.Prepare(idxStr)
|
||||
const prepflags = sqlite3.PREPARE_DONT_LOG | sqlite3.PREPARE_FROM_DDL
|
||||
|
||||
c.scan, _, err = c.table.db.PrepareFlags(idxStr, prepflags)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -194,7 +210,7 @@ func (c *cursor) Filter(idxNum int, idxStr string, arg ...sqlite3.Value) error {
|
||||
}
|
||||
|
||||
if c.cell == nil {
|
||||
c.cell, _, err = c.table.db.Prepare(c.table.cell)
|
||||
c.cell, _, err = c.table.db.PrepareFlags(c.table.cell, prepflags)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
+9
-10
@@ -7,10 +7,9 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/ncruces/go-sqlite3"
|
||||
_ "github.com/ncruces/go-sqlite3/embed"
|
||||
"github.com/ncruces/go-sqlite3/ext/pivot"
|
||||
_ "github.com/ncruces/go-sqlite3/internal/testcfg"
|
||||
"github.com/hanzoai/sqlite3"
|
||||
"github.com/hanzoai/sqlite3/ext/pivot"
|
||||
"github.com/hanzoai/sqlite3/internal/testcfg"
|
||||
)
|
||||
|
||||
// https://antonz.org/sqlite-pivot-table/
|
||||
@@ -92,7 +91,7 @@ func TestMain(m *testing.M) {
|
||||
func TestRegister(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
db, err := sqlite3.Open(":memory:")
|
||||
db, err := sqlite3.OpenContext(testcfg.Context(t), ":memory:")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -141,10 +140,10 @@ func TestRegister(t *testing.T) {
|
||||
}
|
||||
defer stmt.Close()
|
||||
|
||||
if stmt.Step() {
|
||||
if got := stmt.ColumnInt(0); got != 3 {
|
||||
t.Errorf("got %d, want 3", got)
|
||||
}
|
||||
if !stmt.Step() {
|
||||
t.Fatal(stmt.Err())
|
||||
} else if got := stmt.ColumnInt(0); got != 3 {
|
||||
t.Errorf("got %d, want 3", got)
|
||||
}
|
||||
|
||||
err = db.Exec(`ALTER TABLE v_x RENAME TO v_y`)
|
||||
@@ -156,7 +155,7 @@ func TestRegister(t *testing.T) {
|
||||
func TestRegister_errors(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
db, err := sqlite3.Open(":memory:")
|
||||
db, err := sqlite3.OpenContext(testcfg.Context(t), ":memory:")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ import (
|
||||
"strings"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/ncruces/go-sqlite3"
|
||||
"github.com/hanzoai/sqlite3"
|
||||
)
|
||||
|
||||
// Register registers Unicode aware functions for a database connection.
|
||||
|
||||
+16
-14
@@ -6,18 +6,18 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"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"
|
||||
"github.com/hanzoai/sqlite3"
|
||||
"github.com/hanzoai/sqlite3/driver"
|
||||
"github.com/hanzoai/sqlite3/internal/testcfg"
|
||||
"github.com/hanzoai/sqlite3/vfs/memdb"
|
||||
)
|
||||
|
||||
func TestRegister(t *testing.T) {
|
||||
t.Parallel()
|
||||
tmp := memdb.TestDB(t)
|
||||
dsn := memdb.TestDB(t)
|
||||
|
||||
db, err := driver.Open(tmp, Register)
|
||||
ctx := testcfg.Context(t)
|
||||
db, err := driver.Open(dsn, Register)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -68,7 +68,7 @@ func TestRegister(t *testing.T) {
|
||||
|
||||
for _, tt := range tests {
|
||||
var got sql.NullString
|
||||
err := db.QueryRow(`SELECT ` + tt.test).Scan(&got)
|
||||
err := db.QueryRowContext(ctx, `SELECT `+tt.test).Scan(&got)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -80,9 +80,10 @@ func TestRegister(t *testing.T) {
|
||||
|
||||
func TestRegister_errors(t *testing.T) {
|
||||
t.Parallel()
|
||||
tmp := memdb.TestDB(t)
|
||||
dsn := memdb.TestDB(t)
|
||||
|
||||
db, err := driver.Open(tmp, Register)
|
||||
ctx := testcfg.Context(t)
|
||||
db, err := driver.Open(dsn, Register)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -98,7 +99,7 @@ func TestRegister_errors(t *testing.T) {
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
err := db.QueryRow(`SELECT `+tt, `\`).Scan(nil)
|
||||
err := db.QueryRowContext(ctx, `SELECT `+tt, `\`).Scan(nil)
|
||||
if err == nil {
|
||||
t.Fatal("want error")
|
||||
}
|
||||
@@ -107,16 +108,17 @@ func TestRegister_errors(t *testing.T) {
|
||||
|
||||
func TestRegister_pointer(t *testing.T) {
|
||||
t.Parallel()
|
||||
tmp := memdb.TestDB(t)
|
||||
dsn := memdb.TestDB(t)
|
||||
|
||||
db, err := driver.Open(tmp, Register)
|
||||
ctx := testcfg.Context(t)
|
||||
db, err := driver.Open(dsn, Register)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
var got int
|
||||
err = db.QueryRow(`SELECT regexp_count('ABCABCAXYaxy', ?, 1)`,
|
||||
err = db.QueryRowContext(ctx, `SELECT regexp_count('ABCABCAXYaxy', ?, 1)`,
|
||||
sqlite3.Pointer(regexp.MustCompile(`(?i)A.`))).Scan(&got)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
// 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)
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
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
|
||||
}
|
||||
+14
-82
@@ -2,28 +2,27 @@
|
||||
package serdes
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
||||
"github.com/ncruces/go-sqlite3"
|
||||
"github.com/ncruces/go-sqlite3/vfs"
|
||||
"github.com/hanzoai/sqlite3"
|
||||
"github.com/hanzoai/sqlite3/util/vfsutil"
|
||||
"github.com/hanzoai/sqlite3/vfs"
|
||||
)
|
||||
|
||||
const vfsName = "github.com/ncruces/go-sqlite3/ext/serdes.sliceVFS"
|
||||
const vfsName = "github.com/hanzoai/sqlite3/ext/serdes.sliceVFS"
|
||||
|
||||
func init() {
|
||||
vfs.Register(vfsName, sliceVFS{})
|
||||
}
|
||||
|
||||
var fileToOpen = make(chan *sliceFile, 1)
|
||||
var fileToOpen = make(chan *[]byte, 1)
|
||||
|
||||
// Serialize backs up a database into a byte slice.
|
||||
//
|
||||
// https://sqlite.org/c3ref/serialize.html
|
||||
func Serialize(db *sqlite3.Conn, schema string) ([]byte, error) {
|
||||
var file sliceFile
|
||||
var file []byte
|
||||
fileToOpen <- &file
|
||||
err := db.Backup(schema, "file:serdes.db?vfs="+vfsName)
|
||||
return file.data, err
|
||||
err := db.Backup(schema, "file:serdes.db?nolock=1&vfs="+vfsName)
|
||||
return file, err
|
||||
}
|
||||
|
||||
// Deserialize restores a database from a byte slice,
|
||||
@@ -38,11 +37,11 @@ func Serialize(db *sqlite3.Conn, schema string) ([]byte, error) {
|
||||
//
|
||||
// https://sqlite.org/c3ref/deserialize.html
|
||||
//
|
||||
// ["memdb"]: https://pkg.go.dev/github.com/ncruces/go-sqlite3/vfs/memdb
|
||||
// ["reader"]: https://pkg.go.dev/github.com/ncruces/go-sqlite3/vfs/readervfs
|
||||
// ["memdb"]: https://pkg.go.dev/github.com/hanzoai/sqlite3/vfs/memdb
|
||||
// ["reader"]: https://pkg.go.dev/github.com/hanzoai/sqlite3/vfs/readervfs
|
||||
func Deserialize(db *sqlite3.Conn, schema string, data []byte) error {
|
||||
fileToOpen <- &sliceFile{data}
|
||||
return db.Restore(schema, "file:serdes.db?vfs="+vfsName)
|
||||
fileToOpen <- &data
|
||||
return db.Restore(schema, "file:serdes.db?immutable=1&vfs="+vfsName)
|
||||
}
|
||||
|
||||
type sliceVFS struct{}
|
||||
@@ -53,14 +52,14 @@ func (sliceVFS) Open(name string, flags vfs.OpenFlag) (vfs.File, vfs.OpenFlag, e
|
||||
}
|
||||
select {
|
||||
case file := <-fileToOpen:
|
||||
return file, flags | vfs.OPEN_MEMORY, nil
|
||||
return (*vfsutil.SliceFile)(file), flags | vfs.OPEN_MEMORY, nil
|
||||
default:
|
||||
return nil, flags, sqlite3.MISUSE
|
||||
}
|
||||
}
|
||||
|
||||
func (sliceVFS) Delete(name string, dirSync bool) error {
|
||||
// notest // OPEN_MEMORY
|
||||
// notest // no journals to delete
|
||||
return sqlite3.IOERR_DELETE
|
||||
}
|
||||
|
||||
@@ -71,70 +70,3 @@ func (sliceVFS) Access(name string, flag vfs.AccessFlag) (bool, error) {
|
||||
func (sliceVFS) FullPathname(name string) (string, error) {
|
||||
return name, nil
|
||||
}
|
||||
|
||||
type sliceFile struct{ data []byte }
|
||||
|
||||
func (f *sliceFile) ReadAt(b []byte, off int64) (n int, err error) {
|
||||
if d := f.data; off < int64(len(d)) {
|
||||
n = copy(b, d[off:])
|
||||
}
|
||||
if n == 0 {
|
||||
err = io.EOF
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (f *sliceFile) WriteAt(b []byte, off int64) (n int, err error) {
|
||||
if d := f.data; off > int64(len(d)) {
|
||||
f.data = append(d, make([]byte, off-int64(len(d)))...)
|
||||
}
|
||||
d := append(f.data[:off], b...)
|
||||
if len(d) > len(f.data) {
|
||||
f.data = d
|
||||
}
|
||||
return len(b), nil
|
||||
}
|
||||
|
||||
func (f *sliceFile) Size() (int64, error) {
|
||||
return int64(len(f.data)), nil
|
||||
}
|
||||
|
||||
func (f *sliceFile) Truncate(size int64) error {
|
||||
if d := f.data; size < int64(len(d)) {
|
||||
f.data = d[:size]
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *sliceFile) SizeHint(size int64) error {
|
||||
if d := f.data; size > int64(len(d)) {
|
||||
f.data = append(d, make([]byte, size-int64(len(d)))...)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (*sliceFile) Close() error { return nil }
|
||||
|
||||
func (*sliceFile) Sync(flag vfs.SyncFlag) error { return nil }
|
||||
|
||||
func (*sliceFile) Lock(lock vfs.LockLevel) error { return nil }
|
||||
|
||||
func (*sliceFile) Unlock(lock vfs.LockLevel) error { return nil }
|
||||
|
||||
func (*sliceFile) CheckReservedLock() (bool, error) {
|
||||
// notest // OPEN_MEMORY
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func (*sliceFile) SectorSize() int {
|
||||
// notest // IOCAP_POWERSAFE_OVERWRITE
|
||||
return 0
|
||||
}
|
||||
|
||||
func (*sliceFile) DeviceCharacteristics() vfs.DeviceCharacteristic {
|
||||
return vfs.IOCAP_ATOMIC |
|
||||
vfs.IOCAP_SAFE_APPEND |
|
||||
vfs.IOCAP_SEQUENTIAL |
|
||||
vfs.IOCAP_POWERSAFE_OVERWRITE |
|
||||
vfs.IOCAP_SUBPAGE_READ
|
||||
}
|
||||
|
||||
+45
-12
@@ -1,17 +1,46 @@
|
||||
package serdes_test
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
"errors"
|
||||
"io"
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"github.com/ncruces/go-sqlite3"
|
||||
_ "github.com/ncruces/go-sqlite3/embed"
|
||||
"github.com/ncruces/go-sqlite3/ext/serdes"
|
||||
"github.com/hanzoai/sqlite3"
|
||||
"github.com/hanzoai/sqlite3/ext/serdes"
|
||||
"github.com/hanzoai/sqlite3/internal/testcfg"
|
||||
"github.com/hanzoai/sqlite3/vfs"
|
||||
)
|
||||
|
||||
func TestDeserialize(t *testing.T) {
|
||||
//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")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
data, err := serdes.Serialize(db, "main")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
compareDBs(t, data, walDB)
|
||||
|
||||
err = serdes.Deserialize(db, "temp", walDB)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func Test_northwind(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("skipping in short mode")
|
||||
}
|
||||
@@ -21,7 +50,7 @@ func TestDeserialize(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
db, err := sqlite3.Open(":memory:")
|
||||
db, err := sqlite3.OpenContext(testcfg.Context(t), ":memory:")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -37,10 +66,14 @@ func TestDeserialize(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if len(input) != len(output) {
|
||||
compareDBs(t, input, output)
|
||||
}
|
||||
|
||||
func compareDBs(t *testing.T, a, b []byte) {
|
||||
if len(a) != len(b) {
|
||||
t.Fatal("lengths are different")
|
||||
}
|
||||
for i := range input {
|
||||
for i := range a {
|
||||
// These may be different.
|
||||
switch {
|
||||
case 24 <= i && i < 28:
|
||||
@@ -53,14 +86,14 @@ func TestDeserialize(t *testing.T) {
|
||||
// SQLite version that wrote the file.
|
||||
continue
|
||||
}
|
||||
if input[i] != output[i] {
|
||||
t.Errorf("difference at %d: %d %d", i, input[i], output[i])
|
||||
if a[i] != b[i] {
|
||||
t.Errorf("difference at %d: %d %d", i, a[i], b[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func httpGet() ([]byte, error) {
|
||||
res, err := http.Get("https://raw.githubusercontent.com/jpwhite3/northwind-SQLite3/refs/heads/main/dist/northwind.db")
|
||||
res, err := http.Get("https://github.com/jpwhite3/northwind-SQLite3/raw/refs/heads/main/dist/northwind.db")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -69,7 +102,7 @@ func httpGet() ([]byte, error) {
|
||||
}
|
||||
|
||||
func TestOpen_errors(t *testing.T) {
|
||||
_, err := sqlite3.Open("file:test.db?vfs=github.com/ncruces/go-sqlite3/ext/serdes.sliceVFS")
|
||||
_, err := sqlite3.OpenContext(testcfg.Context(t), "file:test.db?vfs=github.com/hanzoai/sqlite3/ext/serdes.sliceVFS")
|
||||
if err == nil {
|
||||
t.Error("want error")
|
||||
}
|
||||
@@ -77,7 +110,7 @@ func TestOpen_errors(t *testing.T) {
|
||||
t.Errorf("got %v, want sqlite3.CANTOPEN", err)
|
||||
}
|
||||
|
||||
_, err = sqlite3.Open("file:serdes.db?vfs=github.com/ncruces/go-sqlite3/ext/serdes.sliceVFS")
|
||||
_, err = sqlite3.OpenContext(testcfg.Context(t), "file:serdes.db?vfs=github.com/hanzoai/sqlite3/ext/serdes.sliceVFS")
|
||||
if err == nil {
|
||||
t.Error("want error")
|
||||
}
|
||||
|
||||
Vendored
BIN
Binary file not shown.
@@ -0,0 +1,14 @@
|
||||
// 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)
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
+25
-19
@@ -13,8 +13,8 @@ import (
|
||||
"strings"
|
||||
"unsafe"
|
||||
|
||||
"github.com/ncruces/go-sqlite3"
|
||||
"github.com/ncruces/go-sqlite3/internal/util"
|
||||
"github.com/hanzoai/sqlite3"
|
||||
"github.com/hanzoai/sqlite3/internal/errutil"
|
||||
)
|
||||
|
||||
// Register registers the statement virtual table.
|
||||
@@ -30,45 +30,50 @@ type table struct {
|
||||
|
||||
func declare(db *sqlite3.Conn, _, _, _ string, arg ...string) (*table, error) {
|
||||
if len(arg) != 1 {
|
||||
return nil, util.ErrorString("statement: wrong number of arguments")
|
||||
return nil, errutil.ErrorString("statement: wrong number of arguments")
|
||||
}
|
||||
|
||||
sql := "SELECT * FROM\n" + arg[0]
|
||||
|
||||
stmt, _, err := db.PrepareFlags(sql, sqlite3.PREPARE_PERSISTENT)
|
||||
stmt, tail, err := db.PrepareFlags(sql,
|
||||
sqlite3.PREPARE_PERSISTENT|sqlite3.PREPARE_FROM_DDL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if tail != "" {
|
||||
stmt.Close()
|
||||
return nil, errutil.TailErr
|
||||
}
|
||||
|
||||
var sep string
|
||||
var str strings.Builder
|
||||
str.WriteString("CREATE TABLE x(")
|
||||
var buf strings.Builder
|
||||
buf.WriteString("CREATE TABLE x(")
|
||||
outputs := stmt.ColumnCount()
|
||||
for i := range outputs {
|
||||
name := sqlite3.QuoteIdentifier(stmt.ColumnName(i))
|
||||
str.WriteString(sep)
|
||||
str.WriteString(name)
|
||||
str.WriteString(" ")
|
||||
str.WriteString(stmt.ColumnDeclType(i))
|
||||
buf.WriteString(sep)
|
||||
buf.WriteString(name)
|
||||
buf.WriteString(" ")
|
||||
buf.WriteString(stmt.ColumnDeclType(i))
|
||||
sep = ","
|
||||
}
|
||||
inputs := stmt.BindCount()
|
||||
for i := 1; i <= inputs; i++ {
|
||||
str.WriteString(sep)
|
||||
buf.WriteString(sep)
|
||||
name := stmt.BindName(i)
|
||||
if name == "" {
|
||||
str.WriteString("[")
|
||||
str.WriteString(strconv.Itoa(i))
|
||||
str.WriteString("] HIDDEN")
|
||||
buf.WriteString("[")
|
||||
buf.WriteString(strconv.Itoa(i))
|
||||
buf.WriteString("] HIDDEN")
|
||||
} else {
|
||||
str.WriteString(sqlite3.QuoteIdentifier(name[1:]))
|
||||
str.WriteString(" HIDDEN")
|
||||
buf.WriteString(sqlite3.QuoteIdentifier(name[1:]))
|
||||
buf.WriteString(" HIDDEN")
|
||||
}
|
||||
sep = ","
|
||||
}
|
||||
str.WriteByte(')')
|
||||
buf.WriteByte(')')
|
||||
|
||||
err = db.DeclareVTab(str.String())
|
||||
err = db.DeclareVTab(buf.String())
|
||||
if err != nil {
|
||||
stmt.Close()
|
||||
return nil, err
|
||||
@@ -129,7 +134,8 @@ func (t *table) Open() (_ sqlite3.VTabCursor, err error) {
|
||||
if !t.inuse {
|
||||
t.inuse = true
|
||||
} else {
|
||||
stmt, _, err = t.stmt.Conn().Prepare(t.sql)
|
||||
stmt, _, err = t.stmt.Conn().PrepareFlags(t.sql,
|
||||
sqlite3.PREPARE_DONT_LOG|sqlite3.PREPARE_FROM_DDL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -6,10 +6,9 @@ import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/ncruces/go-sqlite3"
|
||||
_ "github.com/ncruces/go-sqlite3/embed"
|
||||
"github.com/ncruces/go-sqlite3/ext/statement"
|
||||
_ "github.com/ncruces/go-sqlite3/internal/testcfg"
|
||||
"github.com/hanzoai/sqlite3"
|
||||
"github.com/hanzoai/sqlite3/ext/statement"
|
||||
"github.com/hanzoai/sqlite3/internal/testcfg"
|
||||
)
|
||||
|
||||
func Example() {
|
||||
@@ -57,7 +56,7 @@ func TestMain(m *testing.M) {
|
||||
func TestRegister(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
db, err := sqlite3.Open(":memory:")
|
||||
db, err := sqlite3.OpenContext(testcfg.Context(t), ":memory:")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -92,7 +91,9 @@ func TestRegister(t *testing.T) {
|
||||
}
|
||||
defer stmt.Close()
|
||||
|
||||
if stmt.Step() {
|
||||
if !stmt.Step() {
|
||||
t.Fatal(stmt.Err())
|
||||
} else {
|
||||
x := stmt.ColumnInt(0)
|
||||
y := stmt.ColumnInt(1)
|
||||
hypot := stmt.ColumnInt(2)
|
||||
@@ -105,7 +106,7 @@ func TestRegister(t *testing.T) {
|
||||
func TestRegister_errors(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
db, err := sqlite3.Open(":memory:")
|
||||
db, err := sqlite3.OpenContext(testcfg.Context(t), ":memory:")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package stats
|
||||
|
||||
import "github.com/ncruces/go-sqlite3"
|
||||
import "github.com/hanzoai/sqlite3"
|
||||
|
||||
const (
|
||||
every = iota
|
||||
|
||||
@@ -3,15 +3,14 @@ package stats_test
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/ncruces/go-sqlite3"
|
||||
_ "github.com/ncruces/go-sqlite3/embed"
|
||||
_ "github.com/ncruces/go-sqlite3/internal/testcfg"
|
||||
"github.com/hanzoai/sqlite3"
|
||||
"github.com/hanzoai/sqlite3/internal/testcfg"
|
||||
)
|
||||
|
||||
func TestRegister_boolean(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
db, err := sqlite3.Open(":memory:")
|
||||
db, err := sqlite3.OpenContext(testcfg.Context(t), ":memory:")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -37,7 +36,9 @@ func TestRegister_boolean(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if stmt.Step() {
|
||||
if !stmt.Step() {
|
||||
t.Fatal(stmt.Err())
|
||||
} else {
|
||||
if got := stmt.ColumnBool(0); got != true {
|
||||
t.Errorf("got %v, want true", got)
|
||||
}
|
||||
|
||||
+6
-6
@@ -5,15 +5,15 @@ package stats
|
||||
type kahan struct{ hi, lo float64 }
|
||||
|
||||
func (k *kahan) add(x float64) {
|
||||
y := k.lo + x
|
||||
t := k.hi + y
|
||||
k.lo = y - (t - k.hi)
|
||||
y := float64(k.lo + x)
|
||||
t := float64(k.hi + y)
|
||||
k.lo = y - float64(t-k.hi)
|
||||
k.hi = t
|
||||
}
|
||||
|
||||
func (k *kahan) sub(x float64) {
|
||||
y := k.lo - x
|
||||
t := k.hi + y
|
||||
k.lo = y - (t - k.hi)
|
||||
y := float64(k.lo - x)
|
||||
t := float64(k.hi + y)
|
||||
k.lo = y - float64(t-k.hi)
|
||||
k.hi = t
|
||||
}
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
package stats
|
||||
|
||||
import (
|
||||
"math"
|
||||
|
||||
"github.com/hanzoai/sqlite3"
|
||||
)
|
||||
|
||||
func cot(ctx sqlite3.Context, arg ...sqlite3.Value) {
|
||||
if f := arg[0].Float(); f != 0.0 {
|
||||
ctx.ResultFloat(1 / math.Tan(f))
|
||||
}
|
||||
}
|
||||
|
||||
func cbrt(ctx sqlite3.Context, arg ...sqlite3.Value) {
|
||||
a := arg[0]
|
||||
f := a.Float()
|
||||
if f != 0.0 || a.Type() != sqlite3.NULL {
|
||||
ctx.ResultFloat(math.Cbrt(f))
|
||||
}
|
||||
}
|
||||
+28
-19
@@ -3,7 +3,7 @@ package stats
|
||||
import (
|
||||
"unsafe"
|
||||
|
||||
"github.com/ncruces/go-sqlite3"
|
||||
"github.com/hanzoai/sqlite3"
|
||||
)
|
||||
|
||||
func newMode() sqlite3.AggregateFunction {
|
||||
@@ -19,8 +19,8 @@ type mode struct {
|
||||
|
||||
func (m mode) Value(ctx sqlite3.Context) {
|
||||
var (
|
||||
max = 0
|
||||
typ = sqlite3.NULL
|
||||
max uint
|
||||
i64 int64
|
||||
f64 float64
|
||||
str string
|
||||
@@ -32,7 +32,6 @@ func (m mode) Value(ctx sqlite3.Context) {
|
||||
i64 = k
|
||||
}
|
||||
}
|
||||
f64 = float64(i64)
|
||||
for k, v := range m.reals {
|
||||
if v > max || v == max && k < f64 {
|
||||
typ = sqlite3.FLOAT
|
||||
@@ -66,33 +65,45 @@ func (m mode) Value(ctx sqlite3.Context) {
|
||||
}
|
||||
}
|
||||
|
||||
func (b *mode) Step(ctx sqlite3.Context, arg ...sqlite3.Value) {
|
||||
func (m *mode) Step(ctx sqlite3.Context, arg ...sqlite3.Value) {
|
||||
switch arg[0].Type() {
|
||||
case sqlite3.INTEGER:
|
||||
b.ints.add(arg[0].Int64())
|
||||
if m.reals == nil {
|
||||
m.ints.add(arg[0].Int64())
|
||||
break
|
||||
}
|
||||
fallthrough
|
||||
case sqlite3.FLOAT:
|
||||
b.reals.add(arg[0].Float())
|
||||
m.reals.add(arg[0].Float())
|
||||
for k, v := range m.ints {
|
||||
m.reals[float64(k)] += v
|
||||
}
|
||||
m.ints = nil
|
||||
case sqlite3.TEXT:
|
||||
b.texts.add(arg[0].Text())
|
||||
m.texts.add(arg[0].Text())
|
||||
case sqlite3.BLOB:
|
||||
b.blobs.add(string(arg[0].RawBlob()))
|
||||
m.blobs.add(string(arg[0].RawBlob()))
|
||||
}
|
||||
}
|
||||
|
||||
func (b *mode) Inverse(ctx sqlite3.Context, arg ...sqlite3.Value) {
|
||||
func (m *mode) Inverse(ctx sqlite3.Context, arg ...sqlite3.Value) {
|
||||
switch arg[0].Type() {
|
||||
case sqlite3.INTEGER:
|
||||
b.ints.del(arg[0].Int64())
|
||||
if m.reals == nil {
|
||||
m.ints.del(arg[0].Int64())
|
||||
break
|
||||
}
|
||||
fallthrough
|
||||
case sqlite3.FLOAT:
|
||||
b.reals.del(arg[0].Float())
|
||||
m.reals.del(arg[0].Float())
|
||||
case sqlite3.TEXT:
|
||||
b.texts.del(arg[0].Text())
|
||||
m.texts.del(arg[0].Text())
|
||||
case sqlite3.BLOB:
|
||||
b.blobs.del(string(arg[0].RawBlob()))
|
||||
m.blobs.del(string(arg[0].RawBlob()))
|
||||
}
|
||||
}
|
||||
|
||||
type counter[T comparable] map[T]int
|
||||
type counter[T comparable] map[T]uint
|
||||
|
||||
func (c *counter[T]) add(k T) {
|
||||
if (*c) == nil {
|
||||
@@ -102,11 +113,9 @@ func (c *counter[T]) add(k T) {
|
||||
}
|
||||
|
||||
func (c counter[T]) del(k T) {
|
||||
switch n := c[k]; n {
|
||||
default:
|
||||
c[k] = n - 1
|
||||
case 1:
|
||||
if n := c[k]; n == 1 {
|
||||
delete(c, k)
|
||||
case 0:
|
||||
} else {
|
||||
c[k] = n - 1
|
||||
}
|
||||
}
|
||||
|
||||
+40
-24
@@ -3,15 +3,14 @@ package stats_test
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/ncruces/go-sqlite3"
|
||||
_ "github.com/ncruces/go-sqlite3/embed"
|
||||
_ "github.com/ncruces/go-sqlite3/internal/testcfg"
|
||||
"github.com/hanzoai/sqlite3"
|
||||
"github.com/hanzoai/sqlite3/internal/testcfg"
|
||||
)
|
||||
|
||||
func TestRegister_mode(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
db, err := sqlite3.Open(":memory:")
|
||||
db, err := sqlite3.OpenContext(testcfg.Context(t), ":memory:")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -21,10 +20,10 @@ func TestRegister_mode(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if stmt.Step() {
|
||||
if got := stmt.ColumnInt(0); got != 3 {
|
||||
t.Errorf("got %v, want 3", got)
|
||||
}
|
||||
if !stmt.Step() {
|
||||
t.Fatal(stmt.Err())
|
||||
} else if got := stmt.ColumnInt(0); got != 3 {
|
||||
t.Errorf("got %v, want 3", got)
|
||||
}
|
||||
stmt.Close()
|
||||
|
||||
@@ -32,10 +31,10 @@ func TestRegister_mode(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if stmt.Step() {
|
||||
if got := stmt.ColumnInt(0); got != 1 {
|
||||
t.Errorf("got %v, want 1", got)
|
||||
}
|
||||
if !stmt.Step() {
|
||||
t.Fatal(stmt.Err())
|
||||
} else if got := stmt.ColumnInt(0); got != 1 {
|
||||
t.Errorf("got %v, want 1", got)
|
||||
}
|
||||
stmt.Close()
|
||||
|
||||
@@ -43,10 +42,10 @@ func TestRegister_mode(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if stmt.Step() {
|
||||
if got := stmt.ColumnFloat(0); got != 2.5 {
|
||||
t.Errorf("got %v, want 2.5", got)
|
||||
}
|
||||
if !stmt.Step() {
|
||||
t.Fatal(stmt.Err())
|
||||
} else if got := stmt.ColumnFloat(0); got != 2.5 {
|
||||
t.Errorf("got %v, want 2.5", got)
|
||||
}
|
||||
stmt.Close()
|
||||
|
||||
@@ -54,21 +53,22 @@ func TestRegister_mode(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if stmt.Step() {
|
||||
if got := stmt.ColumnText(0); got != "red" {
|
||||
t.Errorf("got %q, want red", got)
|
||||
}
|
||||
if !stmt.Step() {
|
||||
t.Fatal(stmt.Err())
|
||||
} else if got := stmt.ColumnText(0); got != "red" {
|
||||
t.Errorf("got %q, want red", got)
|
||||
}
|
||||
|
||||
stmt.Close()
|
||||
|
||||
stmt, _, err = db.Prepare(`SELECT mode(column1) FROM (VALUES (X'cafebabe'), ('green'), ('blue'), (X'cafebabe'))`)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if stmt.Step() {
|
||||
if got := stmt.ColumnText(0); got != "\xca\xfe\xba\xbe" {
|
||||
t.Errorf("got %q, want cafebabe", got)
|
||||
}
|
||||
if !stmt.Step() {
|
||||
t.Fatal(stmt.Err())
|
||||
} else if got := stmt.ColumnText(0); got != "\xca\xfe\xba\xbe" {
|
||||
t.Errorf("got %q, want cafebabe", got)
|
||||
}
|
||||
stmt.Close()
|
||||
|
||||
@@ -82,4 +82,20 @@ func TestRegister_mode(t *testing.T) {
|
||||
for stmt.Step() {
|
||||
}
|
||||
stmt.Close()
|
||||
|
||||
stmt, _, err = db.Prepare(`SELECT mode(column1) FROM (VALUES (?), (?), (?), (?), (?))`)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
stmt.BindInt(1, 1)
|
||||
stmt.BindInt(2, 1)
|
||||
stmt.BindInt(3, 2)
|
||||
stmt.BindFloat(4, 2)
|
||||
stmt.BindFloat(5, 2)
|
||||
if !stmt.Step() {
|
||||
t.Fatal(stmt.Err())
|
||||
} else if got := stmt.ColumnInt(0); got != 2 {
|
||||
t.Errorf("got %v, want 2", got)
|
||||
}
|
||||
stmt.Close()
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user