mirror of
https://github.com/ncruces/go-sqlite3.git
synced 2026-01-19 09:04:16 +00:00
Compare commits
27 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cc7bacfb9c | ||
|
|
911e497891 | ||
|
|
3469460635 | ||
|
|
b5adcacec4 | ||
|
|
62d6712f82 | ||
|
|
d34e6197a8 | ||
|
|
f9e867be60 | ||
|
|
96c61a2f55 | ||
|
|
ac94a5406e | ||
|
|
34617e15f0 | ||
|
|
83b3f6ce0a | ||
|
|
f2c8aa0ddf | ||
|
|
63ea13e41e | ||
|
|
b1508bface | ||
|
|
1c6897c8e2 | ||
|
|
170e1dbebd | ||
|
|
25fc5a606a | ||
|
|
8b6c2b28fb | ||
|
|
e59e2ed2a2 | ||
|
|
505c6640c2 | ||
|
|
5b0a063bfe | ||
|
|
32931032d3 | ||
|
|
b7055ef04b | ||
|
|
167025f47a | ||
|
|
b4b50fc547 | ||
|
|
08f7764fe0 | ||
|
|
4e0b8aeaa8 |
2
.github/workflows/test.yml
vendored
2
.github/workflows/test.yml
vendored
@@ -36,7 +36,7 @@ jobs:
|
||||
run: go mod verify
|
||||
|
||||
- name: Vet
|
||||
run: go vet -tags vet ./...
|
||||
run: go vet ./...
|
||||
|
||||
- name: Build
|
||||
run: go build -v ./...
|
||||
|
||||
2
blob.go
2
blob.go
@@ -31,7 +31,6 @@ var _ io.ReadWriteSeeker = &Blob{}
|
||||
//
|
||||
// https://sqlite.org/c3ref/blob_open.html
|
||||
func (c *Conn) OpenBlob(db, table, column string, row int64, write bool) (*Blob, error) {
|
||||
c.checkInterrupt()
|
||||
defer c.arena.mark()()
|
||||
blobPtr := c.arena.new(ptrlen)
|
||||
dbPtr := c.arena.string(db)
|
||||
@@ -43,6 +42,7 @@ func (c *Conn) OpenBlob(db, table, column string, row int64, write bool) (*Blob,
|
||||
flags = 1
|
||||
}
|
||||
|
||||
c.checkInterrupt(c.handle)
|
||||
r := c.call("sqlite3_blob_open", uint64(c.handle),
|
||||
uint64(dbPtr), uint64(tablePtr), uint64(columnPtr),
|
||||
uint64(row), flags, uint64(blobPtr))
|
||||
|
||||
@@ -284,7 +284,10 @@ func walCallback(ctx context.Context, mod api.Module, _, pDB, zSchema uint32, pa
|
||||
//
|
||||
// https://sqlite.org/c3ref/autovacuum_pages.html
|
||||
func (c *Conn) AutoVacuumPages(cb func(schema string, dbPages, freePages, bytesPerPage uint) uint) error {
|
||||
funcPtr := util.AddHandle(c.ctx, cb)
|
||||
var funcPtr uint32
|
||||
if cb != nil {
|
||||
funcPtr = util.AddHandle(c.ctx, cb)
|
||||
}
|
||||
r := c.call("sqlite3_autovacuum_pages_go", uint64(c.handle), uint64(funcPtr))
|
||||
return c.error(r)
|
||||
}
|
||||
|
||||
91
conn.go
91
conn.go
@@ -24,7 +24,7 @@ type Conn struct {
|
||||
pending *Stmt
|
||||
stmts []*Stmt
|
||||
timer *time.Timer
|
||||
busy func(int) bool
|
||||
busy func(context.Context, int) bool
|
||||
log func(xErrorCode, string)
|
||||
collation func(*Conn, string)
|
||||
wal func(*Conn, string, int) error
|
||||
@@ -38,14 +38,20 @@ type Conn struct {
|
||||
handle uint32
|
||||
}
|
||||
|
||||
// Open calls [OpenFlags] with [OPEN_READWRITE], [OPEN_CREATE], [OPEN_URI] and [OPEN_NOFOLLOW].
|
||||
// Open calls [OpenFlags] with [OPEN_READWRITE], [OPEN_CREATE] and [OPEN_URI].
|
||||
func Open(filename string) (*Conn, error) {
|
||||
return newConn(filename, OPEN_READWRITE|OPEN_CREATE|OPEN_URI|OPEN_NOFOLLOW)
|
||||
return newConn(context.Background(), filename, OPEN_READWRITE|OPEN_CREATE|OPEN_URI)
|
||||
}
|
||||
|
||||
// OpenContext is like [Open] but includes a context,
|
||||
// which is used to interrupt the process of opening the connectiton.
|
||||
func OpenContext(ctx context.Context, filename string) (*Conn, error) {
|
||||
return newConn(ctx, filename, OPEN_READWRITE|OPEN_CREATE|OPEN_URI)
|
||||
}
|
||||
|
||||
// OpenFlags opens an SQLite database file as specified by the filename argument.
|
||||
//
|
||||
// If none of the required flags is used, a combination of [OPEN_READWRITE] and [OPEN_CREATE] is used.
|
||||
// If none of the required flags are used, a combination of [OPEN_READWRITE] and [OPEN_CREATE] is used.
|
||||
// If a URI filename is used, PRAGMA statements to execute can be specified using "_pragma":
|
||||
//
|
||||
// sqlite3.Open("file:demo.db?_pragma=busy_timeout(10000)")
|
||||
@@ -55,25 +61,33 @@ func OpenFlags(filename string, flags OpenFlag) (*Conn, error) {
|
||||
if flags&(OPEN_READONLY|OPEN_READWRITE|OPEN_CREATE) == 0 {
|
||||
flags |= OPEN_READWRITE | OPEN_CREATE
|
||||
}
|
||||
return newConn(filename, flags)
|
||||
return newConn(context.Background(), filename, flags)
|
||||
}
|
||||
|
||||
type connKey struct{}
|
||||
|
||||
func newConn(filename string, flags OpenFlag) (conn *Conn, err error) {
|
||||
sqlite, err := instantiateSQLite()
|
||||
func newConn(ctx context.Context, filename string, flags OpenFlag) (res *Conn, _ error) {
|
||||
err := ctx.Err()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
c := &Conn{interrupt: ctx}
|
||||
c.sqlite, err = instantiateSQLite()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer func() {
|
||||
if conn == nil {
|
||||
sqlite.close()
|
||||
if res == nil {
|
||||
c.Close()
|
||||
c.sqlite.close()
|
||||
} else {
|
||||
c.interrupt = context.Background()
|
||||
}
|
||||
}()
|
||||
|
||||
c := &Conn{sqlite: sqlite}
|
||||
c.arena = c.newArena(1024)
|
||||
c.ctx = context.WithValue(c.ctx, connKey{}, c)
|
||||
c.arena = c.newArena(1024)
|
||||
c.handle, err = c.openDB(filename, flags)
|
||||
if err == nil {
|
||||
err = initExtensions(c)
|
||||
@@ -98,6 +112,7 @@ func (c *Conn) openDB(filename string, flags OpenFlag) (uint32, error) {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
c.call("sqlite3_progress_handler_go", uint64(handle), 100)
|
||||
if flags|OPEN_URI != 0 && strings.HasPrefix(filename, "file:") {
|
||||
var pragmas strings.Builder
|
||||
if _, after, ok := strings.Cut(filename, "?"); ok {
|
||||
@@ -109,6 +124,7 @@ func (c *Conn) openDB(filename string, flags OpenFlag) (uint32, error) {
|
||||
}
|
||||
}
|
||||
if pragmas.Len() != 0 {
|
||||
c.checkInterrupt(handle)
|
||||
pragmaPtr := c.arena.string(pragmas.String())
|
||||
r := c.call("sqlite3_exec", uint64(handle), uint64(pragmaPtr), 0, 0, 0)
|
||||
if err := c.sqlite.error(r, handle, pragmas.String()); err != nil {
|
||||
@@ -118,7 +134,6 @@ func (c *Conn) openDB(filename string, flags OpenFlag) (uint32, error) {
|
||||
}
|
||||
}
|
||||
}
|
||||
c.call("sqlite3_progress_handler_go", uint64(handle), 100)
|
||||
return handle, nil
|
||||
}
|
||||
|
||||
@@ -160,10 +175,10 @@ func (c *Conn) Close() error {
|
||||
//
|
||||
// https://sqlite.org/c3ref/exec.html
|
||||
func (c *Conn) Exec(sql string) error {
|
||||
c.checkInterrupt()
|
||||
defer c.arena.mark()()
|
||||
sqlPtr := c.arena.string(sql)
|
||||
|
||||
c.checkInterrupt(c.handle)
|
||||
r := c.call("sqlite3_exec", uint64(c.handle), uint64(sqlPtr), 0, 0, 0)
|
||||
return c.error(r, sql)
|
||||
}
|
||||
@@ -301,8 +316,7 @@ func (c *Conn) ReleaseMemory() error {
|
||||
return c.error(r)
|
||||
}
|
||||
|
||||
// GetInterrupt gets the context set with [Conn.SetInterrupt],
|
||||
// or nil if none was set.
|
||||
// GetInterrupt gets the context set with [Conn.SetInterrupt].
|
||||
func (c *Conn) GetInterrupt() context.Context {
|
||||
return c.interrupt
|
||||
}
|
||||
@@ -322,9 +336,11 @@ func (c *Conn) GetInterrupt() context.Context {
|
||||
//
|
||||
// https://sqlite.org/c3ref/interrupt.html
|
||||
func (c *Conn) SetInterrupt(ctx context.Context) (old context.Context) {
|
||||
// Is it the same context?
|
||||
if ctx == c.interrupt {
|
||||
return ctx
|
||||
old = c.interrupt
|
||||
c.interrupt = ctx
|
||||
|
||||
if ctx == old || ctx.Done() == old.Done() {
|
||||
return old
|
||||
}
|
||||
|
||||
// A busy SQL statement prevents SQLite from ignoring an interrupt
|
||||
@@ -333,32 +349,29 @@ func (c *Conn) SetInterrupt(ctx context.Context) (old context.Context) {
|
||||
defer c.arena.mark()()
|
||||
stmtPtr := c.arena.new(ptrlen)
|
||||
loopPtr := c.arena.string(`WITH RECURSIVE c(x) AS (VALUES(0) UNION ALL SELECT x FROM c) SELECT x FROM c`)
|
||||
c.call("sqlite3_prepare_v3", uint64(c.handle), uint64(loopPtr), math.MaxUint64, 0, uint64(stmtPtr), 0)
|
||||
c.call("sqlite3_prepare_v3", uint64(c.handle), uint64(loopPtr), math.MaxUint64,
|
||||
uint64(PREPARE_PERSISTENT), uint64(stmtPtr), 0)
|
||||
c.pending = &Stmt{c: c}
|
||||
c.pending.handle = util.ReadUint32(c.mod, stmtPtr)
|
||||
}
|
||||
|
||||
old = c.interrupt
|
||||
c.interrupt = ctx
|
||||
|
||||
if old != nil && old.Done() != nil && (ctx == nil || ctx.Err() == nil) {
|
||||
if old.Done() != nil && ctx.Err() == nil {
|
||||
c.pending.Reset()
|
||||
}
|
||||
if ctx != nil && ctx.Done() != nil {
|
||||
if ctx.Done() != nil {
|
||||
c.pending.Step()
|
||||
}
|
||||
return old
|
||||
}
|
||||
|
||||
func (c *Conn) checkInterrupt() {
|
||||
if c.interrupt != nil && c.interrupt.Err() != nil {
|
||||
c.call("sqlite3_interrupt", uint64(c.handle))
|
||||
func (c *Conn) checkInterrupt(handle uint32) {
|
||||
if c.interrupt.Err() != nil {
|
||||
c.call("sqlite3_interrupt", uint64(handle))
|
||||
}
|
||||
}
|
||||
|
||||
func progressCallback(ctx context.Context, mod api.Module, pDB uint32) (interrupt uint32) {
|
||||
if c, ok := ctx.Value(connKey{}).(*Conn); ok && c.handle == pDB &&
|
||||
c.interrupt != nil && c.interrupt.Err() != nil {
|
||||
func progressCallback(ctx context.Context, mod api.Module, _ uint32) (interrupt uint32) {
|
||||
if c, ok := ctx.Value(connKey{}).(*Conn); ok && c.interrupt.Err() != nil {
|
||||
interrupt = 1
|
||||
}
|
||||
return interrupt
|
||||
@@ -373,9 +386,8 @@ func (c *Conn) BusyTimeout(timeout time.Duration) error {
|
||||
return c.error(r)
|
||||
}
|
||||
|
||||
func timeoutCallback(ctx context.Context, mod api.Module, pDB uint32, count, tmout int32) (retry uint32) {
|
||||
if c, ok := ctx.Value(connKey{}).(*Conn); ok &&
|
||||
(c.interrupt == nil || c.interrupt.Err() == nil) {
|
||||
func timeoutCallback(ctx context.Context, mod api.Module, count, tmout int32) (retry uint32) {
|
||||
if c, ok := ctx.Value(connKey{}).(*Conn); ok && c.interrupt.Err() == nil {
|
||||
const delays = "\x01\x02\x05\x0a\x0f\x14\x19\x19\x19\x32\x32\x64"
|
||||
const totals = "\x00\x01\x03\x08\x12\x21\x35\x4e\x67\x80\xb2\xe4"
|
||||
const ndelay = int32(len(delays) - 1)
|
||||
@@ -391,7 +403,7 @@ func timeoutCallback(ctx context.Context, mod api.Module, pDB uint32, count, tmo
|
||||
|
||||
if delay = min(delay, tmout-prior); delay > 0 {
|
||||
delay := time.Duration(delay) * time.Millisecond
|
||||
if c.interrupt == nil || c.interrupt.Done() == nil {
|
||||
if c.interrupt.Done() == nil {
|
||||
time.Sleep(delay)
|
||||
return 1
|
||||
}
|
||||
@@ -414,7 +426,7 @@ func timeoutCallback(ctx context.Context, mod api.Module, pDB uint32, count, tmo
|
||||
// BusyHandler registers a callback to handle [BUSY] errors.
|
||||
//
|
||||
// https://sqlite.org/c3ref/busy_handler.html
|
||||
func (c *Conn) BusyHandler(cb func(count int) (retry bool)) error {
|
||||
func (c *Conn) BusyHandler(cb func(ctx context.Context, count int) (retry bool)) error {
|
||||
var enable uint64
|
||||
if cb != nil {
|
||||
enable = 1
|
||||
@@ -428,9 +440,12 @@ func (c *Conn) BusyHandler(cb func(count int) (retry bool)) error {
|
||||
}
|
||||
|
||||
func busyCallback(ctx context.Context, mod api.Module, pDB uint32, count int32) (retry uint32) {
|
||||
if c, ok := ctx.Value(connKey{}).(*Conn); ok && c.handle == pDB && c.busy != nil &&
|
||||
(c.interrupt == nil || c.interrupt.Err() == nil) {
|
||||
if c.busy(int(count)) {
|
||||
if c, ok := ctx.Value(connKey{}).(*Conn); ok && c.handle == pDB && c.busy != nil {
|
||||
interrupt := c.interrupt
|
||||
if interrupt == nil {
|
||||
interrupt = context.Background()
|
||||
}
|
||||
if interrupt.Err() == nil && c.busy(interrupt, int(count)) {
|
||||
retry = 1
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//go:build (go1.23 || goexperiment.rangefunc) && !vet
|
||||
//go:build go1.23
|
||||
|
||||
package sqlite3
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//go:build !(go1.23 || goexperiment.rangefunc) || vet
|
||||
//go:build !go1.23
|
||||
|
||||
package sqlite3
|
||||
|
||||
|
||||
@@ -40,14 +40,14 @@
|
||||
// When using a custom time struct, you'll have to implement
|
||||
// [database/sql/driver.Valuer] and [database/sql.Scanner].
|
||||
//
|
||||
// The Value method should ideally serialise to a time [format] supported by SQLite.
|
||||
// The Value method should ideally encode to a time [format] supported by SQLite.
|
||||
// This ensures SQL date and time functions work as they should,
|
||||
// and that your schema works with other SQLite tools.
|
||||
// [sqlite3.TimeFormat.Encode] may help.
|
||||
//
|
||||
// The Scan method needs to take into account that the value it receives can be of differing types.
|
||||
// It can already be a [time.Time], if the driver decoded the value according to "_timefmt" rules.
|
||||
// Or it can be a: string, int64, float64, []byte, nil,
|
||||
// Or it can be a: string, int64, float64, []byte, or nil,
|
||||
// depending on the column type and what whoever wrote the value.
|
||||
// [sqlite3.TimeFormat.Decode] may help.
|
||||
//
|
||||
@@ -202,19 +202,19 @@ func (n *connector) Driver() driver.Driver {
|
||||
return n.driver
|
||||
}
|
||||
|
||||
func (n *connector) Connect(ctx context.Context) (_ driver.Conn, err error) {
|
||||
func (n *connector) Connect(ctx context.Context) (res driver.Conn, err error) {
|
||||
c := &conn{
|
||||
txLock: n.txLock,
|
||||
tmRead: n.tmRead,
|
||||
tmWrite: n.tmWrite,
|
||||
}
|
||||
|
||||
c.Conn, err = sqlite3.Open(n.name)
|
||||
c.Conn, err = sqlite3.OpenContext(ctx, n.name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer func() {
|
||||
if err != nil {
|
||||
if res == nil {
|
||||
c.Close()
|
||||
}
|
||||
}()
|
||||
@@ -239,6 +239,7 @@ func (n *connector) Connect(ctx context.Context) (_ driver.Conn, err error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer s.Close()
|
||||
if s.Step() && s.ColumnBool(0) {
|
||||
c.readOnly = '1'
|
||||
} else {
|
||||
@@ -466,6 +467,7 @@ func (s *stmt) ExecContext(ctx context.Context, args []driver.NamedValue) (drive
|
||||
defer s.Stmt.Conn().SetInterrupt(old)
|
||||
|
||||
err = s.Stmt.Exec()
|
||||
s.Stmt.ClearBindings()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -488,7 +490,7 @@ func (s *stmt) setupBindings(args []driver.NamedValue) (err error) {
|
||||
if arg.Name == "" {
|
||||
ids = append(ids, arg.Ordinal)
|
||||
} else {
|
||||
for _, prefix := range []string{":", "@", "$"} {
|
||||
for _, prefix := range [...]string{":", "@", "$"} {
|
||||
if id := s.Stmt.BindIndex(prefix + arg.Name); id != 0 {
|
||||
ids = append(ids, id)
|
||||
}
|
||||
@@ -522,9 +524,9 @@ func (s *stmt) setupBindings(args []driver.NamedValue) (err error) {
|
||||
default:
|
||||
panic(util.AssertErr())
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
@@ -595,10 +597,11 @@ func (r *rows) Close() error {
|
||||
func (r *rows) Columns() []string {
|
||||
if r.names == nil {
|
||||
count := r.Stmt.ColumnCount()
|
||||
r.names = make([]string, count)
|
||||
for i := range r.names {
|
||||
r.names[i] = r.Stmt.ColumnName(i)
|
||||
names := make([]string, count)
|
||||
for i := range names {
|
||||
names[i] = r.Stmt.ColumnName(i)
|
||||
}
|
||||
r.names = names
|
||||
}
|
||||
return r.names
|
||||
}
|
||||
@@ -606,26 +609,29 @@ func (r *rows) Columns() []string {
|
||||
func (r *rows) loadTypes() {
|
||||
if r.nulls == nil {
|
||||
count := r.Stmt.ColumnCount()
|
||||
r.nulls = make([]bool, count)
|
||||
r.types = make([]string, count)
|
||||
for i := range r.nulls {
|
||||
nulls := make([]bool, count)
|
||||
types := make([]string, count)
|
||||
for i := range nulls {
|
||||
if col := r.Stmt.ColumnOriginName(i); col != "" {
|
||||
r.types[i], _, r.nulls[i], _, _, _ = r.Stmt.Conn().TableColumnMetadata(
|
||||
types[i], _, nulls[i], _, _, _ = r.Stmt.Conn().TableColumnMetadata(
|
||||
r.Stmt.ColumnDatabaseName(i),
|
||||
r.Stmt.ColumnTableName(i),
|
||||
col)
|
||||
}
|
||||
}
|
||||
r.nulls = nulls
|
||||
r.types = types
|
||||
}
|
||||
}
|
||||
|
||||
func (r *rows) declType(index int) string {
|
||||
if r.types == nil {
|
||||
count := r.Stmt.ColumnCount()
|
||||
r.types = make([]string, count)
|
||||
for i := range r.types {
|
||||
r.types[i] = strings.ToUpper(r.Stmt.ColumnDeclType(i))
|
||||
types := make([]string, count)
|
||||
for i := range types {
|
||||
types[i] = strings.ToUpper(r.Stmt.ColumnDeclType(i))
|
||||
}
|
||||
r.types = types
|
||||
}
|
||||
return r.types[index]
|
||||
}
|
||||
@@ -665,27 +671,23 @@ func (r *rows) Next(dest []driver.Value) error {
|
||||
for i := range dest {
|
||||
if t, ok := r.decodeTime(i, dest[i]); ok {
|
||||
dest[i] = t
|
||||
continue
|
||||
}
|
||||
if s, ok := dest[i].(string); ok {
|
||||
t, ok := maybeTime(s)
|
||||
if ok {
|
||||
dest[i] = t
|
||||
}
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (r *rows) decodeTime(i int, v any) (_ time.Time, ok bool) {
|
||||
switch r.tmRead {
|
||||
case sqlite3.TimeFormatDefault, time.RFC3339Nano:
|
||||
// handled by maybeTime
|
||||
return
|
||||
}
|
||||
switch v.(type) {
|
||||
case int64, float64, string:
|
||||
switch v := v.(type) {
|
||||
case int64, float64:
|
||||
// could be a time value
|
||||
case string:
|
||||
if r.tmWrite != "" && r.tmWrite != time.RFC3339 && r.tmWrite != time.RFC3339Nano {
|
||||
break
|
||||
}
|
||||
t, ok := maybeTime(v)
|
||||
if ok {
|
||||
return t, true
|
||||
}
|
||||
default:
|
||||
return
|
||||
}
|
||||
|
||||
144
driver/example2_test.go
Normal file
144
driver/example2_test.go
Normal file
@@ -0,0 +1,144 @@
|
||||
//go:build (linux || darwin || windows || freebsd || openbsd || netbsd || dragonfly || illumos || sqlite3_flock) && !sqlite3_nosys
|
||||
|
||||
package driver_test
|
||||
|
||||
// Adapted from: https://go.dev/doc/tutorial/database-access
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"database/sql/driver"
|
||||
"fmt"
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"github.com/ncruces/go-sqlite3"
|
||||
_ "github.com/ncruces/go-sqlite3/driver"
|
||||
_ "github.com/ncruces/go-sqlite3/embed"
|
||||
_ "github.com/ncruces/go-sqlite3/vfs/memdb"
|
||||
)
|
||||
|
||||
func Example_customTime() {
|
||||
db, err := sql.Open("sqlite3", "file:/time.db?vfs=memdb")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
_, err = db.Exec(`
|
||||
CREATE TABLE data (
|
||||
id INTEGER PRIMARY KEY,
|
||||
date_time TEXT
|
||||
) STRICT;
|
||||
`)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// This one will be returned as string to [sql.Scanner] because it doesn't
|
||||
// pass the driver's round-trip test when it tries to figure out if it's
|
||||
// a time. 2009-11-17T20:34:58.650Z goes in, but parsing and formatting
|
||||
// it with [time.RFC3338Nano] results in 2009-11-17T20:34:58.65Z. Though
|
||||
// the times are identical, the trailing zero is lost in the string
|
||||
// representation so the driver considers the conversion unsuccessful.
|
||||
c1 := CustomTime{time.Date(
|
||||
2009, 11, 17, 20, 34, 58, 650000000, time.UTC)}
|
||||
|
||||
// Store our custom time in the database.
|
||||
_, err = db.Exec(`INSERT INTO data (date_time) VALUES(?)`, c1)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
var strc1 string
|
||||
// Retrieve it as a string, the result of Value().
|
||||
err = db.QueryRow(`
|
||||
SELECT date_time
|
||||
FROM data
|
||||
WHERE id = last_insert_rowid()
|
||||
`).Scan(&strc1)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
fmt.Println("in db:", strc1)
|
||||
|
||||
var resc1 CustomTime
|
||||
// Retrieve it as our custom time type, going through Scan().
|
||||
err = db.QueryRow(`
|
||||
SELECT date_time
|
||||
FROM data
|
||||
WHERE id = last_insert_rowid()
|
||||
`).Scan(&resc1)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
fmt.Println("custom time:", resc1)
|
||||
|
||||
// This one will be returned as [time.Time] to [sql.Scanner] because it does
|
||||
// pass the driver's round-trip test when it tries to figure out if it's
|
||||
// a time. 2009-11-17T20:34:58.651Z goes in, and parsing and formatting
|
||||
// it with [time.RFC3339Nano] results in 2009-11-17T20:34:58.651Z.
|
||||
c2 := CustomTime{time.Date(
|
||||
2009, 11, 17, 20, 34, 58, 651000000, time.UTC)}
|
||||
// Store our custom time in the database.
|
||||
_, err = db.Exec(`INSERT INTO data (date_time) VALUES(?)`, c2)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
var strc2 string
|
||||
// Retrieve it as a string, the result of Value().
|
||||
err = db.QueryRow(`
|
||||
SELECT date_time
|
||||
FROM data
|
||||
WHERE id = last_insert_rowid()
|
||||
`).Scan(&strc2)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
fmt.Println("in db:", strc2)
|
||||
|
||||
var resc2 CustomTime
|
||||
// Retrieve it as our custom time type, going through Scan().
|
||||
err = db.QueryRow(`
|
||||
SELECT date_time
|
||||
FROM data
|
||||
WHERE id = last_insert_rowid()
|
||||
`).Scan(&resc2)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
fmt.Println("custom time:", resc2)
|
||||
// Output:
|
||||
// in db: 2009-11-17T20:34:58.650Z
|
||||
// scan type string: 2009-11-17T20:34:58.650Z
|
||||
// custom time: 2009-11-17 20:34:58.65 +0000 UTC
|
||||
// in db: 2009-11-17T20:34:58.651Z
|
||||
// scan type time: 2009-11-17 20:34:58.651 +0000 UTC
|
||||
// custom time: 2009-11-17 20:34:58.651 +0000 UTC
|
||||
}
|
||||
|
||||
type CustomTime struct{ time.Time }
|
||||
|
||||
func (c CustomTime) Value() (driver.Value, error) {
|
||||
return sqlite3.TimeFormat7TZ.Encode(c.UTC()), nil
|
||||
}
|
||||
|
||||
func (c *CustomTime) Scan(value any) error {
|
||||
switch v := value.(type) {
|
||||
case nil:
|
||||
*c = CustomTime{time.Time{}}
|
||||
case time.Time:
|
||||
fmt.Println("scan type time:", v)
|
||||
*c = CustomTime{v}
|
||||
case string:
|
||||
fmt.Println("scan type string:", v)
|
||||
t, err := sqlite3.TimeFormat7TZ.Decode(v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*c = CustomTime{t}
|
||||
default:
|
||||
panic("unsupported value type")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -6,13 +6,10 @@ package driver_test
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"database/sql/driver"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/ncruces/go-sqlite3"
|
||||
_ "github.com/ncruces/go-sqlite3/driver"
|
||||
_ "github.com/ncruces/go-sqlite3/embed"
|
||||
_ "github.com/ncruces/go-sqlite3/vfs/memdb"
|
||||
@@ -153,129 +150,3 @@ func addAlbum(alb Album) (int64, error) {
|
||||
}
|
||||
return id, nil
|
||||
}
|
||||
|
||||
func Example_customTime() {
|
||||
db, err := sql.Open("sqlite3", "file:/time.db?vfs=memdb")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
_, err = db.Exec(`
|
||||
CREATE TABLE data (
|
||||
id INTEGER PRIMARY KEY,
|
||||
date_time TEXT
|
||||
) STRICT;
|
||||
`)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// This one will be returned as string to [sql.Scanner] because it doesn't
|
||||
// pass the driver's round-trip test when it tries to figure out if it's
|
||||
// a time. 2009-11-17T20:34:58.650Z goes in, but parsing and formatting
|
||||
// it with [time.RFC3338Nano] results in 2009-11-17T20:34:58.65Z. Though
|
||||
// the times are identical, the trailing zero is lost in the string
|
||||
// representation so the driver considers the conversion unsuccessful.
|
||||
c1 := CustomTime{time.Date(
|
||||
2009, 11, 17, 20, 34, 58, 650000000, time.UTC)}
|
||||
|
||||
// Store our custom time in the database.
|
||||
_, err = db.Exec(`INSERT INTO data (date_time) VALUES(?)`, c1)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
var strc1 string
|
||||
// Retrieve it as a string, the result of Value().
|
||||
err = db.QueryRow(`
|
||||
SELECT date_time
|
||||
FROM data
|
||||
WHERE id = last_insert_rowid()
|
||||
`).Scan(&strc1)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
fmt.Println("in db:", strc1)
|
||||
|
||||
var resc1 CustomTime
|
||||
// Retrieve it as our custom time type, going through Scan().
|
||||
err = db.QueryRow(`
|
||||
SELECT date_time
|
||||
FROM data
|
||||
WHERE id = last_insert_rowid()
|
||||
`).Scan(&resc1)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
fmt.Println("custom time:", resc1)
|
||||
|
||||
// This one will be returned as [time.Time] to [sql.Scanner] because it does
|
||||
// pass the driver's round-trip test when it tries to figure out if it's
|
||||
// a time. 2009-11-17T20:34:58.651Z goes in, and parsing and formatting
|
||||
// it with [time.RFC3339Nano] results in 2009-11-17T20:34:58.651Z.
|
||||
c2 := CustomTime{time.Date(
|
||||
2009, 11, 17, 20, 34, 58, 651000000, time.UTC)}
|
||||
// Store our custom time in the database.
|
||||
_, err = db.Exec(`INSERT INTO data (date_time) VALUES(?)`, c2)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
var strc2 string
|
||||
// Retrieve it as a string, the result of Value().
|
||||
err = db.QueryRow(`
|
||||
SELECT date_time
|
||||
FROM data
|
||||
WHERE id = last_insert_rowid()
|
||||
`).Scan(&strc2)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
fmt.Println("in db:", strc2)
|
||||
|
||||
var resc2 CustomTime
|
||||
// Retrieve it as our custom time type, going through Scan().
|
||||
err = db.QueryRow(`
|
||||
SELECT date_time
|
||||
FROM data
|
||||
WHERE id = last_insert_rowid()
|
||||
`).Scan(&resc2)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
fmt.Println("custom time:", resc2)
|
||||
// Output:
|
||||
// in db: 2009-11-17T20:34:58.650Z
|
||||
// scan type string: 2009-11-17T20:34:58.650Z
|
||||
// custom time: 2009-11-17 20:34:58.65 +0000 UTC
|
||||
// in db: 2009-11-17T20:34:58.651Z
|
||||
// scan type time: 2009-11-17 20:34:58.651 +0000 UTC
|
||||
// custom time: 2009-11-17 20:34:58.651 +0000 UTC
|
||||
}
|
||||
|
||||
type CustomTime struct{ time.Time }
|
||||
|
||||
func (c CustomTime) Value() (driver.Value, error) {
|
||||
return sqlite3.TimeFormat7TZ.Encode(c.UTC()), nil
|
||||
}
|
||||
|
||||
func (c *CustomTime) Scan(value any) error {
|
||||
switch v := value.(type) {
|
||||
case nil:
|
||||
*c = CustomTime{time.Time{}}
|
||||
case time.Time:
|
||||
fmt.Println("scan type time:", v)
|
||||
*c = CustomTime{v}
|
||||
case string:
|
||||
fmt.Println("scan type string:", v)
|
||||
t, err := sqlite3.TimeFormat7TZ.Decode(v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*c = CustomTime{t}
|
||||
default:
|
||||
panic("unsupported value type")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ The following optional features are compiled in:
|
||||
- [JSON](https://sqlite.org/json1.html)
|
||||
- [R*Tree](https://sqlite.org/rtree.html)
|
||||
- [GeoPoly](https://sqlite.org/geopoly.html)
|
||||
- [Spellfix1](https://sqlite.org/spellfix1.html)
|
||||
- [soundex](https://sqlite.org/lang_corefunc.html#soundex)
|
||||
- [stat4](https://sqlite.org/compile.html#enable_stat4)
|
||||
- [base64](https://github.com/sqlite/sqlite/blob/master/ext/misc/base64.c)
|
||||
|
||||
Binary file not shown.
@@ -33,6 +33,7 @@ mv sqlite/ext/misc/decimal.c build/ext/
|
||||
mv sqlite/ext/misc/ieee754.c build/ext/
|
||||
mv sqlite/ext/misc/regexp.c build/ext/
|
||||
mv sqlite/ext/misc/series.c build/ext/
|
||||
mv sqlite/ext/misc/spellfix.c build/ext/
|
||||
mv sqlite/ext/misc/uint.c build/ext/
|
||||
|
||||
cd build
|
||||
@@ -44,7 +45,7 @@ cd ~-
|
||||
-o bcw2.wasm "build/main.c" \
|
||||
-I"build" \
|
||||
-mexec-model=reactor \
|
||||
-matomics -msimd128 -mmutable-globals \
|
||||
-matomics -msimd128 -mmutable-globals -mmultivalue \
|
||||
-mbulk-memory -mreference-types \
|
||||
-mnontrapping-fptoint -msign-ext \
|
||||
-fno-stack-protector -fno-stack-clash-protection \
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
// import _ "github.com/ncruces/go-sqlite3/embed/bcw2"
|
||||
//
|
||||
// [BEGIN CONCURRENT]: https://sqlite.org/src/doc/begin-concurrent/doc/begin_concurrent.md
|
||||
// [Wal2]: https://www.sqlite.org/cgi/src/doc/wal2/doc/wal2.md
|
||||
// [Wal2]: https://sqlite.org/cgi/src/doc/wal2/doc/wal2.md
|
||||
package bcw2
|
||||
|
||||
import (
|
||||
|
||||
@@ -14,7 +14,7 @@ trap 'rm -f sqlite3.tmp' EXIT
|
||||
-o sqlite3.wasm "$ROOT/sqlite3/main.c" \
|
||||
-I"$ROOT/sqlite3" \
|
||||
-mexec-model=reactor \
|
||||
-matomics -msimd128 -mmutable-globals \
|
||||
-matomics -msimd128 -mmutable-globals -mmultivalue \
|
||||
-mbulk-memory -mreference-types \
|
||||
-mnontrapping-fptoint -msign-ext \
|
||||
-fno-stack-protector -fno-stack-clash-protection \
|
||||
|
||||
@@ -51,6 +51,7 @@ sqlite3_create_collation_go
|
||||
sqlite3_create_function_go
|
||||
sqlite3_create_module_go
|
||||
sqlite3_create_window_function_go
|
||||
sqlite3_data_count
|
||||
sqlite3_database_file_object
|
||||
sqlite3_db_cacheflush
|
||||
sqlite3_db_config
|
||||
|
||||
Binary file not shown.
@@ -34,7 +34,7 @@ type bloom struct {
|
||||
}
|
||||
|
||||
func create(db *sqlite3.Conn, _, schema, table string, arg ...string) (_ *bloom, err error) {
|
||||
t := bloom{
|
||||
b := bloom{
|
||||
db: db,
|
||||
schema: schema,
|
||||
storage: table + "_storage",
|
||||
@@ -54,30 +54,30 @@ func create(db *sqlite3.Conn, _, schema, table string, arg ...string) (_ *bloom,
|
||||
}
|
||||
|
||||
if len(arg) > 1 {
|
||||
t.prob, err = strconv.ParseFloat(arg[1], 64)
|
||||
b.prob, err = strconv.ParseFloat(arg[1], 64)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if t.prob <= 0 || t.prob >= 1 {
|
||||
if b.prob <= 0 || b.prob >= 1 {
|
||||
return nil, util.ErrorString("bloom: probability must be in the range (0,1)")
|
||||
}
|
||||
} else {
|
||||
t.prob = 0.01
|
||||
b.prob = 0.01
|
||||
}
|
||||
|
||||
if len(arg) > 2 {
|
||||
t.hashes, err = strconv.Atoi(arg[2])
|
||||
b.hashes, err = strconv.Atoi(arg[2])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if t.hashes <= 0 {
|
||||
if b.hashes <= 0 {
|
||||
return nil, util.ErrorString("bloom: number of hash functions must be positive")
|
||||
}
|
||||
} else {
|
||||
t.hashes = max(1, numHashes(t.prob))
|
||||
b.hashes = max(1, numHashes(b.prob))
|
||||
}
|
||||
|
||||
t.bytes = numBytes(nelem, t.prob)
|
||||
b.bytes = numBytes(nelem, b.prob)
|
||||
|
||||
err = db.DeclareVTab(
|
||||
`CREATE TABLE x(present, word HIDDEN NOT NULL PRIMARY KEY) WITHOUT ROWID`)
|
||||
@@ -87,7 +87,7 @@ func create(db *sqlite3.Conn, _, schema, table string, arg ...string) (_ *bloom,
|
||||
|
||||
err = db.Exec(fmt.Sprintf(
|
||||
`CREATE TABLE %s.%s (data BLOB, p REAL, n INTEGER, m INTEGER, k INTEGER)`,
|
||||
sqlite3.QuoteIdentifier(t.schema), sqlite3.QuoteIdentifier(t.storage)))
|
||||
sqlite3.QuoteIdentifier(b.schema), sqlite3.QuoteIdentifier(b.storage)))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -98,17 +98,17 @@ func create(db *sqlite3.Conn, _, schema, table string, arg ...string) (_ *bloom,
|
||||
err = db.Exec(fmt.Sprintf(
|
||||
`INSERT INTO %s.%s (rowid, data, p, n, m, k)
|
||||
VALUES (1, zeroblob(%d), %f, %d, %d, %d)`,
|
||||
sqlite3.QuoteIdentifier(t.schema), sqlite3.QuoteIdentifier(t.storage),
|
||||
t.bytes, t.prob, nelem, 8*t.bytes, t.hashes))
|
||||
sqlite3.QuoteIdentifier(b.schema), sqlite3.QuoteIdentifier(b.storage),
|
||||
b.bytes, b.prob, nelem, 8*b.bytes, b.hashes))
|
||||
if err != nil {
|
||||
t.Destroy()
|
||||
b.Destroy()
|
||||
return nil, err
|
||||
}
|
||||
return &t, nil
|
||||
return &b, nil
|
||||
}
|
||||
|
||||
func connect(db *sqlite3.Conn, _, schema, table string, arg ...string) (_ *bloom, err error) {
|
||||
t := bloom{
|
||||
b := bloom{
|
||||
db: db,
|
||||
schema: schema,
|
||||
storage: table + "_storage",
|
||||
@@ -122,7 +122,7 @@ func connect(db *sqlite3.Conn, _, schema, table string, arg ...string) (_ *bloom
|
||||
|
||||
load, _, err := db.Prepare(fmt.Sprintf(
|
||||
`SELECT m/8, p, k FROM %s.%s WHERE rowid = 1`,
|
||||
sqlite3.QuoteIdentifier(t.schema), sqlite3.QuoteIdentifier(t.storage)))
|
||||
sqlite3.QuoteIdentifier(b.schema), sqlite3.QuoteIdentifier(b.storage)))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -135,10 +135,10 @@ func connect(db *sqlite3.Conn, _, schema, table string, arg ...string) (_ *bloom
|
||||
return nil, sqlite3.CORRUPT_VTAB
|
||||
}
|
||||
|
||||
t.bytes = load.ColumnInt64(0)
|
||||
t.prob = load.ColumnFloat(1)
|
||||
t.hashes = load.ColumnInt(2)
|
||||
return &t, nil
|
||||
b.bytes = load.ColumnInt64(0)
|
||||
b.prob = load.ColumnFloat(1)
|
||||
b.hashes = load.ColumnInt(2)
|
||||
return &b, nil
|
||||
}
|
||||
|
||||
func (b *bloom) Destroy() error {
|
||||
|
||||
@@ -92,8 +92,10 @@ func (c *closure) BestIndex(idx *sqlite3.IndexInfo) error {
|
||||
case sqlite3.INDEX_CONSTRAINT_EQ:
|
||||
plan |= 1
|
||||
cost /= 100
|
||||
idx.ConstraintUsage[i].ArgvIndex = 1
|
||||
idx.ConstraintUsage[i].Omit = true
|
||||
idx.ConstraintUsage[i] = sqlite3.IndexConstraintUsage{
|
||||
ArgvIndex: 1,
|
||||
Omit: true,
|
||||
}
|
||||
}
|
||||
continue
|
||||
}
|
||||
@@ -116,8 +118,10 @@ func (c *closure) BestIndex(idx *sqlite3.IndexInfo) error {
|
||||
plan |= posi << 8
|
||||
cost /= 5
|
||||
posi += 1
|
||||
idx.ConstraintUsage[i].ArgvIndex = posi
|
||||
idx.ConstraintUsage[i].Omit = true
|
||||
idx.ConstraintUsage[i] = sqlite3.IndexConstraintUsage{
|
||||
ArgvIndex: posi,
|
||||
Omit: true,
|
||||
}
|
||||
}
|
||||
continue
|
||||
}
|
||||
@@ -126,8 +130,10 @@ func (c *closure) BestIndex(idx *sqlite3.IndexInfo) error {
|
||||
case sqlite3.INDEX_CONSTRAINT_EQ:
|
||||
plan |= posi << 12
|
||||
posi += 1
|
||||
idx.ConstraintUsage[i].ArgvIndex = posi
|
||||
idx.ConstraintUsage[i].Omit = true
|
||||
idx.ConstraintUsage[i] = sqlite3.IndexConstraintUsage{
|
||||
ArgvIndex: posi,
|
||||
Omit: true,
|
||||
}
|
||||
}
|
||||
continue
|
||||
}
|
||||
@@ -136,8 +142,10 @@ func (c *closure) BestIndex(idx *sqlite3.IndexInfo) error {
|
||||
case sqlite3.INDEX_CONSTRAINT_EQ:
|
||||
plan |= posi << 16
|
||||
posi += 1
|
||||
idx.ConstraintUsage[i].ArgvIndex = posi
|
||||
idx.ConstraintUsage[i].Omit = true
|
||||
idx.ConstraintUsage[i] = sqlite3.IndexConstraintUsage{
|
||||
ArgvIndex: posi,
|
||||
Omit: true,
|
||||
}
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ func Register(db *sqlite3.Conn) error {
|
||||
// RegisterFS registers the CSV virtual table.
|
||||
// If a filename is specified, fsys is used to open the file.
|
||||
func RegisterFS(db *sqlite3.Conn, fsys fs.FS) error {
|
||||
declare := func(db *sqlite3.Conn, _, _, _ string, arg ...string) (_ *table, err error) {
|
||||
declare := func(db *sqlite3.Conn, _, _, _ string, arg ...string) (res *table, err error) {
|
||||
var (
|
||||
filename string
|
||||
data string
|
||||
@@ -76,7 +76,7 @@ func RegisterFS(db *sqlite3.Conn, fsys fs.FS) error {
|
||||
return nil, util.ErrorString(`csv: must specify either "filename" or "data" but not both`)
|
||||
}
|
||||
|
||||
table := &table{
|
||||
t := &table{
|
||||
fsys: fsys,
|
||||
name: filename,
|
||||
data: data,
|
||||
@@ -88,7 +88,7 @@ func RegisterFS(db *sqlite3.Conn, fsys fs.FS) error {
|
||||
if schema == "" {
|
||||
var row []string
|
||||
if header || columns < 0 {
|
||||
csv, c, err := table.newReader()
|
||||
csv, c, err := t.newReader()
|
||||
defer c.Close()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -100,22 +100,20 @@ func RegisterFS(db *sqlite3.Conn, fsys fs.FS) error {
|
||||
}
|
||||
schema = getSchema(header, columns, row)
|
||||
} else {
|
||||
defer func() {
|
||||
if err == nil {
|
||||
table.typs, err = getColumnAffinities(schema)
|
||||
}
|
||||
}()
|
||||
t.typs, err = getColumnAffinities(schema)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
err = db.DeclareVTab(schema)
|
||||
if err == nil {
|
||||
err = db.VTabConfig(sqlite3.VTAB_DIRECTONLY)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = db.VTabConfig(sqlite3.VTAB_DIRECTONLY)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return table, nil
|
||||
return t, nil
|
||||
}
|
||||
|
||||
return sqlite3.CreateModule(db, "csv", declare, declare)
|
||||
|
||||
@@ -22,8 +22,9 @@ func getColumnAffinities(schema string) ([]affinity, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
types := make([]affinity, len(tab.Columns))
|
||||
for i, col := range tab.Columns {
|
||||
columns := tab.Columns
|
||||
types := make([]affinity, len(columns))
|
||||
for i, col := range columns {
|
||||
types[i] = getAffinity(col.Type)
|
||||
}
|
||||
return types, nil
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//go:build !(go1.23 || goexperiment.rangefunc) || vet
|
||||
//go:build !go1.23
|
||||
|
||||
package fileio
|
||||
|
||||
|
||||
@@ -31,7 +31,9 @@ func RegisterFS(db *sqlite3.Conn, fsys fs.FS) error {
|
||||
db.CreateFunction("lsmode", 1, sqlite3.DETERMINISTIC, lsmode),
|
||||
sqlite3.CreateModule(db, "fsdir", nil, func(db *sqlite3.Conn, _, _, _ string, _ ...string) (fsdir, error) {
|
||||
err := db.DeclareVTab(`CREATE TABLE x(name,mode,mtime TIMESTAMP,data,path HIDDEN,dir HIDDEN)`)
|
||||
db.VTabConfig(sqlite3.VTAB_DIRECTONLY)
|
||||
if err == nil {
|
||||
err = db.VTabConfig(sqlite3.VTAB_DIRECTONLY)
|
||||
}
|
||||
return fsdir{fsys}, err
|
||||
}))
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//go:build !(go1.23 || goexperiment.rangefunc) || vet
|
||||
//go:build !go1.23
|
||||
|
||||
package fileio
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//go:build (go1.23 || goexperiment.rangefunc) && !vet
|
||||
//go:build go1.23
|
||||
|
||||
package fileio
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ import (
|
||||
|
||||
// Register registers cryptographic hash functions for a database connection.
|
||||
func Register(db *sqlite3.Conn) error {
|
||||
flags := sqlite3.DETERMINISTIC | sqlite3.INNOCUOUS
|
||||
const flags = sqlite3.DETERMINISTIC | sqlite3.INNOCUOUS
|
||||
|
||||
var errs util.ErrorJoiner
|
||||
if crypto.MD4.Available() {
|
||||
|
||||
@@ -39,13 +39,17 @@ func RegisterFS(db *sqlite3.Conn, fsys fs.FS) error {
|
||||
sqlite3.CreateModule(db, "lines", nil,
|
||||
func(db *sqlite3.Conn, _, _, _ string, _ ...string) (lines, error) {
|
||||
err := db.DeclareVTab(`CREATE TABLE x(line TEXT, data HIDDEN)`)
|
||||
db.VTabConfig(sqlite3.VTAB_INNOCUOUS)
|
||||
if err == nil {
|
||||
err = db.VTabConfig(sqlite3.VTAB_INNOCUOUS)
|
||||
}
|
||||
return lines{}, err
|
||||
}),
|
||||
sqlite3.CreateModule(db, "lines_read", nil,
|
||||
func(db *sqlite3.Conn, _, _, _ string, _ ...string) (lines, error) {
|
||||
err := db.DeclareVTab(`CREATE TABLE x(line TEXT, data HIDDEN)`)
|
||||
db.VTabConfig(sqlite3.VTAB_DIRECTONLY)
|
||||
if err == nil {
|
||||
err = db.VTabConfig(sqlite3.VTAB_DIRECTONLY)
|
||||
}
|
||||
return lines{fsys}, err
|
||||
}))
|
||||
}
|
||||
|
||||
@@ -25,15 +25,15 @@ type table struct {
|
||||
cols []*sqlite3.Value
|
||||
}
|
||||
|
||||
func declare(db *sqlite3.Conn, _, _, _ string, arg ...string) (_ *table, err error) {
|
||||
func declare(db *sqlite3.Conn, _, _, _ string, arg ...string) (res *table, err error) {
|
||||
if len(arg) != 3 {
|
||||
return nil, fmt.Errorf("pivot: wrong number of arguments")
|
||||
}
|
||||
|
||||
table := &table{db: db}
|
||||
t := &table{db: db}
|
||||
defer func() {
|
||||
if err != nil {
|
||||
table.Close()
|
||||
if res == nil {
|
||||
t.Close()
|
||||
}
|
||||
}()
|
||||
|
||||
@@ -42,17 +42,17 @@ func declare(db *sqlite3.Conn, _, _, _ string, arg ...string) (_ *table, err err
|
||||
create.WriteString("CREATE TABLE x(")
|
||||
|
||||
// Row key query.
|
||||
table.scan = "SELECT * FROM\n" + arg[0]
|
||||
stmt, _, err := db.Prepare(table.scan)
|
||||
t.scan = "SELECT * FROM\n" + arg[0]
|
||||
stmt, _, err := db.Prepare(t.scan)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer stmt.Close()
|
||||
|
||||
table.keys = make([]string, stmt.ColumnCount())
|
||||
for i := range table.keys {
|
||||
t.keys = make([]string, stmt.ColumnCount())
|
||||
for i := range t.keys {
|
||||
name := sqlite3.QuoteIdentifier(stmt.ColumnName(i))
|
||||
table.keys[i] = name
|
||||
t.keys[i] = name
|
||||
create.WriteString(sep)
|
||||
create.WriteString(name)
|
||||
sep = ","
|
||||
@@ -70,15 +70,15 @@ func declare(db *sqlite3.Conn, _, _, _ string, arg ...string) (_ *table, err err
|
||||
}
|
||||
for stmt.Step() {
|
||||
name := sqlite3.QuoteIdentifier(stmt.ColumnText(1))
|
||||
table.cols = append(table.cols, stmt.ColumnValue(0).Dup())
|
||||
t.cols = append(t.cols, stmt.ColumnValue(0).Dup())
|
||||
create.WriteString(",")
|
||||
create.WriteString(name)
|
||||
}
|
||||
stmt.Close()
|
||||
|
||||
// Pivot cell query.
|
||||
table.cell = "SELECT * FROM\n" + arg[2]
|
||||
stmt, _, err = db.Prepare(table.cell)
|
||||
t.cell = "SELECT * FROM\n" + arg[2]
|
||||
stmt, _, err = db.Prepare(t.cell)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -86,8 +86,8 @@ func declare(db *sqlite3.Conn, _, _, _ string, arg ...string) (_ *table, err err
|
||||
if stmt.ColumnCount() != 1 {
|
||||
return nil, util.ErrorString("pivot: cell query expects 1 result columns")
|
||||
}
|
||||
if stmt.BindCount() != len(table.keys)+1 {
|
||||
return nil, fmt.Errorf("pivot: cell query expects %d bound parameters", len(table.keys)+1)
|
||||
if stmt.BindCount() != len(t.keys)+1 {
|
||||
return nil, fmt.Errorf("pivot: cell query expects %d bound parameters", len(t.keys)+1)
|
||||
}
|
||||
|
||||
create.WriteByte(')')
|
||||
@@ -95,12 +95,12 @@ func declare(db *sqlite3.Conn, _, _, _ string, arg ...string) (_ *table, err err
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return table, nil
|
||||
return t, nil
|
||||
}
|
||||
|
||||
func (t *table) Close() error {
|
||||
for i := range t.cols {
|
||||
t.cols[i].Close()
|
||||
for _, c := range t.cols {
|
||||
c.Close()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ import (
|
||||
|
||||
// Register registers Unicode aware functions for a database connection.
|
||||
func Register(db *sqlite3.Conn) error {
|
||||
flags := sqlite3.DETERMINISTIC | sqlite3.INNOCUOUS
|
||||
const flags = sqlite3.DETERMINISTIC | sqlite3.INNOCUOUS
|
||||
return errors.Join(
|
||||
db.CreateFunction("regexp", 2, flags, regex),
|
||||
db.CreateFunction("regexp_like", 2, flags, regexLike),
|
||||
|
||||
@@ -34,7 +34,7 @@ func declare(db *sqlite3.Conn, _, _, _ string, arg ...string) (*table, error) {
|
||||
|
||||
sql := "SELECT * FROM\n" + arg[0]
|
||||
|
||||
stmt, _, err := db.Prepare(sql)
|
||||
stmt, _, err := db.PrepareFlags(sql, sqlite3.PREPARE_PERSISTENT)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -28,11 +28,13 @@ type percentile struct {
|
||||
}
|
||||
|
||||
func (q *percentile) Step(ctx sqlite3.Context, arg ...sqlite3.Value) {
|
||||
if a := arg[0]; a.NumericType() != sqlite3.NULL {
|
||||
q.nums = append(q.nums, a.Float())
|
||||
a := arg[0]
|
||||
f := a.Float()
|
||||
if f != 0.0 || a.NumericType() != sqlite3.NULL {
|
||||
q.nums = append(q.nums, f)
|
||||
}
|
||||
if q.kind != median {
|
||||
q.arg1 = append(q.arg1[:0], arg[1].RawText()...)
|
||||
if q.kind != median && q.arg1 == nil {
|
||||
q.arg1 = append(q.arg1, arg[1].RawText()...)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -52,7 +52,7 @@ import (
|
||||
|
||||
// Register registers statistics functions.
|
||||
func Register(db *sqlite3.Conn) error {
|
||||
flags := sqlite3.DETERMINISTIC | sqlite3.INNOCUOUS
|
||||
const flags = sqlite3.DETERMINISTIC | sqlite3.INNOCUOUS
|
||||
return errors.Join(
|
||||
db.CreateWindowFunction("var_pop", 1, flags, newVariance(var_pop)),
|
||||
db.CreateWindowFunction("var_samp", 1, flags, newVariance(var_samp)),
|
||||
@@ -121,14 +121,18 @@ func (fn *variance) Value(ctx sqlite3.Context) {
|
||||
}
|
||||
|
||||
func (fn *variance) Step(ctx sqlite3.Context, arg ...sqlite3.Value) {
|
||||
if a := arg[0]; a.NumericType() != sqlite3.NULL {
|
||||
fn.enqueue(a.Float())
|
||||
a := arg[0]
|
||||
f := a.Float()
|
||||
if f != 0.0 || a.NumericType() != sqlite3.NULL {
|
||||
fn.enqueue(f)
|
||||
}
|
||||
}
|
||||
|
||||
func (fn *variance) Inverse(ctx sqlite3.Context, arg ...sqlite3.Value) {
|
||||
if a := arg[0]; a.NumericType() != sqlite3.NULL {
|
||||
fn.dequeue(a.Float())
|
||||
a := arg[0]
|
||||
f := a.Float()
|
||||
if f != 0.0 || a.NumericType() != sqlite3.NULL {
|
||||
fn.dequeue(f)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -177,15 +181,23 @@ func (fn *covariance) Value(ctx sqlite3.Context) {
|
||||
}
|
||||
|
||||
func (fn *covariance) Step(ctx sqlite3.Context, arg ...sqlite3.Value) {
|
||||
a, b := arg[0], arg[1]
|
||||
if a.NumericType() != sqlite3.NULL && b.NumericType() != sqlite3.NULL {
|
||||
fn.enqueue(a.Float(), b.Float())
|
||||
b, a := arg[1], arg[0] // avoid a bounds check
|
||||
fa := a.Float()
|
||||
fb := b.Float()
|
||||
if true &&
|
||||
(fa != 0.0 || a.NumericType() != sqlite3.NULL) &&
|
||||
(fb != 0.0 || b.NumericType() != sqlite3.NULL) {
|
||||
fn.enqueue(fa, fb)
|
||||
}
|
||||
}
|
||||
|
||||
func (fn *covariance) Inverse(ctx sqlite3.Context, arg ...sqlite3.Value) {
|
||||
a, b := arg[0], arg[1]
|
||||
if a.NumericType() != sqlite3.NULL && b.NumericType() != sqlite3.NULL {
|
||||
fn.dequeue(a.Float(), b.Float())
|
||||
b, a := arg[1], arg[0] // avoid a bounds check
|
||||
fa := a.Float()
|
||||
fb := b.Float()
|
||||
if true &&
|
||||
(fa != 0.0 || a.NumericType() != sqlite3.NULL) &&
|
||||
(fb != 0.0 || b.NumericType() != sqlite3.NULL) {
|
||||
fn.dequeue(fa, fb)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,10 @@
|
||||
// - LIKE and REGEXP operators,
|
||||
// - collation sequences.
|
||||
//
|
||||
// It also provides, from PostgreSQL:
|
||||
// - unaccent(),
|
||||
// - initcap().
|
||||
//
|
||||
// The implementation 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;
|
||||
@@ -21,6 +25,7 @@ import (
|
||||
"errors"
|
||||
"regexp"
|
||||
"strings"
|
||||
"unicode"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/ncruces/go-sqlite3"
|
||||
@@ -28,19 +33,35 @@ import (
|
||||
"golang.org/x/text/cases"
|
||||
"golang.org/x/text/collate"
|
||||
"golang.org/x/text/language"
|
||||
"golang.org/x/text/runes"
|
||||
"golang.org/x/text/transform"
|
||||
"golang.org/x/text/unicode/norm"
|
||||
)
|
||||
|
||||
// Set RegisterLike to false to not register a Unicode aware LIKE operator.
|
||||
// Overriding the built-in LIKE operator disables the [LIKE optimization].
|
||||
//
|
||||
// [LIKE optimization]: https://sqlite.org/optoverview.html#the_like_optimization
|
||||
var RegisterLike = true
|
||||
|
||||
// Register registers Unicode aware functions for a database connection.
|
||||
func Register(db *sqlite3.Conn) error {
|
||||
flags := sqlite3.DETERMINISTIC | sqlite3.INNOCUOUS
|
||||
return errors.Join(
|
||||
db.CreateFunction("like", 2, flags, like),
|
||||
db.CreateFunction("like", 3, flags, like),
|
||||
const flags = sqlite3.DETERMINISTIC | sqlite3.INNOCUOUS
|
||||
var errs util.ErrorJoiner
|
||||
if RegisterLike {
|
||||
errs.Join(
|
||||
db.CreateFunction("like", 2, flags, like),
|
||||
db.CreateFunction("like", 3, flags, like))
|
||||
}
|
||||
errs.Join(
|
||||
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("initcap", 1, flags, initcap),
|
||||
db.CreateFunction("initcap", 2, flags, initcap),
|
||||
db.CreateFunction("unaccent", 1, flags, unaccent),
|
||||
db.CreateFunction("icu_load_collation", 2, sqlite3.DIRECTONLY,
|
||||
func(ctx sqlite3.Context, arg ...sqlite3.Value) {
|
||||
name := arg[1].Text()
|
||||
@@ -48,12 +69,13 @@ func Register(db *sqlite3.Conn) error {
|
||||
return
|
||||
}
|
||||
|
||||
err := RegisterCollation(db, arg[0].Text(), name)
|
||||
err := RegisterCollation(ctx.Conn(), arg[0].Text(), name)
|
||||
if err != nil {
|
||||
ctx.ResultError(err)
|
||||
return // notest
|
||||
}
|
||||
}))
|
||||
return errors.Join(errs...)
|
||||
}
|
||||
|
||||
// RegisterCollation registers a Unicode collation sequence for a database connection.
|
||||
@@ -65,6 +87,15 @@ func RegisterCollation(db *sqlite3.Conn, locale, name string) error {
|
||||
return db.CreateCollation(name, collate.New(tag).Compare)
|
||||
}
|
||||
|
||||
// RegisterCollationsNeeded registers Unicode collation sequences on demand for a database connection.
|
||||
func RegisterCollationsNeeded(db *sqlite3.Conn) error {
|
||||
return db.CollationNeeded(func(db *sqlite3.Conn, name string) {
|
||||
if tag, err := language.Parse(name); err == nil {
|
||||
db.CreateCollation(name, collate.New(tag).Compare)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func upper(ctx sqlite3.Context, arg ...sqlite3.Value) {
|
||||
if len(arg) == 1 {
|
||||
ctx.ResultRawText(bytes.ToUpper(arg[0].RawText()))
|
||||
@@ -103,6 +134,35 @@ func lower(ctx sqlite3.Context, arg ...sqlite3.Value) {
|
||||
ctx.ResultRawText(cs.Bytes(arg[0].RawText()))
|
||||
}
|
||||
|
||||
func initcap(ctx sqlite3.Context, arg ...sqlite3.Value) {
|
||||
if len(arg) == 1 {
|
||||
ctx.ResultRawText(bytes.Title(arg[0].RawText()))
|
||||
return
|
||||
}
|
||||
cs, ok := ctx.GetAuxData(1).(cases.Caser)
|
||||
if !ok {
|
||||
t, err := language.Parse(arg[1].Text())
|
||||
if err != nil {
|
||||
ctx.ResultError(err)
|
||||
return // notest
|
||||
}
|
||||
c := cases.Title(t)
|
||||
ctx.SetAuxData(1, c)
|
||||
cs = c
|
||||
}
|
||||
ctx.ResultRawText(cs.Bytes(arg[0].RawText()))
|
||||
}
|
||||
|
||||
func unaccent(ctx sqlite3.Context, arg ...sqlite3.Value) {
|
||||
unaccent := transform.Chain(norm.NFD, runes.Remove(runes.In(unicode.Mn)), norm.NFC)
|
||||
res, _, err := transform.Bytes(unaccent, arg[0].RawText())
|
||||
if err != nil {
|
||||
ctx.ResultError(err) // notest
|
||||
} else {
|
||||
ctx.ResultRawText(res)
|
||||
}
|
||||
}
|
||||
|
||||
func regex(ctx sqlite3.Context, arg ...sqlite3.Value) {
|
||||
re, ok := ctx.GetAuxData(0).(*regexp.Regexp)
|
||||
if !ok {
|
||||
|
||||
@@ -47,6 +47,9 @@ func TestRegister(t *testing.T) {
|
||||
{`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"},
|
||||
{`initcap('Kad je hladno Marko nosi džemper')`, "Kad Je Hladno Marko Nosi Džemper"},
|
||||
{`initcap('Kad je hladno Marko nosi džemper', 'hr-HR')`, "Kad Je Hladno Marko Nosi Džemper"},
|
||||
{`unaccent('Hôtel')`, "Hotel"},
|
||||
{`'Hello' REGEXP 'ell'`, "1"},
|
||||
{`'Hello' REGEXP 'el.'`, "1"},
|
||||
{`'Hello' LIKE 'hel_'`, "0"},
|
||||
@@ -92,7 +95,7 @@ func TestRegister_collation(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = db.Exec(`SELECT icu_load_collation('fr_FR', 'french')`)
|
||||
err = db.Exec(`SELECT icu_load_collation('fr-FR', 'french')`)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -127,6 +130,57 @@ func TestRegister_collation(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestRegisterCollationsNeeded(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
db, err := sqlite3.Open(":memory:")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
RegisterCollationsNeeded(db)
|
||||
|
||||
err = db.Exec(`CREATE TABLE 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)
|
||||
}
|
||||
|
||||
stmt, _, err := db.Prepare(`SELECT word FROM words ORDER BY word COLLATE fr_FR`)
|
||||
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()
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ import (
|
||||
//
|
||||
// Converts a UUID into a 16-byte blob.
|
||||
func Register(db *sqlite3.Conn) error {
|
||||
flags := sqlite3.DETERMINISTIC | sqlite3.INNOCUOUS
|
||||
const flags = sqlite3.DETERMINISTIC | sqlite3.INNOCUOUS
|
||||
return errors.Join(
|
||||
db.CreateFunction("uuid", 0, sqlite3.INNOCUOUS, generate),
|
||||
db.CreateFunction("uuid", 1, sqlite3.INNOCUOUS, generate),
|
||||
|
||||
@@ -12,7 +12,7 @@ import (
|
||||
|
||||
// Register registers the zorder and unzorder SQL functions.
|
||||
func Register(db *sqlite3.Conn) error {
|
||||
flags := sqlite3.DETERMINISTIC | sqlite3.INNOCUOUS
|
||||
const flags = sqlite3.DETERMINISTIC | sqlite3.INNOCUOUS
|
||||
return errors.Join(
|
||||
db.CreateFunction("zorder", -1, flags, zorder),
|
||||
db.CreateFunction("unzorder", 3, flags, unzorder))
|
||||
@@ -47,9 +47,9 @@ func zorder(ctx sqlite3.Context, arg ...sqlite3.Value) {
|
||||
}
|
||||
|
||||
func unzorder(ctx sqlite3.Context, arg ...sqlite3.Value) {
|
||||
z := arg[0].Int64()
|
||||
n := arg[1].Int64()
|
||||
i := arg[2].Int64()
|
||||
n := arg[1].Int64()
|
||||
z := arg[0].Int64()
|
||||
|
||||
var k int
|
||||
var x int64
|
||||
|
||||
28
func.go
28
func.go
@@ -33,16 +33,23 @@ func (c *Conn) CollationNeeded(cb func(db *Conn, name string)) error {
|
||||
// one or more unknown collating sequences.
|
||||
func (c Conn) AnyCollationNeeded() error {
|
||||
r := c.call("sqlite3_anycollseq_init", uint64(c.handle), 0, 0)
|
||||
return c.error(r)
|
||||
if err := c.error(r); err != nil {
|
||||
return err
|
||||
}
|
||||
c.collation = nil
|
||||
return nil
|
||||
}
|
||||
|
||||
// CreateCollation defines a new collating sequence.
|
||||
//
|
||||
// https://sqlite.org/c3ref/create_collation.html
|
||||
func (c *Conn) CreateCollation(name string, fn func(a, b []byte) int) error {
|
||||
var funcPtr uint32
|
||||
defer c.arena.mark()()
|
||||
namePtr := c.arena.string(name)
|
||||
funcPtr := util.AddHandle(c.ctx, fn)
|
||||
if fn != nil {
|
||||
funcPtr = util.AddHandle(c.ctx, fn)
|
||||
}
|
||||
r := c.call("sqlite3_create_collation_go",
|
||||
uint64(c.handle), uint64(namePtr), uint64(funcPtr))
|
||||
return c.error(r)
|
||||
@@ -52,9 +59,12 @@ func (c *Conn) CreateCollation(name string, fn func(a, b []byte) int) error {
|
||||
//
|
||||
// https://sqlite.org/c3ref/create_function.html
|
||||
func (c *Conn) CreateFunction(name string, nArg int, flag FunctionFlag, fn ScalarFunction) error {
|
||||
var funcPtr uint32
|
||||
defer c.arena.mark()()
|
||||
namePtr := c.arena.string(name)
|
||||
funcPtr := util.AddHandle(c.ctx, fn)
|
||||
if fn != nil {
|
||||
funcPtr = util.AddHandle(c.ctx, fn)
|
||||
}
|
||||
r := c.call("sqlite3_create_function_go",
|
||||
uint64(c.handle), uint64(namePtr), uint64(nArg),
|
||||
uint64(flag), uint64(funcPtr))
|
||||
@@ -71,10 +81,13 @@ type ScalarFunction func(ctx Context, arg ...Value)
|
||||
//
|
||||
// https://sqlite.org/c3ref/create_function.html
|
||||
func (c *Conn) CreateWindowFunction(name string, nArg int, flag FunctionFlag, fn func() AggregateFunction) error {
|
||||
var funcPtr uint32
|
||||
defer c.arena.mark()()
|
||||
call := "sqlite3_create_aggregate_function_go"
|
||||
namePtr := c.arena.string(name)
|
||||
funcPtr := util.AddHandle(c.ctx, fn)
|
||||
if fn != nil {
|
||||
funcPtr = util.AddHandle(c.ctx, fn)
|
||||
}
|
||||
call := "sqlite3_create_aggregate_function_go"
|
||||
if _, ok := fn().(WindowFunction); ok {
|
||||
call = "sqlite3_create_window_function_go"
|
||||
}
|
||||
@@ -184,11 +197,12 @@ func callbackAggregate(db *Conn, pAgg, pApp uint32) (AggregateFunction, uint32)
|
||||
|
||||
// We need to create the aggregate.
|
||||
fn := util.GetHandle(db.ctx, pApp).(func() AggregateFunction)()
|
||||
handle := util.AddHandle(db.ctx, fn)
|
||||
if pAgg != 0 {
|
||||
handle := util.AddHandle(db.ctx, fn)
|
||||
util.WriteUint32(db.mod, pAgg, handle)
|
||||
return fn, handle
|
||||
}
|
||||
return fn, handle
|
||||
return fn, 0
|
||||
}
|
||||
|
||||
func callbackArgs(db *Conn, arg []Value, pArg uint32) {
|
||||
|
||||
8
go.mod
8
go.mod
@@ -10,11 +10,11 @@ require (
|
||||
github.com/ncruces/julianday v1.0.0
|
||||
github.com/ncruces/sort v0.1.2
|
||||
github.com/psanford/httpreadat v0.1.0
|
||||
github.com/tetratelabs/wazero v1.8.0
|
||||
golang.org/x/crypto v0.27.0
|
||||
github.com/tetratelabs/wazero v1.8.1
|
||||
golang.org/x/crypto v0.28.0
|
||||
golang.org/x/sync v0.8.0
|
||||
golang.org/x/sys v0.25.0
|
||||
golang.org/x/text v0.18.0
|
||||
golang.org/x/sys v0.26.0
|
||||
golang.org/x/text v0.19.0
|
||||
lukechampine.com/adiantum v1.1.1
|
||||
)
|
||||
|
||||
|
||||
16
go.sum
16
go.sum
@@ -8,15 +8,15 @@ github.com/ncruces/sort v0.1.2 h1:zKQ9CA4fpHPF6xsUhRTfi5EEryspuBpe/QA4VWQOV1U=
|
||||
github.com/ncruces/sort v0.1.2/go.mod h1:vEJUTBJtebIuCMmXD18GKo5GJGhsay+xZFOoBEIXFmE=
|
||||
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.8.0 h1:iEKu0d4c2Pd+QSRieYbnQC9yiFlMS9D+Jr0LsRmcF4g=
|
||||
github.com/tetratelabs/wazero v1.8.0/go.mod h1:yAI0XTsMBhREkM/YDAK/zNou3GoiAce1P6+rp/wQhjs=
|
||||
golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A=
|
||||
golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70=
|
||||
github.com/tetratelabs/wazero v1.8.1 h1:NrcgVbWfkWvVc4UtT4LRLDf91PsOzDzefMdwhLfA550=
|
||||
github.com/tetratelabs/wazero v1.8.1/go.mod h1:yAI0XTsMBhREkM/YDAK/zNou3GoiAce1P6+rp/wQhjs=
|
||||
golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw=
|
||||
golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U=
|
||||
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
|
||||
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34=
|
||||
golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224=
|
||||
golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
|
||||
golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo=
|
||||
golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM=
|
||||
golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
|
||||
lukechampine.com/adiantum v1.1.1 h1:4fp6gTxWCqpEbLy40ExiYDDED3oUNWx5cTqBCtPdZqA=
|
||||
lukechampine.com/adiantum v1.1.1/go.mod h1:LrAYVnTYLnUtE/yMp5bQr0HstAf060YUF8nM0B6+rUw=
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
|
||||
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
|
||||
golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
|
||||
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
|
||||
golang.org/x/term v0.21.0/go.mod h1:ooXLefLobQVslOqselCNF4SxFAaoS6KujMbsGzSDmX0=
|
||||
golang.org/x/term v0.22.0/go.mod h1:F3qCibpT5AMpCRfhfT53vVJwhLtIVHhB9XDjfFvnMI4=
|
||||
golang.org/x/term v0.23.0/go.mod h1:DgV24QBUrK6jhZXl+20l6UWznPlwAHm1Q1mGHtydmSk=
|
||||
golang.org/x/term v0.24.0/go.mod h1:lOBK/LVxemqiMij05LGJ0tzNr8xlmwBRJ81PX6wVLH8=
|
||||
golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M=
|
||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
|
||||
|
||||
@@ -5,7 +5,7 @@ go 1.21
|
||||
toolchain go1.23.0
|
||||
|
||||
require (
|
||||
github.com/ncruces/go-sqlite3 v0.18.3
|
||||
github.com/ncruces/go-sqlite3 v0.18.4
|
||||
gorm.io/gorm v1.25.12
|
||||
)
|
||||
|
||||
@@ -13,7 +13,7 @@ require (
|
||||
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||
github.com/jinzhu/now v1.1.5 // indirect
|
||||
github.com/ncruces/julianday v1.0.0 // indirect
|
||||
github.com/tetratelabs/wazero v1.8.0 // indirect
|
||||
golang.org/x/sys v0.25.0 // indirect
|
||||
golang.org/x/text v0.18.0 // indirect
|
||||
github.com/tetratelabs/wazero v1.8.1 // indirect
|
||||
golang.org/x/sys v0.26.0 // indirect
|
||||
golang.org/x/text v0.19.0 // indirect
|
||||
)
|
||||
|
||||
@@ -2,15 +2,15 @@ github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD
|
||||
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
|
||||
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
|
||||
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
||||
github.com/ncruces/go-sqlite3 v0.18.3 h1:tyMa75uh7LcINcfo0WrzOvcTkfz8Hqu0TEPX+KVyes4=
|
||||
github.com/ncruces/go-sqlite3 v0.18.3/go.mod h1:HAwOtA+cyEX3iN6YmkpQwfT4vMMgCB7rQRFUdOgEFik=
|
||||
github.com/ncruces/go-sqlite3 v0.18.4 h1:Je8o3y33MDwPYY/Cacas8yCsuoUzpNY/AgoSlN2ekyE=
|
||||
github.com/ncruces/go-sqlite3 v0.18.4/go.mod h1:4HLag13gq1k10s4dfGBhMfRVsssJRT9/5hYqVM9RUYo=
|
||||
github.com/ncruces/julianday v1.0.0 h1:fH0OKwa7NWvniGQtxdJRxAgkBMolni2BjDHaWTxqt7M=
|
||||
github.com/ncruces/julianday v1.0.0/go.mod h1:Dusn2KvZrrovOMJuOt0TNXL6tB7U2E8kvza5fFc9G7g=
|
||||
github.com/tetratelabs/wazero v1.8.0 h1:iEKu0d4c2Pd+QSRieYbnQC9yiFlMS9D+Jr0LsRmcF4g=
|
||||
github.com/tetratelabs/wazero v1.8.0/go.mod h1:yAI0XTsMBhREkM/YDAK/zNou3GoiAce1P6+rp/wQhjs=
|
||||
golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34=
|
||||
golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224=
|
||||
golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
|
||||
github.com/tetratelabs/wazero v1.8.1 h1:NrcgVbWfkWvVc4UtT4LRLDf91PsOzDzefMdwhLfA550=
|
||||
github.com/tetratelabs/wazero v1.8.1/go.mod h1:yAI0XTsMBhREkM/YDAK/zNou3GoiAce1P6+rp/wQhjs=
|
||||
golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo=
|
||||
golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM=
|
||||
golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
|
||||
gorm.io/gorm v1.25.12 h1:I0u8i2hWQItBq1WfE0o2+WuL9+8L21K9e2HHSTE/0f8=
|
||||
gorm.io/gorm v1.25.12/go.mod h1:xh7N7RHfYlNc5EmcI/El95gXusucDrQnHXe0+CgWcLQ=
|
||||
|
||||
@@ -47,7 +47,7 @@ func (m *mmappedMemory) Reallocate(size uint64) []byte {
|
||||
// Commit additional memory up to new bytes.
|
||||
err := unix.Mprotect(m.buf[com:new], unix.PROT_READ|unix.PROT_WRITE)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Update committed memory.
|
||||
|
||||
@@ -56,7 +56,7 @@ func (m *virtualMemory) Reallocate(size uint64) []byte {
|
||||
// Commit additional memory up to new bytes.
|
||||
_, err := windows.VirtualAlloc(m.addr, uintptr(new), windows.MEM_COMMIT, windows.PAGE_READWRITE)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Update committed memory.
|
||||
|
||||
@@ -26,6 +26,7 @@ func ExportFuncVI[T0 i32](mod wazero.HostModuleBuilder, name string, fn func(con
|
||||
type funcVII[T0, T1 i32] func(context.Context, api.Module, T0, T1)
|
||||
|
||||
func (fn funcVII[T0, T1]) Call(ctx context.Context, mod api.Module, stack []uint64) {
|
||||
_ = stack[1] // prevent bounds check on every slice access
|
||||
fn(ctx, mod, T0(stack[0]), T1(stack[1]))
|
||||
}
|
||||
|
||||
@@ -39,6 +40,7 @@ func ExportFuncVII[T0, T1 i32](mod wazero.HostModuleBuilder, name string, fn fun
|
||||
type funcVIII[T0, T1, T2 i32] func(context.Context, api.Module, T0, T1, T2)
|
||||
|
||||
func (fn funcVIII[T0, T1, T2]) Call(ctx context.Context, mod api.Module, stack []uint64) {
|
||||
_ = stack[2] // prevent bounds check on every slice access
|
||||
fn(ctx, mod, T0(stack[0]), T1(stack[1]), T2(stack[2]))
|
||||
}
|
||||
|
||||
@@ -52,6 +54,7 @@ func ExportFuncVIII[T0, T1, T2 i32](mod wazero.HostModuleBuilder, name string, f
|
||||
type funcVIIII[T0, T1, T2, T3 i32] func(context.Context, api.Module, T0, T1, T2, T3)
|
||||
|
||||
func (fn funcVIIII[T0, T1, T2, T3]) Call(ctx context.Context, mod api.Module, stack []uint64) {
|
||||
_ = stack[3] // prevent bounds check on every slice access
|
||||
fn(ctx, mod, T0(stack[0]), T1(stack[1]), T2(stack[2]), T3(stack[3]))
|
||||
}
|
||||
|
||||
@@ -65,6 +68,7 @@ func ExportFuncVIIII[T0, T1, T2, T3 i32](mod wazero.HostModuleBuilder, name stri
|
||||
type funcVIIIII[T0, T1, T2, T3, T4 i32] func(context.Context, api.Module, T0, T1, T2, T3, T4)
|
||||
|
||||
func (fn funcVIIIII[T0, T1, T2, T3, T4]) Call(ctx context.Context, mod api.Module, stack []uint64) {
|
||||
_ = stack[4] // prevent bounds check on every slice access
|
||||
fn(ctx, mod, T0(stack[0]), T1(stack[1]), T2(stack[2]), T3(stack[3]), T4(stack[4]))
|
||||
}
|
||||
|
||||
@@ -78,6 +82,7 @@ func ExportFuncVIIIII[T0, T1, T2, T3, T4 i32](mod wazero.HostModuleBuilder, name
|
||||
type funcVIIIIJ[T0, T1, T2, T3 i32, T4 i64] func(context.Context, api.Module, T0, T1, T2, T3, T4)
|
||||
|
||||
func (fn funcVIIIIJ[T0, T1, T2, T3, T4]) Call(ctx context.Context, mod api.Module, stack []uint64) {
|
||||
_ = stack[4] // prevent bounds check on every slice access
|
||||
fn(ctx, mod, T0(stack[0]), T1(stack[1]), T2(stack[2]), T3(stack[3]), T4(stack[4]))
|
||||
}
|
||||
|
||||
@@ -104,6 +109,7 @@ func ExportFuncII[TR, T0 i32](mod wazero.HostModuleBuilder, name string, fn func
|
||||
type funcIII[TR, T0, T1 i32] func(context.Context, api.Module, T0, T1) TR
|
||||
|
||||
func (fn funcIII[TR, T0, T1]) Call(ctx context.Context, mod api.Module, stack []uint64) {
|
||||
_ = stack[1] // prevent bounds check on every slice access
|
||||
stack[0] = uint64(fn(ctx, mod, T0(stack[0]), T1(stack[1])))
|
||||
}
|
||||
|
||||
@@ -117,6 +123,7 @@ func ExportFuncIII[TR, T0, T1 i32](mod wazero.HostModuleBuilder, name string, fn
|
||||
type funcIIII[TR, T0, T1, T2 i32] func(context.Context, api.Module, T0, T1, T2) TR
|
||||
|
||||
func (fn funcIIII[TR, T0, T1, T2]) Call(ctx context.Context, mod api.Module, stack []uint64) {
|
||||
_ = stack[2] // prevent bounds check on every slice access
|
||||
stack[0] = uint64(fn(ctx, mod, T0(stack[0]), T1(stack[1]), T2(stack[2])))
|
||||
}
|
||||
|
||||
@@ -130,6 +137,7 @@ func ExportFuncIIII[TR, T0, T1, T2 i32](mod wazero.HostModuleBuilder, name strin
|
||||
type funcIIIII[TR, T0, T1, T2, T3 i32] func(context.Context, api.Module, T0, T1, T2, T3) TR
|
||||
|
||||
func (fn funcIIIII[TR, T0, T1, T2, T3]) Call(ctx context.Context, mod api.Module, stack []uint64) {
|
||||
_ = stack[3] // prevent bounds check on every slice access
|
||||
stack[0] = uint64(fn(ctx, mod, T0(stack[0]), T1(stack[1]), T2(stack[2]), T3(stack[3])))
|
||||
}
|
||||
|
||||
@@ -143,6 +151,7 @@ func ExportFuncIIIII[TR, T0, T1, T2, T3 i32](mod wazero.HostModuleBuilder, name
|
||||
type funcIIIIII[TR, T0, T1, T2, T3, T4 i32] func(context.Context, api.Module, T0, T1, T2, T3, T4) TR
|
||||
|
||||
func (fn funcIIIIII[TR, T0, T1, T2, T3, T4]) Call(ctx context.Context, mod api.Module, stack []uint64) {
|
||||
_ = stack[4] // prevent bounds check on every slice access
|
||||
stack[0] = uint64(fn(ctx, mod, T0(stack[0]), T1(stack[1]), T2(stack[2]), T3(stack[3]), T4(stack[4])))
|
||||
}
|
||||
|
||||
@@ -156,6 +165,7 @@ func ExportFuncIIIIII[TR, T0, T1, T2, T3, T4 i32](mod wazero.HostModuleBuilder,
|
||||
type funcIIIIIII[TR, T0, T1, T2, T3, T4, T5 i32] func(context.Context, api.Module, T0, T1, T2, T3, T4, T5) TR
|
||||
|
||||
func (fn funcIIIIIII[TR, T0, T1, T2, T3, T4, T5]) Call(ctx context.Context, mod api.Module, stack []uint64) {
|
||||
_ = stack[5] // prevent bounds check on every slice access
|
||||
stack[0] = uint64(fn(ctx, mod, T0(stack[0]), T1(stack[1]), T2(stack[2]), T3(stack[3]), T4(stack[4]), T5(stack[5])))
|
||||
}
|
||||
|
||||
@@ -169,6 +179,7 @@ func ExportFuncIIIIIII[TR, T0, T1, T2, T3, T4, T5 i32](mod wazero.HostModuleBuil
|
||||
type funcIIIIJ[TR, T0, T1, T2 i32, T3 i64] func(context.Context, api.Module, T0, T1, T2, T3) TR
|
||||
|
||||
func (fn funcIIIIJ[TR, T0, T1, T2, T3]) Call(ctx context.Context, mod api.Module, stack []uint64) {
|
||||
_ = stack[3] // prevent bounds check on every slice access
|
||||
stack[0] = uint64(fn(ctx, mod, T0(stack[0]), T1(stack[1]), T2(stack[2]), T3(stack[3])))
|
||||
}
|
||||
|
||||
@@ -182,6 +193,7 @@ func ExportFuncIIIIJ[TR, T0, T1, T2 i32, T3 i64](mod wazero.HostModuleBuilder, n
|
||||
type funcIIJ[TR, T0 i32, T1 i64] func(context.Context, api.Module, T0, T1) TR
|
||||
|
||||
func (fn funcIIJ[TR, T0, T1]) Call(ctx context.Context, mod api.Module, stack []uint64) {
|
||||
_ = stack[1] // prevent bounds check on every slice access
|
||||
stack[0] = uint64(fn(ctx, mod, T0(stack[0]), T1(stack[1])))
|
||||
}
|
||||
|
||||
|
||||
@@ -35,17 +35,22 @@ func DelHandle(ctx context.Context, id uint32) error {
|
||||
s := ctx.Value(moduleKey{}).(*moduleState)
|
||||
a := s.handles[^id]
|
||||
s.handles[^id] = nil
|
||||
s.holes++
|
||||
if l := uint32(len(s.handles)); l == ^id {
|
||||
s.handles = s.handles[:l-1]
|
||||
} else {
|
||||
s.holes++
|
||||
}
|
||||
if c, ok := a.(io.Closer); ok {
|
||||
return c.Close()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func AddHandle(ctx context.Context, a any) (id uint32) {
|
||||
func AddHandle(ctx context.Context, a any) uint32 {
|
||||
if a == nil {
|
||||
panic(NilErr)
|
||||
}
|
||||
|
||||
s := ctx.Value(moduleKey{}).(*moduleState)
|
||||
|
||||
// Find an empty slot.
|
||||
|
||||
50
quote.go
50
quote.go
@@ -3,6 +3,7 @@ package sqlite3
|
||||
import (
|
||||
"bytes"
|
||||
"math"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
@@ -13,6 +14,9 @@ import (
|
||||
|
||||
// Quote escapes and quotes a value
|
||||
// making it safe to embed in SQL text.
|
||||
// Strings with embedded NUL characters are truncated.
|
||||
//
|
||||
// https://sqlite.org/lang_corefunc.html#quote
|
||||
func Quote(value any) string {
|
||||
switch v := value.(type) {
|
||||
case nil:
|
||||
@@ -42,8 +46,8 @@ func Quote(value any) string {
|
||||
return "'" + v.Format(time.RFC3339Nano) + "'"
|
||||
|
||||
case string:
|
||||
if strings.IndexByte(v, 0) >= 0 {
|
||||
break
|
||||
if i := strings.IndexByte(v, 0); i >= 0 {
|
||||
v = v[:i]
|
||||
}
|
||||
|
||||
buf := make([]byte, 2+len(v)+strings.Count(v, "'"))
|
||||
@@ -57,13 +61,13 @@ func Quote(value any) string {
|
||||
buf[i] = b
|
||||
i += 1
|
||||
}
|
||||
buf[i] = '\''
|
||||
buf[len(buf)-1] = '\''
|
||||
return unsafe.String(&buf[0], len(buf))
|
||||
|
||||
case []byte:
|
||||
buf := make([]byte, 3+2*len(v))
|
||||
buf[0] = 'x'
|
||||
buf[1] = '\''
|
||||
buf[0] = 'x'
|
||||
i := 2
|
||||
for _, b := range v {
|
||||
const hex = "0123456789ABCDEF"
|
||||
@@ -71,26 +75,50 @@ func Quote(value any) string {
|
||||
buf[i+1] = hex[b%16]
|
||||
i += 2
|
||||
}
|
||||
buf[i] = '\''
|
||||
buf[len(buf)-1] = '\''
|
||||
return unsafe.String(&buf[0], len(buf))
|
||||
|
||||
case ZeroBlob:
|
||||
if v > ZeroBlob(1e9-3)/2 {
|
||||
break
|
||||
}
|
||||
|
||||
buf := bytes.Repeat([]byte("0"), int(3+2*int64(v)))
|
||||
buf[0] = 'x'
|
||||
buf[1] = '\''
|
||||
buf[0] = 'x'
|
||||
buf[len(buf)-1] = '\''
|
||||
return unsafe.String(&buf[0], len(buf))
|
||||
}
|
||||
|
||||
v := reflect.ValueOf(value)
|
||||
k := v.Kind()
|
||||
|
||||
if k == reflect.Interface || k == reflect.Pointer {
|
||||
if v.IsNil() {
|
||||
return "NULL"
|
||||
}
|
||||
v = v.Elem()
|
||||
k = v.Kind()
|
||||
}
|
||||
|
||||
switch {
|
||||
case v.CanInt():
|
||||
return strconv.FormatInt(v.Int(), 10)
|
||||
case v.CanUint():
|
||||
return strconv.FormatUint(v.Uint(), 10)
|
||||
case v.CanFloat():
|
||||
return Quote(v.Float())
|
||||
case k == reflect.Bool:
|
||||
return Quote(v.Bool())
|
||||
case k == reflect.String:
|
||||
return Quote(v.String())
|
||||
case (k == reflect.Slice || k == reflect.Array && v.CanAddr()) &&
|
||||
v.Type().Elem().Kind() == reflect.Uint8:
|
||||
return Quote(v.Bytes())
|
||||
}
|
||||
|
||||
panic(util.ValueErr)
|
||||
}
|
||||
|
||||
// QuoteIdentifier escapes and quotes an identifier
|
||||
// making it safe to embed in SQL text.
|
||||
// Strings with embedded NUL characters panic.
|
||||
func QuoteIdentifier(id string) string {
|
||||
if strings.IndexByte(id, 0) >= 0 {
|
||||
panic(util.ValueErr)
|
||||
@@ -107,6 +135,6 @@ func QuoteIdentifier(id string) string {
|
||||
buf[i] = b
|
||||
i += 1
|
||||
}
|
||||
buf[i] = '"'
|
||||
buf[len(buf)-1] = '"'
|
||||
return unsafe.String(&buf[0], len(buf))
|
||||
}
|
||||
|
||||
@@ -131,7 +131,7 @@ func (sqlt *sqlite) error(rc uint64, handle uint32, sql ...string) error {
|
||||
err.msg = util.ReadString(sqlt.mod, uint32(r), _MAX_LENGTH)
|
||||
}
|
||||
|
||||
if sql != nil {
|
||||
if len(sql) != 0 {
|
||||
if r := sqlt.call("sqlite3_error_offset", uint64(handle)); r != math.MaxUint32 {
|
||||
err.sql = sql[0][r:]
|
||||
}
|
||||
@@ -301,7 +301,7 @@ func (a *arena) string(s string) uint32 {
|
||||
|
||||
func exportCallbacks(env wazero.HostModuleBuilder) wazero.HostModuleBuilder {
|
||||
util.ExportFuncII(env, "go_progress_handler", progressCallback)
|
||||
util.ExportFuncIIII(env, "go_busy_timeout", timeoutCallback)
|
||||
util.ExportFuncIII(env, "go_busy_timeout", timeoutCallback)
|
||||
util.ExportFuncIII(env, "go_busy_handler", busyCallback)
|
||||
util.ExportFuncII(env, "go_commit_hook", commitCallback)
|
||||
util.ExportFuncVI(env, "go_rollback_hook", rollbackCallback)
|
||||
|
||||
@@ -2,12 +2,12 @@
|
||||
|
||||
#include "sqlite3.h"
|
||||
|
||||
int sqlite3_bind_text_go(sqlite3_stmt* stmt, int i, const char* zData,
|
||||
int sqlite3_bind_text_go(sqlite3_stmt *stmt, int i, const char *zData,
|
||||
sqlite3_uint64 nData) {
|
||||
return sqlite3_bind_text64(stmt, i, zData, nData, &sqlite3_free, SQLITE_UTF8);
|
||||
}
|
||||
|
||||
int sqlite3_bind_blob_go(sqlite3_stmt* stmt, int i, const char* zData,
|
||||
int sqlite3_bind_blob_go(sqlite3_stmt *stmt, int i, const char *zData,
|
||||
sqlite3_uint64 nData) {
|
||||
return sqlite3_bind_blob64(stmt, i, zData, nData, &sqlite3_free);
|
||||
}
|
||||
@@ -22,6 +22,7 @@ int sqlite3_columns_go(sqlite3_stmt *stmt, int nCol, char *aType,
|
||||
switch (aType[i] = sqlite3_column_type(stmt, i)) {
|
||||
default: // SQLITE_NULL
|
||||
aData[i] = (union sqlite3_data){};
|
||||
continue;
|
||||
case SQLITE_INTEGER:
|
||||
aData[i].i = sqlite3_column_int64(stmt, i);
|
||||
continue;
|
||||
|
||||
@@ -18,6 +18,7 @@ curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.46.1/ext/misc/decimal.
|
||||
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.46.1/ext/misc/ieee754.c"
|
||||
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.46.1/ext/misc/regexp.c"
|
||||
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.46.1/ext/misc/series.c"
|
||||
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.46.1/ext/misc/spellfix.c"
|
||||
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.46.1/ext/misc/uint.c"
|
||||
cd ~-
|
||||
|
||||
|
||||
@@ -53,6 +53,9 @@ int sqlite3_collation_needed_go(sqlite3 *db, bool enable) {
|
||||
}
|
||||
|
||||
int sqlite3_create_collation_go(sqlite3 *db, const char *name, go_handle app) {
|
||||
if (app == NULL) {
|
||||
return sqlite3_create_collation_v2(db, name, SQLITE_UTF8, NULL, NULL, NULL);
|
||||
}
|
||||
int rc = sqlite3_create_collation_v2(db, name, SQLITE_UTF8, app, go_compare,
|
||||
go_destroy);
|
||||
if (rc) go_destroy(app);
|
||||
@@ -61,6 +64,10 @@ int sqlite3_create_collation_go(sqlite3 *db, const char *name, go_handle app) {
|
||||
|
||||
int sqlite3_create_function_go(sqlite3 *db, const char *name, int argc,
|
||||
int flags, go_handle app) {
|
||||
if (app == NULL) {
|
||||
return sqlite3_create_function_v2(db, name, argc, SQLITE_UTF8 | flags, NULL,
|
||||
NULL, NULL, NULL, NULL);
|
||||
}
|
||||
return sqlite3_create_function_v2(db, name, argc, SQLITE_UTF8 | flags, app,
|
||||
go_func_wrapper, /*step=*/NULL,
|
||||
/*final=*/NULL, go_destroy);
|
||||
@@ -68,6 +75,10 @@ int sqlite3_create_function_go(sqlite3 *db, const char *name, int argc,
|
||||
|
||||
int sqlite3_create_aggregate_function_go(sqlite3 *db, const char *name,
|
||||
int argc, int flags, go_handle app) {
|
||||
if (app == NULL) {
|
||||
return sqlite3_create_function_v2(db, name, argc, SQLITE_UTF8 | flags, NULL,
|
||||
NULL, NULL, NULL, NULL);
|
||||
}
|
||||
return sqlite3_create_function_v2(db, name, argc, SQLITE_UTF8 | flags, app,
|
||||
/*func=*/NULL, go_step_wrapper,
|
||||
go_final_wrapper, go_destroy);
|
||||
@@ -75,6 +86,10 @@ int sqlite3_create_aggregate_function_go(sqlite3 *db, const char *name,
|
||||
|
||||
int sqlite3_create_window_function_go(sqlite3 *db, const char *name, int argc,
|
||||
int flags, go_handle app) {
|
||||
if (app == NULL) {
|
||||
return sqlite3_create_window_function(db, name, argc, SQLITE_UTF8 | flags,
|
||||
NULL, NULL, NULL, NULL, NULL, NULL);
|
||||
}
|
||||
return sqlite3_create_window_function(
|
||||
db, name, argc, SQLITE_UTF8 | flags, app, go_step_wrapper,
|
||||
go_final_wrapper, go_value_wrapper, go_inverse_wrapper, go_destroy);
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
int go_progress_handler(void *);
|
||||
int go_busy_handler(void *, int);
|
||||
int go_busy_timeout(void *, int count, int tmout);
|
||||
int go_busy_timeout(int count, int tmout);
|
||||
|
||||
int go_commit_hook(void *);
|
||||
void go_rollback_hook(void *);
|
||||
@@ -20,7 +20,7 @@ unsigned int go_autovacuum_pages(void *, const char *, unsigned int,
|
||||
unsigned int, unsigned int);
|
||||
|
||||
void sqlite3_progress_handler_go(sqlite3 *db, int n) {
|
||||
sqlite3_progress_handler(db, n, go_progress_handler, /*arg=*/db);
|
||||
sqlite3_progress_handler(db, n, go_progress_handler, /*arg=*/NULL);
|
||||
}
|
||||
|
||||
int sqlite3_busy_handler_go(sqlite3 *db, bool enable) {
|
||||
@@ -57,15 +57,16 @@ int sqlite3_config_log_go(bool enable) {
|
||||
}
|
||||
|
||||
int sqlite3_autovacuum_pages_go(sqlite3 *db, go_handle app) {
|
||||
int rc = sqlite3_autovacuum_pages(db, go_autovacuum_pages, app, go_destroy);
|
||||
if (rc) go_destroy(app);
|
||||
return rc;
|
||||
if (app == NULL) {
|
||||
return sqlite3_autovacuum_pages(db, NULL, NULL, NULL);
|
||||
}
|
||||
return sqlite3_autovacuum_pages(db, go_autovacuum_pages, app, go_destroy);
|
||||
}
|
||||
|
||||
#ifndef sqliteBusyCallback
|
||||
|
||||
static int sqliteBusyCallback(sqlite3 *db, int count) {
|
||||
return go_busy_timeout(db, count, db->busyTimeout);
|
||||
return go_busy_timeout(count, db->busyTimeout);
|
||||
}
|
||||
|
||||
#endif
|
||||
29
sqlite3/long_double.patch
Normal file
29
sqlite3/long_double.patch
Normal file
@@ -0,0 +1,29 @@
|
||||
--- sqlite3.c.orig
|
||||
+++ sqlite3.c
|
||||
@@ -35561,7 +35561,7 @@
|
||||
|
||||
if( e==0 ){
|
||||
*pResult = s;
|
||||
- }else if( sqlite3Config.bUseLongDouble ){
|
||||
+ }else if( sizeof(LONGDOUBLE_TYPE)>8 && sqlite3Config.bUseLongDouble ){
|
||||
LONGDOUBLE_TYPE r = (LONGDOUBLE_TYPE)s;
|
||||
if( e>0 ){
|
||||
while( e>=100 ){ e-=100; r *= 1.0e+100L; }
|
||||
@@ -35967,7 +35967,7 @@
|
||||
/* Multiply r by powers of ten until it lands somewhere in between
|
||||
** 1.0e+19 and 1.0e+17.
|
||||
*/
|
||||
- if( sqlite3Config.bUseLongDouble ){
|
||||
+ if( sizeof(LONGDOUBLE_TYPE)>8 && sqlite3Config.bUseLongDouble ){
|
||||
LONGDOUBLE_TYPE rr = r;
|
||||
if( rr>=1.0e+19 ){
|
||||
while( rr>=1.0e+119L ){ exp+=100; rr *= 1.0e-100L; }
|
||||
@@ -89354,7 +89354,7 @@
|
||||
** than NULL */
|
||||
return 1;
|
||||
}
|
||||
- if( sqlite3Config.bUseLongDouble ){
|
||||
+ if( sizeof(LONGDOUBLE_TYPE)>8 && sqlite3Config.bUseLongDouble ){
|
||||
LONGDOUBLE_TYPE x = (LONGDOUBLE_TYPE)i;
|
||||
testcase( x<r );
|
||||
testcase( x>r );
|
||||
@@ -7,6 +7,7 @@
|
||||
#include "ext/ieee754.c"
|
||||
#include "ext/regexp.c"
|
||||
#include "ext/series.c"
|
||||
#include "ext/spellfix.c"
|
||||
#include "ext/uint.c"
|
||||
// Bindings
|
||||
#include "bind.c"
|
||||
@@ -26,6 +27,7 @@ __attribute__((constructor)) void init() {
|
||||
sqlite3_auto_extension((void (*)(void))sqlite3_ieee_init);
|
||||
sqlite3_auto_extension((void (*)(void))sqlite3_regexp_init);
|
||||
sqlite3_auto_extension((void (*)(void))sqlite3_series_init);
|
||||
sqlite3_auto_extension((void (*)(void))sqlite3_spellfix_init);
|
||||
sqlite3_auto_extension((void (*)(void))sqlite3_uint_init);
|
||||
sqlite3_auto_extension((void (*)(void))sqlite3_time_init);
|
||||
}
|
||||
@@ -1,13 +1,14 @@
|
||||
#include <math.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "sqlite3.h"
|
||||
|
||||
void sqlite3_result_text_go(sqlite3_context* ctx, const char* zData,
|
||||
void sqlite3_result_text_go(sqlite3_context *ctx, const char *zData,
|
||||
sqlite3_uint64 nData) {
|
||||
sqlite3_result_text64(ctx, zData, nData, &sqlite3_free, SQLITE_UTF8);
|
||||
}
|
||||
|
||||
void sqlite3_result_blob_go(sqlite3_context* ctx, const void* zData,
|
||||
void sqlite3_result_blob_go(sqlite3_context *ctx, const void *zData,
|
||||
sqlite3_uint64 nData) {
|
||||
sqlite3_result_blob64(ctx, zData, nData, &sqlite3_free);
|
||||
}
|
||||
@@ -163,6 +163,10 @@ static int go_vtab_shadown_name_wrapper(const char *zName) { return true; }
|
||||
|
||||
int sqlite3_create_module_go(sqlite3 *db, const char *zName, int flags,
|
||||
go_handle handle) {
|
||||
if (handle == NULL) {
|
||||
return sqlite3_create_module_v2(db, zName, NULL, NULL, NULL);
|
||||
}
|
||||
|
||||
struct go_module *mod = malloc(sizeof(struct go_module));
|
||||
if (mod == NULL) {
|
||||
go_destroy(handle);
|
||||
|
||||
57
stmt.go
57
stmt.go
@@ -30,12 +30,13 @@ func (s *Stmt) Close() error {
|
||||
}
|
||||
|
||||
r := s.c.call("sqlite3_finalize", uint64(s.handle))
|
||||
for i := range s.c.stmts {
|
||||
if s == s.c.stmts[i] {
|
||||
l := len(s.c.stmts) - 1
|
||||
s.c.stmts[i] = s.c.stmts[l]
|
||||
s.c.stmts[l] = nil
|
||||
s.c.stmts = s.c.stmts[:l]
|
||||
stmts := s.c.stmts
|
||||
for i := range stmts {
|
||||
if s == stmts[i] {
|
||||
l := len(stmts) - 1
|
||||
stmts[i] = stmts[l]
|
||||
stmts[l] = nil
|
||||
s.c.stmts = stmts[:l]
|
||||
break
|
||||
}
|
||||
}
|
||||
@@ -105,7 +106,7 @@ func (s *Stmt) Busy() bool {
|
||||
//
|
||||
// https://sqlite.org/c3ref/step.html
|
||||
func (s *Stmt) Step() bool {
|
||||
s.c.checkInterrupt()
|
||||
s.c.checkInterrupt(s.c.handle)
|
||||
r := s.c.call("sqlite3_step", uint64(s.handle))
|
||||
switch r {
|
||||
case _ROW:
|
||||
@@ -376,6 +377,15 @@ func (s *Stmt) BindValue(param int, value Value) error {
|
||||
return s.c.error(r)
|
||||
}
|
||||
|
||||
// DataCount resets the number of columns in a result set.
|
||||
//
|
||||
// https://sqlite.org/c3ref/data_count.html
|
||||
func (s *Stmt) DataCount() int {
|
||||
r := s.c.call("sqlite3_data_count",
|
||||
uint64(s.handle))
|
||||
return int(int32(r))
|
||||
}
|
||||
|
||||
// ColumnCount returns the number of columns in a result set.
|
||||
//
|
||||
// https://sqlite.org/c3ref/column_count.html
|
||||
@@ -630,7 +640,7 @@ func (s *Stmt) Columns(dest []any) error {
|
||||
defer s.c.arena.mark()()
|
||||
count := uint64(len(dest))
|
||||
typePtr := s.c.arena.new(count)
|
||||
dataPtr := s.c.arena.new(8 * count)
|
||||
dataPtr := s.c.arena.new(count * 8)
|
||||
|
||||
r := s.c.call("sqlite3_columns_go",
|
||||
uint64(s.handle), count, uint64(typePtr), uint64(dataPtr))
|
||||
@@ -639,26 +649,31 @@ func (s *Stmt) Columns(dest []any) error {
|
||||
}
|
||||
|
||||
types := util.View(s.c.mod, typePtr, count)
|
||||
|
||||
// Avoid bounds checks on types below.
|
||||
if len(types) != len(dest) {
|
||||
panic(util.AssertErr())
|
||||
}
|
||||
|
||||
for i := range dest {
|
||||
switch types[i] {
|
||||
case byte(INTEGER):
|
||||
dest[i] = int64(util.ReadUint64(s.c.mod, dataPtr+8*uint32(i)))
|
||||
continue
|
||||
dest[i] = int64(util.ReadUint64(s.c.mod, dataPtr))
|
||||
case byte(FLOAT):
|
||||
dest[i] = util.ReadFloat64(s.c.mod, dataPtr+8*uint32(i))
|
||||
continue
|
||||
dest[i] = util.ReadFloat64(s.c.mod, dataPtr)
|
||||
case byte(NULL):
|
||||
dest[i] = nil
|
||||
continue
|
||||
}
|
||||
ptr := util.ReadUint32(s.c.mod, dataPtr+8*uint32(i)+0)
|
||||
len := util.ReadUint32(s.c.mod, dataPtr+8*uint32(i)+4)
|
||||
buf := util.View(s.c.mod, ptr, uint64(len))
|
||||
if types[i] == byte(TEXT) {
|
||||
dest[i] = string(buf)
|
||||
} else {
|
||||
dest[i] = buf
|
||||
default:
|
||||
ptr := util.ReadUint32(s.c.mod, dataPtr+0)
|
||||
len := util.ReadUint32(s.c.mod, dataPtr+4)
|
||||
buf := util.View(s.c.mod, ptr, uint64(len))
|
||||
if types[i] == byte(TEXT) {
|
||||
dest[i] = string(buf)
|
||||
} else {
|
||||
dest[i] = buf
|
||||
}
|
||||
}
|
||||
dataPtr += 8
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -209,6 +209,24 @@ func TestCreateFunction_error(t *testing.T) {
|
||||
stmt.Step()
|
||||
}
|
||||
|
||||
func TestCreateFunction_delete(t *testing.T) {
|
||||
db, err := sqlite3.Open(":memory:")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
err = db.CreateFunction("regexp", 2, 0, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = db.Exec(`SELECT 'a' REGEXP 'a|b'`)
|
||||
if err == nil {
|
||||
t.Error("want error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestOverloadFunction(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package tests
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"io"
|
||||
"log"
|
||||
@@ -224,9 +225,13 @@ func testParallel(t testing.TB, name string, n int) {
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
err = db.BusyHandler(func(count int) (retry bool) {
|
||||
time.Sleep(time.Millisecond)
|
||||
return true
|
||||
err = db.BusyHandler(func(ctx context.Context, count int) (retry bool) {
|
||||
select {
|
||||
case <-time.After(time.Millisecond):
|
||||
return true
|
||||
case <-ctx.Done():
|
||||
return false
|
||||
}
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package tests
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"math"
|
||||
"reflect"
|
||||
"testing"
|
||||
@@ -19,8 +21,8 @@ func TestQuote(t *testing.T) {
|
||||
{`a'bc`, "'a''bc'"},
|
||||
{"\x07bc", "'\abc'"},
|
||||
{"\x1c\n", "'\x1c\n'"},
|
||||
{"\xB0\x00\x0B", "'\xB0'"},
|
||||
{[]byte("\xB0\x00\x0B"), "x'B0000B'"},
|
||||
{"\xB0\x00\x0B", ""},
|
||||
|
||||
{0, "0"},
|
||||
{true, "1"},
|
||||
@@ -33,7 +35,13 @@ func TestQuote(t *testing.T) {
|
||||
{int64(math.MaxInt64), "9223372036854775807"},
|
||||
{time.Unix(0, 0).UTC(), "'1970-01-01T00:00:00Z'"},
|
||||
{sqlite3.ZeroBlob(4), "x'00000000'"},
|
||||
{sqlite3.ZeroBlob(1e9), ""},
|
||||
{int8(0), "0"},
|
||||
{uint(0), "0"},
|
||||
{float32(0), "0"},
|
||||
{(*string)(nil), "NULL"},
|
||||
{json.Number("0"), "'0'"},
|
||||
{&sql.RawBytes{'0'}, "x'30'"},
|
||||
{t, ""}, // panic
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
@@ -62,7 +70,7 @@ func TestQuoteIdentifier(t *testing.T) {
|
||||
{`a'bc`, `"a'bc"`},
|
||||
{"\x07bc", "\"\abc\""},
|
||||
{"\x1c\n", "\"\x1c\n\""},
|
||||
{"\xB0\x00\x0B", ""},
|
||||
{"\xB0\x00\x0B", ""}, // panic
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
|
||||
@@ -146,6 +146,12 @@ func TestStmt(t *testing.T) {
|
||||
if got := stmt.ReadOnly(); got != true {
|
||||
t.Error("got false, want true")
|
||||
}
|
||||
if got := stmt.DataCount(); got != 0 {
|
||||
t.Errorf("got %d, want 0", got)
|
||||
}
|
||||
if got := stmt.ColumnCount(); got != 1 {
|
||||
t.Errorf("got %d, want 1", got)
|
||||
}
|
||||
if got := stmt.ColumnName(0); got != "c" {
|
||||
t.Errorf(`got %q, want "c"`, got)
|
||||
}
|
||||
@@ -503,6 +509,10 @@ func TestStmt(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
if got := stmt.DataCount(); got != 1 {
|
||||
t.Errorf("got %d, want 1", got)
|
||||
}
|
||||
|
||||
db.Stmts()(func(s *sqlite3.Stmt) bool {
|
||||
if s != stmt {
|
||||
t.Error()
|
||||
|
||||
@@ -111,6 +111,7 @@ func TestTimeFormat_Decode(t *testing.T) {
|
||||
|
||||
{sqlite3.TimeFormatDefault, "2013-10-07T04:23:19.12-04:00", reference, 0, offset, false},
|
||||
{sqlite3.TimeFormatDefault, "2013-10-07T08:23:19.12Z", reference, 0, 0, false},
|
||||
{sqlite3.TimeFormatDefault, reference, reference, 0, offset, false},
|
||||
{sqlite3.TimeFormatDefault, false, time.Time{}, 0, 0, true},
|
||||
}
|
||||
|
||||
|
||||
20
tests/vtab_test.go
Normal file
20
tests/vtab_test.go
Normal file
@@ -0,0 +1,20 @@
|
||||
package tests
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/ncruces/go-sqlite3"
|
||||
)
|
||||
|
||||
func TestCreateModule_delete(t *testing.T) {
|
||||
db, err := sqlite3.Open(":memory:")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
err = sqlite3.CreateModule[sqlite3.VTab](db, "generate_series", nil, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
3
time.go
3
time.go
@@ -138,6 +138,9 @@ func (f TimeFormat) Encode(t time.Time) any {
|
||||
//
|
||||
// https://sqlite.org/lang_datefunc.html
|
||||
func (f TimeFormat) Decode(v any) (time.Time, error) {
|
||||
if t, ok := v.(time.Time); ok {
|
||||
return t, nil
|
||||
}
|
||||
switch f {
|
||||
// Numeric formats.
|
||||
case TimeFormatJulianDay:
|
||||
|
||||
29
txn.go
29
txn.go
@@ -3,7 +3,6 @@ package sqlite3
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"runtime"
|
||||
"strconv"
|
||||
@@ -136,23 +135,21 @@ type Savepoint struct {
|
||||
//
|
||||
// https://sqlite.org/lang_savepoint.html
|
||||
func (c *Conn) Savepoint() Savepoint {
|
||||
// Names can be reused; this makes catching bugs more likely.
|
||||
name := saveptName() + "_" + strconv.Itoa(int(rand.Int31()))
|
||||
name := callerName()
|
||||
if name == "" {
|
||||
name = "sqlite3.Savepoint"
|
||||
}
|
||||
// Names can be reused, but this makes catching bugs more likely.
|
||||
name = QuoteIdentifier(name + "_" + strconv.Itoa(int(rand.Int31())))
|
||||
|
||||
err := c.txnExecInterrupted(fmt.Sprintf("SAVEPOINT %q;", name))
|
||||
err := c.txnExecInterrupted("SAVEPOINT " + name)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return Savepoint{c: c, name: name}
|
||||
}
|
||||
|
||||
func saveptName() (name string) {
|
||||
defer func() {
|
||||
if name == "" {
|
||||
name = "sqlite3.Savepoint"
|
||||
}
|
||||
}()
|
||||
|
||||
func callerName() (name string) {
|
||||
var pc [8]uintptr
|
||||
n := runtime.Callers(3, pc[:])
|
||||
if n <= 0 {
|
||||
@@ -189,7 +186,7 @@ func (s Savepoint) Release(errp *error) {
|
||||
if s.c.GetAutocommit() { // There is nothing to commit.
|
||||
return
|
||||
}
|
||||
*errp = s.c.Exec(fmt.Sprintf("RELEASE %q;", s.name))
|
||||
*errp = s.c.Exec("RELEASE " + s.name)
|
||||
if *errp == nil {
|
||||
return
|
||||
}
|
||||
@@ -201,10 +198,8 @@ func (s Savepoint) Release(errp *error) {
|
||||
return
|
||||
}
|
||||
// ROLLBACK and RELEASE even if interrupted.
|
||||
err := s.c.txnExecInterrupted(fmt.Sprintf(`
|
||||
ROLLBACK TO %[1]q;
|
||||
RELEASE %[1]q;
|
||||
`, s.name))
|
||||
err := s.c.txnExecInterrupted("ROLLBACK TO " +
|
||||
s.name + "; RELEASE " + s.name)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@@ -217,7 +212,7 @@ func (s Savepoint) Release(errp *error) {
|
||||
// https://sqlite.org/lang_transaction.html
|
||||
func (s Savepoint) Rollback() error {
|
||||
// ROLLBACK even if interrupted.
|
||||
return s.c.txnExecInterrupted(fmt.Sprintf("ROLLBACK TO %q;", s.name))
|
||||
return s.c.txnExecInterrupted("ROLLBACK TO " + s.name)
|
||||
}
|
||||
|
||||
func (c *Conn) txnExecInterrupted(sql string) error {
|
||||
|
||||
@@ -15,7 +15,7 @@ func OpenFile(name string, flag int, perm fs.FileMode) (*os.File, error) {
|
||||
if name == "" {
|
||||
return nil, &os.PathError{Op: "open", Path: name, Err: ENOENT}
|
||||
}
|
||||
r, e := syscallOpen(name, flag, uint32(perm.Perm()))
|
||||
r, e := syscallOpen(name, flag|O_CLOEXEC, uint32(perm.Perm()))
|
||||
if e != nil {
|
||||
return nil, &os.PathError{Op: "open", Path: name, Err: e}
|
||||
}
|
||||
|
||||
19
vfs/file.go
19
vfs/file.go
@@ -19,17 +19,18 @@ func (vfsOS) FullPathname(path string) (string, error) {
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
fi, err := os.Lstat(path)
|
||||
return path, testSymlinks(filepath.Dir(path))
|
||||
}
|
||||
|
||||
func testSymlinks(path string) error {
|
||||
p, err := filepath.EvalSymlinks(path)
|
||||
if err != nil {
|
||||
if errors.Is(err, fs.ErrNotExist) {
|
||||
return path, nil
|
||||
}
|
||||
return "", err
|
||||
return err
|
||||
}
|
||||
if fi.Mode()&fs.ModeSymlink != 0 {
|
||||
err = _OK_SYMLINK
|
||||
if p != path {
|
||||
return _OK_SYMLINK
|
||||
}
|
||||
return path, err
|
||||
return nil
|
||||
}
|
||||
|
||||
func (vfsOS) Delete(path string, syncDir bool) error {
|
||||
@@ -74,7 +75,7 @@ func (vfsOS) Open(name string, flags OpenFlag) (File, OpenFlag, error) {
|
||||
}
|
||||
|
||||
func (vfsOS) OpenFilename(name *Filename, flags OpenFlag) (File, OpenFlag, error) {
|
||||
var oflags int
|
||||
oflags := _O_NOFOLLOW
|
||||
if flags&OPEN_EXCLUSIVE != 0 {
|
||||
oflags |= os.O_EXCL
|
||||
}
|
||||
|
||||
@@ -43,7 +43,8 @@ func Create(name string, data []byte) {
|
||||
}
|
||||
|
||||
// Convert data from WAL/2 to rollback journal.
|
||||
if len(data) >= 20 && (data[18] == 2 && data[19] == 2 ||
|
||||
if len(data) >= 20 && (false ||
|
||||
data[18] == 2 && data[19] == 2 ||
|
||||
data[18] == 3 && data[19] == 3) {
|
||||
data[18] = 1
|
||||
data[19] = 1
|
||||
|
||||
@@ -7,6 +7,8 @@ import (
|
||||
"os"
|
||||
)
|
||||
|
||||
const _O_NOFOLLOW = 0
|
||||
|
||||
func osAccess(path string, flags AccessFlag) error {
|
||||
fi, err := os.Stat(path)
|
||||
if err != nil {
|
||||
@@ -34,3 +36,12 @@ func osAccess(path string, flags AccessFlag) error {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func osSetMode(file *os.File, modeof string) error {
|
||||
fi, err := os.Stat(modeof)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
file.Chmod(fi.Mode())
|
||||
return nil
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
//go:build !unix || sqlite3_nosys
|
||||
|
||||
package vfs
|
||||
|
||||
import "os"
|
||||
|
||||
func osSetMode(file *os.File, modeof string) error {
|
||||
fi, err := os.Stat(modeof)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
file.Chmod(fi.Mode())
|
||||
return nil
|
||||
}
|
||||
@@ -9,6 +9,8 @@ import (
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
const _O_NOFOLLOW = unix.O_NOFOLLOW
|
||||
|
||||
func osAccess(path string, flags AccessFlag) error {
|
||||
var access uint32 // unix.F_OK
|
||||
switch flags {
|
||||
|
||||
@@ -15,9 +15,6 @@ import (
|
||||
"github.com/psanford/httpreadat"
|
||||
)
|
||||
|
||||
//go:embed testdata/test.db
|
||||
var testDB string
|
||||
|
||||
func Example_http() {
|
||||
readervfs.Create("demo.db", httpreadat.New("https://sanford.io/demo.db"))
|
||||
defer readervfs.Delete("demo.db")
|
||||
@@ -65,6 +62,9 @@ func Example_http() {
|
||||
// 2012.06: 18270 million Dollars
|
||||
}
|
||||
|
||||
//go:embed testdata/test.db
|
||||
var testDB string
|
||||
|
||||
func Example_embed() {
|
||||
readervfs.Create("test.db", ioutil.NewSizeReaderAt(strings.NewReader(testDB)))
|
||||
defer readervfs.Delete("test.db")
|
||||
|
||||
2
vfs/tests/mptest/testdata/build.sh
vendored
2
vfs/tests/mptest/testdata/build.sh
vendored
@@ -10,7 +10,7 @@ WASI_SDK="$ROOT/tools/wasi-sdk/bin"
|
||||
"$WASI_SDK/clang" --target=wasm32-wasi -std=c23 -g0 -O2 \
|
||||
-o mptest.wasm main.c \
|
||||
-I"$ROOT/sqlite3" \
|
||||
-matomics -msimd128 -mmutable-globals \
|
||||
-matomics -msimd128 -mmutable-globals -mmultivalue \
|
||||
-mbulk-memory -mreference-types \
|
||||
-mnontrapping-fptoint -msign-ext \
|
||||
-fno-stack-protector -fno-stack-clash-protection \
|
||||
|
||||
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:4b2ca8d3b914990e5c3486e039a6a4160b36c8d6db6c95e2c6c6619d69d26e1d
|
||||
size 476258
|
||||
oid sha256:a16772cc8f0d3b86e068440876f5ea7861e687420725966cd991c8929d365904
|
||||
size 473706
|
||||
|
||||
2
vfs/tests/speedtest1/testdata/build.sh
vendored
2
vfs/tests/speedtest1/testdata/build.sh
vendored
@@ -10,7 +10,7 @@ WASI_SDK="$ROOT/tools/wasi-sdk/bin"
|
||||
"$WASI_SDK/clang" --target=wasm32-wasi -std=c23 -g0 -O2 \
|
||||
-o speedtest1.wasm main.c \
|
||||
-I"$ROOT/sqlite3" \
|
||||
-matomics -msimd128 -mmutable-globals \
|
||||
-matomics -msimd128 -mmutable-globals -mmultivalue \
|
||||
-mbulk-memory -mreference-types \
|
||||
-mnontrapping-fptoint -msign-ext \
|
||||
-fno-stack-protector -fno-stack-clash-protection \
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:259a3d0311302a0ea1d577a8209425f0dbf1ec3a77e214dbafa0e2c1a30723d3
|
||||
size 489479
|
||||
oid sha256:9a2b5c21bdc8214a2c5a278c34e06b9079813a487fbd575ca2d62386b206f809
|
||||
size 487023
|
||||
|
||||
13
vtab.go
13
vtab.go
@@ -57,9 +57,12 @@ func CreateModule[T VTab](db *Conn, name string, create, connect VTabConstructor
|
||||
flags |= VTAB_SHADOWTABS
|
||||
}
|
||||
|
||||
var modulePtr uint32
|
||||
defer db.arena.mark()()
|
||||
namePtr := db.arena.string(name)
|
||||
modulePtr := util.AddHandle(db.ctx, module[T]{create, connect})
|
||||
if connect != nil {
|
||||
modulePtr = util.AddHandle(db.ctx, module[T]{create, connect})
|
||||
}
|
||||
r := db.call("sqlite3_create_module_go", uint64(db.handle),
|
||||
uint64(namePtr), uint64(flags), uint64(modulePtr))
|
||||
return db.error(r)
|
||||
@@ -352,8 +355,9 @@ func (idx *IndexInfo) load() {
|
||||
idx.OrderBy = make([]IndexOrderBy, util.ReadUint32(mod, ptr+8))
|
||||
|
||||
constraintPtr := util.ReadUint32(mod, ptr+4)
|
||||
constraint := idx.Constraint
|
||||
for i := range idx.Constraint {
|
||||
idx.Constraint[i] = IndexConstraint{
|
||||
constraint[i] = IndexConstraint{
|
||||
Column: int(int32(util.ReadUint32(mod, constraintPtr+0))),
|
||||
Op: IndexConstraintOp(util.ReadUint8(mod, constraintPtr+4)),
|
||||
Usable: util.ReadUint8(mod, constraintPtr+5) != 0,
|
||||
@@ -362,8 +366,9 @@ func (idx *IndexInfo) load() {
|
||||
}
|
||||
|
||||
orderByPtr := util.ReadUint32(mod, ptr+12)
|
||||
for i := range idx.OrderBy {
|
||||
idx.OrderBy[i] = IndexOrderBy{
|
||||
orderBy := idx.OrderBy
|
||||
for i := range orderBy {
|
||||
orderBy[i] = IndexOrderBy{
|
||||
Column: int(int32(util.ReadUint32(mod, orderByPtr+0))),
|
||||
Desc: util.ReadUint8(mod, orderByPtr+4) != 0,
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user