diff --git a/collects/db/private/generic/interfaces.rkt b/collects/db/private/generic/interfaces.rkt index 9d0253aef5..6426ecb8ab 100644 --- a/collects/db/private/generic/interfaces.rkt +++ b/collects/db/private/generic/interfaces.rkt @@ -158,6 +158,8 @@ Only errors with an associated SQLSTATE are represented by exn:fail:sql, specifically only errors originating from a database backend or library. Other errors are typically raised using 'error', producing plain old exn:fail. + +For SQLite, use symbol instead of SQLSTATE string. |# ;; exn:fail:sql diff --git a/collects/db/private/sqlite3/connection.rkt b/collects/db/private/sqlite3/connection.rkt index f548d7e6c4..31263c2e1f 100644 --- a/collects/db/private/sqlite3/connection.rkt +++ b/collects/db/private/sqlite3/connection.rkt @@ -351,46 +351,54 @@ ;; Returns the status code if no error occurred, otherwise ;; raises an exception with an appropriate message. (define (handle-status* who s db) - (if (or (= s SQLITE_OK) - (= s SQLITE_ROW) - (= s SQLITE_DONE)) - s - (error who "~a" (lookup-status-message s db)))) + (cond [(or (= s SQLITE_OK) + (= s SQLITE_ROW) + (= s SQLITE_DONE)) + s] + [else + (let* ([info + (or (assoc s error-table) + '(#f unknown "unknown error code"))] + [sym + (cadr info)] + [message + (cond [(and (= s SQLITE_ERROR) db) + (sqlite3_errmsg db)] + [else (caddr info)])]) + (raise (make-exn:fail:sql (format "~a: ~a" who message) + (current-continuation-marks) + sym + `((code . ,sym) + (message . ,message) + (errcode . ,s)))))])) (define error-table - `([,SQLITE_ERROR . "unknown error"] - [,SQLITE_INTERNAL . "an internal logic error in SQLite"] - [,SQLITE_PERM . "access permission denied"] - [,SQLITE_ABORT . "callback routine requested an abort"] - [,SQLITE_BUSY . "the database file is locked"] - [,SQLITE_LOCKED . "table in the database is locked"] - [,SQLITE_NOMEM . "malloc() failed"] - [,SQLITE_READONLY . "attempt to write a readonly database"] - [,SQLITE_INTERRUPT . "operation terminated by sqlite3_interrupt()"] - [,SQLITE_IOERR . "some kind of disk I/O error occurred"] - [,SQLITE_CORRUPT . "the database disk image is malformed"] - [,SQLITE_NOTFOUND . "(internal only) table or record not found"] - [,SQLITE_FULL . "insertion failed because database is full"] - [,SQLITE_CANTOPEN . "unable to open the database file"] - [,SQLITE_PROTOCOL . "database lock protocol error"] - [,SQLITE_EMPTY . "database is empty"] - [,SQLITE_SCHEMA . "database schema changed"] - [,SQLITE_TOOBIG . "too much data for one row of a table"] - [,SQLITE_CONSTRAINT . "abort due to constraint violation"] - [,SQLITE_MISMATCH . "data type mismatch"] - [,SQLITE_MISUSE . "library used incorrectly"] - [,SQLITE_NOLFS . "uses OS features not supported on host"] - [,SQLITE_AUTH . "authorization denied"] - [,SQLITE_FORMAT . "auxiliary database format error"] - [,SQLITE_RANGE . "2nd parameter to sqlite3_bind out of range"] - [,SQLITE_NOTADB . "file opened that is not a database file"])) - -;; lookup-status-message : integer db/#f -> string -(define (lookup-status-message s db) - (cond [(and (eq? s SQLITE_ERROR) db) - (sqlite3_errmsg db)] - [(assoc s error-table) => cdr] - [else "unknown condition"])) + `([,SQLITE_ERROR error "unknown error"] + [,SQLITE_INTERNAL internal "an internal logic error in SQLite"] + [,SQLITE_PERM perm "access permission denied"] + [,SQLITE_ABORT abort "callback routine requested an abort"] + [,SQLITE_BUSY busy "the database file is locked"] + [,SQLITE_LOCKED locked "table in the database is locked"] + [,SQLITE_NOMEM nomem "malloc() failed"] + [,SQLITE_READONLY readonly "attempt to write a readonly database"] + [,SQLITE_INTERRUPT interrupt "operation terminated by sqlite3_interrupt()"] + [,SQLITE_IOERR ioerr "some kind of disk I/O error occurred"] + [,SQLITE_CORRUPT corrupt "the database disk image is malformed"] + [,SQLITE_NOTFOUND notfound "(internal only) table or record not found"] + [,SQLITE_FULL full "insertion failed because database is full"] + [,SQLITE_CANTOPEN cantopen "unable to open the database file"] + [,SQLITE_PROTOCOL protocol "database lock protocol error"] + [,SQLITE_EMPTY empty "database is empty"] + [,SQLITE_SCHEMA schema "database schema changed"] + [,SQLITE_TOOBIG toobig "too much data for one row of a table"] + [,SQLITE_CONSTRAINT constraint "abort due to constraint violation"] + [,SQLITE_MISMATCH mismatch "data type mismatch"] + [,SQLITE_MISUSE misuse "library used incorrectly"] + [,SQLITE_NOLFS nolfs "uses OS features not supported on host"] + [,SQLITE_AUTH auth "authorization denied"] + [,SQLITE_FORMAT format "auxiliary database format error"] + [,SQLITE_RANGE range "2nd parameter to sqlite3_bind out of range"] + [,SQLITE_NOTADB notadb "file opened that is not a database file"])) ;; http://www.sqlite.org/lang_transaction.html (define maybe-rollback-status-list diff --git a/collects/db/scribblings/query.scrbl b/collects/db/scribblings/query.scrbl index d09955a597..7f285e957a 100644 --- a/collects/db/scribblings/query.scrbl +++ b/collects/db/scribblings/query.scrbl @@ -740,14 +740,27 @@ SQL errors are represented by the @racket[exn:fail:sql] exception type. @defstruct[(exn:fail:sql exn:fail) - ([sqlstate string?] + ([sqlstate (or/c string? symbol?)] [info (listof (cons/c symbol? any/c))])]{ Represents a SQL error originating from the database server or native library. The @racket[sqlstate] field contains the SQLSTATE - code (a five-character string) of the error; refer to the database - system's documentation for the definitions of SQLSTATE codes. The - @racket[info] field contains all information available about the + code (a five-character string) of the error for PostgreSQL, MySQL, + or ODBC connections or a symbol for SQLite connections. Refer to the + database system's documentation for the definitions of error codes: + + @itemlist[ + @item{@hyperlink["http://www.postgresql.org/docs/9.0/static/errcodes-appendix.html"]{ + PostgreSQL SQLSTATE codes}} + @item{@hyperlink["http://dev.mysql.com/doc/refman/5.5/en/error-messages-server.html"]{ + MySQL SQLSTATE codes}} + @item{@hyperlink["http://www.sqlite.org/c3ref/c_abort.html"]{ + SQLite error codes}; errors are represented as a symbol based on + the error constant's name, such as @racket['busy] for @tt{SQLITE_BUSY}} + @item{ODBC: see the database system's documentation} + ] + + The @racket[info] field contains all information available about the error as an association list. The available keys vary, but the @racket['message] key is typically present; its value is a string containing the error message. @@ -759,9 +772,7 @@ type. Errors originating from the @racketmodname[db] library, such as arity and contract errors, type conversion errors, etc, are not - represented by @racket[exn:fail:sql]. SQLite errors are not - represented via @racket[exn:fail:sql], because SQLite does not - provide SQLSTATE error codes. + represented by @racket[exn:fail:sql]. } @section{Database Catalog Information}