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 logicuse-ignore
parent
590d44c02b
commit
b4f5cc77d1
19
error.go
19
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
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
35
sqlite3.go
35
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")
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Reference in New Issue