mirror of
https://github.com/ncruces/go-sqlite3.git
synced 2026-01-19 09:04:16 +00:00
Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ba18facb0d | ||
|
|
5653efa70e | ||
|
|
1acb95917a | ||
|
|
e31a42fb22 |
@@ -28,6 +28,8 @@ and uses [wazero](https://wazero.io/) to provide `cgo`-free SQLite bindings.
|
||||
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/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)
|
||||
|
||||
3
conn.go
3
conn.go
@@ -170,6 +170,9 @@ func (c *Conn) Prepare(sql string) (stmt *Stmt, tail string, err error) {
|
||||
//
|
||||
// https://sqlite.org/c3ref/prepare.html
|
||||
func (c *Conn) PrepareFlags(sql string, flags PrepareFlag) (stmt *Stmt, tail string, err error) {
|
||||
if len(sql) > _MAX_LENGTH {
|
||||
return nil, "", TOOBIG
|
||||
}
|
||||
if emptyStatement(sql) {
|
||||
return nil, "", nil
|
||||
}
|
||||
|
||||
5
const.go
5
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
|
||||
|
||||
@@ -297,6 +297,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++ {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# 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:
|
||||
|
||||
Binary file not shown.
@@ -136,7 +136,7 @@ func Test_ErrorCode_Error(t *testing.T) {
|
||||
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)
|
||||
want += util.ReadString(db.mod, uint32(r), _MAX_NAME)
|
||||
|
||||
got := ErrorCode(i).Error()
|
||||
if got != want {
|
||||
@@ -158,7 +158,7 @@ func Test_ExtendedErrorCode_Error(t *testing.T) {
|
||||
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)
|
||||
want += util.ReadString(db.mod, uint32(r), _MAX_NAME)
|
||||
|
||||
got := ExtendedErrorCode(i).Error()
|
||||
if got != want {
|
||||
|
||||
@@ -59,7 +59,7 @@ func TestRegister(t *testing.T) {
|
||||
|
||||
csv.Register(db)
|
||||
|
||||
data := `
|
||||
const data = `
|
||||
"Rob" "Pike" rob
|
||||
"Ken" Thompson ken
|
||||
Robert "Griesemer" "gri"`
|
||||
@@ -116,35 +116,35 @@ func TestRegister_errors(t *testing.T) {
|
||||
|
||||
err = db.Exec(`CREATE VIRTUAL TABLE temp.users USING csv()`)
|
||||
if err == nil {
|
||||
t.Fatal(err)
|
||||
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(err)
|
||||
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(err)
|
||||
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(err)
|
||||
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(err)
|
||||
t.Fatal("want error")
|
||||
} else {
|
||||
t.Log(err)
|
||||
}
|
||||
|
||||
121
ext/lines/lines.go
Normal file
121
ext/lines/lines.go
Normal file
@@ -0,0 +1,121 @@
|
||||
// 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, arg ...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, arg ...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 {
|
||||
reader bool
|
||||
scanner *bufio.Scanner
|
||||
closer io.Closer
|
||||
rowID int64
|
||||
eof 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]
|
||||
if c.reader {
|
||||
if data.Type() == sqlite3.NULL {
|
||||
if p, ok := data.Pointer().(io.ReaderAt); ok {
|
||||
r = io.NewSectionReader(p, 0, math.MaxInt64)
|
||||
}
|
||||
} else {
|
||||
f, err := os.Open(data.Text())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c.closer = f
|
||||
r = f
|
||||
}
|
||||
} else if data.Type() != sqlite3.NULL {
|
||||
r = bytes.NewReader(data.RawBlob())
|
||||
}
|
||||
|
||||
if r == nil {
|
||||
return fmt.Errorf("lines: unsupported argument:%.0w %v", sqlite3.MISMATCH, data.Type())
|
||||
}
|
||||
c.scanner = bufio.NewScanner(r)
|
||||
c.rowID = 0
|
||||
return c.Next()
|
||||
}
|
||||
178
ext/lines/lines_test.go
Normal file
178
ext/lines/lines_test.go
Normal file
@@ -0,0 +1,178 @@
|
||||
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) {
|
||||
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) {
|
||||
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) {
|
||||
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) {
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
22
func.go
22
func.go
@@ -101,14 +101,14 @@ func funcCallback(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 := aggregateCtxHandle(db, pCtx, nil).(AggregateFunction)
|
||||
fn := aggregateCtxHandle(db, pCtx, nil)
|
||||
fn.Step(Context{db, pCtx}, callbackArgs(db, nArg, pArg)...)
|
||||
}
|
||||
|
||||
func finalCallback(ctx context.Context, mod api.Module, pCtx uint32) {
|
||||
var handle uint32
|
||||
db := ctx.Value(connKey{}).(*Conn)
|
||||
fn := aggregateCtxHandle(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)
|
||||
@@ -117,7 +117,7 @@ func finalCallback(ctx context.Context, mod api.Module, pCtx uint32) {
|
||||
|
||||
func valueCallback(ctx context.Context, mod api.Module, pCtx uint32) {
|
||||
db := ctx.Value(connKey{}).(*Conn)
|
||||
fn := aggregateCtxHandle(db, pCtx, nil).(AggregateFunction)
|
||||
fn := aggregateCtxHandle(db, pCtx, nil)
|
||||
fn.Value(Context{db, pCtx})
|
||||
}
|
||||
|
||||
@@ -132,8 +132,8 @@ func userDataHandle(db *Conn, pCtx uint32) any {
|
||||
return util.GetHandle(db.ctx, pApp)
|
||||
}
|
||||
|
||||
func aggregateCtxHandle(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 {
|
||||
@@ -141,20 +141,18 @@ func aggregateCtxHandle(db *Conn, pCtx uint32, close *uint32) any {
|
||||
}
|
||||
ptr := uint32(db.call(db.api.aggregateCtx, 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.
|
||||
// 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))
|
||||
|
||||
@@ -210,12 +210,12 @@ func (sqlt *sqlite) error(rc uint64, handle uint32, sql ...string) error {
|
||||
}
|
||||
|
||||
if r := sqlt.call(sqlt.api.errstr, rc); r != 0 {
|
||||
err.str = util.ReadString(sqlt.mod, uint32(r), _MAX_STRING)
|
||||
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)
|
||||
err.msg = util.ReadString(sqlt.mod, uint32(r), _MAX_NAME)
|
||||
}
|
||||
|
||||
if sql != nil {
|
||||
|
||||
@@ -3,7 +3,7 @@ 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-*
|
||||
@@ -12,24 +12,24 @@ cat *.patch | patch --posix
|
||||
|
||||
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 ~-
|
||||
13
stmt.go
13
stmt.go
@@ -123,7 +123,7 @@ func (s *Stmt) BindName(param int) string {
|
||||
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.
|
||||
@@ -173,6 +173,9 @@ func (s *Stmt) BindFloat(param int, value float64) error {
|
||||
//
|
||||
// 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,
|
||||
uint64(s.handle), uint64(param),
|
||||
@@ -186,6 +189,9 @@ func (s *Stmt) BindText(param int, value string) error {
|
||||
//
|
||||
// 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(s.c.api.bindText,
|
||||
uint64(s.handle), uint64(param),
|
||||
@@ -200,6 +206,9 @@ func (s *Stmt) BindRawText(param int, value []byte) error {
|
||||
//
|
||||
// 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,
|
||||
uint64(s.handle), uint64(param),
|
||||
@@ -309,7 +318,7 @@ func (s *Stmt) ColumnName(col int) string {
|
||||
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.
|
||||
|
||||
@@ -58,6 +58,7 @@ func TestJSON(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
want := []string{
|
||||
"null", "1", "3.141592653589793",
|
||||
|
||||
@@ -587,3 +587,56 @@ func TestStmt_ColumnTime(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ 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
|
||||
)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
4
vfs/tests/mptest/testdata/mptest.wasm.bz2
vendored
4
vfs/tests/mptest/testdata/mptest.wasm.bz2
vendored
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:74294bf19d213056ef5ffb7a980c3a7de5d029d0621ded53394d3055dfc4f604
|
||||
size 513604
|
||||
oid sha256:6db7a53d189055287cb4eb79d7360bf82e5b3084018b868ea49e70a1dd43f474
|
||||
size 512643
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:966754393264cc43eb931ece22941d0d607e7e776e26c26b548209d2264d01a1
|
||||
size 527530
|
||||
oid sha256:ab2acd50a808d534b516b5e856ad76d15d5d9d390178f27cd5418de832080ae9
|
||||
size 526575
|
||||
|
||||
@@ -45,7 +45,7 @@ func ExportHostFunctions(env wazero.HostModuleBuilder) wazero.HostModuleBuilder
|
||||
}
|
||||
|
||||
func vfsFind(ctx context.Context, mod api.Module, zVfsName uint32) uint32 {
|
||||
name := util.ReadString(mod, zVfsName, _MAX_STRING)
|
||||
name := util.ReadString(mod, zVfsName, _MAX_NAME)
|
||||
if vfs := Find(name); vfs != nil && vfs != (vfsOS{}) {
|
||||
return 1
|
||||
}
|
||||
@@ -371,7 +371,7 @@ func vfsURIParameters(ctx context.Context, mod api.Module, zPath uint32, flags O
|
||||
if stack[0] == 0 {
|
||||
return params
|
||||
}
|
||||
key := util.ReadString(mod, uint32(stack[0]), _MAX_STRING)
|
||||
key := util.ReadString(mod, uint32(stack[0]), _MAX_NAME)
|
||||
if params.Has(key) {
|
||||
continue
|
||||
}
|
||||
@@ -384,7 +384,7 @@ func vfsURIParameters(ctx context.Context, mod api.Module, zPath uint32, flags O
|
||||
if params == nil {
|
||||
params = url.Values{}
|
||||
}
|
||||
params.Set(key, util.ReadString(mod, uint32(stack[0]), _MAX_STRING))
|
||||
params.Set(key, util.ReadString(mod, uint32(stack[0]), _MAX_NAME))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -392,7 +392,7 @@ func vfsGet(mod api.Module, pVfs uint32) VFS {
|
||||
var name string
|
||||
if pVfs != 0 {
|
||||
const zNameOffset = 16
|
||||
name = util.ReadString(mod, util.ReadUint32(mod, pVfs+zNameOffset), _MAX_STRING)
|
||||
name = util.ReadString(mod, util.ReadUint32(mod, pVfs+zNameOffset), _MAX_NAME)
|
||||
}
|
||||
if vfs := Find(name); vfs != nil {
|
||||
return vfs
|
||||
|
||||
16
vtab.go
16
vtab.go
@@ -70,7 +70,7 @@ func implements[T any](typ reflect.Type) bool {
|
||||
//
|
||||
// https://sqlite.org/c3ref/declare_vtab.html
|
||||
func (c *Conn) DeclareVtab(sql string) error {
|
||||
// defer c.arena.reset()
|
||||
// The arena will be cleared by the prepare or exec method.
|
||||
sqlPtr := c.arena.string(sql)
|
||||
r := c.call(c.api.declareVTab, uint64(c.handle), uint64(sqlPtr))
|
||||
return c.error(r)
|
||||
@@ -255,7 +255,7 @@ type IndexConstraintUsage struct {
|
||||
//
|
||||
// https://sqlite.org/c3ref/vtab_rhs_value.html
|
||||
func (idx *IndexInfo) RHSValue(column int) (*Value, error) {
|
||||
// defer idx.c.arena.reset()
|
||||
// The arena will be cleared by the prepare or exec method.
|
||||
valPtr := idx.c.arena.new(ptrlen)
|
||||
r := idx.c.call(idx.c.api.vtabRHSValue,
|
||||
uint64(idx.handle), uint64(column), uint64(valPtr))
|
||||
@@ -369,7 +369,7 @@ func vtabModuleCallback(i int) func(_ context.Context, _ api.Module, _, _, _, _,
|
||||
|
||||
for i := uint32(0); i < argc; i++ {
|
||||
ptr := util.ReadUint32(mod, argv+i*ptrlen)
|
||||
arg[i+1] = reflect.ValueOf(util.ReadString(mod, ptr, _MAX_STRING))
|
||||
arg[i+1] = reflect.ValueOf(util.ReadString(mod, ptr, _MAX_SQL_LENGTH))
|
||||
}
|
||||
|
||||
module := vtabGetHandle(ctx, mod, pMod)
|
||||
@@ -425,13 +425,13 @@ func vtabUpdateCallback(ctx context.Context, mod api.Module, pVTab, argc, argv,
|
||||
|
||||
func vtabRenameCallback(ctx context.Context, mod api.Module, pVTab, zNew uint32) uint32 {
|
||||
vtab := vtabGetHandle(ctx, mod, pVTab).(VTabRenamer)
|
||||
err := vtab.Rename(util.ReadString(mod, zNew, _MAX_STRING))
|
||||
err := vtab.Rename(util.ReadString(mod, zNew, _MAX_NAME))
|
||||
return vtabError(ctx, mod, pVTab, _VTAB_ERROR, err)
|
||||
}
|
||||
|
||||
func vtabFindFuncCallback(ctx context.Context, mod api.Module, pVTab, nArg, zName, pxFunc uint32) uint32 {
|
||||
vtab := vtabGetHandle(ctx, mod, pVTab).(VTabOverloader)
|
||||
fn, op := vtab.FindFunction(int(nArg), util.ReadString(mod, zName, _MAX_STRING))
|
||||
fn, op := vtab.FindFunction(int(nArg), util.ReadString(mod, zName, _MAX_NAME))
|
||||
if fn != nil {
|
||||
handle := util.AddHandle(ctx, fn)
|
||||
util.WriteUint32(mod, pxFunc, handle)
|
||||
@@ -444,8 +444,8 @@ func vtabFindFuncCallback(ctx context.Context, mod api.Module, pVTab, nArg, zNam
|
||||
|
||||
func vtabIntegrityCallback(ctx context.Context, mod api.Module, pVTab, zSchema, zTabName, mFlags, pzErr uint32) uint32 {
|
||||
vtab := vtabGetHandle(ctx, mod, pVTab).(VTabChecker)
|
||||
schema := util.ReadString(mod, zSchema, _MAX_STRING)
|
||||
table := util.ReadString(mod, zTabName, _MAX_STRING)
|
||||
schema := util.ReadString(mod, zSchema, _MAX_NAME)
|
||||
table := util.ReadString(mod, zTabName, _MAX_NAME)
|
||||
err := vtab.Integrity(schema, table, int(mFlags))
|
||||
// xIntegrity should return OK - even if it finds problems in the content of the virtual table.
|
||||
// https://sqlite.org/vtab.html#xintegrity
|
||||
@@ -518,7 +518,7 @@ func cursorFilterCallback(ctx context.Context, mod api.Module, pCur, idxNum, idx
|
||||
args := callbackArgs(db, argc, argv)
|
||||
var idxName string
|
||||
if idxStr != 0 {
|
||||
idxName = util.ReadString(mod, idxStr, _MAX_STRING)
|
||||
idxName = util.ReadString(mod, idxStr, _MAX_NAME)
|
||||
}
|
||||
err := cursor.Filter(int(idxNum), idxName, args...)
|
||||
return vtabError(ctx, mod, pCur, _CURSOR_ERROR, err)
|
||||
|
||||
Reference in New Issue
Block a user