mirror of
https://github.com/ncruces/go-sqlite3.git
synced 2026-01-12 22:19:14 +00:00
Compare commits
46 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7c820ede3c | ||
|
|
089a0c0670 | ||
|
|
8b45cac16b | ||
|
|
06d2ff6752 | ||
|
|
987f0f13a2 | ||
|
|
cd40213898 | ||
|
|
8a0baedc10 | ||
|
|
c667a1f469 | ||
|
|
9c562f5d8b | ||
|
|
d862f47d95 | ||
|
|
a9e32fd3f0 | ||
|
|
b262f5cd01 | ||
|
|
4160b9a4bb | ||
|
|
dbaf2d99cd | ||
|
|
3f05115cd7 | ||
|
|
9bf14becaf | ||
|
|
997e197f54 | ||
|
|
b81fe284b6 | ||
|
|
269306c5c8 | ||
|
|
8a18243830 | ||
|
|
fcd6cc91d8 | ||
|
|
c1838fc0bc | ||
|
|
dc0d8236bf | ||
|
|
ba18facb0d | ||
|
|
5653efa70e | ||
|
|
1acb95917a | ||
|
|
e31a42fb22 | ||
|
|
aec69acca9 | ||
|
|
f2d6bdb8b7 | ||
|
|
9bb01d1f8b | ||
|
|
83c15f2ddc | ||
|
|
97d4248176 | ||
|
|
22d1ae0068 | ||
|
|
ae1d696cf3 | ||
|
|
5992403052 | ||
|
|
f212b6712d | ||
|
|
a49cc084f3 | ||
|
|
2c2f825bc5 | ||
|
|
787086b8c1 | ||
|
|
314098addb | ||
|
|
4bf8a79c1d | ||
|
|
90628ab8aa | ||
|
|
aa02c14430 | ||
|
|
1dc06bff49 | ||
|
|
4e9173661b | ||
|
|
591480cd39 |
40
.github/workflows/cpu.yml
vendored
Normal file
40
.github/workflows/cpu.yml
vendored
Normal file
@@ -0,0 +1,40 @@
|
||||
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@v4
|
||||
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@v4
|
||||
with:
|
||||
go-version: stable
|
||||
|
||||
- name: Install QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
|
||||
- name: Test
|
||||
run: GOARCH=arm64 go test -v ./...
|
||||
2
.github/workflows/cross.sh
vendored
2
.github/workflows/cross.sh
vendored
@@ -19,4 +19,4 @@ echo darwin-flock ; GOOS=darwin GOARCH=amd64 go build -tags sqlite3_flock .
|
||||
echo darwin-nosys ; GOOS=darwin GOARCH=amd64 go build -tags sqlite3_nosys .
|
||||
echo linux-nosys ; GOOS=linux GOARCH=amd64 go build -tags sqlite3_nosys .
|
||||
echo windows-nosys ; GOOS=windows GOARCH=amd64 go build -tags sqlite3_nosys .
|
||||
echo freebsd-nosys ; GOOS=freebsd GOARCH=amd64 go build -tags sqlite3_nosys .
|
||||
echo freebsd-nosys ; GOOS=freebsd GOARCH=amd64 go build -tags sqlite3_nosys .
|
||||
4
.github/workflows/cross.yml
vendored
4
.github/workflows/cross.yml
vendored
@@ -4,13 +4,11 @@ on:
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
test:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
lfs: 'true'
|
||||
|
||||
- name: Set up
|
||||
uses: actions/setup-go@v4
|
||||
|
||||
23
.github/workflows/repro.sh
vendored
Executable file
23
.github/workflows/repro.sh
vendored
Executable file
@@ -0,0 +1,23 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
if [[ "$OSTYPE" == "linux"* ]]; then
|
||||
WASI_SDK="https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-20/wasi-sdk-20.0-linux.tar.gz"
|
||||
BINARYEN="https://github.com/WebAssembly/binaryen/releases/download/version_116/binaryen-version_116-x86_64-linux.tar.gz"
|
||||
elif [[ "$OSTYPE" == "darwin"* ]]; then
|
||||
WASI_SDK="https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-20/wasi-sdk-20.0-macos.tar.gz"
|
||||
BINARYEN="https://github.com/WebAssembly/binaryen/releases/download/version_116/binaryen-version_116-x86_64-macos.tar.gz"
|
||||
elif [[ "$OSTYPE" == "msys" || "$OSTYPE" == "cygwin" ]]; then
|
||||
WASI_SDK="https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-20/wasi-sdk-20.0.m-mingw.tar.gz"
|
||||
BINARYEN="https://github.com/WebAssembly/binaryen/releases/download/version_116/binaryen-version_116-x86_64-windows.tar.gz"
|
||||
fi
|
||||
|
||||
# Download tools
|
||||
mkdir -p tools
|
||||
[ -d "tools/wasi-sdk"* ] || curl -#L "$WASI_SDK" | tar xzC tools &
|
||||
[ -d "tools/binaryen-version"* ] || curl -#L "$BINARYEN" | tar xzC tools &
|
||||
wait
|
||||
|
||||
sqlite3/download.sh # Download SQLite
|
||||
embed/build.sh # Build WASM
|
||||
git diff --exit-code # Check diffs
|
||||
24
.github/workflows/repro.yml
vendored
Normal file
24
.github/workflows/repro.yml
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
name: Reproducible build
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
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@v4
|
||||
with:
|
||||
go-version: stable
|
||||
|
||||
- name: Build
|
||||
run: .github/workflows/repro.sh
|
||||
68
README.md
68
README.md
@@ -8,38 +8,62 @@ Go module `github.com/ncruces/go-sqlite3` wraps a [WASM](https://webassembly.org
|
||||
and uses [wazero](https://wazero.io/) to provide `cgo`-free SQLite bindings.
|
||||
|
||||
- [`github.com/ncruces/go-sqlite3`](https://pkg.go.dev/github.com/ncruces/go-sqlite3)
|
||||
wraps the [C SQLite API](https://www.sqlite.org/cintro.html)
|
||||
wraps the [C SQLite API](https://sqlite.org/cintro.html)
|
||||
([example usage](https://pkg.go.dev/github.com/ncruces/go-sqlite3#example-package)).
|
||||
- [`github.com/ncruces/go-sqlite3/driver`](https://pkg.go.dev/github.com/ncruces/go-sqlite3/driver)
|
||||
provides a [`database/sql`](https://pkg.go.dev/database/sql) driver
|
||||
([example usage](https://pkg.go.dev/github.com/ncruces/go-sqlite3/driver#example-package)).
|
||||
- [`github.com/ncruces/go-sqlite3/embed`](https://pkg.go.dev/github.com/ncruces/go-sqlite3/embed)
|
||||
embeds a build of SQLite into your application.
|
||||
- [`github.com/ncruces/go-sqlite3/ext/blob`](https://pkg.go.dev/github.com/ncruces/go-sqlite3/ext/blob)
|
||||
simplifies incremental BLOB I/O.
|
||||
- [`github.com/ncruces/go-sqlite3/ext/stats`](https://pkg.go.dev/github.com/ncruces/go-sqlite3/ext/stats)
|
||||
registers [statistics functions](https://www.oreilly.com/library/view/sql-in-a/9780596155322/ch04s02.html).
|
||||
- [`github.com/ncruces/go-sqlite3/ext/unicode`](https://pkg.go.dev/github.com/ncruces/go-sqlite3/ext/unicode)
|
||||
registers Unicode aware functions.
|
||||
- [`github.com/ncruces/go-sqlite3/vfs`](https://pkg.go.dev/github.com/ncruces/go-sqlite3/vfs)
|
||||
wraps the [C SQLite VFS API](https://www.sqlite.org/vfs.html) and provides a pure Go implementation.
|
||||
wraps the [C SQLite VFS API](https://sqlite.org/vfs.html) and provides a pure Go implementation.
|
||||
- [`github.com/ncruces/go-sqlite3/gormlite`](https://pkg.go.dev/github.com/ncruces/go-sqlite3/gormlite)
|
||||
provides a [GORM](https://gorm.io) driver.
|
||||
|
||||
### Loadable extensions
|
||||
|
||||
- [`github.com/ncruces/go-sqlite3/ext/array`](https://pkg.go.dev/github.com/ncruces/go-sqlite3/ext/blob)
|
||||
provides the [`array`](https://sqlite.org/carray.html) table-valued function.
|
||||
- [`github.com/ncruces/go-sqlite3/ext/blob`](https://pkg.go.dev/github.com/ncruces/go-sqlite3/ext/blob)
|
||||
simplifies [incremental BLOB I/O](https://sqlite.org/c3ref/blob_open.html).
|
||||
- [`github.com/ncruces/go-sqlite3/ext/csv`](https://pkg.go.dev/github.com/ncruces/go-sqlite3/ext/csv)
|
||||
reads [comma-separated values](https://sqlite.org/csv.html).
|
||||
- [`github.com/ncruces/go-sqlite3/ext/lines`](https://pkg.go.dev/github.com/ncruces/go-sqlite3/ext/lines)
|
||||
reads files [line-by-line](https://github.com/asg017/sqlite-lines).
|
||||
- [`github.com/ncruces/go-sqlite3/ext/pivot`](https://pkg.go.dev/github.com/ncruces/go-sqlite3/ext/pivot)
|
||||
creates [pivot tables](https://github.com/jakethaw/pivot_vtab).
|
||||
- [`github.com/ncruces/go-sqlite3/ext/statement`](https://pkg.go.dev/github.com/ncruces/go-sqlite3/ext/statement)
|
||||
creates [table-valued functions with SQL](https://github.com/0x09/sqlite-statement-vtab).
|
||||
- [`github.com/ncruces/go-sqlite3/ext/stats`](https://pkg.go.dev/github.com/ncruces/go-sqlite3/ext/stats)
|
||||
provides [statistics functions](https://www.oreilly.com/library/view/sql-in-a/9780596155322/ch04s02.html).
|
||||
- [`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/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)
|
||||
implements a VFS for immutable databases.
|
||||
- [`github.com/ncruces/go-sqlite3/gormlite`](https://pkg.go.dev/github.com/ncruces/go-sqlite3/gormlite)
|
||||
provides a [GORM](https://gorm.io) driver.
|
||||
|
||||
### Advanced features
|
||||
|
||||
- [x] [incremental BLOB I/O](https://sqlite.org/c3ref/blob_open.html)
|
||||
- [x] [nested transactions](https://sqlite.org/lang_savepoint.html)
|
||||
- [x] [custom functions](https://sqlite.org/c3ref/create_function.html)
|
||||
- [x] [virtual tables](https://sqlite.org/vtab.html)
|
||||
- [x] [custom VFSes](https://sqlite.org/vfs.html)
|
||||
- [x] [online backup](https://sqlite.org/backup.html)
|
||||
- [x] [JSON support](https://sqlite.org/json1.html)
|
||||
- [x] [Unicode support](https://sqlite.org/src/dir/ext/icu)
|
||||
|
||||
### Caveats
|
||||
|
||||
This module replaces the SQLite [OS Interface](https://www.sqlite.org/vfs.html)
|
||||
This module replaces the SQLite [OS Interface](https://sqlite.org/vfs.html)
|
||||
(aka VFS) with a [pure Go](vfs/) implementation.
|
||||
This has benefits, but also comes with some drawbacks.
|
||||
|
||||
#### Write-Ahead Logging
|
||||
|
||||
Because WASM does not support shared memory,
|
||||
[WAL](https://www.sqlite.org/wal.html) support is [limited](https://www.sqlite.org/wal.html#noshm).
|
||||
[WAL](https://sqlite.org/wal.html) support is [limited](https://sqlite.org/wal.html#noshm).
|
||||
|
||||
To work around this limitation, SQLite is [patched](sqlite3/locking_mode.patch)
|
||||
to always use `EXCLUSIVE` locking mode for WAL databases.
|
||||
@@ -52,7 +76,7 @@ with WAL mode databases you should disable connection pooling by calling
|
||||
#### File Locking
|
||||
|
||||
POSIX advisory locks, which SQLite uses on Unix, are
|
||||
[broken by design](https://www.sqlite.org/src/artifact/2e8b12?ln=1073-1161).
|
||||
[broken by design](https://sqlite.org/src/artifact/2e8b12?ln=1073-1161).
|
||||
|
||||
On Linux, macOS and illumos, this module uses
|
||||
[OFD locks](https://www.gnu.org/software/libc/manual/html_node/Open-File-Description-Locks.html)
|
||||
@@ -67,7 +91,7 @@ On Windows, this module uses `LockFile`, `LockFileEx`, and `UnlockFile`,
|
||||
like SQLite.
|
||||
|
||||
On all other platforms, file locking is not supported, and you must use
|
||||
[`nolock=1`](https://www.sqlite.org/uri.html#urinolock)
|
||||
[`nolock=1`](https://sqlite.org/uri.html#urinolock)
|
||||
to open database files.
|
||||
To use the [`database/sql`](https://pkg.go.dev/database/sql) driver
|
||||
with `nolock=1` you must disable connection pooling by calling
|
||||
@@ -81,22 +105,6 @@ on Linux, macOS, Windows and FreeBSD.
|
||||
Performance is tested by running
|
||||
[speedtest1](https://github.com/sqlite/sqlite/blob/master/test/speedtest1.c).
|
||||
|
||||
### Roadmap
|
||||
|
||||
- [ ] advanced SQLite features
|
||||
- [x] custom functions
|
||||
- [x] nested transactions
|
||||
- [x] incremental BLOB I/O
|
||||
- [x] online backup
|
||||
- [x] JSON support
|
||||
- [ ] virtual tables
|
||||
- [ ] session extension
|
||||
- [ ] custom VFSes
|
||||
- [x] custom VFS API
|
||||
- [x] in-memory VFS
|
||||
- [x] read-only VFS, wrapping an [`io.ReaderAt`](https://pkg.go.dev/io#ReaderAt)
|
||||
- [ ] cloud-based VFS, based on [Cloud Backed SQLite](https://sqlite.org/cloudsqlite/doc/trunk/www/index.wiki)
|
||||
|
||||
### Alternatives
|
||||
|
||||
- [`modernc.org/sqlite`](https://pkg.go.dev/modernc.org/sqlite)
|
||||
|
||||
30
backup.go
30
backup.go
@@ -2,7 +2,7 @@ package sqlite3
|
||||
|
||||
// Backup is an handle to an ongoing online backup operation.
|
||||
//
|
||||
// https://www.sqlite.org/c3ref/backup.html
|
||||
// https://sqlite.org/c3ref/backup.html
|
||||
type Backup struct {
|
||||
c *Conn
|
||||
handle uint32
|
||||
@@ -15,7 +15,7 @@ type Backup struct {
|
||||
// and blocks until the entire backup is complete.
|
||||
// Use [Conn.BackupInit] for incremental backup.
|
||||
//
|
||||
// https://www.sqlite.org/backup.html
|
||||
// https://sqlite.org/backup.html
|
||||
func (src *Conn) Backup(srcDB, dstURI string) error {
|
||||
b, err := src.BackupInit(srcDB, dstURI)
|
||||
if err != nil {
|
||||
@@ -31,7 +31,7 @@ func (src *Conn) Backup(srcDB, dstURI string) error {
|
||||
// Restore opens the SQLite database file srcURI,
|
||||
// and blocks until the entire restore is complete.
|
||||
//
|
||||
// https://www.sqlite.org/backup.html
|
||||
// https://sqlite.org/backup.html
|
||||
func (dst *Conn) Restore(dstDB, srcURI string) error {
|
||||
src, err := dst.openDB(srcURI, OPEN_READONLY|OPEN_URI)
|
||||
if err != nil {
|
||||
@@ -52,7 +52,7 @@ func (dst *Conn) Restore(dstDB, srcURI string) error {
|
||||
// then initializes a backup that copies the contents of srcDB on the src connection
|
||||
// to the "main" database in dstURI.
|
||||
//
|
||||
// https://www.sqlite.org/c3ref/backup_finish.html#sqlite3backupinit
|
||||
// https://sqlite.org/c3ref/backup_finish.html#sqlite3backupinit
|
||||
func (src *Conn) BackupInit(srcDB, dstURI string) (*Backup, error) {
|
||||
dst, err := src.openDB(dstURI, OPEN_READWRITE|OPEN_CREATE|OPEN_URI)
|
||||
if err != nil {
|
||||
@@ -62,7 +62,7 @@ func (src *Conn) BackupInit(srcDB, dstURI string) (*Backup, error) {
|
||||
}
|
||||
|
||||
func (c *Conn) backupInit(dst uint32, dstName string, src uint32, srcName string) (*Backup, error) {
|
||||
defer c.arena.reset()
|
||||
defer c.arena.mark()()
|
||||
dstPtr := c.arena.string(dstName)
|
||||
srcPtr := c.arena.string(srcName)
|
||||
|
||||
@@ -71,12 +71,12 @@ func (c *Conn) backupInit(dst uint32, dstName string, src uint32, srcName string
|
||||
other = src
|
||||
}
|
||||
|
||||
r := c.call(c.api.backupInit,
|
||||
r := c.call("sqlite3_backup_init",
|
||||
uint64(dst), uint64(dstPtr),
|
||||
uint64(src), uint64(srcPtr))
|
||||
if r == 0 {
|
||||
defer c.closeDB(other)
|
||||
r = c.call(c.api.errcode, uint64(dst))
|
||||
r = c.call("sqlite3_errcode", uint64(dst))
|
||||
return nil, c.sqlite.error(r, dst)
|
||||
}
|
||||
|
||||
@@ -91,13 +91,13 @@ func (c *Conn) backupInit(dst uint32, dstName string, src uint32, srcName string
|
||||
//
|
||||
// It is safe to close a nil, zero or closed Backup.
|
||||
//
|
||||
// https://www.sqlite.org/c3ref/backup_finish.html#sqlite3backupfinish
|
||||
// https://sqlite.org/c3ref/backup_finish.html#sqlite3backupfinish
|
||||
func (b *Backup) Close() error {
|
||||
if b == nil || b.handle == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
r := b.c.call(b.c.api.backupFinish, uint64(b.handle))
|
||||
r := b.c.call("sqlite3_backup_finish", uint64(b.handle))
|
||||
b.c.closeDB(b.otherc)
|
||||
b.handle = 0
|
||||
return b.c.error(r)
|
||||
@@ -106,9 +106,9 @@ func (b *Backup) Close() error {
|
||||
// Step copies up to nPage pages between the source and destination databases.
|
||||
// If nPage is negative, all remaining source pages are copied.
|
||||
//
|
||||
// https://www.sqlite.org/c3ref/backup_finish.html#sqlite3backupstep
|
||||
// https://sqlite.org/c3ref/backup_finish.html#sqlite3backupstep
|
||||
func (b *Backup) Step(nPage int) (done bool, err error) {
|
||||
r := b.c.call(b.c.api.backupStep, uint64(b.handle), uint64(nPage))
|
||||
r := b.c.call("sqlite3_backup_step", uint64(b.handle), uint64(nPage))
|
||||
if r == _DONE {
|
||||
return true, nil
|
||||
}
|
||||
@@ -118,17 +118,17 @@ func (b *Backup) Step(nPage int) (done bool, err error) {
|
||||
// Remaining returns the number of pages still to be backed up
|
||||
// at the conclusion of the most recent [Backup.Step].
|
||||
//
|
||||
// https://www.sqlite.org/c3ref/backup_finish.html#sqlite3backupremaining
|
||||
// https://sqlite.org/c3ref/backup_finish.html#sqlite3backupremaining
|
||||
func (b *Backup) Remaining() int {
|
||||
r := b.c.call(b.c.api.backupRemaining, uint64(b.handle))
|
||||
r := b.c.call("sqlite3_backup_remaining", uint64(b.handle))
|
||||
return int(r)
|
||||
}
|
||||
|
||||
// PageCount returns the total number of pages in the source database
|
||||
// at the conclusion of the most recent [Backup.Step].
|
||||
//
|
||||
// https://www.sqlite.org/c3ref/backup_finish.html#sqlite3backuppagecount
|
||||
// https://sqlite.org/c3ref/backup_finish.html#sqlite3backuppagecount
|
||||
func (b *Backup) PageCount() int {
|
||||
r := b.c.call(b.c.api.backupPageCount, uint64(b.handle))
|
||||
r := b.c.call("sqlite3_backup_pagecount", uint64(b.handle))
|
||||
return int(r)
|
||||
}
|
||||
|
||||
50
blob.go
50
blob.go
@@ -15,7 +15,7 @@ type ZeroBlob int64
|
||||
//
|
||||
// It implements [io.ReadWriteSeeker] for incremental BLOB I/O.
|
||||
//
|
||||
// https://www.sqlite.org/c3ref/blob.html
|
||||
// https://sqlite.org/c3ref/blob.html
|
||||
type Blob struct {
|
||||
c *Conn
|
||||
bytes int64
|
||||
@@ -27,10 +27,10 @@ var _ io.ReadWriteSeeker = &Blob{}
|
||||
|
||||
// OpenBlob opens a BLOB for incremental I/O.
|
||||
//
|
||||
// https://www.sqlite.org/c3ref/blob_open.html
|
||||
// https://sqlite.org/c3ref/blob_open.html
|
||||
func (c *Conn) OpenBlob(db, table, column string, row int64, write bool) (*Blob, error) {
|
||||
c.checkInterrupt()
|
||||
defer c.arena.reset()
|
||||
defer c.arena.mark()()
|
||||
blobPtr := c.arena.new(ptrlen)
|
||||
dbPtr := c.arena.string(db)
|
||||
tablePtr := c.arena.string(table)
|
||||
@@ -41,7 +41,7 @@ func (c *Conn) OpenBlob(db, table, column string, row int64, write bool) (*Blob,
|
||||
flags = 1
|
||||
}
|
||||
|
||||
r := c.call(c.api.blobOpen, uint64(c.handle),
|
||||
r := c.call("sqlite3_blob_open", uint64(c.handle),
|
||||
uint64(dbPtr), uint64(tablePtr), uint64(columnPtr),
|
||||
uint64(row), flags, uint64(blobPtr))
|
||||
|
||||
@@ -51,7 +51,7 @@ func (c *Conn) OpenBlob(db, table, column string, row int64, write bool) (*Blob,
|
||||
|
||||
blob := Blob{c: c}
|
||||
blob.handle = util.ReadUint32(c.mod, blobPtr)
|
||||
blob.bytes = int64(c.call(c.api.blobBytes, uint64(blob.handle)))
|
||||
blob.bytes = int64(c.call("sqlite3_blob_bytes", uint64(blob.handle)))
|
||||
return &blob, nil
|
||||
}
|
||||
|
||||
@@ -59,13 +59,13 @@ func (c *Conn) OpenBlob(db, table, column string, row int64, write bool) (*Blob,
|
||||
//
|
||||
// It is safe to close a nil, zero or closed Blob.
|
||||
//
|
||||
// https://www.sqlite.org/c3ref/blob_close.html
|
||||
// https://sqlite.org/c3ref/blob_close.html
|
||||
func (b *Blob) Close() error {
|
||||
if b == nil || b.handle == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
r := b.c.call(b.c.api.blobClose, uint64(b.handle))
|
||||
r := b.c.call("sqlite3_blob_close", uint64(b.handle))
|
||||
|
||||
b.handle = 0
|
||||
return b.c.error(r)
|
||||
@@ -73,14 +73,14 @@ func (b *Blob) Close() error {
|
||||
|
||||
// Size returns the size of the BLOB in bytes.
|
||||
//
|
||||
// https://www.sqlite.org/c3ref/blob_bytes.html
|
||||
// https://sqlite.org/c3ref/blob_bytes.html
|
||||
func (b *Blob) Size() int64 {
|
||||
return b.bytes
|
||||
}
|
||||
|
||||
// Read implements the [io.Reader] interface.
|
||||
//
|
||||
// https://www.sqlite.org/c3ref/blob_read.html
|
||||
// https://sqlite.org/c3ref/blob_read.html
|
||||
func (b *Blob) Read(p []byte) (n int, err error) {
|
||||
if b.offset >= b.bytes {
|
||||
return 0, io.EOF
|
||||
@@ -92,10 +92,10 @@ func (b *Blob) Read(p []byte) (n int, err error) {
|
||||
want = avail
|
||||
}
|
||||
|
||||
defer b.c.arena.reset()
|
||||
defer b.c.arena.mark()()
|
||||
ptr := b.c.arena.new(uint64(want))
|
||||
|
||||
r := b.c.call(b.c.api.blobRead, uint64(b.handle),
|
||||
r := b.c.call("sqlite3_blob_read", uint64(b.handle),
|
||||
uint64(ptr), uint64(want), uint64(b.offset))
|
||||
err = b.c.error(r)
|
||||
if err != nil {
|
||||
@@ -112,7 +112,7 @@ func (b *Blob) Read(p []byte) (n int, err error) {
|
||||
|
||||
// WriteTo implements the [io.WriterTo] interface.
|
||||
//
|
||||
// https://www.sqlite.org/c3ref/blob_read.html
|
||||
// https://sqlite.org/c3ref/blob_read.html
|
||||
func (b *Blob) WriteTo(w io.Writer) (n int64, err error) {
|
||||
if b.offset >= b.bytes {
|
||||
return 0, nil
|
||||
@@ -124,11 +124,11 @@ func (b *Blob) WriteTo(w io.Writer) (n int64, err error) {
|
||||
want = avail
|
||||
}
|
||||
|
||||
ptr := b.c.new(uint64(want))
|
||||
defer b.c.free(ptr)
|
||||
defer b.c.arena.mark()()
|
||||
ptr := b.c.arena.new(uint64(want))
|
||||
|
||||
for want > 0 {
|
||||
r := b.c.call(b.c.api.blobRead, uint64(b.handle),
|
||||
r := b.c.call("sqlite3_blob_read", uint64(b.handle),
|
||||
uint64(ptr), uint64(want), uint64(b.offset))
|
||||
err = b.c.error(r)
|
||||
if err != nil {
|
||||
@@ -156,12 +156,12 @@ func (b *Blob) WriteTo(w io.Writer) (n int64, err error) {
|
||||
|
||||
// Write implements the [io.Writer] interface.
|
||||
//
|
||||
// https://www.sqlite.org/c3ref/blob_write.html
|
||||
// https://sqlite.org/c3ref/blob_write.html
|
||||
func (b *Blob) Write(p []byte) (n int, err error) {
|
||||
defer b.c.arena.reset()
|
||||
defer b.c.arena.mark()()
|
||||
ptr := b.c.arena.bytes(p)
|
||||
|
||||
r := b.c.call(b.c.api.blobWrite, uint64(b.handle),
|
||||
r := b.c.call("sqlite3_blob_write", uint64(b.handle),
|
||||
uint64(ptr), uint64(len(p)), uint64(b.offset))
|
||||
err = b.c.error(r)
|
||||
if err != nil {
|
||||
@@ -173,7 +173,7 @@ func (b *Blob) Write(p []byte) (n int, err error) {
|
||||
|
||||
// ReadFrom implements the [io.ReaderFrom] interface.
|
||||
//
|
||||
// https://www.sqlite.org/c3ref/blob_write.html
|
||||
// https://sqlite.org/c3ref/blob_write.html
|
||||
func (b *Blob) ReadFrom(r io.Reader) (n int64, err error) {
|
||||
want := int64(1024 * 1024)
|
||||
avail := b.bytes - b.offset
|
||||
@@ -187,14 +187,14 @@ func (b *Blob) ReadFrom(r io.Reader) (n int64, err error) {
|
||||
want = 1
|
||||
}
|
||||
|
||||
ptr := b.c.new(uint64(want))
|
||||
defer b.c.free(ptr)
|
||||
defer b.c.arena.mark()()
|
||||
ptr := b.c.arena.new(uint64(want))
|
||||
|
||||
for {
|
||||
mem := util.View(b.c.mod, ptr, uint64(want))
|
||||
m, err := r.Read(mem[:want])
|
||||
if m > 0 {
|
||||
r := b.c.call(b.c.api.blobWrite, uint64(b.handle),
|
||||
r := b.c.call("sqlite3_blob_write", uint64(b.handle),
|
||||
uint64(ptr), uint64(m), uint64(b.offset))
|
||||
err := b.c.error(r)
|
||||
if err != nil {
|
||||
@@ -241,10 +241,10 @@ func (b *Blob) Seek(offset int64, whence int) (int64, error) {
|
||||
|
||||
// Reopen moves a BLOB handle to a new row of the same database table.
|
||||
//
|
||||
// https://www.sqlite.org/c3ref/blob_reopen.html
|
||||
// https://sqlite.org/c3ref/blob_reopen.html
|
||||
func (b *Blob) Reopen(row int64) error {
|
||||
err := b.c.error(b.c.call(b.c.api.blobReopen, uint64(b.handle), uint64(row)))
|
||||
b.bytes = int64(b.c.call(b.c.api.blobBytes, uint64(b.handle)))
|
||||
err := b.c.error(b.c.call("sqlite3_blob_reopen", uint64(b.handle), uint64(row)))
|
||||
b.bytes = int64(b.c.call("sqlite3_blob_bytes", uint64(b.handle)))
|
||||
b.offset = 0
|
||||
return err
|
||||
}
|
||||
|
||||
74
conn.go
74
conn.go
@@ -5,7 +5,6 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
"github.com/ncruces/go-sqlite3/internal/util"
|
||||
@@ -15,7 +14,7 @@ import (
|
||||
// Conn is a database connection handle.
|
||||
// A Conn is not safe for concurrent use by multiple goroutines.
|
||||
//
|
||||
// https://www.sqlite.org/c3ref/sqlite3.html
|
||||
// https://sqlite.org/c3ref/sqlite3.html
|
||||
type Conn struct {
|
||||
*sqlite
|
||||
|
||||
@@ -38,7 +37,7 @@ func Open(filename string) (*Conn, error) {
|
||||
//
|
||||
// sqlite3.Open("file:demo.db?_pragma=busy_timeout(10000)")
|
||||
//
|
||||
// https://www.sqlite.org/c3ref/open.html
|
||||
// https://sqlite.org/c3ref/open.html
|
||||
func OpenFlags(filename string, flags OpenFlag) (*Conn, error) {
|
||||
if flags&(OPEN_READONLY|OPEN_READWRITE|OPEN_CREATE) == 0 {
|
||||
flags |= OPEN_READWRITE | OPEN_CREATE
|
||||
@@ -56,8 +55,6 @@ func newConn(filename string, flags OpenFlag) (conn *Conn, err error) {
|
||||
defer func() {
|
||||
if conn == nil {
|
||||
sqlite.close()
|
||||
} else {
|
||||
runtime.SetFinalizer(conn, util.Finalizer[Conn](3))
|
||||
}
|
||||
}()
|
||||
|
||||
@@ -72,12 +69,12 @@ func newConn(filename string, flags OpenFlag) (conn *Conn, err error) {
|
||||
}
|
||||
|
||||
func (c *Conn) openDB(filename string, flags OpenFlag) (uint32, error) {
|
||||
defer c.arena.reset()
|
||||
defer c.arena.mark()()
|
||||
connPtr := c.arena.new(ptrlen)
|
||||
namePtr := c.arena.string(filename)
|
||||
|
||||
flags |= OPEN_EXRESCODE
|
||||
r := c.call(c.api.open, uint64(namePtr), uint64(connPtr), uint64(flags), 0)
|
||||
r := c.call("sqlite3_open_v2", uint64(namePtr), uint64(connPtr), uint64(flags), 0)
|
||||
|
||||
handle := util.ReadUint32(c.mod, connPtr)
|
||||
if err := c.sqlite.error(r, handle); err != nil {
|
||||
@@ -92,13 +89,12 @@ func (c *Conn) openDB(filename string, flags OpenFlag) (uint32, error) {
|
||||
for _, p := range query["_pragma"] {
|
||||
pragmas.WriteString(`PRAGMA `)
|
||||
pragmas.WriteString(p)
|
||||
pragmas.WriteByte(';')
|
||||
pragmas.WriteString(`;`)
|
||||
}
|
||||
}
|
||||
|
||||
c.arena.reset()
|
||||
pragmaPtr := c.arena.string(pragmas.String())
|
||||
r := c.call(c.api.exec, uint64(handle), uint64(pragmaPtr), 0, 0, 0)
|
||||
r := c.call("sqlite3_exec", uint64(handle), uint64(pragmaPtr), 0, 0, 0)
|
||||
if err := c.sqlite.error(r, handle, pragmas.String()); err != nil {
|
||||
if errors.Is(err, ERROR) {
|
||||
err = fmt.Errorf("sqlite3: invalid _pragma: %w", err)
|
||||
@@ -112,7 +108,7 @@ func (c *Conn) openDB(filename string, flags OpenFlag) (uint32, error) {
|
||||
}
|
||||
|
||||
func (c *Conn) closeDB(handle uint32) {
|
||||
r := c.call(c.api.closeZombie, uint64(handle))
|
||||
r := c.call("sqlite3_close_v2", uint64(handle))
|
||||
if err := c.sqlite.error(r, handle); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@@ -126,7 +122,7 @@ func (c *Conn) closeDB(handle uint32) {
|
||||
//
|
||||
// It is safe to close a nil, zero or closed Conn.
|
||||
//
|
||||
// https://www.sqlite.org/c3ref/close.html
|
||||
// https://sqlite.org/c3ref/close.html
|
||||
func (c *Conn) Close() error {
|
||||
if c == nil || c.handle == 0 {
|
||||
return nil
|
||||
@@ -135,27 +131,26 @@ func (c *Conn) Close() error {
|
||||
c.pending.Close()
|
||||
c.pending = nil
|
||||
|
||||
r := c.call(c.api.close, uint64(c.handle))
|
||||
r := c.call("sqlite3_close", uint64(c.handle))
|
||||
if err := c.error(r); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c.handle = 0
|
||||
runtime.SetFinalizer(c, nil)
|
||||
return c.close()
|
||||
}
|
||||
|
||||
// Exec is a convenience function that allows an application to run
|
||||
// multiple statements of SQL without having to use a lot of code.
|
||||
//
|
||||
// https://www.sqlite.org/c3ref/exec.html
|
||||
// https://sqlite.org/c3ref/exec.html
|
||||
func (c *Conn) Exec(sql string) error {
|
||||
c.checkInterrupt()
|
||||
defer c.arena.reset()
|
||||
defer c.arena.mark()()
|
||||
sqlPtr := c.arena.string(sql)
|
||||
|
||||
r := c.call(c.api.exec, uint64(c.handle), uint64(sqlPtr), 0, 0, 0)
|
||||
return c.error(r)
|
||||
r := c.call("sqlite3_exec", uint64(c.handle), uint64(sqlPtr), 0, 0, 0)
|
||||
return c.error(r, sql)
|
||||
}
|
||||
|
||||
// Prepare calls [Conn.PrepareFlags] with no flags.
|
||||
@@ -168,25 +163,26 @@ func (c *Conn) Prepare(sql string) (stmt *Stmt, tail string, err error) {
|
||||
// If the input text contains no SQL (if the input is an empty string or a comment),
|
||||
// both stmt and err will be nil.
|
||||
//
|
||||
// https://www.sqlite.org/c3ref/prepare.html
|
||||
// https://sqlite.org/c3ref/prepare.html
|
||||
func (c *Conn) PrepareFlags(sql string, flags PrepareFlag) (stmt *Stmt, tail string, err error) {
|
||||
if emptyStatement(sql) {
|
||||
return nil, "", nil
|
||||
if len(sql) > _MAX_LENGTH {
|
||||
return nil, "", TOOBIG
|
||||
}
|
||||
|
||||
defer c.arena.reset()
|
||||
defer c.arena.mark()()
|
||||
stmtPtr := c.arena.new(ptrlen)
|
||||
tailPtr := c.arena.new(ptrlen)
|
||||
sqlPtr := c.arena.string(sql)
|
||||
|
||||
r := c.call(c.api.prepare, uint64(c.handle),
|
||||
r := c.call("sqlite3_prepare_v3", uint64(c.handle),
|
||||
uint64(sqlPtr), uint64(len(sql)+1), uint64(flags),
|
||||
uint64(stmtPtr), uint64(tailPtr))
|
||||
|
||||
stmt = &Stmt{c: c}
|
||||
stmt.handle = util.ReadUint32(c.mod, stmtPtr)
|
||||
i := util.ReadUint32(c.mod, tailPtr)
|
||||
tail = sql[i-sqlPtr:]
|
||||
if sql := sql[util.ReadUint32(c.mod, tailPtr)-sqlPtr:]; sql != "" {
|
||||
tail = sql
|
||||
}
|
||||
|
||||
if err := c.error(r, sql); err != nil {
|
||||
return nil, "", err
|
||||
@@ -194,23 +190,23 @@ func (c *Conn) PrepareFlags(sql string, flags PrepareFlag) (stmt *Stmt, tail str
|
||||
if stmt.handle == 0 {
|
||||
return nil, "", nil
|
||||
}
|
||||
return
|
||||
return stmt, tail, nil
|
||||
}
|
||||
|
||||
// GetAutocommit tests the connection for auto-commit mode.
|
||||
//
|
||||
// https://www.sqlite.org/c3ref/get_autocommit.html
|
||||
// https://sqlite.org/c3ref/get_autocommit.html
|
||||
func (c *Conn) GetAutocommit() bool {
|
||||
r := c.call(c.api.autocommit, uint64(c.handle))
|
||||
r := c.call("sqlite3_get_autocommit", uint64(c.handle))
|
||||
return r != 0
|
||||
}
|
||||
|
||||
// LastInsertRowID returns the rowid of the most recent successful INSERT
|
||||
// on the database connection.
|
||||
//
|
||||
// https://www.sqlite.org/c3ref/last_insert_rowid.html
|
||||
// https://sqlite.org/c3ref/last_insert_rowid.html
|
||||
func (c *Conn) LastInsertRowID() int64 {
|
||||
r := c.call(c.api.lastRowid, uint64(c.handle))
|
||||
r := c.call("sqlite3_last_insert_rowid", uint64(c.handle))
|
||||
return int64(r)
|
||||
}
|
||||
|
||||
@@ -218,9 +214,9 @@ func (c *Conn) LastInsertRowID() int64 {
|
||||
// by the most recently completed INSERT, UPDATE or DELETE statement
|
||||
// on the database connection.
|
||||
//
|
||||
// https://www.sqlite.org/c3ref/changes.html
|
||||
// https://sqlite.org/c3ref/changes.html
|
||||
func (c *Conn) Changes() int64 {
|
||||
r := c.call(c.api.changes, uint64(c.handle))
|
||||
r := c.call("sqlite3_changes64", uint64(c.handle))
|
||||
return int64(r)
|
||||
}
|
||||
|
||||
@@ -237,7 +233,7 @@ func (c *Conn) Changes() int64 {
|
||||
//
|
||||
// SetInterrupt returns the old context assigned to the connection.
|
||||
//
|
||||
// https://www.sqlite.org/c3ref/interrupt.html
|
||||
// https://sqlite.org/c3ref/interrupt.html
|
||||
func (c *Conn) SetInterrupt(ctx context.Context) (old context.Context) {
|
||||
// Is it the same context?
|
||||
if ctx == c.interrupt {
|
||||
@@ -256,16 +252,16 @@ func (c *Conn) SetInterrupt(ctx context.Context) (old context.Context) {
|
||||
c.interrupt = ctx
|
||||
// Remove the handler if the context can't be canceled.
|
||||
if ctx == nil || ctx.Done() == nil {
|
||||
c.call(c.api.progressHandler, uint64(c.handle), 0)
|
||||
c.call("sqlite3_progress_handler_go", uint64(c.handle), 0)
|
||||
return old
|
||||
}
|
||||
|
||||
c.pending.Step()
|
||||
c.call(c.api.progressHandler, uint64(c.handle), 100)
|
||||
c.call("sqlite3_progress_handler_go", uint64(c.handle), 100)
|
||||
return old
|
||||
}
|
||||
|
||||
func callbackProgress(ctx context.Context, mod api.Module, _ uint32) uint32 {
|
||||
func progressCallback(ctx context.Context, mod api.Module, _ uint32) uint32 {
|
||||
if c, ok := ctx.Value(connKey{}).(*Conn); ok {
|
||||
if c.interrupt != nil && c.interrupt.Err() != nil {
|
||||
return 1
|
||||
@@ -276,13 +272,13 @@ func callbackProgress(ctx context.Context, mod api.Module, _ uint32) uint32 {
|
||||
|
||||
func (c *Conn) checkInterrupt() {
|
||||
if c.interrupt != nil && c.interrupt.Err() != nil {
|
||||
c.call(c.api.interrupt, uint64(c.handle))
|
||||
c.call("sqlite3_interrupt", uint64(c.handle))
|
||||
}
|
||||
}
|
||||
|
||||
// Pragma executes a PRAGMA statement and returns any results.
|
||||
//
|
||||
// https://www.sqlite.org/pragma.html
|
||||
// https://sqlite.org/pragma.html
|
||||
func (c *Conn) Pragma(str string) ([]string, error) {
|
||||
stmt, _, err := c.Prepare(`PRAGMA ` + str)
|
||||
if err != nil {
|
||||
@@ -305,7 +301,7 @@ func (c *Conn) error(rc uint64, sql ...string) error {
|
||||
//
|
||||
// It can be used to access SQLite features like [online backup].
|
||||
//
|
||||
// [online backup]: https://www.sqlite.org/backup.html
|
||||
// [online backup]: https://sqlite.org/backup.html
|
||||
type DriverConn interface {
|
||||
Raw() *Conn
|
||||
}
|
||||
|
||||
37
const.go
37
const.go
@@ -9,8 +9,9 @@ const (
|
||||
|
||||
_UTF8 = 1
|
||||
|
||||
_MAX_STRING = 512 // Used for short strings: names, error messages…
|
||||
|
||||
_MAX_NAME = 512 // Used for short strings: names, error messages…
|
||||
_MAX_LENGTH = 1e9
|
||||
_MAX_SQL_LENGTH = 1e9
|
||||
_MAX_ALLOCATION_SIZE = 0x7ffffeff
|
||||
|
||||
ptrlen = 4
|
||||
@@ -18,7 +19,7 @@ const (
|
||||
|
||||
// ErrorCode is a result code that [Error.Code] might return.
|
||||
//
|
||||
// https://www.sqlite.org/rescode.html
|
||||
// https://sqlite.org/rescode.html
|
||||
type ErrorCode uint8
|
||||
|
||||
const (
|
||||
@@ -54,7 +55,7 @@ const (
|
||||
|
||||
// ExtendedErrorCode is a result code that [Error.ExtendedCode] might return.
|
||||
//
|
||||
// https://www.sqlite.org/rescode.html
|
||||
// https://sqlite.org/rescode.html
|
||||
type (
|
||||
ExtendedErrorCode uint16
|
||||
xErrorCode = ExtendedErrorCode
|
||||
@@ -140,7 +141,7 @@ const (
|
||||
|
||||
// OpenFlag is a flag for the [OpenFlags] function.
|
||||
//
|
||||
// https://www.sqlite.org/c3ref/c_open_autoproxy.html
|
||||
// https://sqlite.org/c3ref/c_open_autoproxy.html
|
||||
type OpenFlag uint32
|
||||
|
||||
const (
|
||||
@@ -159,7 +160,7 @@ const (
|
||||
|
||||
// PrepareFlag is a flag that can be passed to [Conn.PrepareFlags].
|
||||
//
|
||||
// https://www.sqlite.org/c3ref/c_prepare_normalize.html
|
||||
// https://sqlite.org/c3ref/c_prepare_normalize.html
|
||||
type PrepareFlag uint32
|
||||
|
||||
const (
|
||||
@@ -168,9 +169,10 @@ const (
|
||||
PREPARE_NO_VTAB PrepareFlag = 0x04
|
||||
)
|
||||
|
||||
// FunctionFlag is a flag that can be passed to [Conn.PrepareFlags].
|
||||
// FunctionFlag is a flag that can be passed to
|
||||
// [Conn.CreateFunction] and [Conn.CreateWindowFunction].
|
||||
//
|
||||
// https://www.sqlite.org/c3ref/c_deterministic.html
|
||||
// https://sqlite.org/c3ref/c_deterministic.html
|
||||
type FunctionFlag uint32
|
||||
|
||||
const (
|
||||
@@ -180,9 +182,26 @@ const (
|
||||
INNOCUOUS FunctionFlag = 0x000200000
|
||||
)
|
||||
|
||||
// StmtStatus name counter values associated with the [Stmt.Status] method.
|
||||
//
|
||||
// https://sqlite.org/c3ref/c_stmtstatus_counter.html
|
||||
type StmtStatus uint32
|
||||
|
||||
const (
|
||||
STMTSTATUS_FULLSCAN_STEP StmtStatus = 1
|
||||
STMTSTATUS_SORT StmtStatus = 2
|
||||
STMTSTATUS_AUTOINDEX StmtStatus = 3
|
||||
STMTSTATUS_VM_STEP StmtStatus = 4
|
||||
STMTSTATUS_REPREPARE StmtStatus = 5
|
||||
STMTSTATUS_RUN StmtStatus = 6
|
||||
STMTSTATUS_FILTER_MISS StmtStatus = 7
|
||||
STMTSTATUS_FILTER_HIT StmtStatus = 8
|
||||
STMTSTATUS_MEMUSED StmtStatus = 99
|
||||
)
|
||||
|
||||
// Datatype is a fundamental datatype of SQLite.
|
||||
//
|
||||
// https://www.sqlite.org/c3ref/c_blob.html
|
||||
// https://sqlite.org/c3ref/c_blob.html
|
||||
type Datatype uint32
|
||||
|
||||
const (
|
||||
|
||||
111
context.go
111
context.go
@@ -12,7 +12,7 @@ import (
|
||||
// Context is the context in which an SQL function executes.
|
||||
// An SQLite [Context] is in no way related to a Go [context.Context].
|
||||
//
|
||||
// https://www.sqlite.org/c3ref/context.html
|
||||
// https://sqlite.org/c3ref/context.html
|
||||
type Context struct {
|
||||
c *Conn
|
||||
handle uint32
|
||||
@@ -29,17 +29,17 @@ func (ctx Context) Conn() *Conn {
|
||||
|
||||
// SetAuxData saves metadata for argument n of the function.
|
||||
//
|
||||
// https://www.sqlite.org/c3ref/get_auxdata.html
|
||||
// https://sqlite.org/c3ref/get_auxdata.html
|
||||
func (ctx Context) SetAuxData(n int, data any) {
|
||||
ptr := util.AddHandle(ctx.c.ctx, data)
|
||||
ctx.c.call(ctx.c.api.setAuxData, uint64(ctx.handle), uint64(n), uint64(ptr))
|
||||
ctx.c.call("sqlite3_set_auxdata_go", uint64(ctx.handle), uint64(n), uint64(ptr))
|
||||
}
|
||||
|
||||
// GetAuxData returns metadata for argument n of the function.
|
||||
//
|
||||
// https://www.sqlite.org/c3ref/get_auxdata.html
|
||||
// https://sqlite.org/c3ref/get_auxdata.html
|
||||
func (ctx Context) GetAuxData(n int) any {
|
||||
ptr := uint32(ctx.c.call(ctx.c.api.getAuxData, uint64(ctx.handle), uint64(n)))
|
||||
ptr := uint32(ctx.c.call("sqlite3_get_auxdata", uint64(ctx.handle), uint64(n)))
|
||||
return util.GetHandle(ctx.c.ctx, ptr)
|
||||
}
|
||||
|
||||
@@ -47,7 +47,7 @@ func (ctx Context) GetAuxData(n int) any {
|
||||
// SQLite does not have a separate boolean storage class.
|
||||
// Instead, boolean values are stored as integers 0 (false) and 1 (true).
|
||||
//
|
||||
// https://www.sqlite.org/c3ref/result_blob.html
|
||||
// https://sqlite.org/c3ref/result_blob.html
|
||||
func (ctx Context) ResultBool(value bool) {
|
||||
var i int64
|
||||
if value {
|
||||
@@ -58,67 +58,77 @@ func (ctx Context) ResultBool(value bool) {
|
||||
|
||||
// ResultInt sets the result of the function to an int.
|
||||
//
|
||||
// https://www.sqlite.org/c3ref/result_blob.html
|
||||
// https://sqlite.org/c3ref/result_blob.html
|
||||
func (ctx Context) ResultInt(value int) {
|
||||
ctx.ResultInt64(int64(value))
|
||||
}
|
||||
|
||||
// ResultInt64 sets the result of the function to an int64.
|
||||
//
|
||||
// https://www.sqlite.org/c3ref/result_blob.html
|
||||
// https://sqlite.org/c3ref/result_blob.html
|
||||
func (ctx Context) ResultInt64(value int64) {
|
||||
ctx.c.call(ctx.c.api.resultInteger,
|
||||
ctx.c.call("sqlite3_result_int64",
|
||||
uint64(ctx.handle), uint64(value))
|
||||
}
|
||||
|
||||
// ResultFloat sets the result of the function to a float64.
|
||||
//
|
||||
// https://www.sqlite.org/c3ref/result_blob.html
|
||||
// https://sqlite.org/c3ref/result_blob.html
|
||||
func (ctx Context) ResultFloat(value float64) {
|
||||
ctx.c.call(ctx.c.api.resultFloat,
|
||||
ctx.c.call("sqlite3_result_double",
|
||||
uint64(ctx.handle), math.Float64bits(value))
|
||||
}
|
||||
|
||||
// ResultText sets the result of the function to a string.
|
||||
//
|
||||
// https://www.sqlite.org/c3ref/result_blob.html
|
||||
// https://sqlite.org/c3ref/result_blob.html
|
||||
func (ctx Context) ResultText(value string) {
|
||||
ptr := ctx.c.newString(value)
|
||||
ctx.c.call(ctx.c.api.resultText,
|
||||
ctx.c.call("sqlite3_result_text64",
|
||||
uint64(ctx.handle), uint64(ptr), uint64(len(value)),
|
||||
uint64(ctx.c.api.destructor), _UTF8)
|
||||
uint64(ctx.c.freer), _UTF8)
|
||||
}
|
||||
|
||||
// ResultRawText sets the text result of the function to a []byte.
|
||||
//
|
||||
// https://sqlite.org/c3ref/result_blob.html
|
||||
func (ctx Context) ResultRawText(value []byte) {
|
||||
ptr := ctx.c.newBytes(value)
|
||||
ctx.c.call("sqlite3_result_text64",
|
||||
uint64(ctx.handle), uint64(ptr), uint64(len(value)),
|
||||
uint64(ctx.c.freer), _UTF8)
|
||||
}
|
||||
|
||||
// ResultBlob sets the result of the function to a []byte.
|
||||
// Returning a nil slice is the same as calling [Context.ResultNull].
|
||||
//
|
||||
// https://www.sqlite.org/c3ref/result_blob.html
|
||||
// https://sqlite.org/c3ref/result_blob.html
|
||||
func (ctx Context) ResultBlob(value []byte) {
|
||||
ptr := ctx.c.newBytes(value)
|
||||
ctx.c.call(ctx.c.api.resultBlob,
|
||||
ctx.c.call("sqlite3_result_blob64",
|
||||
uint64(ctx.handle), uint64(ptr), uint64(len(value)),
|
||||
uint64(ctx.c.api.destructor))
|
||||
uint64(ctx.c.freer))
|
||||
}
|
||||
|
||||
// BindZeroBlob sets the result of the function to a zero-filled, length n BLOB.
|
||||
// ResultZeroBlob sets the result of the function to a zero-filled, length n BLOB.
|
||||
//
|
||||
// https://www.sqlite.org/c3ref/result_blob.html
|
||||
// https://sqlite.org/c3ref/result_blob.html
|
||||
func (ctx Context) ResultZeroBlob(n int64) {
|
||||
ctx.c.call(ctx.c.api.resultZeroBlob,
|
||||
ctx.c.call("sqlite3_result_zeroblob64",
|
||||
uint64(ctx.handle), uint64(n))
|
||||
}
|
||||
|
||||
// ResultNull sets the result of the function to NULL.
|
||||
//
|
||||
// https://www.sqlite.org/c3ref/result_blob.html
|
||||
// https://sqlite.org/c3ref/result_blob.html
|
||||
func (ctx Context) ResultNull() {
|
||||
ctx.c.call(ctx.c.api.resultNull,
|
||||
ctx.c.call("sqlite3_result_null",
|
||||
uint64(ctx.handle))
|
||||
}
|
||||
|
||||
// ResultTime sets the result of the function to a [time.Time].
|
||||
//
|
||||
// https://www.sqlite.org/c3ref/result_blob.html
|
||||
// https://sqlite.org/c3ref/result_blob.html
|
||||
func (ctx Context) ResultTime(value time.Time, format TimeFormat) {
|
||||
if format == TimeFormatDefault {
|
||||
ctx.resultRFC3339Nano(value)
|
||||
@@ -143,77 +153,68 @@ func (ctx Context) resultRFC3339Nano(value time.Time) {
|
||||
buf := util.View(ctx.c.mod, ptr, maxlen)
|
||||
buf = value.AppendFormat(buf[:0], time.RFC3339Nano)
|
||||
|
||||
ctx.c.call(ctx.c.api.resultText,
|
||||
ctx.c.call("sqlite3_result_text64",
|
||||
uint64(ctx.handle), uint64(ptr), uint64(len(buf)),
|
||||
uint64(ctx.c.api.destructor), _UTF8)
|
||||
uint64(ctx.c.freer), _UTF8)
|
||||
}
|
||||
|
||||
// ResultPointer sets the result of the function to NULL, just like [Context.ResultNull],
|
||||
// except that it also associates ptr with that NULL value such that it can be retrieved
|
||||
// within an application-defined SQL function using [Value.Pointer].
|
||||
//
|
||||
// https://www.sqlite.org/c3ref/result_blob.html
|
||||
// https://sqlite.org/c3ref/result_blob.html
|
||||
func (ctx Context) ResultPointer(ptr any) {
|
||||
valPtr := util.AddHandle(ctx.c.ctx, ptr)
|
||||
ctx.c.call(ctx.c.api.resultPointer, uint64(valPtr))
|
||||
ctx.c.call("sqlite3_result_pointer_go", uint64(valPtr))
|
||||
}
|
||||
|
||||
// ResultJSON sets the result of the function to the JSON encoding of value.
|
||||
//
|
||||
// https://www.sqlite.org/c3ref/result_blob.html
|
||||
// https://sqlite.org/c3ref/result_blob.html
|
||||
func (ctx Context) ResultJSON(value any) {
|
||||
data, err := json.Marshal(value)
|
||||
if err != nil {
|
||||
ctx.ResultError(err)
|
||||
return
|
||||
}
|
||||
ptr := ctx.c.newBytes(data)
|
||||
ctx.c.call(ctx.c.api.resultText,
|
||||
uint64(ctx.handle), uint64(ptr), uint64(len(data)),
|
||||
uint64(ctx.c.api.destructor))
|
||||
ctx.ResultRawText(data)
|
||||
}
|
||||
|
||||
// ResultValue sets the result of the function a copy of [Value].
|
||||
// ResultValue sets the result of the function to a copy of [Value].
|
||||
//
|
||||
// https://www.sqlite.org/c3ref/result_blob.html
|
||||
// https://sqlite.org/c3ref/result_blob.html
|
||||
func (ctx Context) ResultValue(value Value) {
|
||||
if value.sqlite != ctx.c.sqlite {
|
||||
ctx.ResultError(MISUSE)
|
||||
return
|
||||
}
|
||||
ctx.c.call(ctx.c.api.resultValue,
|
||||
ctx.c.call("sqlite3_result_value",
|
||||
uint64(ctx.handle), uint64(value.handle))
|
||||
}
|
||||
|
||||
// ResultError sets the result of the function an error.
|
||||
//
|
||||
// https://www.sqlite.org/c3ref/result_blob.html
|
||||
// https://sqlite.org/c3ref/result_blob.html
|
||||
func (ctx Context) ResultError(err error) {
|
||||
if errors.Is(err, NOMEM) {
|
||||
ctx.c.call(ctx.c.api.resultErrorMem, uint64(ctx.handle))
|
||||
ctx.c.call("sqlite3_result_error_nomem", uint64(ctx.handle))
|
||||
return
|
||||
}
|
||||
|
||||
if errors.Is(err, TOOBIG) {
|
||||
ctx.c.call(ctx.c.api.resultErrorBig, uint64(ctx.handle))
|
||||
ctx.c.call("sqlite3_result_error_toobig", uint64(ctx.handle))
|
||||
return
|
||||
}
|
||||
|
||||
str := err.Error()
|
||||
ptr := ctx.c.newString(str)
|
||||
ctx.c.call(ctx.c.api.resultError,
|
||||
uint64(ctx.handle), uint64(ptr), uint64(len(str)))
|
||||
ctx.c.free(ptr)
|
||||
|
||||
var code uint64
|
||||
var ecode ErrorCode
|
||||
var xcode xErrorCode
|
||||
switch {
|
||||
case errors.As(err, &xcode):
|
||||
code = uint64(xcode)
|
||||
case errors.As(err, &ecode):
|
||||
code = uint64(ecode)
|
||||
msg, code := errorCode(err, _OK)
|
||||
if msg != "" {
|
||||
defer ctx.c.arena.mark()()
|
||||
ptr := ctx.c.arena.string(msg)
|
||||
ctx.c.call("sqlite3_result_error",
|
||||
uint64(ctx.handle), uint64(ptr), uint64(len(msg)))
|
||||
}
|
||||
if code != 0 {
|
||||
ctx.c.call(ctx.c.api.resultErrorCode,
|
||||
uint64(ctx.handle), code)
|
||||
if code != _OK {
|
||||
ctx.c.call("sqlite3_result_error_code",
|
||||
uint64(ctx.handle), uint64(code))
|
||||
}
|
||||
}
|
||||
|
||||
170
driver/driver.go
170
driver/driver.go
@@ -12,6 +12,18 @@
|
||||
//
|
||||
// sql.Open("sqlite3", "file:demo.db?_txlock=immediate")
|
||||
//
|
||||
// Possible values are: "deferred", "immediate", "exclusive".
|
||||
// A [read-only] transaction is always "deferred", regardless of "_txlock".
|
||||
//
|
||||
// The time encoding/decoding format can be specified using "_timefmt":
|
||||
//
|
||||
// sql.Open("sqlite3", "file:demo.db?_timefmt=sqlite")
|
||||
//
|
||||
// Possible values are: "auto" (the default), "sqlite", "rfc3339";
|
||||
// "auto" encodes as RFC 3339 and decodes any [format] supported by SQLite;
|
||||
// "sqlite" encodes as SQLite and decodes any [format] supported by SQLite;
|
||||
// "rfc3339" encodes and decodes RFC 3339 only.
|
||||
//
|
||||
// [PRAGMA] statements can be specified using "_pragma":
|
||||
//
|
||||
// sql.Open("sqlite3", "file:demo.db?_pragma=busy_timeout(10000)")
|
||||
@@ -21,16 +33,17 @@
|
||||
// Order matters:
|
||||
// busy timeout and locking mode should be the first PRAGMAs set, in that order.
|
||||
//
|
||||
// [URI]: https://www.sqlite.org/uri.html
|
||||
// [PRAGMA]: https://www.sqlite.org/pragma.html
|
||||
// [TRANSACTION]: https://www.sqlite.org/lang_transaction.html#deferred_immediate_and_exclusive_transactions
|
||||
// [URI]: https://sqlite.org/uri.html
|
||||
// [PRAGMA]: https://sqlite.org/pragma.html
|
||||
// [format]: https://sqlite.org/lang_datefunc.html#time_values
|
||||
// [TRANSACTION]: https://sqlite.org/lang_transaction.html#deferred_immediate_and_exclusive_transactions
|
||||
// [read-only]: https://pkg.go.dev/database/sql#TxOptions
|
||||
package driver
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"database/sql/driver"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
@@ -44,7 +57,7 @@ import (
|
||||
|
||||
// This variable can be replaced with -ldflags:
|
||||
//
|
||||
// go build -ldflags="-X github.com/ncruces/go-sqlite3.driverName=sqlite"
|
||||
// go build -ldflags="-X github.com/ncruces/go-sqlite3/driver.driverName=sqlite"
|
||||
var driverName = "sqlite3"
|
||||
|
||||
func init() {
|
||||
@@ -82,23 +95,52 @@ func (sqlite) OpenConnector(name string) (driver.Connector, error) {
|
||||
|
||||
func newConnector(name string, init func(*sqlite3.Conn) error) (*connector, error) {
|
||||
c := connector{name: name, init: init}
|
||||
|
||||
var txlock, timefmt string
|
||||
if strings.HasPrefix(name, "file:") {
|
||||
if _, after, ok := strings.Cut(name, "?"); ok {
|
||||
query, err := url.ParseQuery(after)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
c.txlock = query.Get("_txlock")
|
||||
c.pragmas = len(query["_pragma"]) > 0
|
||||
txlock = query.Get("_txlock")
|
||||
timefmt = query.Get("_timefmt")
|
||||
c.pragmas = query.Has("_pragma")
|
||||
}
|
||||
}
|
||||
|
||||
switch txlock {
|
||||
case "":
|
||||
c.txBegin = "BEGIN"
|
||||
case "deferred", "immediate", "exclusive":
|
||||
c.txBegin = "BEGIN " + txlock
|
||||
default:
|
||||
return nil, fmt.Errorf("sqlite3: invalid _txlock: %s", txlock)
|
||||
}
|
||||
|
||||
switch timefmt {
|
||||
case "":
|
||||
c.tmRead = sqlite3.TimeFormatAuto
|
||||
c.tmWrite = sqlite3.TimeFormatDefault
|
||||
case "sqlite":
|
||||
c.tmRead = sqlite3.TimeFormatAuto
|
||||
c.tmWrite = sqlite3.TimeFormat3
|
||||
case "rfc3339":
|
||||
c.tmRead = sqlite3.TimeFormatDefault
|
||||
c.tmWrite = sqlite3.TimeFormatDefault
|
||||
default:
|
||||
c.tmRead = sqlite3.TimeFormat(timefmt)
|
||||
c.tmWrite = sqlite3.TimeFormat(timefmt)
|
||||
}
|
||||
return &c, nil
|
||||
}
|
||||
|
||||
type connector struct {
|
||||
init func(*sqlite3.Conn) error
|
||||
name string
|
||||
txlock string
|
||||
txBegin string
|
||||
tmRead sqlite3.TimeFormat
|
||||
tmWrite sqlite3.TimeFormat
|
||||
pragmas bool
|
||||
}
|
||||
|
||||
@@ -107,7 +149,12 @@ func (n *connector) Driver() driver.Driver {
|
||||
}
|
||||
|
||||
func (n *connector) Connect(ctx context.Context) (_ driver.Conn, err error) {
|
||||
var c conn
|
||||
c := &conn{
|
||||
txBegin: n.txBegin,
|
||||
tmRead: n.tmRead,
|
||||
tmWrite: n.tmWrite,
|
||||
}
|
||||
|
||||
c.Conn, err = sqlite3.Open(n.name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -121,14 +168,6 @@ func (n *connector) Connect(ctx context.Context) (_ driver.Conn, err error) {
|
||||
old := c.Conn.SetInterrupt(ctx)
|
||||
defer c.Conn.SetInterrupt(old)
|
||||
|
||||
switch n.txlock {
|
||||
case "":
|
||||
c.txBegin = "BEGIN"
|
||||
case "deferred", "immediate", "exclusive":
|
||||
c.txBegin = "BEGIN " + n.txlock
|
||||
default:
|
||||
return nil, fmt.Errorf("sqlite3: invalid _txlock: %s", n.txlock)
|
||||
}
|
||||
if !n.pragmas {
|
||||
err = c.Conn.Exec(`PRAGMA busy_timeout=60000`)
|
||||
if err != nil {
|
||||
@@ -156,7 +195,7 @@ func (n *connector) Connect(ctx context.Context) (_ driver.Conn, err error) {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return &c, nil
|
||||
return c, nil
|
||||
}
|
||||
|
||||
type conn struct {
|
||||
@@ -164,6 +203,8 @@ type conn struct {
|
||||
txBegin string
|
||||
txCommit string
|
||||
txRollback string
|
||||
tmRead sqlite3.TimeFormat
|
||||
tmWrite sqlite3.TimeFormat
|
||||
readOnly byte
|
||||
}
|
||||
|
||||
@@ -248,19 +289,10 @@ func (c *conn) PrepareContext(ctx context.Context, query string) (driver.Stmt, e
|
||||
return nil, err
|
||||
}
|
||||
if tail != "" {
|
||||
// Check if the tail contains any SQL.
|
||||
st, _, err := c.Conn.Prepare(tail)
|
||||
if err != nil {
|
||||
s.Close()
|
||||
return nil, err
|
||||
}
|
||||
if st != nil {
|
||||
s.Close()
|
||||
st.Close()
|
||||
return nil, util.TailErr
|
||||
}
|
||||
s.Close()
|
||||
return nil, util.TailErr
|
||||
}
|
||||
return &stmt{s, c.Conn}, nil
|
||||
return &stmt{Stmt: s, tmRead: c.tmRead, tmWrite: c.tmWrite}, nil
|
||||
}
|
||||
|
||||
func (c *conn) ExecContext(ctx context.Context, query string, args []driver.NamedValue) (driver.Result, error) {
|
||||
@@ -271,7 +303,7 @@ func (c *conn) ExecContext(ctx context.Context, query string, args []driver.Name
|
||||
|
||||
if savept, ok := ctx.(*saveptCtx); ok {
|
||||
// Called from driver.Savepoint.
|
||||
savept.Savepoint = c.Savepoint()
|
||||
savept.Savepoint = c.Conn.Savepoint()
|
||||
return resultRowsAffected(0), nil
|
||||
}
|
||||
|
||||
@@ -291,8 +323,9 @@ func (*conn) CheckNamedValue(arg *driver.NamedValue) error {
|
||||
}
|
||||
|
||||
type stmt struct {
|
||||
Stmt *sqlite3.Stmt
|
||||
Conn *sqlite3.Conn
|
||||
*sqlite3.Stmt
|
||||
tmWrite sqlite3.TimeFormat
|
||||
tmRead sqlite3.TimeFormat
|
||||
}
|
||||
|
||||
var (
|
||||
@@ -302,10 +335,6 @@ var (
|
||||
_ driver.NamedValueChecker = &stmt{}
|
||||
)
|
||||
|
||||
func (s *stmt) Close() error {
|
||||
return s.Stmt.Close()
|
||||
}
|
||||
|
||||
func (s *stmt) NumInput() int {
|
||||
n := s.Stmt.BindCount()
|
||||
for i := 1; i <= n; i++ {
|
||||
@@ -332,15 +361,15 @@ func (s *stmt) ExecContext(ctx context.Context, args []driver.NamedValue) (drive
|
||||
return nil, err
|
||||
}
|
||||
|
||||
old := s.Conn.SetInterrupt(ctx)
|
||||
defer s.Conn.SetInterrupt(old)
|
||||
old := s.Stmt.Conn().SetInterrupt(ctx)
|
||||
defer s.Stmt.Conn().SetInterrupt(old)
|
||||
|
||||
err = s.Stmt.Exec()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return newResult(s.Conn), nil
|
||||
return newResult(s.Stmt.Conn()), nil
|
||||
}
|
||||
|
||||
func (s *stmt) QueryContext(ctx context.Context, args []driver.NamedValue) (driver.Rows, error) {
|
||||
@@ -348,7 +377,7 @@ func (s *stmt) QueryContext(ctx context.Context, args []driver.NamedValue) (driv
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &rows{ctx, s.Stmt, s.Conn}, nil
|
||||
return &rows{s, ctx}, nil
|
||||
}
|
||||
|
||||
func (s *stmt) setupBindings(args []driver.NamedValue) error {
|
||||
@@ -386,12 +415,12 @@ func (s *stmt) setupBindings(args []driver.NamedValue) error {
|
||||
err = s.Stmt.BindBlob(id, a)
|
||||
case sqlite3.ZeroBlob:
|
||||
err = s.Stmt.BindZeroBlob(id, int64(a))
|
||||
case interface{ Value() any }:
|
||||
err = s.Stmt.BindPointer(id, a.Value())
|
||||
case time.Time:
|
||||
err = s.Stmt.BindTime(id, a, sqlite3.TimeFormatDefault)
|
||||
case json.Marshaler:
|
||||
err = s.Stmt.BindJSON(id, a)
|
||||
err = s.Stmt.BindTime(id, a, s.tmWrite)
|
||||
case interface{ Pointer() any }:
|
||||
err = s.Stmt.BindPointer(id, a.Pointer())
|
||||
case interface{ JSON() any }:
|
||||
err = s.Stmt.BindJSON(id, a.JSON())
|
||||
case nil:
|
||||
err = s.Stmt.BindNull(id)
|
||||
default:
|
||||
@@ -408,8 +437,10 @@ func (s *stmt) setupBindings(args []driver.NamedValue) error {
|
||||
func (s *stmt) CheckNamedValue(arg *driver.NamedValue) error {
|
||||
switch arg.Value.(type) {
|
||||
case bool, int, int64, float64, string, []byte,
|
||||
sqlite3.ZeroBlob, interface{ Value() any },
|
||||
time.Time, json.Marshaler, nil:
|
||||
sqlite3.ZeroBlob, time.Time,
|
||||
interface{ Pointer() any },
|
||||
interface{ JSON() any },
|
||||
nil:
|
||||
return nil
|
||||
default:
|
||||
return driver.ErrSkip
|
||||
@@ -448,12 +479,12 @@ func (r resultRowsAffected) RowsAffected() (int64, error) {
|
||||
}
|
||||
|
||||
type rows struct {
|
||||
ctx context.Context
|
||||
Stmt *sqlite3.Stmt
|
||||
Conn *sqlite3.Conn
|
||||
*stmt
|
||||
ctx context.Context
|
||||
}
|
||||
|
||||
func (r *rows) Close() error {
|
||||
r.Stmt.ClearBindings()
|
||||
return r.Stmt.Reset()
|
||||
}
|
||||
|
||||
@@ -466,9 +497,19 @@ func (r *rows) Columns() []string {
|
||||
return columns
|
||||
}
|
||||
|
||||
func (r *rows) ColumnTypeDatabaseTypeName(index int) string {
|
||||
decltype := r.Stmt.ColumnDeclType(index)
|
||||
if len := len(decltype); len > 0 && decltype[len-1] == ')' {
|
||||
if i := strings.LastIndexByte(decltype, '('); i >= 0 {
|
||||
decltype = decltype[:i]
|
||||
}
|
||||
}
|
||||
return strings.ToUpper(strings.TrimSpace(decltype))
|
||||
}
|
||||
|
||||
func (r *rows) Next(dest []driver.Value) error {
|
||||
old := r.Conn.SetInterrupt(r.ctx)
|
||||
defer r.Conn.SetInterrupt(old)
|
||||
old := r.Stmt.Conn().SetInterrupt(r.ctx)
|
||||
defer r.Stmt.Conn().SetInterrupt(old)
|
||||
|
||||
if !r.Stmt.Step() {
|
||||
if err := r.Stmt.Err(); err != nil {
|
||||
@@ -478,6 +519,10 @@ func (r *rows) Next(dest []driver.Value) error {
|
||||
}
|
||||
|
||||
for i := range dest {
|
||||
if t, ok := r.decodeTime(i); ok {
|
||||
dest[i] = t
|
||||
continue
|
||||
}
|
||||
switch r.Stmt.ColumnType(i) {
|
||||
case sqlite3.INTEGER:
|
||||
dest[i] = r.Stmt.ColumnInt64(i)
|
||||
@@ -496,3 +541,22 @@ func (r *rows) Next(dest []driver.Value) error {
|
||||
|
||||
return r.Stmt.Err()
|
||||
}
|
||||
|
||||
func (s *stmt) decodeTime(i int) (_ time.Time, _ bool) {
|
||||
if s.tmRead == "" {
|
||||
return
|
||||
}
|
||||
switch s.Stmt.ColumnType(i) {
|
||||
case sqlite3.INTEGER, sqlite3.FLOAT, sqlite3.TEXT:
|
||||
// maybe
|
||||
default:
|
||||
return
|
||||
}
|
||||
switch strings.ToUpper(s.Stmt.ColumnDeclType(i)) {
|
||||
case "DATE", "TIME", "DATETIME", "TIMESTAMP":
|
||||
// maybe
|
||||
default:
|
||||
return
|
||||
}
|
||||
return s.Stmt.ColumnTime(i, s.tmRead), s.Stmt.Err() == nil
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/ncruces/go-sqlite3"
|
||||
_ "github.com/ncruces/go-sqlite3/embed"
|
||||
"github.com/ncruces/go-sqlite3/internal/util"
|
||||
)
|
||||
|
||||
@@ -113,13 +114,7 @@ func Test_Open_txLock(t *testing.T) {
|
||||
func Test_Open_txLock_invalid(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
db, err := sql.Open("sqlite3", "file::memory:?_txlock=xclusive")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
_, err = db.Conn(context.TODO())
|
||||
_, err := sql.Open("sqlite3", "file::memory:?_txlock=xclusive")
|
||||
if err == nil {
|
||||
t.Fatal("want error")
|
||||
}
|
||||
@@ -185,12 +180,6 @@ func Test_Prepare(t *testing.T) {
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
stmt, err := db.Prepare(`SELECT 1; -- HERE`)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
defer stmt.Close()
|
||||
|
||||
var serr *sqlite3.Error
|
||||
_, err = db.Prepare(`SELECT`)
|
||||
if err == nil {
|
||||
@@ -206,18 +195,14 @@ func Test_Prepare(t *testing.T) {
|
||||
t.Error("got message:", got)
|
||||
}
|
||||
|
||||
_, err = db.Prepare(`SELECT 1; `)
|
||||
if err.Error() != string(util.TailErr) {
|
||||
t.Error("want tailErr")
|
||||
}
|
||||
|
||||
_, err = db.Prepare(`SELECT 1; SELECT`)
|
||||
if err == nil {
|
||||
t.Error("want error")
|
||||
}
|
||||
if !errors.As(err, &serr) {
|
||||
t.Fatalf("got %T, want sqlite3.Error", err)
|
||||
}
|
||||
if rc := serr.Code(); rc != sqlite3.ERROR {
|
||||
t.Errorf("got %d, want sqlite3.ERROR", rc)
|
||||
}
|
||||
if got := err.Error(); got != `sqlite3: SQL logic error: incomplete input` {
|
||||
t.Error("got message:", got)
|
||||
if err.Error() != string(util.TailErr) {
|
||||
t.Error("want tailErr")
|
||||
}
|
||||
|
||||
_, err = db.Prepare(`SELECT 1; SELECT 2`)
|
||||
@@ -296,6 +281,7 @@ func Test_QueryRow_blob_null(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
want := [][]byte{nil, {0xca, 0xfe}, {0xba, 0xbe}, nil}
|
||||
for i := 0; rows.Next(); i++ {
|
||||
|
||||
@@ -18,7 +18,7 @@ func Example_json() {
|
||||
defer db.Close()
|
||||
|
||||
_, err = db.Exec(`
|
||||
CREATE TABLE orders (
|
||||
CREATE TABLE IF NOT EXISTS orders (
|
||||
cart_id INTEGER PRIMARY KEY,
|
||||
user_id INTEGER NOT NULL,
|
||||
cart TEXT
|
||||
|
||||
@@ -9,7 +9,7 @@ import (
|
||||
|
||||
// Savepoint establishes a new transaction savepoint.
|
||||
//
|
||||
// https://www.sqlite.org/lang_savepoint.html
|
||||
// https://sqlite.org/lang_savepoint.html
|
||||
func Savepoint(tx *sql.Tx) sqlite3.Savepoint {
|
||||
var ctx saveptCtx
|
||||
tx.ExecContext(&ctx, "")
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"log"
|
||||
|
||||
"github.com/ncruces/go-sqlite3/driver"
|
||||
_ "github.com/ncruces/go-sqlite3/embed"
|
||||
_ "github.com/ncruces/go-sqlite3/vfs/memdb"
|
||||
)
|
||||
|
||||
|
||||
@@ -49,16 +49,16 @@ func Fuzz_stringOrTime_1(f *testing.F) {
|
||||
// This checks that any [time.Time] can be recovered as a [time.Time],
|
||||
// with nanosecond accuracy, and preserving any timezone offset.
|
||||
func Fuzz_stringOrTime_2(f *testing.F) {
|
||||
f.Add(0, 0)
|
||||
f.Add(0, 1)
|
||||
f.Add(0, -1)
|
||||
f.Add(0, 999_999_999)
|
||||
f.Add(0, 1_000_000_000)
|
||||
f.Add(7956915742, 222_222_222) // twosday
|
||||
f.Add(639095955742, 222_222_222) // twosday, year 22222AD
|
||||
f.Add(-763421161058, 222_222_222) // twosday, year 22222BC
|
||||
f.Add(int64(0), int64(0))
|
||||
f.Add(int64(0), int64(1))
|
||||
f.Add(int64(0), int64(-1))
|
||||
f.Add(int64(0), int64(999_999_999))
|
||||
f.Add(int64(0), int64(1_000_000_000))
|
||||
f.Add(int64(7956915742), int64(222_222_222)) // twosday
|
||||
f.Add(int64(639095955742), int64(222_222_222)) // twosday, year 22222AD
|
||||
f.Add(int64(-763421161058), int64(222_222_222)) // twosday, year 22222BC
|
||||
|
||||
checkTime := func(t *testing.T, date time.Time) {
|
||||
checkTime := func(t testing.TB, date time.Time) {
|
||||
value := stringOrTime([]byte(date.Format(time.RFC3339Nano)))
|
||||
|
||||
switch v := value.(type) {
|
||||
@@ -80,7 +80,7 @@ func Fuzz_stringOrTime_2(f *testing.F) {
|
||||
}
|
||||
}
|
||||
|
||||
f.Fuzz(func(t *testing.T, sec, nsec int) {
|
||||
f.Fuzz(func(t *testing.T, sec, nsec int64) {
|
||||
// Reduce the search space.
|
||||
if 1e12 < sec || sec < -1e12 {
|
||||
// Dates before 29000BC and after 33000AD; I think we're safe.
|
||||
@@ -91,7 +91,7 @@ func Fuzz_stringOrTime_2(f *testing.F) {
|
||||
return
|
||||
}
|
||||
|
||||
unix := time.Unix(int64(sec), int64(nsec))
|
||||
unix := time.Unix(sec, nsec)
|
||||
checkTime(t, unix)
|
||||
checkTime(t, unix.UTC())
|
||||
checkTime(t, unix.In(time.FixedZone("", -8*3600)))
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
# Embeddable WASM build of SQLite
|
||||
|
||||
This folder includes an embeddable WASM build of SQLite 3.44.0 for use with
|
||||
This folder includes an embeddable WASM build of SQLite 3.44.2 for use with
|
||||
[`github.com/ncruces/go-sqlite3`](https://pkg.go.dev/github.com/ncruces/go-sqlite3).
|
||||
|
||||
The following optional features are compiled in:
|
||||
- [math functions](https://www.sqlite.org/lang_mathfunc.html)
|
||||
- [FTS3/4](https://www.sqlite.org/fts3.html)/[5](https://www.sqlite.org/fts5.html)
|
||||
- [JSON](https://www.sqlite.org/json1.html)
|
||||
- [R*Tree](https://www.sqlite.org/rtree.html)
|
||||
- [GeoPoly](https://www.sqlite.org/geopoly.html)
|
||||
- [soundex](https://www.sqlite.org/lang_corefunc.html#soundex)
|
||||
- [math functions](https://sqlite.org/lang_mathfunc.html)
|
||||
- [FTS3/4](https://sqlite.org/fts3.html)/[5](https://sqlite.org/fts5.html)
|
||||
- [JSON](https://sqlite.org/json1.html)
|
||||
- [R*Tree](https://sqlite.org/rtree.html)
|
||||
- [GeoPoly](https://sqlite.org/geopoly.html)
|
||||
- [soundex](https://sqlite.org/lang_corefunc.html#soundex)
|
||||
- [base64](https://github.com/sqlite/sqlite/blob/master/ext/misc/base64.c)
|
||||
- [decimal](https://github.com/sqlite/sqlite/blob/master/ext/misc/decimal.c)
|
||||
- [regexp](https://github.com/sqlite/sqlite/blob/master/ext/misc/regexp.c)
|
||||
|
||||
@@ -23,7 +23,7 @@ WASI_SDK="$ROOT/tools/wasi-sdk-20.0/bin"
|
||||
|
||||
trap 'rm -f sqlite3.tmp' EXIT
|
||||
"$BINARYEN/wasm-ctor-eval" -g -c _initialize sqlite3.wasm -o sqlite3.tmp
|
||||
"$BINARYEN/wasm-opt" -g --strip -c -O3 \
|
||||
"$BINARYEN/wasm-opt" -g --strip --strip-producers -c -O3 \
|
||||
sqlite3.tmp -o sqlite3.wasm \
|
||||
--enable-simd --enable-mutable-globals --enable-multivalue \
|
||||
--enable-bulk-memory --enable-reference-types \
|
||||
|
||||
@@ -1,80 +1,101 @@
|
||||
free
|
||||
malloc
|
||||
malloc_destructor
|
||||
sqlite3_errcode
|
||||
sqlite3_errstr
|
||||
sqlite3_errmsg
|
||||
sqlite3_error_offset
|
||||
sqlite3_open_v2
|
||||
sqlite3_close
|
||||
sqlite3_close_v2
|
||||
sqlite3_prepare_v3
|
||||
sqlite3_finalize
|
||||
sqlite3_reset
|
||||
sqlite3_step
|
||||
sqlite3_exec
|
||||
sqlite3_interrupt
|
||||
sqlite3_progress_handler_go
|
||||
sqlite3_clear_bindings
|
||||
sqlite3_aggregate_context
|
||||
sqlite3_anycollseq_init
|
||||
sqlite3_backup_finish
|
||||
sqlite3_backup_init
|
||||
sqlite3_backup_pagecount
|
||||
sqlite3_backup_remaining
|
||||
sqlite3_backup_step
|
||||
sqlite3_bind_blob64
|
||||
sqlite3_bind_double
|
||||
sqlite3_bind_int64
|
||||
sqlite3_bind_null
|
||||
sqlite3_bind_parameter_count
|
||||
sqlite3_bind_parameter_index
|
||||
sqlite3_bind_parameter_name
|
||||
sqlite3_bind_null
|
||||
sqlite3_bind_int64
|
||||
sqlite3_bind_double
|
||||
sqlite3_bind_text64
|
||||
sqlite3_bind_blob64
|
||||
sqlite3_bind_zeroblob64
|
||||
sqlite3_bind_pointer_go
|
||||
sqlite3_column_count
|
||||
sqlite3_column_name
|
||||
sqlite3_column_type
|
||||
sqlite3_column_int64
|
||||
sqlite3_column_double
|
||||
sqlite3_column_text
|
||||
sqlite3_bind_text64
|
||||
sqlite3_bind_value
|
||||
sqlite3_bind_zeroblob64
|
||||
sqlite3_blob_bytes
|
||||
sqlite3_blob_close
|
||||
sqlite3_blob_open
|
||||
sqlite3_blob_read
|
||||
sqlite3_blob_reopen
|
||||
sqlite3_blob_write
|
||||
sqlite3_changes64
|
||||
sqlite3_clear_bindings
|
||||
sqlite3_close
|
||||
sqlite3_close_v2
|
||||
sqlite3_column_blob
|
||||
sqlite3_column_bytes
|
||||
sqlite3_blob_open
|
||||
sqlite3_blob_close
|
||||
sqlite3_blob_reopen
|
||||
sqlite3_blob_bytes
|
||||
sqlite3_blob_read
|
||||
sqlite3_blob_write
|
||||
sqlite3_backup_init
|
||||
sqlite3_backup_step
|
||||
sqlite3_backup_finish
|
||||
sqlite3_backup_remaining
|
||||
sqlite3_backup_pagecount
|
||||
sqlite3_uri_parameter
|
||||
sqlite3_uri_key
|
||||
sqlite3_changes64
|
||||
sqlite3_last_insert_rowid
|
||||
sqlite3_get_autocommit
|
||||
sqlite3_anycollseq_init
|
||||
sqlite3_column_count
|
||||
sqlite3_column_decltype
|
||||
sqlite3_column_double
|
||||
sqlite3_column_int64
|
||||
sqlite3_column_name
|
||||
sqlite3_column_text
|
||||
sqlite3_column_type
|
||||
sqlite3_column_value
|
||||
sqlite3_create_aggregate_function_go
|
||||
sqlite3_create_collation_go
|
||||
sqlite3_create_function_go
|
||||
sqlite3_create_aggregate_function_go
|
||||
sqlite3_create_module_go
|
||||
sqlite3_create_window_function_go
|
||||
sqlite3_aggregate_context
|
||||
sqlite3_user_data
|
||||
sqlite3_set_auxdata_go
|
||||
sqlite3_declare_vtab
|
||||
sqlite3_errcode
|
||||
sqlite3_errmsg
|
||||
sqlite3_error_offset
|
||||
sqlite3_errstr
|
||||
sqlite3_exec
|
||||
sqlite3_finalize
|
||||
sqlite3_get_autocommit
|
||||
sqlite3_get_auxdata
|
||||
sqlite3_value_type
|
||||
sqlite3_value_int64
|
||||
sqlite3_value_double
|
||||
sqlite3_value_text
|
||||
sqlite3_value_blob
|
||||
sqlite3_value_bytes
|
||||
sqlite3_value_pointer_go
|
||||
sqlite3_result_null
|
||||
sqlite3_result_int64
|
||||
sqlite3_result_double
|
||||
sqlite3_result_text64
|
||||
sqlite3_interrupt
|
||||
sqlite3_last_insert_rowid
|
||||
sqlite3_open_v2
|
||||
sqlite3_overload_function
|
||||
sqlite3_prepare_v3
|
||||
sqlite3_progress_handler_go
|
||||
sqlite3_reset
|
||||
sqlite3_result_blob64
|
||||
sqlite3_result_zeroblob64
|
||||
sqlite3_result_pointer_go
|
||||
sqlite3_result_value
|
||||
sqlite3_result_double
|
||||
sqlite3_result_error
|
||||
sqlite3_result_error_code
|
||||
sqlite3_result_error_nomem
|
||||
sqlite3_result_error_toobig
|
||||
sqlite3_result_error_toobig
|
||||
sqlite3_result_int64
|
||||
sqlite3_result_null
|
||||
sqlite3_result_pointer_go
|
||||
sqlite3_result_text64
|
||||
sqlite3_result_value
|
||||
sqlite3_result_zeroblob64
|
||||
sqlite3_set_auxdata_go
|
||||
sqlite3_step
|
||||
sqlite3_stmt_busy
|
||||
sqlite3_stmt_readonly
|
||||
sqlite3_stmt_status
|
||||
sqlite3_uri_key
|
||||
sqlite3_uri_parameter
|
||||
sqlite3_user_data
|
||||
sqlite3_value_blob
|
||||
sqlite3_value_bytes
|
||||
sqlite3_value_double
|
||||
sqlite3_value_dup
|
||||
sqlite3_value_free
|
||||
sqlite3_value_int64
|
||||
sqlite3_value_nochange
|
||||
sqlite3_value_pointer_go
|
||||
sqlite3_value_text
|
||||
sqlite3_value_type
|
||||
sqlite3_vtab_collation
|
||||
sqlite3_vtab_config_go
|
||||
sqlite3_vtab_distinct
|
||||
sqlite3_vtab_in
|
||||
sqlite3_vtab_in_first
|
||||
sqlite3_vtab_in_next
|
||||
sqlite3_vtab_nochange
|
||||
sqlite3_vtab_on_conflict
|
||||
sqlite3_vtab_rhs_value
|
||||
Binary file not shown.
35
error.go
35
error.go
@@ -1,6 +1,7 @@
|
||||
package sqlite3
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
@@ -9,7 +10,7 @@ import (
|
||||
|
||||
// Error wraps an SQLite Error Code.
|
||||
//
|
||||
// https://www.sqlite.org/c3ref/errcode.html
|
||||
// https://sqlite.org/c3ref/errcode.html
|
||||
type Error struct {
|
||||
str string
|
||||
msg string
|
||||
@@ -19,14 +20,14 @@ type Error struct {
|
||||
|
||||
// Code returns the primary error code for this error.
|
||||
//
|
||||
// https://www.sqlite.org/rescode.html
|
||||
// https://sqlite.org/rescode.html
|
||||
func (e *Error) Code() ErrorCode {
|
||||
return ErrorCode(e.code)
|
||||
}
|
||||
|
||||
// ExtendedCode returns the extended error code for this error.
|
||||
//
|
||||
// https://www.sqlite.org/rescode.html
|
||||
// https://sqlite.org/rescode.html
|
||||
func (e *Error) ExtendedCode() ExtendedErrorCode {
|
||||
return ExtendedErrorCode(e.code)
|
||||
}
|
||||
@@ -43,8 +44,7 @@ func (e *Error) Error() string {
|
||||
}
|
||||
|
||||
if e.msg != "" {
|
||||
b.WriteByte(':')
|
||||
b.WriteByte(' ')
|
||||
b.WriteString(": ")
|
||||
b.WriteString(e.msg)
|
||||
}
|
||||
|
||||
@@ -135,3 +135,28 @@ func (e ExtendedErrorCode) Temporary() bool {
|
||||
func (e ExtendedErrorCode) Timeout() bool {
|
||||
return e == BUSY_TIMEOUT
|
||||
}
|
||||
|
||||
func errorCode(err error, def ErrorCode) (msg string, code uint32) {
|
||||
switch code := err.(type) {
|
||||
case ErrorCode:
|
||||
return "", uint32(code)
|
||||
case ExtendedErrorCode:
|
||||
return "", uint32(code)
|
||||
case *Error:
|
||||
return code.msg, uint32(code.code)
|
||||
case nil:
|
||||
return "", _OK
|
||||
}
|
||||
|
||||
var ecode ErrorCode
|
||||
var xcode xErrorCode
|
||||
switch {
|
||||
case errors.As(err, &xcode):
|
||||
code = uint32(xcode)
|
||||
case errors.As(err, &ecode):
|
||||
code = uint32(ecode)
|
||||
default:
|
||||
code = uint32(def)
|
||||
}
|
||||
return err.Error(), code
|
||||
}
|
||||
|
||||
@@ -135,8 +135,8 @@ func Test_ErrorCode_Error(t *testing.T) {
|
||||
// Test all error codes.
|
||||
for i := 0; i == int(ErrorCode(i)); i++ {
|
||||
want := "sqlite3: "
|
||||
r := db.call(db.api.errstr, uint64(i))
|
||||
want += util.ReadString(db.mod, uint32(r), _MAX_STRING)
|
||||
r := db.call("sqlite3_errstr", uint64(i))
|
||||
want += util.ReadString(db.mod, uint32(r), _MAX_NAME)
|
||||
|
||||
got := ErrorCode(i).Error()
|
||||
if got != want {
|
||||
@@ -157,8 +157,8 @@ func Test_ExtendedErrorCode_Error(t *testing.T) {
|
||||
// Test all extended error codes.
|
||||
for i := 0; i == int(ExtendedErrorCode(i)); i++ {
|
||||
want := "sqlite3: "
|
||||
r := db.call(db.api.errstr, uint64(i))
|
||||
want += util.ReadString(db.mod, uint32(r), _MAX_STRING)
|
||||
r := db.call("sqlite3_errstr", uint64(i))
|
||||
want += util.ReadString(db.mod, uint32(r), _MAX_NAME)
|
||||
|
||||
got := ExtendedErrorCode(i).Error()
|
||||
if got != want {
|
||||
|
||||
135
ext/array/array.go
Normal file
135
ext/array/array.go
Normal file
@@ -0,0 +1,135 @@
|
||||
// Package array provides the array table-valued SQL function.
|
||||
package array
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
|
||||
"github.com/ncruces/go-sqlite3"
|
||||
)
|
||||
|
||||
// Register registers the array single-argument, table-valued SQL function.
|
||||
// The argument must be an [sqlite3.Pointer] to a Go slice or array
|
||||
// of ints, floats, bools, strings or blobs.
|
||||
//
|
||||
// https://sqlite.org/carray.html
|
||||
func Register(db *sqlite3.Conn) {
|
||||
sqlite3.CreateModule[array](db, "array", nil,
|
||||
func(db *sqlite3.Conn, _, _, _ string, _ ...string) (array, error) {
|
||||
err := db.DeclareVtab(`CREATE TABLE x(value, array HIDDEN)`)
|
||||
return array{}, err
|
||||
})
|
||||
}
|
||||
|
||||
type array struct{}
|
||||
|
||||
func (array) BestIndex(idx *sqlite3.IndexInfo) error {
|
||||
for i, cst := range idx.Constraint {
|
||||
if cst.Column == 1 && cst.Op == sqlite3.INDEX_CONSTRAINT_EQ && cst.Usable {
|
||||
idx.ConstraintUsage[i] = sqlite3.IndexConstraintUsage{
|
||||
Omit: true,
|
||||
ArgvIndex: 1,
|
||||
}
|
||||
idx.EstimatedCost = 1
|
||||
idx.EstimatedRows = 100
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return sqlite3.CONSTRAINT
|
||||
}
|
||||
|
||||
func (array) Open() (sqlite3.VTabCursor, error) {
|
||||
return &cursor{}, nil
|
||||
}
|
||||
|
||||
type cursor struct {
|
||||
array reflect.Value
|
||||
rowID int
|
||||
}
|
||||
|
||||
func (c *cursor) EOF() bool {
|
||||
return c.rowID >= c.array.Len()
|
||||
}
|
||||
|
||||
func (c *cursor) Next() error {
|
||||
c.rowID++
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *cursor) RowID() (int64, error) {
|
||||
return int64(c.rowID), nil
|
||||
}
|
||||
|
||||
func (c *cursor) Column(ctx *sqlite3.Context, n int) error {
|
||||
if n != 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
v := c.array.Index(c.rowID)
|
||||
k := v.Kind()
|
||||
|
||||
if k == reflect.Interface {
|
||||
if v.IsNil() {
|
||||
ctx.ResultNull()
|
||||
return nil
|
||||
}
|
||||
v = v.Elem()
|
||||
k = v.Kind()
|
||||
}
|
||||
|
||||
switch {
|
||||
case v.CanInt():
|
||||
ctx.ResultInt64(v.Int())
|
||||
|
||||
case v.CanUint():
|
||||
i64 := int64(v.Uint())
|
||||
if i64 < 0 {
|
||||
return fmt.Errorf("array: integer element overflow:%.0w %d", sqlite3.MISMATCH, v.Uint())
|
||||
}
|
||||
ctx.ResultInt64(i64)
|
||||
|
||||
case v.CanFloat():
|
||||
ctx.ResultFloat(v.Float())
|
||||
|
||||
case k == reflect.Bool:
|
||||
ctx.ResultBool(v.Bool())
|
||||
|
||||
case k == reflect.String:
|
||||
ctx.ResultText(v.String())
|
||||
|
||||
case (k == reflect.Slice || k == reflect.Array && v.CanAddr()) &&
|
||||
v.Type().Elem().Kind() == reflect.Uint8:
|
||||
ctx.ResultBlob(v.Bytes())
|
||||
|
||||
default:
|
||||
return fmt.Errorf("array: unsupported element:%.0w %v", sqlite3.MISMATCH, v.Type())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *cursor) Filter(idxNum int, idxStr string, arg ...sqlite3.Value) error {
|
||||
array := reflect.ValueOf(arg[0].Pointer())
|
||||
array, err := indexable(array)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c.array = array
|
||||
c.rowID = 0
|
||||
return nil
|
||||
}
|
||||
|
||||
func indexable(v reflect.Value) (reflect.Value, error) {
|
||||
if v.Kind() == reflect.Slice {
|
||||
return v, nil
|
||||
}
|
||||
if v.Kind() == reflect.Array {
|
||||
return v, nil
|
||||
}
|
||||
if v.Kind() == reflect.Pointer {
|
||||
if v := v.Elem(); v.Kind() == reflect.Array {
|
||||
return v, nil
|
||||
}
|
||||
}
|
||||
return v, fmt.Errorf("array: unsupported argument:%.0w %v", sqlite3.MISMATCH, v.Type())
|
||||
}
|
||||
94
ext/array/array_test.go
Normal file
94
ext/array/array_test.go
Normal file
@@ -0,0 +1,94 @@
|
||||
package array_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"math"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/ncruces/go-sqlite3"
|
||||
"github.com/ncruces/go-sqlite3/driver"
|
||||
_ "github.com/ncruces/go-sqlite3/embed"
|
||||
"github.com/ncruces/go-sqlite3/ext/array"
|
||||
)
|
||||
|
||||
func Example() {
|
||||
db, err := driver.Open(":memory:", func(c *sqlite3.Conn) error {
|
||||
array.Register(c)
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
rows, err := db.Query(`
|
||||
SELECT name
|
||||
FROM pragma_function_list
|
||||
WHERE name like 'geopoly%' AND narg IN array(?)`,
|
||||
sqlite3.Pointer([]int{2, 3, 4}))
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
for rows.Next() {
|
||||
var name string
|
||||
err := rows.Scan(&name)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
fmt.Printf("%s\n", name)
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
// Unordered output:
|
||||
// geopoly_regular
|
||||
// geopoly_overlap
|
||||
// geopoly_contains_point
|
||||
// geopoly_within
|
||||
}
|
||||
|
||||
func Test_cursor_Column(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
db, err := driver.Open(":memory:", func(c *sqlite3.Conn) error {
|
||||
array.Register(c)
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
rows, err := db.Query(`
|
||||
SELECT rowid, value FROM array(?)`,
|
||||
sqlite3.Pointer(&[...]any{nil, true, 1, uint(2), math.Pi, "text", []byte{1, 2, 3}}))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
want := []string{"nil", "int64", "int64", "int64", "float64", "string", "[]uint8"}
|
||||
|
||||
for rows.Next() {
|
||||
var id, val any
|
||||
err := rows.Scan(&id, &val)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if want := want[0]; val == nil {
|
||||
if want != "nil" {
|
||||
t.Errorf("got nil, want %s", want)
|
||||
}
|
||||
} else if got := reflect.TypeOf(val).String(); got != want {
|
||||
t.Errorf("got %s, want %s", got, want)
|
||||
}
|
||||
want = want[1:]
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
@@ -5,9 +5,18 @@ import (
|
||||
"errors"
|
||||
|
||||
"github.com/ncruces/go-sqlite3"
|
||||
"github.com/ncruces/go-sqlite3/internal/util"
|
||||
)
|
||||
|
||||
// Register registers the blob_open SQL function.
|
||||
// Register registers the blob_open SQL function:
|
||||
//
|
||||
// blob_open(schema, table, column, rowid, flags, callback, args...)
|
||||
//
|
||||
// The callback must be an [sqlite3.Pointer] to an [OpenCallback].
|
||||
// Any optional args will be passed to the callback,
|
||||
// along with the [sqlite3.Blob] handle.
|
||||
//
|
||||
// https://sqlite.org/c3ref/blob.html
|
||||
func Register(db *sqlite3.Conn) {
|
||||
db.CreateFunction("blob_open", -1,
|
||||
sqlite3.DETERMINISTIC|sqlite3.DIRECTONLY, openBlob)
|
||||
@@ -15,7 +24,7 @@ func Register(db *sqlite3.Conn) {
|
||||
|
||||
func openBlob(ctx sqlite3.Context, arg ...sqlite3.Value) {
|
||||
if len(arg) < 6 {
|
||||
ctx.ResultError(errors.New("wrong number of arguments to function blob_open()"))
|
||||
ctx.ResultError(util.ErrorString("blob_open: wrong number of arguments"))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -26,7 +35,7 @@ func openBlob(ctx sqlite3.Context, arg ...sqlite3.Value) {
|
||||
if ok {
|
||||
err = blob.Reopen(row)
|
||||
if errors.Is(err, sqlite3.MISUSE) {
|
||||
// Blob was closed (db, table or column changed).
|
||||
// Blob was closed (db, table, column or write changed).
|
||||
ok = false
|
||||
}
|
||||
}
|
||||
@@ -50,10 +59,12 @@ func openBlob(ctx sqlite3.Context, arg ...sqlite3.Value) {
|
||||
return
|
||||
}
|
||||
|
||||
// This ensures the blob is closed if db, table or column change.
|
||||
ctx.SetAuxData(0, blob)
|
||||
ctx.SetAuxData(1, blob)
|
||||
ctx.SetAuxData(2, blob)
|
||||
// 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
|
||||
ctx.SetAuxData(4, blob) // write
|
||||
}
|
||||
|
||||
// OpenCallback is the type for the blob_open callback.
|
||||
type OpenCallback func(*sqlite3.Blob, ...sqlite3.Value) error
|
||||
|
||||
@@ -4,10 +4,13 @@ import (
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/ncruces/go-sqlite3"
|
||||
"github.com/ncruces/go-sqlite3/driver"
|
||||
_ "github.com/ncruces/go-sqlite3/embed"
|
||||
"github.com/ncruces/go-sqlite3/ext/array"
|
||||
"github.com/ncruces/go-sqlite3/ext/blob"
|
||||
_ "github.com/ncruces/go-sqlite3/vfs/memdb"
|
||||
)
|
||||
@@ -59,3 +62,67 @@ func Example() {
|
||||
// Output:
|
||||
// Hello BLOB!
|
||||
}
|
||||
|
||||
func TestRegister(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
db, err := sqlite3.Open(":memory:")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
blob.Register(db)
|
||||
array.Register(db)
|
||||
|
||||
err = db.Exec(`SELECT blob_open()`)
|
||||
if err == nil {
|
||||
t.Fatal("want error")
|
||||
} else {
|
||||
t.Log(err)
|
||||
}
|
||||
|
||||
err = db.Exec(`
|
||||
CREATE TABLE IF NOT EXISTS test1 (col);
|
||||
CREATE TABLE IF NOT EXISTS test2 (col);
|
||||
INSERT INTO test1 VALUES (x'cafe');
|
||||
INSERT INTO test2 VALUES (x'babe');
|
||||
`)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
stmt, _, err := db.Prepare(`SELECT blob_open('main', value, 'col', 1, false, ?) FROM array(?)`)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer stmt.Close()
|
||||
|
||||
var got []string
|
||||
err = stmt.BindPointer(1, blob.OpenCallback(func(b *sqlite3.Blob, _ ...sqlite3.Value) error {
|
||||
d, err := io.ReadAll(b)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
got = append(got, string(d))
|
||||
return nil
|
||||
}))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = stmt.BindPointer(2, []string{"test1", "test2"})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = stmt.Exec()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
want := []string{"\xca\xfe", "\xba\xbe"}
|
||||
if !reflect.DeepEqual(got, want) {
|
||||
t.Errorf("got %v, want %v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
211
ext/csv/csv.go
Normal file
211
ext/csv/csv.go
Normal file
@@ -0,0 +1,211 @@
|
||||
// Package csv provides a CSV virtual table.
|
||||
//
|
||||
// The CSV virtual table reads RFC 4180 formatted comma-separated values,
|
||||
// and returns that content as if it were rows and columns of an SQL table.
|
||||
//
|
||||
// https://sqlite.org/csv.html
|
||||
package csv
|
||||
|
||||
import (
|
||||
"encoding/csv"
|
||||
"fmt"
|
||||
"io"
|
||||
"math"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/ncruces/go-sqlite3"
|
||||
)
|
||||
|
||||
// Register registers the CSV virtual table.
|
||||
// If a filename is specified, `os.Open` is used to read it from disk.
|
||||
func Register(db *sqlite3.Conn) {
|
||||
RegisterOpen(db, func(name string) (io.ReaderAt, error) {
|
||||
return os.Open(name)
|
||||
})
|
||||
}
|
||||
|
||||
// RegisterOpen registers the CSV virtual table.
|
||||
// If a filename is specified, open is used to open the file.
|
||||
func RegisterOpen(db *sqlite3.Conn, open func(name string) (io.ReaderAt, error)) {
|
||||
declare := func(db *sqlite3.Conn, _, _, _ string, arg ...string) (_ *table, err error) {
|
||||
var (
|
||||
filename string
|
||||
data string
|
||||
schema string
|
||||
header bool
|
||||
columns int = -1
|
||||
comma rune = ','
|
||||
|
||||
done = map[string]struct{}{}
|
||||
)
|
||||
|
||||
for _, arg := range arg {
|
||||
key, val := getParam(arg)
|
||||
if _, ok := done[key]; ok {
|
||||
return nil, fmt.Errorf("csv: more than one %q parameter", key)
|
||||
}
|
||||
switch key {
|
||||
case "filename":
|
||||
filename = unquoteParam(val)
|
||||
case "data":
|
||||
data = unquoteParam(val)
|
||||
case "schema":
|
||||
schema = unquoteParam(val)
|
||||
case "header":
|
||||
header, err = boolParam(key, val)
|
||||
case "columns":
|
||||
columns, err = uintParam(key, val)
|
||||
case "comma":
|
||||
comma, err = runeParam(key, val)
|
||||
default:
|
||||
return nil, fmt.Errorf("csv: unknown %q parameter", key)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
done[key] = struct{}{}
|
||||
}
|
||||
|
||||
if (filename == "") == (data == "") {
|
||||
return nil, fmt.Errorf(`csv: must specify either "filename" or "data" but not both`)
|
||||
}
|
||||
|
||||
var r io.ReaderAt
|
||||
if filename != "" {
|
||||
r, err = open(filename)
|
||||
} else {
|
||||
r = strings.NewReader(data)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
table := &table{
|
||||
r: r,
|
||||
comma: comma,
|
||||
header: header,
|
||||
bom: -1,
|
||||
}
|
||||
defer func() {
|
||||
if err != nil {
|
||||
table.Close()
|
||||
}
|
||||
}()
|
||||
|
||||
if schema == "" {
|
||||
var row []string
|
||||
if header || columns < 0 {
|
||||
row, err = table.newReader().Read()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
schema = getSchema(header, columns, row)
|
||||
}
|
||||
|
||||
err = db.DeclareVtab(schema)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = db.VtabConfig(sqlite3.VTAB_DIRECTONLY)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return table, nil
|
||||
}
|
||||
|
||||
sqlite3.CreateModule(db, "csv", declare, declare)
|
||||
}
|
||||
|
||||
type table struct {
|
||||
r io.ReaderAt
|
||||
comma rune
|
||||
header bool
|
||||
bom int8
|
||||
}
|
||||
|
||||
func (t *table) Close() error {
|
||||
if c, ok := t.r.(io.Closer); ok {
|
||||
err := c.Close()
|
||||
t.r = nil
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *table) BestIndex(idx *sqlite3.IndexInfo) error {
|
||||
idx.EstimatedCost = 1e6
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *table) Open() (sqlite3.VTabCursor, error) {
|
||||
return &cursor{table: t}, nil
|
||||
}
|
||||
|
||||
func (t *table) Rename(new string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *table) Integrity(schema, table string, flags int) (err error) {
|
||||
if flags&1 == 0 {
|
||||
_, err = t.newReader().ReadAll()
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (t *table) newReader() *csv.Reader {
|
||||
if t.bom < 0 {
|
||||
var bom [3]byte
|
||||
t.r.ReadAt(bom[:], 0)
|
||||
if string(bom[:]) == "\xEF\xBB\xBF" {
|
||||
t.bom = 3
|
||||
} else {
|
||||
t.bom = 0
|
||||
}
|
||||
}
|
||||
csv := csv.NewReader(io.NewSectionReader(t.r, int64(t.bom), math.MaxInt64))
|
||||
csv.ReuseRecord = true
|
||||
csv.Comma = t.comma
|
||||
return csv
|
||||
}
|
||||
|
||||
type cursor struct {
|
||||
table *table
|
||||
csv *csv.Reader
|
||||
row []string
|
||||
rowID int64
|
||||
}
|
||||
|
||||
func (c *cursor) Filter(idxNum int, idxStr string, arg ...sqlite3.Value) error {
|
||||
c.csv = c.table.newReader()
|
||||
if c.table.header {
|
||||
c.Next() // skip header
|
||||
}
|
||||
c.rowID = 0
|
||||
return c.Next()
|
||||
}
|
||||
|
||||
func (c *cursor) Next() (err error) {
|
||||
c.rowID++
|
||||
c.row, err = c.csv.Read()
|
||||
if err != io.EOF {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *cursor) EOF() bool {
|
||||
return c.row == nil
|
||||
}
|
||||
|
||||
func (c *cursor) RowID() (int64, error) {
|
||||
return c.rowID, nil
|
||||
}
|
||||
|
||||
func (c *cursor) Column(ctx *sqlite3.Context, col int) error {
|
||||
if col < len(c.row) {
|
||||
ctx.ResultText(c.row[col])
|
||||
}
|
||||
return nil
|
||||
}
|
||||
155
ext/csv/csv_test.go
Normal file
155
ext/csv/csv_test.go
Normal file
@@ -0,0 +1,155 @@
|
||||
package csv_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"testing"
|
||||
|
||||
"github.com/ncruces/go-sqlite3"
|
||||
_ "github.com/ncruces/go-sqlite3/embed"
|
||||
"github.com/ncruces/go-sqlite3/ext/csv"
|
||||
)
|
||||
|
||||
func Example() {
|
||||
db, err := sqlite3.Open(":memory:")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
csv.Register(db)
|
||||
|
||||
err = db.Exec(`
|
||||
CREATE VIRTUAL TABLE IF NOT EXISTS eurofxref USING csv(
|
||||
filename = 'testdata/eurofxref.csv',
|
||||
header = YES,
|
||||
columns = 42,
|
||||
)`)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
stmt, _, err := db.Prepare(`SELECT USD FROM eurofxref WHERE Date = '2022-02-22'`)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer stmt.Close()
|
||||
|
||||
if stmt.Step() {
|
||||
fmt.Printf("On Twosday, 1€ = $%g", stmt.ColumnFloat(0))
|
||||
}
|
||||
if err := stmt.Reset(); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
err = db.Exec(`DROP TABLE eurofxref`)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
// Output:
|
||||
// On Twosday, 1€ = $1.1342
|
||||
}
|
||||
|
||||
func TestRegister(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
db, err := sqlite3.Open(":memory:")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
csv.Register(db)
|
||||
|
||||
const data = "\xEF\xBB\xBF" + `
|
||||
"Rob" "Pike" rob
|
||||
"Ken" Thompson ken
|
||||
Robert "Griesemer" "gri"`
|
||||
err = db.Exec(`
|
||||
CREATE VIRTUAL TABLE temp.users USING csv(
|
||||
data = ` + sqlite3.Quote(data) + `,
|
||||
schema = 'CREATE TABLE x(first_name, last_name, username)',
|
||||
comma = '\t'
|
||||
)`)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
stmt, _, err := db.Prepare(`SELECT * FROM temp.users WHERE rowid = 1 ORDER BY username`)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer stmt.Close()
|
||||
|
||||
if !stmt.Step() {
|
||||
t.Fatal("no rows")
|
||||
}
|
||||
if got := stmt.ColumnText(1); got != "Pike" {
|
||||
t.Errorf("got %q want Pike", got)
|
||||
}
|
||||
if stmt.Step() {
|
||||
t.Fatal("more rows")
|
||||
}
|
||||
|
||||
err = db.Exec(`ALTER TABLE temp.users RENAME TO csv`)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = db.Exec(`PRAGMA integrity_check`)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = db.Exec(`DROP TABLE temp.csv`)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRegister_errors(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
db, err := sqlite3.Open(":memory:")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
csv.Register(db)
|
||||
|
||||
err = db.Exec(`CREATE VIRTUAL TABLE temp.users USING csv()`)
|
||||
if err == nil {
|
||||
t.Fatal("want error")
|
||||
} else {
|
||||
t.Log(err)
|
||||
}
|
||||
|
||||
err = db.Exec(`CREATE VIRTUAL TABLE temp.users USING csv(data='abc', data='abc')`)
|
||||
if err == nil {
|
||||
t.Fatal("want error")
|
||||
} else {
|
||||
t.Log(err)
|
||||
}
|
||||
|
||||
err = db.Exec(`CREATE VIRTUAL TABLE temp.users USING csv(data='abc', xpto='abc')`)
|
||||
if err == nil {
|
||||
t.Fatal("want error")
|
||||
} else {
|
||||
t.Log(err)
|
||||
}
|
||||
|
||||
err = db.Exec(`CREATE VIRTUAL TABLE temp.users USING csv(data='abc', comma='"')`)
|
||||
if err == nil {
|
||||
t.Fatal("want error")
|
||||
} else {
|
||||
t.Log(err)
|
||||
}
|
||||
|
||||
err = db.Exec(`CREATE VIRTUAL TABLE temp.users USING csv(data='abc', header=tru)`)
|
||||
if err == nil {
|
||||
t.Fatal("want error")
|
||||
} else {
|
||||
t.Log(err)
|
||||
}
|
||||
}
|
||||
65
ext/csv/params.go
Normal file
65
ext/csv/params.go
Normal file
@@ -0,0 +1,65 @@
|
||||
package csv
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func getParam(arg string) (key, val string) {
|
||||
key, val, _ = strings.Cut(arg, "=")
|
||||
key = strings.TrimSpace(key)
|
||||
val = strings.TrimSpace(val)
|
||||
return
|
||||
}
|
||||
|
||||
func uintParam(key, val string) (int, error) {
|
||||
i, err := strconv.ParseUint(val, 10, 15)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("csv: invalid %q parameter: %s", key, val)
|
||||
}
|
||||
return int(i), nil
|
||||
}
|
||||
|
||||
func boolParam(key, val string) (bool, error) {
|
||||
if val == "" || val == "1" ||
|
||||
strings.EqualFold(val, "true") ||
|
||||
strings.EqualFold(val, "yes") ||
|
||||
strings.EqualFold(val, "on") {
|
||||
return true, nil
|
||||
}
|
||||
if val == "0" ||
|
||||
strings.EqualFold(val, "false") ||
|
||||
strings.EqualFold(val, "no") ||
|
||||
strings.EqualFold(val, "off") {
|
||||
return false, nil
|
||||
}
|
||||
return false, fmt.Errorf("csv: invalid %q parameter: %s", key, val)
|
||||
}
|
||||
|
||||
func runeParam(key, val string) (rune, error) {
|
||||
r, _, tail, err := strconv.UnquoteChar(unquoteParam(val), 0)
|
||||
if tail != "" || err != nil {
|
||||
return 0, fmt.Errorf("csv: invalid %q parameter: %s", key, val)
|
||||
}
|
||||
return r, nil
|
||||
}
|
||||
|
||||
func unquoteParam(val string) string {
|
||||
if len(val) < 2 {
|
||||
return val
|
||||
}
|
||||
if val[0] != val[len(val)-1] {
|
||||
return val
|
||||
}
|
||||
var old, new string
|
||||
switch val[0] {
|
||||
default:
|
||||
return val
|
||||
case '"':
|
||||
old, new = `""`, `"`
|
||||
case '\'':
|
||||
old, new = `''`, `'`
|
||||
}
|
||||
return strings.ReplaceAll(val[1:len(val)-1], old, new)
|
||||
}
|
||||
104
ext/csv/params_test.go
Normal file
104
ext/csv/params_test.go
Normal file
@@ -0,0 +1,104 @@
|
||||
package csv
|
||||
|
||||
import "testing"
|
||||
|
||||
func Test_uintParam(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tests := []struct {
|
||||
arg string
|
||||
key string
|
||||
val int
|
||||
err bool
|
||||
}{
|
||||
{"columns 1", "columns 1", 0, true},
|
||||
{"columns = 1", "columns", 1, false},
|
||||
{"columns\t= 2", "columns", 2, false},
|
||||
{" columns = 3", "columns", 3, false},
|
||||
{" columns = -1", "columns", 0, true},
|
||||
{" columns = 32768", "columns", 0, true},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.arg, func(t *testing.T) {
|
||||
key, val := getParam(tt.arg)
|
||||
if key != tt.key {
|
||||
t.Errorf("getParam() %v, want err %v", key, tt.key)
|
||||
}
|
||||
got, err := uintParam(key, val)
|
||||
if (err != nil) != tt.err {
|
||||
t.Fatalf("uintParam() error = %v, want err %v", err, tt.err)
|
||||
}
|
||||
if got != tt.val {
|
||||
t.Errorf("uintParam() = %v, want %v", got, tt.val)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_boolParam(t *testing.T) {
|
||||
tests := []struct {
|
||||
arg string
|
||||
key string
|
||||
val bool
|
||||
err bool
|
||||
}{
|
||||
{"header", "header", true, false},
|
||||
{"header\t= 1", "header", true, false},
|
||||
{" header = 0", "header", false, false},
|
||||
{" header = TrUe", "header", true, false},
|
||||
{" header = FaLsE", "header", false, false},
|
||||
{" header = Yes", "header", true, false},
|
||||
{" header = nO", "header", false, false},
|
||||
{" header = On", "header", true, false},
|
||||
{" header = Off", "header", false, false},
|
||||
{" header = T", "header", false, true},
|
||||
{" header = f", "header", false, true},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.arg, func(t *testing.T) {
|
||||
key, val := getParam(tt.arg)
|
||||
if key != tt.key {
|
||||
t.Errorf("getParam() %v, want err %v", key, tt.key)
|
||||
}
|
||||
got, err := boolParam(key, val)
|
||||
if (err != nil) != tt.err {
|
||||
t.Fatalf("boolParam() error = %v, want err %v", err, tt.err)
|
||||
}
|
||||
if got != tt.val {
|
||||
t.Errorf("boolParam() = %v, want %v", got, tt.val)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_runeParam(t *testing.T) {
|
||||
tests := []struct {
|
||||
arg string
|
||||
key string
|
||||
val rune
|
||||
err bool
|
||||
}{
|
||||
{"comma", "comma", 0, true},
|
||||
{"comma\t= ,", "comma", ',', false},
|
||||
{" comma = ;", "comma", ';', false},
|
||||
{" comma = ;;", "comma", 0, true},
|
||||
{` comma = '\t`, "comma", 0, true},
|
||||
{` comma = '\t'`, "comma", '\t', false},
|
||||
{` comma = "\t"`, "comma", '\t', false},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.arg, func(t *testing.T) {
|
||||
key, val := getParam(tt.arg)
|
||||
if key != tt.key {
|
||||
t.Errorf("getParam() %v, want err %v", key, tt.key)
|
||||
}
|
||||
got, err := runeParam(key, val)
|
||||
if (err != nil) != tt.err {
|
||||
t.Fatalf("runeParam() error = %v, want err %v", err, tt.err)
|
||||
}
|
||||
if got != tt.val {
|
||||
t.Errorf("runeParam() = %v, want %v", got, tt.val)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
39
ext/csv/schema.go
Normal file
39
ext/csv/schema.go
Normal file
@@ -0,0 +1,39 @@
|
||||
package csv
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/ncruces/go-sqlite3"
|
||||
)
|
||||
|
||||
func getSchema(header bool, columns int, row []string) string {
|
||||
var sep string
|
||||
var str strings.Builder
|
||||
str.WriteString("CREATE TABLE x(")
|
||||
|
||||
if 0 <= columns && columns < len(row) {
|
||||
row = row[:columns]
|
||||
}
|
||||
for i, f := range row {
|
||||
str.WriteString(sep)
|
||||
if header && f != "" {
|
||||
str.WriteString(sqlite3.QuoteIdentifier(f))
|
||||
} else {
|
||||
str.WriteString("c")
|
||||
str.WriteString(strconv.Itoa(i + 1))
|
||||
}
|
||||
str.WriteString(" TEXT")
|
||||
sep = ","
|
||||
}
|
||||
for i := len(row); i < columns; i++ {
|
||||
str.WriteString(sep)
|
||||
str.WriteString("c")
|
||||
str.WriteString(strconv.Itoa(i + 1))
|
||||
str.WriteString(" TEXT")
|
||||
sep = ","
|
||||
}
|
||||
str.WriteByte(')')
|
||||
|
||||
return str.String()
|
||||
}
|
||||
28
ext/csv/schema_test.go
Normal file
28
ext/csv/schema_test.go
Normal file
@@ -0,0 +1,28 @@
|
||||
package csv
|
||||
|
||||
import "testing"
|
||||
|
||||
func Test_getSchema(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tests := []struct {
|
||||
header bool
|
||||
columns int
|
||||
row []string
|
||||
want string
|
||||
}{
|
||||
{true, 2, nil, `CREATE TABLE x(c1 TEXT,c2 TEXT)`},
|
||||
{false, 2, nil, `CREATE TABLE x(c1 TEXT,c2 TEXT)`},
|
||||
{false, -1, []string{"abc", ""}, `CREATE TABLE x(c1 TEXT,c2 TEXT)`},
|
||||
{true, 3, []string{"abc", ""}, `CREATE TABLE x("abc" TEXT,c2 TEXT,c3 TEXT)`},
|
||||
{true, -1, []string{"abc", "def"}, `CREATE TABLE x("abc" TEXT,"def" TEXT)`},
|
||||
{true, 1, []string{"abc", "def"}, `CREATE TABLE x("abc" TEXT)`},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.want, func(t *testing.T) {
|
||||
if got := getSchema(tt.header, tt.columns, tt.row); got != tt.want {
|
||||
t.Errorf("getSchema() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
258
ext/csv/testdata/eurofxref.csv
vendored
Normal file
258
ext/csv/testdata/eurofxref.csv
vendored
Normal file
@@ -0,0 +1,258 @@
|
||||
Date,USD,JPY,BGN,CYP,CZK,DKK,EEK,GBP,HUF,LTL,LVL,MTL,PLN,ROL,RON,SEK,SIT,SKK,CHF,ISK,NOK,HRK,RUB,TRL,TRY,AUD,BRL,CAD,CNY,HKD,IDR,ILS,INR,KRW,MXN,MYR,NZD,PHP,SGD,THB,ZAR,
|
||||
2022-12-30,1.0666,140.66,1.9558,N/A,24.116,7.4365,N/A,0.88693,400.87,N/A,N/A,N/A,4.6808,N/A,4.9495,11.1218,N/A,N/A,0.9847,151.5,10.5138,7.5365,N/A,N/A,19.9649,1.5693,5.6386,1.444,7.3582,8.3163,16519.82,3.7554,88.171,1344.09,20.856,4.6984,1.6798,59.32,1.43,36.835,18.0986,
|
||||
2022-12-29,1.0649,142.24,1.9558,N/A,24.191,7.4365,N/A,0.88549,399.6,N/A,N/A,N/A,4.6855,N/A,4.9493,11.158,N/A,N/A,0.984,152.5,10.55,7.5365,N/A,N/A,19.934,1.5859,5.5351,1.4475,7.4151,8.2994,16680.38,3.7575,88.2295,1350.18,20.651,4.7106,1.6887,59.367,1.436,36.877,18.1967,
|
||||
2022-12-28,1.064,142.21,1.9558,N/A,24.252,7.4365,N/A,0.88058,403.3,N/A,N/A,N/A,4.7008,N/A,4.946,11.1038,N/A,N/A,0.9863,151.9,10.4495,7.5365,N/A,N/A,19.9144,1.566,5.6109,1.4361,7.4224,8.2931,16765.93,3.7526,88.0943,1348.59,20.6856,4.7055,1.6772,59.613,1.4323,36.953,18.289,
|
||||
2022-12-27,1.0624,141.68,1.9558,N/A,24.26,7.4366,N/A,0.88333,401.65,N/A,N/A,N/A,4.6683,N/A,4.927,11.1285,N/A,N/A,0.9885,152.3,10.4895,7.5375,N/A,N/A,19.8799,1.577,5.6035,1.4384,7.3994,8.2874,16620.58,3.7278,88.0808,1349.85,20.5515,4.699,1.6916,59.356,1.43,36.775,18.3181,
|
||||
2022-12-23,1.0622,140.86,1.9558,N/A,24.247,7.4364,N/A,0.8803,400.68,N/A,N/A,N/A,4.6423,N/A,4.9056,11.1045,N/A,N/A,0.9867,152.3,10.4448,7.537,N/A,N/A,19.843,1.5857,5.4834,1.4433,7.4198,8.2878,16569.18,3.704,87.958,1359.5,20.7115,4.7002,1.6887,58.623,1.4337,36.842,18.1048,
|
||||
2022-12-22,1.0633,140.42,1.9558,N/A,24.215,7.4367,N/A,0.88243,402.13,N/A,N/A,N/A,4.6443,N/A,4.8993,11.05,N/A,N/A,0.9852,153.3,10.4123,7.538,N/A,N/A,19.8553,1.5804,5.5386,1.4484,7.4229,8.2883,16525.91,3.6942,88.0365,1361.75,20.8485,4.7051,1.6918,58.705,1.4356,36.849,18.2238,
|
||||
2022-12-21,1.0636,140.29,1.9558,N/A,24.218,7.438,N/A,0.87651,402.93,N/A,N/A,N/A,4.6665,N/A,4.8937,11.0623,N/A,N/A,0.9836,152.1,10.4309,7.5419,N/A,N/A,19.8541,1.5859,5.4913,1.4475,7.4219,8.2902,16573.77,3.6989,88.109,1367.63,20.9919,4.7197,1.685,58.556,1.4366,36.907,18.3529,
|
||||
2022-12-20,1.0599,140.58,1.9558,N/A,24.181,7.4388,N/A,0.8753,403.88,N/A,N/A,N/A,4.6757,N/A,4.9125,11.0615,N/A,N/A,0.9854,151.5,10.5098,7.5471,N/A,N/A,19.7744,1.5972,5.6234,1.4451,7.39,8.2488,16537.09,3.6759,87.6649,1363.73,20.9355,4.6991,1.6816,58.549,1.4347,36.853,18.4239,
|
||||
2022-12-19,1.0598,144.65,1.9558,N/A,24.233,7.4382,N/A,0.87118,403.18,N/A,N/A,N/A,4.6853,N/A,4.9107,11.0063,N/A,N/A,0.9884,151.9,10.5025,7.5395,N/A,N/A,19.7676,1.5794,5.6327,1.4472,7.3901,8.2428,16506.72,3.6551,87.5321,1377.17,20.9743,4.6912,1.6632,58.649,1.4378,36.923,18.3074,
|
||||
2022-12-16,1.0619,145.53,1.9558,N/A,24.262,7.4379,N/A,0.87233,407.1,N/A,N/A,N/A,4.6925,N/A,4.9213,11.0153,N/A,N/A,0.9879,150.1,10.4833,7.5385,N/A,N/A,19.8039,1.5866,5.6233,1.4506,7.4037,8.2632,16575.47,3.6689,87.824,1389.7,21.0634,4.6984,1.6687,58.967,1.4413,37.145,18.6708,
|
||||
2022-12-15,1.0621,145.07,1.9558,N/A,24.27,7.4387,N/A,0.86194,406.4,N/A,N/A,N/A,4.689,N/A,4.922,10.898,N/A,N/A,0.9862,150.9,10.4013,7.5395,N/A,N/A,19.806,1.5695,5.6247,1.4443,7.4007,8.2551,16591.4,3.6388,87.9355,1393.97,20.9431,4.6918,1.6628,59.297,1.4406,37.12,18.3599,
|
||||
2022-12-14,1.0649,143.68,1.9558,N/A,24.276,7.4392,N/A,0.86118,406.63,N/A,N/A,N/A,4.681,N/A,4.9248,10.8638,N/A,N/A,0.9865,150.9,10.362,7.538,N/A,N/A,19.8579,1.551,5.6842,1.4441,7.4009,8.2751,16599.51,3.6327,87.8435,1379.99,20.8635,4.6765,1.6508,59.326,1.4349,36.851,18.2563,
|
||||
2022-12-13,1.0545,144.85,1.9558,N/A,24.287,7.4391,N/A,0.85753,409.65,N/A,N/A,N/A,4.6938,N/A,4.9298,10.8965,N/A,N/A,0.9869,151.1,10.4679,7.5495,N/A,N/A,19.6649,1.5553,5.5784,1.4341,7.3637,8.2033,16521.81,3.6266,87.2965,1378.75,20.9435,4.6704,1.6464,58.852,1.4288,36.707,18.6855,
|
||||
2022-12-12,1.0562,144.86,1.9558,N/A,24.307,7.4379,N/A,0.86006,416.78,N/A,N/A,N/A,4.6923,N/A,4.9318,10.9075,N/A,N/A,0.9855,150.7,10.5548,7.554,N/A,N/A,19.6913,1.5625,5.556,1.4428,7.367,8.2103,16524.63,3.6232,87.253,1377.93,20.9047,4.6652,1.6523,58.788,1.4284,36.708,18.4697,
|
||||
2022-12-09,1.0559,143.3,1.9558,N/A,24.293,7.4379,N/A,0.8595,417.53,N/A,N/A,N/A,4.6869,N/A,4.9224,10.9188,N/A,N/A,0.9856,149.5,10.5345,7.555,N/A,N/A,19.6872,1.5553,5.5457,1.438,7.3475,8.2169,16453.46,3.6128,86.9535,1373.94,20.849,4.6512,1.6482,58.47,1.426,36.656,18.2358,
|
||||
2022-12-08,1.0519,143.75,1.9558,N/A,24.324,7.4382,N/A,0.86258,417.66,N/A,N/A,N/A,4.6853,N/A,4.9131,10.906,N/A,N/A,0.9889,149.5,10.488,7.5553,N/A,N/A,19.6114,1.559,5.488,1.4307,7.3324,8.1889,16423.92,3.6206,86.6755,1387.06,20.6989,4.6257,1.6547,58.233,1.4256,36.559,18.0225,
|
||||
2022-12-07,1.0529,144.44,1.9558,N/A,24.322,7.4382,N/A,0.86408,410.63,N/A,N/A,N/A,4.7003,N/A,4.918,10.919,N/A,N/A,0.9893,148.7,10.5255,7.5525,N/A,N/A,19.6256,1.5728,5.5023,1.4387,7.3476,8.1997,16465.17,3.626,86.692,1390.87,20.7534,4.6301,1.6573,58.432,1.4286,36.904,18.1353,
|
||||
2022-12-06,1.0516,143.33,1.9558,N/A,24.316,7.438,N/A,0.8617,415.08,N/A,N/A,N/A,4.6975,N/A,4.913,10.889,N/A,N/A,0.9872,148.9,10.4408,7.5563,N/A,N/A,19.601,1.5625,5.5113,1.4326,7.3494,8.1813,16441.49,3.5831,86.6485,1386.39,20.6884,4.6223,1.6583,58.782,1.4263,36.827,18.2068,
|
||||
2022-12-05,1.0587,143.07,1.9558,N/A,24.351,7.4369,N/A,0.86085,412.13,N/A,N/A,N/A,4.695,N/A,4.9215,10.8931,N/A,N/A,0.9893,148.9,10.3366,7.551,N/A,N/A,19.7326,1.5542,5.5491,1.4198,7.3573,8.2236,16332.36,3.5856,86.5249,1370.87,20.7295,4.6255,1.6498,59.245,1.4271,36.732,18.2038,
|
||||
2022-12-02,1.0538,141.32,1.9558,N/A,24.377,7.4373,N/A,0.85855,410,N/A,N/A,N/A,4.684,N/A,4.9298,10.902,N/A,N/A,0.9834,148.7,10.2615,7.5503,N/A,N/A,19.6392,1.5457,5.4657,1.416,7.3971,8.2035,16179.78,3.5769,85.6435,1366.67,20.187,4.6241,1.6453,58.734,1.4227,36.614,18.2749,
|
||||
2022-12-01,1.0454,142.48,1.9558,N/A,24.361,7.4373,N/A,0.85715,413.5,N/A,N/A,N/A,4.6998,N/A,4.9303,10.8984,N/A,N/A,0.9868,148.7,10.2495,7.55,N/A,N/A,19.4778,1.5377,5.4508,1.4059,7.3965,8.1371,16160.84,3.5642,84.933,1363.81,20.1472,4.605,1.6446,58.793,1.4195,36.526,18.5393,
|
||||
2022-11-30,1.0376,144.28,1.9558,N/A,24.338,7.4366,N/A,0.86488,408.4,N/A,N/A,N/A,4.6635,N/A,4.9245,10.9345,N/A,N/A,0.9854,147.1,10.2648,7.549,N/A,N/A,19.3333,1.5425,5.5063,1.4021,7.3437,8.0944,16271.81,3.5691,84.4215,1365.14,20.0111,4.6147,1.6634,58.697,1.418,36.588,17.5768,
|
||||
2022-11-29,1.0366,143.36,1.9558,N/A,24.334,7.4367,N/A,0.86218,406.5,N/A,N/A,N/A,4.673,N/A,4.9193,10.901,N/A,N/A,0.9862,147.1,10.3313,7.55,N/A,N/A,19.3181,1.5414,5.5126,1.4005,7.4289,8.0965,16301.58,3.5581,84.6548,1375.7,19.8075,4.6735,1.6639,58.64,1.4237,36.706,17.6027,
|
||||
2022-11-28,1.0463,144.9,1.9558,N/A,24.348,7.4367,N/A,0.86606,408.87,N/A,N/A,N/A,4.6938,N/A,4.9246,10.8973,N/A,N/A,0.9872,146.7,10.364,7.5488,N/A,N/A,19.4844,1.5632,5.6354,1.4062,7.5326,8.1782,16440.45,3.6002,85.437,1396.56,20.22,4.6874,1.6827,59.21,1.4375,37.285,17.9376,
|
||||
2022-11-25,1.0375,144.62,1.9558,N/A,24.367,7.4365,N/A,0.85885,411.33,N/A,N/A,N/A,4.6875,N/A,4.9255,10.8183,N/A,N/A,0.9836,146.5,10.2985,7.5473,N/A,N/A,19.3333,1.5404,5.5476,1.3864,7.4425,8.1084,16282.44,3.5551,84.7145,1383.2,20.1069,4.648,1.6651,58.795,1.4277,37.153,17.7677,
|
||||
2022-11-24,1.0413,143.9,1.9558,N/A,24.392,7.4369,N/A,0.85933,413.33,N/A,N/A,N/A,4.6958,N/A,4.9205,10.8573,N/A,N/A,0.9818,146.5,10.3435,7.547,N/A,N/A,19.3969,1.5414,5.5504,1.3894,7.4442,8.1324,16295.35,3.5607,85.0295,1382.28,20.1501,4.6806,1.6598,58.992,1.4319,37.258,17.7246,
|
||||
2022-11-23,1.0325,145.75,1.9558,N/A,24.356,7.437,N/A,0.86369,405.75,N/A,N/A,N/A,4.7033,N/A,4.937,10.8933,N/A,N/A,0.9795,146.7,10.3659,7.5435,N/A,N/A,19.2316,1.5522,5.565,1.3856,7.3982,8.0708,16189.81,3.5681,84.466,1397.42,20.001,4.7237,1.6718,58.914,1.4295,37.423,17.7103,
|
||||
2022-11-22,1.0274,145.2,1.9558,N/A,24.351,7.4377,N/A,0.86358,408.23,N/A,N/A,N/A,4.7125,N/A,4.9269,10.9653,N/A,N/A,0.9791,145.7,10.4445,7.5438,N/A,N/A,19.1221,1.5473,5.4578,1.3765,7.3344,8.0313,16106.79,3.5615,83.8768,1392.68,20.0951,4.7055,1.6707,58.942,1.4167,37.11,17.7568,
|
||||
2022-11-21,1.0246,145.33,1.9558,N/A,24.356,7.4377,N/A,0.86793,409.43,N/A,N/A,N/A,4.7075,N/A,4.9413,10.9873,N/A,N/A,0.9817,147.9,10.4898,7.5425,N/A,N/A,19.0822,1.5471,5.4401,1.377,7.3419,7.9989,16116.76,3.5515,83.7375,1395,19.9357,4.6927,1.6766,58.822,1.4162,37.121,17.7858,
|
||||
2022-11-18,1.0366,145.12,1.9558,N/A,24.351,7.4385,N/A,0.87063,407.41,N/A,N/A,N/A,4.7033,N/A,4.9413,10.9805,N/A,N/A,0.9881,148.9,10.486,7.5415,N/A,N/A,19.3009,1.5433,5.547,1.3841,7.379,8.1092,16224.36,3.591,84.6875,1389.02,20.162,4.7202,1.6757,59.41,1.4229,37.069,17.908,
|
||||
2022-11-17,1.0319,144.8,1.9558,N/A,24.399,7.4383,N/A,0.87475,415.6,N/A,N/A,N/A,4.7153,N/A,4.9254,10.9871,N/A,N/A,0.9818,148.9,10.498,7.541,N/A,N/A,19.2124,1.5526,5.6535,1.382,7.3859,8.077,16224.01,3.5796,84.394,1394.06,20.062,4.7122,1.6986,59.293,1.4221,37.123,18.0961,
|
||||
2022-11-16,1.0412,145.29,1.9558,N/A,24.355,7.4386,N/A,0.87483,408.18,N/A,N/A,N/A,4.7065,N/A,4.9206,10.8754,N/A,N/A,0.9795,148.9,10.3675,7.5443,N/A,N/A,19.3783,1.54,5.5438,1.3801,7.372,8.1444,16248.37,3.5684,84.5905,1378.1,20.1227,4.7323,1.6897,59.678,1.425,37.103,18.0195,
|
||||
2022-11-15,1.0404,144.84,1.9558,N/A,24.326,7.4388,N/A,0.87455,405.45,N/A,N/A,N/A,4.7073,N/A,4.9116,10.8081,N/A,N/A,0.979,149.9,10.357,7.5459,N/A,N/A,19.3608,1.5415,5.548,1.3816,7.3299,8.143,16164.78,3.5694,84.1304,1365.61,20.0795,4.7208,1.6897,59.532,1.4238,36.939,17.8822,
|
||||
2022-11-14,1.0319,144.86,1.9558,N/A,24.289,7.4382,N/A,0.87513,407.28,N/A,N/A,N/A,4.6898,N/A,4.9043,10.7713,N/A,N/A,0.9751,150.3,10.3143,7.5465,N/A,N/A,19.1923,1.5427,5.4605,1.3706,7.2906,8.0852,16052.12,3.541,83.7779,1369.32,20.0985,4.7429,1.6957,59.04,1.4177,36.978,17.8393,
|
||||
2022-11-11,1.0308,143.89,1.9558,N/A,24.278,7.4384,N/A,0.87538,402.08,N/A,N/A,N/A,4.6765,N/A,4.894,10.7241,N/A,N/A,0.9844,148.7,10.2635,7.5445,N/A,N/A,19.0987,1.5459,5.5147,1.3698,7.3267,8.0758,15979.45,3.5255,83.2253,1359.2,20.0239,4.77,1.702,59.106,1.4199,37.088,17.7944,
|
||||
2022-11-10,0.9954,145.47,1.9558,N/A,24.361,7.4381,N/A,0.87298,400.95,N/A,N/A,N/A,4.706,N/A,4.8913,10.8743,N/A,N/A,0.9834,147.5,10.3615,7.5427,N/A,N/A,18.51,1.5525,5.286,1.3467,7.2184,7.8128,15615.6,3.5453,81.3058,1373.96,19.4562,4.6789,1.6984,57.793,1.3963,36.7,17.6882,
|
||||
2022-11-09,1.0039,146.82,1.9558,N/A,24.337,7.4382,N/A,0.87774,403.53,N/A,N/A,N/A,4.701,N/A,4.9045,10.845,N/A,N/A,0.988,146.7,10.322,7.5425,N/A,N/A,18.6728,1.5538,5.1947,1.3501,7.2813,7.8801,15717.07,3.5621,81.6575,1369.73,19.6554,4.7098,1.7033,58.236,1.4061,36.999,17.877,
|
||||
2022-11-08,0.9996,146.25,1.9558,N/A,24.326,7.4378,N/A,0.87378,400.75,N/A,N/A,N/A,4.6918,N/A,4.8978,10.8373,N/A,N/A,0.9911,146.3,10.2795,7.539,N/A,N/A,18.5991,1.5435,5.203,1.3489,7.2495,7.8468,15652.76,3.5436,81.518,1377.94,19.4495,4.7346,1.686,58.187,1.4022,37.22,17.8397,
|
||||
2022-11-07,0.9993,146.18,1.9558,N/A,24.301,7.4393,N/A,0.87135,401.03,N/A,N/A,N/A,4.6865,N/A,4.8855,10.832,N/A,N/A,0.9874,145.9,10.2555,7.5375,N/A,N/A,18.5875,1.5428,5.07,1.3464,7.2189,7.8444,15648.95,3.5402,81.8407,1391.25,19.4395,4.7362,1.6834,58.361,1.4022,37.284,17.7583,
|
||||
2022-11-04,0.9872,145.19,1.9558,N/A,24.422,7.4419,N/A,0.87478,401.15,N/A,N/A,N/A,4.6825,N/A,4.8893,10.8538,N/A,N/A,0.9863,145.5,10.2019,7.5353,N/A,N/A,18.3845,1.5311,4.9682,1.3351,7.0894,7.7493,15491.81,3.5065,81.02,1397.7,19.2611,4.6872,1.6769,57.672,1.3891,36.906,17.7983,
|
||||
2022-11-03,0.9753,144.58,1.9558,N/A,24.539,7.4433,N/A,0.87228,407.87,N/A,N/A,N/A,4.709,N/A,4.9013,10.932,N/A,N/A,0.9889,144.9,10.3543,7.5375,N/A,N/A,18.1602,1.5517,5.0262,1.3452,7.1367,7.656,15400.2,3.4847,80.8845,1391.75,19.2363,4.6271,1.6957,57.463,1.3878,37.091,18.0173,
|
||||
2022-11-02,0.9908,145.75,1.9558,N/A,24.506,7.4431,N/A,0.861,407,N/A,N/A,N/A,4.7035,N/A,4.912,10.9065,N/A,N/A,0.9861,143.7,10.2388,7.5335,N/A,N/A,18.4488,1.5426,5.0964,1.347,7.2156,7.7774,15492.57,3.4987,81.992,1402.01,19.4921,4.6944,1.6844,57.841,1.3983,37.314,17.9608,
|
||||
2022-11-01,0.9947,146.35,1.9558,N/A,24.484,7.4438,N/A,0.86058,406.9,N/A,N/A,N/A,4.7053,N/A,4.9138,10.874,N/A,N/A,0.9878,143.3,10.1835,7.5342,N/A,N/A,18.5216,1.5409,5.1337,1.3469,7.2165,7.8079,15534.6,3.4922,82.084,1404.63,19.5984,4.7119,1.6876,57.786,1.4017,37.45,17.9802,
|
||||
2022-10-31,0.9914,147.4,1.9558,N/A,24.488,7.4444,N/A,0.86115,409.65,N/A,N/A,N/A,4.7085,N/A,4.9143,10.901,N/A,N/A,0.9925,143.3,10.3028,7.531,N/A,N/A,18.4562,1.5529,5.2694,1.3553,7.238,7.7822,15489.55,3.4933,82.1035,1416.12,19.7122,4.6873,1.7099,57.8,1.4038,37.748,18.1736,
|
||||
2022-10-28,0.9951,146.79,1.9558,N/A,24.465,7.4423,N/A,0.8612,411.7,N/A,N/A,N/A,4.7275,N/A,4.9189,10.9403,N/A,N/A,0.992,143.3,10.2695,7.532,N/A,N/A,18.5219,1.5511,5.327,1.3542,7.2159,7.8107,15481.88,3.5215,82.0565,1417.7,19.7718,4.6994,1.7151,57.739,1.4055,37.724,18.053,
|
||||
2022-10-27,1.0037,147.37,1.9558,N/A,24.53,7.4387,N/A,0.86745,412.15,N/A,N/A,N/A,4.7585,N/A,4.8893,10.9583,N/A,N/A,0.9949,143.1,10.342,7.533,N/A,N/A,18.681,1.561,5.3889,1.3672,7.2552,7.8782,15629.06,3.5376,82.656,1428.57,20.015,4.7324,1.7316,58.441,1.4154,37.975,18.1521,
|
||||
2022-10-26,1.0023,147.32,1.9558,N/A,24.535,7.4381,N/A,0.86603,408.09,N/A,N/A,N/A,4.7548,N/A,4.8806,10.953,N/A,N/A,0.9917,143.5,10.3408,7.532,N/A,N/A,18.6461,1.5466,5.2944,1.3568,7.1948,7.8678,15589.27,3.5145,82.206,1422.11,19.8501,4.7262,1.7249,58.493,1.4104,37.862,18.0212,
|
||||
2022-10-25,0.9861,146.84,1.9558,N/A,24.472,7.4387,N/A,0.87143,413.7,N/A,N/A,N/A,4.777,N/A,4.9036,10.9728,N/A,N/A,0.9888,142.9,10.391,7.5315,N/A,N/A,18.3508,1.5599,5.2254,1.3537,7.2072,7.7407,15407.12,3.506,81.653,1417.5,19.6353,4.6697,1.7321,57.988,1.405,37.758,18.2211,
|
||||
2022-10-24,0.9851,146.76,1.9558,N/A,24.482,7.4385,N/A,0.8707,411.88,N/A,N/A,N/A,4.7908,N/A,4.9128,11.0795,N/A,N/A,0.9856,142.5,10.392,7.5337,N/A,N/A,18.3298,1.5631,5.1461,1.3502,7.1544,7.7329,15362.63,3.4997,81.5451,1418.4,19.6514,4.6674,1.7343,58.021,1.4008,37.6,18.0625,
|
||||
2022-10-21,0.973,147.59,1.9558,N/A,24.511,7.4382,N/A,0.87728,412.88,N/A,N/A,N/A,4.7885,N/A,4.9125,11.0868,N/A,N/A,0.9855,141.1,10.4315,7.5325,N/A,N/A,18.0988,1.5646,5.1117,1.3465,7.0504,7.6376,15199.12,3.4803,80.739,1404.32,19.5521,4.6101,1.7347,57.287,1.3917,37.349,18.0021,
|
||||
2022-10-20,0.9811,146.99,1.9558,N/A,24.525,7.4389,N/A,0.87258,411.2,N/A,N/A,N/A,4.7728,N/A,4.9203,10.982,N/A,N/A,0.9836,141.1,10.402,7.5353,N/A,N/A,18.2257,1.5554,5.1387,1.3461,7.0858,7.7008,15250.05,3.4754,81.1755,1400.3,19.7005,4.6396,1.7206,57.742,1.3959,37.36,17.9106,
|
||||
2022-10-19,0.9778,146.34,1.9558,N/A,24.563,7.439,N/A,0.86993,413.78,N/A,N/A,N/A,4.7878,N/A,4.9248,10.9448,N/A,N/A,0.981,141.1,10.3823,7.5325,N/A,N/A,18.1793,1.5568,5.1755,1.3479,7.0672,7.6757,15185.1,3.4628,81.1955,1398.35,19.6845,4.6152,1.7264,57.741,1.3931,37.469,17.8339,
|
||||
2022-10-18,0.9835,146.65,1.9558,N/A,24.593,7.4393,N/A,0.86928,413.08,N/A,N/A,N/A,4.804,N/A,4.9359,10.906,N/A,N/A,0.9792,141.5,10.3528,7.5298,N/A,N/A,18.2813,1.5557,5.1795,1.3495,7.0805,7.72,15214.98,3.464,80.9195,1400.92,19.664,4.6382,1.7251,57.897,1.3963,37.422,17.7904,
|
||||
2022-10-17,0.9739,145,1.9558,N/A,24.562,7.4379,N/A,0.8625,418.3,N/A,N/A,N/A,4.8143,N/A,4.937,10.9893,N/A,N/A,0.9762,140.9,10.342,7.5265,N/A,N/A,18.1043,1.5599,5.1497,1.3452,7.013,7.6448,15061.8,3.4486,80.128,1399.41,19.5,4.5934,1.7404,57.433,1.3896,37.169,17.6769,
|
||||
2022-10-14,0.9717,143.63,1.9558,N/A,24.587,7.4378,N/A,0.86823,418.24,N/A,N/A,N/A,4.8328,N/A,4.9335,11.0035,N/A,N/A,0.9757,140.5,10.3323,7.5266,N/A,N/A,18.0614,1.5493,5.1177,1.3426,6.9952,7.6278,15031.5,3.444,79.9695,1398.5,19.5032,4.5689,1.7302,57.375,1.3852,37.109,17.6932,
|
||||
2022-10-13,0.9739,142.94,1.9558,N/A,24.569,7.4385,N/A,0.86513,430.65,N/A,N/A,N/A,4.8303,N/A,4.9355,11.0098,N/A,N/A,0.9725,140.5,10.3525,7.531,N/A,N/A,18.1041,1.5495,5.1214,1.3443,6.9945,7.644,14952.86,3.4731,79.9981,1392.71,19.4442,4.5691,1.7314,57.352,1.3949,36.843,17.8173,
|
||||
2022-10-12,0.9706,142.34,1.9558,N/A,24.561,7.4399,N/A,0.8784,429.65,N/A,N/A,N/A,4.8495,N/A,4.94,11.02,N/A,N/A,0.9664,140.1,10.4145,7.529,N/A,N/A,18.0427,1.5525,5.1378,1.3395,6.9603,7.6192,14907.04,3.4623,79.8955,1384.66,19.4522,4.5448,1.7372,57.148,1.3941,36.902,17.6876,
|
||||
2022-10-11,0.9723,141.54,1.9558,N/A,24.535,7.439,N/A,0.87703,428.73,N/A,N/A,N/A,4.869,N/A,4.9394,11.0015,N/A,N/A,0.9675,140.7,10.4235,7.5293,N/A,N/A,18.0686,1.545,5.0456,1.3402,6.9669,7.6325,14930.83,3.4776,79.9555,1392.84,19.4115,4.5436,1.7323,57.243,1.3967,37.03,17.6153,
|
||||
2022-10-10,0.9697,141.16,1.9558,N/A,24.521,7.4384,N/A,0.8773,428.2,N/A,N/A,N/A,4.8655,N/A,4.94,10.9502,N/A,N/A,0.968,139.9,10.3378,7.528,N/A,N/A,18.0131,1.536,5.0328,1.3312,6.9344,7.612,14872.51,3.4463,79.9678,1384.26,19.3588,4.5091,1.7369,57.197,1.3939,36.81,17.5866,
|
||||
2022-10-07,0.9797,141.92,1.9558,N/A,24.517,7.4381,N/A,0.87383,423.85,N/A,N/A,N/A,4.8595,N/A,4.9415,10.8555,N/A,N/A,0.97,140.7,10.4498,7.527,N/A,N/A,18.209,1.5266,5.1075,1.3437,6.9715,7.6906,14933.14,3.4477,80.546,1381.42,19.643,4.5556,1.7328,57.747,1.3996,36.602,17.6222,
|
||||
2022-10-06,0.986,142.68,1.9558,N/A,24.479,7.439,N/A,0.87583,422.59,N/A,N/A,N/A,4.8505,N/A,4.9364,10.8728,N/A,N/A,0.9709,139.9,10.4278,7.5288,N/A,N/A,18.3191,1.5263,5.1185,1.3475,7.0164,7.74,15021.24,3.4846,81.0615,1388.39,19.817,4.5726,1.728,57.949,1.4057,36.827,17.5769,
|
||||
2022-10-05,0.9915,143.18,1.9558,N/A,24.524,7.4388,N/A,0.8734,423.6,N/A,N/A,N/A,4.791,N/A,4.9385,10.8376,N/A,N/A,0.9756,141.3,10.4858,7.5255,N/A,N/A,18.4201,1.538,5.1575,1.3493,7.0555,7.7831,15061.81,3.503,80.909,1406.71,19.8625,4.5911,1.7419,58.212,1.4124,37.102,17.6228,
|
||||
2022-10-04,0.9891,143.3,1.9558,N/A,24.544,7.4374,N/A,0.87273,417.68,N/A,N/A,N/A,4.8193,N/A,4.9418,10.8166,N/A,N/A,0.9767,141.9,10.4915,7.523,N/A,N/A,18.3374,1.5318,5.0589,1.3503,7.0384,7.7644,15080.18,3.4885,80.6995,1412.2,19.777,4.5939,1.7368,58.104,1.4148,37.16,17.5437,
|
||||
2022-10-03,0.9764,141.49,1.9558,N/A,24.527,7.4366,N/A,0.8707,424.86,N/A,N/A,N/A,4.832,N/A,4.9479,10.8743,N/A,N/A,0.9658,141.7,10.5655,7.5275,N/A,N/A,18.124,1.5128,5.178,1.3412,6.9481,7.6647,14969.79,3.498,79.898,1408.25,19.604,4.5383,1.7263,57.599,1.4015,37.181,17.5871,
|
||||
2022-09-30,0.9748,141.01,1.9558,N/A,24.549,7.4365,N/A,0.883,422.18,N/A,N/A,N/A,4.8483,N/A,4.949,10.8993,N/A,N/A,0.9561,140.9,10.5838,7.524,N/A,N/A,18.0841,1.5076,5.2584,1.3401,6.9368,7.6521,14863.26,3.4759,79.425,1400.69,19.6393,4.5201,1.7177,57.276,1.4001,36.823,17.5353,
|
||||
2022-09-29,0.9706,140.46,1.9558,N/A,24.687,7.4365,N/A,0.89485,421.93,N/A,N/A,N/A,4.857,N/A,4.9481,10.958,N/A,N/A,0.9538,140.1,10.4518,7.528,N/A,N/A,18,1.4982,5.2521,1.3294,6.9223,7.6192,14735.97,3.4422,79.314,1388.34,19.5779,4.4992,1.704,56.86,1.3961,36.946,17.4466,
|
||||
2022-09-28,0.9565,138.39,1.9558,N/A,24.65,7.4368,N/A,0.90268,411.72,N/A,N/A,N/A,4.8043,N/A,4.9485,10.9194,N/A,N/A,0.9437,139.7,10.4576,7.5313,N/A,N/A,17.7311,1.4924,5.1728,1.3157,6.9199,7.5084,14622.96,3.3931,78.2655,1378.84,19.5294,4.4281,1.6998,56.528,1.3846,36.687,17.2916,
|
||||
2022-09-27,0.9644,139.28,1.9558,N/A,24.661,7.4366,N/A,0.89275,406.65,N/A,N/A,N/A,4.764,N/A,4.9444,10.8533,N/A,N/A,0.9503,139.3,10.3473,7.528,N/A,N/A,17.824,1.4859,5.1235,1.3196,6.9156,7.5704,14604.52,3.3714,78.574,1370.06,19.5832,4.4466,1.6921,56.933,1.3838,36.565,17.2361,
|
||||
2022-09-26,0.9646,139.07,1.9558,N/A,24.64,7.4365,N/A,0.89404,408.83,N/A,N/A,N/A,4.7608,N/A,4.9418,10.9275,N/A,N/A,0.9555,138.9,10.3585,7.5278,N/A,N/A,17.8001,1.4858,5.1504,1.3195,6.9075,7.572,14620.74,3.4069,78.704,1379.4,19.6066,4.4401,1.6886,56.908,1.3842,36.496,17.4247,
|
||||
2022-09-23,0.9754,139.43,1.9558,N/A,24.658,7.4365,N/A,0.88201,406.3,N/A,N/A,N/A,4.7543,N/A,4.9433,10.9328,N/A,N/A,0.9565,139.9,10.2335,7.5228,N/A,N/A,17.9515,1.4828,5.0456,1.3177,6.9442,7.6567,14697.3,3.4152,79.0705,1381.97,19.5708,4.4659,1.6846,57.217,1.3897,36.636,17.3853,
|
||||
2022-09-22,0.9884,139.18,1.9558,N/A,24.657,7.4365,N/A,0.87256,405.25,N/A,N/A,N/A,4.7592,N/A,4.9411,10.8724,N/A,N/A,0.9684,139.9,10.235,7.5235,N/A,N/A,18.1559,1.484,5.0677,1.3278,6.9804,7.7583,14824.28,3.4216,79.897,1384.79,19.6129,4.514,1.6832,57.721,1.3998,36.803,17.3514,
|
||||
2022-09-21,0.9906,142.66,1.9558,N/A,24.637,7.4364,N/A,0.87335,405.1,N/A,N/A,N/A,4.7505,N/A,4.9443,10.9214,N/A,N/A,0.9549,140.3,10.2858,7.5205,N/A,N/A,18.149,1.4851,5.0924,1.3262,6.9821,7.7761,14866.28,3.4298,79.1555,1381.38,19.7847,4.5097,1.6844,57.285,1.4006,36.786,17.4879,
|
||||
2022-09-20,0.9986,143.34,1.9558,N/A,24.556,7.4368,N/A,0.87395,398.58,N/A,N/A,N/A,4.7208,N/A,4.934,10.8338,N/A,N/A,0.9644,140.9,10.273,7.5198,N/A,N/A,18.2833,1.4893,5.2139,1.3268,7.003,7.8382,14997.82,3.4406,79.6095,1390.71,19.9667,4.5516,1.6908,57.497,1.4074,36.968,17.7261,
|
||||
2022-09-19,0.999,143.42,1.9558,N/A,24.494,7.4373,N/A,0.87785,400.85,N/A,N/A,N/A,4.7058,N/A,4.93,10.7993,N/A,N/A,0.9658,139.9,10.2826,7.5215,N/A,N/A,18.2738,1.495,5.2886,1.3294,7.0066,7.8416,14975.43,3.446,79.653,1391.82,20.113,4.5455,1.6807,57.347,1.4082,36.983,17.7267,
|
||||
2022-09-16,0.9954,142.53,1.9558,N/A,24.497,7.4366,N/A,0.874,403.98,N/A,N/A,N/A,4.7143,N/A,4.9238,10.7541,N/A,N/A,0.9579,138.3,10.1985,7.5235,N/A,N/A,18.1923,1.4894,5.2279,1.3226,6.9787,7.8133,14904.67,3.4267,79.3605,1383.58,20.0028,4.5141,1.6717,57.111,1.4025,36.8,17.6004,
|
||||
2022-09-15,0.9992,143.43,1.9558,N/A,24.518,7.4366,N/A,0.86934,407.15,N/A,N/A,N/A,4.7273,N/A,4.9238,10.69,N/A,N/A,0.9572,138.7,10.1203,7.5258,N/A,N/A,18.2477,1.4853,5.1837,1.3172,6.9852,7.8423,14925.35,3.4384,79.7119,1397.18,20.0021,4.5314,1.6689,57.258,1.4062,36.816,17.5283,
|
||||
2022-09-14,0.999,143.08,1.9558,N/A,24.527,7.4366,N/A,0.86498,402.9,N/A,N/A,N/A,4.7163,N/A,4.9297,10.675,N/A,N/A,0.9612,139.7,10.1125,7.5195,N/A,N/A,18.2397,1.4873,5.1827,1.3177,6.955,7.8405,14903.93,3.4339,79.422,1391.97,20.028,4.5225,1.6675,57.054,1.4039,36.608,17.4342,
|
||||
2022-09-13,1.0175,144.5,1.9558,N/A,24.551,7.4366,N/A,0.86793,396.83,N/A,N/A,N/A,4.705,N/A,4.921,10.6108,N/A,N/A,0.9669,140.1,9.9988,7.5255,N/A,N/A,18.564,1.4736,5.1764,1.32,7.0467,7.9855,15099.17,3.4125,80.5453,1397.3,20.1615,4.5869,1.6555,57.665,1.4186,36.859,17.3112,
|
||||
2022-09-12,1.0155,144.49,1.9558,N/A,24.546,7.4365,N/A,0.86778,395.73,N/A,N/A,N/A,4.6965,N/A,4.9135,10.6368,N/A,N/A,0.9667,140.9,9.9718,7.5195,N/A,N/A,18.5232,1.4749,5.1933,1.3194,7.0348,7.9709,15083.83,3.4346,80.692,1397.58,20.1025,4.5733,1.6499,57.701,1.4168,36.873,17.322,
|
||||
2022-09-09,1.0049,143.3,1.9558,N/A,24.536,7.4365,N/A,0.8686,396.3,N/A,N/A,N/A,4.721,N/A,4.9019,10.6643,N/A,N/A,0.9657,140.9,9.9836,7.5245,N/A,N/A,18.3282,1.4704,5.2087,1.307,6.9543,7.8871,14905.33,3.4416,79.9685,1384.64,19.991,4.52,1.6463,57.098,1.4063,36.508,17.3753,
|
||||
2022-09-08,1.0009,143.65,1.9558,N/A,24.543,7.4365,N/A,0.86656,395.48,N/A,N/A,N/A,4.7155,N/A,4.8756,10.7075,N/A,N/A,0.9739,140.3,10.0615,7.515,N/A,N/A,18.2546,1.4824,5.2042,1.3134,6.9564,7.8568,14891.86,3.429,79.7375,1381.7,20.013,4.5051,1.6491,57.031,1.4054,36.418,17.3797,
|
||||
2022-09-07,0.9885,143.2,1.9558,N/A,24.631,7.4365,N/A,0.8651,401.83,N/A,N/A,N/A,4.729,N/A,4.8585,10.6888,N/A,N/A,0.975,141.3,9.9483,7.5143,N/A,N/A,18.0262,1.4748,5.1881,1.3037,6.8968,7.7596,14779.47,3.4053,79.028,1374.44,19.9225,4.4497,1.6459,56.532,1.3931,36.322,17.2582,
|
||||
2022-09-06,0.9928,140.91,1.9558,N/A,24.55,7.4365,N/A,0.85743,402.65,N/A,N/A,N/A,4.7068,N/A,4.8424,10.6825,N/A,N/A,0.9745,141.9,9.8945,7.5133,N/A,N/A,18.0938,1.4651,5.13,1.3029,6.9091,7.7932,14783.94,3.39,79.2305,1366.65,19.8545,4.4676,1.6313,56.655,1.3947,36.242,17.0805,
|
||||
2022-09-05,0.992,139.47,1.9558,N/A,24.622,7.4364,N/A,0.86358,403.9,N/A,N/A,N/A,4.736,N/A,4.8198,10.729,N/A,N/A,0.9747,142.7,9.9188,7.5173,N/A,N/A,18.0792,1.4616,5.1407,1.3043,6.8768,7.7867,14782.83,3.3826,79.2332,1359.98,19.8192,4.4563,1.6289,56.477,1.3932,36.263,17.088,
|
||||
2022-09-02,0.9993,140.36,1.9558,N/A,24.481,7.437,N/A,0.86478,398.38,N/A,N/A,N/A,4.7063,N/A,4.8335,10.7498,N/A,N/A,0.9839,141.5,10.0035,7.5225,N/A,N/A,18.2072,1.4671,5.2153,1.3131,6.9031,7.8439,14895.92,3.396,79.8096,1360.84,20.1024,4.4809,1.6394,56.854,1.4013,36.624,17.2791,
|
||||
2022-09-01,1.0004,139.34,1.9558,N/A,24.488,7.4372,N/A,0.86473,399.58,N/A,N/A,N/A,4.7128,N/A,4.8447,10.7415,N/A,N/A,0.9802,141.7,10.013,7.521,N/A,N/A,18.2149,1.4651,5.2239,1.3169,6.9017,7.8511,14878,3.3644,79.6195,1353.69,20.1954,4.4828,1.6389,56.609,1.4002,36.71,17.1524,
|
||||
2022-08-31,1,138.72,1.9558,N/A,24.55,7.4371,N/A,0.86035,402.8,N/A,N/A,N/A,4.7283,N/A,4.8595,10.6788,N/A,N/A,0.9796,141.7,9.9388,7.5148,N/A,N/A,18.1849,1.4591,5.1482,1.3111,6.8947,7.8488,14849.93,3.3399,79.5465,1342.79,20.2044,4.4755,1.6322,56.153,1.3969,36.45,17.0667,
|
||||
2022-08-30,1.0034,138.71,1.9558,N/A,24.577,7.4376,N/A,0.85645,406.38,N/A,N/A,N/A,4.7323,N/A,4.8657,10.65,N/A,N/A,0.9741,142.1,9.7553,7.5103,N/A,N/A,18.239,1.4472,5.0286,1.3047,6.9233,7.8751,14875,3.3168,79.8025,1350.92,20.0077,4.4907,1.6245,56.393,1.3997,36.494,16.8567,
|
||||
2022-08-29,0.9986,138.49,1.9558,N/A,24.592,7.4379,N/A,0.8542,409.9,N/A,N/A,N/A,4.745,N/A,4.8699,10.628,N/A,N/A,0.967,141.1,9.7675,7.5119,N/A,N/A,18.1605,1.4529,5.0663,1.3026,6.9044,7.8368,14871.09,3.3146,79.8295,1347.47,19.9876,4.4837,1.6305,56.187,1.395,36.399,16.8891,
|
||||
2022-08-26,1.0007,137.02,1.9558,N/A,24.635,7.4379,N/A,0.8459,410.48,N/A,N/A,N/A,4.7485,N/A,4.8729,10.5703,N/A,N/A,0.9642,140.3,9.667,7.5135,N/A,N/A,18.1923,1.4333,5.1069,1.2944,6.8671,7.8521,14825.57,3.2537,79.9025,1333.46,19.924,4.4706,1.6112,56.109,1.3906,36.035,16.7995,
|
||||
2022-08-25,0.997,136.07,1.9558,N/A,24.648,7.4374,N/A,0.84293,408.93,N/A,N/A,N/A,4.7578,N/A,4.8758,10.5525,N/A,N/A,0.9616,140.3,9.64,7.514,N/A,N/A,18.112,1.4306,5.0879,1.2881,6.8317,7.8234,14753.15,3.2791,79.6555,1331.98,19.8132,4.4586,1.6006,55.842,1.3857,35.732,16.7903,
|
||||
2022-08-24,0.9934,135.74,1.9558,N/A,24.629,7.4381,N/A,0.84283,410.93,N/A,N/A,N/A,4.7668,N/A,4.88,10.586,N/A,N/A,0.9576,139.5,9.636,7.5125,N/A,N/A,18.0362,1.4389,5.0606,1.2908,6.822,7.795,14757.6,3.2599,79.3006,1332.84,19.7781,4.4559,1.6065,55.7,1.3857,35.906,16.8976,
|
||||
2022-08-23,0.9927,136.34,1.9558,N/A,24.658,7.4374,N/A,0.84343,410.55,N/A,N/A,N/A,4.7788,N/A,4.8839,10.6063,N/A,N/A,0.9602,140.1,9.7438,7.5128,N/A,N/A,17.983,1.4437,5.0984,1.2928,6.7952,7.79,14743.28,3.2598,79.2805,1332.1,19.9152,4.4557,1.6071,55.661,1.386,35.856,16.9568,
|
||||
2022-08-22,1.0001,137.08,1.9558,N/A,24.651,7.437,N/A,0.84658,408,N/A,N/A,N/A,4.764,N/A,4.8853,10.65,N/A,N/A,0.958,140.9,9.7719,7.5113,N/A,N/A,18.1268,1.4478,5.1752,1.2989,6.8457,7.8468,14895.47,3.2778,79.8615,1344.21,20.1583,4.4854,1.6125,56.218,1.3958,36.099,17.0766,
|
||||
2022-08-19,1.0054,137.67,1.9558,N/A,24.625,7.4373,N/A,0.84938,407.35,N/A,N/A,N/A,4.751,N/A,4.8811,10.6095,N/A,N/A,0.9616,140.5,9.8418,7.5155,N/A,N/A,18.2028,1.4584,5.2334,1.3062,6.8531,7.8877,14951.91,3.2879,80.2988,1343.39,20.3273,4.5007,1.6203,56.325,1.3979,35.988,17.1007,
|
||||
2022-08-18,1.0178,137.17,1.9558,N/A,24.611,7.4389,N/A,0.84391,405.13,N/A,N/A,N/A,4.724,N/A,4.8808,10.5903,N/A,N/A,0.9683,140.5,9.8283,7.5215,N/A,N/A,18.4095,1.4617,5.2326,1.3118,6.906,7.9844,15092.62,3.2993,81.042,1344.81,20.313,4.5536,1.6145,56.776,1.4053,36.254,17.0004,
|
||||
2022-08-17,1.0164,137.36,1.9558,N/A,24.566,7.4377,N/A,0.84208,404.28,N/A,N/A,N/A,4.7078,N/A,4.8825,10.5617,N/A,N/A,0.9686,140.3,9.8428,7.5071,N/A,N/A,18.2568,1.4655,5.2838,1.3117,6.8917,7.9705,15015.81,3.311,80.7555,1337.02,20.3825,4.5413,1.6165,56.772,1.4051,36.052,16.9125,
|
||||
2022-08-16,1.0131,136.11,1.9558,N/A,24.54,7.4368,N/A,0.84218,406.2,N/A,N/A,N/A,4.7043,N/A,4.882,10.5365,N/A,N/A,0.9625,140.3,9.8428,7.51,N/A,N/A,18.1994,1.4463,5.1835,1.3076,6.8767,7.9449,14968.68,3.3087,80.3745,1329.66,20.1595,4.5245,1.6012,56.602,1.398,35.93,16.6556,
|
||||
2022-08-15,1.0195,135.61,1.9558,N/A,24.46,7.4373,N/A,0.84375,398.6,N/A,N/A,N/A,4.6858,N/A,4.8849,10.498,N/A,N/A,0.9631,140.3,9.871,7.5028,N/A,N/A,18.3143,1.4508,5.2268,1.3167,6.905,7.9899,15042.36,3.3294,81.061,1336.35,20.3914,4.5465,1.6002,57.122,1.4036,36.218,16.7375,
|
||||
2022-08-12,1.0285,137.47,1.9558,N/A,24.38,7.4395,N/A,0.84715,392.3,N/A,N/A,N/A,4.6773,N/A,4.8915,10.4515,N/A,N/A,0.9689,140.3,9.813,7.5138,N/A,N/A,18.4733,1.4496,5.3007,1.3148,6.9352,8.06,15104.2,3.345,81.9935,1342.59,20.4925,4.5709,1.5985,57.246,1.4106,36.393,16.7318,
|
||||
2022-08-11,1.0338,136.57,1.9558,N/A,24.346,7.4395,N/A,0.84575,394.18,N/A,N/A,N/A,4.6828,N/A,4.9055,10.36,N/A,N/A,0.9712,139.7,9.804,7.515,N/A,N/A,18.5674,1.4532,5.2447,1.3202,6.9668,8.1118,15215.93,3.3505,82.2845,1344.62,20.6398,4.5952,1.6045,57.225,1.4151,36.343,16.7083,
|
||||
2022-08-10,1.0252,138.16,1.9558,N/A,24.397,7.4397,N/A,0.84608,397.65,N/A,N/A,N/A,4.7063,N/A,4.9138,10.3773,N/A,N/A,0.9713,139.7,9.9118,7.5158,N/A,N/A,18.4099,1.4682,5.251,1.3207,6.9222,8.046,15218.87,3.3773,81.468,1344.16,20.713,4.5693,1.6211,57.061,1.4107,36.42,16.8788,
|
||||
2022-08-09,1.0234,138.26,1.9558,N/A,24.532,7.4407,N/A,0.8452,397.35,N/A,N/A,N/A,4.7085,N/A,4.9038,10.3875,N/A,N/A,0.9763,140.1,9.9365,7.514,N/A,N/A,18.3342,1.4687,5.2478,1.3163,6.9106,8.0334,15197.09,3.3865,81.406,1336.74,20.7145,4.5592,1.6304,56.939,1.411,36.264,17.05,
|
||||
2022-08-08,1.0199,137.62,1.9558,N/A,24.515,7.4405,N/A,0.84165,394.27,N/A,N/A,N/A,4.7043,N/A,4.9163,10.365,N/A,N/A,0.9763,140.1,9.9405,7.5123,N/A,N/A,18.3175,1.4607,5.238,1.3134,6.8931,8.0061,15147.65,3.3895,81.166,1329.93,20.681,4.5477,1.6202,56.554,1.4058,36.38,16.9694,
|
||||
2022-08-05,1.0233,136.22,1.9558,N/A,24.581,7.4415,N/A,0.84268,393.78,N/A,N/A,N/A,4.7085,N/A,4.9251,10.3573,N/A,N/A,0.9776,138.9,9.982,7.5148,N/A,N/A,18.3853,1.4713,5.3348,1.3185,6.9068,8.0328,15236.2,3.4033,81.0469,1324.53,20.8367,4.5598,1.6248,56.524,1.4077,36.373,17.0342,
|
||||
2022-08-04,1.0181,135.81,1.9558,N/A,24.659,7.4425,N/A,0.84231,395.98,N/A,N/A,N/A,4.7233,N/A,4.9261,10.374,N/A,N/A,0.9765,138.7,9.9065,7.515,N/A,N/A,18.2922,1.4607,5.3614,1.307,6.8769,7.9919,15200.94,3.4101,80.7715,1333.7,20.756,4.5387,1.6172,56.633,1.4037,36.606,17.0352,
|
||||
2022-08-03,1.0194,136.18,1.9558,N/A,24.65,7.4427,N/A,0.83629,395.03,N/A,N/A,N/A,4.691,N/A,4.9245,10.3913,N/A,N/A,0.9773,139.3,9.8743,7.5178,N/A,N/A,18.3112,1.4681,5.3547,1.3085,6.883,8.0022,15184.35,3.4362,80.6895,1336.03,21.0788,4.5435,1.6266,56.855,1.4069,36.963,17.1283,
|
||||
2022-08-02,1.0224,133.9,1.9558,N/A,24.644,7.4432,N/A,0.83665,396.82,N/A,N/A,N/A,4.7063,N/A,4.9298,10.3995,N/A,N/A,0.9744,139.3,9.9305,7.5195,N/A,N/A,18.3564,1.4745,5.3175,1.315,6.9117,8.0257,15204.85,3.4486,80.3243,1339.16,20.952,4.5531,1.6254,56.834,1.4103,36.914,16.982,
|
||||
2022-08-01,1.0233,135.38,1.9558,N/A,24.628,7.4457,N/A,0.837,401.35,N/A,N/A,N/A,4.734,N/A,4.9283,10.3668,N/A,N/A,0.9717,138.7,9.8638,7.521,N/A,N/A,18.3475,1.4535,5.2723,1.3076,6.9105,8.0329,15203.21,3.4546,80.9335,1333.3,20.7635,4.5568,1.616,56.734,1.4087,36.977,16.8613,
|
||||
2022-07-29,1.0198,136.42,1.9558,N/A,24.61,7.4438,N/A,0.8399,404.8,N/A,N/A,N/A,4.7375,N/A,4.9343,10.3875,N/A,N/A,0.9744,138.3,9.8773,7.518,N/A,N/A,18.2472,1.4646,5.2739,1.31,6.8705,8.0054,15155.56,3.4714,80.882,1329.4,20.6745,4.5386,1.6283,56.375,1.4088,36.978,16.8627,
|
||||
2022-07-28,1.0122,137.26,1.9558,N/A,24.609,7.4442,N/A,0.83586,407.3,N/A,N/A,N/A,4.7908,N/A,4.9342,10.449,N/A,N/A,0.9745,138.7,9.8983,7.52,N/A,N/A,18.1417,1.4535,5.33,1.2986,6.8325,7.9456,15107.59,3.4701,80.6535,1320.45,20.676,4.5071,1.6172,56.592,1.4009,37.097,17.0011,
|
||||
2022-07-27,1.0152,138.89,1.9558,N/A,24.575,7.4446,N/A,0.84138,404.67,N/A,N/A,N/A,4.7978,N/A,4.9334,10.4545,N/A,N/A,0.9768,139.1,9.9558,7.514,N/A,N/A,18.1859,1.462,5.4039,1.3049,6.8534,7.9692,15213.07,3.4855,81.135,1333.99,20.753,4.5263,1.6306,56.455,1.4088,37.4,17.1347,
|
||||
2022-07-26,1.0124,138.35,1.9558,N/A,24.607,7.4449,N/A,0.84558,400.99,N/A,N/A,N/A,4.742,N/A,4.9324,10.4445,N/A,N/A,0.9765,139.1,10.0105,7.5145,N/A,N/A,18.0705,1.4605,5.4437,1.3035,6.8451,7.9466,15185.27,3.4891,80.805,1326.65,20.7845,4.5113,1.6235,56.16,1.4066,37.18,17.087,
|
||||
2022-07-25,1.0236,139.84,1.9558,N/A,24.535,7.4449,N/A,0.84813,396.5,N/A,N/A,N/A,4.708,N/A,4.9339,10.3973,N/A,N/A,0.9869,139.5,10.0704,7.5195,N/A,N/A,18.2653,1.4707,5.5976,1.3168,6.9094,8.0345,15303.87,3.5201,81.6675,1341.25,20.9376,4.5586,1.6325,57.209,1.4176,37.525,17.1502,
|
||||
2022-07-22,1.019,139.51,1.9558,N/A,24.514,7.4443,N/A,0.85141,398.3,N/A,N/A,N/A,4.7508,N/A,4.9321,10.4328,N/A,N/A,0.9832,139.5,10.1498,7.5234,N/A,N/A,18.094,1.4677,5.5821,1.3105,6.8852,7.9985,15275.69,3.5083,81.384,1335.66,20.9595,4.5366,1.6265,57.24,1.4151,37.392,17.2009,
|
||||
2022-07-21,1.0199,141.46,1.9558,N/A,24.496,7.4446,N/A,0.85545,400.13,N/A,N/A,N/A,4.761,N/A,4.9391,10.426,N/A,N/A,0.9924,139.7,10.175,7.52,N/A,N/A,18.0327,1.4848,5.5777,1.3178,6.904,8.0056,15342.25,3.5203,81.451,1337.47,20.941,4.5457,1.6479,57.512,1.4218,37.66,17.5195,
|
||||
2022-07-20,1.0199,140.92,1.9558,N/A,24.493,7.4452,N/A,0.85178,399.5,N/A,N/A,N/A,4.782,N/A,4.9396,10.4606,N/A,N/A,0.9896,139.5,10.1323,7.5143,N/A,N/A,17.9444,1.4767,5.5427,1.3132,6.8892,8.0062,15275.82,3.5147,81.599,1337.61,20.8967,4.5406,1.6308,57.398,1.4204,37.405,17.3924,
|
||||
2022-07-19,1.0245,141.01,1.9558,N/A,24.555,7.4449,N/A,0.85303,397.45,N/A,N/A,N/A,4.7598,N/A,4.9395,10.4964,N/A,N/A,0.9918,138.9,10.176,7.5093,N/A,N/A,18.018,1.4869,5.5454,1.3264,6.9064,8.0423,15344.49,3.5295,81.898,1340.33,20.8552,4.559,1.6456,57.583,1.4269,37.492,17.457,
|
||||
2022-07-18,1.0131,140.16,1.9558,N/A,24.508,7.4435,N/A,0.84708,402.05,N/A,N/A,N/A,4.776,N/A,4.9389,10.5265,N/A,N/A,0.9911,138.9,10.2553,7.513,N/A,N/A,17.7225,1.4839,5.4505,1.3151,6.8266,7.9528,15157.63,3.5004,81.034,1333.33,20.7095,4.5113,1.6432,57.056,1.4153,37.13,17.383,
|
||||
2022-07-15,1.0059,139.49,1.9558,N/A,24.561,7.443,N/A,0.84988,403.73,N/A,N/A,N/A,4.7953,N/A,4.9407,10.5943,N/A,N/A,0.9849,138.9,10.2763,7.516,N/A,N/A,17.5451,1.4886,5.4434,1.3147,6.7943,7.8963,15081.58,3.5044,80.316,1333.79,20.9209,4.4752,1.6377,56.678,1.4113,36.866,17.2875,
|
||||
2022-07-14,1.0005,139.04,1.9558,N/A,24.417,7.4425,N/A,0.8456,408.78,N/A,N/A,N/A,4.8146,N/A,4.942,10.6019,N/A,N/A,0.9841,138.89,10.2536,7.5122,N/A,N/A,17.494,1.4893,5.4586,1.3162,6.7618,7.8539,15119.56,3.4915,80.0752,1322.29,20.9585,4.4462,1.6437,56.565,1.4065,36.632,17.202,
|
||||
2022-07-13,1.0067,138.02,1.9558,N/A,24.397,7.4416,N/A,0.84371,409.35,N/A,N/A,N/A,4.824,N/A,4.9414,10.602,N/A,N/A,0.9829,138.3,10.2428,7.5155,N/A,N/A,17.5629,1.4802,5.4533,1.3073,6.7722,7.9025,15117.08,3.4864,80.1285,1311.4,20.9029,4.4667,1.635,56.669,1.4134,36.377,17.0527,
|
||||
2022-07-12,1.0042,137.31,1.9558,N/A,24.582,7.4408,N/A,0.84823,409.98,N/A,N/A,N/A,4.819,N/A,4.9413,10.629,N/A,N/A,0.9883,139.1,10.2754,7.517,N/A,N/A,17.4392,1.49,5.4009,1.3094,6.7518,7.8828,15054.05,3.4987,79.8965,1315.1,20.8883,4.4556,1.6395,56.648,1.4127,36.392,17.1509,
|
||||
2022-07-11,1.0098,138.77,1.9558,N/A,24.592,7.4414,N/A,0.8454,408.22,N/A,N/A,N/A,4.7968,N/A,4.943,10.6943,N/A,N/A,0.9908,139.3,10.299,7.518,N/A,N/A,17.5447,1.491,5.3673,1.314,6.7793,7.9265,15131.92,3.5108,80.2435,1320.36,20.7845,4.4704,1.6466,56.574,1.4178,36.585,17.1634,
|
||||
2022-07-08,1.0163,138.05,1.9558,N/A,24.614,7.4424,N/A,0.84585,402.45,N/A,N/A,N/A,4.763,N/A,4.9431,10.6665,N/A,N/A,0.9913,139.5,10.263,7.519,N/A,N/A,17.6026,1.4871,5.4345,1.3201,6.8095,7.9769,15210.73,3.5325,80.528,1321.61,20.8477,4.4992,1.6464,56.882,1.4228,36.602,17.1922,
|
||||
2022-07-07,1.018,138.11,1.9558,N/A,24.779,7.4405,N/A,0.85105,410.04,N/A,N/A,N/A,4.7721,N/A,4.9448,10.723,N/A,N/A,0.9906,139.3,10.291,7.5193,N/A,N/A,17.5551,1.4883,5.4983,1.3227,6.823,7.9893,15265.27,3.5548,80.6,1324.66,20.9675,4.5077,1.6461,56.939,1.4255,36.74,17.0372,
|
||||
2022-07-06,1.0177,137.71,1.9558,N/A,24.778,7.4403,N/A,0.85676,411.8,N/A,N/A,N/A,4.771,N/A,4.944,10.745,N/A,N/A,0.9896,138.5,10.2803,7.5198,N/A,N/A,17.5505,1.4961,5.5116,1.3274,6.8289,7.9864,15287.49,3.5884,80.5321,1331.69,21.0194,4.5028,1.6505,56.779,1.4305,36.78,17.0246,
|
||||
2022-07-05,1.029,139.77,1.9558,N/A,24.751,7.4396,N/A,0.85845,407.38,N/A,N/A,N/A,4.7448,N/A,4.9438,10.8031,N/A,N/A,0.9932,139.1,10.285,7.5246,N/A,N/A,17.5049,1.518,5.5141,1.3364,6.9029,8.0748,15487.93,3.6343,81.673,1348.97,21.0171,4.5477,1.6772,57.009,1.4455,36.879,16.9143,
|
||||
2022-07-04,1.0455,141.51,1.9558,N/A,24.745,7.4391,N/A,0.8596,401.52,N/A,N/A,N/A,4.71,N/A,4.944,10.7658,N/A,N/A,1.0037,139.3,10.2958,7.5301,N/A,N/A,17.5994,1.5205,5.5663,1.3435,6.9977,8.2033,15684.13,3.6655,82.5067,1353.4,21.1972,4.6138,1.6748,57.487,1.4587,37.298,17.0275,
|
||||
2022-07-01,1.0425,141.05,1.9558,N/A,24.753,7.4391,N/A,0.86648,401.11,N/A,N/A,N/A,4.7168,N/A,4.9457,10.7783,N/A,N/A,1.0027,138.7,10.3651,7.531,N/A,N/A,17.4608,1.5382,5.5117,1.3492,6.987,8.1801,15621.64,3.6717,82.3747,1352.58,21.115,4.5943,1.6929,57.452,1.4565,37.186,17.1323,
|
||||
2022-06-30,1.0387,141.54,1.9558,N/A,24.739,7.4392,N/A,0.8582,397.04,N/A,N/A,N/A,4.6904,N/A,4.9464,10.73,N/A,N/A,0.996,138.9,10.3485,7.5307,N/A,N/A,17.322,1.5099,5.4229,1.3425,6.9624,8.1493,15552,3.6392,82.113,1351.6,20.9641,4.5781,1.6705,57.15,1.4483,36.754,17.0143,
|
||||
2022-06-29,1.0517,143.53,1.9558,N/A,24.739,7.4392,N/A,0.86461,394.28,N/A,N/A,N/A,4.6869,N/A,4.9419,10.6848,N/A,N/A,1.0005,139.9,10.3065,7.5285,N/A,N/A,17.4998,1.5256,5.5163,1.3513,7.0382,8.2532,15612.61,3.6344,83.037,1364.02,21.1375,4.6272,1.6871,57.773,1.4607,36.925,16.9295,
|
||||
2022-06-28,1.0561,143.67,1.9558,N/A,24.726,7.4394,N/A,0.8635,398.55,N/A,N/A,N/A,4.6905,N/A,4.9443,10.6543,N/A,N/A,1.0101,139.5,10.337,7.532,N/A,N/A,17.5891,1.521,5.5308,1.3565,7.0775,8.288,15669.91,3.6267,83.408,1361.75,21.088,4.6432,1.6822,57.85,1.4645,37.154,16.9072,
|
||||
2022-06-27,1.0572,143.25,1.9558,N/A,24.724,7.4408,N/A,0.862,402.62,N/A,N/A,N/A,4.699,N/A,4.944,10.6713,N/A,N/A,1.0143,139.7,10.408,7.5333,N/A,N/A,17.4794,1.5278,5.5446,1.3639,7.0737,8.2946,15635.36,3.6009,82.9325,1357.68,21.041,4.6559,1.6801,57.9,1.4641,37.361,16.7967,
|
||||
2022-06-24,1.0524,142.19,1.9558,N/A,24.731,7.4398,N/A,0.85773,401.34,N/A,N/A,N/A,4.7023,N/A,4.9463,10.694,N/A,N/A,1.0072,139.7,10.4345,7.5295,N/A,N/A,18.2856,1.5248,5.4851,1.3657,7.0478,8.2609,15633.96,3.621,82.3985,1364.09,20.9901,4.6327,1.6731,57.83,1.462,37.36,16.7137,
|
||||
2022-06-23,1.0493,142.11,1.9558,N/A,24.75,7.4388,N/A,0.85818,399.6,N/A,N/A,N/A,4.7085,N/A,4.9468,10.705,N/A,N/A,1.013,139.9,10.475,7.5286,N/A,N/A,18.2199,1.5212,5.4515,1.36,7.0367,8.236,15594.75,3.6192,82.1489,1367.21,21.0924,4.623,1.6713,57.44,1.4589,37.261,16.817,
|
||||
2022-06-22,1.0521,143.11,1.9558,N/A,24.712,7.4387,N/A,0.85885,396,N/A,N/A,N/A,4.6905,N/A,4.9467,10.6688,N/A,N/A,1.0153,138.7,10.5045,7.5228,N/A,N/A,18.255,1.5254,5.4349,1.366,7.0604,8.2589,15618.98,3.6432,82.4075,1369.29,21.1491,4.6345,1.6835,57.174,1.4615,37.281,16.7985,
|
||||
2022-06-21,1.055,143.75,1.9558,N/A,24.69,7.4393,N/A,0.8601,396.48,N/A,N/A,N/A,4.6435,N/A,4.9462,10.646,N/A,N/A,1.0214,138.7,10.3283,7.5205,N/A,N/A,18.3049,1.5177,5.442,1.366,7.068,8.2817,15639.62,3.6505,82.423,1365.09,21.2485,4.6399,1.6675,57.304,1.4612,37.294,16.7881,
|
||||
2022-06-20,1.0517,141.94,1.9558,N/A,24.728,7.4387,N/A,0.85748,397.85,N/A,N/A,N/A,4.652,N/A,4.9453,10.6375,N/A,N/A,1.0162,137.3,10.4085,7.5175,N/A,N/A,18.2239,1.5061,5.4117,1.3662,7.0346,8.2558,15589.89,3.639,81.994,1357.54,21.3016,4.6291,1.6549,56.872,1.4589,37.157,16.8603,
|
||||
2022-06-17,1.0486,141.21,1.9558,N/A,24.742,7.4384,N/A,0.855,400.53,N/A,N/A,N/A,4.7003,N/A,4.9469,10.6748,N/A,N/A,1.0105,137.7,10.4525,7.5155,N/A,N/A,18.1495,1.5039,5.3824,1.3631,7.0308,8.2314,15537.05,3.6112,81.871,1356.27,21.4474,4.6159,1.6601,56.371,1.4547,36.974,16.7133,
|
||||
2022-06-16,1.04,138.24,1.9558,N/A,24.742,7.4386,N/A,0.8555,398.1,N/A,N/A,N/A,4.7138,N/A,4.9443,10.6942,N/A,N/A,1.0142,137.5,10.4588,7.5245,N/A,N/A,18.0126,1.4939,5.2559,1.3446,6.9844,8.1638,15427.31,3.5934,81.1945,1346.88,21.4115,4.5786,1.6608,55.7,1.4451,36.566,16.6052,
|
||||
2022-06-15,1.0431,140.49,1.9558,N/A,24.703,7.4392,N/A,0.86328,397.96,N/A,N/A,N/A,4.669,N/A,4.9427,10.6278,N/A,N/A,1.0435,137.5,10.3868,7.5245,N/A,N/A,18.0465,1.5051,5.3164,1.3498,7.0013,8.1883,15361.97,3.6007,81.5142,1346.86,21.4763,4.6037,1.6706,55.627,1.4519,36.529,16.7111,
|
||||
2022-06-14,1.0452,140.62,1.9558,N/A,24.749,7.4403,N/A,0.86578,398.68,N/A,N/A,N/A,4.6563,N/A,4.9443,10.622,N/A,N/A,1.0394,138.3,10.3945,7.5238,N/A,N/A,18.06,1.5174,5.3329,1.3522,7.0417,8.2048,15400.02,3.6208,81.559,1346.72,21.4832,4.6224,1.6755,55.669,1.4541,36.566,16.7959,
|
||||
2022-06-13,1.0455,140.51,1.9558,N/A,24.724,7.4397,N/A,0.8585,399.3,N/A,N/A,N/A,4.6373,N/A,4.9459,10.616,N/A,N/A,1.0375,138.7,10.3222,7.5215,N/A,N/A,18.0495,1.4998,5.2785,1.3435,7.0434,8.2071,15376.17,3.5994,81.606,1349.93,21.2102,4.6195,1.6635,55.72,1.4538,36.425,16.807,
|
||||
2022-06-10,1.0578,141.69,1.9558,N/A,24.705,7.4389,N/A,0.85048,398.48,N/A,N/A,N/A,4.6053,N/A,4.9442,10.5255,N/A,N/A,1.0404,137.7,10.1495,7.5225,N/A,N/A,18.0116,1.4845,5.1718,1.3484,7.0868,8.3031,15393.27,3.5626,82.3355,1344.25,20.8285,4.6564,1.6482,56.101,1.462,36.774,16.5209,
|
||||
2022-06-09,1.0743,143.93,1.9558,N/A,24.689,7.4391,N/A,0.85653,396.45,N/A,N/A,N/A,4.5925,N/A,4.9453,10.5045,N/A,N/A,1.0495,138.7,10.1818,7.5223,N/A,N/A,18.5104,1.4985,5.2506,1.3506,7.1722,8.4317,15646.25,3.5859,83.526,1348.99,21.0248,4.7199,1.6673,56.872,1.4779,37.079,16.4132,
|
||||
2022-06-08,1.0739,143.92,1.9558,N/A,24.622,7.4386,N/A,0.85575,391.25,N/A,N/A,N/A,4.5698,N/A,4.945,10.4938,N/A,N/A,1.0486,138.9,10.1395,7.5215,N/A,N/A,18.453,1.4917,5.2447,1.3467,7.1785,8.4275,15577.86,3.5848,83.414,1349.34,21.0458,4.7187,1.6644,56.799,1.4769,37.076,16.4626,
|
||||
2022-06-07,1.0662,141.66,1.9558,N/A,24.739,7.4395,N/A,0.85365,389.33,N/A,N/A,N/A,4.5813,N/A,4.9426,10.5039,N/A,N/A,1.0423,138.9,10.1843,7.5244,N/A,N/A,17.8702,1.4884,5.1256,1.3437,7.1146,8.3656,15412.37,3.5661,82.873,1340.75,20.8435,4.6865,1.6582,56.421,1.4685,36.768,16.4059,
|
||||
2022-06-06,1.0726,140.16,1.9558,N/A,24.715,7.439,N/A,0.85415,388.05,N/A,N/A,N/A,4.5808,N/A,4.9424,10.452,N/A,N/A,1.032,138.3,10.0853,7.5222,N/A,N/A,17.796,1.4842,5.0986,1.3463,7.1223,8.4154,15464.73,3.5697,83.245,1341.13,20.9078,4.7076,1.6428,56.67,1.4732,36.774,16.4142,
|
||||
2022-06-03,1.073,139.59,1.9558,N/A,24.708,7.4388,N/A,0.8542,394.78,N/A,N/A,N/A,4.5955,N/A,4.9428,10.4589,N/A,N/A,1.0296,137.9,10.103,7.522,N/A,N/A,17.738,1.4805,5.1643,1.3484,7.1465,8.4167,15498.1,3.5751,83.273,1337.4,20.985,4.7094,1.6409,56.738,1.4741,36.777,16.6153,
|
||||
2022-06-02,1.0692,138.72,1.9558,N/A,24.702,7.4391,N/A,0.85195,394.9,N/A,N/A,N/A,4.5787,N/A,4.9398,10.4705,N/A,N/A,1.0264,136.9,10.0845,7.5325,N/A,N/A,17.6175,1.4829,5.1335,1.352,7.135,8.3896,15481.86,3.5705,82.922,1334.06,20.9831,4.6949,1.6413,56.467,1.4701,36.754,16.6143,
|
||||
2022-06-01,1.0712,138.68,1.9558,N/A,24.748,7.4393,N/A,0.85158,395.03,N/A,N/A,N/A,4.5913,N/A,4.9428,10.4758,N/A,N/A,1.0305,137.1,10.0438,7.5345,N/A,N/A,17.6223,1.4861,5.0646,1.3536,7.1586,8.4057,15574.33,3.5682,83.051,1331.44,21.0678,4.6951,1.6442,56.182,1.47,36.801,16.609,
|
||||
2022-05-31,1.0713,137.36,1.9558,N/A,24.714,7.4394,N/A,0.85138,396.2,N/A,N/A,N/A,4.5805,N/A,4.9408,10.5053,N/A,N/A,1.0281,136.3,10.0983,7.541,N/A,N/A,17.5817,1.4933,5.0965,1.3573,7.1402,8.4063,15580.15,3.5746,83.231,1329.32,20.987,4.6907,1.6459,56.323,1.4687,36.751,16.745,
|
||||
2022-05-30,1.0764,137.25,1.9558,N/A,24.712,7.4391,N/A,0.8515,392.18,N/A,N/A,N/A,4.5855,N/A,4.9441,10.518,N/A,N/A,1.0327,136.9,10.1256,7.5305,N/A,N/A,17.6416,1.4982,5.0629,1.3647,7.1735,8.449,15682.41,3.5722,83.475,1331.66,20.8994,4.6998,1.6439,56.322,1.4719,36.7,16.648,
|
||||
2022-05-27,1.0722,136.05,1.9558,N/A,24.7,7.4392,N/A,0.84875,392.83,N/A,N/A,N/A,4.5858,N/A,4.9427,10.5293,N/A,N/A,1.0258,137.9,10.179,7.5379,N/A,N/A,17.582,1.4995,5.0959,1.3661,7.1831,8.4165,15583.97,3.597,83.1915,1343.63,21.136,4.6952,1.6426,56.02,1.4679,36.589,16.746,
|
||||
2022-05-26,1.0697,135.95,1.9558,N/A,24.676,7.4409,N/A,0.85073,391.72,N/A,N/A,N/A,4.6083,N/A,4.9423,10.5983,N/A,N/A,1.0283,138.1,10.2715,7.5355,N/A,N/A,17.5588,1.511,5.1741,1.3715,7.2024,8.397,15628.91,3.5935,83.0065,1352.69,21.1935,4.7045,1.6541,55.975,1.4709,36.589,16.9312,
|
||||
2022-05-25,1.0656,135.34,1.9558,N/A,24.648,7.4405,N/A,0.85295,388.25,N/A,N/A,N/A,4.621,N/A,4.9416,10.5419,N/A,N/A,1.0269,138.3,10.2704,7.5355,N/A,N/A,17.3954,1.5126,5.1736,1.372,7.1334,8.3647,15587.59,3.585,82.6666,1354.61,21.2213,4.6833,1.6539,55.787,1.4676,36.55,16.7628,
|
||||
2022-05-24,1.072,136.49,1.9558,N/A,24.663,7.4411,N/A,0.8575,383.33,N/A,N/A,N/A,4.6015,N/A,4.9446,10.5013,N/A,N/A,1.0334,139.3,10.289,7.5285,N/A,N/A,17.2572,1.5152,5.1793,1.3714,7.1449,8.4143,15711.88,3.5848,83.185,1353.65,21.2456,4.7076,1.6656,56.152,1.4722,36.609,16.7814,
|
||||
2022-05-23,1.0659,136.05,1.9558,N/A,24.594,7.4413,N/A,0.84783,381.65,N/A,N/A,N/A,4.621,N/A,4.947,10.4918,N/A,N/A,1.031,139.1,10.252,7.5275,N/A,N/A,16.8672,1.4982,5.1623,1.3626,7.085,8.3664,15609,3.5745,82.6795,1344.19,21.1273,4.6782,1.6463,55.686,1.4639,36.41,16.7437,
|
||||
2022-05-20,1.0577,135.34,1.9558,N/A,24.67,7.4424,N/A,0.8482,382.93,N/A,N/A,N/A,4.6365,N/A,4.9477,10.4915,N/A,N/A,1.028,138.5,10.262,7.5335,N/A,N/A,16.8201,1.498,5.1989,1.3526,7.0638,8.2999,15501.99,3.533,82.1617,1340.58,21.0314,4.6422,1.6518,55.181,1.4588,36.284,16.7131,
|
||||
2022-05-19,1.0525,134.46,1.9558,N/A,24.7,7.4423,N/A,0.84728,385.83,N/A,N/A,N/A,4.6423,N/A,4.9474,10.5098,N/A,N/A,1.0265,139.5,10.3102,7.5395,N/A,N/A,16.8037,1.5036,5.2094,1.349,7.1028,8.2594,15416.76,3.5623,81.7115,1343.21,21.0043,4.6363,1.6551,55.14,1.4576,36.343,16.8315,
|
||||
2022-05-18,1.0523,135.76,1.9558,N/A,24.647,7.4419,N/A,0.8467,382.88,N/A,N/A,N/A,4.6443,N/A,4.9473,10.4675,N/A,N/A,1.0486,138.9,10.2125,7.535,N/A,N/A,16.7811,1.498,5.1974,1.3488,7.0972,8.2591,15446,3.526,81.6455,1332.76,20.9204,4.6254,1.6548,55.077,1.4598,36.399,16.7313,
|
||||
2022-05-17,1.0541,136.32,1.9558,N/A,24.712,7.4414,N/A,0.844,386.3,N/A,N/A,N/A,4.6488,N/A,4.9478,10.4393,N/A,N/A,1.0457,138.7,10.178,7.5245,N/A,N/A,16.6027,1.4993,5.2621,1.3517,7.0899,8.2744,15433.23,3.5387,81.6515,1333.66,21.0273,4.6211,1.6561,55.137,1.4589,36.361,16.844,
|
||||
2022-05-16,1.0422,135.01,1.9558,N/A,24.71,7.4418,N/A,0.85045,385.85,N/A,N/A,N/A,4.6675,N/A,4.9469,10.4978,N/A,N/A,1.0479,138.3,10.2188,7.5225,N/A,N/A,16.3121,1.5057,5.2819,1.3473,7.0786,8.1812,15294.29,3.5474,81.081,1337.9,20.9324,4.5836,1.6601,54.705,1.4531,36.274,16.9195,
|
||||
2022-05-13,1.0385,133.91,1.9558,N/A,24.74,7.4412,N/A,0.85115,385,N/A,N/A,N/A,4.6883,N/A,4.9455,10.4905,N/A,N/A,1.0385,140.1,10.2043,7.52,N/A,N/A,16.0687,1.5067,5.3204,1.3505,7.0513,8.1522,15193.55,3.5586,80.4315,1330.83,20.988,4.5673,1.6633,54.449,1.45,36.109,16.7789,
|
||||
2022-05-12,1.0408,133.85,1.9558,N/A,24.925,7.4413,N/A,0.85293,382.2,N/A,N/A,N/A,4.668,N/A,4.947,10.5648,N/A,N/A,1.0377,139.7,10.2898,7.5235,N/A,N/A,16.0132,1.5163,5.4161,1.3569,7.0691,8.1702,15255.73,3.5981,80.667,1341.98,21.2531,4.5725,1.6692,54.589,1.4529,36.15,16.8806,
|
||||
2022-05-11,1.0553,137.07,1.9558,N/A,25.365,7.4393,N/A,0.85393,379.13,N/A,N/A,N/A,4.6575,N/A,4.947,10.526,N/A,N/A,1.0446,139.3,10.1793,7.5365,N/A,N/A,16.1851,1.5055,5.3859,1.3685,7.0893,8.2839,15308.87,3.6148,81.4935,1343.99,21.387,4.6185,1.6645,54.992,1.4622,36.492,16.9275,
|
||||
2022-05-10,1.0554,137.38,1.9558,N/A,25.014,7.4386,N/A,0.85595,380.15,N/A,N/A,N/A,4.6763,N/A,4.9458,10.6075,N/A,N/A,1.0479,139.5,10.2315,7.5385,N/A,N/A,16.0883,1.5162,5.4232,1.3707,7.0967,8.2847,15349.06,3.6587,81.5425,1346.56,21.4716,4.6248,1.6707,55.289,1.4667,36.448,17.005,
|
||||
2022-05-09,1.0559,138.1,1.9558,N/A,25.055,7.4385,N/A,0.85235,383.23,N/A,N/A,N/A,4.6985,N/A,4.9467,10.5818,N/A,N/A,1.0462,139.5,10.0583,7.5335,N/A,N/A,15.8941,1.5048,5.4321,1.3656,7.0886,8.2887,15367.67,3.6239,81.7415,1345.48,21.4341,4.6285,1.6584,55.67,1.4676,36.497,17.1332,
|
||||
2022-05-06,1.057,137.9,1.9558,N/A,24.665,7.44,N/A,0.85625,381.47,N/A,N/A,N/A,4.7028,N/A,4.949,10.4686,N/A,N/A,1.0419,138.3,9.9808,7.5336,N/A,N/A,15.8078,1.4888,5.3183,1.356,7.0506,8.2969,15312.44,3.5965,81.298,1343.9,21.3555,4.6191,1.644,55.467,1.4642,36.303,16.9614,
|
||||
2022-05-05,1.0568,137.18,1.9558,N/A,24.606,7.4405,N/A,0.8519,378.65,N/A,N/A,N/A,4.6673,N/A,4.9485,10.3748,N/A,N/A,1.0355,138,9.8463,7.5398,N/A,N/A,15.7051,1.4669,5.2192,1.3483,6.9944,8.2948,15250.92,3.5916,80.6185,1330.89,21.2069,4.5955,1.6273,55.364,1.4561,36.026,16.5862,
|
||||
2022-05-04,1.0531,136.84,1.9558,N/A,24.644,7.4409,N/A,0.84194,377.55,N/A,N/A,N/A,4.6875,N/A,4.947,10.3968,N/A,N/A,1.0324,137.2,9.9042,7.5499,N/A,N/A,15.5769,1.478,5.24,1.3498,6.9594,8.2655,15201.44,3.5487,80.4035,1331.45,21.2947,4.5847,1.6333,55.26,1.4559,36.137,16.6489,
|
||||
2022-05-03,1.0556,137.06,1.9558,N/A,24.662,7.4403,N/A,0.8413,382.15,N/A,N/A,N/A,4.6925,N/A,4.9475,10.3978,N/A,N/A,1.0272,137.6,9.909,7.5555,N/A,N/A,15.6941,1.4825,5.3143,1.357,6.9759,8.2838,15288.47,3.5597,80.842,1335.64,21.5025,4.5956,1.6366,55.455,1.4605,36.387,16.8303,
|
||||
2022-05-02,1.0524,136.63,1.9558,N/A,24.671,7.4391,N/A,0.8381,378.51,N/A,N/A,N/A,4.685,N/A,4.9478,10.4035,N/A,N/A,1.0253,137.2,9.9248,7.561,N/A,N/A,15.6697,1.4913,5.248,1.356,6.9548,8.2581,15293.41,3.5277,80.497,1333.71,21.4758,4.5816,1.6362,55.229,1.4585,36.208,16.7383,
|
||||
2022-04-29,1.054,137.01,1.9558,N/A,24.605,7.4415,N/A,0.83908,378.71,N/A,N/A,N/A,4.678,N/A,4.9479,10.2958,N/A,N/A,1.0229,137.8,9.7525,7.5667,N/A,N/A,15.6385,1.4699,5.1608,1.3426,6.9441,8.2703,15301.52,3.4993,80.638,1326.71,21.4181,4.5886,1.6119,55.2,1.4545,36.026,16.6473,
|
||||
2022-04-28,1.0485,137.13,1.9558,N/A,24.526,7.4421,N/A,0.8435,377.06,N/A,N/A,N/A,4.6891,N/A,4.9479,10.3594,N/A,N/A,1.0216,137.8,9.899,7.5703,N/A,N/A,15.5362,1.4814,5.2465,1.3498,6.9381,8.2267,15222.05,3.5096,80.367,1337.82,21.4531,4.5741,1.6221,54.845,1.4556,36.152,16.7472,
|
||||
2022-04-27,1.0583,135.57,1.9558,N/A,24.55,7.441,N/A,0.84215,379.74,N/A,N/A,N/A,4.7043,N/A,4.948,10.4035,N/A,N/A,1.0229,138.2,9.7838,7.565,N/A,N/A,15.6857,1.4828,5.3045,1.3572,6.9377,8.3045,15259.86,3.5178,81.0705,1341.98,21.6259,4.6142,1.6118,55.195,1.4602,36.331,16.8406,
|
||||
2022-04-26,1.0674,136.15,1.9558,N/A,24.423,7.4393,N/A,0.84135,374.46,N/A,N/A,N/A,4.6466,N/A,4.9458,10.3935,N/A,N/A,1.0229,138.4,9.7943,7.5625,N/A,N/A,15.7944,1.4828,5.249,1.3613,6.9837,8.3735,15365.76,3.5147,81.7265,1340.02,21.6538,4.6485,1.6102,55.681,1.4666,36.596,16.7787,
|
||||
2022-04-25,1.0746,137.73,1.9558,N/A,24.418,7.4391,N/A,0.8433,374.08,N/A,N/A,N/A,4.6398,N/A,4.9455,10.3476,N/A,N/A,1.0267,139.2,9.7018,7.562,N/A,N/A,15.864,1.4972,5.1953,1.3709,7.0398,8.4325,15533.84,3.5306,82.321,1344.49,21.8989,4.6815,1.624,56.259,1.4757,36.542,16.8549,
|
||||
2022-04-22,1.0817,138.83,1.9558,N/A,24.32,7.4402,N/A,0.83925,370.35,N/A,N/A,N/A,4.6336,N/A,4.9455,10.278,N/A,N/A,1.0336,139.8,9.6255,7.5625,N/A,N/A,15.9446,1.4816,5.0926,1.3714,7.0332,8.4859,15603.47,3.5288,82.6943,1344.04,22.0034,4.6784,1.6193,56.721,1.4784,36.724,16.8652,
|
||||
2022-04-21,1.0887,139.61,1.9558,N/A,24.38,7.4403,N/A,0.83523,370.6,N/A,N/A,N/A,4.63,N/A,4.945,10.2553,N/A,N/A,1.0335,139,9.5788,7.5635,N/A,N/A,15.9983,1.4653,5.0324,1.36,7.0228,8.5406,15624.02,3.5153,82.965,1348.33,21.8836,4.6716,1.6053,57.081,1.482,36.891,16.5996,
|
||||
2022-04-20,1.083,138.53,1.9558,N/A,24.409,7.4405,N/A,0.82965,371.36,N/A,N/A,N/A,4.6338,N/A,4.9436,10.23,N/A,N/A,1.0254,139.2,9.5443,7.561,N/A,N/A,15.8892,1.4581,5.0481,1.3579,6.9448,8.494,15537.05,3.4908,82.6348,1337.89,21.6392,4.6415,1.595,56.747,1.4779,36.567,16.3019,
|
||||
2022-04-19,1.0803,138.4,1.9558,N/A,24.424,7.4391,N/A,0.82955,374.12,N/A,N/A,N/A,4.6553,N/A,4.9411,10.3408,N/A,N/A,1.0208,139.8,9.5228,7.562,N/A,N/A,15.8416,1.4663,5.0261,1.3631,6.9008,8.4698,15498.35,3.5038,82.6038,1339.46,21.4725,4.5961,1.6016,56.683,1.4763,36.466,16.0401,
|
||||
2022-04-14,1.0878,136.32,1.9558,N/A,24.42,7.4389,N/A,0.82908,376.57,N/A,N/A,N/A,4.6478,N/A,4.9459,10.3008,N/A,N/A,1.0189,140.4,9.5313,7.5587,N/A,N/A,15.9046,1.4612,5.1226,1.3663,6.932,8.5298,15621.3,3.4896,82.814,1334.71,21.5941,4.603,1.5957,56.759,1.4732,36.615,15.9331,
|
||||
2022-04-13,1.0826,136.26,1.9558,N/A,24.45,7.4377,N/A,0.8328,378.45,N/A,N/A,N/A,4.6453,N/A,4.9415,10.3323,N/A,N/A,1.0116,140.2,9.5693,7.5538,N/A,N/A,15.7992,1.4603,5.0449,1.37,6.8939,8.4867,15549.1,3.4782,82.478,1328.47,21.417,4.5799,1.5991,56.446,1.4769,36.305,15.682,
|
||||
2022-04-12,1.0861,136.29,1.9558,N/A,24.45,7.4379,N/A,0.83455,377.78,N/A,N/A,N/A,4.6552,N/A,4.9417,10.332,N/A,N/A,1.0131,139.6,9.5395,7.5513,N/A,N/A,15.9548,1.4599,5.0944,1.3724,6.9199,8.5112,15609.66,3.4972,82.7285,1335.49,21.5616,4.5972,1.5874,56.574,1.4803,36.531,15.849,
|
||||
2022-04-11,1.09,137.01,1.9558,N/A,24.429,7.4375,N/A,0.83693,378.27,N/A,N/A,N/A,4.6456,N/A,4.9397,10.3128,N/A,N/A,1.018,140,9.5478,7.5519,N/A,N/A,16.0485,1.4654,5.155,1.3738,6.9405,8.544,15658.28,3.501,82.7085,1345.23,21.8653,4.6112,1.5938,56.753,1.4874,36.613,15.9127,
|
||||
2022-04-08,1.0861,134.87,1.9558,N/A,24.479,7.4372,N/A,0.83355,375.66,N/A,N/A,N/A,4.6437,N/A,4.9425,10.2768,N/A,N/A,1.0155,139.6,9.508,7.549,N/A,N/A,16.0237,1.4552,5.1583,1.3675,6.9115,8.5134,15601.96,3.501,82.389,1333.12,21.8729,4.585,1.5849,55.99,1.4801,36.488,15.9968,
|
||||
2022-04-07,1.0916,135.32,1.9558,N/A,24.512,7.4378,N/A,0.8345,379.26,N/A,N/A,N/A,4.637,N/A,4.9419,10.313,N/A,N/A,1.0185,141,9.5595,7.5562,N/A,N/A,16.0929,1.4578,5.146,1.3704,6.9448,8.5554,15692.35,3.5259,82.951,1330.92,21.9806,4.6046,1.5816,56.114,1.4848,36.541,16.052,
|
||||
2022-04-06,1.0923,135.3,1.9558,N/A,24.441,7.4378,N/A,0.83473,377.77,N/A,N/A,N/A,4.6328,N/A,4.9433,10.2855,N/A,N/A,1.0187,141.4,9.5523,7.547,N/A,N/A,16.0998,1.4431,5.0996,1.3647,6.9498,8.5617,15683.36,3.5199,82.8343,1330.44,21.8759,4.604,1.5718,56.167,1.4844,36.701,15.9934,
|
||||
2022-04-05,1.0969,134.76,1.9558,N/A,24.338,7.4378,N/A,0.8349,370.93,N/A,N/A,N/A,4.6265,N/A,4.9438,10.2593,N/A,N/A,1.0141,141.6,9.5398,7.5399,N/A,N/A,16.15,1.4374,5.0384,1.3647,6.9783,8.5917,15732.77,3.5152,82.635,1330.81,21.7474,4.6185,1.5657,56.194,1.4867,36.697,15.9529,
|
||||
2022-04-04,1.1005,135.08,1.9558,N/A,24.32,7.4385,N/A,0.8389,369.15,N/A,N/A,N/A,4.6375,N/A,4.9432,10.3849,N/A,N/A,1.0203,141.8,9.5489,7.5455,N/A,N/A,16.183,1.4651,5.1162,1.3749,7.0026,8.6226,15783.89,3.5312,83.118,1338.41,21.82,4.643,1.586,56.521,1.4938,36.894,16.0957,
|
||||
2022-04-01,1.1052,135.35,1.9558,N/A,24.376,7.4388,N/A,0.84145,368.12,N/A,N/A,N/A,4.6401,N/A,4.9452,10.332,N/A,N/A,1.0217,142,9.6628,7.5675,N/A,N/A,16.2411,1.4696,5.2188,1.3805,7.0311,8.6596,15887.5,3.5315,83.9847,1345.61,21.9087,4.6534,1.5911,57.084,1.4985,36.941,16.1685,
|
||||
2022-03-31,1.1101,135.17,1.9558,N/A,24.375,7.4379,N/A,0.84595,369.77,N/A,N/A,N/A,4.6531,N/A,4.9463,10.337,N/A,N/A,1.0267,142,9.711,7.574,N/A,N/A,16.2823,1.4829,5.3009,1.3896,7.0403,8.6918,15947,3.5243,84.134,1347.37,22.0903,4.6677,1.6014,57.514,1.5028,36.911,16.1727,
|
||||
2022-03-30,1.1126,135.47,1.9558,N/A,24.45,7.4391,N/A,0.84563,368.13,N/A,N/A,N/A,4.6679,N/A,4.9477,10.3498,N/A,N/A,1.0309,142.2,9.6398,7.572,N/A,N/A,16.3296,1.4809,5.2808,1.3891,7.0666,8.7081,15957.24,3.5399,84.38,1346.97,22.1557,4.6779,1.5947,57.906,1.5064,37.144,16.1288,
|
||||
2022-03-29,1.1085,136.66,1.9558,N/A,24.464,7.4388,N/A,0.8444,369.8,N/A,N/A,N/A,4.6594,N/A,4.9478,10.329,N/A,N/A,1.0362,142.2,9.5995,7.5815,N/A,N/A,16.3275,1.4795,5.2434,1.387,7.055,8.6767,15896.31,3.5505,83.9685,1345.62,22.1561,4.6707,1.6054,57.602,1.5051,37.29,16.1804,
|
||||
2022-03-28,1.0966,135.93,1.9558,N/A,24.65,7.4393,N/A,0.83643,374.13,N/A,N/A,N/A,4.718,N/A,4.9483,10.4225,N/A,N/A,1.0257,142.8,9.5123,7.5735,N/A,N/A,16.275,1.459,5.2133,1.3702,6.9862,8.5861,15737.77,3.5313,83.4825,1342.49,21.9841,4.6238,1.5838,57.08,1.4921,37.027,15.9925,
|
||||
2022-03-25,1.1002,134.07,1.9558,N/A,24.645,7.4404,N/A,0.8338,373.81,N/A,N/A,N/A,4.7307,N/A,4.9487,10.3505,N/A,N/A,1.0207,142.2,9.5205,7.5754,N/A,N/A,16.3304,1.4624,5.2634,1.3781,7.0007,8.6117,15777.69,3.5351,83.8235,1343.32,21.9908,4.6324,1.5787,57.322,1.4919,36.906,16.0386,
|
||||
2022-03-24,1.0978,133.71,1.9558,N/A,24.72,7.4397,N/A,0.83288,374.44,N/A,N/A,N/A,4.7421,N/A,4.9489,10.3555,N/A,N/A,1.0225,141.2,9.4923,7.5745,N/A,N/A,16.2917,1.4668,5.3057,1.3806,6.9933,8.5897,15778.31,3.5461,83.879,1342.53,22.177,4.6396,1.5812,57.426,1.4912,36.837,16.1478,
|
||||
2022-03-23,1.0985,132.65,1.9558,N/A,24.605,7.4381,N/A,0.8328,372.25,N/A,N/A,N/A,4.7052,N/A,4.9463,10.4005,N/A,N/A,1.0269,141.4,9.6425,7.571,N/A,N/A,16.3108,1.4728,5.3903,1.384,7.0003,8.5948,15792.11,3.5407,83.9675,1337.76,22.1978,4.6401,1.5822,57.565,1.4919,36.965,16.2501,
|
||||
2022-03-22,1.1024,132.96,1.9558,N/A,24.679,7.4402,N/A,0.83228,371.23,N/A,N/A,N/A,4.6851,N/A,4.9463,10.3822,N/A,N/A,1.0275,142.7,9.6233,7.575,N/A,N/A,16.3432,1.4802,5.4105,1.3867,7.0137,8.6285,15808.01,3.5521,83.9145,1343.81,22.3667,4.6483,1.586,57.749,1.4957,36.881,16.343,
|
||||
2022-03-21,1.1038,131.57,1.9558,N/A,24.683,7.4411,N/A,0.83775,374.48,N/A,N/A,N/A,4.696,N/A,4.947,10.4088,N/A,N/A,1.0278,142.7,9.6575,7.5733,N/A,N/A,16.3773,1.4897,5.502,1.3898,7.0152,8.6387,15826.61,3.5637,84.1835,1342.62,22.48,4.6415,1.5997,57.817,1.496,37.038,16.4543,
|
||||
2022-03-18,1.1008,131.4,1.9558,N/A,24.837,7.4423,N/A,0.83925,375.33,N/A,N/A,N/A,4.7135,N/A,4.9483,10.4303,N/A,N/A,1.0314,142.9,9.694,7.5685,N/A,N/A,16.3054,1.4945,5.5784,1.3911,7.0031,8.6101,15782.11,3.5761,83.7825,1337.31,22.5905,4.6157,1.6026,57.622,1.4952,36.745,16.5347,
|
||||
2022-03-17,1.1051,131.27,1.9558,N/A,24.777,7.4439,N/A,0.84315,372.05,N/A,N/A,N/A,4.6889,N/A,4.9465,10.4503,N/A,N/A,1.0385,142.1,9.78,7.573,N/A,N/A,16.3123,1.5055,5.6339,1.3998,7.0176,8.6391,15835.97,3.5777,83.8435,1340.02,22.788,4.6364,1.613,57.69,1.498,36.767,16.5286,
|
||||
2022-03-16,1.0994,130.05,1.9558,N/A,24.687,7.4412,N/A,0.83988,371.18,N/A,N/A,N/A,4.6765,N/A,4.9473,10.4205,N/A,N/A,1.0336,143.5,9.7988,7.5725,N/A,N/A,16.1783,1.5165,5.6523,1.3967,6.9817,8.5996,15690.36,3.5872,83.7805,1353.77,22.854,4.6147,1.6168,57.433,1.4966,36.681,16.5574,
|
||||
2022-03-15,1.0991,129.67,1.9558,N/A,24.867,7.441,N/A,0.84053,371.41,N/A,N/A,N/A,4.7355,N/A,4.9482,10.526,N/A,N/A,1.0322,144.9,9.849,7.575,N/A,N/A,16.0968,1.5234,5.6385,1.4099,7.0117,8.6026,15710.44,3.6088,83.9555,1366.05,22.9352,4.6239,1.6216,57.536,1.4993,36.842,16.6249,
|
||||
2022-03-14,1.096,129.3,1.9558,N/A,24.89,7.4405,N/A,0.83915,373.88,N/A,N/A,N/A,4.7218,N/A,4.949,10.5368,N/A,N/A,1.0249,145.1,9.8588,7.5745,N/A,N/A,16.2,1.5137,5.5286,1.3978,6.9738,8.5815,15678.53,3.586,83.931,1357.77,22.8311,4.6087,1.613,57.398,1.4947,36.59,16.5029,
|
||||
2022-03-11,1.099,128.46,1.9558,N/A,25.213,7.4402,N/A,0.8397,380.92,N/A,N/A,N/A,4.782,N/A,4.949,10.646,N/A,N/A,1.023,144.9,9.8033,7.5713,N/A,N/A,16.2554,1.5017,5.5077,1.4024,6.9633,8.6007,15696.64,3.5683,83.9875,1354.04,22.9524,4.6098,1.6053,57.461,1.4949,36.542,16.4896,
|
||||
2022-03-10,1.1084,128.54,1.9558,N/A,25.316,7.4401,N/A,0.84175,381.63,N/A,N/A,N/A,4.8239,N/A,4.9491,10.7073,N/A,N/A,1.027,145.5,9.919,7.5665,N/A,N/A,16.574,1.5109,5.5958,1.4189,7.0063,8.6688,15824.95,3.6219,84.607,1360.48,23.3153,4.6414,1.6185,57.825,1.5058,36.71,16.7264,
|
||||
2022-03-09,1.0993,127.31,1.9558,N/A,25.364,7.444,N/A,0.8357,379.66,N/A,N/A,N/A,4.8196,N/A,4.9485,10.734,N/A,N/A,1.0198,145.3,9.798,7.5625,N/A,N/A,16.1323,1.4991,5.5201,1.4108,6.9454,8.5974,15710.06,3.5978,84.2025,1357.08,23.2145,4.6028,1.6055,57.259,1.4966,36.326,16.656,
|
||||
2022-03-08,1.0892,126.03,1.9558,N/A,25.642,7.4441,N/A,0.83185,388.28,N/A,N/A,N/A,4.9103,N/A,4.9494,10.8803,N/A,N/A,1.0111,145.9,9.7925,7.5715,N/A,N/A,15.8183,1.4971,5.5346,1.3978,6.8805,8.5183,15639.76,3.6022,83.924,1344.71,23.2866,4.5556,1.5958,56.9,1.4856,36.156,16.7051,
|
||||
2022-03-07,1.0895,125.55,1.9558,N/A,25.584,7.4406,N/A,0.82625,393.25,N/A,N/A,N/A,4.9525,N/A,4.9494,10.8573,N/A,N/A,1.0069,145.8,9.8325,7.56,N/A,N/A,15.6577,1.4751,5.5065,1.3864,6.8846,8.5154,15685.76,3.5653,83.8125,1338.45,23.0249,4.5426,1.5861,56.832,1.4831,35.866,16.6951,
|
||||
2022-03-04,1.0929,126.17,1.9558,N/A,25.737,7.4394,N/A,0.82388,386.54,N/A,N/A,N/A,4.853,N/A,4.9495,10.7935,N/A,N/A,1.0056,144.2,9.8358,7.5584,N/A,N/A,15.5681,1.4872,5.5313,1.3937,6.9065,8.5411,15725.3,3.5603,83.4354,1332.23,22.7543,4.5661,1.6005,56.814,1.4872,35.776,16.8044,
|
||||
2022-03-03,1.1076,128.18,1.9558,N/A,25.634,7.4399,N/A,0.82773,378.64,N/A,N/A,N/A,4.7691,N/A,4.9496,10.7688,N/A,N/A,1.0192,143.4,9.8418,7.57,N/A,N/A,15.6897,1.5139,5.6041,1.3992,6.9996,8.6547,15934.42,3.5872,84.174,1334.2,22.8945,4.637,1.6329,57.293,1.5042,36.063,16.8798,
|
||||
2022-03-02,1.1106,128.08,1.9558,N/A,25.866,7.4387,N/A,0.83316,382.31,N/A,N/A,N/A,4.8021,N/A,4.9493,10.788,N/A,N/A,1.0216,143,9.8826,7.574,N/A,N/A,15.628,1.5272,5.7313,1.4088,7.0153,8.6785,15980.03,3.5954,84.1765,1338.99,22.999,4.6595,1.6404,57.206,1.5059,36.339,17.1904,
|
||||
2022-03-01,1.1162,128.15,1.9558,N/A,25.465,7.4377,N/A,0.8329,379.6,N/A,N/A,N/A,4.7947,N/A,4.949,10.6893,N/A,N/A,1.0247,142,9.8598,7.567,117.201,N/A,15.5509,1.5365,5.7598,1.4158,7.0462,8.7234,16033.36,3.6152,84.5015,1342.6,22.8558,4.6802,1.6484,57.295,1.515,36.505,17.2145,
|
||||
2022-02-28,1.1199,129.31,1.9558,N/A,24.997,7.4404,N/A,0.8355,369.72,N/A,N/A,N/A,4.6835,N/A,4.9484,10.6055,N/A,N/A,1.0336,141.8,9.9465,7.5655,115.4842,N/A,15.4532,1.5508,5.7828,1.4264,7.067,8.7514,16100.72,3.63,84.554,1347.62,22.9011,4.7019,1.6628,57.432,1.5201,36.593,17.2863,
|
||||
2022-02-25,1.1216,129.64,1.9558,N/A,24.66,7.4418,N/A,0.8374,365.28,N/A,N/A,N/A,4.6369,N/A,4.9479,10.5848,N/A,N/A,1.0398,140.8,9.9756,7.5535,92.5673,N/A,15.4799,1.5541,5.738,1.4325,7.0828,8.7578,16088.71,3.6389,84.347,1346.44,22.9145,4.7107,1.6651,57.549,1.5176,36.441,17.0315,
|
||||
2022-02-24,1.1163,128.28,1.9558,N/A,25.09,7.4405,N/A,0.83463,368.63,N/A,N/A,N/A,4.6554,N/A,4.9501,10.7338,N/A,N/A,1.032,142,10.0878,7.552,95.7175,N/A,16.0525,1.5593,5.6874,1.4316,7.0601,8.7178,16074,3.6618,84.296,1347.7,22.9355,4.6896,1.6692,57.45,1.5125,36.514,17.1634,
|
||||
2022-02-23,1.1344,130.58,1.9558,N/A,24.473,7.4388,N/A,0.83463,357.25,N/A,N/A,N/A,4.5481,N/A,4.9468,10.5658,N/A,N/A,1.0431,141.2,10.0335,7.5362,90.8791,N/A,15.6871,1.5592,5.6808,1.4394,7.1669,8.8529,16270.12,3.6515,84.6135,1350.23,22.9079,4.748,1.6679,57.98,1.5253,36.601,17.0508,
|
||||
2022-02-22,1.1342,130.54,1.9558,N/A,24.496,7.4392,N/A,0.83685,355.89,N/A,N/A,N/A,4.5447,N/A,4.9464,10.5996,N/A,N/A,1.0422,141.2,10.1018,7.537,89.8055,N/A,15.6959,1.5739,5.7677,1.4441,7.1771,8.8495,16293.92,3.6584,84.758,1353.34,23.033,4.7472,1.6848,58.208,1.5268,36.748,17.1768,
|
||||
2022-02-21,1.1338,130.2,1.9558,N/A,24.345,7.4397,N/A,0.83298,357.54,N/A,N/A,N/A,4.5351,N/A,4.9448,10.6535,N/A,N/A,1.0387,141.4,10.1738,7.536,89.0866,N/A,15.4689,1.5751,5.8045,1.4454,7.1831,8.8443,16276.34,3.6433,84.677,1353.02,22.9951,4.7387,1.687,58.3,1.5265,36.588,17.1895,
|
||||
2022-02-18,1.1354,130.59,1.9558,N/A,24.337,7.4382,N/A,0.83425,356.37,N/A,N/A,N/A,4.5201,N/A,4.9453,10.5796,N/A,N/A,1.0452,140.8,10.1465,7.5355,86.2815,N/A,15.4678,1.5754,5.8435,1.4424,7.184,8.8566,16304.49,3.6276,84.6525,1356.45,23.027,4.7528,1.6896,58.403,1.5255,36.435,17.0858,
|
||||
2022-02-17,1.137,130.84,1.9558,N/A,24.383,7.4398,N/A,0.83493,356.08,N/A,N/A,N/A,4.5065,N/A,4.9432,10.593,N/A,N/A,1.0466,141.6,10.1225,7.533,86.388,N/A,15.4945,1.5786,5.8495,1.4439,7.206,8.8692,16291.53,3.6231,85.2935,1360.7,23.0367,4.7612,1.695,58.314,1.5278,36.537,16.9893,
|
||||
2022-02-16,1.1372,131.56,1.9558,N/A,24.365,7.441,N/A,0.8394,355.65,N/A,N/A,N/A,4.4961,N/A,4.9436,10.5363,N/A,N/A,1.0516,141.4,10.1095,7.5295,85.3679,N/A,15.481,1.5859,5.8765,1.4416,7.2101,8.8712,16232.55,3.6256,85.3885,1361.31,23.1718,4.7595,1.7108,58.346,1.5291,36.771,17.214,
|
||||
2022-02-15,1.1345,131.18,1.9558,N/A,24.419,7.4422,N/A,0.83765,355.33,N/A,N/A,N/A,4.5036,N/A,4.941,10.5739,N/A,N/A,1.0483,141.2,10.0893,7.529,85.5025,N/A,15.4716,1.5888,5.8977,1.4433,7.1969,8.8527,16187.89,3.6596,85.443,1357.5,23.1113,4.7493,1.7143,58.144,1.5265,36.724,17.1767,
|
||||
2022-02-14,1.1316,130.6,1.9558,N/A,24.527,7.4411,N/A,0.8372,357.06,N/A,N/A,N/A,4.54,N/A,4.9457,10.6158,N/A,N/A,1.0472,142.6,10.0693,7.5293,86.348,N/A,15.351,1.5902,5.8965,1.4431,7.1937,8.8283,16190.53,3.6835,85.4715,1354.5,23.1331,4.742,1.7112,58.114,1.5247,36.8,17.131,
|
||||
2022-02-11,1.1417,132.24,1.9558,N/A,24.405,7.44,N/A,0.83958,353.38,N/A,N/A,N/A,4.5204,N/A,4.9458,10.553,N/A,N/A,1.0557,141.8,10.0732,7.5312,85.855,N/A,15.4066,1.5927,5.9263,1.4498,7.2564,8.9054,16339.46,3.6958,85.8535,1363.68,23.3183,4.7832,1.7085,58.482,1.5339,37.282,17.2736,
|
||||
2022-02-10,1.1439,132.42,1.9558,N/A,24.35,7.4404,N/A,0.84248,354.02,N/A,N/A,N/A,4.4921,N/A,4.9451,10.5275,N/A,N/A,1.0571,141.8,10.0693,7.5275,85.0187,N/A,15.4838,1.5894,5.9668,1.4498,7.2722,8.9142,16390.21,3.6796,85.9373,1367.52,23.3584,4.7855,1.7076,58.583,1.5345,37.331,17.3078,
|
||||
2022-02-09,1.1435,132.04,1.9558,N/A,24.288,7.4437,N/A,0.84255,352.94,N/A,N/A,N/A,4.5135,N/A,4.9449,10.4075,N/A,N/A,1.0555,142.2,10.0585,7.5285,85.5289,N/A,15.551,1.5933,6.0198,1.4514,7.2759,8.9106,16390.99,3.6817,85.5765,1365.72,23.4719,4.7853,1.7107,58.603,1.5349,37.404,17.5281,
|
||||
2022-02-08,1.1408,131.68,1.9558,N/A,24.259,7.4437,N/A,0.84363,353.09,N/A,N/A,N/A,4.5312,N/A,4.945,10.4433,N/A,N/A,1.0545,142.4,10.0758,7.5215,85.7797,N/A,15.5558,1.6025,6.0209,1.4505,7.2636,8.8923,16418.4,3.6753,85.2545,1367.88,23.5601,4.774,1.7196,58.715,1.5349,37.606,17.638,
|
||||
2022-02-07,1.1447,131.59,1.9558,N/A,24.222,7.4443,N/A,0.84685,353.48,N/A,N/A,N/A,4.5432,N/A,4.9461,10.4483,N/A,N/A,1.0571,143.4,10.0658,7.52,86.5824,N/A,15.5235,1.6097,6.0541,1.4546,7.2807,8.9202,16478.97,3.6547,85.5345,1371.76,23.575,4.7909,1.7278,58.978,1.5389,37.735,17.7,
|
||||
2022-02-04,1.1464,131.72,1.9558,N/A,24.36,7.4432,N/A,0.84593,352.92,N/A,N/A,N/A,4.5474,N/A,4.9466,10.4465,N/A,N/A,1.0567,142.8,10.0483,7.5275,87.3095,N/A,15.5072,1.6165,6.083,1.4583,7.2923,8.9286,16493.16,3.6741,85.6445,1374.04,23.5856,4.7914,1.7287,58.754,1.5419,37.797,17.5875,
|
||||
2022-02-03,1.1286,129.63,1.9558,N/A,24.135,7.4388,N/A,0.83208,353.94,N/A,N/A,N/A,4.5315,N/A,4.9461,10.387,N/A,N/A,1.0407,142.4,9.9545,7.5295,86.1788,N/A,15.3047,1.5849,5.9843,1.4334,7.1795,8.7966,16243.28,3.5949,84.518,1358.42,23.2359,4.7215,1.7001,57.608,1.5212,37.424,17.2864,
|
||||
2022-02-02,1.1323,129.37,1.9558,N/A,24.298,7.4383,N/A,0.83395,354.45,N/A,N/A,N/A,4.5449,N/A,4.9463,10.385,N/A,N/A,1.0399,143.2,9.9228,7.526,85.815,N/A,15.3011,1.5828,5.9677,1.433,7.2026,8.8245,16243.98,3.5777,84.613,1359.23,23.2134,4.7392,1.701,57.785,1.5254,37.575,17.3459,
|
||||
2022-02-01,1.126,129.12,1.9558,N/A,24.335,7.4407,N/A,0.83498,356.38,N/A,N/A,N/A,4.5804,N/A,4.9465,10.4438,N/A,N/A,1.0374,143.8,9.9638,7.5275,86.3238,N/A,15.0644,1.5868,5.9572,1.4299,7.1625,8.7779,16126.29,3.566,84.197,1353.61,23.1296,4.7129,1.7032,57.516,1.5199,37.378,17.1633,
|
||||
2022-01-31,1.1156,128.79,1.9558,N/A,24.372,7.4419,N/A,0.83153,357.19,N/A,N/A,N/A,4.5892,N/A,4.9475,10.489,N/A,N/A,1.0404,143.2,10.0085,7.5293,86.7251,N/A,14.931,1.582,6.003,1.4233,7.0963,8.6994,16036.76,3.564,83.3655,1349.08,23.1856,4.6693,1.6983,56.985,1.511,37.144,17.3734,
|
||||
2022-01-28,1.1138,128.68,1.9558,N/A,24.443,7.4432,N/A,0.83178,358.42,N/A,N/A,N/A,4.5755,N/A,4.9463,10.552,N/A,N/A,1.0378,144,10.026,7.529,86.6113,N/A,15.1424,1.5971,6.0147,1.4239,7.0857,8.681,16047.76,3.5697,83.6015,1349.47,23.1854,4.6668,1.7031,57.028,1.5109,37.229,17.3844,
|
||||
2022-01-27,1.116,128.74,1.9558,N/A,24.427,7.4428,N/A,0.83368,358.09,N/A,N/A,N/A,4.5592,N/A,4.9466,10.445,N/A,N/A,1.0391,145.2,9.9903,7.5328,87.139,N/A,15.1946,1.5771,6.0159,1.4161,7.1061,8.6951,16052.42,3.5695,83.7893,1344.5,23.143,4.6844,1.6872,57.295,1.5089,37.124,17.1112,
|
||||
2022-01-26,1.1277,128.86,1.9558,N/A,24.531,7.442,N/A,0.83458,359.67,N/A,N/A,N/A,4.5864,N/A,4.9451,10.4493,N/A,N/A,1.0386,145.6,10.0115,7.529,89.265,N/A,15.2877,1.5727,6.1084,1.4173,7.1293,8.7791,16185.31,3.5834,84.4288,1350.03,23.1845,4.7268,1.6865,57.728,1.5162,37.169,17.0858,
|
||||
2022-01-25,1.1268,128.49,1.9558,N/A,24.5,7.4437,N/A,0.83713,359.44,N/A,N/A,N/A,4.5751,N/A,4.9448,10.502,N/A,N/A,1.0364,146,10.1385,7.5295,88.7384,N/A,15.2723,1.5814,6.2049,1.4247,7.1325,8.7725,16169.54,3.5898,84.277,1350.71,23.2909,4.7213,1.6911,57.753,1.5157,37.28,17.2669,
|
||||
2022-01-24,1.1304,128.62,1.9558,N/A,24.528,7.4431,N/A,0.83803,359.84,N/A,N/A,N/A,4.5572,N/A,4.9453,10.5038,N/A,N/A,1.0308,145.6,10.1638,7.529,88.649,N/A,15.1621,1.5866,6.1901,1.4269,7.1533,8.8003,16213.69,3.5846,84.3495,1352.41,23.2566,4.7352,1.6901,58.045,1.5216,37.382,17.2509,
|
||||
2022-01-21,1.1348,129.14,1.9558,N/A,24.347,7.4431,N/A,0.83633,358.19,N/A,N/A,N/A,4.5318,N/A,4.9453,10.414,N/A,N/A,1.0353,145.6,10.0523,7.528,86.838,N/A,15.223,1.5774,6.2063,1.4211,7.1946,8.837,16244.2,3.5668,84.419,1351.89,23.2229,4.7508,1.6884,58.171,1.526,37.358,17.1546,
|
||||
2022-01-20,1.1338,129.53,1.9558,N/A,24.263,7.4424,N/A,0.83265,355.81,N/A,N/A,N/A,4.5228,N/A,4.9453,10.3708,N/A,N/A,1.0382,145.4,9.9578,7.525,86.8952,N/A,15.2094,1.5662,6.1621,1.4158,7.1936,8.8274,16267.5,3.5564,84.362,1349.16,23.1684,4.7489,1.6731,58.285,1.5261,37.319,17.2531,
|
||||
2022-01-19,1.1345,129.86,1.9558,N/A,24.313,7.4419,N/A,0.83168,355.88,N/A,N/A,N/A,4.5229,N/A,4.9449,10.3428,N/A,N/A,1.0383,145.2,9.9368,7.5238,86.48,N/A,15.4207,1.5709,6.2657,1.4144,7.2003,8.8392,16283.78,3.5529,84.4135,1347.1,23.0922,4.757,1.6684,58.429,1.5293,37.467,17.3889,
|
||||
2022-01-18,1.1367,130.39,1.9558,N/A,24.426,7.4425,N/A,0.83673,357,N/A,N/A,N/A,4.526,N/A,4.9449,10.3185,N/A,N/A,1.0414,146,9.9638,7.5225,86.7325,N/A,15.4447,1.5833,6.2797,1.4228,7.2212,8.8576,16318.64,3.5557,84.813,1355.33,23.1443,4.7554,1.6798,58.553,1.5349,37.635,17.5398,
|
||||
2022-01-17,1.1403,130.64,1.9558,N/A,24.467,7.4417,N/A,0.83573,356.09,N/A,N/A,N/A,4.5256,N/A,4.9443,10.305,N/A,N/A,1.0429,146.8,9.9623,7.5275,87.3907,N/A,15.2757,1.5811,6.2808,1.4287,7.2402,8.8844,16337.73,3.5479,84.7295,1359.89,23.1901,4.7704,1.6765,58.496,1.5374,37.761,17.601,
|
||||
2022-01-14,1.1447,130.17,1.9558,N/A,24.493,7.4414,N/A,0.83508,356.1,N/A,N/A,N/A,4.5414,N/A,4.9429,10.2684,N/A,N/A,1.0429,147,9.9863,7.5205,88.0011,N/A,15.5256,1.5803,6.3361,1.433,7.2728,8.9119,16388.85,3.5565,84.9445,1361.27,23.2684,4.7831,1.6745,58.727,1.5418,38.033,17.6043,
|
||||
2022-01-13,1.1463,130.98,1.9558,N/A,24.458,7.4409,N/A,0.83545,355.24,N/A,N/A,N/A,4.5361,N/A,4.944,10.238,N/A,N/A,1.0453,147,9.9333,7.5202,86.603,N/A,15.5744,1.5709,6.3518,1.4304,7.2913,8.9289,16379.02,3.5638,84.717,1359.24,23.3894,4.7875,1.6676,58.572,1.5427,38.08,17.6381,
|
||||
2022-01-12,1.137,131.19,1.9558,N/A,24.423,7.4414,N/A,0.83338,355.98,N/A,N/A,N/A,4.5359,N/A,4.9453,10.264,N/A,N/A,1.0486,147,9.927,7.524,84.7559,N/A,15.5922,1.5762,6.3458,1.4261,7.2379,8.863,16295.93,3.5406,84.0285,1353.27,23.1852,4.7595,1.6775,58.08,1.5358,37.936,17.5645,
|
||||
2022-01-11,1.1336,130.95,1.9558,N/A,24.412,7.4404,N/A,0.83475,357.45,N/A,N/A,N/A,4.5438,N/A,4.945,10.3075,N/A,N/A,1.0502,147,10.0165,7.5236,84.8663,N/A,15.696,1.5804,6.389,1.4329,7.2255,8.8384,16221.39,3.549,83.7481,1352.85,23.0888,4.7515,1.6772,57.979,1.5342,37.862,17.7094,
|
||||
2022-01-10,1.1318,130.45,1.9558,N/A,24.357,7.4381,N/A,0.83398,358.4,N/A,N/A,N/A,4.5334,N/A,4.9449,10.3038,N/A,N/A,1.0446,146.2,10.0253,7.5278,84.9825,N/A,15.7183,1.5774,6.3969,1.4327,7.2128,8.8233,16181.9,3.5303,83.8,1355.68,23.0597,4.7536,1.6753,58.141,1.5344,38.074,17.6999,
|
||||
2022-01-07,1.1298,130.9,1.9558,N/A,24.439,7.438,N/A,0.8343,358.68,N/A,N/A,N/A,4.5496,N/A,4.9451,10.2839,N/A,N/A,1.0422,146,10.0288,7.5214,85.298,N/A,15.7206,1.5804,6.4343,1.4374,7.206,8.8133,16188.43,3.5135,83.978,1359.96,23.1109,4.7553,1.6748,58.046,1.5356,38.074,17.6701,
|
||||
2022-01-06,1.1315,131.05,1.9558,N/A,24.528,7.4393,N/A,0.83593,359.84,N/A,N/A,N/A,4.5614,N/A,4.9435,10.3265,N/A,N/A,1.0395,146.8,10.035,7.5197,86.5088,N/A,15.5504,1.5778,6.442,1.4451,7.2187,8.8272,16291.88,3.5247,84.2475,1362.06,23.2549,4.767,1.6752,57.943,1.5388,37.962,17.7932,
|
||||
2022-01-05,1.1319,131.03,1.9558,N/A,24.581,7.4384,N/A,0.83546,362.15,N/A,N/A,N/A,4.5666,N/A,4.946,10.2545,N/A,N/A,1.0364,146.8,9.9672,7.519,85.7275,N/A,15.2446,1.56,6.4146,1.4399,7.2087,8.8227,16263.02,3.4989,84.161,1354.61,23.1422,4.7466,1.6597,57.713,1.534,37.607,17.9369,
|
||||
2022-01-04,1.1279,131.17,1.9558,N/A,24.745,7.4378,N/A,0.83618,365.12,N/A,N/A,N/A,4.5667,N/A,4.9481,10.2808,N/A,N/A,1.0355,147.8,10.0138,7.5185,84.9202,N/A,15.1384,1.5682,6.4174,1.4382,7.1924,8.7919,16199.73,3.4909,84.2055,1352.91,23.1808,4.7214,1.6668,57.988,1.531,37.582,18.108,
|
||||
2022-01-03,1.1355,130.56,1.9558,N/A,24.818,7.4382,N/A,0.84135,367.71,N/A,N/A,N/A,4.5895,N/A,4.9483,10.2958,N/A,N/A,1.0372,147.6,10.0013,7.519,84.5313,N/A,15.0777,1.5691,6.3539,1.442,7.2174,8.8541,16202.02,3.5139,84.3949,1354.4,23.2259,4.7379,1.6651,58.051,1.5333,37.665,17.9661,
|
||||
|
128
ext/lines/lines.go
Normal file
128
ext/lines/lines.go
Normal file
@@ -0,0 +1,128 @@
|
||||
// Package lines provides a virtual table to read large files line-by-line.
|
||||
package lines
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"math"
|
||||
"os"
|
||||
|
||||
"github.com/ncruces/go-sqlite3"
|
||||
)
|
||||
|
||||
// Register registers the lines and lines_read virtual tables.
|
||||
// The lines virtual table reads from a database blob or text.
|
||||
// The lines_read virtual table reads from a file or an [io.ReaderAt].
|
||||
func Register(db *sqlite3.Conn) {
|
||||
sqlite3.CreateModule[lines](db, "lines", nil,
|
||||
func(db *sqlite3.Conn, _, _, _ string, _ ...string) (lines, error) {
|
||||
err := db.DeclareVtab(`CREATE TABLE x(line TEXT, data HIDDEN)`)
|
||||
db.VtabConfig(sqlite3.VTAB_INNOCUOUS)
|
||||
return false, err
|
||||
})
|
||||
sqlite3.CreateModule[lines](db, "lines_read", nil,
|
||||
func(db *sqlite3.Conn, _, _, _ string, _ ...string) (lines, error) {
|
||||
err := db.DeclareVtab(`CREATE TABLE x(line TEXT, data HIDDEN)`)
|
||||
db.VtabConfig(sqlite3.VTAB_DIRECTONLY)
|
||||
return true, err
|
||||
})
|
||||
}
|
||||
|
||||
type lines bool
|
||||
|
||||
func (l lines) BestIndex(idx *sqlite3.IndexInfo) error {
|
||||
for i, cst := range idx.Constraint {
|
||||
if cst.Column == 1 && cst.Op == sqlite3.INDEX_CONSTRAINT_EQ && cst.Usable {
|
||||
idx.ConstraintUsage[i] = sqlite3.IndexConstraintUsage{
|
||||
Omit: true,
|
||||
ArgvIndex: 1,
|
||||
}
|
||||
idx.EstimatedCost = 1e6
|
||||
idx.EstimatedRows = 100
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return sqlite3.CONSTRAINT
|
||||
}
|
||||
|
||||
func (l lines) Open() (sqlite3.VTabCursor, error) {
|
||||
return &cursor{reader: bool(l)}, nil
|
||||
}
|
||||
|
||||
type cursor struct {
|
||||
scanner *bufio.Scanner
|
||||
closer io.Closer
|
||||
rowID int64
|
||||
eof bool
|
||||
reader bool
|
||||
}
|
||||
|
||||
func (c *cursor) Close() (err error) {
|
||||
if c.closer != nil {
|
||||
err = c.closer.Close()
|
||||
c.closer = nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *cursor) EOF() bool {
|
||||
return c.eof
|
||||
}
|
||||
|
||||
func (c *cursor) Next() error {
|
||||
c.rowID++
|
||||
c.eof = !c.scanner.Scan()
|
||||
return c.scanner.Err()
|
||||
}
|
||||
|
||||
func (c *cursor) RowID() (int64, error) {
|
||||
return c.rowID, nil
|
||||
}
|
||||
|
||||
func (c *cursor) Column(ctx *sqlite3.Context, n int) error {
|
||||
if n == 0 {
|
||||
ctx.ResultRawText(c.scanner.Bytes())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *cursor) Filter(idxNum int, idxStr string, arg ...sqlite3.Value) error {
|
||||
if err := c.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var r io.Reader
|
||||
data := arg[0]
|
||||
typ := data.Type()
|
||||
if c.reader {
|
||||
switch typ {
|
||||
case sqlite3.NULL:
|
||||
if p, ok := data.Pointer().(io.ReaderAt); ok {
|
||||
r = io.NewSectionReader(p, 0, math.MaxInt64)
|
||||
}
|
||||
case sqlite3.TEXT:
|
||||
f, err := os.Open(data.Text())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c.closer = f
|
||||
r = f
|
||||
}
|
||||
} else {
|
||||
switch typ {
|
||||
case sqlite3.TEXT:
|
||||
r = bytes.NewReader(data.RawText())
|
||||
case sqlite3.BLOB:
|
||||
r = bytes.NewReader(data.RawBlob())
|
||||
}
|
||||
}
|
||||
|
||||
if r == nil {
|
||||
return fmt.Errorf("lines: unsupported argument:%.0w %v", sqlite3.MISMATCH, typ)
|
||||
}
|
||||
c.scanner = bufio.NewScanner(r)
|
||||
c.rowID = 0
|
||||
return c.Next()
|
||||
}
|
||||
186
ext/lines/lines_test.go
Normal file
186
ext/lines/lines_test.go
Normal file
@@ -0,0 +1,186 @@
|
||||
package lines_test
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/ncruces/go-sqlite3"
|
||||
"github.com/ncruces/go-sqlite3/driver"
|
||||
_ "github.com/ncruces/go-sqlite3/embed"
|
||||
"github.com/ncruces/go-sqlite3/ext/lines"
|
||||
)
|
||||
|
||||
func Example() {
|
||||
db, err := driver.Open(":memory:", func(c *sqlite3.Conn) error {
|
||||
lines.Register(c)
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
// https://storage.googleapis.com/quickdraw_dataset/full/simplified/calendar.ndjson
|
||||
f, err := os.Open("calendar.ndjson")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
rows, err := db.Query(`
|
||||
SELECT
|
||||
line ->> '$.countrycode' as countrycode,
|
||||
COUNT(*)
|
||||
FROM lines_read(?)
|
||||
GROUP BY 1
|
||||
ORDER BY 2 DESC
|
||||
LIMIT 5`,
|
||||
sqlite3.Pointer(f))
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var countrycode sql.RawBytes
|
||||
var count int
|
||||
for rows.Next() {
|
||||
err := rows.Scan(&countrycode, &count)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
fmt.Printf("%s: %d\n", countrycode, count)
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
// Sample output:
|
||||
// US: 141001
|
||||
// GB: 22560
|
||||
// CA: 11759
|
||||
// RU: 9250
|
||||
// DE: 8748
|
||||
}
|
||||
|
||||
func Test_lines(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
db, err := driver.Open(":memory:", func(c *sqlite3.Conn) error {
|
||||
lines.Register(c)
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
const data = "line 1\nline 2\nline 3"
|
||||
|
||||
rows, err := db.Query(`SELECT rowid, line FROM lines(?)`, data)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
for rows.Next() {
|
||||
var id int64
|
||||
var line string
|
||||
err := rows.Scan(&id, &line)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func Test_lines_error(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
db, err := driver.Open(":memory:", func(c *sqlite3.Conn) error {
|
||||
lines.Register(c)
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
_, err = db.Exec(`SELECT rowid, line FROM lines(?)`, nil)
|
||||
if err == nil {
|
||||
t.Fatal("want error")
|
||||
} else {
|
||||
t.Log(err)
|
||||
}
|
||||
|
||||
_, err = db.Exec(`SELECT rowid, line FROM lines_read(?)`, "xpto")
|
||||
if err == nil {
|
||||
t.Fatal("want error")
|
||||
} else {
|
||||
t.Log(err)
|
||||
}
|
||||
}
|
||||
|
||||
func Test_lines_read(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
db, err := driver.Open(":memory:", func(c *sqlite3.Conn) error {
|
||||
lines.Register(c)
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
const data = "line 1\nline 2\nline 3"
|
||||
|
||||
rows, err := db.Query(`SELECT rowid, line FROM lines_read(?)`,
|
||||
sqlite3.Pointer(strings.NewReader(data)))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
for rows.Next() {
|
||||
var id int64
|
||||
var line string
|
||||
err := rows.Scan(&id, &line)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func Test_lines_test(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
db, err := driver.Open(":memory:", func(c *sqlite3.Conn) error {
|
||||
lines.Register(c)
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
rows, err := db.Query(`SELECT rowid, line FROM lines_read(?)`, "lines_test.go")
|
||||
if errors.Is(err, os.ErrNotExist) {
|
||||
t.Skip(err)
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
for rows.Next() {
|
||||
var id int64
|
||||
var line string
|
||||
err := rows.Scan(&id, &line)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
267
ext/pivot/pivot.go
Normal file
267
ext/pivot/pivot.go
Normal file
@@ -0,0 +1,267 @@
|
||||
// Package pivot implements a pivot virtual table.
|
||||
//
|
||||
// https://github.com/jakethaw/pivot_vtab
|
||||
package pivot
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/ncruces/go-sqlite3"
|
||||
)
|
||||
|
||||
// Register registers the pivot virtual table.
|
||||
func Register(db *sqlite3.Conn) {
|
||||
sqlite3.CreateModule(db, "pivot", declare, declare)
|
||||
}
|
||||
|
||||
type table struct {
|
||||
db *sqlite3.Conn
|
||||
scan string
|
||||
cell string
|
||||
keys []string
|
||||
cols []*sqlite3.Value
|
||||
}
|
||||
|
||||
func declare(db *sqlite3.Conn, _, _, _ string, arg ...string) (_ *table, err error) {
|
||||
if len(arg) != 3 {
|
||||
return nil, fmt.Errorf("pivot: wrong number of arguments")
|
||||
}
|
||||
|
||||
table := &table{db: db}
|
||||
defer func() {
|
||||
if err != nil {
|
||||
table.Close()
|
||||
}
|
||||
}()
|
||||
|
||||
var sep string
|
||||
var create strings.Builder
|
||||
create.WriteString("CREATE TABLE x(")
|
||||
|
||||
// Row key query.
|
||||
table.scan = "SELECT * FROM\n" + arg[0]
|
||||
stmt, _, err := db.Prepare(table.scan)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer stmt.Close()
|
||||
|
||||
table.keys = make([]string, stmt.ColumnCount())
|
||||
for i := range table.keys {
|
||||
name := sqlite3.QuoteIdentifier(stmt.ColumnName(i))
|
||||
table.keys[i] = name
|
||||
create.WriteString(sep)
|
||||
create.WriteString(name)
|
||||
sep = ","
|
||||
}
|
||||
stmt.Close()
|
||||
|
||||
// Column definition query.
|
||||
stmt, _, err = db.Prepare("SELECT * FROM\n" + arg[1])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if stmt.ColumnCount() != 2 {
|
||||
return nil, fmt.Errorf("pivot: column definition query expects 2 result columns")
|
||||
}
|
||||
for stmt.Step() {
|
||||
name := sqlite3.QuoteIdentifier(stmt.ColumnText(1))
|
||||
table.cols = append(table.cols, stmt.ColumnValue(0).Dup())
|
||||
create.WriteString(",")
|
||||
create.WriteString(name)
|
||||
}
|
||||
stmt.Close()
|
||||
|
||||
// Pivot cell query.
|
||||
table.cell = "SELECT * FROM\n" + arg[2]
|
||||
stmt, _, err = db.Prepare(table.cell)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if stmt.ColumnCount() != 1 {
|
||||
return nil, fmt.Errorf("pivot: cell query expects 1 result columns")
|
||||
}
|
||||
if stmt.BindCount() != len(table.keys)+1 {
|
||||
return nil, fmt.Errorf("pivot: cell query expects %d bound parameters", len(table.keys)+1)
|
||||
}
|
||||
|
||||
create.WriteByte(')')
|
||||
err = db.DeclareVtab(create.String())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return table, nil
|
||||
}
|
||||
|
||||
func (t *table) Close() error {
|
||||
for i := range t.cols {
|
||||
t.cols[i].Close()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *table) BestIndex(idx *sqlite3.IndexInfo) error {
|
||||
var idxStr strings.Builder
|
||||
idxStr.WriteString(t.scan)
|
||||
|
||||
argvIndex := 1
|
||||
sep := " WHERE "
|
||||
for i, cst := range idx.Constraint {
|
||||
if !cst.Usable || !(0 <= cst.Column && cst.Column < len(t.keys)) {
|
||||
continue
|
||||
}
|
||||
|
||||
var op string
|
||||
switch cst.Op {
|
||||
case sqlite3.INDEX_CONSTRAINT_EQ:
|
||||
op = "="
|
||||
case sqlite3.INDEX_CONSTRAINT_LT:
|
||||
op = "<"
|
||||
case sqlite3.INDEX_CONSTRAINT_GT:
|
||||
op = ">"
|
||||
case sqlite3.INDEX_CONSTRAINT_LE:
|
||||
op = "<="
|
||||
case sqlite3.INDEX_CONSTRAINT_GE:
|
||||
op = ">="
|
||||
case sqlite3.INDEX_CONSTRAINT_NE:
|
||||
op = "<>"
|
||||
case sqlite3.INDEX_CONSTRAINT_MATCH:
|
||||
op = "MATCH"
|
||||
case sqlite3.INDEX_CONSTRAINT_LIKE:
|
||||
op = "LIKE"
|
||||
case sqlite3.INDEX_CONSTRAINT_GLOB:
|
||||
op = "GLOB"
|
||||
case sqlite3.INDEX_CONSTRAINT_REGEXP:
|
||||
op = "REGEXP"
|
||||
case sqlite3.INDEX_CONSTRAINT_IS, sqlite3.INDEX_CONSTRAINT_ISNULL:
|
||||
op = "IS"
|
||||
case sqlite3.INDEX_CONSTRAINT_ISNOT, sqlite3.INDEX_CONSTRAINT_ISNOTNULL:
|
||||
op = "IS NOT"
|
||||
default:
|
||||
continue
|
||||
}
|
||||
|
||||
idxStr.WriteString(sep)
|
||||
idxStr.WriteString(t.keys[cst.Column])
|
||||
idxStr.WriteString(" ")
|
||||
idxStr.WriteString(op)
|
||||
idxStr.WriteString(" ?")
|
||||
|
||||
idx.ConstraintUsage[i] = sqlite3.IndexConstraintUsage{
|
||||
ArgvIndex: argvIndex,
|
||||
Omit: true,
|
||||
}
|
||||
sep = " AND "
|
||||
argvIndex++
|
||||
}
|
||||
|
||||
sep = " ORDER BY "
|
||||
idx.OrderByConsumed = true
|
||||
for _, ord := range idx.OrderBy {
|
||||
if !(0 <= ord.Column && ord.Column < len(t.keys)) {
|
||||
idx.OrderByConsumed = false
|
||||
continue
|
||||
}
|
||||
idxStr.WriteString(sep)
|
||||
idxStr.WriteString(t.keys[ord.Column])
|
||||
if ord.Desc {
|
||||
idxStr.WriteString(" DESC")
|
||||
}
|
||||
sep = ","
|
||||
}
|
||||
|
||||
idx.EstimatedCost = 1e9 / float64(argvIndex)
|
||||
idx.IdxStr = idxStr.String()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *table) Open() (sqlite3.VTabCursor, error) {
|
||||
return &cursor{table: t}, nil
|
||||
}
|
||||
|
||||
func (t *table) Rename(new string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
type cursor struct {
|
||||
table *table
|
||||
scan *sqlite3.Stmt
|
||||
cell *sqlite3.Stmt
|
||||
rowID int64
|
||||
}
|
||||
|
||||
func (c *cursor) Close() error {
|
||||
return errors.Join(c.scan.Close(), c.cell.Close())
|
||||
}
|
||||
|
||||
func (c *cursor) Filter(idxNum int, idxStr string, arg ...sqlite3.Value) error {
|
||||
err := c.scan.Close()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c.scan, _, err = c.table.db.Prepare(idxStr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for i, arg := range arg {
|
||||
err := c.scan.BindValue(i+1, arg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if c.cell == nil {
|
||||
c.cell, _, err = c.table.db.Prepare(c.table.cell)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
c.rowID = 0
|
||||
return c.Next()
|
||||
}
|
||||
|
||||
func (c *cursor) Next() error {
|
||||
if c.scan.Step() {
|
||||
count := c.scan.ColumnCount()
|
||||
for i := 0; i < count; i++ {
|
||||
err := c.cell.BindValue(i+1, c.scan.ColumnValue(i))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
c.rowID++
|
||||
}
|
||||
return c.scan.Err()
|
||||
}
|
||||
|
||||
func (c *cursor) EOF() bool {
|
||||
return !c.scan.Busy()
|
||||
}
|
||||
|
||||
func (c *cursor) RowID() (int64, error) {
|
||||
return c.rowID, nil
|
||||
}
|
||||
|
||||
func (c *cursor) Column(ctx *sqlite3.Context, col int) error {
|
||||
count := c.scan.ColumnCount()
|
||||
if col < count {
|
||||
ctx.ResultValue(c.scan.ColumnValue(col))
|
||||
return nil
|
||||
}
|
||||
|
||||
err := c.cell.BindValue(count+1, *c.table.cols[col-count])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if c.cell.Step() {
|
||||
ctx.ResultValue(c.cell.ColumnValue(0))
|
||||
}
|
||||
return c.cell.Reset()
|
||||
}
|
||||
219
ext/pivot/pivot_test.go
Normal file
219
ext/pivot/pivot_test.go
Normal file
@@ -0,0 +1,219 @@
|
||||
package pivot_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/ncruces/go-sqlite3"
|
||||
_ "github.com/ncruces/go-sqlite3/embed"
|
||||
"github.com/ncruces/go-sqlite3/ext/pivot"
|
||||
)
|
||||
|
||||
// https://antonz.org/sqlite-pivot-table/
|
||||
func Example() {
|
||||
db, err := sqlite3.Open(":memory:")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
pivot.Register(db)
|
||||
|
||||
err = db.Exec(`
|
||||
CREATE TABLE sales(product TEXT, year INT, income DECIMAL);
|
||||
INSERT INTO sales(product, year, income) VALUES
|
||||
('alpha', 2020, 100),
|
||||
('alpha', 2021, 120),
|
||||
('alpha', 2022, 130),
|
||||
('alpha', 2023, 140),
|
||||
('beta', 2020, 10),
|
||||
('beta', 2021, 20),
|
||||
('beta', 2022, 40),
|
||||
('beta', 2023, 80),
|
||||
('gamma', 2020, 80),
|
||||
('gamma', 2021, 75),
|
||||
('gamma', 2022, 78),
|
||||
('gamma', 2023, 80);
|
||||
`)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
err = db.Exec(`
|
||||
CREATE VIRTUAL TABLE v_sales USING pivot(
|
||||
-- rows
|
||||
(SELECT DISTINCT product FROM sales),
|
||||
-- columns
|
||||
(SELECT DISTINCT year, year FROM sales),
|
||||
-- cells
|
||||
(SELECT sum(income) FROM sales WHERE product = ? AND year = ?)
|
||||
)`)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
stmt, _, err := db.Prepare(`SELECT * FROM v_sales`)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer stmt.Close()
|
||||
|
||||
cols := make([]string, stmt.ColumnCount())
|
||||
for i := range cols {
|
||||
cols[i] = stmt.ColumnName(i)
|
||||
}
|
||||
fmt.Println(pretty(cols))
|
||||
for stmt.Step() {
|
||||
for i := range cols {
|
||||
cols[i] = stmt.ColumnText(i)
|
||||
}
|
||||
fmt.Println(pretty(cols))
|
||||
}
|
||||
if err := stmt.Reset(); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// Output:
|
||||
// product 2020 2021 2022 2023
|
||||
// alpha 100 120 130 140
|
||||
// beta 10 20 40 80
|
||||
// gamma 80 75 78 80
|
||||
}
|
||||
|
||||
func TestRegister(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
db, err := sqlite3.Open(":memory:")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
pivot.Register(db)
|
||||
|
||||
err = db.Exec(`
|
||||
CREATE TABLE r AS
|
||||
SELECT 1 id UNION SELECT 2 UNION SELECT 3;
|
||||
|
||||
CREATE TABLE c(
|
||||
id INTEGER PRIMARY KEY,
|
||||
name TEXT
|
||||
);
|
||||
INSERT INTO c (name) VALUES
|
||||
('a'),('b'),('c'),('d');
|
||||
|
||||
CREATE TABLE x(
|
||||
r_id INT,
|
||||
c_id INT,
|
||||
val TEXT
|
||||
);
|
||||
INSERT INTO x (r_id, c_id, val)
|
||||
SELECT r.id, c.id, c.name || r.id
|
||||
FROM c, r;
|
||||
`)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = db.Exec(`
|
||||
CREATE VIRTUAL TABLE v_x USING pivot(
|
||||
-- rows
|
||||
(SELECT id r_id FROM r),
|
||||
-- columns
|
||||
(SELECT id c_id, name FROM c),
|
||||
-- cells
|
||||
(SELECT val FROM x WHERE r_id = ?1 AND c_id = ?2)
|
||||
)`)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
stmt, _, err := db.Prepare(`SELECT * FROM v_x WHERE rowid <> 0 AND r_id <> 1 ORDER BY rowid, r_id DESC LIMIT 1`)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer stmt.Close()
|
||||
|
||||
if stmt.Step() {
|
||||
if got := stmt.ColumnInt(0); got != 3 {
|
||||
t.Errorf("got %d, want 3", got)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestRegister_errors(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
db, err := sqlite3.Open(":memory:")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
pivot.Register(db)
|
||||
|
||||
err = db.Exec(`CREATE VIRTUAL TABLE pivot USING pivot()`)
|
||||
if err == nil {
|
||||
t.Fatal("want error")
|
||||
} else {
|
||||
t.Log(err)
|
||||
}
|
||||
|
||||
err = db.Exec(`CREATE VIRTUAL TABLE split_date USING pivot(SELECT 1, SELECT 2, SELECT 3)`)
|
||||
if err == nil {
|
||||
t.Fatal("want error")
|
||||
} else {
|
||||
t.Log(err)
|
||||
}
|
||||
|
||||
err = db.Exec(`CREATE VIRTUAL TABLE split_date USING pivot((SELECT 1), SELECT 2, SELECT 3)`)
|
||||
if err == nil {
|
||||
t.Fatal("want error")
|
||||
} else {
|
||||
t.Log(err)
|
||||
}
|
||||
|
||||
err = db.Exec(`CREATE VIRTUAL TABLE split_date USING pivot((SELECT 1), (SELECT 2), SELECT 3)`)
|
||||
if err == nil {
|
||||
t.Fatal("want error")
|
||||
} else {
|
||||
t.Log(err)
|
||||
}
|
||||
|
||||
err = db.Exec(`CREATE VIRTUAL TABLE split_date USING pivot((SELECT 1), (SELECT 1, 2), SELECT 3)`)
|
||||
if err == nil {
|
||||
t.Fatal("want error")
|
||||
} else {
|
||||
t.Log(err)
|
||||
}
|
||||
|
||||
err = db.Exec(`CREATE VIRTUAL TABLE split_date USING pivot((SELECT 1), (SELECT 1, 2), (SELECT 3, 4))`)
|
||||
if err == nil {
|
||||
t.Fatal("want error")
|
||||
} else {
|
||||
t.Log(err)
|
||||
}
|
||||
|
||||
err = db.Exec(`CREATE VIRTUAL TABLE split_date USING pivot((SELECT 1), (SELECT 1, 2), (SELECT 3))`)
|
||||
if err == nil {
|
||||
t.Fatal("want error")
|
||||
} else {
|
||||
t.Log(err)
|
||||
}
|
||||
}
|
||||
|
||||
func pretty(cols []string) string {
|
||||
var buf strings.Builder
|
||||
for i, s := range cols {
|
||||
if i != 0 {
|
||||
buf.WriteByte(' ')
|
||||
}
|
||||
for buf.Len()%8 != 0 {
|
||||
buf.WriteByte(' ')
|
||||
}
|
||||
buf.WriteString(s)
|
||||
}
|
||||
return buf.String()
|
||||
}
|
||||
210
ext/statement/stmt.go
Normal file
210
ext/statement/stmt.go
Normal file
@@ -0,0 +1,210 @@
|
||||
// Package statement defines table-valued functions natively using SQL.
|
||||
//
|
||||
// https://github.com/0x09/sqlite-statement-vtab
|
||||
package statement
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"unsafe"
|
||||
|
||||
"github.com/ncruces/go-sqlite3"
|
||||
)
|
||||
|
||||
// Register registers the statement virtual table.
|
||||
func Register(db *sqlite3.Conn) {
|
||||
sqlite3.CreateModule(db, "statement", declare, declare)
|
||||
}
|
||||
|
||||
type table struct {
|
||||
stmt *sqlite3.Stmt
|
||||
sql string
|
||||
inuse bool
|
||||
}
|
||||
|
||||
func declare(db *sqlite3.Conn, _, _, _ string, arg ...string) (*table, error) {
|
||||
if len(arg) != 1 {
|
||||
return nil, fmt.Errorf("statement: wrong number of arguments")
|
||||
}
|
||||
|
||||
sql := "SELECT * FROM\n" + arg[0]
|
||||
|
||||
stmt, _, err := db.Prepare(sql)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var sep string
|
||||
var str strings.Builder
|
||||
str.WriteString("CREATE TABLE x(")
|
||||
outputs := stmt.ColumnCount()
|
||||
for i := 0; i < outputs; i++ {
|
||||
name := sqlite3.QuoteIdentifier(stmt.ColumnName(i))
|
||||
str.WriteString(sep)
|
||||
str.WriteString(name)
|
||||
str.WriteString(" ")
|
||||
str.WriteString(stmt.ColumnDeclType(i))
|
||||
sep = ","
|
||||
}
|
||||
inputs := stmt.BindCount()
|
||||
for i := 1; i <= inputs; i++ {
|
||||
str.WriteString(sep)
|
||||
name := stmt.BindName(i)
|
||||
if name == "" {
|
||||
str.WriteString("[")
|
||||
str.WriteString(strconv.Itoa(i))
|
||||
str.WriteString("] HIDDEN")
|
||||
} else {
|
||||
str.WriteString(sqlite3.QuoteIdentifier(name[1:]))
|
||||
str.WriteString(" HIDDEN")
|
||||
}
|
||||
sep = ","
|
||||
}
|
||||
str.WriteByte(')')
|
||||
|
||||
err = db.DeclareVtab(str.String())
|
||||
if err != nil {
|
||||
stmt.Close()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &table{sql: sql, stmt: stmt}, nil
|
||||
}
|
||||
|
||||
func (t *table) Close() error {
|
||||
return t.stmt.Close()
|
||||
}
|
||||
|
||||
func (t *table) BestIndex(idx *sqlite3.IndexInfo) error {
|
||||
idx.EstimatedCost = 1000
|
||||
|
||||
var argvIndex = 1
|
||||
var needIndex bool
|
||||
var listIndex []int
|
||||
outputs := t.stmt.ColumnCount()
|
||||
for i, cst := range idx.Constraint {
|
||||
// Skip if this is a constraint on one of our output columns.
|
||||
if cst.Column < outputs {
|
||||
continue
|
||||
}
|
||||
|
||||
// A given query plan is only usable if all provided input columns
|
||||
// are usable and have equal constraints only.
|
||||
if !cst.Usable || cst.Op != sqlite3.INDEX_CONSTRAINT_EQ {
|
||||
return sqlite3.CONSTRAINT
|
||||
}
|
||||
|
||||
// The non-zero argvIdx values must be contiguous.
|
||||
// If they're not, build a list and serialize it through IdxStr.
|
||||
nextIndex := cst.Column - outputs + 1
|
||||
idx.ConstraintUsage[i] = sqlite3.IndexConstraintUsage{
|
||||
ArgvIndex: argvIndex,
|
||||
Omit: true,
|
||||
}
|
||||
if nextIndex != argvIndex {
|
||||
needIndex = true
|
||||
}
|
||||
listIndex = append(listIndex, nextIndex)
|
||||
argvIndex++
|
||||
}
|
||||
|
||||
if needIndex {
|
||||
buf, err := json.Marshal(listIndex)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
idx.IdxStr = unsafe.String(&buf[0], len(buf))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *table) Open() (sqlite3.VTabCursor, error) {
|
||||
stmt := t.stmt
|
||||
if !t.inuse {
|
||||
t.inuse = true
|
||||
} else {
|
||||
var err error
|
||||
stmt, _, err = t.stmt.Conn().Prepare(t.sql)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return &cursor{table: t, stmt: stmt}, nil
|
||||
}
|
||||
|
||||
func (t *table) Rename(new string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
type cursor struct {
|
||||
table *table
|
||||
stmt *sqlite3.Stmt
|
||||
arg []sqlite3.Value
|
||||
rowID int64
|
||||
}
|
||||
|
||||
func (c *cursor) Close() error {
|
||||
if c.stmt == c.table.stmt {
|
||||
c.table.inuse = false
|
||||
c.stmt.ClearBindings()
|
||||
return c.stmt.Reset()
|
||||
}
|
||||
return c.stmt.Close()
|
||||
}
|
||||
|
||||
func (c *cursor) Filter(idxNum int, idxStr string, arg ...sqlite3.Value) error {
|
||||
c.arg = arg
|
||||
c.rowID = 0
|
||||
c.stmt.ClearBindings()
|
||||
if err := c.stmt.Reset(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var list []int
|
||||
if idxStr != "" {
|
||||
buf := unsafe.Slice(unsafe.StringData(idxStr), len(idxStr))
|
||||
err := json.Unmarshal(buf, &list)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
for i, arg := range arg {
|
||||
param := i + 1
|
||||
if list != nil {
|
||||
param = list[i]
|
||||
}
|
||||
err := c.stmt.BindValue(param, arg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return c.Next()
|
||||
}
|
||||
|
||||
func (c *cursor) Next() error {
|
||||
if c.stmt.Step() {
|
||||
c.rowID++
|
||||
}
|
||||
return c.stmt.Err()
|
||||
}
|
||||
|
||||
func (c *cursor) EOF() bool {
|
||||
return !c.stmt.Busy()
|
||||
}
|
||||
|
||||
func (c *cursor) RowID() (int64, error) {
|
||||
return c.rowID, nil
|
||||
}
|
||||
|
||||
func (c *cursor) Column(ctx *sqlite3.Context, col int) error {
|
||||
switch outputs := c.stmt.ColumnCount(); {
|
||||
case col < outputs:
|
||||
ctx.ResultValue(c.stmt.ColumnValue(col))
|
||||
case col-outputs < len(c.arg):
|
||||
ctx.ResultValue(c.arg[col-outputs])
|
||||
}
|
||||
return nil
|
||||
}
|
||||
145
ext/statement/stmt_test.go
Normal file
145
ext/statement/stmt_test.go
Normal file
@@ -0,0 +1,145 @@
|
||||
package statement_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"testing"
|
||||
|
||||
"github.com/ncruces/go-sqlite3"
|
||||
_ "github.com/ncruces/go-sqlite3/embed"
|
||||
"github.com/ncruces/go-sqlite3/ext/statement"
|
||||
)
|
||||
|
||||
func Example() {
|
||||
db, err := sqlite3.Open(":memory:")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
statement.Register(db)
|
||||
|
||||
err = db.Exec(`
|
||||
CREATE VIRTUAL TABLE split_date USING statement((
|
||||
SELECT
|
||||
strftime('%Y', :date) AS year,
|
||||
strftime('%m', :date) AS month,
|
||||
strftime('%d', :date) AS day
|
||||
))`)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
stmt, _, err := db.Prepare(`SELECT * FROM split_date('2022-02-22')`)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer stmt.Close()
|
||||
|
||||
if stmt.Step() {
|
||||
fmt.Printf("Twosday was %d-%d-%d", stmt.ColumnInt(0), stmt.ColumnInt(1), stmt.ColumnInt(2))
|
||||
}
|
||||
if err := stmt.Reset(); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// Output:
|
||||
// Twosday was 2022-2-22
|
||||
}
|
||||
|
||||
func TestRegister(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
db, err := sqlite3.Open(":memory:")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
statement.Register(db)
|
||||
|
||||
err = db.Exec(`
|
||||
CREATE VIRTUAL TABLE arguments USING statement((SELECT ? AS a, ? AS b, ? AS c))
|
||||
`)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = db.Exec(`
|
||||
SELECT * from arguments WHERE [2] = 'y' AND [3] = 'z'
|
||||
`)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = db.Exec(`
|
||||
CREATE VIRTUAL TABLE hypot USING statement((SELECT sqrt(:x * :x + :y * :y) AS hypotenuse))
|
||||
`)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
stmt, _, err := db.Prepare(`
|
||||
SELECT x, y, * FROM hypot WHERE x = 3 AND y = 4
|
||||
`)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer stmt.Close()
|
||||
|
||||
if stmt.Step() {
|
||||
x := stmt.ColumnInt(0)
|
||||
y := stmt.ColumnInt(1)
|
||||
hypot := stmt.ColumnInt(2)
|
||||
if x != 3 || y != 4 || hypot != 5 {
|
||||
t.Errorf("hypot(%d, %d) = %d", x, y, hypot)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestRegister_errors(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
db, err := sqlite3.Open(":memory:")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
statement.Register(db)
|
||||
|
||||
err = db.Exec(`CREATE VIRTUAL TABLE split_date USING statement()`)
|
||||
if err == nil {
|
||||
t.Fatal("want error")
|
||||
} else {
|
||||
t.Log(err)
|
||||
}
|
||||
|
||||
err = db.Exec(`CREATE VIRTUAL TABLE split_date USING statement(SELECT 1, SELECT 2)`)
|
||||
if err == nil {
|
||||
t.Fatal("want error")
|
||||
} else {
|
||||
t.Log(err)
|
||||
}
|
||||
|
||||
err = db.Exec(`CREATE VIRTUAL TABLE split_date USING statement((SELECT 1, SELECT 2))`)
|
||||
if err == nil {
|
||||
t.Fatal("want error")
|
||||
} else {
|
||||
t.Log(err)
|
||||
}
|
||||
|
||||
err = db.Exec(`CREATE VIRTUAL TABLE split_date USING statement((SELECT 1; SELECT 2))`)
|
||||
if err == nil {
|
||||
t.Fatal("want error")
|
||||
} else {
|
||||
t.Log(err)
|
||||
}
|
||||
|
||||
err = db.Exec(`CREATE VIRTUAL TABLE split_date USING statement((CREATE TABLE x(val)))`)
|
||||
if err == nil {
|
||||
t.Fatal("want error")
|
||||
} else {
|
||||
t.Log(err)
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,8 @@ import (
|
||||
)
|
||||
|
||||
func Test_welford(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var s1, s2 welford
|
||||
|
||||
s1.enqueue(4)
|
||||
@@ -38,6 +40,8 @@ func Test_welford(t *testing.T) {
|
||||
}
|
||||
|
||||
func Test_covar(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var c1, c2 welford2
|
||||
|
||||
c1.enqueue(3, 70)
|
||||
@@ -64,6 +68,8 @@ func Test_covar(t *testing.T) {
|
||||
}
|
||||
|
||||
func Test_correlation(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var c welford2
|
||||
c.enqueue(1, 3)
|
||||
c.enqueue(2, 2)
|
||||
|
||||
@@ -66,7 +66,7 @@ func RegisterCollation(db *sqlite3.Conn, locale, name string) error {
|
||||
|
||||
func upper(ctx sqlite3.Context, arg ...sqlite3.Value) {
|
||||
if len(arg) == 1 {
|
||||
ctx.ResultBlob(bytes.ToUpper(arg[0].RawBlob()))
|
||||
ctx.ResultRawText(bytes.ToUpper(arg[0].RawText()))
|
||||
return
|
||||
}
|
||||
cs, ok := ctx.GetAuxData(1).(cases.Caser)
|
||||
@@ -80,12 +80,12 @@ func upper(ctx sqlite3.Context, arg ...sqlite3.Value) {
|
||||
ctx.SetAuxData(1, c)
|
||||
cs = c
|
||||
}
|
||||
ctx.ResultBlob(cs.Bytes(arg[0].RawBlob()))
|
||||
ctx.ResultRawText(cs.Bytes(arg[0].RawText()))
|
||||
}
|
||||
|
||||
func lower(ctx sqlite3.Context, arg ...sqlite3.Value) {
|
||||
if len(arg) == 1 {
|
||||
ctx.ResultBlob(bytes.ToLower(arg[0].RawBlob()))
|
||||
ctx.ResultRawText(bytes.ToLower(arg[0].RawText()))
|
||||
return
|
||||
}
|
||||
cs, ok := ctx.GetAuxData(1).(cases.Caser)
|
||||
@@ -99,7 +99,7 @@ func lower(ctx sqlite3.Context, arg ...sqlite3.Value) {
|
||||
ctx.SetAuxData(1, c)
|
||||
cs = c
|
||||
}
|
||||
ctx.ResultBlob(cs.Bytes(arg[0].RawBlob()))
|
||||
ctx.ResultRawText(cs.Bytes(arg[0].RawText()))
|
||||
}
|
||||
|
||||
func regex(ctx sqlite3.Context, arg ...sqlite3.Value) {
|
||||
@@ -113,14 +113,14 @@ func regex(ctx sqlite3.Context, arg ...sqlite3.Value) {
|
||||
re = r
|
||||
ctx.SetAuxData(0, re)
|
||||
}
|
||||
ctx.ResultBool(re.Match(arg[1].RawBlob()))
|
||||
ctx.ResultBool(re.Match(arg[1].RawText()))
|
||||
}
|
||||
|
||||
func like(ctx sqlite3.Context, arg ...sqlite3.Value) {
|
||||
escape := rune(-1)
|
||||
if len(arg) == 3 {
|
||||
var size int
|
||||
b := arg[2].RawBlob()
|
||||
b := arg[2].RawText()
|
||||
escape, size = utf8.DecodeRune(b)
|
||||
if size != len(b) {
|
||||
ctx.ResultError(util.ErrorString("ESCAPE expression must be a single character"))
|
||||
@@ -141,7 +141,7 @@ func like(ctx sqlite3.Context, arg ...sqlite3.Value) {
|
||||
}
|
||||
ctx.SetAuxData(0, re)
|
||||
}
|
||||
ctx.ResultBool(re.Match(arg[1].RawBlob()))
|
||||
ctx.ResultBool(re.Match(arg[1].RawText()))
|
||||
}
|
||||
|
||||
func like2regex(pattern string, escape rune) string {
|
||||
|
||||
@@ -189,6 +189,8 @@ func TestRegister_error(t *testing.T) {
|
||||
}
|
||||
|
||||
func Test_like2regex(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
const prefix = `(?is)\A`
|
||||
const sufix = `\z`
|
||||
tests := []struct {
|
||||
|
||||
82
func.go
82
func.go
@@ -14,47 +14,49 @@ import (
|
||||
// This can be used to load schemas that contain
|
||||
// one or more unknown collating sequences.
|
||||
func (c *Conn) AnyCollationNeeded() {
|
||||
c.call(c.api.anyCollation, uint64(c.handle), 0, 0)
|
||||
c.call("sqlite3_anycollseq_init", uint64(c.handle), 0, 0)
|
||||
}
|
||||
|
||||
// CreateCollation defines a new collating sequence.
|
||||
//
|
||||
// https://www.sqlite.org/c3ref/create_collation.html
|
||||
// https://sqlite.org/c3ref/create_collation.html
|
||||
func (c *Conn) CreateCollation(name string, fn func(a, b []byte) int) error {
|
||||
defer c.arena.mark()()
|
||||
namePtr := c.arena.string(name)
|
||||
funcPtr := util.AddHandle(c.ctx, fn)
|
||||
r := c.call(c.api.createCollation,
|
||||
r := c.call("sqlite3_create_collation_go",
|
||||
uint64(c.handle), uint64(namePtr), uint64(funcPtr))
|
||||
if err := c.error(r); err != nil {
|
||||
util.DelHandle(c.ctx, funcPtr)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
return c.error(r)
|
||||
}
|
||||
|
||||
// CreateFunction defines a new scalar SQL function.
|
||||
//
|
||||
// https://www.sqlite.org/c3ref/create_function.html
|
||||
func (c *Conn) CreateFunction(name string, nArg int, flag FunctionFlag, fn func(ctx Context, arg ...Value)) error {
|
||||
// https://sqlite.org/c3ref/create_function.html
|
||||
func (c *Conn) CreateFunction(name string, nArg int, flag FunctionFlag, fn ScalarFunction) error {
|
||||
defer c.arena.mark()()
|
||||
namePtr := c.arena.string(name)
|
||||
funcPtr := util.AddHandle(c.ctx, fn)
|
||||
r := c.call(c.api.createFunction,
|
||||
r := c.call("sqlite3_create_function_go",
|
||||
uint64(c.handle), uint64(namePtr), uint64(nArg),
|
||||
uint64(flag), uint64(funcPtr))
|
||||
return c.error(r)
|
||||
}
|
||||
|
||||
// ScalarFunction is the type of a scalar SQL function.
|
||||
type ScalarFunction func(ctx Context, arg ...Value)
|
||||
|
||||
// CreateWindowFunction defines a new aggregate or aggregate window SQL function.
|
||||
// If fn returns a [WindowFunction], then an aggregate window function is created.
|
||||
// If fn returns an [io.Closer], it will be called to free resources.
|
||||
//
|
||||
// https://www.sqlite.org/c3ref/create_function.html
|
||||
// https://sqlite.org/c3ref/create_function.html
|
||||
func (c *Conn) CreateWindowFunction(name string, nArg int, flag FunctionFlag, fn func() AggregateFunction) error {
|
||||
call := c.api.createAggregate
|
||||
defer c.arena.mark()()
|
||||
call := "sqlite3_create_aggregate_function_go"
|
||||
namePtr := c.arena.string(name)
|
||||
funcPtr := util.AddHandle(c.ctx, fn)
|
||||
if _, ok := fn().(WindowFunction); ok {
|
||||
call = c.api.createWindow
|
||||
call = "sqlite3_create_window_function_go"
|
||||
}
|
||||
r := c.call(call,
|
||||
uint64(c.handle), uint64(namePtr), uint64(nArg),
|
||||
@@ -64,7 +66,7 @@ func (c *Conn) CreateWindowFunction(name string, nArg int, flag FunctionFlag, fn
|
||||
|
||||
// AggregateFunction is the interface an aggregate function should implement.
|
||||
//
|
||||
// https://www.sqlite.org/appfunc.html
|
||||
// https://sqlite.org/appfunc.html
|
||||
type AggregateFunction interface {
|
||||
// Step is invoked to add a row to the current window.
|
||||
// The function arguments, if any, corresponding to the row being added are passed to Step.
|
||||
@@ -76,7 +78,7 @@ type AggregateFunction interface {
|
||||
|
||||
// WindowFunction is the interface an aggregate window function should implement.
|
||||
//
|
||||
// https://www.sqlite.org/windowfunctions.html
|
||||
// https://sqlite.org/windowfunctions.html
|
||||
type WindowFunction interface {
|
||||
AggregateFunction
|
||||
|
||||
@@ -85,78 +87,76 @@ type WindowFunction interface {
|
||||
Inverse(ctx Context, arg ...Value)
|
||||
}
|
||||
|
||||
func callbackDestroy(ctx context.Context, mod api.Module, pApp uint32) {
|
||||
func destroyCallback(ctx context.Context, mod api.Module, pApp uint32) {
|
||||
util.DelHandle(ctx, pApp)
|
||||
}
|
||||
|
||||
func callbackCompare(ctx context.Context, mod api.Module, pApp, nKey1, pKey1, nKey2, pKey2 uint32) uint32 {
|
||||
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))))
|
||||
}
|
||||
|
||||
func callbackFunc(ctx context.Context, mod api.Module, pCtx, nArg, pArg uint32) {
|
||||
func funcCallback(ctx context.Context, mod api.Module, pCtx, nArg, pArg uint32) {
|
||||
db := ctx.Value(connKey{}).(*Conn)
|
||||
fn := callbackHandle(db, pCtx).(func(ctx Context, arg ...Value))
|
||||
fn := userDataHandle(db, pCtx).(ScalarFunction)
|
||||
fn(Context{db, pCtx}, callbackArgs(db, nArg, pArg)...)
|
||||
}
|
||||
|
||||
func callbackStep(ctx context.Context, mod api.Module, pCtx, nArg, pArg uint32) {
|
||||
func stepCallback(ctx context.Context, mod api.Module, pCtx, nArg, pArg uint32) {
|
||||
db := ctx.Value(connKey{}).(*Conn)
|
||||
fn := callbackAggregate(db, pCtx, nil).(AggregateFunction)
|
||||
fn := aggregateCtxHandle(db, pCtx, nil)
|
||||
fn.Step(Context{db, pCtx}, callbackArgs(db, nArg, pArg)...)
|
||||
}
|
||||
|
||||
func callbackFinal(ctx context.Context, mod api.Module, pCtx uint32) {
|
||||
func finalCallback(ctx context.Context, mod api.Module, pCtx uint32) {
|
||||
var handle uint32
|
||||
db := ctx.Value(connKey{}).(*Conn)
|
||||
fn := callbackAggregate(db, pCtx, &handle).(AggregateFunction)
|
||||
fn := aggregateCtxHandle(db, pCtx, &handle)
|
||||
fn.Value(Context{db, pCtx})
|
||||
if err := util.DelHandle(ctx, handle); err != nil {
|
||||
Context{db, pCtx}.ResultError(err)
|
||||
}
|
||||
}
|
||||
|
||||
func callbackValue(ctx context.Context, mod api.Module, pCtx uint32) {
|
||||
func valueCallback(ctx context.Context, mod api.Module, pCtx uint32) {
|
||||
db := ctx.Value(connKey{}).(*Conn)
|
||||
fn := callbackAggregate(db, pCtx, nil).(AggregateFunction)
|
||||
fn := aggregateCtxHandle(db, pCtx, nil)
|
||||
fn.Value(Context{db, pCtx})
|
||||
}
|
||||
|
||||
func callbackInverse(ctx context.Context, mod api.Module, pCtx, nArg, pArg uint32) {
|
||||
func inverseCallback(ctx context.Context, mod api.Module, pCtx, nArg, pArg uint32) {
|
||||
db := ctx.Value(connKey{}).(*Conn)
|
||||
fn := callbackAggregate(db, pCtx, nil).(WindowFunction)
|
||||
fn := aggregateCtxHandle(db, pCtx, nil).(WindowFunction)
|
||||
fn.Inverse(Context{db, pCtx}, callbackArgs(db, nArg, pArg)...)
|
||||
}
|
||||
|
||||
func callbackHandle(db *Conn, pCtx uint32) any {
|
||||
pApp := uint32(db.call(db.api.userData, uint64(pCtx)))
|
||||
func userDataHandle(db *Conn, pCtx uint32) any {
|
||||
pApp := uint32(db.call("sqlite3_user_data", uint64(pCtx)))
|
||||
return util.GetHandle(db.ctx, pApp)
|
||||
}
|
||||
|
||||
func callbackAggregate(db *Conn, pCtx uint32, close *uint32) any {
|
||||
// On close, we're getting rid of the handle.
|
||||
func aggregateCtxHandle(db *Conn, pCtx uint32, close *uint32) AggregateFunction {
|
||||
// On close, we're getting rid of the aggregate.
|
||||
// Don't allocate space to store it.
|
||||
var size uint64
|
||||
if close == nil {
|
||||
size = ptrlen
|
||||
}
|
||||
ptr := uint32(db.call(db.api.aggregateCtx, uint64(pCtx), size))
|
||||
ptr := uint32(db.call("sqlite3_aggregate_context", uint64(pCtx), size))
|
||||
|
||||
// Try loading the handle, if we already have one, or want a new one.
|
||||
if ptr != 0 || size != 0 {
|
||||
// If we already have an aggregate, return it.
|
||||
if ptr != 0 {
|
||||
if handle := util.ReadUint32(db.mod, ptr); handle != 0 {
|
||||
fn := util.GetHandle(db.ctx, handle)
|
||||
fn := util.GetHandle(db.ctx, handle).(AggregateFunction)
|
||||
if close != nil {
|
||||
*close = handle
|
||||
}
|
||||
if fn != nil {
|
||||
return fn
|
||||
}
|
||||
return fn
|
||||
}
|
||||
}
|
||||
|
||||
// Create a new aggregate and store the handle.
|
||||
fn := callbackHandle(db, pCtx).(func() AggregateFunction)()
|
||||
// Create a new aggregate, and store it if needed.
|
||||
fn := userDataHandle(db, pCtx).(func() AggregateFunction)()
|
||||
if ptr != 0 {
|
||||
util.WriteUint32(db.mod, ptr, util.AddHandle(db.ctx, fn))
|
||||
}
|
||||
|
||||
@@ -74,7 +74,7 @@ func ExampleConn_CreateFunction() {
|
||||
}
|
||||
|
||||
err = db.CreateFunction("upper", 1, sqlite3.DETERMINISTIC|sqlite3.INNOCUOUS, func(ctx sqlite3.Context, arg ...sqlite3.Value) {
|
||||
ctx.ResultBlob(bytes.ToUpper(arg[0].RawBlob()))
|
||||
ctx.ResultRawText(bytes.ToUpper(arg[0].RawText()))
|
||||
})
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
@@ -129,7 +129,7 @@ func ExampleContext_SetAuxData() {
|
||||
ctx.SetAuxData(0, r)
|
||||
re = r
|
||||
}
|
||||
ctx.ResultBool(re.Match(arg[1].RawBlob()))
|
||||
ctx.ResultBool(re.Match(arg[1].RawText()))
|
||||
})
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
|
||||
2
go.mod
2
go.mod
@@ -7,7 +7,7 @@ require (
|
||||
github.com/psanford/httpreadat v0.1.0
|
||||
github.com/tetratelabs/wazero v1.5.0
|
||||
golang.org/x/sync v0.5.0
|
||||
golang.org/x/sys v0.14.0
|
||||
golang.org/x/sys v0.15.0
|
||||
golang.org/x/text v0.14.0
|
||||
)
|
||||
|
||||
|
||||
4
go.sum
4
go.sum
@@ -6,7 +6,7 @@ github.com/tetratelabs/wazero v1.5.0 h1:Yz3fZHivfDiZFUXnWMPUoiW7s8tC1sjdBtlJn08q
|
||||
github.com/tetratelabs/wazero v1.5.0/go.mod h1:0U0G41+ochRKoPKCJlh0jMg1CHkyfK8kDqiirMmKY8A=
|
||||
golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE=
|
||||
golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sys v0.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q=
|
||||
golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
|
||||
golang.org/x/sys v0.15.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,4 +1,3 @@
|
||||
github.com/ncruces/go-sqlite3 v0.9.1/go.mod h1:jFoUbaCDNUS1KN5ZgFxN7bgcWoWfO0EOKeik9QAHZ08=
|
||||
golang.org/x/mod v0.8.0 h1:LUYupSeNrTNCGzR/hVBk2NHZO4hXcVaW1k4Qx7rjPx8=
|
||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/tools v0.6.0 h1:BOw41kyTf3PuCW1pVQf8+Cyg8pMlkYB1oo9iJ6D/lKM=
|
||||
|
||||
@@ -14,13 +14,4 @@ import (
|
||||
db, err := gorm.Open(gormlite.Open("gorm.db"), &gorm.Config{})
|
||||
```
|
||||
|
||||
Checkout [https://gorm.io](https://gorm.io) for details.
|
||||
|
||||
### Foreign-key constraint activation
|
||||
|
||||
Foreign-key constraint is disabled by default in SQLite. To activate it, use connection URL parameter:
|
||||
```go
|
||||
db, err := gorm.Open(gormlite.Open(
|
||||
"file:gorm.db?_pragma=busy_timeout(10000)&_pragma=foreign_keys(1)"),
|
||||
&gorm.Config{})
|
||||
```
|
||||
Checkout [https://gorm.io](https://gorm.io) for details.
|
||||
@@ -13,7 +13,7 @@ import (
|
||||
|
||||
var (
|
||||
sqliteSeparator = "`|\"|'|\t"
|
||||
indexRegexp = regexp.MustCompile(fmt.Sprintf(`(?is)CREATE(?: UNIQUE)? INDEX [%v]?[\w\d-]+[%v]? ON (.*)$`, 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))
|
||||
columnsRegexp = regexp.MustCompile(fmt.Sprintf(`[(,][%v]?(\w+)[%v]?`, sqliteSeparator, sqliteSeparator))
|
||||
|
||||
@@ -97,6 +97,24 @@ func TestParseDDL(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"index with \n from .schema sqlite",
|
||||
[]string{
|
||||
"CREATE TABLE `test-d` (`field` integer NOT NULL)",
|
||||
"CREATE INDEX `idx_uq`\n 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: false, Valid: true},
|
||||
NullableValue: sql.NullBool{Bool: false, Valid: true},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, p := range params {
|
||||
|
||||
@@ -3,14 +3,14 @@ module github.com/ncruces/go-sqlite3/gormlite
|
||||
go 1.21
|
||||
|
||||
require (
|
||||
github.com/ncruces/go-sqlite3 v0.9.1
|
||||
github.com/ncruces/go-sqlite3 v0.10.5
|
||||
gorm.io/gorm v1.25.5
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||
github.com/jinzhu/now v1.1.5 // indirect
|
||||
github.com/ncruces/julianday v0.1.5 // indirect
|
||||
github.com/ncruces/julianday v1.0.0 // indirect
|
||||
github.com/tetratelabs/wazero v1.5.0 // indirect
|
||||
golang.org/x/sys v0.13.0 // indirect
|
||||
golang.org/x/sys v0.14.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.9.1 h1:kV7Zy+ZNyHMfMyZeWc1Yyq+wtgYZDZdp2qAA/wfeMWo=
|
||||
github.com/ncruces/go-sqlite3 v0.9.1/go.mod h1:jFoUbaCDNUS1KN5ZgFxN7bgcWoWfO0EOKeik9QAHZ08=
|
||||
github.com/ncruces/julianday v0.1.5 h1:hDJ9ejiMp3DHsoZ5KW4c1lwfMjbARS7u/gbYcd0FBZk=
|
||||
github.com/ncruces/julianday v0.1.5/go.mod h1:Dusn2KvZrrovOMJuOt0TNXL6tB7U2E8kvza5fFc9G7g=
|
||||
github.com/ncruces/go-sqlite3 v0.10.5 h1:SPnFFYajDfhTuJNjeNwdOhwVCRSAqB1PdSHsGrdfYjw=
|
||||
github.com/ncruces/go-sqlite3 v0.10.5/go.mod h1:8aGu9/G8lLZbvO6TXA0FXTP2liIefFmbpeXuhG4nJLw=
|
||||
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.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
|
||||
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
|
||||
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/sys v0.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q=
|
||||
golang.org/x/sys v0.14.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=
|
||||
|
||||
@@ -50,7 +50,7 @@ func (dialector _Dialector) Initialize(db *gorm.DB) (err error) {
|
||||
if err := db.ConnPool.QueryRowContext(context.Background(), "select sqlite_version()").Scan(&version); err != nil {
|
||||
return err
|
||||
}
|
||||
// https://www.sqlite.org/releaselog/3_35_0.html
|
||||
// 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"},
|
||||
@@ -199,7 +199,7 @@ func (dialector _Dialector) DataTypeOf(field *schema.Field) string {
|
||||
case schema.Int, schema.Uint:
|
||||
if field.AutoIncrement {
|
||||
// doesn't check `PrimaryKey`, to keep backward compatibility
|
||||
// https://www.sqlite.org/autoinc.html
|
||||
// https://sqlite.org/autoinc.html
|
||||
return "integer PRIMARY KEY AUTOINCREMENT"
|
||||
} else {
|
||||
return "integer"
|
||||
|
||||
@@ -20,12 +20,3 @@ diff --git a/tests/tests_test.go b/tests/tests_test.go
|
||||
"gorm.io/driver/sqlserver"
|
||||
"gorm.io/gorm"
|
||||
"gorm.io/gorm/logger"
|
||||
@@ -89,7 +91,7 @@ func OpenTestConnection(cfg *gorm.Config) (db *gorm.DB, err error) {
|
||||
db, err = gorm.Open(mysql.Open(dbDSN), cfg)
|
||||
default:
|
||||
log.Println("testing sqlite3...")
|
||||
- db, err = gorm.Open(sqlite.Open(filepath.Join(os.TempDir(), "gorm.db?_foreign_keys=on")), cfg)
|
||||
+ db, err = gorm.Open(sqlite.Open("file:"+filepath.Join(os.TempDir(), "gorm.db")+"?_pragma=busy_timeout(1000)&_pragma=foreign_keys(1)"), cfg)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package util
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"runtime"
|
||||
"strconv"
|
||||
)
|
||||
@@ -15,9 +14,8 @@ const (
|
||||
OOMErr = ErrorString("sqlite3: out of memory")
|
||||
RangeErr = ErrorString("sqlite3: index out of range")
|
||||
NoNulErr = ErrorString("sqlite3: missing NUL terminator")
|
||||
NoGlobalErr = ErrorString("sqlite3: could not find global: ")
|
||||
NoFuncErr = ErrorString("sqlite3: could not find function: ")
|
||||
BinaryErr = ErrorString("sqlite3: no SQLite binary embed/set/loaded")
|
||||
NoBinaryErr = ErrorString("sqlite3: no SQLite binary embed/set/loaded")
|
||||
BadBinaryErr = ErrorString("sqlite3: invalid SQLite binary embed/set/loaded")
|
||||
TimeErr = ErrorString("sqlite3: invalid time value")
|
||||
WhenceErr = ErrorString("sqlite3: invalid whence")
|
||||
OffsetErr = ErrorString("sqlite3: invalid offset")
|
||||
@@ -35,14 +33,6 @@ func AssertErr() ErrorString {
|
||||
return ErrorString(msg)
|
||||
}
|
||||
|
||||
func Finalizer[T any](skip int) func(*T) {
|
||||
msg := fmt.Sprintf("sqlite3: %T not closed", new(T))
|
||||
if _, file, line, ok := runtime.Caller(skip + 1); ok && skip >= 0 {
|
||||
msg += " (" + file + ":" + strconv.Itoa(line) + ")"
|
||||
}
|
||||
return func(*T) { panic(ErrorString(msg)) }
|
||||
}
|
||||
|
||||
func ErrorCodeString(rc uint32) string {
|
||||
switch rc {
|
||||
case ABORT_ROLLBACK:
|
||||
|
||||
@@ -24,6 +24,17 @@ func View(mod api.Module, ptr uint32, size uint64) []byte {
|
||||
return buf
|
||||
}
|
||||
|
||||
func ReadUint8(mod api.Module, ptr uint32) uint8 {
|
||||
if ptr == 0 {
|
||||
panic(NilErr)
|
||||
}
|
||||
v, ok := mod.Memory().ReadByte(ptr)
|
||||
if !ok {
|
||||
panic(RangeErr)
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
func ReadUint32(mod api.Module, ptr uint32) uint32 {
|
||||
if ptr == 0 {
|
||||
panic(NilErr)
|
||||
@@ -35,6 +46,16 @@ func ReadUint32(mod api.Module, ptr uint32) uint32 {
|
||||
return v
|
||||
}
|
||||
|
||||
func WriteUint8(mod api.Module, ptr uint32, v uint8) {
|
||||
if ptr == 0 {
|
||||
panic(NilErr)
|
||||
}
|
||||
ok := mod.Memory().WriteByte(ptr, v)
|
||||
if !ok {
|
||||
panic(RangeErr)
|
||||
}
|
||||
}
|
||||
|
||||
func WriteUint32(mod api.Module, ptr uint32, v uint32) {
|
||||
if ptr == 0 {
|
||||
panic(NilErr)
|
||||
|
||||
@@ -28,6 +28,20 @@ func TestView_overflow(t *testing.T) {
|
||||
t.Error("want panic")
|
||||
}
|
||||
|
||||
func TestReadUint8_nil(t *testing.T) {
|
||||
defer func() { _ = recover() }()
|
||||
mock := wazerotest.NewModule(wazerotest.NewFixedMemory(wazerotest.PageSize))
|
||||
ReadUint8(mock, 0)
|
||||
t.Error("want panic")
|
||||
}
|
||||
|
||||
func TestReadUint8_range(t *testing.T) {
|
||||
defer func() { _ = recover() }()
|
||||
mock := wazerotest.NewModule(wazerotest.NewFixedMemory(wazerotest.PageSize))
|
||||
ReadUint8(mock, wazerotest.PageSize)
|
||||
t.Error("want panic")
|
||||
}
|
||||
|
||||
func TestReadUint32_nil(t *testing.T) {
|
||||
defer func() { _ = recover() }()
|
||||
mock := wazerotest.NewModule(wazerotest.NewFixedMemory(wazerotest.PageSize))
|
||||
@@ -56,6 +70,20 @@ func TestReadUint64_range(t *testing.T) {
|
||||
t.Error("want panic")
|
||||
}
|
||||
|
||||
func TestWriteUint8_nil(t *testing.T) {
|
||||
defer func() { _ = recover() }()
|
||||
mock := wazerotest.NewModule(wazerotest.NewFixedMemory(wazerotest.PageSize))
|
||||
WriteUint8(mock, 0, 1)
|
||||
t.Error("want panic")
|
||||
}
|
||||
|
||||
func TestWriteUint8_range(t *testing.T) {
|
||||
defer func() { _ = recover() }()
|
||||
mock := wazerotest.NewModule(wazerotest.NewFixedMemory(wazerotest.PageSize))
|
||||
WriteUint8(mock, wazerotest.PageSize, 1)
|
||||
t.Error("want panic")
|
||||
}
|
||||
|
||||
func TestWriteUint32_nil(t *testing.T) {
|
||||
defer func() { _ = recover() }()
|
||||
mock := wazerotest.NewModule(wazerotest.NewFixedMemory(wazerotest.PageSize))
|
||||
|
||||
20
json.go
20
json.go
@@ -9,26 +9,16 @@ import (
|
||||
"github.com/ncruces/go-sqlite3/internal/util"
|
||||
)
|
||||
|
||||
// JSON returns:
|
||||
// a [json.Marshaler] that can be used as an argument to
|
||||
// [database/sql.DB.Exec] and similar methods to
|
||||
// store value as JSON; and
|
||||
// a [database/sql.Scanner] that can be used as an argument to
|
||||
// [database/sql.Row.Scan] and similar methods to
|
||||
// decode JSON into value.
|
||||
// JSON returns a value that can be used as an argument to
|
||||
// [database/sql.DB.Exec], [database/sql.Row.Scan] and similar methods to
|
||||
// store value as JSON, or decode JSON into value.
|
||||
func JSON(value any) any {
|
||||
return jsonValue{value}
|
||||
}
|
||||
|
||||
type jsonValue struct{ any }
|
||||
|
||||
func (j jsonValue) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(j.any)
|
||||
}
|
||||
|
||||
func (j jsonValue) UnmarshalJSON(data []byte) error {
|
||||
return json.Unmarshal(data, j.any)
|
||||
}
|
||||
func (j jsonValue) JSON() any { return j.any }
|
||||
|
||||
func (j jsonValue) Scan(value any) error {
|
||||
var buf []byte
|
||||
@@ -52,5 +42,5 @@ func (j jsonValue) Scan(value any) error {
|
||||
panic(util.AssertErr())
|
||||
}
|
||||
|
||||
return j.UnmarshalJSON(buf)
|
||||
return json.Unmarshal(buf, j.any)
|
||||
}
|
||||
|
||||
@@ -4,11 +4,11 @@ package sqlite3
|
||||
// that can be used as an argument to
|
||||
// [database/sql.DB.Exec] and similar methods.
|
||||
//
|
||||
// https://www.sqlite.org/bindptr.html
|
||||
// https://sqlite.org/bindptr.html
|
||||
func Pointer[T any](val T) any {
|
||||
return pointer[T]{val}
|
||||
}
|
||||
|
||||
type pointer[T any] struct{ val T }
|
||||
|
||||
func (p pointer[T]) Value() any { return p.val }
|
||||
func (p pointer[T]) Pointer() any { return p.val }
|
||||
|
||||
280
sqlite.go
280
sqlite.go
@@ -57,7 +57,7 @@ func compileSQLite() {
|
||||
}
|
||||
}
|
||||
if bin == nil {
|
||||
instance.err = util.BinaryErr
|
||||
instance.err = util.NoBinaryErr
|
||||
return
|
||||
}
|
||||
|
||||
@@ -67,8 +67,9 @@ func compileSQLite() {
|
||||
type sqlite struct {
|
||||
ctx context.Context
|
||||
mod api.Module
|
||||
api sqliteAPI
|
||||
funcs [8]api.Function
|
||||
stack [8]uint64
|
||||
freer uint32
|
||||
}
|
||||
|
||||
func instantiateSQLite() (sqlt *sqlite, err error) {
|
||||
@@ -86,104 +87,12 @@ func instantiateSQLite() (sqlt *sqlite, err error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
getFun := func(name string) api.Function {
|
||||
f := sqlt.mod.ExportedFunction(name)
|
||||
if f == nil {
|
||||
err = util.NoFuncErr + util.ErrorString(name)
|
||||
return nil
|
||||
}
|
||||
return f
|
||||
global := sqlt.mod.ExportedGlobal("malloc_destructor")
|
||||
if global == nil {
|
||||
return nil, util.BadBinaryErr
|
||||
}
|
||||
|
||||
getVal := func(name string) uint32 {
|
||||
g := sqlt.mod.ExportedGlobal(name)
|
||||
if g == nil {
|
||||
err = util.NoGlobalErr + util.ErrorString(name)
|
||||
return 0
|
||||
}
|
||||
return util.ReadUint32(sqlt.mod, uint32(g.Get()))
|
||||
}
|
||||
|
||||
sqlt.api = sqliteAPI{
|
||||
free: getFun("free"),
|
||||
malloc: getFun("malloc"),
|
||||
destructor: getVal("malloc_destructor"),
|
||||
errcode: getFun("sqlite3_errcode"),
|
||||
errstr: getFun("sqlite3_errstr"),
|
||||
errmsg: getFun("sqlite3_errmsg"),
|
||||
erroff: getFun("sqlite3_error_offset"),
|
||||
open: getFun("sqlite3_open_v2"),
|
||||
close: getFun("sqlite3_close"),
|
||||
closeZombie: getFun("sqlite3_close_v2"),
|
||||
prepare: getFun("sqlite3_prepare_v3"),
|
||||
finalize: getFun("sqlite3_finalize"),
|
||||
reset: getFun("sqlite3_reset"),
|
||||
step: getFun("sqlite3_step"),
|
||||
exec: getFun("sqlite3_exec"),
|
||||
interrupt: getFun("sqlite3_interrupt"),
|
||||
progressHandler: getFun("sqlite3_progress_handler_go"),
|
||||
clearBindings: getFun("sqlite3_clear_bindings"),
|
||||
bindCount: getFun("sqlite3_bind_parameter_count"),
|
||||
bindIndex: getFun("sqlite3_bind_parameter_index"),
|
||||
bindName: getFun("sqlite3_bind_parameter_name"),
|
||||
bindNull: getFun("sqlite3_bind_null"),
|
||||
bindInteger: getFun("sqlite3_bind_int64"),
|
||||
bindFloat: getFun("sqlite3_bind_double"),
|
||||
bindText: getFun("sqlite3_bind_text64"),
|
||||
bindBlob: getFun("sqlite3_bind_blob64"),
|
||||
bindZeroBlob: getFun("sqlite3_bind_zeroblob64"),
|
||||
bindPointer: getFun("sqlite3_bind_pointer_go"),
|
||||
columnCount: getFun("sqlite3_column_count"),
|
||||
columnName: getFun("sqlite3_column_name"),
|
||||
columnType: getFun("sqlite3_column_type"),
|
||||
columnInteger: getFun("sqlite3_column_int64"),
|
||||
columnFloat: getFun("sqlite3_column_double"),
|
||||
columnText: getFun("sqlite3_column_text"),
|
||||
columnBlob: getFun("sqlite3_column_blob"),
|
||||
columnBytes: getFun("sqlite3_column_bytes"),
|
||||
blobOpen: getFun("sqlite3_blob_open"),
|
||||
blobClose: getFun("sqlite3_blob_close"),
|
||||
blobReopen: getFun("sqlite3_blob_reopen"),
|
||||
blobBytes: getFun("sqlite3_blob_bytes"),
|
||||
blobRead: getFun("sqlite3_blob_read"),
|
||||
blobWrite: getFun("sqlite3_blob_write"),
|
||||
backupInit: getFun("sqlite3_backup_init"),
|
||||
backupStep: getFun("sqlite3_backup_step"),
|
||||
backupFinish: getFun("sqlite3_backup_finish"),
|
||||
backupRemaining: getFun("sqlite3_backup_remaining"),
|
||||
backupPageCount: getFun("sqlite3_backup_pagecount"),
|
||||
changes: getFun("sqlite3_changes64"),
|
||||
lastRowid: getFun("sqlite3_last_insert_rowid"),
|
||||
autocommit: getFun("sqlite3_get_autocommit"),
|
||||
anyCollation: getFun("sqlite3_anycollseq_init"),
|
||||
createCollation: getFun("sqlite3_create_collation_go"),
|
||||
createFunction: getFun("sqlite3_create_function_go"),
|
||||
createAggregate: getFun("sqlite3_create_aggregate_function_go"),
|
||||
createWindow: getFun("sqlite3_create_window_function_go"),
|
||||
aggregateCtx: getFun("sqlite3_aggregate_context"),
|
||||
userData: getFun("sqlite3_user_data"),
|
||||
setAuxData: getFun("sqlite3_set_auxdata_go"),
|
||||
getAuxData: getFun("sqlite3_get_auxdata"),
|
||||
valueType: getFun("sqlite3_value_type"),
|
||||
valueInteger: getFun("sqlite3_value_int64"),
|
||||
valueFloat: getFun("sqlite3_value_double"),
|
||||
valueText: getFun("sqlite3_value_text"),
|
||||
valueBlob: getFun("sqlite3_value_blob"),
|
||||
valueBytes: getFun("sqlite3_value_bytes"),
|
||||
valuePointer: getFun("sqlite3_value_pointer_go"),
|
||||
resultNull: getFun("sqlite3_result_null"),
|
||||
resultInteger: getFun("sqlite3_result_int64"),
|
||||
resultFloat: getFun("sqlite3_result_double"),
|
||||
resultText: getFun("sqlite3_result_text64"),
|
||||
resultBlob: getFun("sqlite3_result_blob64"),
|
||||
resultZeroBlob: getFun("sqlite3_result_zeroblob64"),
|
||||
resultPointer: getFun("sqlite3_result_pointer_go"),
|
||||
resultValue: getFun("sqlite3_result_value"),
|
||||
resultError: getFun("sqlite3_result_error"),
|
||||
resultErrorCode: getFun("sqlite3_result_error_code"),
|
||||
resultErrorMem: getFun("sqlite3_result_error_nomem"),
|
||||
resultErrorBig: getFun("sqlite3_result_error_toobig"),
|
||||
}
|
||||
sqlt.freer = util.ReadUint32(sqlt.mod, uint32(global.Get()))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -205,17 +114,17 @@ func (sqlt *sqlite) error(rc uint64, handle uint32, sql ...string) error {
|
||||
panic(util.OOMErr)
|
||||
}
|
||||
|
||||
if r := sqlt.call(sqlt.api.errstr, rc); r != 0 {
|
||||
err.str = util.ReadString(sqlt.mod, uint32(r), _MAX_STRING)
|
||||
if r := sqlt.call("sqlite3_errstr", rc); r != 0 {
|
||||
err.str = util.ReadString(sqlt.mod, uint32(r), _MAX_NAME)
|
||||
}
|
||||
|
||||
if handle != 0 {
|
||||
if r := sqlt.call(sqlt.api.errmsg, uint64(handle)); r != 0 {
|
||||
err.msg = util.ReadString(sqlt.mod, uint32(r), _MAX_STRING)
|
||||
if r := sqlt.call("sqlite3_errmsg", uint64(handle)); r != 0 {
|
||||
err.msg = util.ReadString(sqlt.mod, uint32(r), _MAX_NAME)
|
||||
}
|
||||
|
||||
if sql != nil {
|
||||
if r := sqlt.call(sqlt.api.erroff, uint64(handle)); r != math.MaxUint32 {
|
||||
if r := sqlt.call("sqlite3_error_offset", uint64(handle)); r != math.MaxUint32 {
|
||||
err.sql = sql[0][r:]
|
||||
}
|
||||
}
|
||||
@@ -228,12 +137,33 @@ func (sqlt *sqlite) error(rc uint64, handle uint32, sql ...string) error {
|
||||
return &err
|
||||
}
|
||||
|
||||
func (sqlt *sqlite) call(fn api.Function, params ...uint64) uint64 {
|
||||
func (sqlt *sqlite) getfn(name string) (api.Function, uint32) {
|
||||
// https://cr.yp.to/cdb/cdb.txt
|
||||
hash := func(s string) uint32 {
|
||||
var hash uint32 = 5381
|
||||
for _, b := range []byte(s) {
|
||||
hash = (hash<<5 + hash) ^ uint32(b)
|
||||
}
|
||||
return hash
|
||||
}(name) % uint32(len(sqlt.funcs))
|
||||
|
||||
fn := sqlt.funcs[hash]
|
||||
if fn == nil || name != fn.Definition().Name() {
|
||||
fn = sqlt.mod.ExportedFunction(name)
|
||||
} else {
|
||||
sqlt.funcs[hash] = nil
|
||||
}
|
||||
return fn, hash
|
||||
}
|
||||
|
||||
func (sqlt *sqlite) call(name string, params ...uint64) uint64 {
|
||||
copy(sqlt.stack[:], params)
|
||||
fn, hash := sqlt.getfn(name)
|
||||
err := fn.CallWithStack(sqlt.ctx, sqlt.stack[:])
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
sqlt.funcs[hash] = fn
|
||||
return sqlt.stack[0]
|
||||
}
|
||||
|
||||
@@ -241,14 +171,14 @@ func (sqlt *sqlite) free(ptr uint32) {
|
||||
if ptr == 0 {
|
||||
return
|
||||
}
|
||||
sqlt.call(sqlt.api.free, uint64(ptr))
|
||||
sqlt.call("free", uint64(ptr))
|
||||
}
|
||||
|
||||
func (sqlt *sqlite) new(size uint64) uint32 {
|
||||
if size > _MAX_ALLOCATION_SIZE {
|
||||
panic(util.OOMErr)
|
||||
}
|
||||
ptr := uint32(sqlt.call(sqlt.api.malloc, size))
|
||||
ptr := uint32(sqlt.call("malloc", size))
|
||||
if ptr == 0 && size != 0 {
|
||||
panic(util.OOMErr)
|
||||
}
|
||||
@@ -290,17 +220,23 @@ func (a *arena) free() {
|
||||
if a.sqlt == nil {
|
||||
return
|
||||
}
|
||||
a.reset()
|
||||
for _, ptr := range a.ptrs {
|
||||
a.sqlt.free(ptr)
|
||||
}
|
||||
a.sqlt.free(a.base)
|
||||
a.sqlt = nil
|
||||
}
|
||||
|
||||
func (a *arena) reset() {
|
||||
for _, ptr := range a.ptrs {
|
||||
a.sqlt.free(ptr)
|
||||
func (a *arena) mark() (reset func()) {
|
||||
ptrs := len(a.ptrs)
|
||||
next := a.next
|
||||
return func() {
|
||||
for _, ptr := range a.ptrs[ptrs:] {
|
||||
a.sqlt.free(ptr)
|
||||
}
|
||||
a.ptrs = a.ptrs[:ptrs]
|
||||
a.next = next
|
||||
}
|
||||
a.ptrs = nil
|
||||
a.next = 0
|
||||
}
|
||||
|
||||
func (a *arena) new(size uint64) uint32 {
|
||||
@@ -329,95 +265,37 @@ func (a *arena) string(s string) uint32 {
|
||||
return ptr
|
||||
}
|
||||
|
||||
type sqliteAPI struct {
|
||||
free api.Function
|
||||
malloc api.Function
|
||||
errcode api.Function
|
||||
errstr api.Function
|
||||
errmsg api.Function
|
||||
erroff api.Function
|
||||
open api.Function
|
||||
close api.Function
|
||||
closeZombie api.Function
|
||||
prepare api.Function
|
||||
finalize api.Function
|
||||
reset api.Function
|
||||
step api.Function
|
||||
exec api.Function
|
||||
interrupt api.Function
|
||||
progressHandler api.Function
|
||||
clearBindings api.Function
|
||||
bindCount api.Function
|
||||
bindIndex api.Function
|
||||
bindName api.Function
|
||||
bindNull api.Function
|
||||
bindInteger api.Function
|
||||
bindFloat api.Function
|
||||
bindText api.Function
|
||||
bindBlob api.Function
|
||||
bindZeroBlob api.Function
|
||||
bindPointer api.Function
|
||||
columnCount api.Function
|
||||
columnName api.Function
|
||||
columnType api.Function
|
||||
columnInteger api.Function
|
||||
columnFloat api.Function
|
||||
columnText api.Function
|
||||
columnBlob api.Function
|
||||
columnBytes api.Function
|
||||
blobOpen api.Function
|
||||
blobClose api.Function
|
||||
blobReopen api.Function
|
||||
blobBytes api.Function
|
||||
blobRead api.Function
|
||||
blobWrite api.Function
|
||||
backupInit api.Function
|
||||
backupStep api.Function
|
||||
backupFinish api.Function
|
||||
backupRemaining api.Function
|
||||
backupPageCount api.Function
|
||||
changes api.Function
|
||||
lastRowid api.Function
|
||||
autocommit api.Function
|
||||
anyCollation api.Function
|
||||
createCollation api.Function
|
||||
createFunction api.Function
|
||||
createAggregate api.Function
|
||||
createWindow api.Function
|
||||
aggregateCtx api.Function
|
||||
userData api.Function
|
||||
setAuxData api.Function
|
||||
getAuxData api.Function
|
||||
valueType api.Function
|
||||
valueInteger api.Function
|
||||
valueFloat api.Function
|
||||
valueText api.Function
|
||||
valueBlob api.Function
|
||||
valueBytes api.Function
|
||||
valuePointer api.Function
|
||||
resultNull api.Function
|
||||
resultInteger api.Function
|
||||
resultFloat api.Function
|
||||
resultText api.Function
|
||||
resultBlob api.Function
|
||||
resultZeroBlob api.Function
|
||||
resultPointer api.Function
|
||||
resultValue api.Function
|
||||
resultError api.Function
|
||||
resultErrorCode api.Function
|
||||
resultErrorMem api.Function
|
||||
resultErrorBig api.Function
|
||||
destructor uint32
|
||||
}
|
||||
|
||||
func exportCallbacks(env wazero.HostModuleBuilder) wazero.HostModuleBuilder {
|
||||
util.ExportFuncII(env, "go_progress", callbackProgress)
|
||||
util.ExportFuncVI(env, "go_destroy", callbackDestroy)
|
||||
util.ExportFuncIIIIII(env, "go_compare", callbackCompare)
|
||||
util.ExportFuncVIII(env, "go_func", callbackFunc)
|
||||
util.ExportFuncVIII(env, "go_step", callbackStep)
|
||||
util.ExportFuncVI(env, "go_final", callbackFinal)
|
||||
util.ExportFuncVI(env, "go_value", callbackValue)
|
||||
util.ExportFuncVIII(env, "go_inverse", callbackInverse)
|
||||
util.ExportFuncII(env, "go_progress", progressCallback)
|
||||
util.ExportFuncVI(env, "go_destroy", destroyCallback)
|
||||
util.ExportFuncVIII(env, "go_func", funcCallback)
|
||||
util.ExportFuncVIII(env, "go_step", stepCallback)
|
||||
util.ExportFuncVI(env, "go_final", finalCallback)
|
||||
util.ExportFuncVI(env, "go_value", valueCallback)
|
||||
util.ExportFuncVIII(env, "go_inverse", inverseCallback)
|
||||
util.ExportFuncIIIIII(env, "go_compare", compareCallback)
|
||||
util.ExportFuncIIIIII(env, "go_vtab_create", vtabModuleCallback(0))
|
||||
util.ExportFuncIIIIII(env, "go_vtab_connect", vtabModuleCallback(1))
|
||||
util.ExportFuncII(env, "go_vtab_disconnect", vtabDisconnectCallback)
|
||||
util.ExportFuncII(env, "go_vtab_destroy", vtabDestroyCallback)
|
||||
util.ExportFuncIII(env, "go_vtab_best_index", vtabBestIndexCallback)
|
||||
util.ExportFuncIIIII(env, "go_vtab_update", vtabUpdateCallback)
|
||||
util.ExportFuncIII(env, "go_vtab_rename", vtabRenameCallback)
|
||||
util.ExportFuncIIIII(env, "go_vtab_find_function", vtabFindFuncCallback)
|
||||
util.ExportFuncII(env, "go_vtab_begin", vtabBeginCallback)
|
||||
util.ExportFuncII(env, "go_vtab_sync", vtabSyncCallback)
|
||||
util.ExportFuncII(env, "go_vtab_commit", vtabCommitCallback)
|
||||
util.ExportFuncII(env, "go_vtab_rollback", vtabRollbackCallback)
|
||||
util.ExportFuncIII(env, "go_vtab_savepoint", vtabSavepointCallback)
|
||||
util.ExportFuncIII(env, "go_vtab_release", vtabReleaseCallback)
|
||||
util.ExportFuncIII(env, "go_vtab_rollback_to", vtabRollbackToCallback)
|
||||
util.ExportFuncIIIIII(env, "go_vtab_integrity", vtabIntegrityCallback)
|
||||
util.ExportFuncIII(env, "go_cur_open", cursorOpenCallback)
|
||||
util.ExportFuncII(env, "go_cur_close", cursorCloseCallback)
|
||||
util.ExportFuncIIIIII(env, "go_cur_filter", cursorFilterCallback)
|
||||
util.ExportFuncII(env, "go_cur_next", cursorNextCallback)
|
||||
util.ExportFuncII(env, "go_cur_eof", cursorEOFCallback)
|
||||
util.ExportFuncIIII(env, "go_cur_column", cursorColumnCallback)
|
||||
util.ExportFuncIII(env, "go_cur_rowid", cursorRowIDCallback)
|
||||
return env
|
||||
}
|
||||
|
||||
@@ -3,33 +3,33 @@ set -euo pipefail
|
||||
|
||||
cd -P -- "$(dirname -- "$0")"
|
||||
|
||||
curl -#OL "https://sqlite.org/2023/sqlite-amalgamation-3440000.zip"
|
||||
curl -#OL "https://sqlite.org/2023/sqlite-amalgamation-3440200.zip"
|
||||
unzip -d . sqlite-amalgamation-*.zip
|
||||
mv sqlite-amalgamation-*/sqlite3* .
|
||||
rm -rf sqlite-amalgamation-*
|
||||
|
||||
cat *.patch | patch --posix
|
||||
cat *.patch | patch --no-backup-if-mismatch
|
||||
|
||||
mkdir -p ext/
|
||||
cd ext/
|
||||
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.44.0/ext/misc/decimal.c"
|
||||
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.44.0/ext/misc/uint.c"
|
||||
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.44.0/ext/misc/uuid.c"
|
||||
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.44.0/ext/misc/base64.c"
|
||||
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.44.0/ext/misc/regexp.c"
|
||||
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.44.0/ext/misc/series.c"
|
||||
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.44.0/ext/misc/anycollseq.c"
|
||||
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.44.2/ext/misc/decimal.c"
|
||||
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.44.2/ext/misc/uint.c"
|
||||
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.44.2/ext/misc/uuid.c"
|
||||
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.44.2/ext/misc/base64.c"
|
||||
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.44.2/ext/misc/regexp.c"
|
||||
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.44.2/ext/misc/series.c"
|
||||
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.44.2/ext/misc/anycollseq.c"
|
||||
cd ~-
|
||||
|
||||
cd ../vfs/tests/mptest/testdata/
|
||||
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.44.0/mptest/mptest.c"
|
||||
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.44.0/mptest/config01.test"
|
||||
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.44.0/mptest/config02.test"
|
||||
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.44.0/mptest/crash01.test"
|
||||
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.44.0/mptest/crash02.subtest"
|
||||
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.44.0/mptest/multiwrite01.test"
|
||||
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.44.2/mptest/mptest.c"
|
||||
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.44.2/mptest/config01.test"
|
||||
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.44.2/mptest/config02.test"
|
||||
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.44.2/mptest/crash01.test"
|
||||
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.44.2/mptest/crash02.subtest"
|
||||
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.44.2/mptest/multiwrite01.test"
|
||||
cd ~-
|
||||
|
||||
cd ../vfs/tests/speedtest1/testdata/
|
||||
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.44.0/test/speedtest1.c"
|
||||
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.44.2/test/speedtest1.c"
|
||||
cd ~-
|
||||
@@ -1,55 +1,44 @@
|
||||
#include <stddef.h>
|
||||
|
||||
#include "include.h"
|
||||
#include "sqlite3.h"
|
||||
|
||||
int go_compare(void *, int, const void *, int, const void *);
|
||||
void go_func(sqlite3_context *, int, sqlite3_value **);
|
||||
void go_step(sqlite3_context *, int, sqlite3_value **);
|
||||
void go_final(sqlite3_context *);
|
||||
void go_value(sqlite3_context *);
|
||||
void go_inverse(sqlite3_context *, int, sqlite3_value **);
|
||||
void go_destroy(void *);
|
||||
|
||||
int sqlite3_create_collation_go(sqlite3 *db, const char *zName, void *pApp) {
|
||||
return sqlite3_create_collation_v2(db, zName, SQLITE_UTF8, pApp, go_compare,
|
||||
go_destroy);
|
||||
int go_compare(go_handle, int, const void *, int, const void *);
|
||||
|
||||
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);
|
||||
if (rc) go_destroy(app);
|
||||
return rc;
|
||||
}
|
||||
|
||||
int sqlite3_create_function_go(sqlite3 *db, const char *zName, int nArg,
|
||||
int flags, void *pApp) {
|
||||
return sqlite3_create_function_v2(db, zName, nArg, SQLITE_UTF8 | flags, pApp,
|
||||
int sqlite3_create_function_go(sqlite3 *db, const char *name, int argc,
|
||||
int flags, go_handle app) {
|
||||
return sqlite3_create_function_v2(db, name, argc, SQLITE_UTF8 | flags, app,
|
||||
go_func, /*step=*/NULL, /*final=*/NULL,
|
||||
go_destroy);
|
||||
}
|
||||
|
||||
int sqlite3_create_aggregate_function_go(sqlite3 *db, const char *zName,
|
||||
int nArg, int flags, void *pApp) {
|
||||
return sqlite3_create_window_function(db, zName, nArg, SQLITE_UTF8 | flags,
|
||||
pApp, go_step, go_final, /*value=*/NULL,
|
||||
int sqlite3_create_aggregate_function_go(sqlite3 *db, const char *name,
|
||||
int argc, int flags, go_handle app) {
|
||||
return sqlite3_create_window_function(db, name, argc, SQLITE_UTF8 | flags,
|
||||
app, go_step, go_final, /*value=*/NULL,
|
||||
/*inverse=*/NULL, go_destroy);
|
||||
}
|
||||
|
||||
int sqlite3_create_window_function_go(sqlite3 *db, const char *zName, int nArg,
|
||||
int flags, void *pApp) {
|
||||
return sqlite3_create_window_function(db, zName, nArg, SQLITE_UTF8 | flags,
|
||||
pApp, go_step, go_final, go_value,
|
||||
int sqlite3_create_window_function_go(sqlite3 *db, const char *name, int argc,
|
||||
int flags, go_handle app) {
|
||||
return sqlite3_create_window_function(db, name, argc, SQLITE_UTF8 | flags,
|
||||
app, go_step, go_final, go_value,
|
||||
go_inverse, go_destroy);
|
||||
}
|
||||
|
||||
void sqlite3_set_auxdata_go(sqlite3_context *ctx, int iArg, void *pAux) {
|
||||
sqlite3_set_auxdata(ctx, iArg, pAux, go_destroy);
|
||||
}
|
||||
|
||||
#define GO_POINTER_TYPE "github.com/ncruces/go-sqlite3.Pointer"
|
||||
|
||||
int sqlite3_bind_pointer_go(sqlite3_stmt *stmt, int i, void *pApp) {
|
||||
return sqlite3_bind_pointer(stmt, i, pApp, GO_POINTER_TYPE, go_destroy);
|
||||
}
|
||||
|
||||
void sqlite3_result_pointer_go(sqlite3_context *ctx, void *pApp) {
|
||||
sqlite3_result_pointer(ctx, pApp, GO_POINTER_TYPE, go_destroy);
|
||||
}
|
||||
|
||||
void *sqlite3_value_pointer_go(sqlite3_value *val) {
|
||||
return sqlite3_value_pointer(val, GO_POINTER_TYPE);
|
||||
}
|
||||
void sqlite3_set_auxdata_go(sqlite3_context *ctx, int i, go_handle aux) {
|
||||
sqlite3_set_auxdata(ctx, i, aux, go_destroy);
|
||||
}
|
||||
12
sqlite3/include.h
Normal file
12
sqlite3/include.h
Normal file
@@ -0,0 +1,12 @@
|
||||
#pragma once
|
||||
|
||||
#include <assert.h>
|
||||
#include <stddef.h>
|
||||
|
||||
// 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)))
|
||||
|
||||
typedef void *go_handle;
|
||||
void go_destroy(go_handle);
|
||||
static_assert(sizeof(go_handle) == 4, "Unexpected size");
|
||||
@@ -1,7 +1,5 @@
|
||||
// Amalgamation
|
||||
#include "sqlite3.c"
|
||||
// VFS
|
||||
#include "vfs.c"
|
||||
// Extensions
|
||||
#include "ext/anycollseq.c"
|
||||
#include "ext/base64.c"
|
||||
@@ -10,9 +8,15 @@
|
||||
#include "ext/series.c"
|
||||
#include "ext/uint.c"
|
||||
#include "ext/uuid.c"
|
||||
// Bindings
|
||||
#include "func.c"
|
||||
#include "pointer.c"
|
||||
#include "progress.c"
|
||||
#include "time.c"
|
||||
#include "vfs.c"
|
||||
#include "vtab.c"
|
||||
|
||||
sqlite3_destructor_type malloc_destructor = &free;
|
||||
|
||||
__attribute__((constructor)) void init() {
|
||||
sqlite3_initialize();
|
||||
|
||||
17
sqlite3/pointer.c
Normal file
17
sqlite3/pointer.c
Normal file
@@ -0,0 +1,17 @@
|
||||
|
||||
#include "include.h"
|
||||
#include "sqlite3.h"
|
||||
|
||||
#define GO_POINTER_TYPE "github.com/ncruces/go-sqlite3.Pointer"
|
||||
|
||||
int sqlite3_bind_pointer_go(sqlite3_stmt *stmt, int i, go_handle app) {
|
||||
return sqlite3_bind_pointer(stmt, i, app, GO_POINTER_TYPE, go_destroy);
|
||||
}
|
||||
|
||||
void sqlite3_result_pointer_go(sqlite3_context *ctx, go_handle app) {
|
||||
sqlite3_result_pointer(ctx, app, GO_POINTER_TYPE, go_destroy);
|
||||
}
|
||||
|
||||
go_handle sqlite3_value_pointer_go(sqlite3_value *val) {
|
||||
return sqlite3_value_pointer(val, GO_POINTER_TYPE);
|
||||
}
|
||||
@@ -37,25 +37,33 @@
|
||||
#define SQLITE_DEFAULT_WAL_SYNCHRONOUS 1
|
||||
#define SQLITE_LIKE_DOESNT_MATCH_BLOBS
|
||||
#define SQLITE_MAX_EXPR_DEPTH 0
|
||||
#define SQLITE_OMIT_DECLTYPE
|
||||
#define SQLITE_USE_ALLOCA
|
||||
#define SQLITE_OMIT_DEPRECATED
|
||||
#define SQLITE_OMIT_SHARED_CACHE
|
||||
#define SQLITE_OMIT_AUTOINIT
|
||||
#define SQLITE_USE_ALLOCA
|
||||
// #define SQLITE_OMIT_DECLTYPE
|
||||
// #define SQLITE_OMIT_PROGRESS_CALLBACK
|
||||
|
||||
// Other Options
|
||||
|
||||
#define SQLITE_ALLOW_URI_AUTHORITY
|
||||
#define SQLITE_ENABLE_BATCH_ATOMIC_WRITE
|
||||
#define SQLITE_TRUSTED_SCHEMA 0
|
||||
#define SQLITE_DEFAULT_FOREIGN_KEYS 1
|
||||
#define SQLITE_ENABLE_ATOMIC_WRITE
|
||||
#define SQLITE_OMIT_DESERIALIZE
|
||||
#define SQLITE_ENABLE_BATCH_ATOMIC_WRITE
|
||||
|
||||
// Because WASM does not support shared memory,
|
||||
// SQLite disables WAL for WASM builds.
|
||||
// We patch SQLite to use exclusive locking mode instead.
|
||||
// https://www.sqlite.org/wal.html#noshm
|
||||
// https://sqlite.org/wal.html#noshm
|
||||
#undef SQLITE_OMIT_WAL
|
||||
|
||||
// We have our own memdb VFS.
|
||||
// To avoid interactions between the two,
|
||||
// omit sqlite3_serialize/sqlite3_deserialize,
|
||||
// which we also don't wrap.
|
||||
#define SQLITE_OMIT_DESERIALIZE
|
||||
|
||||
// Amalgamated Extensions
|
||||
|
||||
#define SQLITE_ENABLE_MATH_FUNCTIONS 1
|
||||
|
||||
@@ -82,7 +82,7 @@ int sqlite3_time_init(sqlite3 *db, char **pzErrMsg,
|
||||
time_collation,
|
||||
/*destroy=*/NULL);
|
||||
sqlite3_create_function_v2(
|
||||
db, "json_time", -1,
|
||||
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;
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
#include <stddef.h>
|
||||
#include <time.h>
|
||||
|
||||
#include "include.h"
|
||||
#include "sqlite3.h"
|
||||
|
||||
int go_localtime(struct tm *, sqlite3_int64);
|
||||
@@ -9,7 +10,6 @@ int go_vfs_find(const char *zVfsName);
|
||||
|
||||
int go_randomness(sqlite3_vfs *, int nByte, char *zOut);
|
||||
int go_sleep(sqlite3_vfs *, int microseconds);
|
||||
int go_current_time(sqlite3_vfs *, double *);
|
||||
int go_current_time_64(sqlite3_vfs *, sqlite3_int64 *);
|
||||
|
||||
int go_open(sqlite3_vfs *, sqlite3_filename zName, sqlite3_file *, int flags,
|
||||
@@ -60,7 +60,7 @@ static int go_open_wrapper(sqlite3_vfs *vfs, sqlite3_filename zName,
|
||||
|
||||
struct go_file {
|
||||
sqlite3_file base;
|
||||
int handle;
|
||||
go_handle handle;
|
||||
};
|
||||
|
||||
int sqlite3_os_init() {
|
||||
@@ -77,24 +77,21 @@ int sqlite3_os_init() {
|
||||
|
||||
.xRandomness = go_randomness,
|
||||
.xSleep = go_sleep,
|
||||
.xCurrentTime = go_current_time,
|
||||
.xCurrentTimeInt64 = go_current_time_64,
|
||||
};
|
||||
return sqlite3_vfs_register(&os_vfs, /*default=*/true);
|
||||
}
|
||||
|
||||
sqlite3_destructor_type malloc_destructor = &free;
|
||||
|
||||
int localtime_s(struct tm *const pTm, time_t const *const pTime) {
|
||||
return go_localtime(pTm, (sqlite3_int64)*pTime);
|
||||
}
|
||||
|
||||
sqlite3_vfs *sqlite3_vfs_find(const char *zVfsName) {
|
||||
if (zVfsName) {
|
||||
if (zVfsName && go_vfs_find(zVfsName)) {
|
||||
static sqlite3_vfs *go_vfs_list;
|
||||
|
||||
for (sqlite3_vfs *it = go_vfs_list; it; it = it->pNext) {
|
||||
if (!strcmp(zVfsName, it->zName) && go_vfs_find(it->zName)) {
|
||||
if (!strcmp(zVfsName, it->zName)) {
|
||||
return it;
|
||||
}
|
||||
}
|
||||
@@ -109,30 +106,27 @@ sqlite3_vfs *sqlite3_vfs_find(const char *zVfsName) {
|
||||
}
|
||||
}
|
||||
|
||||
if (go_vfs_find(zVfsName)) {
|
||||
sqlite3_vfs *head = go_vfs_list;
|
||||
go_vfs_list = malloc(sizeof(sqlite3_vfs) + strlen(zVfsName) + 1);
|
||||
char *name = (char *)(go_vfs_list + 1);
|
||||
strcpy(name, zVfsName);
|
||||
*go_vfs_list = (sqlite3_vfs){
|
||||
.iVersion = 2,
|
||||
.szOsFile = sizeof(struct go_file),
|
||||
.mxPathname = 512,
|
||||
.zName = name,
|
||||
.pNext = head,
|
||||
sqlite3_vfs *head = go_vfs_list;
|
||||
go_vfs_list = malloc(sizeof(sqlite3_vfs) + strlen(zVfsName) + 1);
|
||||
char *name = (char *)(go_vfs_list + 1);
|
||||
strcpy(name, zVfsName);
|
||||
*go_vfs_list = (sqlite3_vfs){
|
||||
.iVersion = 2,
|
||||
.szOsFile = sizeof(struct go_file),
|
||||
.mxPathname = 512,
|
||||
.zName = name,
|
||||
.pNext = head,
|
||||
|
||||
.xOpen = go_open_wrapper,
|
||||
.xDelete = go_delete,
|
||||
.xAccess = go_access,
|
||||
.xFullPathname = go_full_pathname,
|
||||
.xOpen = go_open_wrapper,
|
||||
.xDelete = go_delete,
|
||||
.xAccess = go_access,
|
||||
.xFullPathname = go_full_pathname,
|
||||
|
||||
.xRandomness = go_randomness,
|
||||
.xSleep = go_sleep,
|
||||
.xCurrentTime = go_current_time,
|
||||
.xCurrentTimeInt64 = go_current_time_64,
|
||||
};
|
||||
return go_vfs_list;
|
||||
}
|
||||
.xRandomness = go_randomness,
|
||||
.xSleep = go_sleep,
|
||||
.xCurrentTimeInt64 = go_current_time_64,
|
||||
};
|
||||
return go_vfs_list;
|
||||
}
|
||||
return sqlite3_vfs_find_orig(zVfsName);
|
||||
}
|
||||
|
||||
230
sqlite3/vtab.c
Normal file
230
sqlite3/vtab.c
Normal file
@@ -0,0 +1,230 @@
|
||||
#include <stddef.h>
|
||||
|
||||
#include "include.h"
|
||||
#include "sqlite3.h"
|
||||
|
||||
#define SQLITE_VTAB_CREATOR_GO /******/ 0x01
|
||||
#define SQLITE_VTAB_DESTROYER_GO /****/ 0x02
|
||||
#define SQLITE_VTAB_UPDATER_GO /******/ 0x04
|
||||
#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_SAVEPOINTER_GO /**/ 0x80
|
||||
|
||||
int go_vtab_create(sqlite3_module *, int argc, const char *const *argv,
|
||||
sqlite3_vtab **, char **pzErr);
|
||||
int go_vtab_connect(sqlite3_module *, int argc, const char *const *argv,
|
||||
sqlite3_vtab **, char **pzErr);
|
||||
|
||||
int go_vtab_disconnect(sqlite3_vtab *);
|
||||
int go_vtab_destroy(sqlite3_vtab *);
|
||||
int go_vtab_best_index(sqlite3_vtab *, sqlite3_index_info *);
|
||||
int go_cur_open(sqlite3_vtab *, sqlite3_vtab_cursor **);
|
||||
|
||||
int go_cur_close(sqlite3_vtab_cursor *);
|
||||
int go_cur_filter(sqlite3_vtab_cursor *, int idxNum, const char *idxStr,
|
||||
int argc, sqlite3_value **argv);
|
||||
int go_cur_next(sqlite3_vtab_cursor *);
|
||||
int go_cur_eof(sqlite3_vtab_cursor *);
|
||||
int go_cur_column(sqlite3_vtab_cursor *, sqlite3_context *, int);
|
||||
int go_cur_rowid(sqlite3_vtab_cursor *, sqlite3_int64 *pRowid);
|
||||
|
||||
int go_vtab_update(sqlite3_vtab *, int, sqlite3_value **, sqlite3_int64 *);
|
||||
int go_vtab_rename(sqlite3_vtab *, const char *zNew);
|
||||
int go_vtab_find_function(sqlite3_vtab *, int nArg, const char *zName,
|
||||
go_handle *pxFunc);
|
||||
|
||||
int go_vtab_begin(sqlite3_vtab *);
|
||||
int go_vtab_sync(sqlite3_vtab *);
|
||||
int go_vtab_commit(sqlite3_vtab *);
|
||||
int go_vtab_rollback(sqlite3_vtab *);
|
||||
|
||||
int go_vtab_savepoint(sqlite3_vtab *, int);
|
||||
int go_vtab_release(sqlite3_vtab *, int);
|
||||
int go_vtab_rollback_to(sqlite3_vtab *, int);
|
||||
|
||||
int go_vtab_integrity(sqlite3_vtab *, const char *zSchema, const char *zTabName,
|
||||
int mFlags, char **pzErr);
|
||||
|
||||
struct go_module {
|
||||
go_handle handle;
|
||||
sqlite3_module base;
|
||||
};
|
||||
|
||||
struct go_vtab {
|
||||
go_handle handle;
|
||||
sqlite3_vtab base;
|
||||
};
|
||||
|
||||
struct go_cursor {
|
||||
go_handle handle;
|
||||
sqlite3_vtab_cursor base;
|
||||
};
|
||||
|
||||
static void go_mod_destroy(void *pAux) {
|
||||
struct go_module *mod = pAux;
|
||||
void *handle = mod->handle;
|
||||
free(mod);
|
||||
go_destroy(handle);
|
||||
}
|
||||
|
||||
static int go_vtab_create_wrapper(sqlite3 *db, void *pAux, int argc,
|
||||
const char *const *argv,
|
||||
sqlite3_vtab **ppVTab, char **pzErr) {
|
||||
struct go_vtab *vtab = calloc(1, sizeof(struct go_vtab));
|
||||
if (vtab == NULL) return SQLITE_NOMEM;
|
||||
*ppVTab = &vtab->base;
|
||||
|
||||
struct go_module *mod = pAux;
|
||||
int rc = go_vtab_create(&mod->base, argc, argv, ppVTab, pzErr);
|
||||
if (rc) {
|
||||
if (*pzErr) *pzErr = sqlite3_mprintf("%s", *pzErr);
|
||||
free(vtab);
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int go_vtab_connect_wrapper(sqlite3 *db, void *pAux, int argc,
|
||||
const char *const *argv,
|
||||
sqlite3_vtab **ppVTab, char **pzErr) {
|
||||
struct go_vtab *vtab = calloc(1, sizeof(struct go_vtab));
|
||||
if (vtab == NULL) return SQLITE_NOMEM;
|
||||
*ppVTab = &vtab->base;
|
||||
|
||||
struct go_module *mod = pAux;
|
||||
int rc = go_vtab_connect(&mod->base, argc, argv, ppVTab, pzErr);
|
||||
if (rc) {
|
||||
free(vtab);
|
||||
if (*pzErr) *pzErr = sqlite3_mprintf("%s", *pzErr);
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int go_vtab_disconnect_wrapper(sqlite3_vtab *pVTab) {
|
||||
struct go_vtab *vtab = container_of(pVTab, struct go_vtab, base);
|
||||
int rc = go_vtab_disconnect(pVTab);
|
||||
free(vtab);
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int go_vtab_destroy_wrapper(sqlite3_vtab *pVTab) {
|
||||
struct go_vtab *vtab = container_of(pVTab, struct go_vtab, base);
|
||||
int rc = go_vtab_destroy(pVTab);
|
||||
free(vtab);
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int go_cur_open_wrapper(sqlite3_vtab *pVTab,
|
||||
sqlite3_vtab_cursor **ppCursor) {
|
||||
struct go_cursor *cur = calloc(1, sizeof(struct go_cursor));
|
||||
if (cur == NULL) return SQLITE_NOMEM;
|
||||
*ppCursor = &cur->base;
|
||||
|
||||
int rc = go_cur_open(pVTab, ppCursor);
|
||||
if (rc) free(cur);
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int go_cur_close_wrapper(sqlite3_vtab_cursor *pCursor) {
|
||||
struct go_cursor *cur = container_of(pCursor, struct go_cursor, base);
|
||||
int rc = go_cur_close(pCursor);
|
||||
free(cur);
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int go_vtab_find_function_wrapper(
|
||||
sqlite3_vtab *pVTab, int nArg, const char *zName,
|
||||
void (**pxFunc)(sqlite3_context *, int, sqlite3_value **), void **ppArg) {
|
||||
struct go_vtab *vtab = container_of(pVTab, struct go_vtab, base);
|
||||
|
||||
go_handle handle;
|
||||
int rc = go_vtab_find_function(pVTab, nArg, zName, &handle);
|
||||
if (rc) {
|
||||
*pxFunc = go_func;
|
||||
*ppArg = handle;
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int go_vtab_integrity_wrapper(sqlite3_vtab *pVTab, const char *zSchema,
|
||||
const char *zTabName, int mFlags,
|
||||
char **pzErr) {
|
||||
int rc = go_vtab_integrity(pVTab, zSchema, zTabName, mFlags, pzErr);
|
||||
if (rc && *pzErr) *pzErr = sqlite3_mprintf("%s", *pzErr);
|
||||
return rc;
|
||||
}
|
||||
|
||||
int sqlite3_create_module_go(sqlite3 *db, const char *zName, int flags,
|
||||
go_handle handle) {
|
||||
struct go_module *mod = malloc(sizeof(struct go_module));
|
||||
if (mod == NULL) {
|
||||
go_destroy(handle);
|
||||
return SQLITE_NOMEM;
|
||||
}
|
||||
|
||||
mod->handle = handle;
|
||||
mod->base = (sqlite3_module){
|
||||
.iVersion = 4,
|
||||
.xConnect = go_vtab_connect_wrapper,
|
||||
.xDisconnect = go_vtab_disconnect_wrapper,
|
||||
.xBestIndex = go_vtab_best_index,
|
||||
.xOpen = go_cur_open_wrapper,
|
||||
.xClose = go_cur_close_wrapper,
|
||||
.xFilter = go_cur_filter,
|
||||
.xNext = go_cur_next,
|
||||
.xEof = go_cur_eof,
|
||||
.xColumn = go_cur_column,
|
||||
.xRowid = go_cur_rowid,
|
||||
};
|
||||
if (flags & SQLITE_VTAB_CREATOR_GO) {
|
||||
mod->base.xCreate = go_vtab_create_wrapper;
|
||||
}
|
||||
if (flags & SQLITE_VTAB_DESTROYER_GO) {
|
||||
mod->base.xDestroy = go_vtab_destroy_wrapper;
|
||||
}
|
||||
if (flags & SQLITE_VTAB_UPDATER_GO) {
|
||||
mod->base.xUpdate = go_vtab_update;
|
||||
}
|
||||
if (flags & SQLITE_VTAB_RENAMER_GO) {
|
||||
mod->base.xRename = go_vtab_rename;
|
||||
}
|
||||
if (flags & SQLITE_VTAB_OVERLOADER_GO) {
|
||||
mod->base.xFindFunction = go_vtab_find_function_wrapper;
|
||||
}
|
||||
if (flags & SQLITE_VTAB_CHECKER_GO) {
|
||||
mod->base.xIntegrity = go_vtab_integrity_wrapper;
|
||||
}
|
||||
if (flags & SQLITE_VTAB_TX_GO) {
|
||||
mod->base.xBegin = go_vtab_begin;
|
||||
mod->base.xSync = go_vtab_sync;
|
||||
mod->base.xCommit = go_vtab_commit;
|
||||
mod->base.xRollback = go_vtab_rollback;
|
||||
}
|
||||
if (flags & SQLITE_VTAB_SAVEPOINTER_GO) {
|
||||
mod->base.xSavepoint = go_vtab_savepoint;
|
||||
mod->base.xRelease = go_vtab_release;
|
||||
mod->base.xRollbackTo = go_vtab_rollback_to;
|
||||
}
|
||||
if (mod->base.xCreate && !mod->base.xDestroy) {
|
||||
mod->base.xDestroy = mod->base.xDisconnect;
|
||||
}
|
||||
if (mod->base.xDestroy && !mod->base.xCreate) {
|
||||
mod->base.xCreate = mod->base.xConnect;
|
||||
}
|
||||
|
||||
return sqlite3_create_module_v2(db, zName, &mod->base, mod, go_mod_destroy);
|
||||
}
|
||||
|
||||
int sqlite3_vtab_config_go(sqlite3 *db, int op, int constraint) {
|
||||
return sqlite3_vtab_config(db, op, constraint);
|
||||
}
|
||||
|
||||
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");
|
||||
static_assert(sizeof(struct sqlite3_index_info) == 72, "Unexpected size");
|
||||
static_assert(sizeof(struct sqlite3_index_orderby) == 8, "Unexpected size");
|
||||
static_assert(sizeof(struct sqlite3_index_constraint) == 12, "Unexpected size");
|
||||
static_assert(sizeof(struct sqlite3_index_constraint_usage) == 8,
|
||||
"Unexpected size");
|
||||
@@ -37,7 +37,7 @@ func Test_sqlite_call_closed(t *testing.T) {
|
||||
sqlite.close()
|
||||
|
||||
defer func() { _ = recover() }()
|
||||
sqlite.call(sqlite.api.free)
|
||||
sqlite.call("free")
|
||||
t.Error("want panic")
|
||||
}
|
||||
|
||||
|
||||
247
stmt.go
247
stmt.go
@@ -11,7 +11,7 @@ import (
|
||||
|
||||
// Stmt is a prepared statement object.
|
||||
//
|
||||
// https://www.sqlite.org/c3ref/stmt.html
|
||||
// https://sqlite.org/c3ref/stmt.html
|
||||
type Stmt struct {
|
||||
c *Conn
|
||||
err error
|
||||
@@ -22,33 +22,49 @@ type Stmt struct {
|
||||
//
|
||||
// It is safe to close a nil, zero or closed Stmt.
|
||||
//
|
||||
// https://www.sqlite.org/c3ref/finalize.html
|
||||
// https://sqlite.org/c3ref/finalize.html
|
||||
func (s *Stmt) Close() error {
|
||||
if s == nil || s.handle == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
r := s.c.call(s.c.api.finalize, uint64(s.handle))
|
||||
r := s.c.call("sqlite3_finalize", uint64(s.handle))
|
||||
|
||||
s.handle = 0
|
||||
return s.c.error(r)
|
||||
}
|
||||
|
||||
// Conn returns the database connection to which the prepared statement belongs.
|
||||
//
|
||||
// https://sqlite.org/c3ref/db_handle.html
|
||||
func (s *Stmt) Conn() *Conn {
|
||||
return s.c
|
||||
}
|
||||
|
||||
// ReadOnly returns true if and only if the statement
|
||||
// makes no direct changes to the content of the database file.
|
||||
//
|
||||
// https://sqlite.org/c3ref/stmt_readonly.html
|
||||
func (s *Stmt) ReadOnly() bool {
|
||||
r := s.c.call("sqlite3_stmt_readonly", uint64(s.handle))
|
||||
return r != 0
|
||||
}
|
||||
|
||||
// Reset resets the prepared statement object.
|
||||
//
|
||||
// https://www.sqlite.org/c3ref/reset.html
|
||||
// https://sqlite.org/c3ref/reset.html
|
||||
func (s *Stmt) Reset() error {
|
||||
r := s.c.call(s.c.api.reset, uint64(s.handle))
|
||||
r := s.c.call("sqlite3_reset", uint64(s.handle))
|
||||
s.err = nil
|
||||
return s.c.error(r)
|
||||
}
|
||||
|
||||
// ClearBindings resets all bindings on the prepared statement.
|
||||
// Busy determines if a prepared statement has been reset.
|
||||
//
|
||||
// https://www.sqlite.org/c3ref/clear_bindings.html
|
||||
func (s *Stmt) ClearBindings() error {
|
||||
r := s.c.call(s.c.api.clearBindings, uint64(s.handle))
|
||||
return s.c.error(r)
|
||||
// https://sqlite.org/c3ref/stmt_busy.html
|
||||
func (s *Stmt) Busy() bool {
|
||||
r := s.c.call("sqlite3_stmt_busy", uint64(s.handle))
|
||||
return r != 0
|
||||
}
|
||||
|
||||
// Step evaluates the SQL statement.
|
||||
@@ -59,12 +75,13 @@ func (s *Stmt) ClearBindings() error {
|
||||
// If an error has occurred, Step returns false;
|
||||
// call [Stmt.Err] or [Stmt.Reset] to get the error.
|
||||
//
|
||||
// https://www.sqlite.org/c3ref/step.html
|
||||
// https://sqlite.org/c3ref/step.html
|
||||
func (s *Stmt) Step() bool {
|
||||
s.c.checkInterrupt()
|
||||
r := s.c.call(s.c.api.step, uint64(s.handle))
|
||||
r := s.c.call("sqlite3_step", uint64(s.handle))
|
||||
switch r {
|
||||
case _ROW:
|
||||
s.err = nil
|
||||
return true
|
||||
case _DONE:
|
||||
s.err = nil
|
||||
@@ -77,7 +94,7 @@ func (s *Stmt) Step() bool {
|
||||
// Err gets the last error occurred during [Stmt.Step].
|
||||
// Err returns nil after [Stmt.Reset] is called.
|
||||
//
|
||||
// https://www.sqlite.org/c3ref/step.html
|
||||
// https://sqlite.org/c3ref/step.html
|
||||
func (s *Stmt) Err() error {
|
||||
return s.err
|
||||
}
|
||||
@@ -90,11 +107,32 @@ func (s *Stmt) Exec() error {
|
||||
return s.Reset()
|
||||
}
|
||||
|
||||
// Status monitors the performance characteristics of prepared statements.
|
||||
//
|
||||
// https://sqlite.org/c3ref/stmt_status.html
|
||||
func (s *Stmt) Status(op StmtStatus, reset bool) int {
|
||||
var i uint64
|
||||
if reset {
|
||||
i = 1
|
||||
}
|
||||
r := s.c.call("sqlite3_stmt_status", uint64(s.handle),
|
||||
uint64(op), i)
|
||||
return int(r)
|
||||
}
|
||||
|
||||
// ClearBindings resets all bindings on the prepared statement.
|
||||
//
|
||||
// https://sqlite.org/c3ref/clear_bindings.html
|
||||
func (s *Stmt) ClearBindings() error {
|
||||
r := s.c.call("sqlite3_clear_bindings", uint64(s.handle))
|
||||
return s.c.error(r)
|
||||
}
|
||||
|
||||
// BindCount returns the number of SQL parameters in the prepared statement.
|
||||
//
|
||||
// https://www.sqlite.org/c3ref/bind_parameter_count.html
|
||||
// https://sqlite.org/c3ref/bind_parameter_count.html
|
||||
func (s *Stmt) BindCount() int {
|
||||
r := s.c.call(s.c.api.bindCount,
|
||||
r := s.c.call("sqlite3_bind_parameter_count",
|
||||
uint64(s.handle))
|
||||
return int(r)
|
||||
}
|
||||
@@ -102,11 +140,11 @@ func (s *Stmt) BindCount() int {
|
||||
// BindIndex returns the index of a parameter in the prepared statement
|
||||
// given its name.
|
||||
//
|
||||
// https://www.sqlite.org/c3ref/bind_parameter_index.html
|
||||
// https://sqlite.org/c3ref/bind_parameter_index.html
|
||||
func (s *Stmt) BindIndex(name string) int {
|
||||
defer s.c.arena.reset()
|
||||
defer s.c.arena.mark()()
|
||||
namePtr := s.c.arena.string(name)
|
||||
r := s.c.call(s.c.api.bindIndex,
|
||||
r := s.c.call("sqlite3_bind_parameter_index",
|
||||
uint64(s.handle), uint64(namePtr))
|
||||
return int(r)
|
||||
}
|
||||
@@ -114,16 +152,16 @@ func (s *Stmt) BindIndex(name string) int {
|
||||
// BindName returns the name of a parameter in the prepared statement.
|
||||
// The leftmost SQL parameter has an index of 1.
|
||||
//
|
||||
// https://www.sqlite.org/c3ref/bind_parameter_name.html
|
||||
// https://sqlite.org/c3ref/bind_parameter_name.html
|
||||
func (s *Stmt) BindName(param int) string {
|
||||
r := s.c.call(s.c.api.bindName,
|
||||
r := s.c.call("sqlite3_bind_parameter_name",
|
||||
uint64(s.handle), uint64(param))
|
||||
|
||||
ptr := uint32(r)
|
||||
if ptr == 0 {
|
||||
return ""
|
||||
}
|
||||
return util.ReadString(s.c.mod, ptr, _MAX_STRING)
|
||||
return util.ReadString(s.c.mod, ptr, _MAX_NAME)
|
||||
}
|
||||
|
||||
// BindBool binds a bool to the prepared statement.
|
||||
@@ -131,7 +169,7 @@ func (s *Stmt) BindName(param int) string {
|
||||
// SQLite does not have a separate boolean storage class.
|
||||
// Instead, boolean values are stored as integers 0 (false) and 1 (true).
|
||||
//
|
||||
// https://www.sqlite.org/c3ref/bind_blob.html
|
||||
// https://sqlite.org/c3ref/bind_blob.html
|
||||
func (s *Stmt) BindBool(param int, value bool) error {
|
||||
var i int64
|
||||
if value {
|
||||
@@ -143,7 +181,7 @@ func (s *Stmt) BindBool(param int, value bool) error {
|
||||
// BindInt binds an int to the prepared statement.
|
||||
// The leftmost SQL parameter has an index of 1.
|
||||
//
|
||||
// https://www.sqlite.org/c3ref/bind_blob.html
|
||||
// https://sqlite.org/c3ref/bind_blob.html
|
||||
func (s *Stmt) BindInt(param int, value int) error {
|
||||
return s.BindInt64(param, int64(value))
|
||||
}
|
||||
@@ -151,9 +189,9 @@ func (s *Stmt) BindInt(param int, value int) error {
|
||||
// BindInt64 binds an int64 to the prepared statement.
|
||||
// The leftmost SQL parameter has an index of 1.
|
||||
//
|
||||
// https://www.sqlite.org/c3ref/bind_blob.html
|
||||
// https://sqlite.org/c3ref/bind_blob.html
|
||||
func (s *Stmt) BindInt64(param int, value int64) error {
|
||||
r := s.c.call(s.c.api.bindInteger,
|
||||
r := s.c.call("sqlite3_bind_int64",
|
||||
uint64(s.handle), uint64(param), uint64(value))
|
||||
return s.c.error(r)
|
||||
}
|
||||
@@ -161,9 +199,9 @@ func (s *Stmt) BindInt64(param int, value int64) error {
|
||||
// BindFloat binds a float64 to the prepared statement.
|
||||
// The leftmost SQL parameter has an index of 1.
|
||||
//
|
||||
// https://www.sqlite.org/c3ref/bind_blob.html
|
||||
// https://sqlite.org/c3ref/bind_blob.html
|
||||
func (s *Stmt) BindFloat(param int, value float64) error {
|
||||
r := s.c.call(s.c.api.bindFloat,
|
||||
r := s.c.call("sqlite3_bind_double",
|
||||
uint64(s.handle), uint64(param), math.Float64bits(value))
|
||||
return s.c.error(r)
|
||||
}
|
||||
@@ -171,13 +209,32 @@ func (s *Stmt) BindFloat(param int, value float64) error {
|
||||
// BindText binds a string to the prepared statement.
|
||||
// The leftmost SQL parameter has an index of 1.
|
||||
//
|
||||
// https://www.sqlite.org/c3ref/bind_blob.html
|
||||
// https://sqlite.org/c3ref/bind_blob.html
|
||||
func (s *Stmt) BindText(param int, value string) error {
|
||||
if len(value) > _MAX_LENGTH {
|
||||
return TOOBIG
|
||||
}
|
||||
ptr := s.c.newString(value)
|
||||
r := s.c.call(s.c.api.bindText,
|
||||
r := s.c.call("sqlite3_bind_text64",
|
||||
uint64(s.handle), uint64(param),
|
||||
uint64(ptr), uint64(len(value)),
|
||||
uint64(s.c.api.destructor), _UTF8)
|
||||
uint64(s.c.freer), _UTF8)
|
||||
return s.c.error(r)
|
||||
}
|
||||
|
||||
// BindRawText binds a []byte to the prepared statement as text.
|
||||
// The leftmost SQL parameter has an index of 1.
|
||||
//
|
||||
// https://sqlite.org/c3ref/bind_blob.html
|
||||
func (s *Stmt) BindRawText(param int, value []byte) error {
|
||||
if len(value) > _MAX_LENGTH {
|
||||
return TOOBIG
|
||||
}
|
||||
ptr := s.c.newBytes(value)
|
||||
r := s.c.call("sqlite3_bind_text64",
|
||||
uint64(s.handle), uint64(param),
|
||||
uint64(ptr), uint64(len(value)),
|
||||
uint64(s.c.freer), _UTF8)
|
||||
return s.c.error(r)
|
||||
}
|
||||
|
||||
@@ -185,22 +242,25 @@ func (s *Stmt) BindText(param int, value string) error {
|
||||
// The leftmost SQL parameter has an index of 1.
|
||||
// Binding a nil slice is the same as calling [Stmt.BindNull].
|
||||
//
|
||||
// https://www.sqlite.org/c3ref/bind_blob.html
|
||||
// https://sqlite.org/c3ref/bind_blob.html
|
||||
func (s *Stmt) BindBlob(param int, value []byte) error {
|
||||
if len(value) > _MAX_LENGTH {
|
||||
return TOOBIG
|
||||
}
|
||||
ptr := s.c.newBytes(value)
|
||||
r := s.c.call(s.c.api.bindBlob,
|
||||
r := s.c.call("sqlite3_bind_blob64",
|
||||
uint64(s.handle), uint64(param),
|
||||
uint64(ptr), uint64(len(value)),
|
||||
uint64(s.c.api.destructor))
|
||||
uint64(s.c.freer))
|
||||
return s.c.error(r)
|
||||
}
|
||||
|
||||
// BindZeroBlob binds a zero-filled, length n BLOB to the prepared statement.
|
||||
// The leftmost SQL parameter has an index of 1.
|
||||
//
|
||||
// https://www.sqlite.org/c3ref/bind_blob.html
|
||||
// https://sqlite.org/c3ref/bind_blob.html
|
||||
func (s *Stmt) BindZeroBlob(param int, n int64) error {
|
||||
r := s.c.call(s.c.api.bindZeroBlob,
|
||||
r := s.c.call("sqlite3_bind_zeroblob64",
|
||||
uint64(s.handle), uint64(param), uint64(n))
|
||||
return s.c.error(r)
|
||||
}
|
||||
@@ -208,9 +268,9 @@ func (s *Stmt) BindZeroBlob(param int, n int64) error {
|
||||
// BindNull binds a NULL to the prepared statement.
|
||||
// The leftmost SQL parameter has an index of 1.
|
||||
//
|
||||
// https://www.sqlite.org/c3ref/bind_blob.html
|
||||
// https://sqlite.org/c3ref/bind_blob.html
|
||||
func (s *Stmt) BindNull(param int) error {
|
||||
r := s.c.call(s.c.api.bindNull,
|
||||
r := s.c.call("sqlite3_bind_null",
|
||||
uint64(s.handle), uint64(param))
|
||||
return s.c.error(r)
|
||||
}
|
||||
@@ -218,7 +278,7 @@ func (s *Stmt) BindNull(param int) error {
|
||||
// BindTime binds a [time.Time] to the prepared statement.
|
||||
// The leftmost SQL parameter has an index of 1.
|
||||
//
|
||||
// https://www.sqlite.org/c3ref/bind_blob.html
|
||||
// https://sqlite.org/c3ref/bind_blob.html
|
||||
func (s *Stmt) BindTime(param int, value time.Time, format TimeFormat) error {
|
||||
if format == TimeFormatDefault {
|
||||
return s.bindRFC3339Nano(param, value)
|
||||
@@ -243,21 +303,22 @@ func (s *Stmt) bindRFC3339Nano(param int, value time.Time) error {
|
||||
buf := util.View(s.c.mod, ptr, maxlen)
|
||||
buf = value.AppendFormat(buf[:0], time.RFC3339Nano)
|
||||
|
||||
r := s.c.call(s.c.api.bindText,
|
||||
r := s.c.call("sqlite3_bind_text64",
|
||||
uint64(s.handle), uint64(param),
|
||||
uint64(ptr), uint64(len(buf)),
|
||||
uint64(s.c.api.destructor), _UTF8)
|
||||
uint64(s.c.freer), _UTF8)
|
||||
return s.c.error(r)
|
||||
}
|
||||
|
||||
// BindPointer binds a NULL to the prepared statement, just like [Stmt.BindNull],
|
||||
// but it also associates ptr with that NULL value such that it can be retrieved
|
||||
// within an application-defined SQL function using [Value.Pointer].
|
||||
// The leftmost SQL parameter has an index of 1.
|
||||
//
|
||||
// https://www.sqlite.org/c3ref/bind_blob.html
|
||||
// https://sqlite.org/c3ref/bind_blob.html
|
||||
func (s *Stmt) BindPointer(param int, ptr any) error {
|
||||
valPtr := util.AddHandle(s.c.ctx, ptr)
|
||||
r := s.c.call(s.c.api.bindPointer,
|
||||
r := s.c.call("sqlite3_bind_pointer_go",
|
||||
uint64(s.handle), uint64(param), uint64(valPtr))
|
||||
return s.c.error(r)
|
||||
}
|
||||
@@ -265,25 +326,33 @@ func (s *Stmt) BindPointer(param int, ptr any) error {
|
||||
// BindJSON binds the JSON encoding of value to the prepared statement.
|
||||
// The leftmost SQL parameter has an index of 1.
|
||||
//
|
||||
// https://www.sqlite.org/c3ref/bind_blob.html
|
||||
// https://sqlite.org/c3ref/bind_blob.html
|
||||
func (s *Stmt) BindJSON(param int, value any) error {
|
||||
data, err := json.Marshal(value)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ptr := s.c.newBytes(data)
|
||||
r := s.c.call(s.c.api.bindText,
|
||||
uint64(s.handle), uint64(param),
|
||||
uint64(ptr), uint64(len(data)),
|
||||
uint64(s.c.api.destructor), _UTF8)
|
||||
return s.BindRawText(param, data)
|
||||
}
|
||||
|
||||
// BindValue binds a copy of value to the prepared statement.
|
||||
// The leftmost SQL parameter has an index of 1.
|
||||
//
|
||||
// https://sqlite.org/c3ref/bind_blob.html
|
||||
func (s *Stmt) BindValue(param int, value Value) error {
|
||||
if value.sqlite != s.c.sqlite {
|
||||
return MISUSE
|
||||
}
|
||||
r := s.c.call("sqlite3_bind_value",
|
||||
uint64(s.handle), uint64(param), uint64(value.handle))
|
||||
return s.c.error(r)
|
||||
}
|
||||
|
||||
// ColumnCount returns the number of columns in a result set.
|
||||
//
|
||||
// https://www.sqlite.org/c3ref/column_count.html
|
||||
// https://sqlite.org/c3ref/column_count.html
|
||||
func (s *Stmt) ColumnCount() int {
|
||||
r := s.c.call(s.c.api.columnCount,
|
||||
r := s.c.call("sqlite3_column_count",
|
||||
uint64(s.handle))
|
||||
return int(r)
|
||||
}
|
||||
@@ -291,35 +360,48 @@ func (s *Stmt) ColumnCount() int {
|
||||
// ColumnName returns the name of the result column.
|
||||
// The leftmost column of the result set has the index 0.
|
||||
//
|
||||
// https://www.sqlite.org/c3ref/column_name.html
|
||||
// https://sqlite.org/c3ref/column_name.html
|
||||
func (s *Stmt) ColumnName(col int) string {
|
||||
r := s.c.call(s.c.api.columnName,
|
||||
r := s.c.call("sqlite3_column_name",
|
||||
uint64(s.handle), uint64(col))
|
||||
|
||||
ptr := uint32(r)
|
||||
if ptr == 0 {
|
||||
panic(util.OOMErr)
|
||||
}
|
||||
return util.ReadString(s.c.mod, ptr, _MAX_STRING)
|
||||
return util.ReadString(s.c.mod, ptr, _MAX_NAME)
|
||||
}
|
||||
|
||||
// ColumnType returns the initial [Datatype] of the result column.
|
||||
// The leftmost column of the result set has the index 0.
|
||||
//
|
||||
// https://www.sqlite.org/c3ref/column_blob.html
|
||||
// https://sqlite.org/c3ref/column_blob.html
|
||||
func (s *Stmt) ColumnType(col int) Datatype {
|
||||
r := s.c.call(s.c.api.columnType,
|
||||
r := s.c.call("sqlite3_column_type",
|
||||
uint64(s.handle), uint64(col))
|
||||
return Datatype(r)
|
||||
}
|
||||
|
||||
// ColumnDeclType returns the declared datatype of the result column.
|
||||
// The leftmost column of the result set has the index 0.
|
||||
//
|
||||
// https://sqlite.org/c3ref/column_decltype.html
|
||||
func (s *Stmt) ColumnDeclType(col int) string {
|
||||
r := s.c.call("sqlite3_column_decltype",
|
||||
uint64(s.handle), uint64(col))
|
||||
if r == 0 {
|
||||
return ""
|
||||
}
|
||||
return util.ReadString(s.c.mod, uint32(r), _MAX_NAME)
|
||||
}
|
||||
|
||||
// ColumnBool returns the value of the result column as a bool.
|
||||
// The leftmost column of the result set has the index 0.
|
||||
// SQLite does not have a separate boolean storage class.
|
||||
// Instead, boolean values are retrieved as integers,
|
||||
// with 0 converted to false and any other value to true.
|
||||
//
|
||||
// https://www.sqlite.org/c3ref/column_blob.html
|
||||
// https://sqlite.org/c3ref/column_blob.html
|
||||
func (s *Stmt) ColumnBool(col int) bool {
|
||||
if i := s.ColumnInt64(col); i != 0 {
|
||||
return true
|
||||
@@ -330,7 +412,7 @@ func (s *Stmt) ColumnBool(col int) bool {
|
||||
// ColumnInt returns the value of the result column as an int.
|
||||
// The leftmost column of the result set has the index 0.
|
||||
//
|
||||
// https://www.sqlite.org/c3ref/column_blob.html
|
||||
// https://sqlite.org/c3ref/column_blob.html
|
||||
func (s *Stmt) ColumnInt(col int) int {
|
||||
return int(s.ColumnInt64(col))
|
||||
}
|
||||
@@ -338,9 +420,9 @@ func (s *Stmt) ColumnInt(col int) int {
|
||||
// ColumnInt64 returns the value of the result column as an int64.
|
||||
// The leftmost column of the result set has the index 0.
|
||||
//
|
||||
// https://www.sqlite.org/c3ref/column_blob.html
|
||||
// https://sqlite.org/c3ref/column_blob.html
|
||||
func (s *Stmt) ColumnInt64(col int) int64 {
|
||||
r := s.c.call(s.c.api.columnInteger,
|
||||
r := s.c.call("sqlite3_column_int64",
|
||||
uint64(s.handle), uint64(col))
|
||||
return int64(r)
|
||||
}
|
||||
@@ -348,9 +430,9 @@ func (s *Stmt) ColumnInt64(col int) int64 {
|
||||
// ColumnFloat returns the value of the result column as a float64.
|
||||
// The leftmost column of the result set has the index 0.
|
||||
//
|
||||
// https://www.sqlite.org/c3ref/column_blob.html
|
||||
// https://sqlite.org/c3ref/column_blob.html
|
||||
func (s *Stmt) ColumnFloat(col int) float64 {
|
||||
r := s.c.call(s.c.api.columnFloat,
|
||||
r := s.c.call("sqlite3_column_double",
|
||||
uint64(s.handle), uint64(col))
|
||||
return math.Float64frombits(r)
|
||||
}
|
||||
@@ -358,7 +440,7 @@ func (s *Stmt) ColumnFloat(col int) float64 {
|
||||
// ColumnTime returns the value of the result column as a [time.Time].
|
||||
// The leftmost column of the result set has the index 0.
|
||||
//
|
||||
// https://www.sqlite.org/c3ref/column_blob.html
|
||||
// https://sqlite.org/c3ref/column_blob.html
|
||||
func (s *Stmt) ColumnTime(col int, format TimeFormat) time.Time {
|
||||
var v any
|
||||
switch s.ColumnType(col) {
|
||||
@@ -383,7 +465,7 @@ func (s *Stmt) ColumnTime(col int, format TimeFormat) time.Time {
|
||||
// ColumnText returns the value of the result column as a string.
|
||||
// The leftmost column of the result set has the index 0.
|
||||
//
|
||||
// https://www.sqlite.org/c3ref/column_blob.html
|
||||
// https://sqlite.org/c3ref/column_blob.html
|
||||
func (s *Stmt) ColumnText(col int) string {
|
||||
return string(s.ColumnRawText(col))
|
||||
}
|
||||
@@ -392,7 +474,7 @@ func (s *Stmt) ColumnText(col int) string {
|
||||
// the value of the result column as a []byte.
|
||||
// The leftmost column of the result set has the index 0.
|
||||
//
|
||||
// https://www.sqlite.org/c3ref/column_blob.html
|
||||
// https://sqlite.org/c3ref/column_blob.html
|
||||
func (s *Stmt) ColumnBlob(col int, buf []byte) []byte {
|
||||
return append(buf, s.ColumnRawBlob(col)...)
|
||||
}
|
||||
@@ -402,9 +484,9 @@ func (s *Stmt) ColumnBlob(col int, buf []byte) []byte {
|
||||
// subsequent calls to [Stmt] methods.
|
||||
// The leftmost column of the result set has the index 0.
|
||||
//
|
||||
// https://www.sqlite.org/c3ref/column_blob.html
|
||||
// https://sqlite.org/c3ref/column_blob.html
|
||||
func (s *Stmt) ColumnRawText(col int) []byte {
|
||||
r := s.c.call(s.c.api.columnText,
|
||||
r := s.c.call("sqlite3_column_text",
|
||||
uint64(s.handle), uint64(col))
|
||||
return s.columnRawBytes(col, uint32(r))
|
||||
}
|
||||
@@ -414,21 +496,21 @@ func (s *Stmt) ColumnRawText(col int) []byte {
|
||||
// subsequent calls to [Stmt] methods.
|
||||
// The leftmost column of the result set has the index 0.
|
||||
//
|
||||
// https://www.sqlite.org/c3ref/column_blob.html
|
||||
// https://sqlite.org/c3ref/column_blob.html
|
||||
func (s *Stmt) ColumnRawBlob(col int) []byte {
|
||||
r := s.c.call(s.c.api.columnBlob,
|
||||
r := s.c.call("sqlite3_column_blob",
|
||||
uint64(s.handle), uint64(col))
|
||||
return s.columnRawBytes(col, uint32(r))
|
||||
}
|
||||
|
||||
func (s *Stmt) columnRawBytes(col int, ptr uint32) []byte {
|
||||
if ptr == 0 {
|
||||
r := s.c.call(s.c.api.errcode, uint64(s.c.handle))
|
||||
r := s.c.call("sqlite3_errcode", uint64(s.c.handle))
|
||||
s.err = s.c.error(r)
|
||||
return nil
|
||||
}
|
||||
|
||||
r := s.c.call(s.c.api.columnBytes,
|
||||
r := s.c.call("sqlite3_column_bytes",
|
||||
uint64(s.handle), uint64(col))
|
||||
return util.View(s.c.mod, ptr, r)
|
||||
}
|
||||
@@ -437,7 +519,7 @@ func (s *Stmt) columnRawBytes(col int, ptr uint32) []byte {
|
||||
// and stores it in the value pointed to by ptr.
|
||||
// The leftmost column of the result set has the index 0.
|
||||
//
|
||||
// https://www.sqlite.org/c3ref/column_blob.html
|
||||
// https://sqlite.org/c3ref/column_blob.html
|
||||
func (s *Stmt) ColumnJSON(col int, ptr any) error {
|
||||
var data []byte
|
||||
switch s.ColumnType(col) {
|
||||
@@ -457,17 +539,16 @@ func (s *Stmt) ColumnJSON(col int, ptr any) error {
|
||||
return json.Unmarshal(data, ptr)
|
||||
}
|
||||
|
||||
// Return true if stmt is an empty SQL statement.
|
||||
// This is used as an optimization.
|
||||
// It's OK to always return false here.
|
||||
func emptyStatement(stmt string) bool {
|
||||
for _, b := range []byte(stmt) {
|
||||
switch b {
|
||||
case ' ', '\n', '\r', '\t', '\v', '\f':
|
||||
case ';':
|
||||
default:
|
||||
return false
|
||||
}
|
||||
// ColumnValue returns the unprotected value of the result column.
|
||||
// The leftmost column of the result set has the index 0.
|
||||
//
|
||||
// https://sqlite.org/c3ref/column_blob.html
|
||||
func (s *Stmt) ColumnValue(col int) Value {
|
||||
r := s.c.call("sqlite3_column_value",
|
||||
uint64(s.handle), uint64(col))
|
||||
return Value{
|
||||
unprot: true,
|
||||
sqlite: s.c.sqlite,
|
||||
handle: uint32(r),
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
60
stmt_test.go
60
stmt_test.go
@@ -1,60 +0,0 @@
|
||||
package sqlite3
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func Test_emptyStatement(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
stmt string
|
||||
want bool
|
||||
}{
|
||||
{"empty", "", true},
|
||||
{"space", " ", true},
|
||||
{"separator", ";\n ", true},
|
||||
{"begin", "BEGIN", false},
|
||||
{"select", "SELECT 1;", false},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := emptyStatement(tt.stmt); got != tt.want {
|
||||
t.Errorf("got %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Fuzz_emptyStatement(f *testing.F) {
|
||||
f.Add("")
|
||||
f.Add(" ")
|
||||
f.Add(";\n ")
|
||||
f.Add("; ;\v")
|
||||
f.Add("BEGIN")
|
||||
f.Add("SELECT 1;")
|
||||
|
||||
db, err := Open(":memory:")
|
||||
if err != nil {
|
||||
f.Fatal(err)
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
f.Fuzz(func(t *testing.T, sql string) {
|
||||
// If empty, SQLite parses it as empty.
|
||||
if emptyStatement(sql) {
|
||||
stmt, tail, err := db.Prepare(sql)
|
||||
if err != nil {
|
||||
t.Errorf("%q, %v", sql, err)
|
||||
}
|
||||
if stmt != nil {
|
||||
t.Errorf("%q, %v", sql, stmt)
|
||||
}
|
||||
if tail != "" {
|
||||
t.Errorf("%q", sql)
|
||||
}
|
||||
stmt.Close()
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -75,8 +75,8 @@ func TestConn_Open_modeof(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestConn_Close(t *testing.T) {
|
||||
var conn *sqlite3.Conn
|
||||
conn.Close()
|
||||
var db *sqlite3.Conn
|
||||
db.Close()
|
||||
}
|
||||
|
||||
func TestConn_Close_BUSY(t *testing.T) {
|
||||
|
||||
@@ -15,6 +15,9 @@ import (
|
||||
//go:embed testdata/wal.db
|
||||
var waldb []byte
|
||||
|
||||
//go:embed testdata/utf16be.db
|
||||
var utf16db []byte
|
||||
|
||||
func TestDB_memory(t *testing.T) {
|
||||
t.Parallel()
|
||||
testDB(t, ":memory:")
|
||||
@@ -34,19 +37,29 @@ func TestDB_nolock(t *testing.T) {
|
||||
|
||||
func TestDB_wal(t *testing.T) {
|
||||
t.Parallel()
|
||||
wal := filepath.Join(t.TempDir(), "test.db")
|
||||
err := os.WriteFile(wal, waldb, 0666)
|
||||
tmp := filepath.Join(t.TempDir(), "test.db")
|
||||
err := os.WriteFile(tmp, waldb, 0666)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
testDB(t, wal)
|
||||
testDB(t, tmp)
|
||||
}
|
||||
|
||||
func TestDB_utf16(t *testing.T) {
|
||||
t.Parallel()
|
||||
tmp := filepath.Join(t.TempDir(), "test.db")
|
||||
err := os.WriteFile(tmp, utf16db, 0666)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
testDB(t, tmp)
|
||||
}
|
||||
|
||||
func TestDB_vfs(t *testing.T) {
|
||||
testDB(t, "file:test.db?vfs=memdb")
|
||||
}
|
||||
|
||||
func testDB(t *testing.T, name string) {
|
||||
func testDB(t testing.TB, name string) {
|
||||
db, err := sqlite3.Open(name)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -80,6 +93,9 @@ func testDB(t *testing.T, name string) {
|
||||
id := stmt.ColumnInt(0)
|
||||
name := stmt.ColumnText(1)
|
||||
|
||||
if row >= 3 {
|
||||
continue
|
||||
}
|
||||
if id != ids[row] {
|
||||
t.Errorf("got %d, want %d", id, ids[row])
|
||||
}
|
||||
|
||||
@@ -72,6 +72,17 @@ func TestDriver(t *testing.T) {
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
typs, err := rows.ColumnTypes()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if got := typs[0].DatabaseTypeName(); got != "INT" {
|
||||
t.Errorf("got %s, want INT", got)
|
||||
}
|
||||
if got := typs[1].DatabaseTypeName(); got != "VARCHAR" {
|
||||
t.Errorf("got %s, want INT", got)
|
||||
}
|
||||
|
||||
row := 0
|
||||
ids := []int{0, 1, 2}
|
||||
names := []string{"go", "zig", "whatever"}
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/ncruces/go-sqlite3"
|
||||
_ "github.com/ncruces/go-sqlite3/embed"
|
||||
)
|
||||
|
||||
func Test_base64(t *testing.T) {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package tests
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"math"
|
||||
"testing"
|
||||
@@ -8,26 +9,36 @@ import (
|
||||
|
||||
"github.com/ncruces/go-sqlite3"
|
||||
"github.com/ncruces/go-sqlite3/driver"
|
||||
_ "github.com/ncruces/go-sqlite3/embed"
|
||||
"github.com/ncruces/julianday"
|
||||
)
|
||||
|
||||
func TestJSON(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
db, err := driver.Open(":memory:", nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
_, err = db.Exec(`CREATE TABLE IF NOT EXISTS test (col)`)
|
||||
conn, err := db.Conn(ctx)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
_, err = conn.ExecContext(ctx, `CREATE TABLE IF NOT EXISTS test (col)`)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
reference := time.Date(2013, 10, 7, 4, 23, 19, 120_000_000, time.FixedZone("", -4*3600))
|
||||
|
||||
_, err = db.Exec(
|
||||
_, err = conn.ExecContext(ctx,
|
||||
`INSERT INTO test (col) VALUES (?), (?), (?), (?)`,
|
||||
nil, 1, math.Pi, reference,
|
||||
)
|
||||
@@ -35,7 +46,7 @@ func TestJSON(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
_, err = db.Exec(
|
||||
_, err = conn.ExecContext(ctx,
|
||||
`INSERT INTO test (col) VALUES (?), (?), (?), (?)`,
|
||||
sqlite3.JSON(math.Pi), sqlite3.JSON(false),
|
||||
julianday.Format(reference), sqlite3.JSON([]string{}))
|
||||
@@ -43,10 +54,11 @@ func TestJSON(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
rows, err := db.Query("SELECT * FROM test")
|
||||
rows, err := conn.QueryContext(ctx, "SELECT * FROM test")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
want := []string{
|
||||
"null", "1", "3.141592653589793",
|
||||
|
||||
@@ -11,7 +11,7 @@ import (
|
||||
|
||||
"github.com/ncruces/go-sqlite3"
|
||||
_ "github.com/ncruces/go-sqlite3/embed"
|
||||
_ "github.com/ncruces/go-sqlite3/vfs/memdb"
|
||||
"github.com/ncruces/go-sqlite3/vfs/memdb"
|
||||
)
|
||||
|
||||
func TestParallel(t *testing.T) {
|
||||
@@ -96,7 +96,16 @@ func TestChildProcess(t *testing.T) {
|
||||
testParallel(t, name, 1000)
|
||||
}
|
||||
|
||||
func testParallel(t *testing.T, name string, n int) {
|
||||
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)"
|
||||
testParallel(b, name, b.N)
|
||||
}
|
||||
|
||||
func testParallel(t testing.TB, name string, n int) {
|
||||
writer := func() error {
|
||||
db, err := sqlite3.Open(name)
|
||||
if err != nil {
|
||||
@@ -174,7 +183,7 @@ func testParallel(t *testing.T, name string, n int) {
|
||||
}
|
||||
}
|
||||
|
||||
func testIntegrity(t *testing.T, name string) {
|
||||
func testIntegrity(t testing.TB, name string) {
|
||||
db, err := sqlite3.Open(name)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/ncruces/go-sqlite3"
|
||||
_ "github.com/ncruces/go-sqlite3/embed"
|
||||
)
|
||||
|
||||
func TestStmt(t *testing.T) {
|
||||
@@ -29,6 +30,10 @@ func TestStmt(t *testing.T) {
|
||||
}
|
||||
defer stmt.Close()
|
||||
|
||||
if got := stmt.ReadOnly(); got != false {
|
||||
t.Error("got true, want false")
|
||||
}
|
||||
|
||||
if got := stmt.BindCount(); got != 1 {
|
||||
t.Errorf("got %d, want 1", got)
|
||||
}
|
||||
@@ -136,6 +141,10 @@ func TestStmt(t *testing.T) {
|
||||
}
|
||||
defer stmt.Close()
|
||||
|
||||
if got := stmt.ReadOnly(); got != true {
|
||||
t.Error("got false, want true")
|
||||
}
|
||||
|
||||
if stmt.Step() {
|
||||
if got := stmt.ColumnType(0); got != sqlite3.INTEGER {
|
||||
t.Errorf("got %v, want INTEGER", got)
|
||||
@@ -585,4 +594,61 @@ func TestStmt_ColumnTime(t *testing.T) {
|
||||
t.Errorf("want error")
|
||||
}
|
||||
}
|
||||
|
||||
if got := stmt.Status(sqlite3.STMTSTATUS_RUN, true); got != 1 {
|
||||
t.Errorf("got %d, want 1", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStmt_Error(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
db, err := sqlite3.Open(":memory:")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
var blob [1e9 + 1]byte
|
||||
|
||||
_, _, err = db.Prepare(string(blob[:]))
|
||||
if err == nil {
|
||||
t.Errorf("want error")
|
||||
} else {
|
||||
t.Log(err)
|
||||
}
|
||||
|
||||
stmt, _, err := db.Prepare(`SELECT ?`)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer stmt.Close()
|
||||
|
||||
err = stmt.BindText(1, string(blob[:]))
|
||||
if err == nil {
|
||||
t.Errorf("want error")
|
||||
} else {
|
||||
t.Log(err)
|
||||
}
|
||||
|
||||
err = stmt.BindBlob(1, blob[:])
|
||||
if err == nil {
|
||||
t.Errorf("want error")
|
||||
} else {
|
||||
t.Log(err)
|
||||
}
|
||||
|
||||
err = stmt.BindRawText(1, blob[:])
|
||||
if err == nil {
|
||||
t.Errorf("want error")
|
||||
} else {
|
||||
t.Log(err)
|
||||
}
|
||||
|
||||
err = stmt.BindZeroBlob(1, 1e9+1)
|
||||
if err == nil {
|
||||
t.Errorf("want error")
|
||||
} else {
|
||||
t.Log(err)
|
||||
}
|
||||
}
|
||||
|
||||
BIN
tests/testdata/utf16be.db
vendored
Normal file
BIN
tests/testdata/utf16be.db
vendored
Normal file
Binary file not shown.
@@ -1,6 +1,7 @@
|
||||
package tests
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"testing"
|
||||
@@ -8,6 +9,7 @@ import (
|
||||
|
||||
"github.com/ncruces/go-sqlite3"
|
||||
"github.com/ncruces/go-sqlite3/driver"
|
||||
_ "github.com/ncruces/go-sqlite3/embed"
|
||||
)
|
||||
|
||||
func TestTimeFormat_Encode(t *testing.T) {
|
||||
@@ -129,13 +131,22 @@ func TestTimeFormat_Decode(t *testing.T) {
|
||||
func TestTimeFormat_Scanner(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
db, err := driver.Open(":memory:", nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
_, err = db.Exec(
|
||||
conn, err := db.Conn(ctx)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
_, err = conn.ExecContext(ctx,
|
||||
`CREATE TABLE IF NOT EXISTS test (col)`)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -143,13 +154,13 @@ func TestTimeFormat_Scanner(t *testing.T) {
|
||||
|
||||
reference := time.Date(2013, 10, 7, 4, 23, 19, 120_000_000, time.FixedZone("", -4*3600))
|
||||
|
||||
_, err = db.Exec(`INSERT INTO test VALUES (?)`, sqlite3.TimeFormat7TZ.Encode(reference))
|
||||
_, err = conn.ExecContext(ctx, `INSERT INTO test VALUES (?)`, sqlite3.TimeFormat7TZ.Encode(reference))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
var got time.Time
|
||||
err = db.QueryRow("SELECT * FROM test").Scan(sqlite3.TimeFormatAuto.Scanner(&got))
|
||||
err = conn.QueryRowContext(ctx, "SELECT * FROM test").Scan(sqlite3.TimeFormatAuto.Scanner(&got))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/ncruces/go-sqlite3"
|
||||
_ "github.com/ncruces/go-sqlite3/embed"
|
||||
)
|
||||
|
||||
func TestConn_Transaction_exec(t *testing.T) {
|
||||
|
||||
@@ -5,7 +5,6 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/ncruces/go-sqlite3"
|
||||
_ "github.com/ncruces/go-sqlite3/driver"
|
||||
_ "github.com/ncruces/go-sqlite3/embed"
|
||||
"github.com/ncruces/go-sqlite3/vfs/memdb"
|
||||
"github.com/ncruces/go-sqlite3/vfs/readervfs"
|
||||
|
||||
10
time.go
10
time.go
@@ -15,12 +15,12 @@ import (
|
||||
// See the documentation for the [TimeFormatDefault] constant
|
||||
// for formats recognized by SQLite.
|
||||
//
|
||||
// https://www.sqlite.org/lang_datefunc.html
|
||||
// https://sqlite.org/lang_datefunc.html
|
||||
type TimeFormat string
|
||||
|
||||
// TimeFormats recognized by SQLite to encode/decode time values.
|
||||
//
|
||||
// https://www.sqlite.org/lang_datefunc.html
|
||||
// https://sqlite.org/lang_datefunc.html#time_values
|
||||
const (
|
||||
TimeFormatDefault TimeFormat = "" // time.RFC3339Nano
|
||||
|
||||
@@ -83,9 +83,9 @@ const (
|
||||
// a float64 for [TimeFormatJulianDay] and [TimeFormatUnixFrac],
|
||||
// or an int64 for the other numeric formats.
|
||||
//
|
||||
// https://www.sqlite.org/lang_datefunc.html
|
||||
// https://sqlite.org/lang_datefunc.html
|
||||
//
|
||||
// [collating sequence]: https://www.sqlite.org/datatype3.html#collating_sequences
|
||||
// [collating sequence]: https://sqlite.org/datatype3.html#collating_sequences
|
||||
func (f TimeFormat) Encode(t time.Time) any {
|
||||
switch f {
|
||||
// Numeric formats
|
||||
@@ -136,7 +136,7 @@ func (f TimeFormat) Encode(t time.Time) any {
|
||||
// Unix timestamps before 1980 and after 9999 may be misinterpreted as julian day numbers,
|
||||
// or have the wrong time unit.
|
||||
//
|
||||
// https://www.sqlite.org/lang_datefunc.html
|
||||
// https://sqlite.org/lang_datefunc.html
|
||||
func (f TimeFormat) Decode(v any) (time.Time, error) {
|
||||
switch f {
|
||||
// Numeric formats
|
||||
|
||||
28
tx.go
28
tx.go
@@ -12,14 +12,14 @@ import (
|
||||
|
||||
// Tx is an in-progress database transaction.
|
||||
//
|
||||
// https://www.sqlite.org/lang_transaction.html
|
||||
// https://sqlite.org/lang_transaction.html
|
||||
type Tx struct {
|
||||
c *Conn
|
||||
}
|
||||
|
||||
// Begin starts a deferred transaction.
|
||||
//
|
||||
// https://www.sqlite.org/lang_transaction.html
|
||||
// https://sqlite.org/lang_transaction.html
|
||||
func (c *Conn) Begin() Tx {
|
||||
// BEGIN even if interrupted.
|
||||
err := c.txExecInterrupted(`BEGIN DEFERRED`)
|
||||
@@ -31,7 +31,7 @@ func (c *Conn) Begin() Tx {
|
||||
|
||||
// BeginImmediate starts an immediate transaction.
|
||||
//
|
||||
// https://www.sqlite.org/lang_transaction.html
|
||||
// https://sqlite.org/lang_transaction.html
|
||||
func (c *Conn) BeginImmediate() (Tx, error) {
|
||||
err := c.Exec(`BEGIN IMMEDIATE`)
|
||||
if err != nil {
|
||||
@@ -42,7 +42,7 @@ func (c *Conn) BeginImmediate() (Tx, error) {
|
||||
|
||||
// BeginExclusive starts an exclusive transaction.
|
||||
//
|
||||
// https://www.sqlite.org/lang_transaction.html
|
||||
// https://sqlite.org/lang_transaction.html
|
||||
func (c *Conn) BeginExclusive() (Tx, error) {
|
||||
err := c.Exec(`BEGIN EXCLUSIVE`)
|
||||
if err != nil {
|
||||
@@ -56,14 +56,14 @@ func (c *Conn) BeginExclusive() (Tx, error) {
|
||||
//
|
||||
// This is meant to be deferred:
|
||||
//
|
||||
// func doWork(conn *sqlite3.Conn) (err error) {
|
||||
// tx := conn.Begin()
|
||||
// func doWork(db *sqlite3.Conn) (err error) {
|
||||
// tx := db.Begin()
|
||||
// defer tx.End(&err)
|
||||
//
|
||||
// // ... do work in the transaction
|
||||
// }
|
||||
//
|
||||
// https://www.sqlite.org/lang_transaction.html
|
||||
// https://sqlite.org/lang_transaction.html
|
||||
func (tx Tx) End(errp *error) {
|
||||
recovered := recover()
|
||||
if recovered != nil {
|
||||
@@ -94,7 +94,7 @@ func (tx Tx) End(errp *error) {
|
||||
|
||||
// Commit commits the transaction.
|
||||
//
|
||||
// https://www.sqlite.org/lang_transaction.html
|
||||
// https://sqlite.org/lang_transaction.html
|
||||
func (tx Tx) Commit() error {
|
||||
return tx.c.Exec(`COMMIT`)
|
||||
}
|
||||
@@ -102,7 +102,7 @@ func (tx Tx) Commit() error {
|
||||
// Rollback rolls back the transaction,
|
||||
// even if the connection has been interrupted.
|
||||
//
|
||||
// https://www.sqlite.org/lang_transaction.html
|
||||
// https://sqlite.org/lang_transaction.html
|
||||
func (tx Tx) Rollback() error {
|
||||
return tx.c.txExecInterrupted(`ROLLBACK`)
|
||||
}
|
||||
@@ -110,7 +110,7 @@ func (tx Tx) Rollback() error {
|
||||
// Savepoint is a marker within a transaction
|
||||
// that allows for partial rollback.
|
||||
//
|
||||
// https://www.sqlite.org/lang_savepoint.html
|
||||
// https://sqlite.org/lang_savepoint.html
|
||||
type Savepoint struct {
|
||||
c *Conn
|
||||
name string
|
||||
@@ -118,7 +118,7 @@ type Savepoint struct {
|
||||
|
||||
// Savepoint establishes a new transaction savepoint.
|
||||
//
|
||||
// https://www.sqlite.org/lang_savepoint.html
|
||||
// https://sqlite.org/lang_savepoint.html
|
||||
func (c *Conn) Savepoint() Savepoint {
|
||||
// Names can be reused; this makes catching bugs more likely.
|
||||
name := saveptName() + "_" + strconv.Itoa(int(rand.Int31()))
|
||||
@@ -156,8 +156,8 @@ func saveptName() (name string) {
|
||||
//
|
||||
// This is meant to be deferred:
|
||||
//
|
||||
// func doWork(conn *sqlite3.Conn) (err error) {
|
||||
// savept := conn.Savepoint()
|
||||
// func doWork(db *sqlite3.Conn) (err error) {
|
||||
// savept := db.Savepoint()
|
||||
// defer savept.Release(&err)
|
||||
//
|
||||
// // ... do work in the transaction
|
||||
@@ -198,7 +198,7 @@ func (s Savepoint) Release(errp *error) {
|
||||
// even if the connection has been interrupted.
|
||||
// Rollback does not release the savepoint.
|
||||
//
|
||||
// https://www.sqlite.org/lang_transaction.html
|
||||
// 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))
|
||||
|
||||
69
value.go
69
value.go
@@ -11,17 +11,50 @@ import (
|
||||
|
||||
// Value is any value that can be stored in a database table.
|
||||
//
|
||||
// https://www.sqlite.org/c3ref/value.html
|
||||
// https://sqlite.org/c3ref/value.html
|
||||
type Value struct {
|
||||
*sqlite
|
||||
handle uint32
|
||||
unprot bool
|
||||
copied bool
|
||||
}
|
||||
|
||||
func (v Value) protected() uint64 {
|
||||
if v.unprot {
|
||||
panic(util.ValueErr)
|
||||
}
|
||||
return uint64(v.handle)
|
||||
}
|
||||
|
||||
// Dup makes a copy of the SQL value and returns a pointer to that copy.
|
||||
//
|
||||
// https://sqlite.org/c3ref/value_dup.html
|
||||
func (v Value) Dup() *Value {
|
||||
r := v.call("sqlite3_value_dup", uint64(v.handle))
|
||||
return &Value{
|
||||
copied: true,
|
||||
sqlite: v.sqlite,
|
||||
handle: uint32(r),
|
||||
}
|
||||
}
|
||||
|
||||
// Close frees an SQL value previously obtained by [Value.Dup].
|
||||
//
|
||||
// https://sqlite.org/c3ref/value_dup.html
|
||||
func (dup *Value) Close() error {
|
||||
if !dup.copied {
|
||||
panic(util.ValueErr)
|
||||
}
|
||||
dup.call("sqlite3_value_free", uint64(dup.handle))
|
||||
dup.handle = 0
|
||||
return nil
|
||||
}
|
||||
|
||||
// Type returns the initial [Datatype] of the value.
|
||||
//
|
||||
// https://www.sqlite.org/c3ref/value_blob.html
|
||||
// https://sqlite.org/c3ref/value_blob.html
|
||||
func (v Value) Type() Datatype {
|
||||
r := v.call(v.api.valueType, uint64(v.handle))
|
||||
r := v.call("sqlite3_value_type", v.protected())
|
||||
return Datatype(r)
|
||||
}
|
||||
|
||||
@@ -30,7 +63,7 @@ func (v Value) Type() Datatype {
|
||||
// Instead, boolean values are retrieved as integers,
|
||||
// with 0 converted to false and any other value to true.
|
||||
//
|
||||
// https://www.sqlite.org/c3ref/value_blob.html
|
||||
// https://sqlite.org/c3ref/value_blob.html
|
||||
func (v Value) Bool() bool {
|
||||
if i := v.Int64(); i != 0 {
|
||||
return true
|
||||
@@ -40,30 +73,30 @@ func (v Value) Bool() bool {
|
||||
|
||||
// Int returns the value as an int.
|
||||
//
|
||||
// https://www.sqlite.org/c3ref/value_blob.html
|
||||
// https://sqlite.org/c3ref/value_blob.html
|
||||
func (v Value) Int() int {
|
||||
return int(v.Int64())
|
||||
}
|
||||
|
||||
// Int64 returns the value as an int64.
|
||||
//
|
||||
// https://www.sqlite.org/c3ref/value_blob.html
|
||||
// https://sqlite.org/c3ref/value_blob.html
|
||||
func (v Value) Int64() int64 {
|
||||
r := v.call(v.api.valueInteger, uint64(v.handle))
|
||||
r := v.call("sqlite3_value_int64", v.protected())
|
||||
return int64(r)
|
||||
}
|
||||
|
||||
// Float returns the value as a float64.
|
||||
//
|
||||
// https://www.sqlite.org/c3ref/value_blob.html
|
||||
// https://sqlite.org/c3ref/value_blob.html
|
||||
func (v Value) Float() float64 {
|
||||
r := v.call(v.api.valueFloat, uint64(v.handle))
|
||||
r := v.call("sqlite3_value_double", v.protected())
|
||||
return math.Float64frombits(r)
|
||||
}
|
||||
|
||||
// Time returns the value as a [time.Time].
|
||||
//
|
||||
// https://www.sqlite.org/c3ref/value_blob.html
|
||||
// https://sqlite.org/c3ref/value_blob.html
|
||||
func (v Value) Time(format TimeFormat) time.Time {
|
||||
var a any
|
||||
switch v.Type() {
|
||||
@@ -84,7 +117,7 @@ func (v Value) Time(format TimeFormat) time.Time {
|
||||
|
||||
// Text returns the value as a string.
|
||||
//
|
||||
// https://www.sqlite.org/c3ref/value_blob.html
|
||||
// https://sqlite.org/c3ref/value_blob.html
|
||||
func (v Value) Text() string {
|
||||
return string(v.RawText())
|
||||
}
|
||||
@@ -92,7 +125,7 @@ func (v Value) Text() string {
|
||||
// Blob appends to buf and returns
|
||||
// the value as a []byte.
|
||||
//
|
||||
// https://www.sqlite.org/c3ref/value_blob.html
|
||||
// https://sqlite.org/c3ref/value_blob.html
|
||||
func (v Value) Blob(buf []byte) []byte {
|
||||
return append(buf, v.RawBlob()...)
|
||||
}
|
||||
@@ -101,9 +134,9 @@ func (v Value) Blob(buf []byte) []byte {
|
||||
// The []byte is owned by SQLite and may be invalidated by
|
||||
// subsequent calls to [Value] methods.
|
||||
//
|
||||
// https://www.sqlite.org/c3ref/value_blob.html
|
||||
// https://sqlite.org/c3ref/value_blob.html
|
||||
func (v Value) RawText() []byte {
|
||||
r := v.call(v.api.valueText, uint64(v.handle))
|
||||
r := v.call("sqlite3_value_text", v.protected())
|
||||
return v.rawBytes(uint32(r))
|
||||
}
|
||||
|
||||
@@ -111,9 +144,9 @@ func (v Value) RawText() []byte {
|
||||
// The []byte is owned by SQLite and may be invalidated by
|
||||
// subsequent calls to [Value] methods.
|
||||
//
|
||||
// https://www.sqlite.org/c3ref/value_blob.html
|
||||
// https://sqlite.org/c3ref/value_blob.html
|
||||
func (v Value) RawBlob() []byte {
|
||||
r := v.call(v.api.valueBlob, uint64(v.handle))
|
||||
r := v.call("sqlite3_value_blob", v.protected())
|
||||
return v.rawBytes(uint32(r))
|
||||
}
|
||||
|
||||
@@ -122,14 +155,14 @@ func (v Value) rawBytes(ptr uint32) []byte {
|
||||
return nil
|
||||
}
|
||||
|
||||
r := v.call(v.api.valueBytes, uint64(v.handle))
|
||||
r := v.call("sqlite3_value_bytes", v.protected())
|
||||
return util.View(v.mod, ptr, r)
|
||||
}
|
||||
|
||||
// Pointer gets the pointer associated with this value,
|
||||
// or nil if it has no associated pointer.
|
||||
func (v Value) Pointer() any {
|
||||
r := v.call(v.api.valuePointer, uint64(v.handle))
|
||||
r := v.call("sqlite3_value_pointer_go", v.protected())
|
||||
return util.GetHandle(v.ctx, uint32(r))
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Go SQLite VFS API
|
||||
|
||||
This package implements the SQLite [OS Interface](https://www.sqlite.org/vfs.html) (aka VFS).
|
||||
This package implements the SQLite [OS Interface](https://sqlite.org/vfs.html) (aka VFS).
|
||||
|
||||
It replaces the default SQLite VFS with a pure Go implementation.
|
||||
|
||||
|
||||
20
vfs/api.go
20
vfs/api.go
@@ -7,7 +7,7 @@ import "net/url"
|
||||
//
|
||||
// Use sqlite3.ErrorCode or sqlite3.ExtendedErrorCode to return specific error codes to SQLite.
|
||||
//
|
||||
// https://www.sqlite.org/c3ref/vfs.html
|
||||
// https://sqlite.org/c3ref/vfs.html
|
||||
type VFS interface {
|
||||
Open(name string, flags OpenFlag) (File, OpenFlag, error)
|
||||
Delete(name string, syncDir bool) error
|
||||
@@ -18,7 +18,7 @@ type VFS interface {
|
||||
// VFSParams extends VFS with the ability to handle URI parameters
|
||||
// through the OpenParams method.
|
||||
//
|
||||
// https://www.sqlite.org/c3ref/uri_boolean.html
|
||||
// https://sqlite.org/c3ref/uri_boolean.html
|
||||
type VFSParams interface {
|
||||
VFS
|
||||
OpenParams(name string, flags OpenFlag, params url.Values) (File, OpenFlag, error)
|
||||
@@ -29,7 +29,7 @@ type VFSParams interface {
|
||||
// Use sqlite3.ErrorCode or sqlite3.ExtendedErrorCode to return specific error codes to SQLite.
|
||||
// In particular, sqlite3.BUSY is necessary to correctly implement lock methods.
|
||||
//
|
||||
// https://www.sqlite.org/c3ref/io_methods.html
|
||||
// https://sqlite.org/c3ref/io_methods.html
|
||||
type File interface {
|
||||
Close() error
|
||||
ReadAt(p []byte, off int64) (n int, err error)
|
||||
@@ -47,7 +47,7 @@ type File interface {
|
||||
// FileLockState extends File to implement the
|
||||
// SQLITE_FCNTL_LOCKSTATE file control opcode.
|
||||
//
|
||||
// https://www.sqlite.org/c3ref/c_fcntl_begin_atomic_write.html#sqlitefcntllockstate
|
||||
// https://sqlite.org/c3ref/c_fcntl_begin_atomic_write.html#sqlitefcntllockstate
|
||||
type FileLockState interface {
|
||||
File
|
||||
LockState() LockLevel
|
||||
@@ -56,7 +56,7 @@ type FileLockState interface {
|
||||
// FileSizeHint extends File to implement the
|
||||
// SQLITE_FCNTL_SIZE_HINT file control opcode.
|
||||
//
|
||||
// https://www.sqlite.org/c3ref/c_fcntl_begin_atomic_write.html#sqlitefcntlsizehint
|
||||
// https://sqlite.org/c3ref/c_fcntl_begin_atomic_write.html#sqlitefcntlsizehint
|
||||
type FileSizeHint interface {
|
||||
File
|
||||
SizeHint(size int64) error
|
||||
@@ -65,7 +65,7 @@ type FileSizeHint interface {
|
||||
// FileHasMoved extends File to implement the
|
||||
// SQLITE_FCNTL_HAS_MOVED file control opcode.
|
||||
//
|
||||
// https://www.sqlite.org/c3ref/c_fcntl_begin_atomic_write.html#sqlitefcntlhasmoved
|
||||
// https://sqlite.org/c3ref/c_fcntl_begin_atomic_write.html#sqlitefcntlhasmoved
|
||||
type FileHasMoved interface {
|
||||
File
|
||||
HasMoved() (bool, error)
|
||||
@@ -74,7 +74,7 @@ type FileHasMoved interface {
|
||||
// FileOverwrite extends File to implement the
|
||||
// SQLITE_FCNTL_OVERWRITE file control opcode.
|
||||
//
|
||||
// https://www.sqlite.org/c3ref/c_fcntl_begin_atomic_write.html#sqlitefcntloverwrite
|
||||
// https://sqlite.org/c3ref/c_fcntl_begin_atomic_write.html#sqlitefcntloverwrite
|
||||
type FileOverwrite interface {
|
||||
File
|
||||
Overwrite() error
|
||||
@@ -83,7 +83,7 @@ type FileOverwrite interface {
|
||||
// FilePowersafeOverwrite extends File to implement the
|
||||
// SQLITE_FCNTL_POWERSAFE_OVERWRITE file control opcode.
|
||||
//
|
||||
// https://www.sqlite.org/c3ref/c_fcntl_begin_atomic_write.html#sqlitefcntlpowersafeoverwrite
|
||||
// https://sqlite.org/c3ref/c_fcntl_begin_atomic_write.html#sqlitefcntlpowersafeoverwrite
|
||||
type FilePowersafeOverwrite interface {
|
||||
File
|
||||
PowersafeOverwrite() bool
|
||||
@@ -93,7 +93,7 @@ type FilePowersafeOverwrite interface {
|
||||
// FilePowersafeOverwrite extends File to implement the
|
||||
// SQLITE_FCNTL_COMMIT_PHASETWO file control opcode.
|
||||
//
|
||||
// https://www.sqlite.org/c3ref/c_fcntl_begin_atomic_write.html#sqlitefcntlcommitphasetwo
|
||||
// https://sqlite.org/c3ref/c_fcntl_begin_atomic_write.html#sqlitefcntlcommitphasetwo
|
||||
type FileCommitPhaseTwo interface {
|
||||
File
|
||||
CommitPhaseTwo() error
|
||||
@@ -103,7 +103,7 @@ type FileCommitPhaseTwo interface {
|
||||
// SQLITE_FCNTL_BEGIN_ATOMIC_WRITE, SQLITE_FCNTL_COMMIT_ATOMIC_WRITE
|
||||
// and SQLITE_FCNTL_ROLLBACK_ATOMIC_WRITE file control opcodes.
|
||||
//
|
||||
// https://www.sqlite.org/c3ref/c_fcntl_begin_atomic_write.html#sqlitefcntlbeginatomicwrite
|
||||
// https://sqlite.org/c3ref/c_fcntl_begin_atomic_write.html#sqlitefcntlbeginatomicwrite
|
||||
type FileBatchAtomicWrite interface {
|
||||
File
|
||||
BeginAtomicWrite() error
|
||||
|
||||
16
vfs/const.go
16
vfs/const.go
@@ -3,12 +3,12 @@ package vfs
|
||||
import "github.com/ncruces/go-sqlite3/internal/util"
|
||||
|
||||
const (
|
||||
_MAX_STRING = 512 // Used for short strings: names, error messages…
|
||||
_MAX_NAME = 512 // Used for short strings: names, error messages…
|
||||
_MAX_PATHNAME = 512
|
||||
_DEFAULT_SECTOR_SIZE = 4096
|
||||
)
|
||||
|
||||
// https://www.sqlite.org/rescode.html
|
||||
// https://sqlite.org/rescode.html
|
||||
type _ErrorCode uint32
|
||||
|
||||
func (e _ErrorCode) Error() string {
|
||||
@@ -49,7 +49,7 @@ const (
|
||||
|
||||
// OpenFlag is a flag for the [VFS] Open method.
|
||||
//
|
||||
// https://www.sqlite.org/c3ref/c_open_autoproxy.html
|
||||
// https://sqlite.org/c3ref/c_open_autoproxy.html
|
||||
type OpenFlag uint32
|
||||
|
||||
const (
|
||||
@@ -78,7 +78,7 @@ const (
|
||||
|
||||
// AccessFlag is a flag for the [VFS] Access method.
|
||||
//
|
||||
// https://www.sqlite.org/c3ref/c_access_exists.html
|
||||
// https://sqlite.org/c3ref/c_access_exists.html
|
||||
type AccessFlag uint32
|
||||
|
||||
const (
|
||||
@@ -89,7 +89,7 @@ const (
|
||||
|
||||
// SyncFlag is a flag for the [File] Sync method.
|
||||
//
|
||||
// https://www.sqlite.org/c3ref/c_sync_dataonly.html
|
||||
// https://sqlite.org/c3ref/c_sync_dataonly.html
|
||||
type SyncFlag uint32
|
||||
|
||||
const (
|
||||
@@ -100,7 +100,7 @@ const (
|
||||
|
||||
// LockLevel is a value used with [File] Lock and Unlock methods.
|
||||
//
|
||||
// https://www.sqlite.org/c3ref/c_lock_exclusive.html
|
||||
// https://sqlite.org/c3ref/c_lock_exclusive.html
|
||||
type LockLevel uint32
|
||||
|
||||
const (
|
||||
@@ -146,7 +146,7 @@ const (
|
||||
|
||||
// DeviceCharacteristic is a flag retuned by the [File] DeviceCharacteristics method.
|
||||
//
|
||||
// https://www.sqlite.org/c3ref/c_iocap_atomic.html
|
||||
// https://sqlite.org/c3ref/c_iocap_atomic.html
|
||||
type DeviceCharacteristic uint32
|
||||
|
||||
const (
|
||||
@@ -167,7 +167,7 @@ const (
|
||||
IOCAP_BATCH_ATOMIC DeviceCharacteristic = 0x00004000
|
||||
)
|
||||
|
||||
// https://www.sqlite.org/c3ref/c_fcntl_begin_atomic_write.html
|
||||
// https://sqlite.org/c3ref/c_fcntl_begin_atomic_write.html
|
||||
type _FcntlOpcode uint32
|
||||
|
||||
const (
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Go `"memdb"` SQLite VFS
|
||||
|
||||
This package implements the [`"memdb"`](https://www.sqlite.org/src/file/src/memdb.c)
|
||||
This package implements the [`"memdb"`](https://sqlite.org/src/file/src/memdb.c)
|
||||
SQLite VFS in pure Go.
|
||||
|
||||
It has some benefits over the C version:
|
||||
|
||||
@@ -36,6 +36,12 @@ func Create(name string, data []byte) {
|
||||
db := new(memDB)
|
||||
db.size = int64(len(data))
|
||||
|
||||
// Convert data from WAL to rollback journal.
|
||||
if len(data) >= 20 && data[18] == 2 && data[19] == 2 {
|
||||
data[18] = 1
|
||||
data[19] = 1
|
||||
}
|
||||
|
||||
sectors := divRoundUp(db.size, sectorSize)
|
||||
db.data = make([]*[sectorSize]byte, sectors)
|
||||
for i := range db.data {
|
||||
|
||||
28
vfs/memdb/memdb_test.go
Normal file
28
vfs/memdb/memdb_test.go
Normal file
@@ -0,0 +1,28 @@
|
||||
package memdb
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
_ "embed"
|
||||
|
||||
"github.com/ncruces/go-sqlite3"
|
||||
_ "github.com/ncruces/go-sqlite3/embed"
|
||||
)
|
||||
|
||||
//go:embed testdata/wal.db
|
||||
var walDB []byte
|
||||
|
||||
func Test_wal(t *testing.T) {
|
||||
Create("test.db", walDB)
|
||||
|
||||
db, err := sqlite3.Open("file:/test.db?vfs=memdb")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
err = db.Exec(`CREATE TABLE IF NOT EXISTS users (id INT, name VARCHAR(10))`)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
BIN
vfs/memdb/testdata/wal.db
vendored
Normal file
BIN
vfs/memdb/testdata/wal.db
vendored
Normal file
Binary file not shown.
@@ -7,12 +7,6 @@ import (
|
||||
"os"
|
||||
)
|
||||
|
||||
const (
|
||||
_S_IREAD = 0400
|
||||
_S_IWRITE = 0200
|
||||
_S_IEXEC = 0100
|
||||
)
|
||||
|
||||
func osAccess(path string, flags AccessFlag) error {
|
||||
fi, err := os.Stat(path)
|
||||
if err != nil {
|
||||
@@ -22,12 +16,18 @@ func osAccess(path string, flags AccessFlag) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
var want fs.FileMode = _S_IREAD
|
||||
const (
|
||||
S_IREAD = 0400
|
||||
S_IWRITE = 0200
|
||||
S_IEXEC = 0100
|
||||
)
|
||||
|
||||
var want fs.FileMode = S_IREAD
|
||||
if flags == ACCESS_READWRITE {
|
||||
want |= _S_IWRITE
|
||||
want |= S_IWRITE
|
||||
}
|
||||
if fi.IsDir() {
|
||||
want |= _S_IEXEC
|
||||
want |= S_IEXEC
|
||||
}
|
||||
if fi.Mode()&want != want {
|
||||
return fs.ErrPermission
|
||||
|
||||
@@ -18,7 +18,7 @@ import (
|
||||
var testDB string
|
||||
|
||||
func Example_http() {
|
||||
readervfs.Create("demo.db", httpreadat.New("https://www.sanford.io/demo.db"))
|
||||
readervfs.Create("demo.db", httpreadat.New("https://sanford.io/demo.db"))
|
||||
defer readervfs.Delete("demo.db")
|
||||
|
||||
db, err := sql.Open("sqlite3", "file:demo.db?vfs=reader")
|
||||
@@ -34,8 +34,8 @@ func Example_http() {
|
||||
}
|
||||
rows, err := db.Query(`
|
||||
SELECT period, data_value, magntude, units FROM csv
|
||||
WHERE period > '2010'
|
||||
LIMIT 10`)
|
||||
WHERE period > '2010'
|
||||
LIMIT 10`)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@ import (
|
||||
|
||||
type readerVFS struct{}
|
||||
|
||||
// Open implements the [vfs.VFS] interface.
|
||||
func (readerVFS) Open(name string, flags vfs.OpenFlag) (vfs.File, vfs.OpenFlag, error) {
|
||||
if flags&vfs.OPEN_MAIN_DB == 0 {
|
||||
return nil, flags, sqlite3.CANTOPEN
|
||||
@@ -20,17 +19,14 @@ func (readerVFS) Open(name string, flags vfs.OpenFlag) (vfs.File, vfs.OpenFlag,
|
||||
return nil, flags, sqlite3.CANTOPEN
|
||||
}
|
||||
|
||||
// Delete implements the [vfs.VFS] interface.
|
||||
func (readerVFS) Delete(name string, dirSync bool) error {
|
||||
return sqlite3.IOERR_DELETE
|
||||
}
|
||||
|
||||
// Access implements the [vfs.VFS] interface.
|
||||
func (readerVFS) Access(name string, flag vfs.AccessFlag) (bool, error) {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// FullPathname implements the [vfs.VFS] interface.
|
||||
func (readerVFS) FullPathname(name string) (string, error) {
|
||||
return name, nil
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ var (
|
||||
// If there is no match, nil is returned.
|
||||
// If name is empty, the default VFS is returned.
|
||||
//
|
||||
// https://www.sqlite.org/c3ref/vfs_find.html
|
||||
// https://sqlite.org/c3ref/vfs_find.html
|
||||
func Find(name string) VFS {
|
||||
if name == "" || name == "os" {
|
||||
return vfsOS{}
|
||||
@@ -24,7 +24,7 @@ func Find(name string) VFS {
|
||||
|
||||
// Register registers a VFS.
|
||||
//
|
||||
// https://www.sqlite.org/c3ref/vfs_find.html
|
||||
// https://sqlite.org/c3ref/vfs_find.html
|
||||
func Register(name string, vfs VFS) {
|
||||
if name == "" || name == "os" {
|
||||
return
|
||||
@@ -39,7 +39,7 @@ func Register(name string, vfs VFS) {
|
||||
|
||||
// Unregister unregisters a VFS.
|
||||
//
|
||||
// https://www.sqlite.org/c3ref/vfs_find.html
|
||||
// https://sqlite.org/c3ref/vfs_find.html
|
||||
func Unregister(name string) {
|
||||
vfsRegistryMtx.Lock()
|
||||
defer vfsRegistryMtx.Unlock()
|
||||
|
||||
@@ -102,7 +102,7 @@ func Test_config01(t *testing.T) {
|
||||
cfg := config(ctx).WithArgs("mptest", name, "config01.test")
|
||||
mod, err := rt.InstantiateModule(ctx, module, cfg)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
t.Fatal(err)
|
||||
}
|
||||
mod.Close(ctx)
|
||||
}
|
||||
@@ -120,7 +120,7 @@ func Test_config02(t *testing.T) {
|
||||
cfg := config(ctx).WithArgs("mptest", name, "config02.test")
|
||||
mod, err := rt.InstantiateModule(ctx, module, cfg)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
t.Fatal(err)
|
||||
}
|
||||
mod.Close(ctx)
|
||||
}
|
||||
@@ -135,7 +135,7 @@ func Test_crash01(t *testing.T) {
|
||||
cfg := config(ctx).WithArgs("mptest", name, "crash01.test")
|
||||
mod, err := rt.InstantiateModule(ctx, module, cfg)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
t.Fatal(err)
|
||||
}
|
||||
mod.Close(ctx)
|
||||
}
|
||||
@@ -150,7 +150,7 @@ func Test_multiwrite01(t *testing.T) {
|
||||
cfg := config(ctx).WithArgs("mptest", name, "multiwrite01.test")
|
||||
mod, err := rt.InstantiateModule(ctx, module, cfg)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
t.Fatal(err)
|
||||
}
|
||||
mod.Close(ctx)
|
||||
}
|
||||
@@ -163,7 +163,7 @@ func Test_config01_memory(t *testing.T) {
|
||||
"--timeout", "1000")
|
||||
mod, err := rt.InstantiateModule(ctx, module, cfg)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
t.Fatal(err)
|
||||
}
|
||||
mod.Close(ctx)
|
||||
}
|
||||
@@ -180,7 +180,7 @@ func Test_multiwrite01_memory(t *testing.T) {
|
||||
"--timeout", "1000")
|
||||
mod, err := rt.InstantiateModule(ctx, module, cfg)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
t.Fatal(err)
|
||||
}
|
||||
mod.Close(ctx)
|
||||
}
|
||||
|
||||
2
vfs/tests/mptest/testdata/build.sh
vendored
2
vfs/tests/mptest/testdata/build.sh
vendored
@@ -23,7 +23,7 @@ WASI_SDK="$ROOT/tools/wasi-sdk-20.0/bin"
|
||||
-DSQLITE_THREADSAFE=0 -DSQLITE_OMIT_LOAD_EXTENSION \
|
||||
-D_WASI_EMULATED_GETPID -lwasi-emulated-getpid
|
||||
|
||||
"$BINARYEN/wasm-opt" -g --strip -c -O3 \
|
||||
"$BINARYEN/wasm-opt" -g --strip --strip-producers -c -O3 \
|
||||
mptest.wasm -o mptest.tmp \
|
||||
--enable-simd --enable-mutable-globals --enable-multivalue \
|
||||
--enable-bulk-memory --enable-reference-types \
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user