Repository: mattn/go-sqlite3
Pull request #878 (2020-11-13 20:00:34)

sqlite3.go: remove -DSQLITE_DISABLE_INTRINSIC: better builds
This "disables the use of compiler-specific built-in functions such
as __builtin_bswap32()" (from the SQLite docs) so this change might
produce slightly better code. My primary motivation, however, is that
the "default" configuration for SQLite, which is widely tested, does
not set this preprocessor macro.

From looking at Github issues, it appears this was added to avoid a
build error on Mac OS X 10.11, in 2017:
https://github.com/mattn/go-sqlite3/issues/386

There have been a number of changes to sqlite3 since then.
I found a machine running Mac OS X 10.11.6. It was able to build and
run the tests in this package with this change.

Mac OS X 10.11 is has not been supported by Apple since 2018
(currently Apple is releasing updates for Mac OS 10.13 and newer; 11
is the current release). However, Go 1.14 is supported, and it
requires Mac OS X 10.11 or newer: https://golang.org/doc/go1.14
Go 1.15 only supports Mac OS 10.12 and newer:
https://golang.org/doc/go1.15


Patch:
diff --git a/.github/workflows/go.yaml b/.github/workflows/go.yaml
index e55191b..579e9ad 100644
--- a/.github/workflows/go.yaml
+++ b/.github/workflows/go.yaml
@@ -33,7 +33,7 @@ jobs:
 
       - name: Add $GOPATH/bin to $PATH
         run: |
-          echo "::add-path::$(go env GOPATH)/bin"
+          echo "$(go env GOPATH)/bin" >> $GITHUB_PATH
 
       - uses: actions/checkout@v2
 
@@ -88,7 +88,7 @@ jobs:
 
       - name: Add $GOPATH/bin to $PATH
         run: |
-          echo "::add-path::$(go env GOPATH)/bin"
+          echo "$(go env GOPATH)/bin" >> $GITHUB_PATH
         shell: msys2 {0}
 
       - uses: actions/checkout@v2
diff --git a/doc.go b/doc.go
index c721f77..ac27633 100644
--- a/doc.go
+++ b/doc.go
@@ -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.
 
 */
diff --git a/sqlite3.go b/sqlite3.go
index 63e1c4f..ca805fa 100644
--- a/sqlite3.go
+++ b/sqlite3.go
@@ -18,7 +18,6 @@ package sqlite3
 #cgo CFLAGS: -DSQLITE_ENABLE_FTS4_UNICODE61
 #cgo CFLAGS: -DSQLITE_TRACE_SIZE_LIMIT=15
 #cgo CFLAGS: -DSQLITE_OMIT_DEPRECATED
-#cgo CFLAGS: -DSQLITE_DISABLE_INTRINSIC
 #cgo CFLAGS: -DSQLITE_DEFAULT_WAL_SYNCHRONOUS=1
 #cgo CFLAGS: -DSQLITE_ENABLE_UPDATE_DELETE_LIMIT
 #cgo CFLAGS: -Wno-deprecated-declarations
@@ -1041,6 +1040,7 @@ func (d *SQLiteDriver) Open(dsn string) (driver.Conn, error) {
 	secureDelete := "DEFAULT"
 	synchronousMode := "NORMAL"
 	writableSchema := -1
+	vfsName := ""
 
 	pos := strings.IndexRune(dsn, '?')
 	if pos >= 1 {
@@ -1364,6 +1364,10 @@ func (d *SQLiteDriver) Open(dsn string) (driver.Conn, error) {
 			}
 		}
 
+		if val := params.Get("vfs"); val != "" {
+			vfsName = val
+		}
+
 		if !strings.HasPrefix(dsn, "file:") {
 			dsn = dsn[:pos]
 		}
@@ -1372,9 +1376,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.


Test patch:
diff --git a/sqlite3_go113_test.go b/sqlite3_go113_test.go
index 74036f8..6f74e6b 100644
--- a/sqlite3_go113_test.go
+++ b/sqlite3_go113_test.go
@@ -49,6 +49,10 @@ func TestBeginTxCancel(t *testing.T) {
 				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{})
diff --git a/sqlite3_go18_test.go b/sqlite3_go18_test.go
index 5ee3d81..8c8c451 100644
--- a/sqlite3_go18_test.go
+++ b/sqlite3_go18_test.go
@@ -150,8 +150,11 @@ func TestExecContextCancel(t *testing.T) {
 	ts := time.Now()
 	initDatabase(t, db, 1000)
 	spent := time.Since(ts)
-	if spent < 100*time.Millisecond {
-		t.Skip("test will be too racy, as ExecContext below will be too fast.")
+	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
@@ -160,14 +163,17 @@ 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
-	ctx, cancel := context.WithTimeout(context.Background(), spent/2)
+	// 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.Log(n, err, time.Since(ts))
+		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())
diff --git a/sqlite3_test.go b/sqlite3_test.go
index d5b0cea..878ec49 100644
--- a/sqlite3_test.go
+++ b/sqlite3_test.go
@@ -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"