Skip to content

Commit

Permalink
add SystemErrno to Error (#740)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
rittneje authored and mattn committed Dec 17, 2019
1 parent 590d44c commit b4f5cc7
Show file tree
Hide file tree
Showing 4 changed files with 77 additions and 12 deletions.
19 changes: 17 additions & 2 deletions error.go
Expand Up @@ -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
Expand All @@ -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. */
}
Expand Down Expand Up @@ -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
Expand Down
31 changes: 31 additions & 0 deletions error_test.go
Expand Up @@ -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 changes: 27 additions & 8 deletions sqlite3.go
Expand Up @@ -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 (
Expand All @@ -198,6 +204,7 @@ import (
"strconv"
"strings"
"sync"
"syscall"
"time"
"unsafe"
)
Expand Down Expand Up @@ -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,
}
}

Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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")
Expand Down
4 changes: 2 additions & 2 deletions sqlite3_test.go
Expand Up @@ -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)
Expand Down

0 comments on commit b4f5cc7

Please sign in to comment.