Repository: mattn/go-sqlite3
Pull request #763 (2019-12-13 22:12:22)

Fix typo

coveralls: 
[![Coverage Status](https://coveralls.io/builds/27608553/badge)](https://coveralls.io/builds/27608553)

Coverage decreased (-0.01%) to 51.254% when pulling **cdb3e3e30d0f462b630c68ae093066658151e51c on ligurio:patch-1** into **590d44c02bca83987d23f6eab75e6d0ddf95f644 on mattn:master**.


Patch:
diff --git a/_example/limit/limit.go b/_example/limit/limit.go
index 4e4b897..1f33634 100644
--- a/_example/limit/limit.go
+++ b/_example/limit/limit.go
@@ -27,7 +27,7 @@ func createBulkInsertQuery(n int, start int) (query string, args []interface{})
 	return
 }
 
-func bukInsert(db *sql.DB, query string, args []interface{}) (err error) {
+func bulkInsert(db *sql.DB, query string, args []interface{}) (err error) {
 	stmt, err := db.Prepare(query)
 	if err != nil {
 		return
@@ -76,7 +76,7 @@ func main() {
 
 	num := 400
 	query, args := createBulkInsertQuery(num, 0)
-	err = bukInsert(db, query, args)
+	err = bulkInsert(db, query, args)
 	if err != nil {
 		log.Fatal(err)
 	}
@@ -88,7 +88,7 @@ func main() {
 	log.Printf("updated SQLITE_LIMIT_VARIABLE_NUMBER: %d", limitVariableNumber)
 
 	query, args = createBulkInsertQuery(num, num)
-	err = bukInsert(db, query, args)
+	err = bulkInsert(db, query, args)
 	if err != nil {
 		if err != nil {
 			log.Printf("expect failed since SQLITE_LIMIT_VARIABLE_NUMBER is too small: %v", err)
@@ -102,7 +102,7 @@ func main() {
 	log.Printf("updated SQLITE_LIMIT_VARIABLE_NUMBER: %d", limitVariableNumber)
 
 	query, args = createBulkInsertQuery(500, num+num)
-	err = bukInsert(db, query, args)
+	err = bulkInsert(db, query, args)
 	if err != nil {
 		if err != nil {
 			log.Fatal(err)
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)