mirror of
https://github.com/ncruces/go-sqlite3.git
synced 2026-01-19 09:04:16 +00:00
Compare commits
42 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fd3a3a3499 | ||
|
|
ec96c77715 | ||
|
|
c61f7b90f6 | ||
|
|
7bd31c3443 | ||
|
|
f2f698b78a | ||
|
|
846b95d2d4 | ||
|
|
b9453aefb6 | ||
|
|
28b6fedef0 | ||
|
|
fed9ce6e1c | ||
|
|
0ec08c2e74 | ||
|
|
4439cd302c | ||
|
|
705eab456a | ||
|
|
d1d5e355c4 | ||
|
|
d3da8cc4f3 | ||
|
|
6def6f735c | ||
|
|
e02c5b5db0 | ||
|
|
52d42e4b21 | ||
|
|
396e6537b4 | ||
|
|
78cb9abefd | ||
|
|
b76cb33e62 | ||
|
|
c7eea620a3 | ||
|
|
76e2733fef | ||
|
|
58df08329d | ||
|
|
cdab468a92 | ||
|
|
7438fdb664 | ||
|
|
da0e98f17e | ||
|
|
bb0c77c6fa | ||
|
|
ea8894162b | ||
|
|
9898fbfffa | ||
|
|
031087327d | ||
|
|
c9cc893ed7 | ||
|
|
99ad7ff766 | ||
|
|
019c71fb55 | ||
|
|
88cf845651 | ||
|
|
354242e528 | ||
|
|
3d906d47dd | ||
|
|
9df3488964 | ||
|
|
d998b5f36c | ||
|
|
9f58a5d669 | ||
|
|
7d52cb259b | ||
|
|
35bbd8a0b0 | ||
|
|
bce66299ab |
10
.github/workflows/bsd.sh
vendored
Executable file
10
.github/workflows/bsd.sh
vendored
Executable file
@@ -0,0 +1,10 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
echo 'set -euo pipefail' > test.sh
|
||||
|
||||
for p in $(go list ./...); do
|
||||
dir=".${p#github.com/ncruces/go-sqlite3}"
|
||||
name="$(basename "$p").test"
|
||||
(cd ${dir}; GOOS=freebsd go test -c)
|
||||
[ -f "${dir}/${name}" ] && echo "(cd ${dir}; ./${name} -test.v)" >> test.sh
|
||||
done
|
||||
24
.github/workflows/bsd.yml
vendored
24
.github/workflows/bsd.yml
vendored
@@ -1,24 +0,0 @@
|
||||
name: BSD
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: macos-12
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
lfs: 'true'
|
||||
|
||||
- name: Test
|
||||
uses: cross-platform-actions/action@v0.22.0
|
||||
with:
|
||||
operating_system: freebsd
|
||||
version: '13.2'
|
||||
memory: 8G
|
||||
sync_files: runner-to-vm
|
||||
run: |
|
||||
sudo pkg install -y go121
|
||||
go121 test -v ./...
|
||||
40
.github/workflows/cpu.yml
vendored
40
.github/workflows/cpu.yml
vendored
@@ -1,40 +0,0 @@
|
||||
name: CPUs
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
test-386:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
lfs: 'true'
|
||||
|
||||
- name: Set up
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: stable
|
||||
|
||||
- name: Test
|
||||
run: GOARCH=386 go test -v ./...
|
||||
|
||||
test-arm:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
lfs: 'true'
|
||||
|
||||
- name: Set up
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: stable
|
||||
|
||||
- name: Install QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
|
||||
- name: Test
|
||||
run: GOARCH=arm64 go test -v ./...
|
||||
1
.github/workflows/cross.sh
vendored
1
.github/workflows/cross.sh
vendored
@@ -1,4 +1,5 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
echo android ; GOOS=android GOARCH=amd64 go build .
|
||||
echo darwin ; GOOS=darwin GOARCH=amd64 go build .
|
||||
|
||||
3
.github/workflows/cross.yml
vendored
3
.github/workflows/cross.yml
vendored
@@ -10,8 +10,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Set up
|
||||
uses: actions/setup-go@v5
|
||||
- uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: stable
|
||||
|
||||
|
||||
63
.github/workflows/go.yml
vendored
63
.github/workflows/go.yml
vendored
@@ -1,63 +0,0 @@
|
||||
name: Go
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ "main" ]
|
||||
pull_request:
|
||||
branches: [ "main" ]
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
test:
|
||||
strategy:
|
||||
matrix:
|
||||
os: [macos-latest, ubuntu-latest, windows-latest]
|
||||
runs-on: ${{ matrix.os }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
lfs: 'true'
|
||||
|
||||
- name: Set up
|
||||
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 ./...
|
||||
|
||||
- name: Test no locks
|
||||
run: go test -v -tags sqlite3_nosys ./tests -run TestDB_nolock
|
||||
|
||||
- name: Test BSD locks
|
||||
run: go test -v -tags sqlite3_flock ./...
|
||||
if: matrix.os == 'macos-latest'
|
||||
|
||||
- name: Coverage report
|
||||
uses: ncruces/go-coverage-report@v0
|
||||
with:
|
||||
chart: true
|
||||
amend: true
|
||||
if: |
|
||||
github.event_name == 'push' &&
|
||||
matrix.os == 'ubuntu-latest'
|
||||
6
.github/workflows/repro.sh
vendored
6
.github/workflows/repro.sh
vendored
@@ -3,13 +3,13 @@ set -euo pipefail
|
||||
|
||||
if [[ "$OSTYPE" == "linux"* ]]; then
|
||||
WASI_SDK="https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-21/wasi-sdk-21.0-linux.tar.gz"
|
||||
BINARYEN="https://github.com/WebAssembly/binaryen/releases/download/version_116/binaryen-version_116-x86_64-linux.tar.gz"
|
||||
BINARYEN="https://github.com/WebAssembly/binaryen/releases/download/version_117/binaryen-version_117-x86_64-linux.tar.gz"
|
||||
elif [[ "$OSTYPE" == "darwin"* ]]; then
|
||||
WASI_SDK="https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-21/wasi-sdk-21.0-macos.tar.gz"
|
||||
BINARYEN="https://github.com/WebAssembly/binaryen/releases/download/version_116/binaryen-version_116-x86_64-macos.tar.gz"
|
||||
BINARYEN="https://github.com/WebAssembly/binaryen/releases/download/version_117/binaryen-version_117-x86_64-macos.tar.gz"
|
||||
elif [[ "$OSTYPE" == "msys" || "$OSTYPE" == "cygwin" ]]; then
|
||||
WASI_SDK="https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-21/wasi-sdk-21.0.m-mingw.tar.gz"
|
||||
BINARYEN="https://github.com/WebAssembly/binaryen/releases/download/version_116/binaryen-version_116-x86_64-windows.tar.gz"
|
||||
BINARYEN="https://github.com/WebAssembly/binaryen/releases/download/version_117/binaryen-version_117-x86_64-windows.tar.gz"
|
||||
fi
|
||||
|
||||
# Download tools
|
||||
|
||||
3
.github/workflows/repro.yml
vendored
3
.github/workflows/repro.yml
vendored
@@ -15,8 +15,7 @@ jobs:
|
||||
with:
|
||||
lfs: 'true'
|
||||
|
||||
- name: Set up
|
||||
uses: actions/setup-go@v5
|
||||
- uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: stable
|
||||
|
||||
|
||||
129
.github/workflows/test.yml
vendored
Normal file
129
.github/workflows/test.yml
vendored
Normal file
@@ -0,0 +1,129 @@
|
||||
name: Test
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ "main" ]
|
||||
pull_request:
|
||||
branches: [ "main" ]
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
test:
|
||||
strategy:
|
||||
matrix:
|
||||
os: [macos-latest, ubuntu-latest, windows-latest]
|
||||
runs-on: ${{ matrix.os }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with: { lfs: 'true' }
|
||||
|
||||
- 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 ./...
|
||||
|
||||
- name: Test BSD locks
|
||||
run: go test -v -tags sqlite3_flock ./...
|
||||
if: matrix.os == 'macos-latest'
|
||||
|
||||
- name: Test no locks
|
||||
run: go test -v -tags sqlite3_nosys ./tests -run TestDB_nolock
|
||||
|
||||
- name: Test GORM
|
||||
run: gormlite/test.sh
|
||||
|
||||
- uses: ncruces/go-coverage-report@v0
|
||||
with:
|
||||
chart: true
|
||||
amend: true
|
||||
if: |
|
||||
github.event_name == 'push' &&
|
||||
matrix.os == 'ubuntu-latest'
|
||||
|
||||
test-bsd:
|
||||
runs-on: ubuntu-latest
|
||||
needs: test
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with: { lfs: 'true' }
|
||||
|
||||
- uses: actions/setup-go@v5
|
||||
with: { go-version: stable }
|
||||
|
||||
- name: Build
|
||||
run: .github/workflows/bsd.sh
|
||||
|
||||
- name: Test
|
||||
uses: cross-platform-actions/action@v0.23.0
|
||||
with:
|
||||
operating_system: freebsd
|
||||
version: '14.0'
|
||||
shell: bash
|
||||
run: source test.sh
|
||||
sync_files: runner-to-vm
|
||||
|
||||
test-m1:
|
||||
runs-on: macos-14
|
||||
needs: test
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with: { lfs: 'true' }
|
||||
|
||||
- uses: actions/setup-go@v5
|
||||
with: { go-version: stable }
|
||||
|
||||
- name: Test
|
||||
run: go test -v ./...
|
||||
|
||||
test-386:
|
||||
runs-on: ubuntu-latest
|
||||
needs: test
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with: { lfs: 'true' }
|
||||
|
||||
- uses: actions/setup-go@v5
|
||||
with: { go-version: stable }
|
||||
|
||||
- name: Test
|
||||
run: GOARCH=386 go test -v -short ./...
|
||||
|
||||
test-arm:
|
||||
runs-on: ubuntu-latest
|
||||
needs: test
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with: { lfs: 'true' }
|
||||
|
||||
- uses: actions/setup-go@v5
|
||||
with: { go-version: stable }
|
||||
|
||||
- uses: docker/setup-qemu-action@v3
|
||||
|
||||
- name: Test
|
||||
run: GOARCH=arm64 go test -v -short ./...
|
||||
10
README.md
10
README.md
@@ -48,6 +48,8 @@ Go, wazero and [`x/sys`](https://pkg.go.dev/golang.org/x/sys) are the _only_ run
|
||||
provides [statistics](https://www.oreilly.com/library/view/sql-in-a/9780596155322/ch04s02.html) functions.
|
||||
- [`github.com/ncruces/go-sqlite3/ext/unicode`](https://pkg.go.dev/github.com/ncruces/go-sqlite3/ext/unicode)
|
||||
provides [Unicode aware](https://sqlite.org/src/dir/ext/icu) functions.
|
||||
- [`github.com/ncruces/go-sqlite3/ext/zorder`](https://pkg.go.dev/github.com/ncruces/go-sqlite3/ext/zorder)
|
||||
maps multidimensional data to one dimension.
|
||||
- [`github.com/ncruces/go-sqlite3/vfs/memdb`](https://pkg.go.dev/github.com/ncruces/go-sqlite3/vfs/memdb)
|
||||
implements an in-memory VFS.
|
||||
- [`github.com/ncruces/go-sqlite3/vfs/readervfs`](https://pkg.go.dev/github.com/ncruces/go-sqlite3/vfs/readervfs)
|
||||
@@ -100,12 +102,16 @@ On BSD Unixes, this module uses
|
||||
[BSD locks](https://man.freebsd.org/cgi/man.cgi?query=flock&sektion=2).
|
||||
On BSD Unixes, BSD locks are fully compatible with POSIX advisory locks.
|
||||
|
||||
On Windows, this module uses `LockFile`, `LockFileEx`, and `UnlockFile`,
|
||||
On Windows, this module uses `LockFileEx` and `UnlockFileEx`,
|
||||
like SQLite.
|
||||
|
||||
On all other platforms, file locking is not supported, and you must use
|
||||
[`nolock=1`](https://sqlite.org/uri.html#urinolock)
|
||||
(or [`immutable=1`](https://sqlite.org/uri.html#uriimmutable))
|
||||
to open database files.
|
||||
You can use [`vfs.SupportsFileLocking`](https://pkg.go.dev/github.com/ncruces/go-sqlite3/vfs#SupportsFileLocking)
|
||||
to check if your platform supports file locking.
|
||||
|
||||
To use the [`database/sql`](https://pkg.go.dev/database/sql) driver
|
||||
with `nolock=1` you must disable connection pooling by calling
|
||||
[`db.SetMaxOpenConns(1)`](https://pkg.go.dev/database/sql#DB.SetMaxOpenConns).
|
||||
@@ -113,7 +119,7 @@ with `nolock=1` you must disable connection pooling by calling
|
||||
### 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://www.sqlite.org/testing.html) and
|
||||
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.
|
||||
|
||||
The pure Go VFS is tested by running SQLite's
|
||||
|
||||
@@ -121,7 +121,7 @@ func (b *Backup) Step(nPage int) (done bool, err error) {
|
||||
// https://sqlite.org/c3ref/backup_finish.html#sqlite3backupremaining
|
||||
func (b *Backup) Remaining() int {
|
||||
r := b.c.call("sqlite3_backup_remaining", uint64(b.handle))
|
||||
return int(r)
|
||||
return int(int32(r))
|
||||
}
|
||||
|
||||
// PageCount returns the total number of pages in the source database
|
||||
@@ -130,5 +130,5 @@ func (b *Backup) Remaining() int {
|
||||
// https://sqlite.org/c3ref/backup_finish.html#sqlite3backuppagecount
|
||||
func (b *Backup) PageCount() int {
|
||||
r := b.c.call("sqlite3_backup_pagecount", uint64(b.handle))
|
||||
return int(r)
|
||||
return int(int32(r))
|
||||
}
|
||||
|
||||
48
config.go
48
config.go
@@ -35,7 +35,7 @@ func (c *Conn) Config(op DBConfig, arg ...bool) (bool, error) {
|
||||
|
||||
// ConfigLog sets up the error logging callback for the connection.
|
||||
//
|
||||
// https://www.sqlite.org/errlog.html
|
||||
// https://sqlite.org/errlog.html
|
||||
func (c *Conn) ConfigLog(cb func(code ExtendedErrorCode, msg string)) error {
|
||||
var enable uint64
|
||||
if cb != nil {
|
||||
@@ -55,3 +55,49 @@ func logCallback(ctx context.Context, mod api.Module, _, iCode, zMsg uint32) {
|
||||
c.log(xErrorCode(iCode), msg)
|
||||
}
|
||||
}
|
||||
|
||||
// Limit allows the size of various constructs to be
|
||||
// limited on a connection by connection basis.
|
||||
//
|
||||
// https://sqlite.org/c3ref/limit.html
|
||||
func (c *Conn) Limit(id LimitCategory, value int) int {
|
||||
r := c.call("sqlite3_limit", uint64(c.handle), uint64(id), uint64(value))
|
||||
return int(int32(r))
|
||||
}
|
||||
|
||||
// SetAuthorizer registers an authorizer callback with the database connection.
|
||||
//
|
||||
// https://sqlite.org/c3ref/set_authorizer.html
|
||||
func (c *Conn) SetAuthorizer(cb func(action AuthorizerActionCode, name3rd, name4th, schema, nameInner string) AuthorizerReturnCode) error {
|
||||
var enable uint64
|
||||
if cb != nil {
|
||||
enable = 1
|
||||
}
|
||||
r := c.call("sqlite3_set_authorizer_go", uint64(c.handle), enable)
|
||||
if err := c.error(r); err != nil {
|
||||
return err
|
||||
}
|
||||
c.authorizer = cb
|
||||
return nil
|
||||
|
||||
}
|
||||
|
||||
func authorizerCallback(ctx context.Context, mod api.Module, pDB uint32, action AuthorizerActionCode, zName3rd, zName4th, zSchema, zNameInner uint32) AuthorizerReturnCode {
|
||||
if c, ok := ctx.Value(connKey{}).(*Conn); ok && c.handle == pDB && c.authorizer != nil {
|
||||
var name3rd, name4th, schema, nameInner string
|
||||
if zName3rd != 0 {
|
||||
name3rd = util.ReadString(mod, zName3rd, _MAX_NAME)
|
||||
}
|
||||
if zName4th != 0 {
|
||||
name4th = util.ReadString(mod, zName4th, _MAX_NAME)
|
||||
}
|
||||
if zSchema != 0 {
|
||||
schema = util.ReadString(mod, zSchema, _MAX_NAME)
|
||||
}
|
||||
if zNameInner != 0 {
|
||||
nameInner = util.ReadString(mod, zNameInner, _MAX_NAME)
|
||||
}
|
||||
return c.authorizer(action, name3rd, name4th, schema, nameInner)
|
||||
}
|
||||
return AUTH_OK
|
||||
}
|
||||
|
||||
108
conn.go
108
conn.go
@@ -4,8 +4,10 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/ncruces/go-sqlite3/internal/util"
|
||||
"github.com/tetratelabs/wazero/api"
|
||||
@@ -18,10 +20,16 @@ import (
|
||||
type Conn struct {
|
||||
*sqlite
|
||||
|
||||
interrupt context.Context
|
||||
pending *Stmt
|
||||
log func(code xErrorCode, msg string)
|
||||
arena arena
|
||||
interrupt context.Context
|
||||
pending *Stmt
|
||||
busy func(int) bool
|
||||
log func(xErrorCode, string)
|
||||
collation func(*Conn, string)
|
||||
authorizer func(AuthorizerActionCode, string, string, string, string) AuthorizerReturnCode
|
||||
update func(AuthorizerActionCode, string, string, int64)
|
||||
commit func() bool
|
||||
rollback func()
|
||||
arena arena
|
||||
|
||||
handle uint32
|
||||
}
|
||||
@@ -194,6 +202,32 @@ func (c *Conn) PrepareFlags(sql string, flags PrepareFlag) (stmt *Stmt, tail str
|
||||
return stmt, tail, nil
|
||||
}
|
||||
|
||||
// DBName returns the schema name for n-th database on the database connection.
|
||||
//
|
||||
// https://sqlite.org/c3ref/db_name.html
|
||||
func (c *Conn) DBName(n int) string {
|
||||
r := c.call("sqlite3_db_name", uint64(c.handle), uint64(n))
|
||||
|
||||
ptr := uint32(r)
|
||||
if ptr == 0 {
|
||||
return ""
|
||||
}
|
||||
return util.ReadString(c.mod, ptr, _MAX_NAME)
|
||||
}
|
||||
|
||||
// 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) {
|
||||
var ptr uint32
|
||||
if schema != "" {
|
||||
defer c.arena.mark()()
|
||||
ptr = c.arena.string(schema)
|
||||
}
|
||||
r := c.call("sqlite3_db_readonly", uint64(c.handle), uint64(ptr))
|
||||
return int32(r) > 0, int32(r) < 0
|
||||
}
|
||||
|
||||
// GetAutocommit tests the connection for auto-commit mode.
|
||||
//
|
||||
// https://sqlite.org/c3ref/get_autocommit.html
|
||||
@@ -211,6 +245,14 @@ func (c *Conn) LastInsertRowID() int64 {
|
||||
return int64(r)
|
||||
}
|
||||
|
||||
// SetLastInsertRowID allows the application to set the value returned by
|
||||
// [Conn.LastInsertRowID].
|
||||
//
|
||||
// https://sqlite.org/c3ref/set_last_insert_rowid.html
|
||||
func (c *Conn) SetLastInsertRowID(id int64) {
|
||||
c.call("sqlite3_set_last_insert_rowid", uint64(c.handle), uint64(id))
|
||||
}
|
||||
|
||||
// Changes returns the number of rows modified, inserted or deleted
|
||||
// by the most recently completed INSERT, UPDATE or DELETE statement
|
||||
// on the database connection.
|
||||
@@ -221,6 +263,24 @@ func (c *Conn) Changes() int64 {
|
||||
return int64(r)
|
||||
}
|
||||
|
||||
// TotalChanges returns the number of rows modified, inserted or deleted
|
||||
// by all INSERT, UPDATE or DELETE statements completed
|
||||
// since the database connection was opened.
|
||||
//
|
||||
// https://sqlite.org/c3ref/total_changes.html
|
||||
func (c *Conn) TotalChanges() int64 {
|
||||
r := c.call("sqlite3_total_changes64", uint64(c.handle))
|
||||
return int64(r)
|
||||
}
|
||||
|
||||
// ReleaseMemory frees memory used by a database connection.
|
||||
//
|
||||
// https://sqlite.org/c3ref/db_release_memory.html
|
||||
func (c *Conn) ReleaseMemory() error {
|
||||
r := c.call("sqlite3_db_release_memory", uint64(c.handle))
|
||||
return c.error(r)
|
||||
}
|
||||
|
||||
// SetInterrupt interrupts a long-running query when a context is done.
|
||||
//
|
||||
// Subsequent uses of the connection will return [INTERRUPT]
|
||||
@@ -265,8 +325,8 @@ func (c *Conn) checkInterrupt() {
|
||||
}
|
||||
}
|
||||
|
||||
func progressCallback(ctx context.Context, mod api.Module, _ uint32) uint32 {
|
||||
if c, ok := ctx.Value(connKey{}).(*Conn); ok {
|
||||
func progressCallback(ctx context.Context, mod api.Module, pDB uint32) uint32 {
|
||||
if c, ok := ctx.Value(connKey{}).(*Conn); ok && c.handle == pDB && c.commit != nil {
|
||||
if c.interrupt != nil && c.interrupt.Err() != nil {
|
||||
return 1
|
||||
}
|
||||
@@ -274,9 +334,41 @@ func progressCallback(ctx context.Context, mod api.Module, _ uint32) uint32 {
|
||||
return 0
|
||||
}
|
||||
|
||||
// Pragma executes a PRAGMA statement and returns any results.
|
||||
// BusyTimeout sets a busy timeout.
|
||||
//
|
||||
// https://sqlite.org/pragma.html
|
||||
// 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)
|
||||
r := c.call("sqlite3_busy_timeout", uint64(c.handle), uint64(ms))
|
||||
return c.error(r)
|
||||
}
|
||||
|
||||
// BusyHandler registers a callback to handle [BUSY] errors.
|
||||
//
|
||||
// https://sqlite.org/c3ref/busy_handler.html
|
||||
func (c *Conn) BusyHandler(cb func(count int) (retry bool)) error {
|
||||
var enable uint64
|
||||
if cb != nil {
|
||||
enable = 1
|
||||
}
|
||||
r := c.call("sqlite3_busy_handler_go", uint64(c.handle), enable)
|
||||
if err := c.error(r); err != nil {
|
||||
return err
|
||||
}
|
||||
c.busy = cb
|
||||
return nil
|
||||
}
|
||||
|
||||
func busyCallback(ctx context.Context, mod api.Module, pDB, count uint32) uint32 {
|
||||
if c, ok := ctx.Value(connKey{}).(*Conn); ok && c.handle == pDB && c.busy != nil {
|
||||
if retry := c.busy(int(count)); retry {
|
||||
return 1
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// Deprecated: executes a PRAGMA statement and returns results.
|
||||
func (c *Conn) Pragma(str string) ([]string, error) {
|
||||
stmt, _, err := c.Prepare(`PRAGMA ` + str)
|
||||
if err != nil {
|
||||
|
||||
87
const.go
87
const.go
@@ -229,6 +229,93 @@ const (
|
||||
DBCONFIG_REVERSE_SCANORDER DBConfig = 1019
|
||||
)
|
||||
|
||||
// LimitCategory are the available run-time limit categories.
|
||||
//
|
||||
// https://sqlite.org/c3ref/c_limit_attached.html
|
||||
type LimitCategory uint32
|
||||
|
||||
const (
|
||||
LIMIT_LENGTH LimitCategory = 0
|
||||
LIMIT_SQL_LENGTH LimitCategory = 1
|
||||
LIMIT_COLUMN LimitCategory = 2
|
||||
LIMIT_EXPR_DEPTH LimitCategory = 3
|
||||
LIMIT_COMPOUND_SELECT LimitCategory = 4
|
||||
LIMIT_VDBE_OP LimitCategory = 5
|
||||
LIMIT_FUNCTION_ARG LimitCategory = 6
|
||||
LIMIT_ATTACHED LimitCategory = 7
|
||||
LIMIT_LIKE_PATTERN_LENGTH LimitCategory = 8
|
||||
LIMIT_VARIABLE_NUMBER LimitCategory = 9
|
||||
LIMIT_TRIGGER_DEPTH LimitCategory = 10
|
||||
LIMIT_WORKER_THREADS LimitCategory = 11
|
||||
)
|
||||
|
||||
// AuthorizerActionCode are the integer action codes
|
||||
// that the authorizer callback may be passed.
|
||||
//
|
||||
// https://sqlite.org/c3ref/c_alter_table.html
|
||||
type AuthorizerActionCode uint32
|
||||
|
||||
const (
|
||||
/************************************************ 3rd ************ 4th ***********/
|
||||
CREATE_INDEX AuthorizerActionCode = 1 /* Index Name Table Name */
|
||||
CREATE_TABLE AuthorizerActionCode = 2 /* Table Name NULL */
|
||||
CREATE_TEMP_INDEX AuthorizerActionCode = 3 /* Index Name Table Name */
|
||||
CREATE_TEMP_TABLE AuthorizerActionCode = 4 /* Table Name NULL */
|
||||
CREATE_TEMP_TRIGGER AuthorizerActionCode = 5 /* Trigger Name Table Name */
|
||||
CREATE_TEMP_VIEW AuthorizerActionCode = 6 /* View Name NULL */
|
||||
CREATE_TRIGGER AuthorizerActionCode = 7 /* Trigger Name Table Name */
|
||||
CREATE_VIEW AuthorizerActionCode = 8 /* View Name NULL */
|
||||
DELETE AuthorizerActionCode = 9 /* Table Name NULL */
|
||||
DROP_INDEX AuthorizerActionCode = 10 /* Index Name Table Name */
|
||||
DROP_TABLE AuthorizerActionCode = 11 /* Table Name NULL */
|
||||
DROP_TEMP_INDEX AuthorizerActionCode = 12 /* Index Name Table Name */
|
||||
DROP_TEMP_TABLE AuthorizerActionCode = 13 /* Table Name NULL */
|
||||
DROP_TEMP_TRIGGER AuthorizerActionCode = 14 /* Trigger Name Table Name */
|
||||
DROP_TEMP_VIEW AuthorizerActionCode = 15 /* View Name NULL */
|
||||
DROP_TRIGGER AuthorizerActionCode = 16 /* Trigger Name Table Name */
|
||||
DROP_VIEW AuthorizerActionCode = 17 /* View Name NULL */
|
||||
INSERT AuthorizerActionCode = 18 /* Table Name NULL */
|
||||
PRAGMA AuthorizerActionCode = 19 /* Pragma Name 1st arg or NULL */
|
||||
READ AuthorizerActionCode = 20 /* Table Name Column Name */
|
||||
SELECT AuthorizerActionCode = 21 /* NULL NULL */
|
||||
TRANSACTION AuthorizerActionCode = 22 /* Operation NULL */
|
||||
UPDATE AuthorizerActionCode = 23 /* Table Name Column Name */
|
||||
ATTACH AuthorizerActionCode = 24 /* Filename NULL */
|
||||
DETACH AuthorizerActionCode = 25 /* Database Name NULL */
|
||||
ALTER_TABLE AuthorizerActionCode = 26 /* Database Name Table Name */
|
||||
REINDEX AuthorizerActionCode = 27 /* Index Name NULL */
|
||||
ANALYZE AuthorizerActionCode = 28 /* Table Name NULL */
|
||||
CREATE_VTABLE AuthorizerActionCode = 29 /* Table Name Module Name */
|
||||
DROP_VTABLE AuthorizerActionCode = 30 /* Table Name Module Name */
|
||||
FUNCTION AuthorizerActionCode = 31 /* NULL Function Name */
|
||||
SAVEPOINT AuthorizerActionCode = 32 /* Operation Savepoint Name */
|
||||
COPY AuthorizerActionCode = 0 /* No longer used */
|
||||
RECURSIVE AuthorizerActionCode = 33 /* NULL NULL */
|
||||
)
|
||||
|
||||
// AuthorizerReturnCode are the integer codes
|
||||
// that the authorizer callback may return.
|
||||
//
|
||||
// https://sqlite.org/c3ref/c_deny.html
|
||||
type AuthorizerReturnCode uint32
|
||||
|
||||
const (
|
||||
AUTH_OK AuthorizerReturnCode = 0
|
||||
AUTH_DENY AuthorizerReturnCode = 1 /* Abort the SQL statement with an error */
|
||||
AUTH_IGNORE AuthorizerReturnCode = 2 /* Don't allow access, but don't generate an error */
|
||||
)
|
||||
|
||||
// TxnState are the allowed return values from [Conn.TxnState].
|
||||
//
|
||||
// https://sqlite.org/c3ref/c_txn_none.html
|
||||
type TxnState uint32
|
||||
|
||||
const (
|
||||
TXN_NONE TxnState = 0
|
||||
TXN_READ TxnState = 1
|
||||
TXN_WRITE TxnState = 2
|
||||
)
|
||||
|
||||
// Datatype is a fundamental datatype of SQLite.
|
||||
//
|
||||
// https://sqlite.org/c3ref/c_blob.html
|
||||
|
||||
@@ -222,7 +222,7 @@ func (ctx Context) ResultError(err error) {
|
||||
// VTabNoChange may return true if a column is being fetched as part
|
||||
// of an update during which the column value will not change.
|
||||
//
|
||||
// https://www.sqlite.org/c3ref/vtab_nochange.html
|
||||
// https://sqlite.org/c3ref/vtab_nochange.html
|
||||
func (ctx Context) VTabNoChange() bool {
|
||||
r := ctx.c.call("sqlite3_vtab_nochange", uint64(ctx.handle))
|
||||
return r != 0
|
||||
|
||||
@@ -170,7 +170,7 @@ func (n *connector) Connect(ctx context.Context) (_ driver.Conn, err error) {
|
||||
defer c.Conn.SetInterrupt(old)
|
||||
|
||||
if !n.pragmas {
|
||||
err = c.Conn.Exec(`PRAGMA busy_timeout=60000`)
|
||||
err = c.Conn.BusyTimeout(60 * time.Second)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -538,8 +538,13 @@ func (r *rows) Next(dest []driver.Value) error {
|
||||
for i := range dest {
|
||||
if t, ok := r.decodeTime(i, dest[i]); ok {
|
||||
dest[i] = t
|
||||
} else if s, ok := dest[i].(string); ok {
|
||||
dest[i] = stringOrTime(s)
|
||||
continue
|
||||
}
|
||||
if s, ok := dest[i].(string); ok {
|
||||
t, ok := maybeTime(s)
|
||||
if ok {
|
||||
dest[i] = t
|
||||
}
|
||||
}
|
||||
}
|
||||
return err
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package driver
|
||||
|
||||
import (
|
||||
"database/sql/driver"
|
||||
"time"
|
||||
)
|
||||
|
||||
@@ -9,24 +8,24 @@ import (
|
||||
// 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 stringOrTime(text string) driver.Value {
|
||||
func maybeTime(text string) (_ time.Time, _ bool) {
|
||||
// Weed out (some) values that can't possibly be
|
||||
// [time.RFC3339Nano] timestamps.
|
||||
if len(text) < len("2006-01-02T15:04:05Z") {
|
||||
return text
|
||||
return
|
||||
}
|
||||
if len(text) > len(time.RFC3339Nano) {
|
||||
return text
|
||||
return
|
||||
}
|
||||
if text[4] != '-' || text[10] != 'T' || text[16] != ':' {
|
||||
return text
|
||||
return
|
||||
}
|
||||
|
||||
// Slow path.
|
||||
var buf [len(time.RFC3339Nano)]byte
|
||||
date, err := time.Parse(time.RFC3339Nano, text)
|
||||
if err == nil && text == string(date.AppendFormat(buf[:0], time.RFC3339Nano)) {
|
||||
return date
|
||||
return date, true
|
||||
}
|
||||
return text
|
||||
return
|
||||
}
|
||||
|
||||
@@ -22,26 +22,18 @@ func Fuzz_stringOrTime_1(f *testing.F) {
|
||||
f.Add("Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.")
|
||||
|
||||
f.Fuzz(func(t *testing.T, str string) {
|
||||
value := stringOrTime(str)
|
||||
|
||||
switch v := value.(type) {
|
||||
case time.Time:
|
||||
v, ok := maybeTime(str)
|
||||
if ok {
|
||||
// Make sure times round-trip to the same string:
|
||||
// https://pkg.go.dev/database/sql#Rows.Scan
|
||||
if v.Format(time.RFC3339Nano) != str {
|
||||
t.Fatalf("did not round-trip: %q", str)
|
||||
}
|
||||
case string:
|
||||
if v != str {
|
||||
t.Fatalf("did not round-trip: %q", str)
|
||||
}
|
||||
|
||||
} else {
|
||||
date, err := time.Parse(time.RFC3339Nano, str)
|
||||
if err == nil && date.Format(time.RFC3339Nano) == str {
|
||||
t.Fatalf("would round-trip: %q", str)
|
||||
}
|
||||
default:
|
||||
t.Fatalf("invalid type %T: %q", v, str)
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -59,10 +51,8 @@ func Fuzz_stringOrTime_2(f *testing.F) {
|
||||
f.Add(int64(-763421161058), int64(222_222_222)) // twosday, year 22222BC
|
||||
|
||||
checkTime := func(t testing.TB, date time.Time) {
|
||||
value := stringOrTime(date.Format(time.RFC3339Nano))
|
||||
|
||||
switch v := value.(type) {
|
||||
case time.Time:
|
||||
v, ok := maybeTime(date.Format(time.RFC3339Nano))
|
||||
if ok {
|
||||
// Make sure times round-trip to the same time:
|
||||
if !v.Equal(date) {
|
||||
t.Fatalf("did not round-trip: %v", date)
|
||||
@@ -73,10 +63,8 @@ func Fuzz_stringOrTime_2(f *testing.F) {
|
||||
if off1 != off2 {
|
||||
t.Fatalf("did not round-trip: %v", date)
|
||||
}
|
||||
case string:
|
||||
} else {
|
||||
t.Fatalf("was not recovered: %v", date)
|
||||
default:
|
||||
t.Fatalf("invalid type %T: %v", v, date)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
# Embeddable WASM build of SQLite
|
||||
|
||||
This folder includes an embeddable WASM build of SQLite 3.45.0 for use with
|
||||
This folder includes an embeddable WASM build of SQLite 3.45.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)
|
||||
- [FTS3/4](https://sqlite.org/fts3.html)/[5](https://sqlite.org/fts5.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)
|
||||
|
||||
@@ -4,7 +4,7 @@ set -euo pipefail
|
||||
cd -P -- "$(dirname -- "$0")"
|
||||
|
||||
ROOT=../
|
||||
BINARYEN="$ROOT/tools/binaryen-version_116/bin"
|
||||
BINARYEN="$ROOT/tools/binaryen-version_117/bin"
|
||||
WASI_SDK="$ROOT/tools/wasi-sdk-21.0/bin"
|
||||
|
||||
"$WASI_SDK/clang" --target=wasm32-wasi -std=c17 -flto -g0 -O2 \
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
free
|
||||
malloc
|
||||
malloc_destructor
|
||||
sqlite3_aggregate_context
|
||||
sqlite3_anycollseq_init
|
||||
sqlite3_backup_finish
|
||||
sqlite3_backup_init
|
||||
@@ -25,10 +24,13 @@ 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
|
||||
@@ -40,6 +42,7 @@ 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
|
||||
@@ -47,6 +50,9 @@ sqlite3_create_function_go
|
||||
sqlite3_create_module_go
|
||||
sqlite3_create_window_function_go
|
||||
sqlite3_db_config
|
||||
sqlite3_db_name
|
||||
sqlite3_db_readonly
|
||||
sqlite3_db_release_memory
|
||||
sqlite3_declare_vtab
|
||||
sqlite3_errcode
|
||||
sqlite3_errmsg
|
||||
@@ -58,6 +64,7 @@ sqlite3_get_autocommit
|
||||
sqlite3_get_auxdata
|
||||
sqlite3_interrupt
|
||||
sqlite3_last_insert_rowid
|
||||
sqlite3_limit
|
||||
sqlite3_open_v2
|
||||
sqlite3_overload_function
|
||||
sqlite3_prepare_v3
|
||||
@@ -75,14 +82,19 @@ sqlite3_result_pointer_go
|
||||
sqlite3_result_text64
|
||||
sqlite3_result_value
|
||||
sqlite3_result_zeroblob64
|
||||
sqlite3_rollback_hook_go
|
||||
sqlite3_set_authorizer_go
|
||||
sqlite3_set_auxdata_go
|
||||
sqlite3_set_last_insert_rowid
|
||||
sqlite3_step
|
||||
sqlite3_stmt_busy
|
||||
sqlite3_stmt_readonly
|
||||
sqlite3_stmt_status
|
||||
sqlite3_total_changes64
|
||||
sqlite3_txn_state
|
||||
sqlite3_update_hook_go
|
||||
sqlite3_uri_key
|
||||
sqlite3_uri_parameter
|
||||
sqlite3_user_data
|
||||
sqlite3_value_blob
|
||||
sqlite3_value_bytes
|
||||
sqlite3_value_double
|
||||
|
||||
Binary file not shown.
@@ -30,9 +30,9 @@ import (
|
||||
//
|
||||
// https://sqlite.org/c3ref/blob.html
|
||||
func Register(db *sqlite3.Conn) {
|
||||
db.CreateFunction("readblob", 6, sqlite3.DIRECTONLY, readblob)
|
||||
db.CreateFunction("writeblob", 6, sqlite3.DIRECTONLY, writeblob)
|
||||
db.CreateFunction("openblob", -1, sqlite3.DIRECTONLY, openblob)
|
||||
db.CreateFunction("readblob", 6, 0, readblob)
|
||||
db.CreateFunction("writeblob", 6, 0, writeblob)
|
||||
db.CreateFunction("openblob", -1, 0, openblob)
|
||||
}
|
||||
|
||||
// OpenCallback is the type for the openblob callback.
|
||||
@@ -128,12 +128,12 @@ func getAuxBlob(ctx sqlite3.Context, arg []sqlite3.Value, write bool) (*sqlite3.
|
||||
return ctx.Conn().OpenBlob(db, table, column, row, write)
|
||||
}
|
||||
|
||||
func setAuxBlob(ctx sqlite3.Context, blob *sqlite3.Blob, writer bool) {
|
||||
func setAuxBlob(ctx sqlite3.Context, blob *sqlite3.Blob, open bool) {
|
||||
// This ensures the blob is closed if db, table, column or write change.
|
||||
ctx.SetAuxData(0, blob) // db
|
||||
ctx.SetAuxData(1, blob) // table
|
||||
ctx.SetAuxData(2, blob) // column
|
||||
if writer {
|
||||
if open {
|
||||
ctx.SetAuxData(4, blob) // write
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ func Register(db *sqlite3.Conn) {
|
||||
// and the table-valued function fsdir;
|
||||
// fsys will be used to read files and list directories.
|
||||
func RegisterFS(db *sqlite3.Conn, fsys fs.FS) {
|
||||
db.CreateFunction("lsmode", 1, 0, lsmode)
|
||||
db.CreateFunction("lsmode", 1, sqlite3.DETERMINISTIC, lsmode)
|
||||
db.CreateFunction("readfile", 1, sqlite3.DIRECTONLY, readfile(fsys))
|
||||
if fsys == nil {
|
||||
db.CreateFunction("writefile", -1, sqlite3.DIRECTONLY, writefile)
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
// - regr_count: count non-null pairs of variables
|
||||
// - regr_slope: slope of the least-squares-fit linear equation
|
||||
// - regr_intercept: y-intercept of the least-squares-fit linear equation
|
||||
// - regr_json: all regr stats in a JSON object
|
||||
//
|
||||
// These join the [Built-in Aggregate Functions]:
|
||||
// - count: count rows/values
|
||||
@@ -52,6 +53,7 @@ func Register(db *sqlite3.Conn) {
|
||||
db.CreateWindowFunction("regr_slope", 2, flags, newCovariance(regr_slope))
|
||||
db.CreateWindowFunction("regr_intercept", 2, flags, newCovariance(regr_intercept))
|
||||
db.CreateWindowFunction("regr_count", 2, flags, newCovariance(regr_count))
|
||||
db.CreateWindowFunction("regr_json", 2, flags, newCovariance(regr_json))
|
||||
}
|
||||
|
||||
const (
|
||||
@@ -69,6 +71,7 @@ const (
|
||||
regr_slope
|
||||
regr_intercept
|
||||
regr_count
|
||||
regr_json
|
||||
)
|
||||
|
||||
func newVariance(kind int) func() sqlite3.AggregateFunction {
|
||||
@@ -144,6 +147,9 @@ func (fn *covariance) Value(ctx sqlite3.Context) {
|
||||
case regr_count:
|
||||
ctx.ResultInt64(fn.regr_count())
|
||||
return
|
||||
case regr_json:
|
||||
ctx.ResultText(fn.regr_json())
|
||||
return
|
||||
}
|
||||
ctx.ResultFloat(r)
|
||||
}
|
||||
|
||||
@@ -107,7 +107,7 @@ func TestRegister_covariance(t *testing.T) {
|
||||
regr_avgy(y, x), regr_avgx(y, x),
|
||||
regr_syy(y, x), regr_sxx(y, x), regr_sxy(y, x),
|
||||
regr_slope(y, x), regr_intercept(y, x), regr_r2(y, x),
|
||||
regr_count(y, x)
|
||||
regr_count(y, x), regr_json(y, x)
|
||||
FROM data`)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -151,6 +151,12 @@ func TestRegister_covariance(t *testing.T) {
|
||||
if got := stmt.ColumnInt(11); got != 5 {
|
||||
t.Errorf("got %v, want 5", got)
|
||||
}
|
||||
var a map[string]float64
|
||||
if err := stmt.ColumnJSON(12, &a); err != nil {
|
||||
t.Error(err)
|
||||
} else if got := a["count"]; got != 5 {
|
||||
t.Errorf("got %v, want 5", got)
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
package stats
|
||||
|
||||
import "math"
|
||||
import (
|
||||
"math"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Welford's algorithm with Kahan summation:
|
||||
// https://en.wikipedia.org/wiki/Algorithms_for_calculating_variance#Welford's_online_algorithm
|
||||
@@ -108,6 +112,32 @@ func (w welford2) regr_r2() float64 {
|
||||
return w.cov.hi * w.cov.hi / (w.m2y.hi * w.m2x.hi)
|
||||
}
|
||||
|
||||
func (w welford2) regr_json() string {
|
||||
var json strings.Builder
|
||||
var num [32]byte
|
||||
json.Grow(128)
|
||||
json.WriteString(`{"count":`)
|
||||
json.Write(strconv.AppendInt(num[:0], w.regr_count(), 10))
|
||||
json.WriteString(`,"avgy":`)
|
||||
json.Write(strconv.AppendFloat(num[:0], w.regr_avgy(), 'g', -1, 64))
|
||||
json.WriteString(`,"avgx":`)
|
||||
json.Write(strconv.AppendFloat(num[:0], w.regr_avgx(), 'g', -1, 64))
|
||||
json.WriteString(`,"syy":`)
|
||||
json.Write(strconv.AppendFloat(num[:0], w.regr_syy(), 'g', -1, 64))
|
||||
json.WriteString(`,"sxx":`)
|
||||
json.Write(strconv.AppendFloat(num[:0], w.regr_sxx(), 'g', -1, 64))
|
||||
json.WriteString(`,"sxy":`)
|
||||
json.Write(strconv.AppendFloat(num[:0], w.regr_sxy(), 'g', -1, 64))
|
||||
json.WriteString(`,"slope":`)
|
||||
json.Write(strconv.AppendFloat(num[:0], w.regr_slope(), 'g', -1, 64))
|
||||
json.WriteString(`,"intercept":`)
|
||||
json.Write(strconv.AppendFloat(num[:0], w.regr_intercept(), 'g', -1, 64))
|
||||
json.WriteString(`,"r2":`)
|
||||
json.Write(strconv.AppendFloat(num[:0], w.regr_r2(), 'g', -1, 64))
|
||||
json.WriteByte('}')
|
||||
return json.String()
|
||||
}
|
||||
|
||||
func (w *welford2) enqueue(y, x float64) {
|
||||
w.n++
|
||||
d1y := y - w.m1y.hi - w.m1y.lo
|
||||
|
||||
58
ext/zorder/zorder.go
Normal file
58
ext/zorder/zorder.go
Normal file
@@ -0,0 +1,58 @@
|
||||
// Package zorder provides functions for z-order transformations.
|
||||
//
|
||||
// https://sqlite.org/src/doc/tip/ext/misc/zorder.c
|
||||
package zorder
|
||||
|
||||
import (
|
||||
"github.com/ncruces/go-sqlite3"
|
||||
"github.com/ncruces/go-sqlite3/internal/util"
|
||||
)
|
||||
|
||||
// Register registers the zorder and unzorder SQL functions.
|
||||
func Register(db *sqlite3.Conn) {
|
||||
flags := sqlite3.DETERMINISTIC | sqlite3.INNOCUOUS
|
||||
db.CreateFunction("zorder", -1, flags, zorder)
|
||||
db.CreateFunction("unzorder", 3, flags, unzorder)
|
||||
}
|
||||
|
||||
func zorder(ctx sqlite3.Context, arg ...sqlite3.Value) {
|
||||
var x [63]int64
|
||||
for i := range arg {
|
||||
x[i] = arg[i].Int64()
|
||||
}
|
||||
if len(arg) > len(x) {
|
||||
ctx.ResultError(util.ErrorString("zorder: too many parameters"))
|
||||
return
|
||||
}
|
||||
|
||||
var z int64
|
||||
if len(arg) > 0 {
|
||||
for i := 0; i < 63; i++ {
|
||||
j := i % len(arg)
|
||||
z |= (x[j] & 1) << i
|
||||
x[j] >>= 1
|
||||
}
|
||||
}
|
||||
|
||||
for i := range arg {
|
||||
if x[i] != 0 {
|
||||
ctx.ResultError(util.ErrorString("zorder: parameter too large"))
|
||||
return
|
||||
}
|
||||
}
|
||||
ctx.ResultInt64(z)
|
||||
}
|
||||
|
||||
func unzorder(ctx sqlite3.Context, arg ...sqlite3.Value) {
|
||||
z := arg[0].Int64()
|
||||
n := arg[1].Int64()
|
||||
i := arg[2].Int64()
|
||||
|
||||
var k int
|
||||
var x int64
|
||||
for j := i; j < 63; j += n {
|
||||
x |= ((z >> j) & 1) << k
|
||||
k++
|
||||
}
|
||||
ctx.ResultInt64(x)
|
||||
}
|
||||
106
ext/zorder/zorder_test.go
Normal file
106
ext/zorder/zorder_test.go
Normal file
@@ -0,0 +1,106 @@
|
||||
package zorder_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/ncruces/go-sqlite3"
|
||||
"github.com/ncruces/go-sqlite3/driver"
|
||||
_ "github.com/ncruces/go-sqlite3/embed"
|
||||
"github.com/ncruces/go-sqlite3/ext/zorder"
|
||||
)
|
||||
|
||||
func TestRegister_zorder(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
db, err := driver.Open(":memory:", func(c *sqlite3.Conn) error {
|
||||
zorder.Register(c)
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
var got int64
|
||||
err = db.QueryRow(`SELECT zorder(2, 3)`).Scan(&got)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if got != 14 {
|
||||
t.Errorf("got %d, want 14", got)
|
||||
}
|
||||
|
||||
err = db.QueryRow(`SELECT zorder(4, 5)`).Scan(&got)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if got != 50 {
|
||||
t.Errorf("got %d, want 14", got)
|
||||
}
|
||||
|
||||
var check bool
|
||||
err = db.QueryRow(`SELECT zorder(3, 4) BETWEEN zorder(2, 3) AND zorder(4, 5)`).Scan(&check)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !check {
|
||||
t.Error("want true")
|
||||
}
|
||||
|
||||
err = db.QueryRow(`SELECT zorder(2, 2) NOT BETWEEN zorder(2, 3) AND zorder(4, 5)`).Scan(&check)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !check {
|
||||
t.Error("want true")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRegister_unzorder(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
db, err := driver.Open(":memory:", func(c *sqlite3.Conn) error {
|
||||
zorder.Register(c)
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
var got int64
|
||||
err = db.QueryRow(`SELECT unzorder(zorder(3, 4), 2, 0)`).Scan(&got)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if got != 3 {
|
||||
t.Errorf("got %d, want 3", got)
|
||||
}
|
||||
|
||||
err = db.QueryRow(`SELECT unzorder(zorder(3, 4), 2, 1)`).Scan(&got)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if got != 4 {
|
||||
t.Errorf("got %d, want 4", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRegister_error(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
db, err := driver.Open(":memory:", func(c *sqlite3.Conn) error {
|
||||
zorder.Register(c)
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
var got int64
|
||||
err = db.QueryRow(`SELECT zorder(1, 2, 3, 100000)`).Scan(&got)
|
||||
if err == nil {
|
||||
t.Error("want error")
|
||||
}
|
||||
}
|
||||
28
func.go
28
func.go
@@ -8,8 +8,25 @@ import (
|
||||
"github.com/tetratelabs/wazero/api"
|
||||
)
|
||||
|
||||
// AnyCollationNeeded registers a fake collating function
|
||||
// for any unknown collating sequence.
|
||||
// CollationNeeded registers a callback to be invoked
|
||||
// whenever an unknown collation sequence is required.
|
||||
//
|
||||
// https://sqlite.org/c3ref/collation_needed.html
|
||||
func (c *Conn) CollationNeeded(cb func(db *Conn, name string)) error {
|
||||
var enable uint64
|
||||
if cb != nil {
|
||||
enable = 1
|
||||
}
|
||||
r := c.call("sqlite3_collation_needed_go", uint64(c.handle), enable)
|
||||
if err := c.error(r); err != nil {
|
||||
return err
|
||||
}
|
||||
c.collation = cb
|
||||
return nil
|
||||
}
|
||||
|
||||
// AnyCollationNeeded uses [Conn.CollationNeeded] to register
|
||||
// a fake collating function for any unknown collating sequence.
|
||||
// The fake collating function works like BINARY.
|
||||
//
|
||||
// This can be used to load schemas that contain
|
||||
@@ -106,6 +123,13 @@ func destroyCallback(ctx context.Context, mod api.Module, pApp uint32) {
|
||||
util.DelHandle(ctx, pApp)
|
||||
}
|
||||
|
||||
func collationCallback(ctx context.Context, mod api.Module, pArg, pDB, eTextRep, zName uint32) {
|
||||
if c, ok := ctx.Value(connKey{}).(*Conn); ok && c.handle == pDB && c.collation != nil {
|
||||
name := util.ReadString(mod, zName, _MAX_NAME)
|
||||
c.collation(c, name)
|
||||
}
|
||||
}
|
||||
|
||||
func compareCallback(ctx context.Context, mod api.Module, pApp, nKey1, pKey1, nKey2, pKey2 uint32) uint32 {
|
||||
fn := util.GetHandle(ctx, pApp).(func(a, b []byte) int)
|
||||
return uint32(fn(util.View(mod, pKey1, uint64(nKey1)), util.View(mod, pKey2, uint64(nKey2))))
|
||||
|
||||
13
func_test.go
13
func_test.go
@@ -6,11 +6,9 @@ import (
|
||||
"log"
|
||||
"regexp"
|
||||
|
||||
"golang.org/x/text/collate"
|
||||
"golang.org/x/text/language"
|
||||
|
||||
"github.com/ncruces/go-sqlite3"
|
||||
_ "github.com/ncruces/go-sqlite3/embed"
|
||||
"github.com/ncruces/go-sqlite3/ext/unicode"
|
||||
)
|
||||
|
||||
func ExampleConn_CreateCollation() {
|
||||
@@ -30,12 +28,17 @@ func ExampleConn_CreateCollation() {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
err = db.CreateCollation("french", collate.New(language.French).Compare)
|
||||
err = db.CollationNeeded(func(db *sqlite3.Conn, name string) {
|
||||
err := unicode.RegisterCollation(db, name, name)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
})
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
stmt, _, err := db.Prepare(`SELECT word FROM words ORDER BY word COLLATE french`)
|
||||
stmt, _, err := db.Prepare(`SELECT word FROM words ORDER BY word COLLATE fr_FR`)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
6
go.mod
6
go.mod
@@ -5,10 +5,10 @@ go 1.21
|
||||
require (
|
||||
github.com/ncruces/julianday v1.0.0
|
||||
github.com/psanford/httpreadat v0.1.0
|
||||
github.com/tetratelabs/wazero v1.6.0
|
||||
golang.org/x/crypto v0.18.0
|
||||
github.com/tetratelabs/wazero v1.7.0
|
||||
golang.org/x/crypto v0.21.0
|
||||
golang.org/x/sync v0.6.0
|
||||
golang.org/x/sys v0.16.0
|
||||
golang.org/x/sys v0.18.0
|
||||
golang.org/x/text v0.14.0
|
||||
)
|
||||
|
||||
|
||||
12
go.sum
12
go.sum
@@ -2,13 +2,13 @@ github.com/ncruces/julianday v1.0.0 h1:fH0OKwa7NWvniGQtxdJRxAgkBMolni2BjDHaWTxqt
|
||||
github.com/ncruces/julianday v1.0.0/go.mod h1:Dusn2KvZrrovOMJuOt0TNXL6tB7U2E8kvza5fFc9G7g=
|
||||
github.com/psanford/httpreadat v0.1.0 h1:VleW1HS2zO7/4c7c7zNl33fO6oYACSagjJIyMIwZLUE=
|
||||
github.com/psanford/httpreadat v0.1.0/go.mod h1:Zg7P+TlBm3bYbyHTKv/EdtSJZn3qwbPwpfZ/I9GKCRE=
|
||||
github.com/tetratelabs/wazero v1.6.0 h1:z0H1iikCdP8t+q341xqepY4EWvHEw8Es7tlqiVzlP3g=
|
||||
github.com/tetratelabs/wazero v1.6.0/go.mod h1:0U0G41+ochRKoPKCJlh0jMg1CHkyfK8kDqiirMmKY8A=
|
||||
golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc=
|
||||
golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg=
|
||||
github.com/tetratelabs/wazero v1.7.0 h1:jg5qPydno59wqjpGrHph81lbtHzTrWzwwtD4cD88+hQ=
|
||||
github.com/tetratelabs/wazero v1.7.0/go.mod h1:ytl6Zuh20R/eROuyDaGPkp82O9C/DJfXAwJfQ3X6/7Y=
|
||||
golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA=
|
||||
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
|
||||
golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ=
|
||||
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU=
|
||||
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
|
||||
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
|
||||
golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58=
|
||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||
|
||||
@@ -13,6 +13,7 @@ import (
|
||||
|
||||
var (
|
||||
sqliteSeparator = "`|\"|'|\t"
|
||||
uniqueRegexp = regexp.MustCompile(fmt.Sprintf(`^CONSTRAINT [%v]?[\w-]+[%v]? UNIQUE (.*)$`, sqliteSeparator, sqliteSeparator))
|
||||
indexRegexp = regexp.MustCompile(fmt.Sprintf(`(?is)CREATE(?: UNIQUE)? INDEX [%v]?[\w\d-]+[%v]?(?s:.*?)ON (.*)$`, sqliteSeparator, sqliteSeparator))
|
||||
tableRegexp = regexp.MustCompile(fmt.Sprintf(`(?is)(CREATE TABLE [%v]?[\w\d-]+[%v]?)(?:\s*\((.*)\))?`, sqliteSeparator, sqliteSeparator))
|
||||
separatorRegexp = regexp.MustCompile(fmt.Sprintf("[%v]", sqliteSeparator))
|
||||
@@ -103,11 +104,24 @@ func parseDDL(strs ...string) (*ddl, error) {
|
||||
|
||||
for _, f := range result.fields {
|
||||
fUpper := strings.ToUpper(f)
|
||||
if strings.HasPrefix(fUpper, "CHECK") ||
|
||||
strings.HasPrefix(fUpper, "CONSTRAINT") {
|
||||
if strings.HasPrefix(fUpper, "CHECK") {
|
||||
continue
|
||||
}
|
||||
if strings.HasPrefix(fUpper, "CONSTRAINT") {
|
||||
matches := uniqueRegexp.FindStringSubmatch(f)
|
||||
if len(matches) > 0 {
|
||||
if columns := getAllColumns(matches[1]); len(columns) == 1 {
|
||||
for idx, column := range result.columns {
|
||||
if column.NameValue.String == columns[0] {
|
||||
column.UniqueValue = sql.NullBool{Bool: true, Valid: true}
|
||||
result.columns[idx] = column
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if strings.HasPrefix(fUpper, "PRIMARY KEY") {
|
||||
for _, name := range getAllColumns(f) {
|
||||
for idx, column := range result.columns {
|
||||
@@ -159,14 +173,7 @@ func parseDDL(strs ...string) (*ddl, error) {
|
||||
}
|
||||
}
|
||||
} else if matches := indexRegexp.FindStringSubmatch(str); len(matches) > 0 {
|
||||
for _, column := range getAllColumns(matches[1]) {
|
||||
for idx, c := range result.columns {
|
||||
if c.NameValue.String == column {
|
||||
c.UniqueValue = sql.NullBool{Bool: strings.ToUpper(strings.Fields(str)[1]) == "UNIQUE", Valid: true}
|
||||
result.columns[idx] = c
|
||||
}
|
||||
}
|
||||
}
|
||||
// don't report Unique by UniqueIndex
|
||||
} else {
|
||||
return nil, errors.New("invalid DDL")
|
||||
}
|
||||
@@ -269,20 +276,6 @@ func (d *ddl) getColumns() []string {
|
||||
return res
|
||||
}
|
||||
|
||||
func (d *ddl) alterColumn(name, sql string) bool {
|
||||
reg := regexp.MustCompile("^(`|'|\"| )" + regexp.QuoteMeta(name) + "(`|'|\"| ) .*?$")
|
||||
|
||||
for i := 0; i < len(d.fields); i++ {
|
||||
if reg.MatchString(d.fields[i]) {
|
||||
d.fields[i] = sql
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
d.fields = append(d.fields, sql)
|
||||
return true
|
||||
}
|
||||
|
||||
func (d *ddl) removeColumn(name string) bool {
|
||||
reg := regexp.MustCompile("^(`|'|\"| )" + regexp.QuoteMeta(name) + "(`|'|\"| ) .*?$")
|
||||
|
||||
|
||||
@@ -16,11 +16,12 @@ func TestParseDDL(t *testing.T) {
|
||||
columns []migrator.ColumnType
|
||||
}{
|
||||
{"with_fk", []string{
|
||||
"CREATE TABLE `notes` (`id` integer NOT NULL,`text` varchar(500) DEFAULT \"hello\",`age` integer DEFAULT 18,`user_id` integer,PRIMARY KEY (`id`),CONSTRAINT `fk_users_notes` FOREIGN KEY (`user_id`) REFERENCES `users`(`id`))",
|
||||
"CREATE TABLE `notes` (" +
|
||||
"`id` integer NOT NULL,`text` varchar(500) DEFAULT \"hello\",`age` integer DEFAULT 18,`user_id` integer,PRIMARY KEY (`id`),CONSTRAINT `fk_users_notes` FOREIGN KEY (`user_id`) REFERENCES `users`(`id`))",
|
||||
"CREATE UNIQUE INDEX `idx_profiles_refer` ON `profiles`(`text`)",
|
||||
}, 6, []migrator.ColumnType{
|
||||
{NameValue: sql.NullString{String: "id", Valid: true}, DataTypeValue: sql.NullString{String: "integer", Valid: true}, ColumnTypeValue: sql.NullString{String: "integer", Valid: true}, PrimaryKeyValue: sql.NullBool{Bool: true, Valid: true}, NullableValue: sql.NullBool{Valid: true}, UniqueValue: sql.NullBool{Valid: true}, DefaultValueValue: sql.NullString{Valid: false}},
|
||||
{NameValue: sql.NullString{String: "text", Valid: true}, DataTypeValue: sql.NullString{String: "varchar", Valid: true}, LengthValue: sql.NullInt64{Int64: 500, Valid: true}, ColumnTypeValue: sql.NullString{String: "varchar(500)", Valid: true}, DefaultValueValue: sql.NullString{String: "hello", Valid: true}, NullableValue: sql.NullBool{Bool: true, Valid: true}, UniqueValue: sql.NullBool{Bool: true, Valid: true}, PrimaryKeyValue: sql.NullBool{Valid: true}},
|
||||
{NameValue: sql.NullString{String: "text", Valid: true}, DataTypeValue: sql.NullString{String: "varchar", Valid: true}, LengthValue: sql.NullInt64{Int64: 500, Valid: true}, ColumnTypeValue: sql.NullString{String: "varchar(500)", Valid: true}, DefaultValueValue: sql.NullString{String: "hello", Valid: true}, NullableValue: sql.NullBool{Bool: true, Valid: true}, UniqueValue: sql.NullBool{Bool: false, Valid: true}, PrimaryKeyValue: sql.NullBool{Valid: true}},
|
||||
{NameValue: sql.NullString{String: "age", Valid: true}, DataTypeValue: sql.NullString{String: "integer", Valid: true}, ColumnTypeValue: sql.NullString{String: "integer", Valid: true}, DefaultValueValue: sql.NullString{String: "18", Valid: true}, NullableValue: sql.NullBool{Bool: true, Valid: true}, UniqueValue: sql.NullBool{Valid: true}, PrimaryKeyValue: sql.NullBool{Valid: true}},
|
||||
{NameValue: sql.NullString{String: "user_id", Valid: true}, DataTypeValue: sql.NullString{String: "integer", Valid: true}, ColumnTypeValue: sql.NullString{String: "integer", Valid: true}, DefaultValueValue: sql.NullString{Valid: false}, NullableValue: sql.NullBool{Bool: true, Valid: true}, UniqueValue: sql.NullBool{Valid: true}, PrimaryKeyValue: sql.NullBool{Valid: true}},
|
||||
},
|
||||
@@ -56,28 +57,54 @@ func TestParseDDL(t *testing.T) {
|
||||
ColumnTypeValue: sql.NullString{String: "int", Valid: true},
|
||||
NullableValue: sql.NullBool{Bool: false, Valid: true},
|
||||
DefaultValueValue: sql.NullString{Valid: false},
|
||||
UniqueValue: sql.NullBool{Bool: true, Valid: true},
|
||||
UniqueValue: sql.NullBool{Bool: false, Valid: true},
|
||||
PrimaryKeyValue: sql.NullBool{Valid: true},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
}, {
|
||||
"unique index",
|
||||
[]string{
|
||||
"CREATE TABLE `test-b` (`field` integer NOT NULL)",
|
||||
"CREATE UNIQUE INDEX `idx_uq` ON `test-b`(`field`) WHERE field = 0",
|
||||
},
|
||||
1,
|
||||
[]migrator.ColumnType{
|
||||
{
|
||||
NameValue: sql.NullString{String: "field", Valid: true},
|
||||
DataTypeValue: sql.NullString{String: "integer", Valid: true},
|
||||
ColumnTypeValue: sql.NullString{String: "integer", Valid: true},
|
||||
PrimaryKeyValue: sql.NullBool{Bool: false, Valid: true},
|
||||
UniqueValue: sql.NullBool{Bool: true, Valid: true},
|
||||
NullableValue: sql.NullBool{Bool: false, Valid: true},
|
||||
},
|
||||
[]migrator.ColumnType{{
|
||||
NameValue: sql.NullString{String: "field", Valid: true},
|
||||
DataTypeValue: sql.NullString{String: "integer", Valid: true},
|
||||
ColumnTypeValue: sql.NullString{String: "integer", Valid: true},
|
||||
PrimaryKeyValue: sql.NullBool{Bool: false, Valid: true},
|
||||
UniqueValue: sql.NullBool{Bool: false, Valid: true},
|
||||
NullableValue: sql.NullBool{Bool: false, Valid: true},
|
||||
}},
|
||||
}, {
|
||||
"normal index",
|
||||
[]string{
|
||||
"CREATE TABLE `test-c` (`field` integer NOT NULL)",
|
||||
"CREATE INDEX `idx_uq` ON `test-c`(`field`)",
|
||||
},
|
||||
1,
|
||||
[]migrator.ColumnType{{
|
||||
NameValue: sql.NullString{String: "field", Valid: true},
|
||||
DataTypeValue: sql.NullString{String: "integer", Valid: true},
|
||||
ColumnTypeValue: sql.NullString{String: "integer", Valid: true},
|
||||
PrimaryKeyValue: sql.NullBool{Bool: false, Valid: true},
|
||||
UniqueValue: sql.NullBool{Bool: false, Valid: true},
|
||||
NullableValue: sql.NullBool{Bool: false, Valid: true},
|
||||
}},
|
||||
}, {
|
||||
"unique constraint",
|
||||
[]string{
|
||||
"CREATE TABLE `unique_struct` (`name` text,CONSTRAINT `uni_unique_struct_name` UNIQUE (`name`))",
|
||||
},
|
||||
2,
|
||||
[]migrator.ColumnType{{
|
||||
NameValue: sql.NullString{String: "name", Valid: true},
|
||||
DataTypeValue: sql.NullString{String: "text", Valid: true},
|
||||
ColumnTypeValue: sql.NullString{String: "text", Valid: true},
|
||||
PrimaryKeyValue: sql.NullBool{Bool: false, Valid: true},
|
||||
UniqueValue: sql.NullBool{Bool: true, Valid: true},
|
||||
NullableValue: sql.NullBool{Bool: true, Valid: true},
|
||||
}},
|
||||
},
|
||||
{
|
||||
"non-unique index",
|
||||
|
||||
@@ -3,9 +3,9 @@ set -euo pipefail
|
||||
|
||||
cd -P -- "$(dirname -- "$0")"
|
||||
|
||||
curl -#OL "https://github.com/go-gorm/sqlite/raw/master/ddlmod.go"
|
||||
curl -#OL "https://github.com/go-gorm/sqlite/raw/master/ddlmod_test.go"
|
||||
curl -#OL "https://github.com/go-gorm/sqlite/raw/master/error_translator.go"
|
||||
curl -#OL "https://github.com/go-gorm/sqlite/raw/master/migrator.go"
|
||||
curl -#OL "https://github.com/go-gorm/sqlite/raw/master/sqlite.go"
|
||||
curl -#OL "https://github.com/go-gorm/sqlite/raw/master/sqlite_test.go"
|
||||
curl -#OL "https://github.com/go-gorm/sqlite/raw/v1.5.5/ddlmod.go"
|
||||
curl -#OL "https://github.com/go-gorm/sqlite/raw/v1.5.5/ddlmod_test.go"
|
||||
curl -#OL "https://github.com/go-gorm/sqlite/raw/v1.5.5/error_translator.go"
|
||||
curl -#OL "https://github.com/go-gorm/sqlite/raw/v1.5.5/migrator.go"
|
||||
curl -#OL "https://github.com/go-gorm/sqlite/raw/v1.5.5/sqlite.go"
|
||||
curl -#OL "https://github.com/go-gorm/sqlite/raw/v1.5.5/sqlite_test.go"
|
||||
@@ -3,14 +3,14 @@ module github.com/ncruces/go-sqlite3/gormlite
|
||||
go 1.21
|
||||
|
||||
require (
|
||||
github.com/ncruces/go-sqlite3 v0.11.0
|
||||
gorm.io/gorm v1.25.5
|
||||
github.com/ncruces/go-sqlite3 v0.12.2
|
||||
gorm.io/gorm v1.25.7
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||
github.com/jinzhu/now v1.1.5 // indirect
|
||||
github.com/ncruces/julianday v1.0.0 // indirect
|
||||
github.com/tetratelabs/wazero v1.5.0 // indirect
|
||||
golang.org/x/sys v0.15.0 // indirect
|
||||
github.com/tetratelabs/wazero v1.7.0-pre.1 // indirect
|
||||
golang.org/x/sys v0.18.0 // indirect
|
||||
)
|
||||
|
||||
@@ -2,15 +2,15 @@ github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD
|
||||
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
|
||||
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
|
||||
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
||||
github.com/ncruces/go-sqlite3 v0.11.0 h1:PDjs8Ve2Z0GWmHyKQHGUyG78grCXKhiHCUZQI8CqXO8=
|
||||
github.com/ncruces/go-sqlite3 v0.11.0/go.mod h1:zaYJ6xP+EQiWJCa3nd3h28cD8DuSIcIqh+LrJMrBN9k=
|
||||
github.com/ncruces/go-sqlite3 v0.12.2 h1:NO8lFyFTA6aUtDWviQX2Rzqi1RX3X52peWq/MLgV1Gc=
|
||||
github.com/ncruces/go-sqlite3 v0.12.2/go.mod h1:+8dWcBxb2Yar4EcCwav1a21MpKZbztwOYBLSRYt9bMY=
|
||||
github.com/ncruces/julianday v1.0.0 h1:fH0OKwa7NWvniGQtxdJRxAgkBMolni2BjDHaWTxqt7M=
|
||||
github.com/ncruces/julianday v1.0.0/go.mod h1:Dusn2KvZrrovOMJuOt0TNXL6tB7U2E8kvza5fFc9G7g=
|
||||
github.com/tetratelabs/wazero v1.5.0 h1:Yz3fZHivfDiZFUXnWMPUoiW7s8tC1sjdBtlJn08qYa0=
|
||||
github.com/tetratelabs/wazero v1.5.0/go.mod h1:0U0G41+ochRKoPKCJlh0jMg1CHkyfK8kDqiirMmKY8A=
|
||||
golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
|
||||
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
github.com/tetratelabs/wazero v1.7.0-pre.1 h1:mOcomS6m5tz4gZgUaocVm0o64uDPPAmErJJmiOVLHvw=
|
||||
github.com/tetratelabs/wazero v1.7.0-pre.1/go.mod h1:ytl6Zuh20R/eROuyDaGPkp82O9C/DJfXAwJfQ3X6/7Y=
|
||||
golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
|
||||
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
gorm.io/gorm v1.25.5 h1:zR9lOiiYf09VNh5Q1gphfyia1JpiClIWG9hQaxB/mls=
|
||||
gorm.io/gorm v1.25.5/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8=
|
||||
gorm.io/gorm v1.25.7 h1:VsD6acwRjz2zFxGO50gPO6AkNs7KKnvfzUjHQhZDz/A=
|
||||
gorm.io/gorm v1.25.7/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8=
|
||||
|
||||
@@ -79,14 +79,28 @@ func (m _Migrator) AlterColumn(value interface{}, name string) error {
|
||||
return m.RunWithoutForeignKey(func() error {
|
||||
return m.recreateTable(value, nil, func(ddl *ddl, stmt *gorm.Statement) (*ddl, []interface{}, error) {
|
||||
if field := stmt.Schema.LookUpField(name); field != nil {
|
||||
if ddl.alterColumn(field.DBName, fmt.Sprintf("`%s` ?", field.DBName)) {
|
||||
return nil, nil, fmt.Errorf("field `%s` not found in origin ddl, ddl= '%s'", name, ddl.compile())
|
||||
var sqlArgs []interface{}
|
||||
for i, f := range ddl.fields {
|
||||
if matches := columnRegexp.FindStringSubmatch(f); len(matches) > 1 && matches[1] == field.DBName {
|
||||
ddl.fields[i] = fmt.Sprintf("`%v` ?", field.DBName)
|
||||
sqlArgs = []interface{}{m.FullDataTypeOf(field)}
|
||||
// table created by old version might look like `CREATE TABLE ? (? varchar(10) UNIQUE)`.
|
||||
// FullDataTypeOf doesn't contain UNIQUE, so we need to add unique constraint.
|
||||
if strings.Contains(strings.ToUpper(matches[3]), " UNIQUE") {
|
||||
uniName := m.DB.NamingStrategy.UniqueName(stmt.Table, field.DBName)
|
||||
uni, _ := m.GuessConstraintInterfaceAndTable(stmt, uniName)
|
||||
if uni != nil {
|
||||
uniSQL, uniArgs := uni.Build()
|
||||
ddl.addConstraint(uniName, uniSQL)
|
||||
sqlArgs = append(sqlArgs, uniArgs...)
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return ddl, []interface{}{m.FullDataTypeOf(field)}, nil
|
||||
return ddl, sqlArgs, nil
|
||||
}
|
||||
|
||||
return nil, nil, fmt.Errorf("failed to alter field with name `%s`", name)
|
||||
return nil, nil, fmt.Errorf("failed to alter field with name %v", name)
|
||||
})
|
||||
})
|
||||
}
|
||||
@@ -153,7 +167,7 @@ func (m _Migrator) DropColumn(value interface{}, name string) error {
|
||||
|
||||
func (m _Migrator) CreateConstraint(value interface{}, name string) error {
|
||||
return m.RunWithValue(value, func(stmt *gorm.Statement) error {
|
||||
constraint, chk, table := m.GuessConstraintAndTable(stmt, name)
|
||||
constraint, table := m.GuessConstraintInterfaceAndTable(stmt, name)
|
||||
|
||||
return m.recreateTable(value, &table,
|
||||
func(ddl *ddl, stmt *gorm.Statement) (*ddl, []interface{}, error) {
|
||||
@@ -164,12 +178,8 @@ func (m _Migrator) CreateConstraint(value interface{}, name string) error {
|
||||
)
|
||||
|
||||
if constraint != nil {
|
||||
constraintName = constraint.Name
|
||||
constraintSql, constraintValues = buildConstraint(constraint)
|
||||
} else if chk != nil {
|
||||
constraintName = chk.Name
|
||||
constraintSql = "CONSTRAINT ? CHECK (?)"
|
||||
constraintValues = []interface{}{clause.Column{Name: chk.Name}, clause.Expr{SQL: chk.Constraint}}
|
||||
constraintName = constraint.GetName()
|
||||
constraintSql, constraintValues = constraint.Build()
|
||||
} else {
|
||||
return nil, nil, nil
|
||||
}
|
||||
@@ -182,11 +192,9 @@ func (m _Migrator) CreateConstraint(value interface{}, name string) error {
|
||||
|
||||
func (m _Migrator) DropConstraint(value interface{}, name string) error {
|
||||
return m.RunWithValue(value, func(stmt *gorm.Statement) error {
|
||||
constraint, chk, table := m.GuessConstraintAndTable(stmt, name)
|
||||
constraint, table := m.GuessConstraintInterfaceAndTable(stmt, name)
|
||||
if constraint != nil {
|
||||
name = constraint.Name
|
||||
} else if chk != nil {
|
||||
name = chk.Name
|
||||
name = constraint.GetName()
|
||||
}
|
||||
|
||||
return m.recreateTable(value, &table,
|
||||
@@ -200,11 +208,9 @@ func (m _Migrator) DropConstraint(value interface{}, name string) error {
|
||||
func (m _Migrator) HasConstraint(value interface{}, name string) bool {
|
||||
var count int64
|
||||
m.RunWithValue(value, func(stmt *gorm.Statement) error {
|
||||
constraint, chk, table := m.GuessConstraintAndTable(stmt, name)
|
||||
constraint, table := m.GuessConstraintInterfaceAndTable(stmt, name)
|
||||
if constraint != nil {
|
||||
name = constraint.Name
|
||||
} else if chk != nil {
|
||||
name = chk.Name
|
||||
name = constraint.GetName()
|
||||
}
|
||||
|
||||
m.DB.Raw(
|
||||
@@ -317,26 +323,44 @@ func (m _Migrator) DropIndex(value interface{}, name string) error {
|
||||
})
|
||||
}
|
||||
|
||||
func buildConstraint(constraint *schema.Constraint) (sql string, results []interface{}) {
|
||||
sql = "CONSTRAINT ? FOREIGN KEY ? REFERENCES ??"
|
||||
if constraint.OnDelete != "" {
|
||||
sql += " ON DELETE " + constraint.OnDelete
|
||||
}
|
||||
type _Index struct {
|
||||
Seq int
|
||||
Name string
|
||||
Unique bool
|
||||
Origin string
|
||||
Partial bool
|
||||
}
|
||||
|
||||
if constraint.OnUpdate != "" {
|
||||
sql += " ON UPDATE " + constraint.OnUpdate
|
||||
}
|
||||
|
||||
var foreignKeys, references []interface{}
|
||||
for _, field := range constraint.ForeignKeys {
|
||||
foreignKeys = append(foreignKeys, clause.Column{Name: field.DBName})
|
||||
}
|
||||
|
||||
for _, field := range constraint.References {
|
||||
references = append(references, clause.Column{Name: field.DBName})
|
||||
}
|
||||
results = append(results, clause.Table{Name: constraint.Name}, foreignKeys, clause.Table{Name: constraint.ReferenceSchema.Table}, references)
|
||||
return
|
||||
// GetIndexes return Indexes []gorm.Index and execErr error,
|
||||
// See the [doc]
|
||||
//
|
||||
// [doc]: https://www.sqlite.org/pragma.html#pragma_index_list
|
||||
func (m _Migrator) GetIndexes(value interface{}) ([]gorm.Index, error) {
|
||||
indexes := make([]gorm.Index, 0)
|
||||
err := m.RunWithValue(value, func(stmt *gorm.Statement) error {
|
||||
rst := make([]*_Index, 0)
|
||||
if err := m.DB.Debug().Raw("SELECT * FROM PRAGMA_index_list(?)", stmt.Table).Scan(&rst).Error; err != nil { // alias `PRAGMA index_list(?)`
|
||||
return err
|
||||
}
|
||||
for _, index := range rst {
|
||||
if index.Origin == "u" { // skip the index was created by a UNIQUE constraint
|
||||
continue
|
||||
}
|
||||
var columns []string
|
||||
if err := m.DB.Raw("SELECT name FROM PRAGMA_index_info(?)", index.Name).Scan(&columns).Error; err != nil { // alias `PRAGMA index_info(?)`
|
||||
return err
|
||||
}
|
||||
indexes = append(indexes, &migrator.Index{
|
||||
TableName: stmt.Table,
|
||||
NameValue: index.Name,
|
||||
ColumnList: columns,
|
||||
PrimaryKeyValue: sql.NullBool{Bool: index.Origin == "pk", Valid: true}, // The exceptions are INTEGER PRIMARY KEY
|
||||
UniqueValue: sql.NullBool{Bool: index.Unique, Valid: true},
|
||||
})
|
||||
}
|
||||
return nil
|
||||
})
|
||||
return indexes, err
|
||||
}
|
||||
|
||||
func (m _Migrator) getRawDDL(table string) (string, error) {
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
package gormlite
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"strconv"
|
||||
|
||||
@@ -46,23 +45,12 @@ func (dialector _Dialector) Initialize(db *gorm.DB) (err error) {
|
||||
db.ConnPool = conn
|
||||
}
|
||||
|
||||
var version string
|
||||
if err := db.ConnPool.QueryRowContext(context.Background(), "select sqlite_version()").Scan(&version); err != nil {
|
||||
return err
|
||||
}
|
||||
// https://sqlite.org/releaselog/3_35_0.html
|
||||
if compareVersion(version, "3.35.0") >= 0 {
|
||||
callbacks.RegisterDefaultCallbacks(db, &callbacks.Config{
|
||||
CreateClauses: []string{"INSERT", "VALUES", "ON CONFLICT", "RETURNING"},
|
||||
UpdateClauses: []string{"UPDATE", "SET", "FROM", "WHERE", "RETURNING"},
|
||||
DeleteClauses: []string{"DELETE", "FROM", "WHERE", "RETURNING"},
|
||||
LastInsertIDReversed: true,
|
||||
})
|
||||
} else {
|
||||
callbacks.RegisterDefaultCallbacks(db, &callbacks.Config{
|
||||
LastInsertIDReversed: true,
|
||||
})
|
||||
}
|
||||
callbacks.RegisterDefaultCallbacks(db, &callbacks.Config{
|
||||
CreateClauses: []string{"INSERT", "VALUES", "ON CONFLICT", "RETURNING"},
|
||||
UpdateClauses: []string{"UPDATE", "SET", "FROM", "WHERE", "RETURNING"},
|
||||
DeleteClauses: []string{"DELETE", "FROM", "WHERE", "RETURNING"},
|
||||
LastInsertIDReversed: true,
|
||||
})
|
||||
|
||||
for k, v := range dialector.ClauseBuilders() {
|
||||
db.ClauseBuilders[k] = v
|
||||
@@ -231,27 +219,3 @@ func (dialectopr _Dialector) RollbackTo(tx *gorm.DB, name string) error {
|
||||
tx.Exec("ROLLBACK TO SAVEPOINT " + name)
|
||||
return nil
|
||||
}
|
||||
|
||||
func compareVersion(version1, version2 string) int {
|
||||
n, m := len(version1), len(version2)
|
||||
i, j := 0, 0
|
||||
for i < n || j < m {
|
||||
x := 0
|
||||
for ; i < n && version1[i] != '.'; i++ {
|
||||
x = x*10 + int(version1[i]-'0')
|
||||
}
|
||||
i++
|
||||
y := 0
|
||||
for ; j < m && version2[j] != '.'; j++ {
|
||||
y = y*10 + int(version2[j]-'0')
|
||||
}
|
||||
j++
|
||||
if x > y {
|
||||
return 1
|
||||
}
|
||||
if x < y {
|
||||
return -1
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
@@ -3,10 +3,11 @@ set -euo pipefail
|
||||
|
||||
cd -P -- "$(dirname -- "$0")"
|
||||
|
||||
rm -rf gorm/ tests/
|
||||
go work use -r .
|
||||
go test
|
||||
|
||||
rm -rf gorm/ tests/
|
||||
git clone --filter=blob:none https://github.com/go-gorm/gorm.git
|
||||
git clone --branch v1.25.7 --filter=blob:none https://github.com/go-gorm/gorm.git
|
||||
mv gorm/tests tests
|
||||
rm -rf gorm/
|
||||
|
||||
|
||||
@@ -75,6 +75,19 @@ func ExportFuncVIIIII[T0, T1, T2, T3, T4 i32](mod wazero.HostModuleBuilder, name
|
||||
Export(name)
|
||||
}
|
||||
|
||||
type funcVIIIIJ[T0, T1, T2, T3 i32, T4 i64] func(context.Context, api.Module, T0, T1, T2, T3, T4)
|
||||
|
||||
func (fn funcVIIIIJ[T0, T1, T2, T3, T4]) Call(ctx context.Context, mod api.Module, stack []uint64) {
|
||||
fn(ctx, mod, T0(stack[0]), T1(stack[1]), T2(stack[2]), T3(stack[3]), T4(stack[4]))
|
||||
}
|
||||
|
||||
func ExportFuncVIIIIJ[T0, T1, T2, T3 i32, T4 i64](mod wazero.HostModuleBuilder, name string, fn func(context.Context, api.Module, T0, T1, T2, T3, T4)) {
|
||||
mod.NewFunctionBuilder().
|
||||
WithGoModuleFunction(funcVIIIIJ[T0, T1, T2, T3, T4](fn),
|
||||
[]api.ValueType{api.ValueTypeI32, api.ValueTypeI32, api.ValueTypeI32, api.ValueTypeI32, api.ValueTypeI64}, nil).
|
||||
Export(name)
|
||||
}
|
||||
|
||||
type funcII[TR, T0 i32] func(context.Context, api.Module, T0) TR
|
||||
|
||||
func (fn funcII[TR, T0]) Call(ctx context.Context, mod api.Module, stack []uint64) {
|
||||
@@ -140,6 +153,19 @@ func ExportFuncIIIIII[TR, T0, T1, T2, T3, T4 i32](mod wazero.HostModuleBuilder,
|
||||
Export(name)
|
||||
}
|
||||
|
||||
type funcIIIIIII[TR, T0, T1, T2, T3, T4, T5 i32] func(context.Context, api.Module, T0, T1, T2, T3, T4, T5) TR
|
||||
|
||||
func (fn funcIIIIIII[TR, T0, T1, T2, T3, T4, T5]) Call(ctx context.Context, mod api.Module, stack []uint64) {
|
||||
stack[0] = uint64(fn(ctx, mod, T0(stack[0]), T1(stack[1]), T2(stack[2]), T3(stack[3]), T4(stack[4]), T5(stack[5])))
|
||||
}
|
||||
|
||||
func ExportFuncIIIIIII[TR, T0, T1, T2, T3, T4, T5 i32](mod wazero.HostModuleBuilder, name string, fn func(context.Context, api.Module, T0, T1, T2, T3, T4, T5) TR) {
|
||||
mod.NewFunctionBuilder().
|
||||
WithGoModuleFunction(funcIIIIIII[TR, T0, T1, T2, T3, T4, T5](fn),
|
||||
[]api.ValueType{api.ValueTypeI32, api.ValueTypeI32, api.ValueTypeI32, api.ValueTypeI32, api.ValueTypeI32, api.ValueTypeI32}, []api.ValueType{api.ValueTypeI32}).
|
||||
Export(name)
|
||||
}
|
||||
|
||||
type funcIIIIJ[TR, T0, T1, T2 i32, T3 i64] func(context.Context, api.Module, T0, T1, T2, T3) TR
|
||||
|
||||
func (fn funcIIIIJ[TR, T0, T1, T2, T3]) Call(ctx context.Context, mod api.Module, stack []uint64) {
|
||||
|
||||
12
sqlite.go
12
sqlite.go
@@ -289,7 +289,12 @@ func (a *arena) string(s string) uint32 {
|
||||
}
|
||||
|
||||
func exportCallbacks(env wazero.HostModuleBuilder) wazero.HostModuleBuilder {
|
||||
util.ExportFuncII(env, "go_progress", progressCallback)
|
||||
util.ExportFuncIII(env, "go_busy_handler", busyCallback)
|
||||
util.ExportFuncII(env, "go_progress_handler", progressCallback)
|
||||
util.ExportFuncII(env, "go_commit_hook", commitCallback)
|
||||
util.ExportFuncVI(env, "go_rollback_hook", rollbackCallback)
|
||||
util.ExportFuncVIIIIJ(env, "go_update_hook", updateCallback)
|
||||
util.ExportFuncIIIIIII(env, "go_authorizer", authorizerCallback)
|
||||
util.ExportFuncVIII(env, "go_log", logCallback)
|
||||
util.ExportFuncVI(env, "go_destroy", destroyCallback)
|
||||
util.ExportFuncVIIII(env, "go_func", funcCallback)
|
||||
@@ -297,9 +302,10 @@ func exportCallbacks(env wazero.HostModuleBuilder) wazero.HostModuleBuilder {
|
||||
util.ExportFuncVIII(env, "go_final", finalCallback)
|
||||
util.ExportFuncVII(env, "go_value", valueCallback)
|
||||
util.ExportFuncVIIII(env, "go_inverse", inverseCallback)
|
||||
util.ExportFuncVIIII(env, "go_collation_needed", collationCallback)
|
||||
util.ExportFuncIIIIII(env, "go_compare", compareCallback)
|
||||
util.ExportFuncIIIIII(env, "go_vtab_create", vtabModuleCallback(0))
|
||||
util.ExportFuncIIIIII(env, "go_vtab_connect", vtabModuleCallback(1))
|
||||
util.ExportFuncIIIIII(env, "go_vtab_create", vtabModuleCallback(xCreate))
|
||||
util.ExportFuncIIIIII(env, "go_vtab_connect", vtabModuleCallback(xConnect))
|
||||
util.ExportFuncII(env, "go_vtab_disconnect", vtabDisconnectCallback)
|
||||
util.ExportFuncII(env, "go_vtab_destroy", vtabDestroyCallback)
|
||||
util.ExportFuncIII(env, "go_vtab_best_index", vtabBestIndexCallback)
|
||||
|
||||
556
sqlite3/date.patch
Normal file
556
sqlite3/date.patch
Normal file
@@ -0,0 +1,556 @@
|
||||
# Backport from 3.46.
|
||||
# https://sqlite.org/draft/releaselog/current.html
|
||||
--- sqlite3.c.orig
|
||||
+++ sqlite3.c
|
||||
@@ -71,13 +71,14 @@ struct DateTime {
|
||||
int tz; /* Timezone offset in minutes */
|
||||
double s; /* Seconds */
|
||||
char validJD; /* True (1) if iJD is valid */
|
||||
- char rawS; /* Raw numeric value stored in s */
|
||||
char validYMD; /* True (1) if Y,M,D are valid */
|
||||
char validHMS; /* True (1) if h,m,s are valid */
|
||||
- char validTZ; /* True (1) if tz is valid */
|
||||
- char tzSet; /* Timezone was set explicitly */
|
||||
- char isError; /* An overflow has occurred */
|
||||
- char useSubsec; /* Display subsecond precision */
|
||||
+ char nFloor; /* Days to implement "floor" */
|
||||
+ unsigned rawS : 1; /* Raw numeric value stored in s */
|
||||
+ unsigned isError : 1; /* An overflow has occurred */
|
||||
+ unsigned useSubsec : 1; /* Display subsecond precision */
|
||||
+ unsigned isUtc : 1; /* Time is known to be UTC */
|
||||
+ unsigned isLocal : 1; /* Time is known to be localtime */
|
||||
};
|
||||
|
||||
|
||||
@@ -175,6 +176,8 @@ static int parseTimezone(const char *zDate, DateTime *p){
|
||||
sgn = +1;
|
||||
}else if( c=='Z' || c=='z' ){
|
||||
zDate++;
|
||||
+ p->isLocal = 0;
|
||||
+ p->isUtc = 1;
|
||||
goto zulu_time;
|
||||
}else{
|
||||
return c!=0;
|
||||
@@ -187,7 +190,6 @@ static int parseTimezone(const char *zDate, DateTime *p){
|
||||
p->tz = sgn*(nMn + nHr*60);
|
||||
zulu_time:
|
||||
while( sqlite3Isspace(*zDate) ){ zDate++; }
|
||||
- p->tzSet = 1;
|
||||
return *zDate!=0;
|
||||
}
|
||||
|
||||
@@ -231,7 +233,6 @@ static int parseHhMmSs(const char *zDate, DateTime *p){
|
||||
p->m = m;
|
||||
p->s = s + ms;
|
||||
if( parseTimezone(zDate, p) ) return 1;
|
||||
- p->validTZ = (p->tz!=0)?1:0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -278,15 +279,40 @@ static void computeJD(DateTime *p){
|
||||
p->validJD = 1;
|
||||
if( p->validHMS ){
|
||||
p->iJD += p->h*3600000 + p->m*60000 + (sqlite3_int64)(p->s*1000 + 0.5);
|
||||
- if( p->validTZ ){
|
||||
+ if( p->tz ){
|
||||
p->iJD -= p->tz*60000;
|
||||
p->validYMD = 0;
|
||||
p->validHMS = 0;
|
||||
- p->validTZ = 0;
|
||||
+ p->tz = 0;
|
||||
+ p->isUtc = 1;
|
||||
+ p->isLocal = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+/*
|
||||
+** Given the YYYY-MM-DD information current in p, determine if there
|
||||
+** is day-of-month overflow and set nFloor to the number of days that
|
||||
+** would need to be subtracted from the date in order to bring the
|
||||
+** date back to the end of the month.
|
||||
+*/
|
||||
+static void computeFloor(DateTime *p){
|
||||
+ assert( p->validYMD || p->isError );
|
||||
+ assert( p->D>=0 && p->D<=31 );
|
||||
+ assert( p->M>=0 && p->M<=12 );
|
||||
+ if( p->D<=28 ){
|
||||
+ p->nFloor = 0;
|
||||
+ }else if( (1<<p->M) & 0x15aa ){
|
||||
+ p->nFloor = 0;
|
||||
+ }else if( p->M!=2 ){
|
||||
+ p->nFloor = (p->D==31);
|
||||
+ }else if( p->Y%4!=0 || (p->Y%100==0 && p->Y%400!=0) ){
|
||||
+ p->nFloor = p->D - 28;
|
||||
+ }else{
|
||||
+ p->nFloor = p->D - 29;
|
||||
+ }
|
||||
+}
|
||||
+
|
||||
/*
|
||||
** Parse dates of the form
|
||||
**
|
||||
@@ -325,12 +351,16 @@ static int parseYyyyMmDd(const char *zDate, DateTime *p){
|
||||
p->Y = neg ? -Y : Y;
|
||||
p->M = M;
|
||||
p->D = D;
|
||||
- if( p->validTZ ){
|
||||
+ computeFloor(p);
|
||||
+ if( p->tz ){
|
||||
computeJD(p);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
+
|
||||
+static void clearYMD_HMS_TZ(DateTime *p); /* Forward declaration */
|
||||
+
|
||||
/*
|
||||
** Set the time to the current time reported by the VFS.
|
||||
**
|
||||
@@ -340,6 +370,9 @@ static int setDateTimeToCurrent(sqlite3_context *context, DateTime *p){
|
||||
p->iJD = sqlite3StmtCurrentTime(context);
|
||||
if( p->iJD>0 ){
|
||||
p->validJD = 1;
|
||||
+ p->isUtc = 1;
|
||||
+ p->isLocal = 0;
|
||||
+ clearYMD_HMS_TZ(p);
|
||||
return 0;
|
||||
}else{
|
||||
return 1;
|
||||
@@ -478,7 +511,7 @@ static void computeYMD_HMS(DateTime *p){
|
||||
static void clearYMD_HMS_TZ(DateTime *p){
|
||||
p->validYMD = 0;
|
||||
p->validHMS = 0;
|
||||
- p->validTZ = 0;
|
||||
+ p->tz = 0;
|
||||
}
|
||||
|
||||
#ifndef SQLITE_OMIT_LOCALTIME
|
||||
@@ -610,7 +643,7 @@ static int toLocaltime(
|
||||
p->validHMS = 1;
|
||||
p->validJD = 0;
|
||||
p->rawS = 0;
|
||||
- p->validTZ = 0;
|
||||
+ p->tz = 0;
|
||||
p->isError = 0;
|
||||
return SQLITE_OK;
|
||||
}
|
||||
@@ -630,12 +663,12 @@ static const struct {
|
||||
float rLimit; /* Maximum NNN value for this transform */
|
||||
float rXform; /* Constant used for this transform */
|
||||
} aXformType[] = {
|
||||
- { 6, "second", 4.6427e+14, 1.0 },
|
||||
- { 6, "minute", 7.7379e+12, 60.0 },
|
||||
- { 4, "hour", 1.2897e+11, 3600.0 },
|
||||
- { 3, "day", 5373485.0, 86400.0 },
|
||||
- { 5, "month", 176546.0, 2592000.0 },
|
||||
- { 4, "year", 14713.0, 31536000.0 },
|
||||
+ /* 0 */ { 6, "second", 4.6427e+14, 1.0 },
|
||||
+ /* 1 */ { 6, "minute", 7.7379e+12, 60.0 },
|
||||
+ /* 2 */ { 4, "hour", 1.2897e+11, 3600.0 },
|
||||
+ /* 3 */ { 3, "day", 5373485.0, 86400.0 },
|
||||
+ /* 4 */ { 5, "month", 176546.0, 30.0*86400.0 },
|
||||
+ /* 5 */ { 4, "year", 14713.0, 365.0*86400.0 },
|
||||
};
|
||||
|
||||
/*
|
||||
@@ -667,14 +700,20 @@ static void autoAdjustDate(DateTime *p){
|
||||
** NNN.NNNN seconds
|
||||
** NNN months
|
||||
** NNN years
|
||||
+** +/-YYYY-MM-DD HH:MM:SS.SSS
|
||||
+** ceiling
|
||||
+** floor
|
||||
** start of month
|
||||
** start of year
|
||||
** start of week
|
||||
** start of day
|
||||
** weekday N
|
||||
** unixepoch
|
||||
+** auto
|
||||
** localtime
|
||||
** utc
|
||||
+** subsec
|
||||
+** subsecond
|
||||
**
|
||||
** Return 0 on success and 1 if there is any kind of error. If the error
|
||||
** is in a system call (i.e. localtime()), then an error message is written
|
||||
@@ -705,6 +744,37 @@ static int parseModifier(
|
||||
}
|
||||
break;
|
||||
}
|
||||
+ case 'c': {
|
||||
+ /*
|
||||
+ ** ceiling
|
||||
+ **
|
||||
+ ** Resolve day-of-month overflow by rolling forward into the next
|
||||
+ ** month. As this is the default action, this modifier is really
|
||||
+ ** a no-op that is only included for symmetry. See "floor".
|
||||
+ */
|
||||
+ if( sqlite3_stricmp(z, "ceiling")==0 ){
|
||||
+ computeJD(p);
|
||||
+ clearYMD_HMS_TZ(p);
|
||||
+ rc = 0;
|
||||
+ p->nFloor = 0;
|
||||
+ }
|
||||
+ break;
|
||||
+ }
|
||||
+ case 'f': {
|
||||
+ /*
|
||||
+ ** floor
|
||||
+ **
|
||||
+ ** Resolve day-of-month overflow by rolling back to the end of the
|
||||
+ ** previous month.
|
||||
+ */
|
||||
+ if( sqlite3_stricmp(z, "floor")==0 ){
|
||||
+ computeJD(p);
|
||||
+ p->iJD -= p->nFloor*86400000;
|
||||
+ clearYMD_HMS_TZ(p);
|
||||
+ rc = 0;
|
||||
+ }
|
||||
+ break;
|
||||
+ }
|
||||
case 'j': {
|
||||
/*
|
||||
** julianday
|
||||
@@ -731,7 +801,9 @@ static int parseModifier(
|
||||
** show local time.
|
||||
*/
|
||||
if( sqlite3_stricmp(z, "localtime")==0 && sqlite3NotPureFunc(pCtx) ){
|
||||
- rc = toLocaltime(p, pCtx);
|
||||
+ rc = p->isLocal ? SQLITE_OK : toLocaltime(p, pCtx);
|
||||
+ p->isUtc = 0;
|
||||
+ p->isLocal = 1;
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -756,7 +828,7 @@ static int parseModifier(
|
||||
}
|
||||
#ifndef SQLITE_OMIT_LOCALTIME
|
||||
else if( sqlite3_stricmp(z, "utc")==0 && sqlite3NotPureFunc(pCtx) ){
|
||||
- if( p->tzSet==0 ){
|
||||
+ if( p->isUtc==0 ){
|
||||
i64 iOrigJD; /* Original localtime */
|
||||
i64 iGuess; /* Guess at the corresponding utc time */
|
||||
int cnt = 0; /* Safety to prevent infinite loop */
|
||||
@@ -779,7 +851,8 @@ static int parseModifier(
|
||||
memset(p, 0, sizeof(*p));
|
||||
p->iJD = iGuess;
|
||||
p->validJD = 1;
|
||||
- p->tzSet = 1;
|
||||
+ p->isUtc = 1;
|
||||
+ p->isLocal = 0;
|
||||
}
|
||||
rc = SQLITE_OK;
|
||||
}
|
||||
@@ -799,7 +872,7 @@ static int parseModifier(
|
||||
&& r>=0.0 && r<7.0 && (n=(int)r)==r ){
|
||||
sqlite3_int64 Z;
|
||||
computeYMD_HMS(p);
|
||||
- p->validTZ = 0;
|
||||
+ p->tz = 0;
|
||||
p->validJD = 0;
|
||||
computeJD(p);
|
||||
Z = ((p->iJD + 129600000)/86400000) % 7;
|
||||
@@ -839,7 +912,7 @@ static int parseModifier(
|
||||
p->h = p->m = 0;
|
||||
p->s = 0.0;
|
||||
p->rawS = 0;
|
||||
- p->validTZ = 0;
|
||||
+ p->tz = 0;
|
||||
p->validJD = 0;
|
||||
if( sqlite3_stricmp(z,"month")==0 ){
|
||||
p->D = 1;
|
||||
@@ -910,6 +983,7 @@ static int parseModifier(
|
||||
x = p->M>0 ? (p->M-1)/12 : (p->M-12)/12;
|
||||
p->Y += x;
|
||||
p->M -= x*12;
|
||||
+ computeFloor(p);
|
||||
computeJD(p);
|
||||
p->validHMS = 0;
|
||||
p->validYMD = 0;
|
||||
@@ -956,11 +1030,12 @@ static int parseModifier(
|
||||
z += n;
|
||||
while( sqlite3Isspace(*z) ) z++;
|
||||
n = sqlite3Strlen30(z);
|
||||
- if( n>10 || n<3 ) break;
|
||||
+ if( n<3 || n>10 ) break;
|
||||
if( sqlite3UpperToLower[(u8)z[n-1]]=='s' ) n--;
|
||||
computeJD(p);
|
||||
assert( rc==1 );
|
||||
rRounder = r<0 ? -0.5 : +0.5;
|
||||
+ p->nFloor = 0;
|
||||
for(i=0; i<ArraySize(aXformType); i++){
|
||||
if( aXformType[i].nName==n
|
||||
&& sqlite3_strnicmp(aXformType[i].zName, z, n)==0
|
||||
@@ -968,21 +1043,24 @@ static int parseModifier(
|
||||
){
|
||||
switch( i ){
|
||||
case 4: { /* Special processing to add months */
|
||||
- assert( strcmp(aXformType[i].zName,"month")==0 );
|
||||
+ assert( strcmp(aXformType[4].zName,"month")==0 );
|
||||
computeYMD_HMS(p);
|
||||
p->M += (int)r;
|
||||
x = p->M>0 ? (p->M-1)/12 : (p->M-12)/12;
|
||||
p->Y += x;
|
||||
p->M -= x*12;
|
||||
+ computeFloor(p);
|
||||
p->validJD = 0;
|
||||
r -= (int)r;
|
||||
break;
|
||||
}
|
||||
case 5: { /* Special processing to add years */
|
||||
int y = (int)r;
|
||||
- assert( strcmp(aXformType[i].zName,"year")==0 );
|
||||
+ assert( strcmp(aXformType[5].zName,"year")==0 );
|
||||
computeYMD_HMS(p);
|
||||
+ assert( p->M>=0 && p->M<=12 );
|
||||
p->Y += y;
|
||||
+ computeFloor(p);
|
||||
p->validJD = 0;
|
||||
r -= (int)r;
|
||||
break;
|
||||
@@ -1236,22 +1314,83 @@ static void dateFunc(
|
||||
}
|
||||
}
|
||||
|
||||
+/*
|
||||
+** Compute the number of days after the most recent January 1.
|
||||
+**
|
||||
+** In other words, compute the zero-based day number for the
|
||||
+** current year:
|
||||
+**
|
||||
+** Jan01 = 0, Jan02 = 1, ..., Jan31 = 30, Feb01 = 31, ...
|
||||
+** Dec31 = 364 or 365.
|
||||
+*/
|
||||
+static int daysAfterJan01(DateTime *pDate){
|
||||
+ DateTime jan01 = *pDate;
|
||||
+ assert( jan01.validYMD );
|
||||
+ assert( jan01.validHMS );
|
||||
+ assert( pDate->validJD );
|
||||
+ jan01.validJD = 0;
|
||||
+ jan01.M = 1;
|
||||
+ jan01.D = 1;
|
||||
+ computeJD(&jan01);
|
||||
+ return (int)((pDate->iJD-jan01.iJD+43200000)/86400000);
|
||||
+}
|
||||
+
|
||||
+/*
|
||||
+** Return the number of days after the most recent Monday.
|
||||
+**
|
||||
+** In other words, return the day of the week according
|
||||
+** to this code:
|
||||
+**
|
||||
+** 0=Monday, 1=Tuesday, 2=Wednesday, ..., 6=Sunday.
|
||||
+*/
|
||||
+static int daysAfterMonday(DateTime *pDate){
|
||||
+ assert( pDate->validJD );
|
||||
+ return (int)((pDate->iJD+43200000)/86400000) % 7;
|
||||
+}
|
||||
+
|
||||
+/*
|
||||
+** Return the number of days after the most recent Sunday.
|
||||
+**
|
||||
+** In other words, return the day of the week according
|
||||
+** to this code:
|
||||
+**
|
||||
+** 0=Sunday, 1=Monday, 2=Tues, ..., 6=Saturday
|
||||
+*/
|
||||
+static int daysAfterSunday(DateTime *pDate){
|
||||
+ assert( pDate->validJD );
|
||||
+ return (int)((pDate->iJD+129600000)/86400000) % 7;
|
||||
+}
|
||||
+
|
||||
/*
|
||||
** strftime( FORMAT, TIMESTRING, MOD, MOD, ...)
|
||||
**
|
||||
** Return a string described by FORMAT. Conversions as follows:
|
||||
**
|
||||
-** %d day of month
|
||||
+** %d day of month 01-31
|
||||
+** %e day of month 1-31
|
||||
** %f ** fractional seconds SS.SSS
|
||||
+** %F ISO date. YYYY-MM-DD
|
||||
+** %G ISO year corresponding to %V 0000-9999.
|
||||
+** %g 2-digit ISO year corresponding to %V 00-99
|
||||
** %H hour 00-24
|
||||
-** %j day of year 000-366
|
||||
+** %k hour 0-24 (leading zero converted to space)
|
||||
+** %I hour 01-12
|
||||
+** %j day of year 001-366
|
||||
** %J ** julian day number
|
||||
+** %l hour 1-12 (leading zero converted to space)
|
||||
** %m month 01-12
|
||||
** %M minute 00-59
|
||||
+** %p "am" or "pm"
|
||||
+** %P "AM" or "PM"
|
||||
+** %R time as HH:MM
|
||||
** %s seconds since 1970-01-01
|
||||
** %S seconds 00-59
|
||||
-** %w day of week 0-6 Sunday==0
|
||||
-** %W week of year 00-53
|
||||
+** %T time as HH:MM:SS
|
||||
+** %u day of week 1-7 Monday==1, Sunday==7
|
||||
+** %w day of week 0-6 Sunday==0, Monday==1
|
||||
+** %U week of year 00-53 (First Sunday is start of week 01)
|
||||
+** %V week of year 01-53 (First week containing Thursday is week 01)
|
||||
+** %W week of year 00-53 (First Monday is start of week 01)
|
||||
** %Y year 0000-9999
|
||||
** %% %
|
||||
*/
|
||||
@@ -1288,7 +1427,7 @@ static void strftimeFunc(
|
||||
sqlite3_str_appendf(&sRes, cf=='d' ? "%02d" : "%2d", x.D);
|
||||
break;
|
||||
}
|
||||
- case 'f': {
|
||||
+ case 'f': { /* Fractional seconds. (Non-standard) */
|
||||
double s = x.s;
|
||||
if( s>59.999 ) s = 59.999;
|
||||
sqlite3_str_appendf(&sRes, "%06.3f", s);
|
||||
@@ -1298,6 +1437,21 @@ static void strftimeFunc(
|
||||
sqlite3_str_appendf(&sRes, "%04d-%02d-%02d", x.Y, x.M, x.D);
|
||||
break;
|
||||
}
|
||||
+ case 'G': /* Fall thru */
|
||||
+ case 'g': {
|
||||
+ DateTime y = x;
|
||||
+ assert( y.validJD );
|
||||
+ /* Move y so that it is the Thursday in the same week as x */
|
||||
+ y.iJD += (3 - daysAfterMonday(&x))*86400000;
|
||||
+ y.validYMD = 0;
|
||||
+ computeYMD(&y);
|
||||
+ if( cf=='g' ){
|
||||
+ sqlite3_str_appendf(&sRes, "%02d", y.Y%100);
|
||||
+ }else{
|
||||
+ sqlite3_str_appendf(&sRes, "%04d", y.Y);
|
||||
+ }
|
||||
+ break;
|
||||
+ }
|
||||
case 'H':
|
||||
case 'k': {
|
||||
sqlite3_str_appendf(&sRes, cf=='H' ? "%02d" : "%2d", x.h);
|
||||
@@ -1311,25 +1465,11 @@ static void strftimeFunc(
|
||||
sqlite3_str_appendf(&sRes, cf=='I' ? "%02d" : "%2d", h);
|
||||
break;
|
||||
}
|
||||
- case 'W': /* Fall thru */
|
||||
- case 'j': {
|
||||
- int nDay; /* Number of days since 1st day of year */
|
||||
- DateTime y = x;
|
||||
- y.validJD = 0;
|
||||
- y.M = 1;
|
||||
- y.D = 1;
|
||||
- computeJD(&y);
|
||||
- nDay = (int)((x.iJD-y.iJD+43200000)/86400000);
|
||||
- if( cf=='W' ){
|
||||
- int wd; /* 0=Monday, 1=Tuesday, ... 6=Sunday */
|
||||
- wd = (int)(((x.iJD+43200000)/86400000)%7);
|
||||
- sqlite3_str_appendf(&sRes,"%02d",(nDay+7-wd)/7);
|
||||
- }else{
|
||||
- sqlite3_str_appendf(&sRes,"%03d",nDay+1);
|
||||
- }
|
||||
+ case 'j': { /* Day of year. Jan01==1, Jan02==2, and so forth */
|
||||
+ sqlite3_str_appendf(&sRes,"%03d",daysAfterJan01(&x)+1);
|
||||
break;
|
||||
}
|
||||
- case 'J': {
|
||||
+ case 'J': { /* Julian day number. (Non-standard) */
|
||||
sqlite3_str_appendf(&sRes,"%.16g",x.iJD/86400000.0);
|
||||
break;
|
||||
}
|
||||
@@ -1372,13 +1512,33 @@ static void strftimeFunc(
|
||||
sqlite3_str_appendf(&sRes,"%02d:%02d:%02d", x.h, x.m, (int)x.s);
|
||||
break;
|
||||
}
|
||||
- case 'u': /* Fall thru */
|
||||
- case 'w': {
|
||||
- char c = (char)(((x.iJD+129600000)/86400000) % 7) + '0';
|
||||
+ case 'u': /* Day of week. 1 to 7. Monday==1, Sunday==7 */
|
||||
+ case 'w': { /* Day of week. 0 to 6. Sunday==0, Monday==1 */
|
||||
+ char c = (char)daysAfterSunday(&x) + '0';
|
||||
if( c=='0' && cf=='u' ) c = '7';
|
||||
sqlite3_str_appendchar(&sRes, 1, c);
|
||||
break;
|
||||
}
|
||||
+ case 'U': { /* Week num. 00-53. First Sun of the year is week 01 */
|
||||
+ sqlite3_str_appendf(&sRes,"%02d",
|
||||
+ (daysAfterJan01(&x)-daysAfterSunday(&x)+7)/7);
|
||||
+ break;
|
||||
+ }
|
||||
+ case 'V': { /* Week num. 01-53. First week with a Thur is week 01 */
|
||||
+ DateTime y = x;
|
||||
+ /* Adjust y so that is the Thursday in the same week as x */
|
||||
+ assert( y.validJD );
|
||||
+ y.iJD += (3 - daysAfterMonday(&x))*86400000;
|
||||
+ y.validYMD = 0;
|
||||
+ computeYMD(&y);
|
||||
+ sqlite3_str_appendf(&sRes,"%02d", daysAfterJan01(&y)/7+1);
|
||||
+ break;
|
||||
+ }
|
||||
+ case 'W': { /* Week num. 00-53. First Mon of the year is week 01 */
|
||||
+ sqlite3_str_appendf(&sRes,"%02d",
|
||||
+ (daysAfterJan01(&x)-daysAfterMonday(&x)+7)/7);
|
||||
+ break;
|
||||
+ }
|
||||
case 'Y': {
|
||||
sqlite3_str_appendf(&sRes,"%04d",x.Y);
|
||||
break;
|
||||
@@ -1525,9 +1685,7 @@ static void timediffFunc(
|
||||
d1.iJD = d2.iJD - d1.iJD;
|
||||
d1.iJD += (u64)1486995408 * (u64)100000;
|
||||
}
|
||||
- d1.validYMD = 0;
|
||||
- d1.validHMS = 0;
|
||||
- d1.validTZ = 0;
|
||||
+ clearYMD_HMS_TZ(&d1);
|
||||
computeYMD_HMS(&d1);
|
||||
sqlite3StrAccumInit(&sRes, 0, 0, 0, 100);
|
||||
sqlite3_str_appendf(&sRes, "%c%04d-%02d-%02d %02d:%02d:%06.3f",
|
||||
@@ -1596,6 +1754,36 @@ static void currentTimeFunc(
|
||||
}
|
||||
#endif
|
||||
|
||||
+#if !defined(SQLITE_OMIT_DATETIME_FUNCS) && defined(SQLITE_DEBUG)
|
||||
+/*
|
||||
+** datedebug(...)
|
||||
+**
|
||||
+** This routine returns JSON that describes the internal DateTime object.
|
||||
+** Used for debugging and testing only. Subject to change.
|
||||
+*/
|
||||
+static void datedebugFunc(
|
||||
+ sqlite3_context *context,
|
||||
+ int argc,
|
||||
+ sqlite3_value **argv
|
||||
+){
|
||||
+ DateTime x;
|
||||
+ if( isDate(context, argc, argv, &x)==0 ){
|
||||
+ char *zJson;
|
||||
+ zJson = sqlite3_mprintf(
|
||||
+ "{iJD:%lld,Y:%d,M:%d,D:%d,h:%d,m:%d,tz:%d,"
|
||||
+ "s:%.3f,validJD:%d,validYMS:%d,validHMS:%d,"
|
||||
+ "nFloor:%d,rawS:%d,isError:%d,useSubsec:%d,"
|
||||
+ "isUtc:%d,isLocal:%d}",
|
||||
+ x.iJD, x.Y, x.M, x.D, x.h, x.m, x.tz,
|
||||
+ x.s, x.validJD, x.validYMD, x.validHMS,
|
||||
+ x.nFloor, x.rawS, x.isError, x.useSubsec,
|
||||
+ x.isUtc, x.isLocal);
|
||||
+ sqlite3_result_text(context, zJson, -1, sqlite3_free);
|
||||
+ }
|
||||
+}
|
||||
+#endif /* !SQLITE_OMIT_DATETIME_FUNCS && SQLITE_DEBUG */
|
||||
+
|
||||
+
|
||||
/*
|
||||
** This function registered all of the above C functions as SQL
|
||||
** functions. This should be the only routine in this file with
|
||||
@@ -1611,6 +1799,9 @@ void sqlite3RegisterDateTimeFunctions(void){
|
||||
PURE_DATE(datetime, -1, 0, 0, datetimeFunc ),
|
||||
PURE_DATE(strftime, -1, 0, 0, strftimeFunc ),
|
||||
PURE_DATE(timediff, 2, 0, 0, timediffFunc ),
|
||||
+#ifdef SQLITE_DEBUG
|
||||
+ PURE_DATE(datedebug, -1, 0, 0, datedebugFunc ),
|
||||
+#endif
|
||||
DFUNCTION(current_time, 0, 0, 0, ctimeFunc ),
|
||||
DFUNCTION(current_timestamp, 0, 0, 0, ctimestampFunc),
|
||||
DFUNCTION(current_date, 0, 0, 0, cdateFunc ),
|
||||
@@ -3,7 +3,7 @@ set -euo pipefail
|
||||
|
||||
cd -P -- "$(dirname -- "$0")"
|
||||
|
||||
curl -#OL "https://sqlite.org/2024/sqlite-amalgamation-3450000.zip"
|
||||
curl -#OL "https://sqlite.org/2024/sqlite-amalgamation-3450200.zip"
|
||||
unzip -d . sqlite-amalgamation-*.zip
|
||||
mv sqlite-amalgamation-*/sqlite3* .
|
||||
rm -rf sqlite-amalgamation-*
|
||||
@@ -12,25 +12,25 @@ cat *.patch | patch --no-backup-if-mismatch
|
||||
|
||||
mkdir -p ext/
|
||||
cd ext/
|
||||
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.45.0/ext/misc/anycollseq.c"
|
||||
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.45.0/ext/misc/base64.c"
|
||||
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.45.0/ext/misc/decimal.c"
|
||||
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.45.0/ext/misc/ieee754.c"
|
||||
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.45.0/ext/misc/regexp.c"
|
||||
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.45.0/ext/misc/series.c"
|
||||
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.45.0/ext/misc/uint.c"
|
||||
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.45.0/ext/misc/uuid.c"
|
||||
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.45.2/ext/misc/anycollseq.c"
|
||||
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.45.2/ext/misc/base64.c"
|
||||
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.45.2/ext/misc/decimal.c"
|
||||
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.45.2/ext/misc/ieee754.c"
|
||||
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.45.2/ext/misc/regexp.c"
|
||||
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.45.2/ext/misc/series.c"
|
||||
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.45.2/ext/misc/uint.c"
|
||||
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.45.2/ext/misc/uuid.c"
|
||||
cd ~-
|
||||
|
||||
cd ../vfs/tests/mptest/testdata/
|
||||
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.45.0/mptest/mptest.c"
|
||||
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.45.0/mptest/config01.test"
|
||||
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.45.0/mptest/config02.test"
|
||||
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.45.0/mptest/crash01.test"
|
||||
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.45.0/mptest/crash02.subtest"
|
||||
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.45.0/mptest/multiwrite01.test"
|
||||
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.45.2/mptest/mptest.c"
|
||||
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.45.2/mptest/config01.test"
|
||||
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.45.2/mptest/config02.test"
|
||||
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.45.2/mptest/crash01.test"
|
||||
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.45.2/mptest/crash02.subtest"
|
||||
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.45.2/mptest/multiwrite01.test"
|
||||
cd ~-
|
||||
|
||||
cd ../vfs/tests/speedtest1/testdata/
|
||||
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.45.0/test/speedtest1.c"
|
||||
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.45.2/test/speedtest1.c"
|
||||
cd ~-
|
||||
@@ -1,8 +1,11 @@
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
|
||||
#include "include.h"
|
||||
#include "sqlite3.h"
|
||||
|
||||
void go_collation_needed(void *, sqlite3 *, int, const char *);
|
||||
|
||||
int go_compare(go_handle, int, const void *, int, const void *);
|
||||
|
||||
void go_func(sqlite3_context *, go_handle, int, sqlite3_value **);
|
||||
@@ -44,6 +47,11 @@ void go_inverse_wrapper(sqlite3_context *ctx, int nArg, sqlite3_value **pArg) {
|
||||
go_inverse(ctx, *agg, nArg, pArg);
|
||||
}
|
||||
|
||||
int sqlite3_collation_needed_go(sqlite3 *db, bool enable) {
|
||||
return sqlite3_collation_needed(db, /*arg=*/NULL,
|
||||
enable ? go_collation_needed : NULL);
|
||||
}
|
||||
|
||||
int sqlite3_create_collation_go(sqlite3 *db, const char *name, go_handle app) {
|
||||
int rc = sqlite3_create_collation_v2(db, name, SQLITE_UTF8, app, go_compare,
|
||||
go_destroy);
|
||||
|
||||
44
sqlite3/hooks.c
Normal file
44
sqlite3/hooks.c
Normal file
@@ -0,0 +1,44 @@
|
||||
#include <stdbool.h>
|
||||
|
||||
#include "sqlite3.h"
|
||||
|
||||
int go_progress_handler(void *);
|
||||
int go_busy_handler(void *, int);
|
||||
|
||||
int go_commit_hook(void *);
|
||||
void go_rollback_hook(void *);
|
||||
void go_update_hook(void *, int, char const *, char const *, sqlite3_int64);
|
||||
|
||||
int go_authorizer(void *, int, const char *, const char *, const char *,
|
||||
const char *);
|
||||
|
||||
void go_log(void *, int, const char *);
|
||||
|
||||
void sqlite3_progress_handler_go(sqlite3 *db, int n) {
|
||||
sqlite3_progress_handler(db, n, go_progress_handler, /*arg=*/db);
|
||||
}
|
||||
|
||||
int sqlite3_busy_handler_go(sqlite3 *db, bool enable) {
|
||||
return sqlite3_busy_handler(db, enable ? go_busy_handler : NULL, /*arg=*/db);
|
||||
}
|
||||
|
||||
void sqlite3_commit_hook_go(sqlite3 *db, bool enable) {
|
||||
sqlite3_commit_hook(db, enable ? go_commit_hook : NULL, /*arg=*/db);
|
||||
}
|
||||
|
||||
void sqlite3_rollback_hook_go(sqlite3 *db, bool enable) {
|
||||
sqlite3_rollback_hook(db, enable ? go_rollback_hook : NULL, /*arg=*/db);
|
||||
}
|
||||
|
||||
void sqlite3_update_hook_go(sqlite3 *db, bool enable) {
|
||||
sqlite3_update_hook(db, enable ? go_update_hook : NULL, /*arg=*/db);
|
||||
}
|
||||
|
||||
int sqlite3_set_authorizer_go(sqlite3 *db, bool enable) {
|
||||
return sqlite3_set_authorizer(db, enable ? go_authorizer : NULL, /*arg=*/db);
|
||||
}
|
||||
|
||||
int sqlite3_config_log_go(bool enable) {
|
||||
return sqlite3_config(SQLITE_CONFIG_LOG, enable ? go_log : NULL,
|
||||
/*arg=*/NULL);
|
||||
}
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
// https://github.com/JuliaLang/julia/blob/v1.9.4/src/julia.h#L67-L68
|
||||
#define container_of(ptr, type, member) \
|
||||
((type *)((char *)(ptr)-offsetof(type, member)))
|
||||
((type *)((char *)(ptr) - offsetof(type, member)))
|
||||
|
||||
typedef void *go_handle;
|
||||
void go_destroy(go_handle);
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
# ISO week date specifiers.
|
||||
# https://sqlite.org/forum/forumpost/73d99e4497e8e6a7
|
||||
--- sqlite3.c.orig
|
||||
+++ sqlite3.c
|
||||
@@ -1373,6 +1373,29 @@ static void strftimeFunc(
|
||||
sqlite3_str_appendchar(&sRes, 1, c);
|
||||
break;
|
||||
}
|
||||
+ case 'V': /* Fall thru */
|
||||
+ case 'G': {
|
||||
+ DateTime y = x;
|
||||
+ computeJD(&y);
|
||||
+ y.validYMD = 0;
|
||||
+ /* Adjust date to Thursday this week:
|
||||
+ The number in parentheses is 0 for Monday, 3 for Thursday */
|
||||
+ y.iJD += (3 - (((y.iJD+43200000)/86400000) % 7))*86400000;
|
||||
+ computeYMD(&y);
|
||||
+ if( cf=='G' ){
|
||||
+ sqlite3_str_appendf(&sRes,"%04d",y.Y);
|
||||
+ }else{
|
||||
+ int nDay; /* Number of days since 1st day of year */
|
||||
+ i64 tJD = y.iJD;
|
||||
+ y.validJD = 0;
|
||||
+ y.M = 1;
|
||||
+ y.D = 1;
|
||||
+ computeJD(&y);
|
||||
+ nDay = (int)((tJD-y.iJD+43200000)/86400000);
|
||||
+ sqlite3_str_appendf(&sRes,"%02d",nDay/7+1);
|
||||
+ }
|
||||
+ break;
|
||||
+ }
|
||||
case 'Y': {
|
||||
sqlite3_str_appendf(&sRes,"%04d",x.Y);
|
||||
break;
|
||||
@@ -1,7 +1,7 @@
|
||||
# Use exclusive locking mode for WAL databases with v1 VFSes.
|
||||
--- sqlite3.c.orig
|
||||
+++ sqlite3.c
|
||||
@@ -63210,7 +63210,9 @@
|
||||
@@ -64209,7 +64209,9 @@
|
||||
SQLITE_PRIVATE int sqlite3PagerWalSupported(Pager *pPager){
|
||||
const sqlite3_io_methods *pMethods = pPager->fd->pMethods;
|
||||
if( pPager->noLock ) return 0;
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
#include <stdbool.h>
|
||||
|
||||
#include "sqlite3.h"
|
||||
|
||||
void go_log(void *, int, const char *);
|
||||
|
||||
int sqlite3_config_log_go(bool enable) {
|
||||
return sqlite3_config(SQLITE_CONFIG_LOG, enable ? go_log : NULL, NULL);
|
||||
}
|
||||
@@ -12,9 +12,8 @@
|
||||
// Bindings
|
||||
#include "column.c"
|
||||
#include "func.c"
|
||||
#include "log.c"
|
||||
#include "hooks.c"
|
||||
#include "pointer.c"
|
||||
#include "progress.c"
|
||||
#include "time.c"
|
||||
#include "vfs.c"
|
||||
#include "vtab.c"
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
#include <stddef.h>
|
||||
|
||||
#include "sqlite3.h"
|
||||
|
||||
int go_progress(void *);
|
||||
|
||||
void sqlite3_progress_handler_go(sqlite3 *db, int n) {
|
||||
sqlite3_progress_handler(db, n, go_progress, /*arg=*/NULL);
|
||||
}
|
||||
@@ -9,10 +9,12 @@
|
||||
#define HAVE_INT16_T 1
|
||||
#define HAVE_INT32_T 1
|
||||
#define HAVE_INT64_T 1
|
||||
#define HAVE_INTPTR_T 1
|
||||
#define HAVE_UINT8_T 1
|
||||
#define HAVE_UINT16_T 1
|
||||
#define HAVE_UINT32_T 1
|
||||
#define HAVE_UINT64_T 1
|
||||
#define HAVE_UINTPTR_T 1
|
||||
#define HAVE_STDINT_H 1
|
||||
#define HAVE_INTTYPES_H 1
|
||||
|
||||
@@ -20,6 +22,8 @@
|
||||
#define HAVE_LOG10 1
|
||||
#define HAVE_ISNAN 1
|
||||
|
||||
#define HAVE_STRCHRNUL 1
|
||||
|
||||
#define HAVE_USLEEP 1
|
||||
#define HAVE_NANOSLEEP 1
|
||||
|
||||
@@ -37,6 +41,7 @@
|
||||
#define SQLITE_DEFAULT_WAL_SYNCHRONOUS 1
|
||||
#define SQLITE_LIKE_DOESNT_MATCH_BLOBS
|
||||
#define SQLITE_MAX_EXPR_DEPTH 0
|
||||
#define SQLITE_STRICT_SUBTYPE 1
|
||||
#define SQLITE_USE_ALLOCA
|
||||
#define SQLITE_OMIT_DEPRECATED
|
||||
#define SQLITE_OMIT_SHARED_CACHE
|
||||
@@ -68,18 +73,12 @@
|
||||
|
||||
#define SQLITE_ENABLE_MATH_FUNCTIONS 1
|
||||
#define SQLITE_ENABLE_JSON1 1
|
||||
#define SQLITE_ENABLE_FTS3 1
|
||||
#define SQLITE_ENABLE_FTS3_PARENTHESIS 1
|
||||
#define SQLITE_ENABLE_FTS4 1
|
||||
#define SQLITE_ENABLE_FTS5 1
|
||||
#define SQLITE_ENABLE_RTREE 1
|
||||
#define SQLITE_ENABLE_GEOPOLY 1
|
||||
|
||||
#define SQLITE_SOUNDEX
|
||||
|
||||
// Session Extension
|
||||
// #define SQLITE_ENABLE_SESSION
|
||||
// #define SQLITE_ENABLE_PREUPDATE_HOOK
|
||||
#define SQLITE_UNTESTABLE
|
||||
|
||||
// Implemented in vfs.c.
|
||||
int localtime_s(struct tm *const pTm, time_t const *const pTime);
|
||||
@@ -27,63 +27,10 @@ static int time_collation(void *pArg, int nKey1, const void *pKey1, int nKey2,
|
||||
return rc;
|
||||
}
|
||||
|
||||
static void json_time_func(sqlite3_context *context, int argc,
|
||||
sqlite3_value **argv) {
|
||||
DateTime x;
|
||||
if (isDate(context, argc, argv, &x)) return;
|
||||
if (x.tzSet && x.tz) {
|
||||
x.iJD += x.tz * 60000;
|
||||
if (!validJulianDay(x.iJD)) return;
|
||||
x.validYMD = 0;
|
||||
x.validHMS = 0;
|
||||
}
|
||||
computeYMD_HMS(&x);
|
||||
|
||||
sqlite3 *db = sqlite3_context_db_handle(context);
|
||||
sqlite3_str *res = sqlite3_str_new(db);
|
||||
|
||||
sqlite3_str_appendf(res, "%04d-%02d-%02dT%02d:%02d:%02d", //
|
||||
x.Y, x.M, x.D, //
|
||||
x.h, x.m, (int)(x.iJD / 1000 % 60));
|
||||
|
||||
if (x.useSubsec) {
|
||||
int rem = x.iJD % 1000;
|
||||
if (rem) {
|
||||
sqlite3_str_appendchar(res, 1, '.');
|
||||
sqlite3_str_appendchar(res, 1, '0' + rem / 100);
|
||||
if ((rem %= 100)) {
|
||||
sqlite3_str_appendchar(res, 1, '0' + rem / 10);
|
||||
if ((rem %= 10)) {
|
||||
sqlite3_str_appendchar(res, 1, '0' + rem);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (x.tz) {
|
||||
sqlite3_str_appendf(res, "%+03d:%02d", x.tz / 60, abs(x.tz) % 60);
|
||||
} else {
|
||||
sqlite3_str_appendchar(res, 1, 'Z');
|
||||
}
|
||||
|
||||
int rc = sqlite3_str_errcode(res);
|
||||
if (rc) {
|
||||
sqlite3_result_error_code(context, rc);
|
||||
return;
|
||||
}
|
||||
|
||||
int n = sqlite3_str_length(res);
|
||||
sqlite3_result_text(context, sqlite3_str_finish(res), n, sqlite3_free);
|
||||
}
|
||||
|
||||
int sqlite3_time_init(sqlite3 *db, char **pzErrMsg,
|
||||
const sqlite3_api_routines *pApi) {
|
||||
sqlite3_create_collation_v2(db, "time", SQLITE_UTF8, /*arg=*/NULL,
|
||||
time_collation,
|
||||
/*destroy=*/NULL);
|
||||
sqlite3_create_function_v2(
|
||||
db, "json_time", /*nArg=*/-1,
|
||||
SQLITE_UTF8 | SQLITE_DETERMINISTIC | SQLITE_INNOCUOUS, /*arg=*/NULL,
|
||||
json_time_func, /*step=*/NULL, /*final=*/NULL, /*destroy=*/NULL);
|
||||
return SQLITE_OK;
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
# Set UTC timezone, compute local offset.
|
||||
--- sqlite3.c.orig
|
||||
+++ sqlite3.c
|
||||
@@ -340,6 +340,7 @@ static int setDateTimeToCurrent(sqlite3_context *context, DateTime *p){
|
||||
p->iJD = sqlite3StmtCurrentTime(context);
|
||||
if( p->iJD>0 ){
|
||||
p->validJD = 1;
|
||||
+ p->tzSet = 1;
|
||||
return 0;
|
||||
}else{
|
||||
return 1;
|
||||
@@ -355,6 +356,7 @@ static int setDateTimeToCurrent(sqlite3_context *context, DateTime *p){
|
||||
static void setRawDateNumber(DateTime *p, double r){
|
||||
p->s = r;
|
||||
p->rawS = 1;
|
||||
+ p->tzSet = 1;
|
||||
if( r>=0.0 && r<5373484.5 ){
|
||||
p->iJD = (sqlite3_int64)(r*86400000.0 + 0.5);
|
||||
p->validJD = 1;
|
||||
@@ -731,7 +733,16 @@ static int parseModifier(
|
||||
** show local time.
|
||||
*/
|
||||
if( sqlite3_stricmp(z, "localtime")==0 && sqlite3NotPureFunc(pCtx) ){
|
||||
- rc = toLocaltime(p, pCtx);
|
||||
+ if( p->tzSet!=0 || p->tz==0 ) {
|
||||
+ rc = toLocaltime(p, pCtx);
|
||||
+ i64 iOrigJD = p->iJD;
|
||||
+ p->tzSet = 0;
|
||||
+ computeJD(p);
|
||||
+ p->tz = (p->iJD-iOrigJD)/60000;
|
||||
+ if( abs(p->tz)>= 900 ) p->tz = 0;
|
||||
+ } else {
|
||||
+ rc = 0;
|
||||
+ }
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -781,6 +792,7 @@ static int parseModifier(
|
||||
p->validJD = 1;
|
||||
p->tzSet = 1;
|
||||
}
|
||||
+ p->tz = 0;
|
||||
rc = SQLITE_OK;
|
||||
}
|
||||
#endif
|
||||
@@ -1,7 +1,7 @@
|
||||
# Wrap sqlite3_vfs_find.
|
||||
--- sqlite3.c.orig
|
||||
+++ sqlite3.c
|
||||
@@ -25394,7 +25394,7 @@
|
||||
@@ -26089,7 +26089,7 @@
|
||||
** Locate a VFS by name. If no name is given, simply return the
|
||||
** first VFS on the list.
|
||||
*/
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
#define SQLITE_VTAB_RENAMER_GO /******/ 0x08
|
||||
#define SQLITE_VTAB_OVERLOADER_GO /***/ 0x10
|
||||
#define SQLITE_VTAB_CHECKER_GO /******/ 0x20
|
||||
#define SQLITE_VTAB_TX_GO /***********/ 0x40
|
||||
#define SQLITE_VTAB_TXN_GO /**********/ 0x40
|
||||
#define SQLITE_VTAB_SAVEPOINTER_GO /**/ 0x80
|
||||
|
||||
int go_vtab_create(sqlite3_module *, int argc, const char *const *argv,
|
||||
@@ -193,7 +193,7 @@ int sqlite3_create_module_go(sqlite3 *db, const char *zName, int flags,
|
||||
if (flags & SQLITE_VTAB_CHECKER_GO) {
|
||||
mod->base.xIntegrity = go_vtab_integrity_wrapper;
|
||||
}
|
||||
if (flags & SQLITE_VTAB_TX_GO) {
|
||||
if (flags & SQLITE_VTAB_TXN_GO) {
|
||||
mod->base.xBegin = go_vtab_begin;
|
||||
mod->base.xSync = go_vtab_sync;
|
||||
mod->base.xCommit = go_vtab_commit;
|
||||
@@ -218,6 +218,7 @@ int sqlite3_vtab_config_go(sqlite3 *db, int op, int constraint) {
|
||||
return sqlite3_vtab_config(db, op, constraint);
|
||||
}
|
||||
|
||||
static_assert(offsetof(struct sqlite3_vtab, zErrMsg) == 8, "Unexpected offset");
|
||||
static_assert(offsetof(struct go_module, base) == 4, "Unexpected offset");
|
||||
static_assert(offsetof(struct go_vtab, base) == 4, "Unexpected offset");
|
||||
static_assert(offsetof(struct go_cursor, base) == 4, "Unexpected offset");
|
||||
|
||||
3
stmt.go
3
stmt.go
@@ -111,6 +111,9 @@ func (s *Stmt) Exec() error {
|
||||
//
|
||||
// https://sqlite.org/c3ref/stmt_status.html
|
||||
func (s *Stmt) Status(op StmtStatus, reset bool) int {
|
||||
if op > STMTSTATUS_FILTER_HIT && op != STMTSTATUS_MEMUSED {
|
||||
return 0
|
||||
}
|
||||
var i uint64
|
||||
if reset {
|
||||
i = 1
|
||||
|
||||
@@ -371,6 +371,12 @@ func TestBlob_Reopen(t *testing.T) {
|
||||
}
|
||||
rowids = append(rowids, db.LastInsertRowID())
|
||||
}
|
||||
if changes := db.Changes(); changes != 1 {
|
||||
t.Errorf("got %d want 1", changes)
|
||||
}
|
||||
if changes := db.TotalChanges(); changes != 100 {
|
||||
t.Errorf("got %d want 100", changes)
|
||||
}
|
||||
|
||||
var blob *sqlite3.Blob
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ package tests
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"math"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
@@ -364,3 +365,123 @@ func TestConn_ConfigLog(t *testing.T) {
|
||||
t.Error("want sqlite3.ERROR")
|
||||
}
|
||||
}
|
||||
|
||||
func TestConn_Limit(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
db, err := sqlite3.Open(":memory:")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
l := db.Limit(sqlite3.LIMIT_COLUMN, -1)
|
||||
if l != 2000 {
|
||||
t.Errorf("got %d, want 2000", l)
|
||||
}
|
||||
|
||||
l = db.Limit(sqlite3.LIMIT_COLUMN, 100)
|
||||
if l != 2000 {
|
||||
t.Errorf("got %d, want 2000", l)
|
||||
}
|
||||
|
||||
l = db.Limit(sqlite3.LIMIT_COLUMN, -1)
|
||||
if l != 100 {
|
||||
t.Errorf("got %d, want 100", l)
|
||||
}
|
||||
|
||||
l = db.Limit(math.MaxUint32, -1)
|
||||
if l != -1 {
|
||||
t.Errorf("got %d, want -1", l)
|
||||
}
|
||||
}
|
||||
|
||||
func TestConn_SetAuthorizer(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
db, err := sqlite3.Open(":memory:")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
err = db.SetAuthorizer(func(action sqlite3.AuthorizerActionCode, name3rd, name4th, schema, nameInner string) sqlite3.AuthorizerReturnCode {
|
||||
return sqlite3.AUTH_DENY
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = db.Exec(`SELECT * FROM sqlite_schema`)
|
||||
if !errors.Is(err, sqlite3.AUTH) {
|
||||
t.Errorf("got %v, want sqlite3.AUTH", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestConn_ReleaseMemory(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
db, err := sqlite3.Open(":memory:")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
err = db.ReleaseMemory()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestConn_SetLastInsertRowID(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
db, err := sqlite3.Open(":memory:")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
db.SetLastInsertRowID(42)
|
||||
|
||||
got := db.LastInsertRowID()
|
||||
if got != 42 {
|
||||
t.Errorf("got %d, want 42", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestConn_ReadOnly(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
db, err := sqlite3.Open(":memory:")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
if ro, ok := db.ReadOnly(""); ro != false || ok != false {
|
||||
t.Errorf("got %v,%v", ro, ok)
|
||||
}
|
||||
|
||||
if ro, ok := db.ReadOnly("xpto"); ro != false || ok != true {
|
||||
t.Errorf("got %v,%v", ro, ok)
|
||||
}
|
||||
}
|
||||
|
||||
func TestConn_DBName(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
db, err := sqlite3.Open(":memory:")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
if name := db.DBName(0); name != "main" {
|
||||
t.Errorf("got %s", name)
|
||||
}
|
||||
|
||||
if name := db.DBName(5); name != "" {
|
||||
t.Errorf("got %s", name)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"golang.org/x/sync/errgroup"
|
||||
|
||||
@@ -39,10 +40,7 @@ func TestMemory(t *testing.T) {
|
||||
iter = 5000
|
||||
}
|
||||
|
||||
name := "file:/test.db?vfs=memdb" +
|
||||
"&_pragma=busy_timeout(10000)" +
|
||||
"&_pragma=journal_mode(memory)" +
|
||||
"&_pragma=synchronous(off)"
|
||||
name := "file:/test.db?vfs=memdb"
|
||||
testParallel(t, name, iter)
|
||||
testIntegrity(t, name)
|
||||
}
|
||||
@@ -100,10 +98,7 @@ func TestChildProcess(t *testing.T) {
|
||||
|
||||
func BenchmarkMemory(b *testing.B) {
|
||||
memdb.Delete("test.db")
|
||||
name := "file:/test.db?vfs=memdb" +
|
||||
"&_pragma=busy_timeout(10000)" +
|
||||
"&_pragma=journal_mode(memory)" +
|
||||
"&_pragma=synchronous(off)"
|
||||
name := "file:/test.db?vfs=memdb"
|
||||
testParallel(b, name, b.N)
|
||||
}
|
||||
|
||||
@@ -115,6 +110,14 @@ func testParallel(t testing.TB, name string, n int) {
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
err = db.BusyHandler(func(count int) (retry bool) {
|
||||
time.Sleep(time.Millisecond)
|
||||
return true
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = db.Exec(`CREATE TABLE IF NOT EXISTS users (id INT, name VARCHAR(10))`)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -135,7 +138,7 @@ func testParallel(t testing.TB, name string, n int) {
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
err = db.Exec(`PRAGMA busy_timeout=10000`)
|
||||
err = db.BusyTimeout(10 * time.Second)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -221,28 +221,6 @@ func TestDB_timeCollation(t *testing.T) {
|
||||
func TestDB_isoWeek(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tests := []time.Time{
|
||||
time.Date(1977, 1, 1, 0, 0, 0, 0, time.UTC),
|
||||
time.Date(1977, 1, 2, 0, 0, 0, 0, time.UTC),
|
||||
time.Date(1977, 12, 31, 0, 0, 0, 0, time.UTC),
|
||||
time.Date(1978, 1, 1, 0, 0, 0, 0, time.UTC),
|
||||
time.Date(1978, 1, 2, 0, 0, 0, 0, time.UTC),
|
||||
time.Date(1978, 12, 31, 0, 0, 0, 0, time.UTC),
|
||||
time.Date(1979, 1, 1, 0, 0, 0, 0, time.UTC),
|
||||
time.Date(1979, 1, 2, 0, 0, 0, 0, time.UTC),
|
||||
time.Date(1979, 12, 31, 0, 0, 0, 0, time.UTC),
|
||||
time.Date(1980, 1, 1, 0, 0, 0, 0, time.UTC),
|
||||
time.Date(1980, 12, 28, 0, 0, 0, 0, time.UTC),
|
||||
time.Date(1980, 12, 29, 0, 0, 0, 0, time.UTC),
|
||||
time.Date(1980, 12, 30, 0, 0, 0, 0, time.UTC),
|
||||
time.Date(1980, 12, 31, 0, 0, 0, 0, time.UTC),
|
||||
time.Date(1981, 1, 1, 0, 0, 0, 0, time.UTC),
|
||||
time.Date(1981, 12, 31, 0, 0, 0, 0, time.UTC),
|
||||
time.Date(1982, 1, 1, 0, 0, 0, 0, time.UTC),
|
||||
time.Date(1982, 1, 2, 0, 0, 0, 0, time.UTC),
|
||||
time.Date(1982, 1, 3, 0, 0, 0, 0, time.UTC),
|
||||
}
|
||||
|
||||
db, err := sqlite3.Open(":memory:")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -255,7 +233,9 @@ func TestDB_isoWeek(t *testing.T) {
|
||||
}
|
||||
defer stmt.Close()
|
||||
|
||||
for _, tm := range tests {
|
||||
tend := time.Date(2500, 1, 1, 0, 0, 0, 0, time.UTC)
|
||||
tstart := time.Date(1500, 1, 1, 12, 0, 0, 0, time.UTC)
|
||||
for tm := tstart; tm.Before(tend); tm = tm.AddDate(0, 0, 1) {
|
||||
stmt.BindTime(1, tm, sqlite3.TimeFormatDefault)
|
||||
if stmt.Step() {
|
||||
y, w := tm.ISOWeek()
|
||||
|
||||
@@ -18,6 +18,10 @@ func TestConn_Transaction_exec(t *testing.T) {
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
db.RollbackHook(func() {})
|
||||
db.CommitHook(func() bool { return true })
|
||||
db.UpdateHook(func(sqlite3.AuthorizerActionCode, string, string, int64) {})
|
||||
|
||||
err = db.Exec(`CREATE TABLE IF NOT EXISTS test (col)`)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -47,6 +51,10 @@ func TestConn_Transaction_exec(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if s := db.TxnState("main"); s != sqlite3.TXN_WRITE {
|
||||
t.Errorf("got %d", s)
|
||||
}
|
||||
|
||||
if succeed {
|
||||
return nil
|
||||
}
|
||||
122
tx.go → txn.go
122
tx.go → txn.go
@@ -8,50 +8,53 @@ import (
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/ncruces/go-sqlite3/internal/util"
|
||||
"github.com/tetratelabs/wazero/api"
|
||||
)
|
||||
|
||||
// Tx is an in-progress database transaction.
|
||||
// Txn is an in-progress database transaction.
|
||||
//
|
||||
// https://sqlite.org/lang_transaction.html
|
||||
type Tx struct {
|
||||
type Txn struct {
|
||||
c *Conn
|
||||
}
|
||||
|
||||
// Begin starts a deferred transaction.
|
||||
//
|
||||
// https://sqlite.org/lang_transaction.html
|
||||
func (c *Conn) Begin() Tx {
|
||||
func (c *Conn) Begin() Txn {
|
||||
// BEGIN even if interrupted.
|
||||
err := c.txExecInterrupted(`BEGIN DEFERRED`)
|
||||
err := c.txnExecInterrupted(`BEGIN DEFERRED`)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return Tx{c}
|
||||
return Txn{c}
|
||||
}
|
||||
|
||||
// BeginImmediate starts an immediate transaction.
|
||||
//
|
||||
// https://sqlite.org/lang_transaction.html
|
||||
func (c *Conn) BeginImmediate() (Tx, error) {
|
||||
func (c *Conn) BeginImmediate() (Txn, error) {
|
||||
err := c.Exec(`BEGIN IMMEDIATE`)
|
||||
if err != nil {
|
||||
return Tx{}, err
|
||||
return Txn{}, err
|
||||
}
|
||||
return Tx{c}, nil
|
||||
return Txn{c}, nil
|
||||
}
|
||||
|
||||
// BeginExclusive starts an exclusive transaction.
|
||||
//
|
||||
// https://sqlite.org/lang_transaction.html
|
||||
func (c *Conn) BeginExclusive() (Tx, error) {
|
||||
func (c *Conn) BeginExclusive() (Txn, error) {
|
||||
err := c.Exec(`BEGIN EXCLUSIVE`)
|
||||
if err != nil {
|
||||
return Tx{}, err
|
||||
return Txn{}, err
|
||||
}
|
||||
return Tx{c}, nil
|
||||
return Txn{c}, nil
|
||||
}
|
||||
|
||||
// End calls either [Tx.Commit] or [Tx.Rollback]
|
||||
// End calls either [Txn.Commit] or [Txn.Rollback]
|
||||
// depending on whether *error points to a nil or non-nil error.
|
||||
//
|
||||
// This is meant to be deferred:
|
||||
@@ -64,7 +67,7 @@ func (c *Conn) BeginExclusive() (Tx, error) {
|
||||
// }
|
||||
//
|
||||
// https://sqlite.org/lang_transaction.html
|
||||
func (tx Tx) End(errp *error) {
|
||||
func (tx Txn) End(errp *error) {
|
||||
recovered := recover()
|
||||
if recovered != nil {
|
||||
defer panic(recovered)
|
||||
@@ -95,7 +98,7 @@ func (tx Tx) End(errp *error) {
|
||||
// Commit commits the transaction.
|
||||
//
|
||||
// https://sqlite.org/lang_transaction.html
|
||||
func (tx Tx) Commit() error {
|
||||
func (tx Txn) Commit() error {
|
||||
return tx.c.Exec(`COMMIT`)
|
||||
}
|
||||
|
||||
@@ -103,8 +106,8 @@ func (tx Tx) Commit() error {
|
||||
// even if the connection has been interrupted.
|
||||
//
|
||||
// https://sqlite.org/lang_transaction.html
|
||||
func (tx Tx) Rollback() error {
|
||||
return tx.c.txExecInterrupted(`ROLLBACK`)
|
||||
func (tx Txn) Rollback() error {
|
||||
return tx.c.txnExecInterrupted(`ROLLBACK`)
|
||||
}
|
||||
|
||||
// Savepoint is a marker within a transaction
|
||||
@@ -123,7 +126,7 @@ func (c *Conn) Savepoint() Savepoint {
|
||||
// Names can be reused; this makes catching bugs more likely.
|
||||
name := saveptName() + "_" + strconv.Itoa(int(rand.Int31()))
|
||||
|
||||
err := c.txExecInterrupted(fmt.Sprintf("SAVEPOINT %q;", name))
|
||||
err := c.txnExecInterrupted(fmt.Sprintf("SAVEPOINT %q;", name))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@@ -185,7 +188,7 @@ func (s Savepoint) Release(errp *error) {
|
||||
return
|
||||
}
|
||||
// ROLLBACK and RELEASE even if interrupted.
|
||||
err := s.c.txExecInterrupted(fmt.Sprintf(`
|
||||
err := s.c.txnExecInterrupted(fmt.Sprintf(`
|
||||
ROLLBACK TO %[1]q;
|
||||
RELEASE %[1]q;
|
||||
`, s.name))
|
||||
@@ -201,10 +204,10 @@ func (s Savepoint) Release(errp *error) {
|
||||
// https://sqlite.org/lang_transaction.html
|
||||
func (s Savepoint) Rollback() error {
|
||||
// ROLLBACK even if interrupted.
|
||||
return s.c.txExecInterrupted(fmt.Sprintf("ROLLBACK TO %q;", s.name))
|
||||
return s.c.txnExecInterrupted(fmt.Sprintf("ROLLBACK TO %q;", s.name))
|
||||
}
|
||||
|
||||
func (c *Conn) txExecInterrupted(sql string) error {
|
||||
func (c *Conn) txnExecInterrupted(sql string) error {
|
||||
err := c.Exec(sql)
|
||||
if errors.Is(err, INTERRUPT) {
|
||||
old := c.SetInterrupt(context.Background())
|
||||
@@ -213,3 +216,82 @@ func (c *Conn) txExecInterrupted(sql string) error {
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// TxnState starts a deferred transaction.
|
||||
//
|
||||
// https://sqlite.org/c3ref/txn_state.html
|
||||
func (c *Conn) TxnState(schema string) TxnState {
|
||||
var ptr uint32
|
||||
if schema != "" {
|
||||
defer c.arena.mark()()
|
||||
ptr = c.arena.string(schema)
|
||||
}
|
||||
r := c.call("sqlite3_txn_state", uint64(c.handle), uint64(ptr))
|
||||
return TxnState(r)
|
||||
}
|
||||
|
||||
// Deprecated: renamed for consistency with [Conn.TxnState].
|
||||
type Tx = Txn
|
||||
|
||||
// CommitHook registers a callback function to be invoked
|
||||
// whenever a transaction is committed.
|
||||
// Return true to allow the commit operation to continue normally.
|
||||
//
|
||||
// https://sqlite.org/c3ref/commit_hook.html
|
||||
func (c *Conn) CommitHook(cb func() (ok bool)) {
|
||||
var enable uint64
|
||||
if cb != nil {
|
||||
enable = 1
|
||||
}
|
||||
c.call("sqlite3_commit_hook_go", uint64(c.handle), enable)
|
||||
c.commit = cb
|
||||
}
|
||||
|
||||
// RollbackHook registers a callback function to be invoked
|
||||
// whenever a transaction is rolled back.
|
||||
//
|
||||
// https://sqlite.org/c3ref/commit_hook.html
|
||||
func (c *Conn) RollbackHook(cb func()) {
|
||||
var enable uint64
|
||||
if cb != nil {
|
||||
enable = 1
|
||||
}
|
||||
c.call("sqlite3_rollback_hook_go", uint64(c.handle), enable)
|
||||
c.rollback = cb
|
||||
}
|
||||
|
||||
// RollbackHook registers a callback function to be invoked
|
||||
// whenever a row is updated, inserted or deleted in a rowid table.
|
||||
//
|
||||
// https://sqlite.org/c3ref/update_hook.html
|
||||
func (c *Conn) UpdateHook(cb func(action AuthorizerActionCode, schema, table string, rowid int64)) {
|
||||
var enable uint64
|
||||
if cb != nil {
|
||||
enable = 1
|
||||
}
|
||||
c.call("sqlite3_update_hook_go", uint64(c.handle), enable)
|
||||
c.update = cb
|
||||
}
|
||||
|
||||
func commitCallback(ctx context.Context, mod api.Module, pDB uint32) uint32 {
|
||||
if c, ok := ctx.Value(connKey{}).(*Conn); ok && c.handle == pDB && c.commit != nil {
|
||||
if ok := c.commit(); !ok {
|
||||
return 1
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func rollbackCallback(ctx context.Context, mod api.Module, pDB uint32) {
|
||||
if c, ok := ctx.Value(connKey{}).(*Conn); ok && c.handle == pDB && c.rollback != nil {
|
||||
c.rollback()
|
||||
}
|
||||
}
|
||||
|
||||
func updateCallback(ctx context.Context, mod api.Module, pDB uint32, action AuthorizerActionCode, zSchema, zTabName uint32, rowid uint64) {
|
||||
if c, ok := ctx.Value(connKey{}).(*Conn); ok && c.handle == pDB && c.update != nil {
|
||||
schema := util.ReadString(mod, zSchema, _MAX_NAME)
|
||||
table := util.ReadString(mod, zTabName, _MAX_NAME)
|
||||
c.update(action, schema, table, int64(rowid))
|
||||
}
|
||||
}
|
||||
@@ -97,6 +97,10 @@ func syscallOpen(path string, mode int, perm uint32) (fd Handle, err error) {
|
||||
// Necessary for opening directory handles.
|
||||
attrs |= FILE_FLAG_BACKUP_SEMANTICS
|
||||
}
|
||||
if mode&O_SYNC != 0 {
|
||||
const _FILE_FLAG_WRITE_THROUGH = 0x80000000
|
||||
attrs |= _FILE_FLAG_WRITE_THROUGH
|
||||
}
|
||||
return CreateFile(pathp, access, sharemode, sa, createmode, attrs, 0)
|
||||
}
|
||||
|
||||
|
||||
4
value.go
4
value.go
@@ -204,7 +204,7 @@ func (v Value) NoChange() bool {
|
||||
// InFirst returns the first element
|
||||
// on the right-hand side of an IN constraint.
|
||||
//
|
||||
// https://www.sqlite.org/c3ref/vtab_in_first.html
|
||||
// https://sqlite.org/c3ref/vtab_in_first.html
|
||||
func (v Value) InFirst() (Value, error) {
|
||||
defer v.c.arena.mark()()
|
||||
valPtr := v.c.arena.new(ptrlen)
|
||||
@@ -221,7 +221,7 @@ func (v Value) InFirst() (Value, error) {
|
||||
// InNext returns the next element
|
||||
// on the right-hand side of an IN constraint.
|
||||
//
|
||||
// https://www.sqlite.org/c3ref/vtab_in_first.html
|
||||
// https://sqlite.org/c3ref/vtab_in_first.html
|
||||
func (v Value) InNext() (Value, error) {
|
||||
defer v.c.arena.mark()()
|
||||
valPtr := v.c.arena.new(ptrlen)
|
||||
|
||||
12
vfs/api.go
12
vfs/api.go
@@ -80,6 +80,16 @@ type FileOverwrite interface {
|
||||
Overwrite() error
|
||||
}
|
||||
|
||||
// FilePersistentWAL extends File to implement the
|
||||
// SQLITE_FCNTL_PERSIST_WAL file control opcode.
|
||||
//
|
||||
// https://sqlite.org/c3ref/c_fcntl_begin_atomic_write.html#sqlitefcntlpersistwal
|
||||
type FilePersistentWAL interface {
|
||||
File
|
||||
PersistentWAL() bool
|
||||
SetPersistentWAL(bool)
|
||||
}
|
||||
|
||||
// FilePowersafeOverwrite extends File to implement the
|
||||
// SQLITE_FCNTL_POWERSAFE_OVERWRITE file control opcode.
|
||||
//
|
||||
@@ -90,7 +100,7 @@ type FilePowersafeOverwrite interface {
|
||||
SetPowersafeOverwrite(bool)
|
||||
}
|
||||
|
||||
// FilePowersafeOverwrite extends File to implement the
|
||||
// FileCommitPhaseTwo extends File to implement the
|
||||
// SQLITE_FCNTL_COMMIT_PHASETWO file control opcode.
|
||||
//
|
||||
// https://sqlite.org/c3ref/c_fcntl_begin_atomic_write.html#sqlitefcntlcommitphasetwo
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
//go:build !go1.21
|
||||
|
||||
package vfs
|
||||
|
||||
func clear(b []byte) {
|
||||
for i := range b {
|
||||
b[i] = 0
|
||||
}
|
||||
}
|
||||
@@ -126,9 +126,10 @@ func (vfsOS) OpenParams(name string, flags OpenFlag, params url.Values) (File, O
|
||||
type vfsFile struct {
|
||||
*os.File
|
||||
lock LockLevel
|
||||
psow bool
|
||||
syncDir bool
|
||||
readOnly bool
|
||||
keepWAL bool
|
||||
syncDir bool
|
||||
psow bool
|
||||
}
|
||||
|
||||
var (
|
||||
@@ -136,6 +137,7 @@ var (
|
||||
_ FileLockState = &vfsFile{}
|
||||
_ FileHasMoved = &vfsFile{}
|
||||
_ FileSizeHint = &vfsFile{}
|
||||
_ FilePersistentWAL = &vfsFile{}
|
||||
_ FilePowersafeOverwrite = &vfsFile{}
|
||||
)
|
||||
|
||||
@@ -198,4 +200,6 @@ func (f *vfsFile) HasMoved() (bool, error) {
|
||||
|
||||
func (f *vfsFile) LockState() LockLevel { return f.lock }
|
||||
func (f *vfsFile) PowersafeOverwrite() bool { return f.psow }
|
||||
func (f *vfsFile) PersistentWAL() bool { return f.keepWAL }
|
||||
func (f *vfsFile) SetPowersafeOverwrite(psow bool) { f.psow = psow }
|
||||
func (f *vfsFile) SetPersistentWAL(keepWAL bool) { f.keepWAL = keepWAL }
|
||||
|
||||
10
vfs/lock.go
10
vfs/lock.go
@@ -65,14 +65,20 @@ func (f *vfsFile) Lock(lock LockLevel) error {
|
||||
if f.lock <= LOCK_NONE || f.lock >= LOCK_EXCLUSIVE {
|
||||
panic(util.AssertErr())
|
||||
}
|
||||
reserved := f.lock == LOCK_RESERVED
|
||||
// A PENDING lock is needed before acquiring an EXCLUSIVE lock.
|
||||
if f.lock < LOCK_PENDING {
|
||||
if rc := osGetPendingLock(f.File); rc != _OK {
|
||||
// If we're already RESERVED, we can block indefinitely,
|
||||
// since only new readers may briefly hold the PENDING lock.
|
||||
if rc := osGetPendingLock(f.File, reserved /* block */); rc != _OK {
|
||||
return rc
|
||||
}
|
||||
f.lock = LOCK_PENDING
|
||||
}
|
||||
if rc := osGetExclusiveLock(f.File); rc != _OK {
|
||||
// We already have PENDING, so we're just waiting for readers to leave.
|
||||
// If we were RESERVED, we can wait for a little while, before invoking
|
||||
// the busy handler; we will only do this once.
|
||||
if rc := osGetExclusiveLock(f.File, reserved /* wait */); rc != _OK {
|
||||
return rc
|
||||
}
|
||||
f.lock = LOCK_EXCLUSIVE
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
//go:build !go1.21
|
||||
|
||||
package memdb
|
||||
|
||||
func clear[T any](b []T) {
|
||||
var zero T
|
||||
for i := range b {
|
||||
b[i] = zero
|
||||
}
|
||||
}
|
||||
@@ -19,28 +19,17 @@ func osUnlock(file *os.File, start, len int64) _ErrorCode {
|
||||
return _OK
|
||||
}
|
||||
|
||||
func osLock(file *os.File, how int, timeout time.Duration, def _ErrorCode) _ErrorCode {
|
||||
before := time.Now()
|
||||
var err error
|
||||
for {
|
||||
err = unix.Flock(int(file.Fd()), how)
|
||||
if errno, _ := err.(unix.Errno); errno != unix.EAGAIN {
|
||||
break
|
||||
}
|
||||
if timeout <= 0 || timeout < time.Since(before) {
|
||||
break
|
||||
}
|
||||
time.Sleep(time.Millisecond)
|
||||
}
|
||||
func osLock(file *os.File, how int, def _ErrorCode) _ErrorCode {
|
||||
err := unix.Flock(int(file.Fd()), how)
|
||||
return osLockErrorCode(err, def)
|
||||
}
|
||||
|
||||
func osReadLock(file *os.File, start, len int64, timeout time.Duration) _ErrorCode {
|
||||
return osLock(file, unix.LOCK_SH|unix.LOCK_NB, timeout, _IOERR_RDLOCK)
|
||||
func osReadLock(file *os.File, _ /*start*/, _ /*len*/ int64, _ /*timeout*/ time.Duration) _ErrorCode {
|
||||
return osLock(file, unix.LOCK_SH|unix.LOCK_NB, _IOERR_RDLOCK)
|
||||
}
|
||||
|
||||
func osWriteLock(file *os.File, start, len int64, timeout time.Duration) _ErrorCode {
|
||||
return osLock(file, unix.LOCK_EX|unix.LOCK_NB, timeout, _IOERR_LOCK)
|
||||
func osWriteLock(file *os.File, _ /*start*/, _ /*len*/ int64, _ /*timeout*/ time.Duration) _ErrorCode {
|
||||
return osLock(file, unix.LOCK_EX|unix.LOCK_NB, _IOERR_LOCK)
|
||||
}
|
||||
|
||||
func osCheckLock(file *os.File, start, len int64) (bool, _ErrorCode) {
|
||||
|
||||
@@ -23,7 +23,7 @@ type flocktimeout_t struct {
|
||||
timeout unix.Timespec
|
||||
}
|
||||
|
||||
func osSync(file *os.File, fullsync, dataonly bool) error {
|
||||
func osSync(file *os.File, fullsync, _ /*dataonly*/ bool) error {
|
||||
if fullsync {
|
||||
return file.Sync()
|
||||
}
|
||||
@@ -75,9 +75,12 @@ func osLock(file *os.File, typ int16, start, len int64, timeout time.Duration, d
|
||||
Len: len,
|
||||
}}
|
||||
var err error
|
||||
if timeout == 0 {
|
||||
switch {
|
||||
case timeout == 0:
|
||||
err = unix.FcntlFlock(file.Fd(), _F_OFD_SETLK, &lock.fl)
|
||||
} else {
|
||||
case timeout < 0:
|
||||
err = unix.FcntlFlock(file.Fd(), _F_OFD_SETLKW, &lock.fl)
|
||||
default:
|
||||
lock.timeout = unix.NsecToTimespec(int64(timeout / time.Nanosecond))
|
||||
err = unix.FcntlFlock(file.Fd(), _F_OFD_SETLKWTIMEOUT, &lock.fl)
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
func osSync(file *os.File, fullsync, dataonly bool) error {
|
||||
func osSync(file *os.File, _ /*fullsync*/, dataonly bool) error {
|
||||
if dataonly {
|
||||
_, _, err := unix.Syscall(unix.SYS_FDATASYNC, file.Fd(), 0, 0)
|
||||
if err != 0 {
|
||||
|
||||
@@ -4,30 +4,38 @@ package vfs
|
||||
|
||||
import "os"
|
||||
|
||||
func osGetSharedLock(file *os.File) _ErrorCode {
|
||||
// SupportsFileLocking is false on platforms that do not support file locking.
|
||||
// To open a database file in one such platform,
|
||||
// you need to use the [nolock] or [immutable] URI parameters.
|
||||
//
|
||||
// [nolock]: https://sqlite.org/uri.html#urinolock
|
||||
// [immutable]: https://sqlite.org/uri.html#uriimmutable
|
||||
const SupportsFileLocking = false
|
||||
|
||||
func osGetSharedLock(_ *os.File) _ErrorCode {
|
||||
return _IOERR_RDLOCK
|
||||
}
|
||||
|
||||
func osGetReservedLock(file *os.File) _ErrorCode {
|
||||
func osGetReservedLock(_ *os.File) _ErrorCode {
|
||||
return _IOERR_LOCK
|
||||
}
|
||||
|
||||
func osGetPendingLock(file *os.File) _ErrorCode {
|
||||
func osGetPendingLock(_ *os.File, _ bool) _ErrorCode {
|
||||
return _IOERR_LOCK
|
||||
}
|
||||
|
||||
func osGetExclusiveLock(file *os.File) _ErrorCode {
|
||||
func osGetExclusiveLock(_ *os.File, _ bool) _ErrorCode {
|
||||
return _IOERR_LOCK
|
||||
}
|
||||
|
||||
func osDowngradeLock(file *os.File, state LockLevel) _ErrorCode {
|
||||
func osDowngradeLock(_ *os.File, _ LockLevel) _ErrorCode {
|
||||
return _IOERR_RDLOCK
|
||||
}
|
||||
|
||||
func osReleaseLock(file *os.File, _ LockLevel) _ErrorCode {
|
||||
func osReleaseLock(_ *os.File, _ LockLevel) _ErrorCode {
|
||||
return _IOERR_UNLOCK
|
||||
}
|
||||
|
||||
func osCheckReservedLock(file *os.File) (bool, _ErrorCode) {
|
||||
func osCheckReservedLock(_ *os.File) (bool, _ErrorCode) {
|
||||
return false, _IOERR_CHECKRESERVEDLOCK
|
||||
}
|
||||
|
||||
@@ -27,17 +27,24 @@ func osLock(file *os.File, typ int16, start, len int64, timeout time.Duration, d
|
||||
Start: start,
|
||||
Len: len,
|
||||
}
|
||||
before := time.Now()
|
||||
var err error
|
||||
for {
|
||||
switch {
|
||||
case timeout == 0:
|
||||
err = unix.FcntlFlock(file.Fd(), unix.F_OFD_SETLK, &lock)
|
||||
if errno, _ := err.(unix.Errno); errno != unix.EAGAIN {
|
||||
break
|
||||
case timeout < 0:
|
||||
err = unix.FcntlFlock(file.Fd(), unix.F_OFD_SETLKW, &lock)
|
||||
default:
|
||||
before := time.Now()
|
||||
for {
|
||||
err = unix.FcntlFlock(file.Fd(), unix.F_OFD_SETLK, &lock)
|
||||
if errno, _ := err.(unix.Errno); errno != unix.EAGAIN {
|
||||
break
|
||||
}
|
||||
if timeout <= 0 || timeout < time.Since(before) {
|
||||
break
|
||||
}
|
||||
osSleep(time.Millisecond)
|
||||
}
|
||||
if timeout <= 0 || timeout < time.Since(before) {
|
||||
break
|
||||
}
|
||||
time.Sleep(time.Millisecond)
|
||||
}
|
||||
return osLockErrorCode(err, def)
|
||||
}
|
||||
|
||||
9
vfs/os_std_sleep.go
Normal file
9
vfs/os_std_sleep.go
Normal file
@@ -0,0 +1,9 @@
|
||||
//go:build !windows || sqlite3_nosys
|
||||
|
||||
package vfs
|
||||
|
||||
import "time"
|
||||
|
||||
func osSleep(d time.Duration) {
|
||||
time.Sleep(d)
|
||||
}
|
||||
@@ -4,6 +4,6 @@ package vfs
|
||||
|
||||
import "os"
|
||||
|
||||
func osSync(file *os.File, fullsync, dataonly bool) error {
|
||||
func osSync(file *os.File, _ /*fullsync*/, _ /*dataonly*/ bool) error {
|
||||
return file.Sync()
|
||||
}
|
||||
|
||||
@@ -9,6 +9,14 @@ import (
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
// SupportsFileLocking is false on platforms that do not support file locking.
|
||||
// To open a database file in one such platform,
|
||||
// you need to use the [nolock] or [immutable] URI parameters.
|
||||
//
|
||||
// [nolock]: https://sqlite.org/uri.html#urinolock
|
||||
// [immutable]: https://sqlite.org/uri.html#uriimmutable
|
||||
const SupportsFileLocking = true
|
||||
|
||||
func osGetSharedLock(file *os.File) _ErrorCode {
|
||||
// Test the PENDING lock before acquiring a new SHARED lock.
|
||||
if pending, _ := osCheckLock(file, _PENDING_BYTE, 1); pending {
|
||||
@@ -23,14 +31,22 @@ func osGetReservedLock(file *os.File) _ErrorCode {
|
||||
return osWriteLock(file, _RESERVED_BYTE, 1, 0)
|
||||
}
|
||||
|
||||
func osGetPendingLock(file *os.File) _ErrorCode {
|
||||
func osGetPendingLock(file *os.File, block bool) _ErrorCode {
|
||||
var timeout time.Duration
|
||||
if block {
|
||||
timeout = -1
|
||||
}
|
||||
// Acquire the PENDING lock.
|
||||
return osWriteLock(file, _PENDING_BYTE, 1, 0)
|
||||
return osWriteLock(file, _PENDING_BYTE, 1, timeout)
|
||||
}
|
||||
|
||||
func osGetExclusiveLock(file *os.File) _ErrorCode {
|
||||
func osGetExclusiveLock(file *os.File, wait bool) _ErrorCode {
|
||||
var timeout time.Duration
|
||||
if wait {
|
||||
timeout = time.Millisecond
|
||||
}
|
||||
// Acquire the EXCLUSIVE lock.
|
||||
return osWriteLock(file, _SHARED_FIRST, _SHARED_SIZE, time.Millisecond)
|
||||
return osWriteLock(file, _SHARED_FIRST, _SHARED_SIZE, timeout)
|
||||
}
|
||||
|
||||
func osDowngradeLock(file *os.File, state LockLevel) _ErrorCode {
|
||||
@@ -9,6 +9,14 @@ import (
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
// SupportsFileLocking is false on platforms that do not support file locking.
|
||||
// To open a database file in one such platform,
|
||||
// you need to use the [nolock] or [immutable] URI parameters.
|
||||
//
|
||||
// [nolock]: https://sqlite.org/uri.html#urinolock
|
||||
// [immutable]: https://sqlite.org/uri.html#uriimmutable
|
||||
const SupportsFileLocking = true
|
||||
|
||||
func osGetSharedLock(file *os.File) _ErrorCode {
|
||||
// Acquire the PENDING lock temporarily before acquiring a new SHARED lock.
|
||||
rc := osReadLock(file, _PENDING_BYTE, 1, 0)
|
||||
@@ -28,17 +36,27 @@ func osGetReservedLock(file *os.File) _ErrorCode {
|
||||
return osWriteLock(file, _RESERVED_BYTE, 1, 0)
|
||||
}
|
||||
|
||||
func osGetPendingLock(file *os.File) _ErrorCode {
|
||||
func osGetPendingLock(file *os.File, block bool) _ErrorCode {
|
||||
var timeout time.Duration
|
||||
if block {
|
||||
timeout = -1
|
||||
}
|
||||
|
||||
// Acquire the PENDING lock.
|
||||
return osWriteLock(file, _PENDING_BYTE, 1, 0)
|
||||
return osWriteLock(file, _PENDING_BYTE, 1, timeout)
|
||||
}
|
||||
|
||||
func osGetExclusiveLock(file *os.File) _ErrorCode {
|
||||
func osGetExclusiveLock(file *os.File, wait bool) _ErrorCode {
|
||||
var timeout time.Duration
|
||||
if wait {
|
||||
timeout = time.Millisecond
|
||||
}
|
||||
|
||||
// Release the SHARED lock.
|
||||
osUnlock(file, _SHARED_FIRST, _SHARED_SIZE)
|
||||
|
||||
// Acquire the EXCLUSIVE lock.
|
||||
rc := osWriteLock(file, _SHARED_FIRST, _SHARED_SIZE, time.Millisecond)
|
||||
rc := osWriteLock(file, _SHARED_FIRST, _SHARED_SIZE, timeout)
|
||||
|
||||
if rc != _OK {
|
||||
// Reacquire the SHARED lock.
|
||||
@@ -49,7 +67,7 @@ func osGetExclusiveLock(file *os.File) _ErrorCode {
|
||||
|
||||
func osDowngradeLock(file *os.File, state LockLevel) _ErrorCode {
|
||||
if state >= LOCK_EXCLUSIVE {
|
||||
// Release the SHARED lock.
|
||||
// Release the EXCLUSIVE lock.
|
||||
osUnlock(file, _SHARED_FIRST, _SHARED_SIZE)
|
||||
|
||||
// Reacquire the SHARED lock.
|
||||
@@ -102,44 +120,43 @@ func osUnlock(file *os.File, start, len uint32) _ErrorCode {
|
||||
}
|
||||
|
||||
func osLock(file *os.File, flags, start, len uint32, timeout time.Duration, def _ErrorCode) _ErrorCode {
|
||||
before := time.Now()
|
||||
var err error
|
||||
for {
|
||||
err = windows.LockFileEx(windows.Handle(file.Fd()), flags,
|
||||
0, len, 0, &windows.Overlapped{Offset: start})
|
||||
if errno, _ := err.(windows.Errno); errno != windows.ERROR_LOCK_VIOLATION {
|
||||
break
|
||||
}
|
||||
if timeout <= 0 || timeout < time.Since(before) {
|
||||
break
|
||||
}
|
||||
if err := windows.TimeBeginPeriod(1); err != nil {
|
||||
break
|
||||
}
|
||||
time.Sleep(time.Millisecond)
|
||||
if err := windows.TimeEndPeriod(1); err != nil {
|
||||
break
|
||||
switch {
|
||||
case timeout == 0:
|
||||
err = osLockEx(file, flags|windows.LOCKFILE_FAIL_IMMEDIATELY, start, len)
|
||||
case timeout < 0:
|
||||
err = osLockEx(file, flags, start, len)
|
||||
default:
|
||||
before := time.Now()
|
||||
for {
|
||||
err = osLockEx(file, flags|windows.LOCKFILE_FAIL_IMMEDIATELY, start, len)
|
||||
if errno, _ := err.(windows.Errno); errno != windows.ERROR_LOCK_VIOLATION {
|
||||
break
|
||||
}
|
||||
if timeout <= 0 || timeout < time.Since(before) {
|
||||
break
|
||||
}
|
||||
osSleep(time.Millisecond)
|
||||
}
|
||||
}
|
||||
return osLockErrorCode(err, def)
|
||||
}
|
||||
|
||||
func osLockEx(file *os.File, flags, start, len uint32) error {
|
||||
return windows.LockFileEx(windows.Handle(file.Fd()), flags,
|
||||
0, len, 0, &windows.Overlapped{Offset: start})
|
||||
}
|
||||
|
||||
func osReadLock(file *os.File, start, len uint32, timeout time.Duration) _ErrorCode {
|
||||
return osLock(file,
|
||||
windows.LOCKFILE_FAIL_IMMEDIATELY,
|
||||
start, len, timeout, _IOERR_RDLOCK)
|
||||
return osLock(file, 0, start, len, timeout, _IOERR_RDLOCK)
|
||||
}
|
||||
|
||||
func osWriteLock(file *os.File, start, len uint32, timeout time.Duration) _ErrorCode {
|
||||
return osLock(file,
|
||||
windows.LOCKFILE_FAIL_IMMEDIATELY|windows.LOCKFILE_EXCLUSIVE_LOCK,
|
||||
start, len, timeout, _IOERR_LOCK)
|
||||
return osLock(file, windows.LOCKFILE_EXCLUSIVE_LOCK, start, len, timeout, _IOERR_LOCK)
|
||||
}
|
||||
|
||||
func osCheckLock(file *os.File, start, len uint32) (bool, _ErrorCode) {
|
||||
rc := osLock(file,
|
||||
windows.LOCKFILE_FAIL_IMMEDIATELY,
|
||||
start, len, 0, _IOERR_CHECKRESERVEDLOCK)
|
||||
rc := osLock(file, 0, start, len, 0, _IOERR_CHECKRESERVEDLOCK)
|
||||
if rc == _BUSY {
|
||||
return true, _OK
|
||||
}
|
||||
@@ -165,3 +182,16 @@ func osLockErrorCode(err error, def _ErrorCode) _ErrorCode {
|
||||
}
|
||||
return def
|
||||
}
|
||||
|
||||
func osSleep(d time.Duration) {
|
||||
if d > 0 {
|
||||
period := max(1, d/(5*time.Millisecond))
|
||||
if period < 16 {
|
||||
windows.TimeBeginPeriod(uint32(period))
|
||||
}
|
||||
time.Sleep(d)
|
||||
if period < 16 {
|
||||
windows.TimeEndPeriod(uint32(period))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,6 +49,9 @@ func TestMain(m *testing.M) {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if !strings.HasPrefix(compressed, "BZh") {
|
||||
panic("Please use Git LFS to clone this repo: https://git-lfs.com/")
|
||||
}
|
||||
binary, err := io.ReadAll(bzip2.NewReader(strings.NewReader(compressed)))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
@@ -129,6 +132,9 @@ func Test_crash01(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("skipping in short mode")
|
||||
}
|
||||
if os.Getenv("CI") != "" {
|
||||
t.Skip("skipping in CI")
|
||||
}
|
||||
|
||||
ctx := util.NewContext(newContext(t))
|
||||
name := filepath.Join(t.TempDir(), "test.db")
|
||||
@@ -157,10 +163,8 @@ func Test_multiwrite01(t *testing.T) {
|
||||
|
||||
func Test_config01_memory(t *testing.T) {
|
||||
ctx := util.NewContext(newContext(t))
|
||||
cfg := config(ctx).WithArgs("mptest", "/test.db",
|
||||
"config01.test",
|
||||
"--vfs", "memdb",
|
||||
"--timeout", "1000")
|
||||
cfg := config(ctx).WithArgs("mptest", "/test.db", "config01.test",
|
||||
"--vfs", "memdb")
|
||||
mod, err := rt.InstantiateModule(ctx, module, cfg)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -174,10 +178,8 @@ func Test_multiwrite01_memory(t *testing.T) {
|
||||
}
|
||||
|
||||
ctx := util.NewContext(newContext(t))
|
||||
cfg := config(ctx).WithArgs("mptest", "/test.db",
|
||||
"multiwrite01.test",
|
||||
"--vfs", "memdb",
|
||||
"--timeout", "1000")
|
||||
cfg := config(ctx).WithArgs("mptest", "/test.db", "multiwrite01.test",
|
||||
"--vfs", "memdb")
|
||||
mod, err := rt.InstantiateModule(ctx, module, cfg)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
|
||||
2
vfs/tests/mptest/testdata/build.sh
vendored
2
vfs/tests/mptest/testdata/build.sh
vendored
@@ -4,7 +4,7 @@ set -euo pipefail
|
||||
cd -P -- "$(dirname -- "$0")"
|
||||
|
||||
ROOT=../../../../
|
||||
BINARYEN="$ROOT/tools/binaryen-version_116/bin"
|
||||
BINARYEN="$ROOT/tools/binaryen-version_117/bin"
|
||||
WASI_SDK="$ROOT/tools/wasi-sdk-21.0/bin"
|
||||
|
||||
"$WASI_SDK/clang" --target=wasm32-wasi -std=c17 -flto -g0 -O2 \
|
||||
|
||||
4
vfs/tests/mptest/testdata/mptest.wasm.bz2
vendored
4
vfs/tests/mptest/testdata/mptest.wasm.bz2
vendored
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:bb763a5cc1c10683441f34ea64253eefc5b7b2d7208f9db6632c5b64da9a50a7
|
||||
size 520353
|
||||
oid sha256:b5f4778b49a6b99a1be11db5cb07e3c5b52f91a932a97e33c895f52c6f54bf57
|
||||
size 469149
|
||||
|
||||
@@ -46,6 +46,9 @@ func TestMain(m *testing.M) {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if !strings.HasPrefix(compressed, "BZh") {
|
||||
panic("Please use Git LFS to clone this repo: https://git-lfs.com/")
|
||||
}
|
||||
binary, err := io.ReadAll(bzip2.NewReader(strings.NewReader(compressed)))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
|
||||
2
vfs/tests/speedtest1/testdata/build.sh
vendored
2
vfs/tests/speedtest1/testdata/build.sh
vendored
@@ -4,7 +4,7 @@ set -euo pipefail
|
||||
cd -P -- "$(dirname -- "$0")"
|
||||
|
||||
ROOT=../../../../
|
||||
BINARYEN="$ROOT/tools/binaryen-version_116/bin"
|
||||
BINARYEN="$ROOT/tools/binaryen-version_117/bin"
|
||||
WASI_SDK="$ROOT/tools/wasi-sdk-21.0/bin"
|
||||
|
||||
"$WASI_SDK/clang" --target=wasm32-wasi -std=c17 -flto -g0 -O2 \
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:a2d0b8b5d044cd993ccec28f3c491d862971d0e2e6ac78d6aa757d252ab63a4b
|
||||
size 534561
|
||||
oid sha256:06882576fdea2c8e164dd2d97dce394dc825dca3cee30c9efc9601b84de92865
|
||||
size 483191
|
||||
|
||||
35
vfs/vfs.go
35
vfs/vfs.go
@@ -79,7 +79,7 @@ func vfsRandomness(ctx context.Context, mod api.Module, pVfs, nByte, zByte uint3
|
||||
}
|
||||
|
||||
func vfsSleep(ctx context.Context, mod api.Module, pVfs, nMicro uint32) _ErrorCode {
|
||||
time.Sleep(time.Duration(nMicro) * time.Microsecond)
|
||||
osSleep(time.Duration(nMicro) * time.Microsecond)
|
||||
return _OK
|
||||
}
|
||||
|
||||
@@ -257,19 +257,26 @@ func vfsFileControl(ctx context.Context, mod api.Module, pFile uint32, op _Fcntl
|
||||
return _OK
|
||||
}
|
||||
|
||||
case _FCNTL_PERSIST_WAL:
|
||||
if file, ok := file.(FilePersistentWAL); ok {
|
||||
if i := util.ReadUint32(mod, pArg); int32(i) >= 0 {
|
||||
file.SetPersistentWAL(i != 0)
|
||||
} else if file.PersistentWAL() {
|
||||
util.WriteUint32(mod, pArg, 1)
|
||||
} else {
|
||||
util.WriteUint32(mod, pArg, 0)
|
||||
}
|
||||
return _OK
|
||||
}
|
||||
|
||||
case _FCNTL_POWERSAFE_OVERWRITE:
|
||||
if file, ok := file.(FilePowersafeOverwrite); ok {
|
||||
switch util.ReadUint32(mod, pArg) {
|
||||
case 0:
|
||||
file.SetPowersafeOverwrite(false)
|
||||
case 1:
|
||||
file.SetPowersafeOverwrite(true)
|
||||
default:
|
||||
if file.PowersafeOverwrite() {
|
||||
util.WriteUint32(mod, pArg, 1)
|
||||
} else {
|
||||
util.WriteUint32(mod, pArg, 0)
|
||||
}
|
||||
if i := util.ReadUint32(mod, pArg); int32(i) >= 0 {
|
||||
file.SetPowersafeOverwrite(i != 0)
|
||||
} else if file.PowersafeOverwrite() {
|
||||
util.WriteUint32(mod, pArg, 1)
|
||||
} else {
|
||||
util.WriteUint32(mod, pArg, 0)
|
||||
}
|
||||
return _OK
|
||||
}
|
||||
@@ -284,12 +291,10 @@ func vfsFileControl(ctx context.Context, mod api.Module, pFile uint32, op _Fcntl
|
||||
case _FCNTL_HAS_MOVED:
|
||||
if file, ok := file.(FileHasMoved); ok {
|
||||
moved, err := file.HasMoved()
|
||||
|
||||
var res uint32
|
||||
if moved {
|
||||
res = 1
|
||||
}
|
||||
|
||||
util.WriteUint32(mod, pArg, res)
|
||||
return vfsErrorCode(err, _IOERR_FSTAT)
|
||||
}
|
||||
@@ -326,6 +331,8 @@ func vfsFileControl(ctx context.Context, mod api.Module, pFile uint32, op _Fcntl
|
||||
// Consider also implementing these opcodes (in use by SQLite):
|
||||
// _FCNTL_BUSYHANDLER
|
||||
// _FCNTL_CHUNK_SIZE
|
||||
// _FCNTL_CKPT_DONE
|
||||
// _FCNTL_CKPT_START
|
||||
// _FCNTL_PRAGMA
|
||||
// _FCNTL_SYNC
|
||||
return _NOTFOUND
|
||||
|
||||
41
vtab.go
41
vtab.go
@@ -22,7 +22,7 @@ func CreateModule[T VTab](db *Conn, name string, create, connect VTabConstructor
|
||||
VTAB_RENAMER = 0x08
|
||||
VTAB_OVERLOADER = 0x10
|
||||
VTAB_CHECKER = 0x20
|
||||
VTAB_TX = 0x40
|
||||
VTAB_TXN = 0x40
|
||||
VTAB_SAVEPOINTER = 0x80
|
||||
)
|
||||
|
||||
@@ -46,8 +46,8 @@ func CreateModule[T VTab](db *Conn, name string, create, connect VTabConstructor
|
||||
if implements[VTabChecker](vtab) {
|
||||
flags |= VTAB_CHECKER
|
||||
}
|
||||
if implements[VTabTx](vtab) {
|
||||
flags |= VTAB_TX
|
||||
if implements[VTabTxn](vtab) {
|
||||
flags |= VTAB_TXN
|
||||
}
|
||||
if implements[VTabSavepointer](vtab) {
|
||||
flags |= VTAB_SAVEPOINTER
|
||||
@@ -78,7 +78,7 @@ func (c *Conn) DeclareVTab(sql string) error {
|
||||
|
||||
// VTabConflictMode is a virtual table conflict resolution mode.
|
||||
//
|
||||
// https://www.sqlite.org/c3ref/c_fail.html
|
||||
// https://sqlite.org/c3ref/c_fail.html
|
||||
type VTabConflictMode uint8
|
||||
|
||||
const (
|
||||
@@ -128,6 +128,13 @@ type VTabConstructor[T VTab] func(db *Conn, module, schema, table string, arg ..
|
||||
|
||||
type module[T VTab] [2]VTabConstructor[T]
|
||||
|
||||
type vtabConstructor int
|
||||
|
||||
const (
|
||||
xCreate vtabConstructor = 0
|
||||
xConnect vtabConstructor = 1
|
||||
)
|
||||
|
||||
// A VTab describes a particular instance of the virtual table.
|
||||
// A VTab may optionally implement [io.Closer] to free resources.
|
||||
//
|
||||
@@ -180,14 +187,14 @@ type VTabChecker interface {
|
||||
Integrity(schema, table string, flags int) error
|
||||
}
|
||||
|
||||
// A VTabTx allows a virtual table to implement
|
||||
// A VTabTxn allows a virtual table to implement
|
||||
// transactions with two-phase commit.
|
||||
//
|
||||
// Anything that is required as part of a commit that may fail
|
||||
// should be performed in the Sync() callback.
|
||||
// Current versions of SQLite ignore any errors
|
||||
// returned by Commit() and Rollback().
|
||||
type VTabTx interface {
|
||||
type VTabTxn interface {
|
||||
VTab
|
||||
// https://sqlite.org/vtab.html#xBegin
|
||||
Begin() error
|
||||
@@ -199,12 +206,15 @@ type VTabTx interface {
|
||||
Rollback() error
|
||||
}
|
||||
|
||||
// Deprecated: renamed for consistency with [Conn.TxnState].
|
||||
type VTabTx = VTabTxn
|
||||
|
||||
// A VTabSavepointer allows a virtual table to implement
|
||||
// nested transactions.
|
||||
//
|
||||
// https://sqlite.org/vtab.html#xsavepoint
|
||||
type VTabSavepointer interface {
|
||||
VTabTx
|
||||
VTabTxn
|
||||
Savepoint(id int) error
|
||||
Release(id int) error
|
||||
RollbackTo(id int) error
|
||||
@@ -414,7 +424,7 @@ const (
|
||||
INDEX_SCAN_UNIQUE IndexScanFlag = 1
|
||||
)
|
||||
|
||||
func vtabModuleCallback(i int) func(_ context.Context, _ api.Module, _, _, _, _, _ uint32) uint32 {
|
||||
func vtabModuleCallback(i vtabConstructor) func(_ context.Context, _ api.Module, _, _, _, _, _ uint32) uint32 {
|
||||
return func(ctx context.Context, mod api.Module, pMod, nArg, pArg, ppVTab, pzErr uint32) uint32 {
|
||||
arg := make([]reflect.Value, 1+nArg)
|
||||
arg[0] = reflect.ValueOf(ctx.Value(connKey{}))
|
||||
@@ -425,7 +435,7 @@ func vtabModuleCallback(i int) func(_ context.Context, _ api.Module, _, _, _, _,
|
||||
}
|
||||
|
||||
module := vtabGetHandle(ctx, mod, pMod)
|
||||
res := reflect.ValueOf(module).Index(i).Call(arg)
|
||||
res := reflect.ValueOf(module).Index(int(i)).Call(arg)
|
||||
err, _ := res[1].Interface().(error)
|
||||
if err == nil {
|
||||
vtabPutHandle(ctx, mod, ppVTab, res[0].Interface())
|
||||
@@ -509,25 +519,25 @@ func vtabIntegrityCallback(ctx context.Context, mod api.Module, pVTab, zSchema,
|
||||
}
|
||||
|
||||
func vtabBeginCallback(ctx context.Context, mod api.Module, pVTab uint32) uint32 {
|
||||
vtab := vtabGetHandle(ctx, mod, pVTab).(VTabTx)
|
||||
vtab := vtabGetHandle(ctx, mod, pVTab).(VTabTxn)
|
||||
err := vtab.Begin()
|
||||
return vtabError(ctx, mod, pVTab, _VTAB_ERROR, err)
|
||||
}
|
||||
|
||||
func vtabSyncCallback(ctx context.Context, mod api.Module, pVTab uint32) uint32 {
|
||||
vtab := vtabGetHandle(ctx, mod, pVTab).(VTabTx)
|
||||
vtab := vtabGetHandle(ctx, mod, pVTab).(VTabTxn)
|
||||
err := vtab.Sync()
|
||||
return vtabError(ctx, mod, pVTab, _VTAB_ERROR, err)
|
||||
}
|
||||
|
||||
func vtabCommitCallback(ctx context.Context, mod api.Module, pVTab uint32) uint32 {
|
||||
vtab := vtabGetHandle(ctx, mod, pVTab).(VTabTx)
|
||||
vtab := vtabGetHandle(ctx, mod, pVTab).(VTabTxn)
|
||||
err := vtab.Commit()
|
||||
return vtabError(ctx, mod, pVTab, _VTAB_ERROR, err)
|
||||
}
|
||||
|
||||
func vtabRollbackCallback(ctx context.Context, mod api.Module, pVTab uint32) uint32 {
|
||||
vtab := vtabGetHandle(ctx, mod, pVTab).(VTabTx)
|
||||
vtab := vtabGetHandle(ctx, mod, pVTab).(VTabTxn)
|
||||
err := vtab.Rollback()
|
||||
return vtabError(ctx, mod, pVTab, _VTAB_ERROR, err)
|
||||
}
|
||||
@@ -618,13 +628,14 @@ const (
|
||||
)
|
||||
|
||||
func vtabError(ctx context.Context, mod api.Module, ptr, kind uint32, err error) uint32 {
|
||||
const zErrMsgOffset = 8
|
||||
msg, code := errorCode(err, ERROR)
|
||||
if msg != "" && ptr != 0 {
|
||||
switch kind {
|
||||
case _VTAB_ERROR:
|
||||
ptr = ptr + 8 // zErrMsg
|
||||
ptr = ptr + zErrMsgOffset // zErrMsg
|
||||
case _CURSOR_ERROR:
|
||||
ptr = util.ReadUint32(mod, ptr) + 8 // pVTab->zErrMsg
|
||||
ptr = util.ReadUint32(mod, ptr) + zErrMsgOffset // pVTab->zErrMsg
|
||||
}
|
||||
db := ctx.Value(connKey{}).(*Conn)
|
||||
if ptr := util.ReadUint32(mod, ptr); ptr != 0 {
|
||||
|
||||
Reference in New Issue
Block a user