add SystemErrno to Error (#740)

* adding SystemErrno to Error, and fixing error logic when open fails

* fix for old versions of libsqlite3 that do not have sqlite3_system_errno defined

* fixing pre-processor logic
This commit is contained in:
rittneje 2019-12-17 01:58:28 -05:00 committed by mattn
parent 590d44c02b
commit b4f5cc77d1
4 changed files with 78 additions and 13 deletions

View File

@ -5,7 +5,15 @@
package sqlite3 package sqlite3
/*
#ifndef USE_LIBSQLITE3
#include <sqlite3-binding.h>
#else
#include <sqlite3.h>
#endif
*/
import "C" import "C"
import "syscall"
// ErrNo inherit errno. // ErrNo inherit errno.
type ErrNo int type ErrNo int
@ -20,6 +28,7 @@ type ErrNoExtended int
type Error struct { type Error struct {
Code ErrNo /* The error code returned by SQLite */ Code ErrNo /* The error code returned by SQLite */
ExtendedCode ErrNoExtended /* The extended 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(), err string /* The error string returned by sqlite3_errmsg(),
this usually contains more specific details. */ this usually contains more specific details. */
} }
@ -72,10 +81,16 @@ func (err ErrNoExtended) Error() string {
} }
func (err Error) Error() string { func (err Error) Error() string {
var str string
if err.err != "" { 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 // result codes from http://www.sqlite.org/c3ref/c_abort_rollback.html

View File

@ -240,5 +240,36 @@ func TestExtendedErrorCodes_Unique(t *testing.T) {
extended, expected) 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)
}
} }

View File

@ -183,6 +183,12 @@ static int _sqlite3_limit(sqlite3* db, int limitId, int newLimit) {
return sqlite3_limit(db, limitId, newLimit); return sqlite3_limit(db, limitId, newLimit);
#endif #endif
} }
#if SQLITE_VERSION_NUMBER < 3012000
static int sqlite3_system_errno(sqlite3 *db) {
return 0;
}
#endif
*/ */
import "C" import "C"
import ( import (
@ -198,6 +204,7 @@ import (
"strconv" "strconv"
"strings" "strings"
"sync" "sync"
"syscall"
"time" "time"
"unsafe" "unsafe"
) )
@ -749,15 +756,28 @@ func (c *SQLiteConn) lastError() error {
return lastError(c.db) return lastError(c.db)
} }
// Note: may be called with db == nil
func lastError(db *C.sqlite3) error { 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 { if rv == C.SQLITE_OK {
return nil 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{ return Error{
Code: ErrNo(rv), Code: ErrNo(rv),
ExtendedCode: ErrNoExtended(C.sqlite3_extended_errcode(db)), ExtendedCode: ErrNoExtended(extrv),
err: C.GoString(C.sqlite3_errmsg(db)), SystemErrno: systemErrno,
err: errStr,
} }
} }
@ -869,10 +889,6 @@ func (c *SQLiteConn) begin(ctx context.Context) (driver.Tx, error) {
return &SQLiteTx{c}, nil 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. // Open database and return a new connection.
// //
// A pragma can take either zero or one argument. // 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, mutex|C.SQLITE_OPEN_READWRITE|C.SQLITE_OPEN_CREATE,
nil) nil)
if rv != 0 { 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 { if db != nil {
C.sqlite3_close_v2(db) C.sqlite3_close_v2(db)
} }
return nil, Error{Code: ErrNo(rv)} return nil, err
} }
if db == nil { if db == nil {
return nil, errors.New("sqlite succeeded without returning a database") return nil, errors.New("sqlite succeeded without returning a database")

View File

@ -305,8 +305,8 @@ func TestInsert(t *testing.T) {
func TestUpsert(t *testing.T) { func TestUpsert(t *testing.T) {
_, n, _ := Version() _, n, _ := Version()
if !(n >= 3024000) { if n < 3024000 {
t.Skip("UPSERT requires sqlite3 => 3.24.0") t.Skip("UPSERT requires sqlite3 >= 3.24.0")
} }
tempFilename := TempFilename(t) tempFilename := TempFilename(t)
defer os.Remove(tempFilename) defer os.Remove(tempFilename)