Repository: mattn/go-sqlite3 (c)

Issue:
Different query names
In the [documentation](https://godoc.org/github.com/mattn/go-sqlite3#SQLiteDriver.Open), it is used `shared`, while that in the [project page](https://github.com/mattn/go-sqlite3#connection-string) is being used `cache`.

Pull request:
fix typo in doc comment for Open
Fixes #767. Some gofmt fixes came along for the ride.

Patch:
diff --git a/error.go b/error.go
index 0c4992f..696281c 100644
--- a/error.go
+++ b/error.go
@@ -5,7 +5,15 @@
 
 package sqlite3
 
+/*
+#ifndef USE_LIBSQLITE3
+#include <sqlite3-binding.h>
+#else
+#include <sqlite3.h>
+#endif
+*/
 import "C"
+import "syscall"
 
 // ErrNo inherit errno.
 type ErrNo int
@@ -20,6 +28,7 @@ type ErrNoExtended int
 type Error struct {
 	Code         ErrNo         /* The error code returned by SQLite */
 	ExtendedCode ErrNoExtended /* The extended error code returned by SQLite */
+	SystemErrno  syscall.Errno /* The system errno returned by the OS through SQLite, if applicable */
 	err          string        /* The error string returned by sqlite3_errmsg(),
 	this usually contains more specific details. */
 }
@@ -72,10 +81,16 @@ func (err ErrNoExtended) Error() string {
 }
 
 func (err Error) Error() string {
+	var str string
 	if err.err != "" {
-		return err.err
+		str = err.err
+	} else {
+		str = C.GoString(C.sqlite3_errstr(C.int(err.Code)))
 	}
-	return errorString(err)
+	if err.SystemErrno != 0 {
+		str += ": " + err.SystemErrno.Error()
+	}
+	return str
 }
 
 // result codes from http://www.sqlite.org/c3ref/c_abort_rollback.html
diff --git a/sqlite3.go b/sqlite3.go
index 7f0e7c0..59e3670 100644
--- a/sqlite3.go
+++ b/sqlite3.go
@@ -183,6 +183,12 @@ static int _sqlite3_limit(sqlite3* db, int limitId, int newLimit) {
   return sqlite3_limit(db, limitId, newLimit);
 #endif
 }
+
+#if SQLITE_VERSION_NUMBER < 3012000
+static int sqlite3_system_errno(sqlite3 *db) {
+  return 0;
+}
+#endif
 */
 import "C"
 import (
@@ -198,6 +204,7 @@ import (
 	"strconv"
 	"strings"
 	"sync"
+	"syscall"
 	"time"
 	"unsafe"
 )
@@ -749,15 +756,28 @@ func (c *SQLiteConn) lastError() error {
 	return lastError(c.db)
 }
 
+// Note: may be called with db == nil
 func lastError(db *C.sqlite3) error {
-	rv := C.sqlite3_errcode(db)
+	rv := C.sqlite3_errcode(db) // returns SQLITE_NOMEM if db == nil
 	if rv == C.SQLITE_OK {
 		return nil
 	}
+	extrv := C.sqlite3_extended_errcode(db)    // returns SQLITE_NOMEM if db == nil
+	errStr := C.GoString(C.sqlite3_errmsg(db)) // returns "out of memory" if db == nil
+
+	// https://www.sqlite.org/c3ref/system_errno.html
+	// sqlite3_system_errno is only meaningful if the error code was SQLITE_CANTOPEN,
+	// or it was SQLITE_IOERR and the extended code was not SQLITE_IOERR_NOMEM
+	var systemErrno syscall.Errno
+	if rv == C.SQLITE_CANTOPEN || (rv == C.SQLITE_IOERR && extrv != C.SQLITE_IOERR_NOMEM) {
+		systemErrno = syscall.Errno(C.sqlite3_system_errno(db))
+	}
+
 	return Error{
 		Code:         ErrNo(rv),
-		ExtendedCode: ErrNoExtended(C.sqlite3_extended_errcode(db)),
-		err:          C.GoString(C.sqlite3_errmsg(db)),
+		ExtendedCode: ErrNoExtended(extrv),
+		SystemErrno:  systemErrno,
+		err:          errStr,
 	}
 }
 
@@ -869,10 +889,6 @@ func (c *SQLiteConn) begin(ctx context.Context) (driver.Tx, error) {
 	return &SQLiteTx{c}, nil
 }
 
-func errorString(err Error) string {
-	return C.GoString(C.sqlite3_errstr(C.int(err.Code)))
-}
-
 // Open database and return a new connection.
 //
 // A pragma can take either zero or one argument.
@@ -897,7 +913,7 @@ func errorString(err Error) string {
 //      - rwc
 //      - memory
 //
-//   shared
+//   cache
 //     SQLite Shared-Cache Mode
 //     https://www.sqlite.org/sharedcache.html
 //     Values:
@@ -1342,10 +1358,13 @@ func (d *SQLiteDriver) Open(dsn string) (driver.Conn, error) {
 		mutex|C.SQLITE_OPEN_READWRITE|C.SQLITE_OPEN_CREATE,
 		nil)
 	if rv != 0 {
+		// Save off the error _before_ closing the database.
+		// This is safe even if db is nil.
+		err := lastError(db)
 		if db != nil {
 			C.sqlite3_close_v2(db)
 		}
-		return nil, Error{Code: ErrNo(rv)}
+		return nil, err
 	}
 	if db == nil {
 		return nil, errors.New("sqlite succeeded without returning a database")
@@ -1891,7 +1910,7 @@ func (s *SQLiteStmt) exec(ctx context.Context, args []namedValue) (driver.Result
 		resultCh <- result{r, err}
 	}()
 	select {
-	case rv := <- resultCh:
+	case rv := <-resultCh:
 		return rv.r, rv.err
 	case <-ctx.Done():
 		select {
@@ -1992,7 +2011,7 @@ func (rc *SQLiteRows) Next(dest []driver.Value) error {
 		resultCh <- rc.nextSyncLocked(dest)
 	}()
 	select {
-	case err := <- resultCh:
+	case err := <-resultCh:
 		return err
 	case <-rc.ctx.Done():
 		select {


Test patch:
diff --git a/error_test.go b/error_test.go
index 7cb33ae..3cfad06 100644
--- a/error_test.go
+++ b/error_test.go
@@ -240,5 +240,36 @@ func TestExtendedErrorCodes_Unique(t *testing.T) {
 				extended, expected)
 		}
 	}
+}
+
+func TestError_SystemErrno(t *testing.T) {
+	_, n, _ := Version()
+	if n < 3012000 {
+		t.Skip("sqlite3_system_errno requires sqlite3 >= 3.12.0")
+	}
+
+	// open a non-existent database in read-only mode so we get an IO error.
+	db, err := sql.Open("sqlite3", "file:nonexistent.db?mode=ro")
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer db.Close()
 
+	err = db.Ping()
+	if err == nil {
+		t.Fatal("expected error pinging read-only non-existent database, but got nil")
+	}
+
+	serr, ok := err.(Error)
+	if !ok {
+		t.Fatalf("expected error to be of type Error, but got %[1]T %[1]v", err)
+	}
+
+	if serr.SystemErrno == 0 {
+		t.Fatal("expected SystemErrno to be set")
+	}
+
+	if !os.IsNotExist(serr.SystemErrno) {
+		t.Errorf("expected SystemErrno to be a not exists error, but got %v", serr.SystemErrno)
+	}
 }
diff --git a/sqlite3_test.go b/sqlite3_test.go
index c8ef6d4..4b8fe01 100644
--- a/sqlite3_test.go
+++ b/sqlite3_test.go
@@ -305,8 +305,8 @@ func TestInsert(t *testing.T) {
 
 func TestUpsert(t *testing.T) {
 	_, n, _ := Version()
-	if !(n >= 3024000) {
-		t.Skip("UPSERT requires sqlite3 => 3.24.0")
+	if n < 3024000 {
+		t.Skip("UPSERT requires sqlite3 >= 3.24.0")
 	}
 	tempFilename := TempFilename(t)
 	defer os.Remove(tempFilename)