aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--error.go19
-rw-r--r--error_test.go31
-rw-r--r--sqlite3.go35
-rw-r--r--sqlite3_test.go4
4 files changed, 77 insertions, 12 deletions
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/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.go b/sqlite3.go
index 7f0e7c0..61eeff7 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.
@@ -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")
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)