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
|
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
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
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);
|
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")
|
||||||
|
|
|
@ -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)
|
||||||
|
|
Loading…
Reference in New Issue