mirror of
https://github.com/status-im/go-sqlcipher.git
synced 2026-08-27 16:21:09 +00:00
upgrade go-sqlite3 to v1.14.5
This commit is contained in:
+1
-2
@@ -9,8 +9,7 @@ tracked for changes (maintenance details below):
|
||||
Update code from https://github.com/mattn/go-sqlite3
|
||||
----------------------------------------------------
|
||||
|
||||
Current HEAD: 4c2df3cc4614a8001c1afdebcbda0fcf6e045719
|
||||
(from 2020-07-21)
|
||||
Current release: v1.14.5
|
||||
|
||||
Use ./track_go-sqlite3.sh
|
||||
|
||||
|
||||
+25
-31
@@ -35,56 +35,55 @@ import (
|
||||
//export callbackTrampoline
|
||||
func callbackTrampoline(ctx *C.sqlite3_context, argc int, argv **C.sqlite3_value) {
|
||||
args := (*[(math.MaxInt32 - 1) / unsafe.Sizeof((*C.sqlite3_value)(nil))]*C.sqlite3_value)(unsafe.Pointer(argv))[:argc:argc]
|
||||
fi := lookupHandle(uintptr(C.sqlite3_user_data(ctx))).(*functionInfo)
|
||||
fi := lookupHandle(C.sqlite3_user_data(ctx)).(*functionInfo)
|
||||
fi.Call(ctx, args)
|
||||
}
|
||||
|
||||
//export stepTrampoline
|
||||
func stepTrampoline(ctx *C.sqlite3_context, argc C.int, argv **C.sqlite3_value) {
|
||||
args := (*[(math.MaxInt32 - 1) / unsafe.Sizeof((*C.sqlite3_value)(nil))]*C.sqlite3_value)(unsafe.Pointer(argv))[:int(argc):int(argc)]
|
||||
ai := lookupHandle(uintptr(C.sqlite3_user_data(ctx))).(*aggInfo)
|
||||
ai := lookupHandle(C.sqlite3_user_data(ctx)).(*aggInfo)
|
||||
ai.Step(ctx, args)
|
||||
}
|
||||
|
||||
//export doneTrampoline
|
||||
func doneTrampoline(ctx *C.sqlite3_context) {
|
||||
handle := uintptr(C.sqlite3_user_data(ctx))
|
||||
ai := lookupHandle(handle).(*aggInfo)
|
||||
ai := lookupHandle(C.sqlite3_user_data(ctx)).(*aggInfo)
|
||||
ai.Done(ctx)
|
||||
}
|
||||
|
||||
//export compareTrampoline
|
||||
func compareTrampoline(handlePtr uintptr, la C.int, a *C.char, lb C.int, b *C.char) C.int {
|
||||
func compareTrampoline(handlePtr unsafe.Pointer, la C.int, a *C.char, lb C.int, b *C.char) C.int {
|
||||
cmp := lookupHandle(handlePtr).(func(string, string) int)
|
||||
return C.int(cmp(C.GoStringN(a, la), C.GoStringN(b, lb)))
|
||||
}
|
||||
|
||||
//export commitHookTrampoline
|
||||
func commitHookTrampoline(handle uintptr) int {
|
||||
func commitHookTrampoline(handle unsafe.Pointer) int {
|
||||
callback := lookupHandle(handle).(func() int)
|
||||
return callback()
|
||||
}
|
||||
|
||||
//export rollbackHookTrampoline
|
||||
func rollbackHookTrampoline(handle uintptr) {
|
||||
func rollbackHookTrampoline(handle unsafe.Pointer) {
|
||||
callback := lookupHandle(handle).(func())
|
||||
callback()
|
||||
}
|
||||
|
||||
//export updateHookTrampoline
|
||||
func updateHookTrampoline(handle uintptr, op int, db *C.char, table *C.char, rowid int64) {
|
||||
func updateHookTrampoline(handle unsafe.Pointer, op int, db *C.char, table *C.char, rowid int64) {
|
||||
callback := lookupHandle(handle).(func(int, string, string, int64))
|
||||
callback(op, C.GoString(db), C.GoString(table), rowid)
|
||||
}
|
||||
|
||||
//export authorizerTrampoline
|
||||
func authorizerTrampoline(handle uintptr, op int, arg1 *C.char, arg2 *C.char, arg3 *C.char) int {
|
||||
func authorizerTrampoline(handle unsafe.Pointer, op int, arg1 *C.char, arg2 *C.char, arg3 *C.char) int {
|
||||
callback := lookupHandle(handle).(func(int, string, string, string) int)
|
||||
return callback(op, C.GoString(arg1), C.GoString(arg2), C.GoString(arg3))
|
||||
}
|
||||
|
||||
//export preUpdateHookTrampoline
|
||||
func preUpdateHookTrampoline(handle uintptr, dbHandle uintptr, op int, db *C.char, table *C.char, oldrowid int64, newrowid int64) {
|
||||
func preUpdateHookTrampoline(handle unsafe.Pointer, dbHandle uintptr, op int, db *C.char, table *C.char, oldrowid int64, newrowid int64) {
|
||||
hval := lookupHandleVal(handle)
|
||||
data := SQLitePreUpdateData{
|
||||
Conn: hval.db,
|
||||
@@ -105,33 +104,27 @@ type handleVal struct {
|
||||
}
|
||||
|
||||
var handleLock sync.Mutex
|
||||
var handleVals = make(map[uintptr]handleVal)
|
||||
var handleIndex uintptr = 100
|
||||
var handleVals = make(map[unsafe.Pointer]handleVal)
|
||||
|
||||
func newHandle(db *SQLiteConn, v interface{}) uintptr {
|
||||
func newHandle(db *SQLiteConn, v interface{}) unsafe.Pointer {
|
||||
handleLock.Lock()
|
||||
defer handleLock.Unlock()
|
||||
i := handleIndex
|
||||
handleIndex++
|
||||
handleVals[i] = handleVal{db, v}
|
||||
return i
|
||||
}
|
||||
|
||||
func lookupHandleVal(handle uintptr) handleVal {
|
||||
handleLock.Lock()
|
||||
defer handleLock.Unlock()
|
||||
r, ok := handleVals[handle]
|
||||
if !ok {
|
||||
if handle >= 100 && handle < handleIndex {
|
||||
panic("deleted handle")
|
||||
} else {
|
||||
panic("invalid handle")
|
||||
}
|
||||
val := handleVal{db: db, val: v}
|
||||
var p unsafe.Pointer = C.malloc(C.size_t(1))
|
||||
if p == nil {
|
||||
panic("can't allocate 'cgo-pointer hack index pointer': ptr == nil")
|
||||
}
|
||||
return r
|
||||
handleVals[p] = val
|
||||
return p
|
||||
}
|
||||
|
||||
func lookupHandle(handle uintptr) interface{} {
|
||||
func lookupHandleVal(handle unsafe.Pointer) handleVal {
|
||||
handleLock.Lock()
|
||||
defer handleLock.Unlock()
|
||||
return handleVals[handle]
|
||||
}
|
||||
|
||||
func lookupHandle(handle unsafe.Pointer) interface{} {
|
||||
return lookupHandleVal(handle).val
|
||||
}
|
||||
|
||||
@@ -141,6 +134,7 @@ func deleteHandles(db *SQLiteConn) {
|
||||
for handle, val := range handleVals {
|
||||
if val.db == db {
|
||||
delete(handleVals, handle)
|
||||
C.free(handle)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ This works as a driver for database/sql.
|
||||
|
||||
Installation
|
||||
|
||||
go get github.com/mutecomm/go-sqlcipher/v4
|
||||
go get github.com/mattn/go-sqlite3
|
||||
|
||||
Supported Types
|
||||
|
||||
@@ -79,9 +79,8 @@ Then, you can use this extension.
|
||||
|
||||
Connection Hook
|
||||
|
||||
You can hook and inject your code when the connection is established. database/sql
|
||||
doesn't provide a way to get native go-sqlite3 interfaces. So if you want,
|
||||
you need to set ConnectHook and get the SQLiteConn.
|
||||
You can hook and inject your code when the connection is established by setting
|
||||
ConnectHook to get the SQLiteConn.
|
||||
|
||||
sql.Register("sqlite3_with_hook_example",
|
||||
&sqlite3.SQLiteDriver{
|
||||
@@ -91,21 +90,45 @@ you need to set ConnectHook and get the SQLiteConn.
|
||||
},
|
||||
})
|
||||
|
||||
You can also use database/sql.Conn.Raw (Go >= 1.13):
|
||||
|
||||
conn, err := db.Conn(context.Background())
|
||||
// if err != nil { ... }
|
||||
defer conn.Close()
|
||||
err = conn.Raw(func (driverConn interface{}) error {
|
||||
sqliteConn := driverConn.(*sqlite3.SQLiteConn)
|
||||
// ... use sqliteConn
|
||||
})
|
||||
// if err != nil { ... }
|
||||
|
||||
Go SQlite3 Extensions
|
||||
|
||||
If you want to register Go functions as SQLite extension functions,
|
||||
call RegisterFunction from ConnectHook.
|
||||
If you want to register Go functions as SQLite extension functions
|
||||
you can make a custom driver by calling RegisterFunction from
|
||||
ConnectHook.
|
||||
|
||||
regex = func(re, s string) (bool, error) {
|
||||
return regexp.MatchString(re, s)
|
||||
}
|
||||
sql.Register("sqlite3_with_go_func",
|
||||
sql.Register("sqlite3_extended",
|
||||
&sqlite3.SQLiteDriver{
|
||||
ConnectHook: func(conn *sqlite3.SQLiteConn) error {
|
||||
return conn.RegisterFunc("regexp", regex, true)
|
||||
},
|
||||
})
|
||||
|
||||
You can then use the custom driver by passing its name to sql.Open.
|
||||
|
||||
var i int
|
||||
conn, err := sql.Open("sqlite3_extended", "./foo.db")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
err = db.QueryRow(`SELECT regexp("foo.*", "seafood")`).Scan(&i)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
See the documentation of RegisterFunc for more details.
|
||||
|
||||
*/
|
||||
|
||||
+34
-20
@@ -473,7 +473,7 @@ func (c *SQLiteConn) RegisterCollation(name string, cmp func(string, string) int
|
||||
handle := newHandle(c, cmp)
|
||||
cname := C.CString(name)
|
||||
defer C.free(unsafe.Pointer(cname))
|
||||
rv := C.sqlite3_create_collation(c.db, cname, C.SQLITE_UTF8, unsafe.Pointer(handle), (*[0]byte)(unsafe.Pointer(C.compareTrampoline)))
|
||||
rv := C.sqlite3_create_collation(c.db, cname, C.SQLITE_UTF8, handle, (*[0]byte)(unsafe.Pointer(C.compareTrampoline)))
|
||||
if rv != C.SQLITE_OK {
|
||||
return c.lastError()
|
||||
}
|
||||
@@ -491,7 +491,7 @@ func (c *SQLiteConn) RegisterCommitHook(callback func() int) {
|
||||
if callback == nil {
|
||||
C.sqlite3_commit_hook(c.db, nil, nil)
|
||||
} else {
|
||||
C.sqlite3_commit_hook(c.db, (*[0]byte)(C.commitHookTrampoline), unsafe.Pointer(newHandle(c, callback)))
|
||||
C.sqlite3_commit_hook(c.db, (*[0]byte)(C.commitHookTrampoline), newHandle(c, callback))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -504,7 +504,7 @@ func (c *SQLiteConn) RegisterRollbackHook(callback func()) {
|
||||
if callback == nil {
|
||||
C.sqlite3_rollback_hook(c.db, nil, nil)
|
||||
} else {
|
||||
C.sqlite3_rollback_hook(c.db, (*[0]byte)(C.rollbackHookTrampoline), unsafe.Pointer(newHandle(c, callback)))
|
||||
C.sqlite3_rollback_hook(c.db, (*[0]byte)(C.rollbackHookTrampoline), newHandle(c, callback))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -521,7 +521,7 @@ func (c *SQLiteConn) RegisterUpdateHook(callback func(int, string, string, int64
|
||||
if callback == nil {
|
||||
C.sqlite3_update_hook(c.db, nil, nil)
|
||||
} else {
|
||||
C.sqlite3_update_hook(c.db, (*[0]byte)(C.updateHookTrampoline), unsafe.Pointer(newHandle(c, callback)))
|
||||
C.sqlite3_update_hook(c.db, (*[0]byte)(C.updateHookTrampoline), newHandle(c, callback))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -535,7 +535,7 @@ func (c *SQLiteConn) RegisterAuthorizer(callback func(int, string, string, strin
|
||||
if callback == nil {
|
||||
C.sqlite3_set_authorizer(c.db, nil, nil)
|
||||
} else {
|
||||
C.sqlite3_set_authorizer(c.db, (*[0]byte)(C.authorizerTrampoline), unsafe.Pointer(newHandle(c, callback)))
|
||||
C.sqlite3_set_authorizer(c.db, (*[0]byte)(C.authorizerTrampoline), newHandle(c, callback))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -616,8 +616,8 @@ func (c *SQLiteConn) RegisterFunc(name string, impl interface{}, pure bool) erro
|
||||
return nil
|
||||
}
|
||||
|
||||
func sqlite3CreateFunction(db *C.sqlite3, zFunctionName *C.char, nArg C.int, eTextRep C.int, pApp uintptr, xFunc unsafe.Pointer, xStep unsafe.Pointer, xFinal unsafe.Pointer) C.int {
|
||||
return C._sqlite3_create_function(db, zFunctionName, nArg, eTextRep, C.uintptr_t(pApp), (*[0]byte)(xFunc), (*[0]byte)(xStep), (*[0]byte)(xFinal))
|
||||
func sqlite3CreateFunction(db *C.sqlite3, zFunctionName *C.char, nArg C.int, eTextRep C.int, pApp unsafe.Pointer, xFunc unsafe.Pointer, xStep unsafe.Pointer, xFinal unsafe.Pointer) C.int {
|
||||
return C._sqlite3_create_function(db, zFunctionName, nArg, eTextRep, C.uintptr_t(uintptr(pApp)), (*[0]byte)(xFunc), (*[0]byte)(xStep), (*[0]byte)(xFinal))
|
||||
}
|
||||
|
||||
// RegisterAggregator makes a Go type available as a SQLite aggregation function.
|
||||
@@ -1010,13 +1010,6 @@ func (c *SQLiteConn) begin(ctx context.Context) (driver.Tx, error) {
|
||||
// Warning: misuse of this pragma can easily result in a corrupt database file.
|
||||
//
|
||||
//
|
||||
// go-sqlcipher adds the following query parameters to those used by SQLite:
|
||||
//
|
||||
// _pragma_key=XXX
|
||||
// Specify PRAGMA key.
|
||||
//
|
||||
// _pragma_cipher_page_size=XXX
|
||||
// Set the PRAGMA cipher_page_size to adjust the page size.
|
||||
func (d *SQLiteDriver) Open(dsn string) (driver.Conn, error) {
|
||||
if C.sqlite3_threadsafe() == 0 {
|
||||
return nil, errors.New("sqlite library was not compiled for thread-safe operation")
|
||||
@@ -1048,6 +1041,7 @@ func (d *SQLiteDriver) Open(dsn string) (driver.Conn, error) {
|
||||
secureDelete := "DEFAULT"
|
||||
synchronousMode := "NORMAL"
|
||||
writableSchema := -1
|
||||
vfsName := ""
|
||||
|
||||
// SQLCipher PRAGMA's
|
||||
pragmaKey := ""
|
||||
@@ -1375,6 +1369,10 @@ func (d *SQLiteDriver) Open(dsn string) (driver.Conn, error) {
|
||||
}
|
||||
}
|
||||
|
||||
if val := params.Get("vfs"); val != "" {
|
||||
vfsName = val
|
||||
}
|
||||
|
||||
// _pragma_key
|
||||
if val := params.Get("_pragma_key"); val != "" {
|
||||
pragmaKey = val
|
||||
@@ -1397,9 +1395,14 @@ func (d *SQLiteDriver) Open(dsn string) (driver.Conn, error) {
|
||||
var db *C.sqlite3
|
||||
name := C.CString(dsn)
|
||||
defer C.free(unsafe.Pointer(name))
|
||||
var vfs *C.char
|
||||
if vfsName != "" {
|
||||
vfs = C.CString(vfsName)
|
||||
defer C.free(unsafe.Pointer(vfs))
|
||||
}
|
||||
rv := C._sqlite3_open_v2(name, &db,
|
||||
mutex|C.SQLITE_OPEN_READWRITE|C.SQLITE_OPEN_CREATE,
|
||||
nil)
|
||||
vfs)
|
||||
if rv != 0 {
|
||||
// Save off the error _before_ closing the database.
|
||||
// This is safe even if db is nil.
|
||||
@@ -1962,6 +1965,14 @@ func (s *SQLiteStmt) Exec(args []driver.Value) (driver.Result, error) {
|
||||
return s.exec(context.Background(), list)
|
||||
}
|
||||
|
||||
func isInterruptErr(err error) bool {
|
||||
sqliteErr, ok := err.(Error)
|
||||
if ok {
|
||||
return sqliteErr.Code == ErrInterrupt
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// exec executes a query that doesn't return rows. Attempts to honor context timeout.
|
||||
func (s *SQLiteStmt) exec(ctx context.Context, args []namedValue) (driver.Result, error) {
|
||||
if ctx.Done() == nil {
|
||||
@@ -1977,19 +1988,22 @@ func (s *SQLiteStmt) exec(ctx context.Context, args []namedValue) (driver.Result
|
||||
r, err := s.execSync(args)
|
||||
resultCh <- result{r, err}
|
||||
}()
|
||||
var rv result
|
||||
select {
|
||||
case rv := <-resultCh:
|
||||
return rv.r, rv.err
|
||||
case rv = <-resultCh:
|
||||
case <-ctx.Done():
|
||||
select {
|
||||
case <-resultCh: // no need to interrupt
|
||||
case rv = <-resultCh: // no need to interrupt, operation completed in db
|
||||
default:
|
||||
// this is still racy and can be no-op if executed between sqlite3_* calls in execSync.
|
||||
C.sqlite3_interrupt(s.c.db)
|
||||
<-resultCh // ensure goroutine completed
|
||||
rv = <-resultCh // wait for goroutine completed
|
||||
if isInterruptErr(rv.err) {
|
||||
return nil, ctx.Err()
|
||||
}
|
||||
}
|
||||
return nil, ctx.Err()
|
||||
}
|
||||
return rv.r, rv.err
|
||||
}
|
||||
|
||||
func (s *SQLiteStmt) execSync(args []namedValue) (driver.Result, error) {
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
// Copyright (C) 2019 Yasuhiro Matsumoto <mattn.jp@gmail.com>.
|
||||
//
|
||||
// Use of this source code is governed by an MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build go1.13,cgo
|
||||
|
||||
package sqlite3
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"database/sql/driver"
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestBeginTxCancel(t *testing.T) {
|
||||
srcTempFilename := TempFilename(t)
|
||||
defer os.Remove(srcTempFilename)
|
||||
|
||||
db, err := sql.Open("sqlite3", srcTempFilename)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
db.SetMaxOpenConns(10)
|
||||
db.SetMaxIdleConns(5)
|
||||
|
||||
defer db.Close()
|
||||
initDatabase(t, db, 100)
|
||||
|
||||
// create several go-routines to expose racy issue
|
||||
for i := 0; i < 1000; i++ {
|
||||
func() {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
conn, err := db.Conn(ctx)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer func() {
|
||||
if err := conn.Close(); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}()
|
||||
|
||||
err = conn.Raw(func(driverConn interface{}) error {
|
||||
d, ok := driverConn.(driver.ConnBeginTx)
|
||||
if !ok {
|
||||
t.Fatal("unexpected: wrong type")
|
||||
}
|
||||
// checks that conn.Raw can be used to get *SQLiteConn
|
||||
if _, ok = driverConn.(*SQLiteConn); !ok {
|
||||
t.Fatalf("conn.Raw() driverConn type=%T, expected *SQLiteConn", driverConn)
|
||||
}
|
||||
|
||||
go cancel() // make it cancel concurrently with exec("BEGIN");
|
||||
tx, err := d.BeginTx(ctx, driver.TxOptions{})
|
||||
switch err {
|
||||
case nil:
|
||||
switch err := tx.Rollback(); err {
|
||||
case nil, sql.ErrTxDone:
|
||||
default:
|
||||
return err
|
||||
}
|
||||
case context.Canceled:
|
||||
default:
|
||||
// must not fail with "cannot start a transaction within a transaction"
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}()
|
||||
}
|
||||
}
|
||||
+45
-1
@@ -136,6 +136,50 @@ func TestShortTimeout(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestExecContextCancel(t *testing.T) {
|
||||
srcTempFilename := TempFilename(t)
|
||||
defer os.Remove(srcTempFilename)
|
||||
|
||||
db, err := sql.Open("sqlite3", srcTempFilename)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
defer db.Close()
|
||||
|
||||
ts := time.Now()
|
||||
initDatabase(t, db, 1000)
|
||||
spent := time.Since(ts)
|
||||
const minTestTime = 100 * time.Millisecond
|
||||
if spent < minTestTime {
|
||||
t.Skipf("test will be too racy (spent=%s < min=%s) as ExecContext below will be too fast.",
|
||||
spent.String(), minTestTime.String(),
|
||||
)
|
||||
}
|
||||
|
||||
// expected to be extremely slow query
|
||||
q := `
|
||||
INSERT INTO test_table (key1, key_id, key2, key3, key4, key5, key6, data)
|
||||
SELECT t1.key1 || t2.key1, t1.key_id || t2.key_id, t1.key2 || t2.key2, t1.key3 || t2.key3, t1.key4 || t2.key4, t1.key5 || t2.key5, t1.key6 || t2.key6, t1.data || t2.data
|
||||
FROM test_table t1 LEFT OUTER JOIN test_table t2`
|
||||
// expect query above take ~ same time as setup above
|
||||
// This is racy: the context must be valid so sql/db.ExecContext calls the sqlite3 driver.
|
||||
// It starts the query, the context expires, then calls sqlite3_interrupt
|
||||
ctx, cancel := context.WithTimeout(context.Background(), minTestTime/2)
|
||||
defer cancel()
|
||||
ts = time.Now()
|
||||
r, err := db.ExecContext(ctx, q)
|
||||
// racy check
|
||||
if r != nil {
|
||||
n, err := r.RowsAffected()
|
||||
t.Logf("query should not have succeeded: rows=%d; err=%v; duration=%s",
|
||||
n, err, time.Since(ts).String())
|
||||
}
|
||||
if err != context.DeadlineExceeded {
|
||||
t.Fatal(err, ctx.Err())
|
||||
}
|
||||
}
|
||||
|
||||
func TestQueryRowContextCancel(t *testing.T) {
|
||||
srcTempFilename := TempFilename(t)
|
||||
defer os.Remove(srcTempFilename)
|
||||
@@ -191,7 +235,7 @@ func TestQueryRowContextCancelParallel(t *testing.T) {
|
||||
testCtx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
for i := 0; i < 50; i++ {
|
||||
for i := 0; i < 10; i++ {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
|
||||
@@ -14,5 +14,6 @@ package sqlite3
|
||||
#cgo darwin CFLAGS: -I/usr/local/opt/sqlite/include
|
||||
#cgo openbsd LDFLAGS: -lsqlite3
|
||||
#cgo solaris LDFLAGS: -lsqlite3
|
||||
#cgo windows LDFLAGS: -lsqlite3
|
||||
*/
|
||||
import "C"
|
||||
|
||||
@@ -68,7 +68,7 @@ func (c *SQLiteConn) loadExtension(lib string, entry *string) error {
|
||||
|
||||
var centry *C.char
|
||||
if entry != nil {
|
||||
centry := C.CString(*entry)
|
||||
centry = C.CString(*entry)
|
||||
defer C.free(unsafe.Pointer(centry))
|
||||
}
|
||||
|
||||
|
||||
+36
-26
@@ -288,10 +288,13 @@ type InfoOrderBy struct {
|
||||
}
|
||||
|
||||
func constraints(info *C.sqlite3_index_info) []InfoConstraint {
|
||||
l := info.nConstraint
|
||||
slice := (*[1 << 30]C.struct_sqlite3_index_constraint)(unsafe.Pointer(info.aConstraint))[:l:l]
|
||||
slice := *(*[]C.struct_sqlite3_index_constraint)(unsafe.Pointer(&reflect.SliceHeader{
|
||||
Data: uintptr(unsafe.Pointer(info.aConstraint)),
|
||||
Len: int(info.nConstraint),
|
||||
Cap: int(info.nConstraint),
|
||||
}))
|
||||
|
||||
cst := make([]InfoConstraint, 0, l)
|
||||
cst := make([]InfoConstraint, 0, len(slice))
|
||||
for _, c := range slice {
|
||||
var usable bool
|
||||
if c.usable > 0 {
|
||||
@@ -307,10 +310,13 @@ func constraints(info *C.sqlite3_index_info) []InfoConstraint {
|
||||
}
|
||||
|
||||
func orderBys(info *C.sqlite3_index_info) []InfoOrderBy {
|
||||
l := info.nOrderBy
|
||||
slice := (*[1 << 30]C.struct_sqlite3_index_orderby)(unsafe.Pointer(info.aOrderBy))[:l:l]
|
||||
slice := *(*[]C.struct_sqlite3_index_orderby)(unsafe.Pointer(&reflect.SliceHeader{
|
||||
Data: uintptr(unsafe.Pointer(info.aOrderBy)),
|
||||
Len: int(info.nOrderBy),
|
||||
Cap: int(info.nOrderBy),
|
||||
}))
|
||||
|
||||
ob := make([]InfoOrderBy, 0, l)
|
||||
ob := make([]InfoOrderBy, 0, len(slice))
|
||||
for _, c := range slice {
|
||||
var desc bool
|
||||
if c.desc > 0 {
|
||||
@@ -347,7 +353,7 @@ func mPrintf(format, arg string) *C.char {
|
||||
|
||||
//export goMInit
|
||||
func goMInit(db, pClientData unsafe.Pointer, argc C.int, argv **C.char, pzErr **C.char, isCreate C.int) C.uintptr_t {
|
||||
m := lookupHandle(uintptr(pClientData)).(*sqliteModule)
|
||||
m := lookupHandle(pClientData).(*sqliteModule)
|
||||
if m.c.db != (*C.sqlite3)(db) {
|
||||
*pzErr = mPrintf("%s", "Inconsistent db handles")
|
||||
return 0
|
||||
@@ -373,12 +379,12 @@ func goMInit(db, pClientData unsafe.Pointer, argc C.int, argv **C.char, pzErr **
|
||||
}
|
||||
vt := sqliteVTab{m, vTab}
|
||||
*pzErr = nil
|
||||
return C.uintptr_t(newHandle(m.c, &vt))
|
||||
return C.uintptr_t(uintptr(newHandle(m.c, &vt)))
|
||||
}
|
||||
|
||||
//export goVRelease
|
||||
func goVRelease(pVTab unsafe.Pointer, isDestroy C.int) *C.char {
|
||||
vt := lookupHandle(uintptr(pVTab)).(*sqliteVTab)
|
||||
vt := lookupHandle(pVTab).(*sqliteVTab)
|
||||
var err error
|
||||
if isDestroy == 1 {
|
||||
err = vt.vTab.Destroy()
|
||||
@@ -393,7 +399,7 @@ func goVRelease(pVTab unsafe.Pointer, isDestroy C.int) *C.char {
|
||||
|
||||
//export goVOpen
|
||||
func goVOpen(pVTab unsafe.Pointer, pzErr **C.char) C.uintptr_t {
|
||||
vt := lookupHandle(uintptr(pVTab)).(*sqliteVTab)
|
||||
vt := lookupHandle(pVTab).(*sqliteVTab)
|
||||
vTabCursor, err := vt.vTab.Open()
|
||||
if err != nil {
|
||||
*pzErr = mPrintf("%s", err.Error())
|
||||
@@ -401,12 +407,12 @@ func goVOpen(pVTab unsafe.Pointer, pzErr **C.char) C.uintptr_t {
|
||||
}
|
||||
vtc := sqliteVTabCursor{vt, vTabCursor}
|
||||
*pzErr = nil
|
||||
return C.uintptr_t(newHandle(vt.module.c, &vtc))
|
||||
return C.uintptr_t(uintptr(newHandle(vt.module.c, &vtc)))
|
||||
}
|
||||
|
||||
//export goVBestIndex
|
||||
func goVBestIndex(pVTab unsafe.Pointer, icp unsafe.Pointer) *C.char {
|
||||
vt := lookupHandle(uintptr(pVTab)).(*sqliteVTab)
|
||||
vt := lookupHandle(pVTab).(*sqliteVTab)
|
||||
info := (*C.sqlite3_index_info)(icp)
|
||||
csts := constraints(info)
|
||||
res, err := vt.vTab.BestIndex(csts, orderBys(info))
|
||||
@@ -418,13 +424,17 @@ func goVBestIndex(pVTab unsafe.Pointer, icp unsafe.Pointer) *C.char {
|
||||
}
|
||||
|
||||
// Get a pointer to constraint_usage struct so we can update in place.
|
||||
l := info.nConstraint
|
||||
s := (*[1 << 30]C.struct_sqlite3_index_constraint_usage)(unsafe.Pointer(info.aConstraintUsage))[:l:l]
|
||||
|
||||
slice := *(*[]C.struct_sqlite3_index_constraint_usage)(unsafe.Pointer(&reflect.SliceHeader{
|
||||
Data: uintptr(unsafe.Pointer(info.aConstraintUsage)),
|
||||
Len: int(info.nConstraint),
|
||||
Cap: int(info.nConstraint),
|
||||
}))
|
||||
index := 1
|
||||
for i := C.int(0); i < info.nConstraint; i++ {
|
||||
for i := range slice {
|
||||
if res.Used[i] {
|
||||
s[i].argvIndex = C.int(index)
|
||||
s[i].omit = C.uchar(1)
|
||||
slice[i].argvIndex = C.int(index)
|
||||
slice[i].omit = C.uchar(1)
|
||||
index++
|
||||
}
|
||||
}
|
||||
@@ -445,7 +455,7 @@ func goVBestIndex(pVTab unsafe.Pointer, icp unsafe.Pointer) *C.char {
|
||||
|
||||
//export goVClose
|
||||
func goVClose(pCursor unsafe.Pointer) *C.char {
|
||||
vtc := lookupHandle(uintptr(pCursor)).(*sqliteVTabCursor)
|
||||
vtc := lookupHandle(pCursor).(*sqliteVTabCursor)
|
||||
err := vtc.vTabCursor.Close()
|
||||
if err != nil {
|
||||
return mPrintf("%s", err.Error())
|
||||
@@ -455,13 +465,13 @@ func goVClose(pCursor unsafe.Pointer) *C.char {
|
||||
|
||||
//export goMDestroy
|
||||
func goMDestroy(pClientData unsafe.Pointer) {
|
||||
m := lookupHandle(uintptr(pClientData)).(*sqliteModule)
|
||||
m := lookupHandle(pClientData).(*sqliteModule)
|
||||
m.module.DestroyModule()
|
||||
}
|
||||
|
||||
//export goVFilter
|
||||
func goVFilter(pCursor unsafe.Pointer, idxNum C.int, idxName *C.char, argc C.int, argv **C.sqlite3_value) *C.char {
|
||||
vtc := lookupHandle(uintptr(pCursor)).(*sqliteVTabCursor)
|
||||
vtc := lookupHandle(pCursor).(*sqliteVTabCursor)
|
||||
args := (*[(math.MaxInt32 - 1) / unsafe.Sizeof((*C.sqlite3_value)(nil))]*C.sqlite3_value)(unsafe.Pointer(argv))[:argc:argc]
|
||||
vals := make([]interface{}, 0, argc)
|
||||
for _, v := range args {
|
||||
@@ -480,7 +490,7 @@ func goVFilter(pCursor unsafe.Pointer, idxNum C.int, idxName *C.char, argc C.int
|
||||
|
||||
//export goVNext
|
||||
func goVNext(pCursor unsafe.Pointer) *C.char {
|
||||
vtc := lookupHandle(uintptr(pCursor)).(*sqliteVTabCursor)
|
||||
vtc := lookupHandle(pCursor).(*sqliteVTabCursor)
|
||||
err := vtc.vTabCursor.Next()
|
||||
if err != nil {
|
||||
return mPrintf("%s", err.Error())
|
||||
@@ -490,7 +500,7 @@ func goVNext(pCursor unsafe.Pointer) *C.char {
|
||||
|
||||
//export goVEof
|
||||
func goVEof(pCursor unsafe.Pointer) C.int {
|
||||
vtc := lookupHandle(uintptr(pCursor)).(*sqliteVTabCursor)
|
||||
vtc := lookupHandle(pCursor).(*sqliteVTabCursor)
|
||||
err := vtc.vTabCursor.EOF()
|
||||
if err {
|
||||
return 1
|
||||
@@ -500,7 +510,7 @@ func goVEof(pCursor unsafe.Pointer) C.int {
|
||||
|
||||
//export goVColumn
|
||||
func goVColumn(pCursor, cp unsafe.Pointer, col C.int) *C.char {
|
||||
vtc := lookupHandle(uintptr(pCursor)).(*sqliteVTabCursor)
|
||||
vtc := lookupHandle(pCursor).(*sqliteVTabCursor)
|
||||
c := (*SQLiteContext)(cp)
|
||||
err := vtc.vTabCursor.Column(c, int(col))
|
||||
if err != nil {
|
||||
@@ -511,7 +521,7 @@ func goVColumn(pCursor, cp unsafe.Pointer, col C.int) *C.char {
|
||||
|
||||
//export goVRowid
|
||||
func goVRowid(pCursor unsafe.Pointer, pRowid *C.sqlite3_int64) *C.char {
|
||||
vtc := lookupHandle(uintptr(pCursor)).(*sqliteVTabCursor)
|
||||
vtc := lookupHandle(pCursor).(*sqliteVTabCursor)
|
||||
rowid, err := vtc.vTabCursor.Rowid()
|
||||
if err != nil {
|
||||
return mPrintf("%s", err.Error())
|
||||
@@ -522,7 +532,7 @@ func goVRowid(pCursor unsafe.Pointer, pRowid *C.sqlite3_int64) *C.char {
|
||||
|
||||
//export goVUpdate
|
||||
func goVUpdate(pVTab unsafe.Pointer, argc C.int, argv **C.sqlite3_value, pRowid *C.sqlite3_int64) *C.char {
|
||||
vt := lookupHandle(uintptr(pVTab)).(*sqliteVTab)
|
||||
vt := lookupHandle(pVTab).(*sqliteVTab)
|
||||
|
||||
var tname string
|
||||
if n, ok := vt.vTab.(interface {
|
||||
@@ -642,7 +652,7 @@ func (c *SQLiteConn) CreateModule(moduleName string, module Module) error {
|
||||
mname := C.CString(moduleName)
|
||||
defer C.free(unsafe.Pointer(mname))
|
||||
udm := sqliteModule{c, moduleName, module}
|
||||
rv := C._sqlite3_create_module(c.db, mname, C.uintptr_t(newHandle(c, &udm)))
|
||||
rv := C._sqlite3_create_module(c.db, mname, C.uintptr_t(uintptr(newHandle(c, &udm))))
|
||||
if rv != C.SQLITE_OK {
|
||||
return c.lastError()
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ import (
|
||||
"os"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
@@ -101,6 +102,43 @@ func TestOpen(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestOpenWithVFS(t *testing.T) {
|
||||
filename := t.Name() + ".sqlite"
|
||||
|
||||
if err := os.Remove(filename); err != nil && !os.IsNotExist(err) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.Remove(filename)
|
||||
|
||||
db, err := sql.Open("sqlite3", fmt.Sprintf("file:%s?vfs=hello", filename))
|
||||
if err != nil {
|
||||
t.Fatal("Failed to open", err)
|
||||
}
|
||||
err = db.Ping()
|
||||
if err == nil {
|
||||
t.Fatal("Failed to open", err)
|
||||
}
|
||||
db.Close()
|
||||
|
||||
defer os.Remove(filename)
|
||||
|
||||
var vfs string
|
||||
if runtime.GOOS == "windows" {
|
||||
vfs = "win32-none"
|
||||
} else {
|
||||
vfs = "unix-none"
|
||||
}
|
||||
db, err = sql.Open("sqlite3", fmt.Sprintf("file:%s?vfs=%s", filename, vfs))
|
||||
if err != nil {
|
||||
t.Fatal("Failed to open", err)
|
||||
}
|
||||
err = db.Ping()
|
||||
if err != nil {
|
||||
t.Fatal("Failed to ping", err)
|
||||
}
|
||||
db.Close()
|
||||
}
|
||||
|
||||
func TestOpenNoCreate(t *testing.T) {
|
||||
filename := t.Name() + ".sqlite"
|
||||
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
// +build !cgo
|
||||
// +build upgrade
|
||||
// +build upgrade,ignore
|
||||
|
||||
package main
|
||||
|
||||
|
||||
Reference in New Issue
Block a user