;;; Sam Thibault ;;; ODBC/SQL interface for scsh ;;; Spring 1999 ;;; This is file: odbc1.scm ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;; ;;; General Comments ;;; ---------------- ;;; This file contains several items: ;;; ;;; 1. Mappings of sql/odbc/c datatypes to integer values used by ODBC. ;;; ;;; 2. Definitions of records used by the scsh-sql interface and some ;;; additional tools for accessing the records/vector structures. ;;; ;;; 3. Error handling tools for scsh-sql. ;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;; The headers below will be included in C file the generated by cig (foreign-source "#if 0" "#include \"cli0cli.h\"" "#include \"cli0defs.h\"" "#include \"cli0env.h\"" "#else" "#include " "#include " "#include " "#endif" "") ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;; ;;; Datatypes ;;; --------- ;;; These value assign sql/odbc/c datatypes to the values used by odbc. There ;;; is also a function to decode the integer values so error messages can b ;;; more useful. ;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (define sql/char 1) (define sql/numeric 2) (define sql/decimal 3) (define sql/integer 4) (define sql/smallint 5) (define sql/float 6) (define sql/real 7) (define sql/double 8) (define sql/varchar 12) (define sql/date 9) (define sql/time 10) (define sql/timestamp 11) (define sql/longvarchar -1) (define sql/binary -2) (define sql/varbinary -3) (define sql/longvarbinary -4) (define sql/bigint -5) (define sql/tinyint -6) (define sql/bit -7) (define (type-val->string type) (cond ((= type sql/char) "sql/char") ((= type sql/numeric) "sql/numeric") ((= type sql/decimal) "sql/decimal") ((= type sql/integer) "sql/integer") ((= type sql/smallint) "sql/smallint") ((= type sql/float) "sql/float") ((= type sql/real) "sql/real") ((= type sql/double) "sql/double") ((= type sql/varchar) "sql/varchar") ((= type sql/date) "sql/date") ((= type sql/time) "sql/time") ((= type sql/timestamp) "sql/timestamp") ((= type sql/longvarchar) "sql/longvarchar") ((= type sql/binary) "sql/binary") ((= type sql/varbinary) "sql/varbinary") ((= type sql/longvarbinary) "sql/longvarbinary") ((= type sql/bigint) "sql/bigint") ((= type sql/tinyint) "sql/tinyint") ((= type sql/bit) "sql/bit") (else "unknown data type"))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;; ;;; Records ;;; ------- ;;; Here are definitions of records/vectors for storing table-descriptions and ;;; cursor information. ;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;; ;;; making a reference chart ;;; ;;; +-------+ ;;; cursor -> | hstmt | ;;; +-------+ +---+---+---+---+---+---+ - - - - - - +---+ ;;; | cols -+-----> | 0 | 1 | 2 | 3 | 4 | 5 | | N | ;;; +-------+ +---+---+-+-+---+---+---+ - - - - - - +---+ ;;; | ;;; | +-------------+ ;;; +--> | column name | ;;; +-------------+ ;;; | name size | ;;; +-------------+ ;;; | data type | ;;; +-------------+ ;;; | precision | ;;; +-------------+ ;;; | scale | ;;; +-------------+ ;;; | nullable | (alien) ;;; +-------------+ +-------+ ;;; | ----+---> | data | ;;; +-------------+ +-------+ ;;; (define-record column-desc name size type precision scale nullable target) (define-record table-desc hstmt ; ODBC statment handle cols) ; vector of column-desc records (see above) ;;; These functions reference items in the cursor. (define (column-name table-desc column-number) (column-desc:name (vector-ref (table-desc:cols table-desc) column-number))) (define (column-size table-desc column-number) (column-desc:size (vector-ref (table-desc:cols table-desc) column-number))) (define (column-type table-desc column-number) (column-desc:type (vector-ref (table-desc:cols table-desc) column-number))) (define (column-precision table-desc column-number) (column-desc:precision (vector-ref (table-desc:cols table-desc) column-number))) (define (column-scale table-desc column-number) (column-desc:scale (vector-ref (table-desc:cols table-desc) column-number))) (define (column-nullable table-desc column-number) (column-desc:nullable (vector-ref (table-desc:cols table-desc) column-number))) (define (column-target table-desc column-number) (column-desc:target (vector-ref (table-desc:cols table-desc) column-number))) ;;; These functions are for changing items in a cursor. (define (set-column-name! table-desc column-number val) (set-column-desc:name (vector-ref (table-desc:cols table-desc) column-number) val)) (define (set-column-size! table-desc column-number val) (set-column-desc:size (vector-ref (table-desc:cols table-desc) column-number) val)) (define (set-column-type! table-desc column-number val) (set-column-desc:type (vector-ref (table-desc:cols table-desc) column-number) val)) (define (set-column-precision! table-desc column-number val) (set-column-desc:precision (vector-ref (table-desc:cols table-desc) column-number) val)) (define (set-column-scale! table-desc column-number val) (set-column-desc:scale (vector-ref (table-desc:cols table-desc) column-number) val)) (define (set-column-nullable! table-desc column-number val) (set-column-desc:nullable (vector-ref (table-desc:cols table-desc) column-number) val)) (define (set-column-target! table-desc column-number val) (set-column-desc:target (vector-ref (table-desc:cols table-desc) column-number) val)) (define-record cursor col-data ; a table-desc ncols ; number of cols in desc stmt ; statement handle cmd) ; command that created this cursor (to recycle stmt handle) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;; ;;; Error system ;;; ------------ ;;; The functions defined here signal and handle errors in the scsh-sql ;;; interface. The define-foreigns link to C functions defined in scsh-sql.c. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;; These functions get the error message from ODBC (define-foreign get-henv-error-state ("GetHenvErrorState" ((C "SQLHENV ~a") environment)) static-string static-string) (define-foreign get-hdbc-error-state ("GetHdbcErrorState" ((C "SQLHENV ~a") environment) ((C "SQLHDBC ~a") connection)) static-string static-string) (define-foreign get-hstmt-error-state ("GetHstmtErrorState" ((C "SQLHENV ~a") environment) ((C "SQLHSTMT ~a") statement)) static-string static-string) (define (sql-error environment connection statement) (cond (statement (get-hstmt-error-state (de-ref-env (server-env)) (de-ref-stmt statement))) (connection (get-hdbc-error-state (de-ref-env (server-env)) (de-ref-con connection))) (else (get-henv-error-state (de-ref-env environment))))) ;;; In order to deal with odbc errors, we will wrap the define-foreigns for ;;; the ODBC functions in Scheme functions that can raise an appropriate error. ;;; First, we define conditions for 5 of the 7 possible odbc error types. For ;;; two of the ODBC functions we will return a boolean value. ;;; ;;; code = odbc return type = scheme condition ;;;------=-----------------------=----------------- ;;; (-2) = SQL_INVALID_HANDLE = sql-invalid-error ;;; (-1) = SQL_ERROR = sql-error ;;; 0 = SQL_SUCCESS = #t ;;; 1 = SQL_SUCCESS_WITH_INFO = sql-info-warning ;;; 2 = SQL_STILL_EXECUTING = sql-busy-exception ;;; 99 = SQL_NEED_DATA = sql-param-exception ;;; 100 = SQL_NO_DATA_FOUND = #f ;;; SQL_INVALID_HANDLE (define-condition-type 'sql-invalid-error '(error)) (define sql-invalid-error? (condition-predicate 'sql-invalid-error)) (define (raise-sql-invalid-error function code message henv hdbc hstmt) (signal 'sql-invalid-error function code message henv hdbc hstmt)) ;;; SQL_ERROR (define-condition-type 'sql-error '(error)) (define sql-error? (condition-predicate 'sql-error)) (define (raise-sql-error function code message henv hdbc hstmt) (signal 'sql-error function code message henv hdbc hstmt)) ;;; SQL_SUCCESS ;;; #t ;;; SQL_SUCCESS_WITH_INFO (define-condition-type 'sql-info-warning '()) (define sql-info-warning? (condition-predicate 'sql-info-warning)) (define (raise-sql-info-warning function code message henv hdbc hstmt) (signal 'sql-info-warning function code message henv hdbc hstmt)) ;;; SQL_STILL_EXECUTING (define-condition-type 'sql-busy-exception '(error)) (define sql-busy-exception? (condition-predicate 'sql-busy-exception)) (define (raise-sql-busy-exception function code message henv hdbc hstmt) (signal 'sql-busy-exception function code message henv hdbc hstmt)) ;;; SQL_NEED_DATA (define-condition-type 'sql-param-exception '(error)) (define sql-param-exception? (condition-predicate 'sql-param-exception)) (define (raise-sql-param-exception function code message henv hdbc hstmt) (signal 'sql-param-exception function code message henv hdbc hstmt)) ;;; SQL_NO_DATA_FOUND ;;; #f ;;; translate-return is the function which will enclose the define-foreigns of ;;; the ODBC functions. For the errors/exceptions/warnings which can be ;;; raised there is a handler defined below. (define (translate-return return-code function henv hdbc hstmt) (receive (code message) (sql-error henv hdbc hstmt) (case return-code ((-2) (raise-sql-invalid-error function code message henv hdbc hstmt)) ;; INVALID_HANDLE ((-1) (raise-sql-error function code message henv hdbc hstmt)) ;; SQL_ERROR ((0) #t) ;; SQL_SUCCESS ((1) (raise-sql-info-warning function code message henv hdbc hstmt) #t) ;; SQL_SUCCESS_WITH_INFO ((2) (raise-sql-busy-exception function code message henv hdbc hstmt)) ;; STILL_EXECUTING ((99) (raise-sql-param-exception function code message henv hdbc hstmt)) ;;SQL_NEED_DATA ((100) #f) ;; SQL_NO_DATA_FOUND (else (error function "impossible return code: contact samt@ai.mit.edu"))))) ;;; Tools for handlers. (define (with-sql-invalid-handler* handler thunk) (with-handler (lambda (condition more) (if (sql-invalid-error? condition) (let ((stuff (cdr condition))) ; (function code message) (handler (car stuff) ; function (cadr stuff) ; error code (caddr stuff) ; error message (list-ref stuff 3) ; henv (list-ref stuff 4) ; hdbc (list-ref stuff 5)))) ; hstmt (more)) thunk)) (define (with-sql-error-handler* handler thunk) (with-handler (lambda (condition more) (if (sql-error? condition) (let ((stuff (cdr condition))) ; (function code message) (handler (car stuff) ; function (cadr stuff) ; error code (caddr stuff) ; error message (list-ref stuff 3) ; henv (list-ref stuff 4) ; hdbc (list-ref stuff 5)))) ; hstmt (more)) thunk)) (define (with-sql-info-handler* handler thunk) (with-handler (lambda (condition more) (if (sql-info-warning? condition) (let ((stuff (cdr condition))) ; (function code message) (handler (car stuff) ; function (cadr stuff) ; error code (caddr stuff) ; error message (list-ref stuff 3) ; henv (list-ref stuff 4) ; hdbc (list-ref stuff 5)))) ; hstmt (more)) thunk)) (define (with-sql-busy-handler* handler thunk) (with-handler (lambda (condition more) (if (sql-busy-exception? condition) (let ((stuff (cdr condition))) ; (function code message) (handler (car stuff) ; function (cadr stuff) ; error code (caddr stuff) ; error message (list-ref stuff 3) ; henv (list-ref stuff 4) ; hdbc (list-ref stuff 5)))) ; hstmt (more)) thunk)) (define (with-sql-param-handler* handler thunk) (with-handler (lambda (condition more) (if (sql-param-exception? condition) (let ((stuff (cdr condition))) ; (function code message) (handler (car stuff) ; function (cadr stuff) ; error code (caddr stuff) ; error message (list-ref stuff 3) ; henv (list-ref stuff 4) ; hdbc (list-ref stuff 5)))) ; hstmt (more)) thunk)) ;;; Catch all sql errors/exceptions/warnings. (define (with-sql-handler* handler thunk) (with-handler (lambda (condition more) (if (or (sql-invalid-error? condition) (sql-error? condition) (sql-info-warning? condition) (sql-busy-exception? condition) (sql-param-exception? condition)) (let ((stuff (cdr condition))) ; (function code message) (handler (car stuff) ; function (cadr stuff) ; error code (caddr stuff) ; error message (list-ref stuff 3) ; henv (list-ref stuff 4) ; hdbc (list-ref stuff 5)))) ; hstmt (more)) thunk))