Repository: mattn/go-sqlite3
Pull request #873 (2020-11-10 16:30:43)

.github/workflows: stop using deprecated add-path command
Github is disabling the add-path command in workflows on 2020-11-16.
Switch to their new preferred way of doing this, by appending to the
file referred to with $GITHUB_PATH. See:

https://github.blog/changelog/2020-10-01-github-actions-deprecating-set-env-and-add-path-commands/
https://docs.github.com/en/free-pro-team@latest/actions/reference/workflow-commands-for-github-actions#adding-a-system-path

This should fix the following warning from the workflows:

```
.github#L1
The `add-path` command is deprecated and will be disabled on November
16th. Please upgrade to using Environment Files. For more information
see:
https://github.blog/changelog/2020-10-01-github-actions-deprecating-set-env-and-add-path-commands/
```

codecov-io: # [Codecov](https://codecov.io/gh/mattn/go-sqlite3/pull/873?src=pr&el=h1) Report
> Merging [#873](https://codecov.io/gh/mattn/go-sqlite3/pull/873?src=pr&el=desc) (0d2f1b4) into [master](https://codecov.io/gh/mattn/go-sqlite3/commit/92f580b350d88e068bfbe223ea51f64044c0f280?el=desc) (92f580b) will **decrease** coverage by `0.73%`.
> The diff coverage is `n/a`.

[![Impacted file tree graph](https://codecov.io/gh/mattn/go-sqlite3/pull/873/graphs/tree.svg?width=650&height=150&src=pr&token=iuTYvLwNp6)](https://codecov.io/gh/mattn/go-sqlite3/pull/873?src=pr&el=tree)

```diff
@@            Coverage Diff             @@
##           master     #873      +/-   ##
==========================================
- Coverage   47.26%   46.52%   -0.74%     
==========================================
  Files          12       11       -1     
  Lines        1917     1440     -477     
==========================================
- Hits          906      670     -236     
+ Misses        876      636     -240     
+ Partials      135      134       -1     
```


| [Impacted Files](https://codecov.io/gh/mattn/go-sqlite3/pull/873?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [sqlite3\_load\_extension.go](https://codecov.io/gh/mattn/go-sqlite3/pull/873/diff?src=pr&el=tree#diff-c3FsaXRlM19sb2FkX2V4dGVuc2lvbi5nbw==) | `59.37% <0.00%> (-3.67%)` | :arrow_down: |
| [backup.go](https://codecov.io/gh/mattn/go-sqlite3/pull/873/diff?src=pr&el=tree#diff-YmFja3VwLmdv) | `83.33% <0.00%> (-1.29%)` | :arrow_down: |
| [callback.go](https://codecov.io/gh/mattn/go-sqlite3/pull/873/diff?src=pr&el=tree#diff-Y2FsbGJhY2suZ28=) | `58.16% <0.00%> (-0.81%)` | :arrow_down: |
| [error.go](https://codecov.io/gh/mattn/go-sqlite3/pull/873/diff?src=pr&el=tree#diff-ZXJyb3IuZ28=) | `81.81% <0.00%> (-0.54%)` | :arrow_down: |
| [convert.go](https://codecov.io/gh/mattn/go-sqlite3/pull/873/diff?src=pr&el=tree#diff-Y29udmVydC5nbw==) | `0.00% <0.00%> (ø)` | |
| [sqlite3\_go18.go](https://codecov.io/gh/mattn/go-sqlite3/pull/873/diff?src=pr&el=tree#diff-c3FsaXRlM19nbzE4Lmdv) | `88.23% <0.00%> (ø)` | |
| [sqlite3\_type.go](https://codecov.io/gh/mattn/go-sqlite3/pull/873/diff?src=pr&el=tree#diff-c3FsaXRlM190eXBlLmdv) | `0.00% <0.00%> (ø)` | |
| [sqlite3\_context.go](https://codecov.io/gh/mattn/go-sqlite3/pull/873/diff?src=pr&el=tree#diff-c3FsaXRlM19jb250ZXh0Lmdv) | `0.00% <0.00%> (ø)` | |
| [sqlite3\_func\_crypt.go](https://codecov.io/gh/mattn/go-sqlite3/pull/873/diff?src=pr&el=tree#diff-c3FsaXRlM19mdW5jX2NyeXB0Lmdv) | `100.00% <0.00%> (ø)` | |
| [sqlite3\_opt\_userauth\_omit.go](https://codecov.io/gh/mattn/go-sqlite3/pull/873/diff?src=pr&el=tree#diff-c3FsaXRlM19vcHRfdXNlcmF1dGhfb21pdC5nbw==) | `10.00% <0.00%> (ø)` | |
| ... and [2 more](https://codecov.io/gh/mattn/go-sqlite3/pull/873/diff?src=pr&el=tree-more) | |

------

[Continue to review full report at Codecov](https://codecov.io/gh/mattn/go-sqlite3/pull/873?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/mattn/go-sqlite3/pull/873?src=pr&el=footer). Last update [92f580b...0d2f1b4](https://codecov.io/gh/mattn/go-sqlite3/pull/873?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).


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..8cdceac 100644
--- a/sqlite3.go
+++ b/sqlite3.go
@@ -1041,6 +1041,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 +1365,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 +1377,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"