mirror of
https://github.com/ncruces/go-sqlite3.git
synced 2026-01-12 22:19:14 +00:00
Compare commits
20 Commits
gormlite/v
...
gormlite/v
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
58a32d7c9d | ||
|
|
6765e883c1 | ||
|
|
18fc608433 | ||
|
|
77f37893b9 | ||
|
|
f1e36e2581 | ||
|
|
772b9153c7 | ||
|
|
4b280a3a7e | ||
|
|
19b6098bf6 | ||
|
|
2aa685320f | ||
|
|
9941be05c2 | ||
|
|
a0a9ab7737 | ||
|
|
a77727a1ce | ||
|
|
47fe032078 | ||
|
|
bdfe279444 | ||
|
|
a86937a54e | ||
|
|
6ef422fbde | ||
|
|
ff0cb6fb88 | ||
|
|
72db90efdf | ||
|
|
5a3fdef3c5 | ||
|
|
ff34b0cae1 |
1
.github/workflows/go.yml
vendored
1
.github/workflows/go.yml
vendored
@@ -57,6 +57,7 @@ jobs:
|
||||
with:
|
||||
chart: 'true'
|
||||
amend: 'true'
|
||||
reuse-go: 'true'
|
||||
if: |
|
||||
matrix.os == 'ubuntu-latest' &&
|
||||
github.event_name == 'push'
|
||||
|
||||
@@ -50,13 +50,15 @@ OFD locks are fully compatible with process-associated POSIX advisory locks.
|
||||
|
||||
On BSD Unixes, this module uses
|
||||
[BSD locks](https://man.freebsd.org/cgi/man.cgi?query=flock&sektion=2).
|
||||
BSD locks may _not_ be compatible with process-associated POSIX advisory locks.
|
||||
BSD locks may _not_ be compatible with process-associated POSIX advisory locks
|
||||
(they are on FreeBSD).
|
||||
|
||||
#### Testing
|
||||
|
||||
The pure Go VFS is tested by running an unmodified build of SQLite's
|
||||
The pure Go VFS is tested by running SQLite's
|
||||
[mptest](https://github.com/sqlite/sqlite/blob/master/mptest/mptest.c)
|
||||
on Linux, macOS and Windows.
|
||||
on Linux, macOS and Windows;
|
||||
BSD code paths are tested on macOS using the `sqlite3_bsd` build tag.
|
||||
Performance is tested by running
|
||||
[speedtest1](https://github.com/sqlite/sqlite/blob/master/test/speedtest1.c).
|
||||
|
||||
@@ -73,6 +75,7 @@ Performance is tested by running
|
||||
- [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)
|
||||
- [ ] [MVCC](https://en.wikipedia.org/wiki/Multiversion_concurrency_control) VFS, using [BadgerDB](https://github.com/dgraph-io/badger)
|
||||
|
||||
### Alternatives
|
||||
|
||||
|
||||
@@ -46,36 +46,65 @@ func init() {
|
||||
|
||||
type sqlite struct{}
|
||||
|
||||
func (sqlite) Open(name string) (_ driver.Conn, err error) {
|
||||
var c conn
|
||||
c.Conn, err = sqlite3.Open(name)
|
||||
func (sqlite) Open(name string) (driver.Conn, error) {
|
||||
c, err := sqlite{}.OpenConnector(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return c.Connect(context.Background())
|
||||
}
|
||||
|
||||
var pragmas bool
|
||||
c.txBegin = "BEGIN"
|
||||
func (sqlite) OpenConnector(name string) (driver.Connector, error) {
|
||||
c := connector{name: name}
|
||||
if strings.HasPrefix(name, "file:") {
|
||||
if _, after, ok := strings.Cut(name, "?"); ok {
|
||||
query, _ := url.ParseQuery(after)
|
||||
|
||||
switch s := query.Get("_txlock"); s {
|
||||
case "":
|
||||
c.txBegin = "BEGIN"
|
||||
case "deferred", "immediate", "exclusive":
|
||||
c.txBegin = "BEGIN " + s
|
||||
default:
|
||||
c.Close()
|
||||
return nil, fmt.Errorf("sqlite3: invalid _txlock: %s", s)
|
||||
query, err := url.ParseQuery(after)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
pragmas = len(query["_pragma"]) > 0
|
||||
c.txlock = query.Get("_txlock")
|
||||
c.pragmas = len(query["_pragma"]) > 0
|
||||
}
|
||||
}
|
||||
if !pragmas {
|
||||
err := c.Conn.Exec(`PRAGMA busy_timeout=60000`)
|
||||
return &c, nil
|
||||
}
|
||||
|
||||
type connector struct {
|
||||
name string
|
||||
txlock string
|
||||
pragmas bool
|
||||
}
|
||||
|
||||
func (n *connector) Driver() driver.Driver {
|
||||
return sqlite{}
|
||||
}
|
||||
|
||||
func (n *connector) Connect(ctx context.Context) (_ driver.Conn, err error) {
|
||||
var c conn
|
||||
c.Conn, err = sqlite3.Open(n.name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer func() {
|
||||
if err != nil {
|
||||
c.Close()
|
||||
}
|
||||
}()
|
||||
|
||||
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 {
|
||||
return nil, err
|
||||
}
|
||||
c.reusable = true
|
||||
@@ -86,7 +115,6 @@ func (sqlite) Open(name string) (_ driver.Conn, err error) {
|
||||
PRAGMA_query_only;
|
||||
`)
|
||||
if err != nil {
|
||||
c.Close()
|
||||
return nil, err
|
||||
}
|
||||
if s.Step() {
|
||||
@@ -95,7 +123,6 @@ func (sqlite) Open(name string) (_ driver.Conn, err error) {
|
||||
}
|
||||
err = s.Close()
|
||||
if err != nil {
|
||||
c.Close()
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
@@ -255,13 +282,14 @@ func (s *stmt) Query(args []driver.Value) (driver.Rows, error) {
|
||||
}
|
||||
|
||||
func (s *stmt) ExecContext(ctx context.Context, args []driver.NamedValue) (driver.Result, error) {
|
||||
// Use QueryContext to setup bindings.
|
||||
// No need to close rows: that simply resets the statement, exec does the same.
|
||||
_, err := s.QueryContext(ctx, args)
|
||||
err := s.setupBindings(args)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
old := s.Conn.SetInterrupt(ctx)
|
||||
defer s.Conn.SetInterrupt(old)
|
||||
|
||||
err = s.Stmt.Exec()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -271,10 +299,18 @@ func (s *stmt) ExecContext(ctx context.Context, args []driver.NamedValue) (drive
|
||||
}
|
||||
|
||||
func (s *stmt) QueryContext(ctx context.Context, args []driver.NamedValue) (driver.Rows, error) {
|
||||
err := s.Stmt.ClearBindings()
|
||||
err := s.setupBindings(args)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &rows{ctx, s.Stmt, s.Conn}, nil
|
||||
}
|
||||
|
||||
func (s *stmt) setupBindings(args []driver.NamedValue) error {
|
||||
err := s.Stmt.ClearBindings()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var ids [3]int
|
||||
for _, arg := range args {
|
||||
@@ -314,11 +350,10 @@ func (s *stmt) QueryContext(ctx context.Context, args []driver.NamedValue) (driv
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return &rows{ctx, s.Stmt, s.Conn}, nil
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *stmt) CheckNamedValue(arg *driver.NamedValue) error {
|
||||
|
||||
@@ -9,6 +9,7 @@ The following optional features are compiled in:
|
||||
- [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)
|
||||
- [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)
|
||||
|
||||
Binary file not shown.
178
ext/unicode/unicode.go
Normal file
178
ext/unicode/unicode.go
Normal file
@@ -0,0 +1,178 @@
|
||||
// Package unicode provides an alternative to the SQLite ICU extension.
|
||||
//
|
||||
// Provides Unicode aware:
|
||||
// - upper and lower functions,
|
||||
// - LIKE and REGEXP operators,
|
||||
// - collation sequences.
|
||||
//
|
||||
// This package is not 100% compatible with the ICU extension:
|
||||
// - upper and lower use [strings.ToUpper], [strings.ToLower] and [cases];
|
||||
// - the LIKE operator follows [strings.EqualFold] rules;
|
||||
// - the REGEXP operator uses Go [regex/syntax];
|
||||
// - collation sequences use [collate].
|
||||
//
|
||||
// Expect subtle differences (e.g.) in the handling of Turkish case folding.
|
||||
package unicode
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"regexp"
|
||||
"strings"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/ncruces/go-sqlite3"
|
||||
"github.com/ncruces/go-sqlite3/internal/util"
|
||||
"golang.org/x/text/cases"
|
||||
"golang.org/x/text/collate"
|
||||
"golang.org/x/text/language"
|
||||
)
|
||||
|
||||
// Register registers Unicode aware functions for a database connection.
|
||||
func Register(db *sqlite3.Conn) {
|
||||
flags := sqlite3.DETERMINISTIC | sqlite3.INNOCUOUS
|
||||
|
||||
db.CreateFunction("like", 2, flags, like)
|
||||
db.CreateFunction("like", 3, flags, like)
|
||||
db.CreateFunction("upper", 1, flags, upper)
|
||||
db.CreateFunction("upper", 2, flags, upper)
|
||||
db.CreateFunction("lower", 1, flags, lower)
|
||||
db.CreateFunction("lower", 2, flags, lower)
|
||||
db.CreateFunction("regexp", 2, flags, regex)
|
||||
db.CreateFunction("icu_load_collation", 2, sqlite3.DIRECTONLY,
|
||||
func(ctx sqlite3.Context, arg ...sqlite3.Value) {
|
||||
name := arg[1].Text()
|
||||
if name == "" {
|
||||
return
|
||||
}
|
||||
|
||||
err := RegisterCollation(db, name, arg[0].Text())
|
||||
if err != nil {
|
||||
ctx.ResultError(err)
|
||||
return
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func RegisterCollation(db *sqlite3.Conn, name, lang string) error {
|
||||
tag, err := language.Parse(lang)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return db.CreateCollation(name, collate.New(tag).Compare)
|
||||
}
|
||||
|
||||
func upper(ctx sqlite3.Context, arg ...sqlite3.Value) {
|
||||
if len(arg) == 1 {
|
||||
ctx.ResultBlob(bytes.ToUpper(arg[0].RawBlob()))
|
||||
return
|
||||
}
|
||||
cs, ok := ctx.GetAuxData(1).(cases.Caser)
|
||||
if !ok {
|
||||
t, err := language.Parse(arg[1].Text())
|
||||
if err != nil {
|
||||
ctx.ResultError(err)
|
||||
return
|
||||
}
|
||||
c := cases.Upper(t)
|
||||
ctx.SetAuxData(1, c)
|
||||
cs = c
|
||||
}
|
||||
ctx.ResultBlob(cs.Bytes(arg[0].RawBlob()))
|
||||
}
|
||||
|
||||
func lower(ctx sqlite3.Context, arg ...sqlite3.Value) {
|
||||
if len(arg) == 1 {
|
||||
ctx.ResultBlob(bytes.ToLower(arg[0].RawBlob()))
|
||||
return
|
||||
}
|
||||
cs, ok := ctx.GetAuxData(1).(cases.Caser)
|
||||
if !ok {
|
||||
t, err := language.Parse(arg[1].Text())
|
||||
if err != nil {
|
||||
ctx.ResultError(err)
|
||||
return
|
||||
}
|
||||
c := cases.Lower(t)
|
||||
ctx.SetAuxData(1, c)
|
||||
cs = c
|
||||
}
|
||||
ctx.ResultBlob(cs.Bytes(arg[0].RawBlob()))
|
||||
}
|
||||
|
||||
func regex(ctx sqlite3.Context, arg ...sqlite3.Value) {
|
||||
re, ok := ctx.GetAuxData(0).(*regexp.Regexp)
|
||||
if !ok {
|
||||
r, err := regexp.Compile(arg[0].Text())
|
||||
if err != nil {
|
||||
ctx.ResultError(err)
|
||||
return
|
||||
}
|
||||
re = r
|
||||
ctx.SetAuxData(0, re)
|
||||
}
|
||||
ctx.ResultBool(re.Match(arg[1].RawBlob()))
|
||||
}
|
||||
|
||||
func like(ctx sqlite3.Context, arg ...sqlite3.Value) {
|
||||
escape := rune(-1)
|
||||
if len(arg) == 3 {
|
||||
var size int
|
||||
b := arg[2].RawBlob()
|
||||
escape, size = utf8.DecodeRune(b)
|
||||
if size != len(b) {
|
||||
ctx.ResultError(util.ErrorString("ESCAPE expression must be a single character"))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
type likeData struct {
|
||||
*regexp.Regexp
|
||||
escape rune
|
||||
}
|
||||
|
||||
re, ok := ctx.GetAuxData(0).(likeData)
|
||||
if !ok || re.escape != escape {
|
||||
re = likeData{
|
||||
regexp.MustCompile(like2regex(arg[0].Text(), escape)),
|
||||
escape,
|
||||
}
|
||||
ctx.SetAuxData(0, re)
|
||||
}
|
||||
ctx.ResultBool(re.Match(arg[1].RawBlob()))
|
||||
}
|
||||
|
||||
func like2regex(pattern string, escape rune) string {
|
||||
var re strings.Builder
|
||||
start := 0
|
||||
literal := false
|
||||
re.Grow(len(pattern) + 10)
|
||||
re.WriteString(`(?is)\A`) // case insensitive, . matches any character
|
||||
for i, r := range pattern {
|
||||
if start < 0 {
|
||||
start = i
|
||||
}
|
||||
if literal {
|
||||
literal = false
|
||||
continue
|
||||
}
|
||||
var symbol string
|
||||
switch r {
|
||||
case '_':
|
||||
symbol = `.`
|
||||
case '%':
|
||||
symbol = `.*`
|
||||
case escape:
|
||||
literal = true
|
||||
default:
|
||||
continue
|
||||
}
|
||||
re.WriteString(regexp.QuoteMeta(pattern[start:i]))
|
||||
re.WriteString(symbol)
|
||||
start = -1
|
||||
}
|
||||
if start >= 0 {
|
||||
re.WriteString(regexp.QuoteMeta(pattern[start:]))
|
||||
}
|
||||
re.WriteString(`\z`)
|
||||
return re.String()
|
||||
}
|
||||
215
ext/unicode/unicode_test.go
Normal file
215
ext/unicode/unicode_test.go
Normal file
@@ -0,0 +1,215 @@
|
||||
package unicode
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/ncruces/go-sqlite3"
|
||||
_ "github.com/ncruces/go-sqlite3/embed"
|
||||
)
|
||||
|
||||
func TestRegister(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
db, err := sqlite3.Open(":memory:")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
exec := func(fn string) string {
|
||||
stmt, _, err := db.Prepare(`SELECT ` + fn)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer stmt.Close()
|
||||
|
||||
if stmt.Step() {
|
||||
return stmt.ColumnText(0)
|
||||
}
|
||||
t.Fatal(stmt.Err())
|
||||
return ""
|
||||
}
|
||||
|
||||
Register(db)
|
||||
|
||||
tests := []struct {
|
||||
test string
|
||||
want string
|
||||
}{
|
||||
{`upper('hello')`, "HELLO"},
|
||||
{`lower('HELLO')`, "hello"},
|
||||
{`upper('привет')`, "ПРИВЕТ"},
|
||||
{`lower('ПРИВЕТ')`, "привет"},
|
||||
{`upper('istanbul')`, "ISTANBUL"},
|
||||
{`upper('istanbul', 'tr-TR')`, "İSTANBUL"},
|
||||
{`lower('Dünyanın İlk Borsası', 'tr-TR')`, "dünyanın ilk borsası"},
|
||||
{`upper('Dünyanın İlk Borsası', 'tr-TR')`, "DÜNYANIN İLK BORSASI"},
|
||||
{`'Hello' REGEXP 'ell'`, "1"},
|
||||
{`'Hello' REGEXP 'el.'`, "1"},
|
||||
{`'Hello' LIKE 'hel_'`, "0"},
|
||||
{`'Hello' LIKE 'hel%'`, "1"},
|
||||
{`'Hello' LIKE 'h_llo'`, "1"},
|
||||
{`'Hello' LIKE 'hello'`, "1"},
|
||||
{`'Привет' LIKE 'ПРИВЕТ'`, "1"},
|
||||
{`'100%' LIKE '100|%' ESCAPE '|'`, "1"},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.test, func(t *testing.T) {
|
||||
if got := exec(tt.test); got != tt.want {
|
||||
t.Errorf("exec(%q) = %q, want %q", tt.test, got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
err = db.Close()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRegister_collation(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
db, err := sqlite3.Open(":memory:")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
Register(db)
|
||||
|
||||
err = db.Exec(`CREATE TABLE IF NOT EXISTS words (word VARCHAR(10))`)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = db.Exec(`INSERT INTO words (word) VALUES ('côte'), ('cote'), ('coter'), ('coté'), ('cotée'), ('côté')`)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = db.Exec(`SELECT icu_load_collation('fr_FR', 'french')`)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
stmt, _, err := db.Prepare(`SELECT word FROM words ORDER BY word COLLATE french`)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer stmt.Close()
|
||||
|
||||
got, want := []string{}, []string{"cote", "coté", "côte", "côté", "cotée", "coter"}
|
||||
|
||||
for stmt.Step() {
|
||||
got = append(got, stmt.ColumnText(0))
|
||||
}
|
||||
if err := stmt.Err(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(got, want) {
|
||||
t.Error("not equal")
|
||||
}
|
||||
|
||||
err = stmt.Close()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = db.Close()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRegister_error(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
db, err := sqlite3.Open(":memory:")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
Register(db)
|
||||
|
||||
err = db.Exec(`SELECT upper('hello', 'enUS')`)
|
||||
if err == nil {
|
||||
t.Error("want error")
|
||||
}
|
||||
if !errors.Is(err, sqlite3.ERROR) {
|
||||
t.Errorf("got %v, want sqlite3.ERROR", err)
|
||||
}
|
||||
|
||||
err = db.Exec(`SELECT lower('hello', 'enUS')`)
|
||||
if err == nil {
|
||||
t.Error("want error")
|
||||
}
|
||||
if !errors.Is(err, sqlite3.ERROR) {
|
||||
t.Errorf("got %v, want sqlite3.ERROR", err)
|
||||
}
|
||||
|
||||
err = db.Exec(`SELECT 'hello' REGEXP '\'`)
|
||||
if err == nil {
|
||||
t.Error("want error")
|
||||
}
|
||||
if !errors.Is(err, sqlite3.ERROR) {
|
||||
t.Errorf("got %v, want sqlite3.ERROR", err)
|
||||
}
|
||||
|
||||
err = db.Exec(`SELECT 'hello' LIKE 'HELLO' ESCAPE '\\'`)
|
||||
if err == nil {
|
||||
t.Error("want error")
|
||||
}
|
||||
if !errors.Is(err, sqlite3.ERROR) {
|
||||
t.Errorf("got %v, want sqlite3.ERROR", err)
|
||||
}
|
||||
|
||||
err = db.Exec(`SELECT icu_load_collation('enUS', 'error')`)
|
||||
if err == nil {
|
||||
t.Error("want error")
|
||||
}
|
||||
if !errors.Is(err, sqlite3.ERROR) {
|
||||
t.Errorf("got %v, want sqlite3.ERROR", err)
|
||||
}
|
||||
|
||||
err = db.Exec(`SELECT icu_load_collation('enUS', '')`)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
err = db.Close()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func Test_like2regex(t *testing.T) {
|
||||
const prefix = `(?is)\A`
|
||||
const sufix = `\z`
|
||||
tests := []struct {
|
||||
pattern string
|
||||
escape rune
|
||||
want string
|
||||
}{
|
||||
{`a`, -1, `a`},
|
||||
{`a.`, -1, `a\.`},
|
||||
{`a%`, -1, `a.*`},
|
||||
{`a\`, -1, `a\\`},
|
||||
{`a_b`, -1, `a.b`},
|
||||
{`a|b`, '|', `ab`},
|
||||
{`a|_`, '|', `a_`},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.pattern, func(t *testing.T) {
|
||||
want := prefix + tt.want + sufix
|
||||
if got := like2regex(tt.pattern, tt.escape); got != want {
|
||||
t.Errorf("like2regex() = %q, want %q", got, want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
35
func_test.go
35
func_test.go
@@ -18,6 +18,7 @@ func ExampleConn_CreateCollation() {
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
err = db.Exec(`CREATE TABLE IF NOT EXISTS words (word VARCHAR(10))`)
|
||||
if err != nil {
|
||||
@@ -46,16 +47,6 @@ func ExampleConn_CreateCollation() {
|
||||
if err := stmt.Err(); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
err = stmt.Close()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
err = db.Close()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
// Output:
|
||||
// cote
|
||||
// coté
|
||||
@@ -70,6 +61,7 @@ func ExampleConn_CreateFunction() {
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
err = db.Exec(`CREATE TABLE IF NOT EXISTS words (word VARCHAR(10))`)
|
||||
if err != nil {
|
||||
@@ -100,16 +92,6 @@ func ExampleConn_CreateFunction() {
|
||||
if err := stmt.Err(); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
err = stmt.Close()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
err = db.Close()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
// Unordered output:
|
||||
// COTE
|
||||
// COTÉ
|
||||
@@ -124,6 +106,7 @@ func ExampleContext_SetAuxData() {
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
err = db.Exec(`CREATE TABLE IF NOT EXISTS words (word VARCHAR(10))`)
|
||||
if err != nil {
|
||||
@@ -146,7 +129,7 @@ func ExampleContext_SetAuxData() {
|
||||
ctx.SetAuxData(0, r)
|
||||
re = r
|
||||
}
|
||||
ctx.ResultBool(re.Match(arg[1].RawText()))
|
||||
ctx.ResultBool(re.Match(arg[1].RawBlob()))
|
||||
})
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
@@ -164,16 +147,6 @@ func ExampleContext_SetAuxData() {
|
||||
if err := stmt.Err(); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
err = stmt.Close()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
err = db.Close()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
// Unordered output:
|
||||
// cote
|
||||
// côte
|
||||
|
||||
@@ -14,6 +14,7 @@ func ExampleConn_CreateWindowFunction() {
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
err = db.Exec(`CREATE TABLE IF NOT EXISTS words (word VARCHAR(10))`)
|
||||
if err != nil {
|
||||
@@ -42,16 +43,6 @@ func ExampleConn_CreateWindowFunction() {
|
||||
if err := stmt.Err(); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
err = stmt.Close()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
err = db.Close()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
// Output:
|
||||
// 1
|
||||
// 2
|
||||
@@ -87,7 +78,7 @@ func (f *countASCII) isASCII(arg sqlite3.Value) bool {
|
||||
if arg.Type() != sqlite3.TEXT {
|
||||
return false
|
||||
}
|
||||
for _, c := range arg.RawText() {
|
||||
for _, c := range arg.RawBlob() {
|
||||
if c > unicode.MaxASCII {
|
||||
return false
|
||||
}
|
||||
|
||||
8
go.mod
8
go.mod
@@ -1,14 +1,14 @@
|
||||
module github.com/ncruces/go-sqlite3
|
||||
|
||||
go 1.19
|
||||
go 1.21
|
||||
|
||||
require (
|
||||
github.com/ncruces/julianday v0.1.5
|
||||
github.com/psanford/httpreadat v0.1.0
|
||||
github.com/tetratelabs/wazero v1.2.1
|
||||
github.com/tetratelabs/wazero v1.4.0
|
||||
golang.org/x/sync v0.3.0
|
||||
golang.org/x/sys v0.10.0
|
||||
golang.org/x/text v0.10.0
|
||||
golang.org/x/sys v0.11.0
|
||||
golang.org/x/text v0.12.0
|
||||
)
|
||||
|
||||
retract v0.4.0 // tagged from the wrong branch
|
||||
|
||||
12
go.sum
12
go.sum
@@ -2,11 +2,11 @@ github.com/ncruces/julianday v0.1.5 h1:hDJ9ejiMp3DHsoZ5KW4c1lwfMjbARS7u/gbYcd0FB
|
||||
github.com/ncruces/julianday v0.1.5/go.mod h1:Dusn2KvZrrovOMJuOt0TNXL6tB7U2E8kvza5fFc9G7g=
|
||||
github.com/psanford/httpreadat v0.1.0 h1:VleW1HS2zO7/4c7c7zNl33fO6oYACSagjJIyMIwZLUE=
|
||||
github.com/psanford/httpreadat v0.1.0/go.mod h1:Zg7P+TlBm3bYbyHTKv/EdtSJZn3qwbPwpfZ/I9GKCRE=
|
||||
github.com/tetratelabs/wazero v1.2.1 h1:J4X2hrGzJvt+wqltuvcSjHQ7ujQxA9gb6PeMs4qlUWs=
|
||||
github.com/tetratelabs/wazero v1.2.1/go.mod h1:wYx2gNRg8/WihJfSDxA1TIL8H+GkfLYm+bIfbblu9VQ=
|
||||
github.com/tetratelabs/wazero v1.4.0 h1:9/MirYvmkJ/zSUOygKY/ia3t+e+RqIZXKbylIby1WYk=
|
||||
github.com/tetratelabs/wazero v1.4.0/go.mod h1:0U0G41+ochRKoPKCJlh0jMg1CHkyfK8kDqiirMmKY8A=
|
||||
golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E=
|
||||
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
|
||||
golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA=
|
||||
golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/text v0.10.0 h1:UpjohKhiEgNc0CSauXmwYftY1+LlaC75SJwh0SgCX58=
|
||||
golang.org/x/text v0.10.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM=
|
||||
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/text v0.12.0 h1:k+n5B8goJNdU7hSvEtMUz3d1Q6D/XW4COJSJR6fN0mc=
|
||||
golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
module github.com/ncruces/go-sqlite3/gormlite
|
||||
|
||||
go 1.19
|
||||
go 1.21
|
||||
|
||||
require (
|
||||
github.com/ncruces/go-sqlite3 v0.8.2
|
||||
gorm.io/gorm v1.25.2
|
||||
github.com/ncruces/go-sqlite3 v0.8.5
|
||||
gorm.io/gorm v1.25.4
|
||||
)
|
||||
|
||||
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/tetratelabs/wazero v1.2.1 // indirect
|
||||
golang.org/x/sys v0.10.0 // indirect
|
||||
github.com/tetratelabs/wazero v1.4.0 // indirect
|
||||
golang.org/x/sys v0.11.0 // indirect
|
||||
)
|
||||
|
||||
@@ -2,14 +2,14 @@ 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.8.2 h1:U28YYK/Kl5aUsRljcnvShZZHBgWLzuR26zUntP4uNbQ=
|
||||
github.com/ncruces/go-sqlite3 v0.8.2/go.mod h1:yGeN+m3ug1wD/+Kg2A3TnochT6H6Or6+2q8KHN//r9k=
|
||||
github.com/ncruces/go-sqlite3 v0.8.5 h1:JeNcbJ4rsZ07ZVyqPdnFlfmVSWDW0ONoiuZSUBC369Y=
|
||||
github.com/ncruces/go-sqlite3 v0.8.5/go.mod h1:XvDtjKk5MgwHX7L4I7BPzzKl36bTZ7+Hr6Kr2QeVkVw=
|
||||
github.com/ncruces/julianday v0.1.5 h1:hDJ9ejiMp3DHsoZ5KW4c1lwfMjbARS7u/gbYcd0FBZk=
|
||||
github.com/ncruces/julianday v0.1.5/go.mod h1:Dusn2KvZrrovOMJuOt0TNXL6tB7U2E8kvza5fFc9G7g=
|
||||
github.com/tetratelabs/wazero v1.2.1 h1:J4X2hrGzJvt+wqltuvcSjHQ7ujQxA9gb6PeMs4qlUWs=
|
||||
github.com/tetratelabs/wazero v1.2.1/go.mod h1:wYx2gNRg8/WihJfSDxA1TIL8H+GkfLYm+bIfbblu9VQ=
|
||||
golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA=
|
||||
golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/text v0.10.0 h1:UpjohKhiEgNc0CSauXmwYftY1+LlaC75SJwh0SgCX58=
|
||||
gorm.io/gorm v1.25.2 h1:gs1o6Vsa+oVKG/a9ElL3XgyGfghFfkKA2SInQaCyMho=
|
||||
gorm.io/gorm v1.25.2/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k=
|
||||
github.com/tetratelabs/wazero v1.4.0 h1:9/MirYvmkJ/zSUOygKY/ia3t+e+RqIZXKbylIby1WYk=
|
||||
github.com/tetratelabs/wazero v1.4.0/go.mod h1:0U0G41+ochRKoPKCJlh0jMg1CHkyfK8kDqiirMmKY8A=
|
||||
golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM=
|
||||
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/text v0.12.0 h1:k+n5B8goJNdU7hSvEtMUz3d1Q6D/XW4COJSJR6fN0mc=
|
||||
gorm.io/gorm v1.25.4 h1:iyNd8fNAe8W9dvtlgeRI5zSVZPsq3OpcTu37cYcpCmw=
|
||||
gorm.io/gorm v1.25.4/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k=
|
||||
|
||||
@@ -5,7 +5,6 @@ import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"gorm.io/gorm"
|
||||
"gorm.io/gorm/callbacks"
|
||||
@@ -136,19 +135,51 @@ func (dialector Dialector) BindVarTo(writer clause.Writer, stmt *gorm.Statement,
|
||||
}
|
||||
|
||||
func (dialector Dialector) QuoteTo(writer clause.Writer, str string) {
|
||||
writer.WriteByte('`')
|
||||
if strings.Contains(str, ".") {
|
||||
for idx, str := range strings.Split(str, ".") {
|
||||
if idx > 0 {
|
||||
writer.WriteString(".`")
|
||||
var (
|
||||
underQuoted, selfQuoted bool
|
||||
continuousBacktick int8
|
||||
shiftDelimiter int8
|
||||
)
|
||||
|
||||
for _, v := range []byte(str) {
|
||||
switch v {
|
||||
case '`':
|
||||
continuousBacktick++
|
||||
if continuousBacktick == 2 {
|
||||
writer.WriteString("``")
|
||||
continuousBacktick = 0
|
||||
}
|
||||
writer.WriteString(str)
|
||||
writer.WriteByte('`')
|
||||
case '.':
|
||||
if continuousBacktick > 0 || !selfQuoted {
|
||||
shiftDelimiter = 0
|
||||
underQuoted = false
|
||||
continuousBacktick = 0
|
||||
writer.WriteString("`")
|
||||
}
|
||||
writer.WriteByte(v)
|
||||
continue
|
||||
default:
|
||||
if shiftDelimiter-continuousBacktick <= 0 && !underQuoted {
|
||||
writer.WriteString("`")
|
||||
underQuoted = true
|
||||
if selfQuoted = continuousBacktick > 0; selfQuoted {
|
||||
continuousBacktick -= 1
|
||||
}
|
||||
}
|
||||
|
||||
for ; continuousBacktick > 0; continuousBacktick -= 1 {
|
||||
writer.WriteString("``")
|
||||
}
|
||||
|
||||
writer.WriteByte(v)
|
||||
}
|
||||
} else {
|
||||
writer.WriteString(str)
|
||||
writer.WriteByte('`')
|
||||
shiftDelimiter++
|
||||
}
|
||||
|
||||
if continuousBacktick > 0 && !selfQuoted {
|
||||
writer.WriteString("``")
|
||||
}
|
||||
writer.WriteString("`")
|
||||
}
|
||||
|
||||
func (dialector Dialector) Explain(sql string, vars ...interface{}) string {
|
||||
|
||||
@@ -3,7 +3,7 @@ set -euo pipefail
|
||||
|
||||
cd -P -- "$(dirname -- "$0")"
|
||||
|
||||
rm -rf gorm/ tests/ $TMPDIR/gorm.db
|
||||
rm -rf gorm/ tests/
|
||||
git clone --filter=blob:none https://github.com/go-gorm/gorm.git
|
||||
mv gorm/tests tests
|
||||
rm -rf gorm/
|
||||
@@ -11,8 +11,14 @@ rm -rf gorm/
|
||||
patch -p1 -N < tests.patch
|
||||
|
||||
cd tests
|
||||
go mod edit \
|
||||
-require github.com/ncruces/go-sqlite3/gormlite@v0.0.0 \
|
||||
-replace github.com/ncruces/go-sqlite3/gormlite=../ \
|
||||
-replace github.com/ncruces/go-sqlite3=../../ \
|
||||
-droprequire gorm.io/driver/sqlite \
|
||||
-dropreplace gorm.io/gorm
|
||||
go mod tidy && go work use . && go test
|
||||
|
||||
cd ..
|
||||
rm -rf tests/ $TMPDIR/gorm.db
|
||||
rm -rf tests/
|
||||
go work use -r .
|
||||
@@ -4,26 +4,6 @@ diff --git a/tests/.gitignore b/tests/.gitignore
|
||||
@@ -1 +1 @@
|
||||
-go.sum
|
||||
+*
|
||||
diff --git a/tests/go.mod b/tests/go.mod
|
||||
--- a/tests/go.mod
|
||||
+++ b/tests/go.mod
|
||||
@@ -7,12 +7,12 @@ require (
|
||||
github.com/jackc/pgx/v5 v5.3.1 // indirect
|
||||
github.com/jinzhu/now v1.1.5
|
||||
github.com/lib/pq v1.10.8
|
||||
- github.com/mattn/go-sqlite3 v1.14.16 // indirect
|
||||
+ github.com/ncruces/go-sqlite3 v0.7.2
|
||||
+ github.com/ncruces/go-sqlite3/gormlite v0.0.0
|
||||
gorm.io/driver/mysql v1.5.0
|
||||
gorm.io/driver/postgres v1.5.0
|
||||
- gorm.io/driver/sqlite v1.5.0
|
||||
gorm.io/driver/sqlserver v1.5.1
|
||||
- gorm.io/gorm v1.25.1
|
||||
+ gorm.io/gorm v1.25.2
|
||||
)
|
||||
|
||||
-replace gorm.io/gorm => ../
|
||||
+replace github.com/ncruces/go-sqlite3/gormlite => ../
|
||||
diff --git a/tests/tests_test.go b/tests/tests_test.go
|
||||
--- a/tests/tests_test.go
|
||||
+++ b/tests/tests_test.go
|
||||
@@ -40,3 +20,12 @@ 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 {
|
||||
|
||||
@@ -3,6 +3,8 @@ package util
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
|
||||
"github.com/tetratelabs/wazero/experimental"
|
||||
)
|
||||
|
||||
type handleKey struct{}
|
||||
@@ -11,22 +13,21 @@ type handleState struct {
|
||||
empty int
|
||||
}
|
||||
|
||||
func NewContext(ctx context.Context) (context.Context, io.Closer) {
|
||||
func NewContext(ctx context.Context) context.Context {
|
||||
state := new(handleState)
|
||||
return context.WithValue(ctx, handleKey{}, state), state
|
||||
ctx = experimental.WithCloseNotifier(ctx, state)
|
||||
ctx = context.WithValue(ctx, handleKey{}, state)
|
||||
return ctx
|
||||
}
|
||||
|
||||
func (s *handleState) Close() (err error) {
|
||||
func (s *handleState) CloseNotify(ctx context.Context, exitCode uint32) {
|
||||
for _, h := range s.handles {
|
||||
if c, ok := h.(io.Closer); ok {
|
||||
if e := c.Close(); err == nil {
|
||||
err = e
|
||||
}
|
||||
c.Close()
|
||||
}
|
||||
}
|
||||
s.handles = nil
|
||||
s.empty = 0
|
||||
return err
|
||||
}
|
||||
|
||||
func GetHandle(ctx context.Context, id uint32) any {
|
||||
|
||||
55
sqlite.go
55
sqlite.go
@@ -3,7 +3,6 @@ package sqlite3
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"math"
|
||||
"os"
|
||||
"sync"
|
||||
@@ -32,23 +31,6 @@ var instance struct {
|
||||
once sync.Once
|
||||
}
|
||||
|
||||
func instantiateSQLite() (*sqlite, error) {
|
||||
ctx := context.Background()
|
||||
|
||||
instance.once.Do(compileSQLite)
|
||||
if instance.err != nil {
|
||||
return nil, instance.err
|
||||
}
|
||||
|
||||
cfg := wazero.NewModuleConfig()
|
||||
|
||||
mod, err := instance.runtime.InstantiateModule(ctx, instance.compiled, cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return newSQLite(mod)
|
||||
}
|
||||
|
||||
func compileSQLite() {
|
||||
ctx := context.Background()
|
||||
instance.runtime = wazero.NewRuntime(ctx)
|
||||
@@ -77,23 +59,32 @@ func compileSQLite() {
|
||||
}
|
||||
|
||||
type sqlite struct {
|
||||
ctx context.Context
|
||||
mod api.Module
|
||||
closer io.Closer
|
||||
api sqliteAPI
|
||||
stack [8]uint64
|
||||
ctx context.Context
|
||||
mod api.Module
|
||||
api sqliteAPI
|
||||
stack [8]uint64
|
||||
}
|
||||
|
||||
type sqliteKey struct{}
|
||||
|
||||
func newSQLite(mod api.Module) (sqlt *sqlite, err error) {
|
||||
func instantiateSQLite() (sqlt *sqlite, err error) {
|
||||
instance.once.Do(compileSQLite)
|
||||
if instance.err != nil {
|
||||
return nil, instance.err
|
||||
}
|
||||
|
||||
sqlt = new(sqlite)
|
||||
sqlt.ctx, sqlt.closer = util.NewContext(context.Background())
|
||||
sqlt.ctx = util.NewContext(context.Background())
|
||||
sqlt.ctx = context.WithValue(sqlt.ctx, sqliteKey{}, sqlt)
|
||||
sqlt.mod = mod
|
||||
|
||||
sqlt.mod, err = instance.runtime.InstantiateModule(sqlt.ctx,
|
||||
instance.compiled, wazero.NewModuleConfig())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
getFun := func(name string) api.Function {
|
||||
f := mod.ExportedFunction(name)
|
||||
f := sqlt.mod.ExportedFunction(name)
|
||||
if f == nil {
|
||||
err = util.NoFuncErr + util.ErrorString(name)
|
||||
return nil
|
||||
@@ -102,12 +93,12 @@ func newSQLite(mod api.Module) (sqlt *sqlite, err error) {
|
||||
}
|
||||
|
||||
getVal := func(name string) uint32 {
|
||||
g := mod.ExportedGlobal(name)
|
||||
g := sqlt.mod.ExportedGlobal(name)
|
||||
if g == nil {
|
||||
err = util.NoGlobalErr + util.ErrorString(name)
|
||||
return 0
|
||||
}
|
||||
return util.ReadUint32(mod, uint32(g.Get()))
|
||||
return util.ReadUint32(sqlt.mod, uint32(g.Get()))
|
||||
}
|
||||
|
||||
sqlt.api = sqliteAPI{
|
||||
@@ -191,9 +182,7 @@ func newSQLite(mod api.Module) (sqlt *sqlite, err error) {
|
||||
}
|
||||
|
||||
func (sqlt *sqlite) close() error {
|
||||
err := sqlt.mod.Close(sqlt.ctx)
|
||||
sqlt.closer.Close()
|
||||
return err
|
||||
return sqlt.mod.Close(sqlt.ctx)
|
||||
}
|
||||
|
||||
func (sqlt *sqlite) error(rc uint64, handle uint32, sql ...string) error {
|
||||
@@ -232,8 +221,6 @@ func (sqlt *sqlite) call(fn api.Function, params ...uint64) uint64 {
|
||||
copy(sqlt.stack[:], params)
|
||||
err := fn.CallWithStack(sqlt.ctx, sqlt.stack[:])
|
||||
if err != nil {
|
||||
// The module closed or panicked; release resources.
|
||||
sqlt.closer.Close()
|
||||
panic(err)
|
||||
}
|
||||
return sqlt.stack[0]
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
#define SQLITE_USE_ALLOCA
|
||||
|
||||
// Other Options
|
||||
|
||||
#define SQLITE_ALLOW_URI_AUTHORITY
|
||||
#define SQLITE_ENABLE_BATCH_ATOMIC_WRITE
|
||||
#define SQLITE_ENABLE_ATOMIC_WRITE
|
||||
@@ -55,5 +56,7 @@
|
||||
// #define SQLITE_ENABLE_SESSION
|
||||
// #define SQLITE_ENABLE_PREUPDATE_HOOK
|
||||
|
||||
#define SQLITE_SOUNDEX
|
||||
|
||||
// Implemented in vfs.c.
|
||||
int localtime_s(struct tm *const pTm, time_t const *const pTime);
|
||||
8
stmt.go
8
stmt.go
@@ -61,12 +61,12 @@ func (s *Stmt) ClearBindings() error {
|
||||
func (s *Stmt) Step() bool {
|
||||
s.c.checkInterrupt()
|
||||
r := s.c.call(s.c.api.step, uint64(s.handle))
|
||||
if r == _ROW {
|
||||
switch r {
|
||||
case _ROW:
|
||||
return true
|
||||
}
|
||||
if r == _DONE {
|
||||
case _DONE:
|
||||
s.err = nil
|
||||
} else {
|
||||
default:
|
||||
s.err = s.c.error(r)
|
||||
}
|
||||
return false
|
||||
|
||||
9
vfs/clear.go
Normal file
9
vfs/clear.go
Normal file
@@ -0,0 +1,9 @@
|
||||
//go:build !go1.21
|
||||
|
||||
package vfs
|
||||
|
||||
func clear(b []byte) {
|
||||
for i := range b {
|
||||
b[i] = 0
|
||||
}
|
||||
}
|
||||
@@ -41,8 +41,7 @@ func Test_vfsLock(t *testing.T) {
|
||||
pOutput = 32
|
||||
)
|
||||
mod := wazerotest.NewModule(wazerotest.NewMemory(wazerotest.PageSize))
|
||||
ctx, closer := util.NewContext(context.TODO())
|
||||
defer closer.Close()
|
||||
ctx := util.NewContext(context.TODO())
|
||||
|
||||
vfsFileRegister(ctx, mod, pFile1, &vfsFile{File: file1})
|
||||
vfsFileRegister(ctx, mod, pFile2, &vfsFile{File: file2})
|
||||
|
||||
10
vfs/memdb/clear.go
Normal file
10
vfs/memdb/clear.go
Normal file
@@ -0,0 +1,10 @@
|
||||
//go:build !go1.21
|
||||
|
||||
package memdb
|
||||
|
||||
func clear[T any](b []T) {
|
||||
var zero T
|
||||
for i := range b {
|
||||
b[i] = zero
|
||||
}
|
||||
}
|
||||
@@ -285,10 +285,3 @@ func divRoundUp(a, b int64) int64 {
|
||||
func modRoundUp(a, b int64) int64 {
|
||||
return b - (b-a%b)%b
|
||||
}
|
||||
|
||||
func clear[T any](b []T) {
|
||||
var zero T
|
||||
for i := range b {
|
||||
b[i] = zero
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
package readervfs_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"log"
|
||||
"strings"
|
||||
|
||||
_ "embed"
|
||||
|
||||
@@ -15,7 +15,7 @@ import (
|
||||
)
|
||||
|
||||
//go:embed testdata/test.db
|
||||
var testDB []byte
|
||||
var testDB string
|
||||
|
||||
func Example_http() {
|
||||
readervfs.Create("demo.db", httpreadat.New("https://www.sanford.io/demo.db"))
|
||||
@@ -65,7 +65,7 @@ func Example_http() {
|
||||
}
|
||||
|
||||
func Example_embed() {
|
||||
readervfs.Create("test.db", readervfs.NewSizeReaderAt(bytes.NewReader(testDB)))
|
||||
readervfs.Create("test.db", readervfs.NewSizeReaderAt(strings.NewReader(testDB)))
|
||||
defer readervfs.Delete("test.db")
|
||||
|
||||
db, err := sql.Open("sqlite3", "file:test.db?vfs=reader")
|
||||
|
||||
@@ -83,16 +83,15 @@ func system(ctx context.Context, mod api.Module, ptr uint32) uint32 {
|
||||
|
||||
cfg := config(ctx).WithArgs(args...)
|
||||
go func() {
|
||||
ctx, closer := util.NewContext(ctx)
|
||||
ctx := util.NewContext(ctx)
|
||||
mod, _ := rt.InstantiateModule(ctx, module, cfg)
|
||||
mod.Close(ctx)
|
||||
closer.Close()
|
||||
}()
|
||||
return 0
|
||||
}
|
||||
|
||||
func Test_config01(t *testing.T) {
|
||||
ctx, closer := util.NewContext(newContext(t))
|
||||
ctx := util.NewContext(newContext(t))
|
||||
name := filepath.Join(t.TempDir(), "test.db")
|
||||
cfg := config(ctx).WithArgs("mptest", name, "config01.test")
|
||||
mod, err := rt.InstantiateModule(ctx, module, cfg)
|
||||
@@ -100,7 +99,6 @@ func Test_config01(t *testing.T) {
|
||||
t.Error(err)
|
||||
}
|
||||
mod.Close(ctx)
|
||||
closer.Close()
|
||||
}
|
||||
|
||||
func Test_config02(t *testing.T) {
|
||||
@@ -111,7 +109,7 @@ func Test_config02(t *testing.T) {
|
||||
t.Skip("skipping in CI")
|
||||
}
|
||||
|
||||
ctx, closer := util.NewContext(newContext(t))
|
||||
ctx := util.NewContext(newContext(t))
|
||||
name := filepath.Join(t.TempDir(), "test.db")
|
||||
cfg := config(ctx).WithArgs("mptest", name, "config02.test")
|
||||
mod, err := rt.InstantiateModule(ctx, module, cfg)
|
||||
@@ -119,7 +117,6 @@ func Test_config02(t *testing.T) {
|
||||
t.Error(err)
|
||||
}
|
||||
mod.Close(ctx)
|
||||
closer.Close()
|
||||
}
|
||||
|
||||
func Test_crash01(t *testing.T) {
|
||||
@@ -127,7 +124,7 @@ func Test_crash01(t *testing.T) {
|
||||
t.Skip("skipping in short mode")
|
||||
}
|
||||
|
||||
ctx, closer := util.NewContext(newContext(t))
|
||||
ctx := util.NewContext(newContext(t))
|
||||
name := filepath.Join(t.TempDir(), "test.db")
|
||||
cfg := config(ctx).WithArgs("mptest", name, "crash01.test")
|
||||
mod, err := rt.InstantiateModule(ctx, module, cfg)
|
||||
@@ -135,7 +132,6 @@ func Test_crash01(t *testing.T) {
|
||||
t.Error(err)
|
||||
}
|
||||
mod.Close(ctx)
|
||||
closer.Close()
|
||||
}
|
||||
|
||||
func Test_multiwrite01(t *testing.T) {
|
||||
@@ -143,7 +139,7 @@ func Test_multiwrite01(t *testing.T) {
|
||||
t.Skip("skipping in short mode")
|
||||
}
|
||||
|
||||
ctx, closer := util.NewContext(newContext(t))
|
||||
ctx := util.NewContext(newContext(t))
|
||||
name := filepath.Join(t.TempDir(), "test.db")
|
||||
cfg := config(ctx).WithArgs("mptest", name, "multiwrite01.test")
|
||||
mod, err := rt.InstantiateModule(ctx, module, cfg)
|
||||
@@ -151,11 +147,10 @@ func Test_multiwrite01(t *testing.T) {
|
||||
t.Error(err)
|
||||
}
|
||||
mod.Close(ctx)
|
||||
closer.Close()
|
||||
}
|
||||
|
||||
func Test_config01_memory(t *testing.T) {
|
||||
ctx, closer := util.NewContext(newContext(t))
|
||||
ctx := util.NewContext(newContext(t))
|
||||
cfg := config(ctx).WithArgs("mptest", "test.db",
|
||||
"config01.test",
|
||||
"--vfs", "memdb",
|
||||
@@ -165,7 +160,6 @@ func Test_config01_memory(t *testing.T) {
|
||||
t.Error(err)
|
||||
}
|
||||
mod.Close(ctx)
|
||||
closer.Close()
|
||||
}
|
||||
|
||||
func Test_multiwrite01_memory(t *testing.T) {
|
||||
@@ -173,7 +167,7 @@ func Test_multiwrite01_memory(t *testing.T) {
|
||||
t.Skip("skipping in short mode")
|
||||
}
|
||||
|
||||
ctx, closer := util.NewContext(newContext(t))
|
||||
ctx := util.NewContext(newContext(t))
|
||||
cfg := config(ctx).WithArgs("mptest", "/test.db",
|
||||
"multiwrite01.test",
|
||||
"--vfs", "memdb",
|
||||
@@ -183,7 +177,6 @@ func Test_multiwrite01_memory(t *testing.T) {
|
||||
t.Error(err)
|
||||
}
|
||||
mod.Close(ctx)
|
||||
closer.Close()
|
||||
}
|
||||
|
||||
func newContext(t *testing.T) context.Context {
|
||||
|
||||
@@ -75,7 +75,7 @@ func initFlags() {
|
||||
|
||||
func Benchmark_speedtest1(b *testing.B) {
|
||||
output.Reset()
|
||||
ctx, closer := util.NewContext(context.Background())
|
||||
ctx := util.NewContext(context.Background())
|
||||
name := filepath.Join(b.TempDir(), "test.db")
|
||||
args := append(options, "--size", strconv.Itoa(b.N), name)
|
||||
cfg := wazero.NewModuleConfig().
|
||||
@@ -89,5 +89,4 @@ func Benchmark_speedtest1(b *testing.B) {
|
||||
b.Error(err)
|
||||
}
|
||||
mod.Close(ctx)
|
||||
closer.Close()
|
||||
}
|
||||
|
||||
@@ -432,9 +432,3 @@ func vfsErrorCode(err error, def _ErrorCode) _ErrorCode {
|
||||
}
|
||||
return def
|
||||
}
|
||||
|
||||
func clear(b []byte) {
|
||||
for i := range b {
|
||||
b[i] = 0
|
||||
}
|
||||
}
|
||||
|
||||
@@ -220,8 +220,7 @@ func Test_vfsAccess(t *testing.T) {
|
||||
|
||||
func Test_vfsFile(t *testing.T) {
|
||||
mod := wazerotest.NewModule(wazerotest.NewMemory(wazerotest.PageSize))
|
||||
ctx, closer := util.NewContext(context.TODO())
|
||||
defer closer.Close()
|
||||
ctx := util.NewContext(context.TODO())
|
||||
|
||||
// Open a temporary file.
|
||||
rc := vfsOpen(ctx, mod, 0, 0, 4, OPEN_CREATE|OPEN_EXCLUSIVE|OPEN_READWRITE|OPEN_DELETEONCLOSE, 0)
|
||||
@@ -293,8 +292,7 @@ func Test_vfsFile(t *testing.T) {
|
||||
|
||||
func Test_vfsFile_psow(t *testing.T) {
|
||||
mod := wazerotest.NewModule(wazerotest.NewMemory(wazerotest.PageSize))
|
||||
ctx, closer := util.NewContext(context.TODO())
|
||||
defer closer.Close()
|
||||
ctx := util.NewContext(context.TODO())
|
||||
|
||||
// Open a temporary file.
|
||||
rc := vfsOpen(ctx, mod, 0, 0, 4, OPEN_CREATE|OPEN_EXCLUSIVE|OPEN_READWRITE|OPEN_DELETEONCLOSE, 0)
|
||||
|
||||
Reference in New Issue
Block a user