Compare commits

...

24 Commits

Author SHA1 Message Date
Nuno Cruces
b71cd295c2 Updated dependencies. 2023-08-25 09:56:09 +01:00
Nuno Cruces
5b3b61a304 SQLite 3.43.0. 2023-08-24 18:56:23 +01:00
Nuno Cruces
d661d15723 wazero v1.5.0. 2023-08-24 18:56:10 +01:00
Nuno Cruces
1e38165ad0 Timer resolution. 2023-08-20 03:12:55 +01:00
Nuno Cruces
58a32d7c9d Update GORM. 2023-08-20 00:56:08 +01:00
Nuno Cruces
6765e883c1 Register collation. 2023-08-10 13:39:52 +01:00
Nuno Cruces
18fc608433 Embed database as string. 2023-08-10 13:23:54 +01:00
Nuno Cruces
77f37893b9 Driver connector. 2023-08-10 13:18:13 +01:00
Nuno Cruces
f1e36e2581 Updated dependencies. 2023-08-09 16:30:32 +01:00
Nuno Cruces
772b9153c7 Use clear builtin. 2023-08-09 16:16:45 +01:00
Nuno Cruces
4b280a3a7e Updated dependencies. 2023-08-09 15:22:48 +01:00
Nuno Cruces
19b6098bf6 Update go.yml (#28) 2023-08-05 01:12:16 +01:00
dependabot[bot]
2aa685320f Bump golang.org/x/text from 0.11.0 to 0.12.0 (#26)
Bumps [golang.org/x/text](https://github.com/golang/text) from 0.11.0 to 0.12.0.
- [Release notes](https://github.com/golang/text/releases)
- [Commits](https://github.com/golang/text/compare/v0.11.0...v0.12.0)

---
updated-dependencies:
- dependency-name: golang.org/x/text
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-08-05 00:36:56 +01:00
dependabot[bot]
9941be05c2 Bump golang.org/x/sys from 0.10.0 to 0.11.0 (#27)
Bumps [golang.org/x/sys](https://github.com/golang/sys) from 0.10.0 to 0.11.0.
- [Commits](https://github.com/golang/sys/compare/v0.10.0...v0.11.0)

---
updated-dependencies:
- dependency-name: golang.org/x/sys
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-08-05 00:35:11 +01:00
Nuno Cruces
a0a9ab7737 Avoid unnecessary alloc. 2023-08-04 14:12:36 +01:00
Nuno Cruces
a77727a1ce Port script. 2023-07-31 15:27:10 +01:00
Nuno Cruces
47fe032078 Updated dependencies. 2023-07-26 12:42:18 +01:00
Nuno Cruces
bdfe279444 Soundex. 2023-07-26 02:02:39 +01:00
dependabot[bot]
a86937a54e Bump github.com/tetratelabs/wazero from 1.3.0 to 1.3.1
Bumps [github.com/tetratelabs/wazero](https://github.com/tetratelabs/wazero) from 1.3.0 to 1.3.1.
- [Release notes](https://github.com/tetratelabs/wazero/releases)
- [Commits](https://github.com/tetratelabs/wazero/compare/v1.3.0...v1.3.1)

---
updated-dependencies:
- dependency-name: github.com/tetratelabs/wazero
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-07-25 08:02:20 +01:00
Nuno Cruces
6ef422fbde Unicode tests. 2023-07-13 12:19:32 +01:00
Nuno Cruces
ff0cb6fb88 Unicode tests, fixes. 2023-07-12 13:39:07 +01:00
Nuno Cruces
72db90efdf Unicode. 2023-07-11 16:34:15 +01:00
Nuno Cruces
5a3fdef3c5 wazero v1.3.0. 2023-07-11 12:30:39 +01:00
dependabot[bot]
ff34b0cae1 Bump golang.org/x/text from 0.10.0 to 0.11.0
Bumps [golang.org/x/text](https://github.com/golang/text) from 0.10.0 to 0.11.0.
- [Release notes](https://github.com/golang/text/releases)
- [Commits](https://github.com/golang/text/compare/v0.10.0...v0.11.0)

---
updated-dependencies:
- dependency-name: golang.org/x/text
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-07-04 23:55:17 +01:00
36 changed files with 657 additions and 265 deletions

View File

@@ -57,6 +57,7 @@ jobs:
with:
chart: 'true'
amend: 'true'
reuse-go: 'true'
if: |
matrix.os == 'ubuntu-latest' &&
github.event_name == 'push'

View File

@@ -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

View File

@@ -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 {

View File

@@ -1,6 +1,6 @@
# Embeddable WASM build of SQLite
This folder includes an embeddable WASM build of SQLite 3.42.0 for use with
This folder includes an embeddable WASM build of SQLite 3.43.0 for use with
[`github.com/ncruces/go-sqlite3`](https://pkg.go.dev/github.com/ncruces/go-sqlite3).
The following optional features are compiled in:
@@ -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
View 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
View 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)
}
})
}
}

View File

@@ -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

View File

@@ -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
View File

@@ -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.5.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.1-0.20230807062127-60ecf133770b
golang.org/x/text v0.12.0
)
retract v0.4.0 // tagged from the wrong branch

12
go.sum
View File

@@ -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.5.0 h1:Yz3fZHivfDiZFUXnWMPUoiW7s8tC1sjdBtlJn08qYa0=
github.com/tetratelabs/wazero v1.5.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.1-0.20230807062127-60ecf133770b h1:SVkT2ohjl5jbz5YNEn1KslYMggyeWG0FbUBDnQOZ3s8=
golang.org/x/sys v0.11.1-0.20230807062127-60ecf133770b/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=

View File

@@ -1,4 +1,4 @@
go 1.19
go 1.21
use (
.

View File

@@ -1,3 +1,4 @@
github.com/ncruces/go-sqlite3 v0.8.6/go.mod h1:LAWp8Mj+m2ki1HeRJo7flS3luCUqG+GQKjK4BKLc++0=
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=

View File

@@ -1,16 +1,16 @@
module github.com/ncruces/go-sqlite3/gormlite
go 1.19
go 1.20
require (
github.com/ncruces/go-sqlite3 v0.8.2
gorm.io/gorm v1.25.2
github.com/ncruces/go-sqlite3 v0.8.6
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.5.0 // indirect
golang.org/x/sys v0.11.1-0.20230807062127-60ecf133770b // indirect
)

View File

@@ -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.6 h1:y0b+rwqkrvV1lscEVk93SeH2trnlKMD5XKtIqVqm9QE=
github.com/ncruces/go-sqlite3 v0.8.6/go.mod h1:LAWp8Mj+m2ki1HeRJo7flS3luCUqG+GQKjK4BKLc++0=
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.5.0 h1:Yz3fZHivfDiZFUXnWMPUoiW7s8tC1sjdBtlJn08qYa0=
github.com/tetratelabs/wazero v1.5.0/go.mod h1:0U0G41+ochRKoPKCJlh0jMg1CHkyfK8kDqiirMmKY8A=
golang.org/x/sys v0.11.1-0.20230807062127-60ecf133770b h1:SVkT2ohjl5jbz5YNEn1KslYMggyeWG0FbUBDnQOZ3s8=
golang.org/x/sys v0.11.1-0.20230807062127-60ecf133770b/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=

View File

@@ -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 {

View File

@@ -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 .

View File

@@ -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 {

View File

@@ -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 {

View File

@@ -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]

View File

@@ -3,33 +3,33 @@ set -euo pipefail
cd -P -- "$(dirname -- "$0")"
curl -#OL "https://sqlite.org/2023/sqlite-amalgamation-3420000.zip"
curl -#OL "https://sqlite.org/2023/sqlite-amalgamation-3430000.zip"
unzip -d . sqlite-amalgamation-*.zip
mv sqlite-amalgamation-*/sqlite3* .
rm -rf sqlite-amalgamation-*
cat *.patch | patch
cat *.patch | patch --posix
mkdir -p ext/
cd ext/
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.42.0/ext/misc/decimal.c"
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.42.0/ext/misc/uint.c"
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.42.0/ext/misc/uuid.c"
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.42.0/ext/misc/base64.c"
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.42.0/ext/misc/regexp.c"
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.42.0/ext/misc/series.c"
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.42.0/ext/misc/anycollseq.c"
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.43.0/ext/misc/decimal.c"
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.43.0/ext/misc/uint.c"
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.43.0/ext/misc/uuid.c"
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.43.0/ext/misc/base64.c"
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.43.0/ext/misc/regexp.c"
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.43.0/ext/misc/series.c"
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.43.0/ext/misc/anycollseq.c"
cd ~-
cd ../vfs/tests/mptest/testdata/
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.42.0/mptest/mptest.c"
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.42.0/mptest/config01.test"
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.42.0/mptest/config02.test"
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.42.0/mptest/crash01.test"
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.42.0/mptest/crash02.subtest"
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.42.0/mptest/multiwrite01.test"
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.43.0/mptest/mptest.c"
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.43.0/mptest/config01.test"
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.43.0/mptest/config02.test"
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.43.0/mptest/crash01.test"
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.43.0/mptest/crash02.subtest"
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.43.0/mptest/multiwrite01.test"
cd ~-
cd ../vfs/tests/speedtest1/testdata/
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.42.0/test/speedtest1.c"
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.43.0/test/speedtest1.c"
cd ~-

View File

@@ -1,26 +0,0 @@
# Allow the VFS to force memory journal mode
# regardless of SQLITE_OMIT_DESERIALIZE.
--- sqlite3.c.orig
+++ sqlite3.c
@@ -60425,11 +60425,7 @@
int rc = SQLITE_OK; /* Return code */
int tempFile = 0; /* True for temp files (incl. in-memory files) */
int memDb = 0; /* True if this is an in-memory file */
-#ifndef SQLITE_OMIT_DESERIALIZE
int memJM = 0; /* Memory journal mode */
-#else
-# define memJM 0
-#endif
int readOnly = 0; /* True if this is a read-only file */
int journalFileSize; /* Bytes to allocate for each journal fd */
char *zPathname = 0; /* Full path to database file */
@@ -60628,9 +60624,7 @@
int fout = 0; /* VFS flags returned by xOpen() */
rc = sqlite3OsOpen(pVfs, pPager->zFilename, pPager->fd, vfsFlags, &fout);
assert( !memDb );
-#ifndef SQLITE_OMIT_DESERIALIZE
pPager->memVfs = memJM = (fout&SQLITE_OPEN_MEMORY)!=0;
-#endif
readOnly = (fout&SQLITE_OPEN_READONLY)!=0;
/* If the file was successfully opened for read/write access,

View File

@@ -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);

View File

@@ -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
View File

@@ -0,0 +1,9 @@
//go:build !go1.21
package vfs
func clear(b []byte) {
for i := range b {
b[i] = 0
}
}

View File

@@ -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
View 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
}
}

View File

@@ -176,6 +176,8 @@ func (m *memFile) Size() (int64, error) {
return m.size, nil
}
const spinWait = 25 * time.Microsecond
func (m *memFile) Lock(lock vfs.LockLevel) error {
if m.lock >= lock {
return nil
@@ -210,8 +212,8 @@ func (m *memFile) Lock(lock vfs.LockLevel) error {
m.pending = m
}
for start := time.Now(); m.shared > 1; {
if time.Since(start) > time.Millisecond {
for before := time.Now(); m.shared > 1; {
if time.Since(before) > spinWait {
return sqlite3.BUSY
}
m.lockMtx.Unlock()
@@ -285,10 +287,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
}
}

View File

@@ -20,16 +20,16 @@ func osUnlock(file *os.File, start, len int64) _ErrorCode {
}
func osLock(file *os.File, how int, timeout time.Duration, def _ErrorCode) _ErrorCode {
before := time.Now()
var err error
for {
err = unix.Flock(int(file.Fd()), how)
if errno, _ := err.(unix.Errno); errno != unix.EAGAIN {
break
}
if timeout < time.Millisecond {
if timeout <= 0 || timeout < time.Since(before) {
break
}
timeout -= time.Millisecond
time.Sleep(time.Millisecond)
}
return osLockErrorCode(err, def)

View File

@@ -27,16 +27,16 @@ func osLock(file *os.File, typ int16, start, len int64, timeout time.Duration, d
Start: start,
Len: len,
}
before := time.Now()
var err error
for {
err = unix.FcntlFlock(file.Fd(), unix.F_OFD_SETLK, &lock)
if errno, _ := err.(unix.Errno); errno != unix.EAGAIN {
break
}
if timeout < time.Millisecond {
if timeout <= 0 || timeout < time.Since(before) {
break
}
timeout -= time.Millisecond
time.Sleep(time.Millisecond)
}
return osLockErrorCode(err, def)

View File

@@ -138,6 +138,7 @@ func osUnlock(file *os.File, start, len uint32) _ErrorCode {
}
func osLock(file *os.File, flags, start, len uint32, timeout time.Duration, def _ErrorCode) _ErrorCode {
before := time.Now()
var err error
for {
err = windows.LockFileEx(windows.Handle(file.Fd()), flags,
@@ -145,11 +146,16 @@ func osLock(file *os.File, flags, start, len uint32, timeout time.Duration, def
if errno, _ := err.(windows.Errno); errno != windows.ERROR_LOCK_VIOLATION {
break
}
if timeout < time.Millisecond {
if timeout <= 0 || timeout < time.Since(before) {
break
}
if err := windows.TimeBeginPeriod(1); err != nil {
break
}
timeout -= time.Millisecond
time.Sleep(time.Millisecond)
if err := windows.TimeEndPeriod(1); err != nil {
break
}
}
return osLockErrorCode(err, def)
}

View File

@@ -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")

View File

@@ -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 {

View File

@@ -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()
}

View File

@@ -432,9 +432,3 @@ func vfsErrorCode(err error, def _ErrorCode) _ErrorCode {
}
return def
}
func clear(b []byte) {
for i := range b {
b[i] = 0
}
}

View File

@@ -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)