Repository: mattn/go-sqlite3
Pull request #872 (2020-11-10 15:56:28)

sqlite3.go: Remove -DSQLITE_ENABLE_FTS4_UNICODE61: not supported
This option was enabled by default in sqlite3 on 2014-07-03.
This setting does nothing. It can now be disabled with
SQLITE_DISABLE_FTS3_UNICODE. See the upstream commit:
https://sqlite.org/src/info/0cc0230ae9cfc976

I think this change was imported into this project with commit
ee9da4840dd680d1fe2927692ea4ab5f8cd7c91c on 2015-06-12.


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..a66c7f7 100644
--- a/sqlite3.go
+++ b/sqlite3.go
@@ -15,10 +15,8 @@ package sqlite3
 #cgo CFLAGS: -DHAVE_USLEEP=1
 #cgo CFLAGS: -DSQLITE_ENABLE_FTS3
 #cgo CFLAGS: -DSQLITE_ENABLE_FTS3_PARENTHESIS
-#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 +1039,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 +1363,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 +1375,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"