Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add SystemErrno to Error #740

Merged
merged 3 commits into from Dec 17, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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 @@ -238,5 +238,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 @@ -303,8 +303,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