Commit 6f513732 authored by gwenn's avatar gwenn

Improve error handling by giving access to details.

parent 1624e5b0
...@@ -4,3 +4,4 @@ _testmain.go ...@@ -4,3 +4,4 @@ _testmain.go
_obj _obj
_test _test
*.swp *.swp
_cgo_*
\ No newline at end of file
...@@ -15,7 +15,6 @@ func init() { ...@@ -15,7 +15,6 @@ func init() {
} }
type Driver struct { type Driver struct {
} }
type connImpl struct { type connImpl struct {
c *Conn c *Conn
......
...@@ -41,6 +41,45 @@ import ( ...@@ -41,6 +41,45 @@ import (
"unsafe" "unsafe"
) )
type ConnError struct {
c *Conn
code Errno
msg string
details string
}
func (e *ConnError) Code() Errno {
return e.code
}
// FIXME it might be the case that a second error occurs on a separate thread in between the time of the first error and the call to this method.
func (e *ConnError) ExtendedCode() int {
return int(C.sqlite3_extended_errcode(e.c.db))
}
// Return Database file name from which the error comes from.
func (e *ConnError) Filename() string {
return e.c.Filename
}
func (e *ConnError) Error() string {
if len(e.details) > 0 {
return fmt.Sprintf("%s: %s (%s)", e.code.Error(), e.msg, e.details)
} else if len(e.msg) > 0 {
return fmt.Sprintf("%s: %s", e.code.Error(), e.msg)
}
return e.code.Error()
}
type StmtError struct {
ConnError
s *Stmt
}
func (e *StmtError) SQL() string {
return e.s.SQL()
}
// Result codes // Result codes
type Errno int type Errno int
...@@ -53,93 +92,102 @@ func (e Errno) Error() string { ...@@ -53,93 +92,102 @@ func (e Errno) Error() string {
} }
var ( var (
ErrError error = Errno(1) // /* SQL error or missing database */ ErrError error = Errno(C.SQLITE_ERROR) /* SQL error or missing database */
ErrInternal error = Errno(2) // /* Internal logic error in SQLite */ ErrInternal error = Errno(C.SQLITE_INTERNAL) /* Internal logic error in SQLite */
ErrPerm error = Errno(3) // /* Access permission denied */ ErrPerm error = Errno(C.SQLITE_PERM) /* Access permission denied */
ErrAbort error = Errno(4) // /* Callback routine requested an abort */ ErrAbort error = Errno(C.SQLITE_ABORT) /* Callback routine requested an abort */
ErrBusy error = Errno(5) // /* The database file is locked */ ErrBusy error = Errno(C.SQLITE_BUSY) /* The database file is locked */
ErrLocked error = Errno(6) // /* A table in the database is locked */ ErrLocked error = Errno(C.SQLITE_LOCKED) /* A table in the database is locked */
ErrNoMem error = Errno(7) // /* A malloc() failed */ ErrNoMem error = Errno(C.SQLITE_NOMEM) /* A malloc() failed */
ErrReadOnly error = Errno(8) // /* Attempt to write a readonly database */ ErrReadOnly error = Errno(C.SQLITE_READONLY) /* Attempt to write a readonly database */
ErrInterrupt error = Errno(9) // /* Operation terminated by sqlite3_interrupt()*/ ErrInterrupt error = Errno(C.SQLITE_INTERRUPT) /* Operation terminated by sqlite3_interrupt()*/
ErrIOErr error = Errno(10) // /* Some kind of disk I/O error occurred */ ErrIOErr error = Errno(C.SQLITE_IOERR) /* Some kind of disk I/O error occurred */
ErrCorrupt error = Errno(11) // /* The database disk image is malformed */ ErrCorrupt error = Errno(C.SQLITE_CORRUPT) /* The database disk image is malformed */
ErrFull error = Errno(13) // /* Insertion failed because database is full */ ErrNotFound error = Errno(C.SQLITE_NOTFOUND) /* Unknown opcode in sqlite3_file_control() */
ErrCantOpen error = Errno(14) // /* Unable to open the database file */ ErrFull error = Errno(C.SQLITE_FULL) /* Insertion failed because database is full */
ErrEmpty error = Errno(16) // /* Database is empty */ ErrCantOpen error = Errno(C.SQLITE_CANTOPEN) /* Unable to open the database file */
ErrSchema error = Errno(17) // /* The database schema changed */ ErrProtocol error = Errno(C.SQLITE_PROTOCOL) /* Database lock protocol error */
ErrTooBig error = Errno(18) // /* String or BLOB exceeds size limit */ ErrEmpty error = Errno(C.SQLITE_EMPTY) /* Database is empty */
ErrConstraint error = Errno(19) // /* Abort due to constraint violation */ ErrSchema error = Errno(C.SQLITE_SCHEMA) /* The database schema changed */
ErrMismatch error = Errno(20) // /* Data type mismatch */ ErrTooBig error = Errno(C.SQLITE_TOOBIG) /* String or BLOB exceeds size limit */
ErrMisuse error = Errno(21) // /* Library used incorrectly */ ErrConstraint error = Errno(C.SQLITE_CONSTRAINT) /* Abort due to constraint violation */
ErrNolfs error = Errno(22) // /* Uses OS features not supported on host */ ErrMismatch error = Errno(C.SQLITE_MISMATCH) /* Data type mismatch */
ErrAuth error = Errno(23) // /* Authorization denied */ ErrMisuse error = Errno(C.SQLITE_MISUSE) /* Library used incorrectly */
ErrFormat error = Errno(24) // /* Auxiliary database format error */ ErrNolfs error = Errno(C.SQLITE_NOLFS) /* Uses OS features not supported on host */
ErrRange error = Errno(25) // /* 2nd parameter to sqlite3_bind out of range */ ErrAuth error = Errno(C.SQLITE_AUTH) /* Authorization denied */
ErrNotDB error = Errno(26) // /* File opened that is not a database file */ ErrFormat error = Errno(C.SQLITE_FORMAT) /* Auxiliary database format error */
Row = Errno(100) // /* sqlite3_step() has another row ready */ ErrRange error = Errno(C.SQLITE_RANGE) /* 2nd parameter to sqlite3_bind out of range */
Done = Errno(101) // /* sqlite3_step() has finished executing */ ErrNotDB error = Errno(C.SQLITE_NOTADB) /* File opened that is not a database file */
Row = Errno(C.SQLITE_ROW) /* sqlite3_step() has another row ready */
Done = Errno(C.SQLITE_DONE) /* sqlite3_step() has finished executing */
ErrSpecific = Errno(-1) /* Gosqlite specific error */
) )
var errText = map[Errno]string{ var errText = map[Errno]string{
1: "SQL error or missing database", C.SQLITE_ERROR: "SQL error or missing database",
2: "Internal logic error in SQLite", C.SQLITE_INTERNAL: "Internal logic error in SQLite",
3: "Access permission denied", C.SQLITE_PERM: "Access permission denied",
4: "Callback routine requested an abort", C.SQLITE_ABORT: "Callback routine requested an abort",
5: "The database file is locked", C.SQLITE_BUSY: "The database file is locked",
6: "A table in the database is locked", C.SQLITE_LOCKED: "A table in the database is locked",
7: "A malloc() failed", C.SQLITE_NOMEM: "A malloc() failed",
8: "Attempt to write a readonly database", C.SQLITE_READONLY: "Attempt to write a readonly database",
9: "Operation terminated by sqlite3_interrupt()", C.SQLITE_INTERRUPT: "Operation terminated by sqlite3_interrupt()",
10: "Some kind of disk I/O error occurred", C.SQLITE_IOERR: "Some kind of disk I/O error occurred",
11: "The database disk image is malformed", C.SQLITE_CORRUPT: "The database disk image is malformed",
12: "NOT USED. Table or record not found", C.SQLITE_NOTFOUND: "Unknown opcode in sqlite3_file_control()",
13: "Insertion failed because database is full", C.SQLITE_FULL: "Insertion failed because database is full",
14: "Unable to open the database file", C.SQLITE_CANTOPEN: "Unable to open the database file",
15: "NOT USED. Database lock protocol error", C.SQLITE_PROTOCOL: "Database lock protocol error",
16: "Database is empty", C.SQLITE_EMPTY: "Database is empty",
17: "The database schema changed", C.SQLITE_SCHEMA: "The database schema changed",
18: "String or BLOB exceeds size limit", C.SQLITE_TOOBIG: "String or BLOB exceeds size limit",
19: "Abort due to constraint violation", C.SQLITE_CONSTRAINT: "Abort due to constraint violation",
20: "Data type mismatch", C.SQLITE_MISMATCH: "Data type mismatch",
21: "Library used incorrectly", C.SQLITE_MISUSE: "Library used incorrectly",
22: "Uses OS features not supported on host", C.SQLITE_NOLFS: "Uses OS features not supported on host",
23: "Authorization denied", C.SQLITE_AUTH: "Authorization denied",
24: "Auxiliary database format error", C.SQLITE_FORMAT: "Auxiliary database format error",
25: "2nd parameter to sqlite3_bind out of range", C.SQLITE_RANGE: "2nd parameter to sqlite3_bind out of range",
26: "File opened that is not a database file", C.SQLITE_NOTADB: "File opened that is not a database file",
100: "sqlite3_step() has another row ready", Row: "sqlite3_step() has another row ready",
101: "sqlite3_step() has finished executing", Done: "sqlite3_step() has finished executing",
} ErrSpecific: "Gosqlite specific error",
}
func (c *Conn) error(rv C.int, detail ...string) error {
if c == nil || c.db == nil { func (c *Conn) error(rv C.int, details ...string) error {
if c == nil {
return errors.New("nil sqlite database") return errors.New("nil sqlite database")
} }
if rv == C.SQLITE_OK { if rv == C.SQLITE_OK {
return nil return nil
} }
if rv == 21 { // misuse err := &ConnError{c: c, code: Errno(rv), msg: C.GoString(C.sqlite3_errmsg(c.db))}
return Errno(rv) if len(details) > 0 {
err.details = details[0]
} }
if len(detail) > 0 { return err
return fmt.Errorf("%s: %s @ %s", Errno(rv).Error(), C.GoString(C.sqlite3_errmsg(c.db)), detail[0]) }
}
return fmt.Errorf("%s: %s", Errno(rv).Error(), C.GoString(C.sqlite3_errmsg(c.db))) func (c *Conn) specificError(msg string, a ...interface{}) error {
return &ConnError{c: c, code: ErrSpecific, msg: fmt.Sprintf(msg, a...)}
} }
// Return error code or message func (s *Stmt) specificError(msg string, a ...interface{}) error {
// Calls http://sqlite.org/c3ref/errcode.html return &StmtError{ConnError{c: s.c, code: ErrSpecific, msg: fmt.Sprintf(msg, a...)}, s}
func (c *Conn) Error() error { }
if c == nil || c.db == nil {
func (c *Conn) LastError() error {
if c == nil {
return errors.New("nil sqlite database") return errors.New("nil sqlite database")
} }
return c.error(C.sqlite3_errcode(c.db)) return &ConnError{c: c, code: Errno(C.sqlite3_errcode(c.db)), msg: C.GoString(C.sqlite3_errmsg(c.db))}
} }
// Database connection handle // Database connection handle
type Conn struct { type Conn struct {
db *C.sqlite3 db *C.sqlite3
Filename string
authorizer *sqliteAuthorizer authorizer *sqliteAuthorizer
busyHandler *sqliteBusyHandler busyHandler *sqliteBusyHandler
profile *sqliteProfile profile *sqliteProfile
...@@ -219,17 +267,13 @@ func OpenVfs(filename string, vfsname string, flags ...OpenFlag) (*Conn, error) ...@@ -219,17 +267,13 @@ func OpenVfs(filename string, vfsname string, flags ...OpenFlag) (*Conn, error)
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")
} }
return &Conn{db: db}, nil return &Conn{db: db, Filename: filename}, nil
} }
// Set a busy timeout // Set a busy timeout
// Calls http://sqlite.org/c3ref/busy_timeout.html // Calls http://sqlite.org/c3ref/busy_timeout.html
func (c *Conn) BusyTimeout(ms int) error { func (c *Conn) BusyTimeout(ms int) error {
rv := C.sqlite3_busy_timeout(c.db, C.int(ms)) return c.error(C.sqlite3_busy_timeout(c.db, C.int(ms)))
if rv == C.SQLITE_OK {
return nil
}
return Errno(rv)
} }
// Enable or disable the enforcement of foreign key constraints // Enable or disable the enforcement of foreign key constraints
...@@ -240,6 +284,7 @@ func (c *Conn) BusyTimeout(ms int) error { ...@@ -240,6 +284,7 @@ func (c *Conn) BusyTimeout(ms int) error {
func (c *Conn) EnableFKey(b bool) (bool, error) { func (c *Conn) EnableFKey(b bool) (bool, error) {
return c.queryOrSetEnableFKey(btocint(b)) return c.queryOrSetEnableFKey(btocint(b))
} }
// Calls sqlite3_db_config(db, SQLITE_DBCONFIG_ENABLE_FKEY, -1) // Calls sqlite3_db_config(db, SQLITE_DBCONFIG_ENABLE_FKEY, -1)
// Another way is PRAGMA foreign_keys; // Another way is PRAGMA foreign_keys;
// //
...@@ -253,7 +298,13 @@ func (c *Conn) queryOrSetEnableFKey(i C.int) (bool, error) { ...@@ -253,7 +298,13 @@ func (c *Conn) queryOrSetEnableFKey(i C.int) (bool, error) {
if rv == C.SQLITE_OK { if rv == C.SQLITE_OK {
return (ok == 1), nil return (ok == 1), nil
} }
return false, Errno(rv) return false, c.error(rv)
}
// Enable or disable the extended result codes feature of SQLite.
// Calls http://sqlite.org/c3ref/extended_result_codes.html
func (c *Conn) EnableExtendedResultCodes(b bool) error {
return c.error(C.sqlite3_extended_result_codes(c.db, btocint(b)))
} }
// Prepare and execute one parameterized statement or many statements (separated by semi-colon). // Prepare and execute one parameterized statement or many statements (separated by semi-colon).
...@@ -286,7 +337,7 @@ func (c *Conn) Exec(cmd string, args ...interface{}) error { ...@@ -286,7 +337,7 @@ func (c *Conn) Exec(cmd string, args ...interface{}) error {
if len(s.tail) > 0 { if len(s.tail) > 0 {
if len(args) > 0 { if len(args) > 0 {
s.Finalize() s.Finalize()
return errors.New("Cannot execute multiple statements when args are specified") return c.specificError("Cannot execute multiple statements when args are specified")
} }
} }
cmd = s.tail cmd = s.tail
...@@ -312,6 +363,7 @@ func (c *Conn) Exists(query string, args ...interface{}) (bool, error) { ...@@ -312,6 +363,7 @@ func (c *Conn) Exists(query string, args ...interface{}) (bool, error) {
func (c *Conn) Changes() int { func (c *Conn) Changes() int {
return int(C.sqlite3_changes(c.db)) return int(C.sqlite3_changes(c.db))
} }
// Total number of rows Modified // Total number of rows Modified
// Calls http://sqlite.org/c3ref/total_changes.html // Calls http://sqlite.org/c3ref/total_changes.html
func (c *Conn) TotalChanges() int { func (c *Conn) TotalChanges() int {
...@@ -367,6 +419,7 @@ func (c *Conn) Commit() error { ...@@ -367,6 +419,7 @@ func (c *Conn) Commit() error {
// TODO Check autocommit? // TODO Check autocommit?
return c.exec("COMMIT") return c.exec("COMMIT")
} }
// Rollback transaction // Rollback transaction
func (c *Conn) Rollback() error { func (c *Conn) Rollback() error {
// TODO Check autocommit? // TODO Check autocommit?
...@@ -376,11 +429,7 @@ func (c *Conn) Rollback() error { ...@@ -376,11 +429,7 @@ func (c *Conn) Rollback() error {
func (c *Conn) exec(cmd string) error { func (c *Conn) exec(cmd string) error {
cmdstr := C.CString(cmd) cmdstr := C.CString(cmd)
defer C.free(unsafe.Pointer(cmdstr)) defer C.free(unsafe.Pointer(cmdstr))
rv := C.sqlite3_exec(c.db, cmdstr, nil, nil, nil) return c.error(C.sqlite3_exec(c.db, cmdstr, nil, nil, nil))
if rv != C.SQLITE_OK {
return c.error(rv)
}
return nil
} }
// SQL statement // SQL statement
...@@ -407,7 +456,7 @@ type Stmt struct { ...@@ -407,7 +456,7 @@ type Stmt struct {
// Calls sqlite3_prepare_v2 and sqlite3_bind_* // Calls sqlite3_prepare_v2 and sqlite3_bind_*
// http://sqlite.org/c3ref/prepare.html, http://sqlite.org/c3ref/bind_blob.html, // http://sqlite.org/c3ref/prepare.html, http://sqlite.org/c3ref/bind_blob.html,
func (c *Conn) Prepare(cmd string, args ...interface{}) (*Stmt, error) { func (c *Conn) Prepare(cmd string, args ...interface{}) (*Stmt, error) {
if c == nil || c.db == nil { if c == nil {
return nil, errors.New("nil sqlite database") return nil, errors.New("nil sqlite database")
} }
cmdstr := C.CString(cmd) cmdstr := C.CString(cmd)
...@@ -492,7 +541,7 @@ func (s *Stmt) BindParameterIndex(name string) (int, error) { ...@@ -492,7 +541,7 @@ func (s *Stmt) BindParameterIndex(name string) (int, error) {
defer C.free(unsafe.Pointer(cname)) defer C.free(unsafe.Pointer(cname))
index = int(C.sqlite3_bind_parameter_index(s.stmt, cname)) index = int(C.sqlite3_bind_parameter_index(s.stmt, cname))
if index == 0 { if index == 0 {
return -1, fmt.Errorf("invalid parameter name: %s", name) return -1, s.specificError("invalid parameter name: %s", name)
} }
s.params[name] = index s.params[name] = index
return index, nil return index, nil
...@@ -504,7 +553,7 @@ func (s *Stmt) BindParameterIndex(name string) (int, error) { ...@@ -504,7 +553,7 @@ func (s *Stmt) BindParameterIndex(name string) (int, error) {
func (s *Stmt) BindParameterName(i int) (string, error) { func (s *Stmt) BindParameterName(i int) (string, error) {
name := C.sqlite3_bind_parameter_name(s.stmt, C.int(i)) name := C.sqlite3_bind_parameter_name(s.stmt, C.int(i))
if name == nil { if name == nil {
return "", fmt.Errorf("invalid parameter index: %d", i) return "", s.specificError("invalid parameter index: %d", i)
} }
return C.GoString(name), nil return C.GoString(name), nil
} }
...@@ -516,12 +565,12 @@ func (s *Stmt) NamedBind(args ...interface{}) error { ...@@ -516,12 +565,12 @@ func (s *Stmt) NamedBind(args ...interface{}) error {
return err return err
} }
if len(args)%2 != 0 { if len(args)%2 != 0 {
return errors.New("Expected an even number of arguments") return s.specificError("Expected an even number of arguments")
} }
for i := 0; i < len(args); i += 2 { for i := 0; i < len(args); i += 2 {
name, ok := args[i].(string) name, ok := args[i].(string)
if !ok { if !ok {
return errors.New("non-string param name") return s.specificError("non-string param name")
} }
index, err := s.BindParameterIndex(name) // How to look up only once for one statement ? index, err := s.BindParameterIndex(name) // How to look up only once for one statement ?
if err != nil { if err != nil {
...@@ -545,8 +594,8 @@ func (s *Stmt) Bind(args ...interface{}) error { ...@@ -545,8 +594,8 @@ func (s *Stmt) Bind(args ...interface{}) error {
} }
n := s.BindParameterCount() n := s.BindParameterCount()
if n != len(args) { // What happens when the number of arguments is less than the number of parameters? if n != len(args) {
return fmt.Errorf("incorrect argument count for Stmt.Bind: have %d want %d", len(args), n) return s.specificError("incorrect argument count for Stmt.Bind: have %d want %d", len(args), n)
} }
for i, v := range args { for i, v := range args {
...@@ -591,12 +640,9 @@ func (s *Stmt) BindByIndex(index int, value interface{}) error { ...@@ -591,12 +640,9 @@ func (s *Stmt) BindByIndex(index int, value interface{}) error {
case ZeroBlobLength: case ZeroBlobLength:
rv = C.sqlite3_bind_zeroblob(s.stmt, i, C.int(value)) rv = C.sqlite3_bind_zeroblob(s.stmt, i, C.int(value))
default: default:
return fmt.Errorf("unsupported type in Bind: %s", reflect.TypeOf(value)) return s.specificError("unsupported type in Bind: %s", reflect.TypeOf(value))
}
if rv != C.SQLITE_OK {
return s.c.error(rv)
} }
return nil return s.c.error(rv)
} }
// Evaluate an SQL statement // Evaluate an SQL statement
...@@ -632,21 +678,13 @@ func (s *Stmt) Next() (bool, error) { ...@@ -632,21 +678,13 @@ func (s *Stmt) Next() (bool, error) {
// Reset a prepared statement // Reset a prepared statement
// Calls http://sqlite.org/c3ref/reset.html // Calls http://sqlite.org/c3ref/reset.html
func (s *Stmt) Reset() error { func (s *Stmt) Reset() error {
rv := C.sqlite3_reset(s.stmt) return s.c.error(C.sqlite3_reset(s.stmt))
if rv != C.SQLITE_OK {
return s.c.error(rv)
}
return nil
} }
// Reset all bindings on a prepared statement // Reset all bindings on a prepared statement
// Calls http://sqlite.org/c3ref/clear_bindings.html // Calls http://sqlite.org/c3ref/clear_bindings.html
func (s *Stmt) ClearBindings() error { func (s *Stmt) ClearBindings() error {
rv := C.sqlite3_clear_bindings(s.stmt) return s.c.error(C.sqlite3_clear_bindings(s.stmt))
if rv != C.SQLITE_OK {
return s.c.error(rv)
}
return nil
} }
// Number of columns in a result set // Number of columns in a result set
...@@ -654,6 +692,7 @@ func (s *Stmt) ClearBindings() error { ...@@ -654,6 +692,7 @@ func (s *Stmt) ClearBindings() error {
func (s *Stmt) ColumnCount() int { func (s *Stmt) ColumnCount() int {
return int(C.sqlite3_column_count(s.stmt)) return int(C.sqlite3_column_count(s.stmt))
} }
// Number of columns in a result set // Number of columns in a result set
// Calls http://sqlite.org/c3ref/data_count.html // Calls http://sqlite.org/c3ref/data_count.html
func (s *Stmt) DataCount() int { func (s *Stmt) DataCount() int {
...@@ -728,12 +767,12 @@ func (s *Stmt) ColumnType(index int) Type { ...@@ -728,12 +767,12 @@ func (s *Stmt) ColumnType(index int) Type {
// http://sqlite.org/c3ref/column_blob.html // http://sqlite.org/c3ref/column_blob.html
func (s *Stmt) NamedScan(args ...interface{}) error { func (s *Stmt) NamedScan(args ...interface{}) error {
if len(args)%2 != 0 { if len(args)%2 != 0 {
return errors.New("Expected an even number of arguments") return s.specificError("Expected an even number of arguments")
} }
for i := 0; i < len(args); i += 2 { for i := 0; i < len(args); i += 2 {
name, ok := args[i].(string) name, ok := args[i].(string)
if !ok { if !ok {
return errors.New("non-string field name") return s.specificError("non-string field name")
} }
index, err := s.ColumnIndex(name) // How to look up only once for one statement ? index, err := s.ColumnIndex(name) // How to look up only once for one statement ?
if err != nil { if err != nil {
...@@ -768,7 +807,7 @@ func (s *Stmt) NamedScan(args ...interface{}) error { ...@@ -768,7 +807,7 @@ func (s *Stmt) NamedScan(args ...interface{}) error {
func (s *Stmt) Scan(args ...interface{}) error { func (s *Stmt) Scan(args ...interface{}) error {
n := s.ColumnCount() n := s.ColumnCount()
if n != len(args) { // What happens when the number of arguments is less than the number of columns? if n != len(args) { // What happens when the number of arguments is less than the number of columns?
return fmt.Errorf("incorrect argument count for Stmt.Scan: have %d want %d", len(args), n) return s.specificError("incorrect argument count for Stmt.Scan: have %d want %d", len(args), n)
} }
for i, v := range args { for i, v := range args {
...@@ -802,7 +841,7 @@ func (s *Stmt) ColumnIndex(name string) (int, error) { ...@@ -802,7 +841,7 @@ func (s *Stmt) ColumnIndex(name string) (int, error) {
if ok { if ok {
return index, nil return index, nil
} }
return 0, fmt.Errorf("invalid column name: %s", name) return 0, s.specificError("invalid column name: %s", name)
} }
// Returns true when column is null and Stmt.CheckNull is activated. // Returns true when column is null and Stmt.CheckNull is activated.
...@@ -852,7 +891,7 @@ func (s *Stmt) ScanByIndex(index int, value interface{}) (bool, error) { ...@@ -852,7 +891,7 @@ func (s *Stmt) ScanByIndex(index int, value interface{}) (bool, error) {
*value = s.ScanValue(index) *value = s.ScanValue(index)
isNull = *value == nil isNull = *value == nil
default: default:
return false, fmt.Errorf("unsupported type in Scan: %s", reflect.TypeOf(value)) return false, s.specificError("unsupported type in Scan: %s", reflect.TypeOf(value))
} }
return isNull, err return isNull, err
} }
...@@ -1037,14 +1076,14 @@ func (s *Stmt) checkTypeMismatch(source, target Type) error { ...@@ -1037,14 +1076,14 @@ func (s *Stmt) checkTypeMismatch(source, target Type) error {
case Text: case Text:
fallthrough fallthrough
case Blob: case Blob:
return s.c.error(20) return s.specificError("Type mismatch, source %s vs target %s", source, target)
} }
case Float: case Float:
switch source { switch source {
case Text: case Text:
fallthrough fallthrough
case Blob: case Blob:
return s.c.error(20) return s.specificError("Type mismatch, source %s vs target %s", source, target)
} }
} }
return nil return nil
...@@ -1079,7 +1118,7 @@ func (c *Conn) Close() error { ...@@ -1079,7 +1118,7 @@ func (c *Conn) Close() error {
// Dangling statements // Dangling statements
stmt := C.sqlite3_next_stmt(c.db, nil) stmt := C.sqlite3_next_stmt(c.db, nil)
for stmt != nil { for stmt != nil {
Log(21, "Dangling statement") Log(C.SQLITE_MISUSE, "Dangling statement")
C.sqlite3_finalize(stmt) C.sqlite3_finalize(stmt)
stmt = C.sqlite3_next_stmt(c.db, stmt) stmt = C.sqlite3_next_stmt(c.db, stmt)
} }
...@@ -1103,6 +1142,7 @@ func (s *Stmt) ReadOnly() bool { ...@@ -1103,6 +1142,7 @@ func (s *Stmt) ReadOnly() bool {
func (c *Conn) EnableLoadExtension(b bool) { func (c *Conn) EnableLoadExtension(b bool) {
C.sqlite3_enable_load_extension(c.db, btocint(b)) C.sqlite3_enable_load_extension(c.db, btocint(b))
} }
// Load an extension // Load an extension
// Calls http://sqlite.org/c3ref/load_extension.html // Calls http://sqlite.org/c3ref/load_extension.html
func (c *Conn) LoadExtension(file string, proc ...string) error { func (c *Conn) LoadExtension(file string, proc ...string) error {
...@@ -1117,7 +1157,7 @@ func (c *Conn) LoadExtension(file string, proc ...string) error { ...@@ -1117,7 +1157,7 @@ func (c *Conn) LoadExtension(file string, proc ...string) error {
rv := C.sqlite3_load_extension(c.db, cfile, cproc, &errMsg) rv := C.sqlite3_load_extension(c.db, cfile, cproc, &errMsg)
if rv != C.SQLITE_OK { if rv != C.SQLITE_OK {
defer C.sqlite3_free(unsafe.Pointer(errMsg)) defer C.sqlite3_free(unsafe.Pointer(errMsg))
return fmt.Errorf("%s: %s", Errno(rv).Error(), C.GoString(errMsg)) return c.error(rv, C.GoString(errMsg))
} }
return nil return nil
} }
...@@ -1146,14 +1186,14 @@ func (c *Conn) IntegrityCheck(max int, quick bool) error { ...@@ -1146,14 +1186,14 @@ func (c *Conn) IntegrityCheck(max int, quick bool) error {
if ok, err := s.Next(); err != nil { if ok, err := s.Next(); err != nil {
return err return err
} else if !ok { } else if !ok {
return errors.New("Integrity check failed (no result)") return c.specificError("Integrity check failed (no result)")
} }
msg, null := s.ScanText(0) msg, null := s.ScanText(0)
if null { if null {
return errors.New("Integrity check failed (null result)") return c.specificError("Integrity check failed (null result)")
} }
if msg != "ok" { if msg != "ok" {
return fmt.Errorf("Integrity check failed (%s)", msg) return c.specificError("Integrity check failed (%s)", msg)
} }
return nil return nil
} }
......
...@@ -2,6 +2,7 @@ package sqlite_test ...@@ -2,6 +2,7 @@ package sqlite_test
import ( import (
. "github.com/gwenn/gosqlite" . "github.com/gwenn/gosqlite"
"reflect"
"strings" "strings"
"testing" "testing"
) )
...@@ -53,6 +54,14 @@ func TestEnableFKey(t *testing.T) { ...@@ -53,6 +54,14 @@ func TestEnableFKey(t *testing.T) {
} }
} }
func TestEnableExtendedResultCodes(t *testing.T) {
db := open(t)
defer db.Close()
if err := db.EnableExtendedResultCodes(true); err != nil {
t.Fatalf("cannot enabled extended result codes: %s", err)
}
}
func TestIntegrityCheck(t *testing.T) { func TestIntegrityCheck(t *testing.T) {
db := open(t) db := open(t)
defer db.Close() defer db.Close()
...@@ -106,7 +115,7 @@ func TestInsert(t *testing.T) { ...@@ -106,7 +115,7 @@ func TestInsert(t *testing.T) {
} }
c := db.Changes() c := db.Changes()
if c != 1 { if c != 1 {
t.Errorf("insert error: %d <> 1", c) t.Errorf("insert error: %d but got 1", c)
} }
} }
if err := db.Commit(); err != nil { if err := db.Commit(); err != nil {
...@@ -115,7 +124,7 @@ func TestInsert(t *testing.T) { ...@@ -115,7 +124,7 @@ func TestInsert(t *testing.T) {
lastId := db.LastInsertRowid() lastId := db.LastInsertRowid()
if lastId != 1000 { if lastId != 1000 {
t.Errorf("last insert row id error: %d <> 1000", lastId) t.Errorf("last insert row id error: %d but got 1000", lastId)
} }
cs, _ := db.Prepare("SELECT COUNT(*) FROM test") cs, _ := db.Prepare("SELECT COUNT(*) FROM test")
...@@ -123,11 +132,11 @@ func TestInsert(t *testing.T) { ...@@ -123,11 +132,11 @@ func TestInsert(t *testing.T) {
paramCount := cs.BindParameterCount() paramCount := cs.BindParameterCount()
if paramCount != 0 { if paramCount != 0 {
t.Errorf("bind parameter count error: %d <> 0", paramCount) t.Errorf("bind parameter count error: %d but got 0", paramCount)
} }
columnCount := cs.ColumnCount() columnCount := cs.ColumnCount()
if columnCount != 1 { if columnCount != 1 {
t.Errorf("column count error: %d <> 1", columnCount) t.Errorf("column count error: %d but got 1", columnCount)
} }
if !Must(cs.Next()) { if !Must(cs.Next()) {
...@@ -162,15 +171,15 @@ func TestInsertWithStatement(t *testing.T) { ...@@ -162,15 +171,15 @@ func TestInsertWithStatement(t *testing.T) {
paramCount := s.BindParameterCount() paramCount := s.BindParameterCount()
if paramCount != 3 { if paramCount != 3 {
t.Errorf("bind parameter count error: %d <> 3", paramCount) t.Errorf("bind parameter count error: %d but got 3", paramCount)
} }
firstParamName, berr := s.BindParameterName(1) firstParamName, berr := s.BindParameterName(1)
if firstParamName != ":f" { if firstParamName != ":f" {
t.Errorf("bind parameter name error: %s <> ':f' (%s)", firstParamName, berr) t.Errorf("bind parameter name error: %s but got ':f' (%s)", firstParamName, berr)
} }
lastParamIndex, berr := s.BindParameterIndex(":s") lastParamIndex, berr := s.BindParameterIndex(":s")
if lastParamIndex != 3 { if lastParamIndex != 3 {
t.Errorf("bind parameter name error: %d <> 3 (%s)", lastParamIndex, berr) t.Errorf("bind parameter name error: %d but got 3 (%s)", lastParamIndex, berr)
} }
db.Begin() db.Begin()
...@@ -180,7 +189,7 @@ func TestInsertWithStatement(t *testing.T) { ...@@ -180,7 +189,7 @@ func TestInsertWithStatement(t *testing.T) {
t.Fatalf("insert error: %s", ierr) t.Fatalf("insert error: %s", ierr)
} }
if c != 1 { if c != 1 {
t.Errorf("insert error: %d <> 1", c) t.Errorf("insert error: %d but got 1", c)
} }
} }
...@@ -209,11 +218,11 @@ func TestInsertWithStatement(t *testing.T) { ...@@ -209,11 +218,11 @@ func TestInsertWithStatement(t *testing.T) {
defer rs.Finalize() defer rs.Finalize()
columnCount := rs.ColumnCount() columnCount := rs.ColumnCount()
if columnCount != 3 { if columnCount != 3 {
t.Errorf("column count error: %d <> 3", columnCount) t.Errorf("column count error: %d but got 3", columnCount)
} }
secondColumnName := rs.ColumnName(1) secondColumnName := rs.ColumnName(1)
if secondColumnName != "int_num" { if secondColumnName != "int_num" {
t.Errorf("column name error: %s <> 'int_num'", secondColumnName) t.Errorf("column name error: %s but got 'int_num'", secondColumnName)
} }
if Must(rs.Next()) { if Must(rs.Next()) {
...@@ -222,13 +231,13 @@ func TestInsertWithStatement(t *testing.T) { ...@@ -222,13 +231,13 @@ func TestInsertWithStatement(t *testing.T) {
var sstr string var sstr string
rs.Scan(&fnum, &inum, &sstr) rs.Scan(&fnum, &inum, &sstr)
if fnum != 0 { if fnum != 0 {
t.Errorf("Expected 0 <> %f\n", fnum) t.Errorf("Expected 0 but got %f\n", fnum)
} }
if inum != 0 { if inum != 0 {
t.Errorf("Expected 0 <> %d\n", inum) t.Errorf("Expected 0 but got %d\n", inum)
} }
if sstr != "hello" { if sstr != "hello" {
t.Errorf("Expected 'hello' <> %s\n", sstr) t.Errorf("Expected 'hello' but got %s\n", sstr)
} }
} }
if Must(rs.Next()) { if Must(rs.Next()) {
...@@ -237,13 +246,13 @@ func TestInsertWithStatement(t *testing.T) { ...@@ -237,13 +246,13 @@ func TestInsertWithStatement(t *testing.T) {
var sstr string var sstr string
rs.NamedScan("a_string", &sstr, "float_num", &fnum, "int_num", &inum) rs.NamedScan("a_string", &sstr, "float_num", &fnum, "int_num", &inum)
if fnum != 3.14 { if fnum != 3.14 {
t.Errorf("Expected 3.14 <> %f\n", fnum) t.Errorf("Expected 3.14 but got %f\n", fnum)
} }
if inum != 1 { if inum != 1 {
t.Errorf("Expected 1 <> %d\n", inum) t.Errorf("Expected 1 but got %d\n", inum)
} }
if sstr != "hello" { if sstr != "hello" {
t.Errorf("Expected 'hello' <> %s\n", sstr) t.Errorf("Expected 'hello' but got %s\n", sstr)
} }
} }
if 999 != rs.Status(STMTSTATUS_FULLSCAN_STEP, false) { if 999 != rs.Status(STMTSTATUS_FULLSCAN_STEP, false) {
...@@ -305,7 +314,7 @@ func TestBlob(t *testing.T) { ...@@ -305,7 +314,7 @@ func TestBlob(t *testing.T) {
t.Fatalf("blob read error: %s", err) t.Fatalf("blob read error: %s", err)
} }
if n != 10 { if n != 10 {
t.Fatalf("Expected 10 bytes <> %d", n) t.Fatalf("Expected 10 bytes but got %d", n)
} }
//fmt.Printf("%#v\n", content) //fmt.Printf("%#v\n", content)
br.Close() br.Close()
...@@ -328,19 +337,19 @@ func TestScanColumn(t *testing.T) { ...@@ -328,19 +337,19 @@ func TestScanColumn(t *testing.T) {
if null { if null {
t.Errorf("Expected not null value") t.Errorf("Expected not null value")
} else if i1 != 1 { } else if i1 != 1 {
t.Errorf("Expected 1 <> %d\n", i1) t.Errorf("Expected 1 but got %d\n", i1)
} }
null = Must(s.ScanByIndex(1, &i2 /*, true*/ )) null = Must(s.ScanByIndex(1, &i2 /*, true*/ ))
if !null { if !null {
t.Errorf("Expected null value") t.Errorf("Expected null value")
} else if i2 != 0 { } else if i2 != 0 {
t.Errorf("Expected 0 <> %d\n", i2) t.Errorf("Expected 0 but got %d\n", i2)
} }
null = Must(s.ScanByIndex(2, &i3 /*, true*/ )) null = Must(s.ScanByIndex(2, &i3 /*, true*/ ))
if null { if null {
t.Errorf("Expected not null value") t.Errorf("Expected not null value")
} else if i3 != 0 { } else if i3 != 0 {
t.Errorf("Expected 0 <> %d\n", i3) t.Errorf("Expected 0 but got %d\n", i3)
} }
} }
...@@ -361,19 +370,48 @@ func TestNamedScanColumn(t *testing.T) { ...@@ -361,19 +370,48 @@ func TestNamedScanColumn(t *testing.T) {
if null { if null {
t.Errorf("Expected not null value") t.Errorf("Expected not null value")
} else if i1 != 1 { } else if i1 != 1 {
t.Errorf("Expected 1 <> %d\n", i1) t.Errorf("Expected 1 but got %d\n", i1)
} }
null = Must(s.ScanByName("i2", &i2 /*, true*/ )) null = Must(s.ScanByName("i2", &i2 /*, true*/ ))
if !null { if !null {
t.Errorf("Expected null value") t.Errorf("Expected null value")
} else if i2 != 0 { } else if i2 != 0 {
t.Errorf("Expected 0 <> %d\n", i2) t.Errorf("Expected 0 but got %d\n", i2)
} }
null = Must(s.ScanByName("i3", &i3 /*, true*/ )) null = Must(s.ScanByName("i3", &i3 /*, true*/ ))
if null { if null {
t.Errorf("Expected not null value") t.Errorf("Expected not null value")
} else if i3 != 0 { } else if i3 != 0 {
t.Errorf("Expected 0 <> %d\n", i3) t.Errorf("Expected 0 but got %d\n", i3)
}
}
func TestScanCheck(t *testing.T) {
db := open(t)
defer db.Close()
s, err := db.Prepare("select 'hello'")
if err != nil {
t.Fatalf("prepare error: %s", err)
}
defer s.Finalize()
if !Must(s.Next()) {
t.Fatal("no result")
}
var i int
_, err = s.ScanByIndex(0, &i)
if serr, ok := err.(*StmtError); ok {
if serr.Filename() != "" {
t.Errorf("Expected '' but got '%s'", serr.Filename())
}
if serr.Code() != ErrSpecific {
t.Errorf("Expected %s but got %s", ErrSpecific, serr.Code())
}
if serr.SQL() != s.SQL() {
t.Errorf("Expected %s but got %s", s.SQL(), serr.SQL())
}
} else {
t.Errorf("Expected StmtError but got %s", reflect.TypeOf(err))
} }
} }
......
...@@ -265,6 +265,7 @@ func (s *Stmt) Status(op StmtStatus, reset bool) int { ...@@ -265,6 +265,7 @@ func (s *Stmt) Status(op StmtStatus, reset bool) int {
func MemoryUsed() int64 { func MemoryUsed() int64 {
return int64(C.sqlite3_memory_used()) return int64(C.sqlite3_memory_used())
} }
// Memory allocator statistics // Memory allocator statistics
// Calls sqlite3_memory_highwater: http://sqlite.org/c3ref/memory_highwater.html // Calls sqlite3_memory_highwater: http://sqlite.org/c3ref/memory_highwater.html
func MemoryHighwater(reset bool) int64 { func MemoryHighwater(reset bool) int64 {
...@@ -276,6 +277,7 @@ func MemoryHighwater(reset bool) int64 { ...@@ -276,6 +277,7 @@ func MemoryHighwater(reset bool) int64 {
func SoftHeapLimit() int64 { func SoftHeapLimit() int64 {
return SetSoftHeapLimit(-1) return SetSoftHeapLimit(-1)
} }
// Impose a limit on heap size // Impose a limit on heap size
// Calls http://sqlite.org/c3ref/soft_heap_limit64.html // Calls http://sqlite.org/c3ref/soft_heap_limit64.html
func SetSoftHeapLimit(n int64) int64 { func SetSoftHeapLimit(n int64) int64 {
......
...@@ -20,7 +20,7 @@ func authorizer(d interface{}, action Action, arg1, arg2, dbName, triggerName st ...@@ -20,7 +20,7 @@ func authorizer(d interface{}, action Action, arg1, arg2, dbName, triggerName st
} }
func profile(d interface{}, sql string, nanoseconds uint64) { func profile(d interface{}, sql string, nanoseconds uint64) {
//fmt.Printf("%s: %s = %d µs\n", d, sql, nanoseconds/10e3) //fmt.Printf("%s: %s = %d µs\n", d, sql, nanoseconds/1e3)
} }
func progressHandler(d interface{}) bool { func progressHandler(d interface{}) bool {
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment