#lang racket/base (require ffi/unsafe ffi/unsafe/define "ffi-constants.rkt") (provide (all-from-out "ffi-constants.rkt")) (provide (protect-out (all-defined-out))) (define-cpointer-type _sqlhandle) (define-cpointer-type _sqlhenv) (define-cpointer-type _sqlhdbc) (define-cpointer-type _sqlhstmt) (define _sqllen _long) (define _sqlulen _ulong) (define _sqlsmallint _sshort) (define _sqlusmallint _ushort) (define _sqlinteger _sint) (define _sqluinteger _uint) (define _sqlreturn _sqlsmallint) (define-ffi-definer define-mz #f) (define-mz scheme_utf16_to_ucs4 (_fun (src srcstart srcend) :: (src : _bytes) (srcstart : _intptr) (srcend : _intptr) (#f : _pointer) ;; No buffer so it'll allocate for us. (0 : _intptr) (clen : (_ptr o _intptr)) (1 : _intptr) -> (out : _gcpointer) -> (begin (ptr-set! out _int32 clen 0) (values out clen)))) (define-mz scheme_ucs4_to_utf16 (_fun (src srcstart srcend) :: (src : _string/ucs-4) (srcstart : _intptr) (srcend : _intptr) (#f : _pointer) ;; No buffer so it'll allocate for us. (0 : _intptr) (clen : (_ptr o _intptr)) (1 : _intptr) -> (out : _gcpointer) -> (begin (ptr-set! out _int16 clen 0) (values out clen)))) (define-mz scheme_make_sized_char_string (_fun (chars clen copy?) :: (chars : _gcpointer) (clen : _intptr) (copy? : _bool) -> _racket)) (define scheme_make_sized_byte_string/string (get-ffi-obj 'scheme_make_sized_byte_string #f (_fun (buf len) :: (buf : _string/ucs-4) (len : _intptr) (#t : _bool) -> _racket))) ;; For dealing with param buffers, which must not be moved by GC (define (copy-buffer buffer) (let* ([buffer (if (string? buffer) (string->bytes/utf-8 buffer) buffer)] [n (bytes-length buffer)] [rawcopy (malloc (add1 n) 'atomic-interior)] [copy (make-sized-byte-string rawcopy n)]) (memcpy copy buffer n) (ptr-set! rawcopy _byte n 0) copy)) (define (int->buffer n) (let ([copy (make-sized-byte-string (malloc 4 'atomic-interior) 4)]) (integer->integer-bytes n 4 #t (system-big-endian?) copy 0) copy)) (define (cpstr2 str) (let-values ([(shorts slen) (scheme_ucs4_to_utf16 str 0 (string-length str))]) (let* ([n (* slen 2)] [rawcopy (malloc (add1 n) 'atomic-interior)] [copy (make-sized-byte-string rawcopy n)]) (memcpy copy shorts n) (ptr-set! rawcopy _byte n 0) copy))) (define (cpstr4 str) (copy-buffer (scheme_make_sized_byte_string/string str (* (string-length str) 4)))) (define (mkstr2 buf len fresh?) (let-values ([(chars clen) (scheme_utf16_to_ucs4 buf 0 (quotient len 2))]) (scheme_make_sized_char_string chars clen #f))) (define (mkstr4 buf len fresh?) (scheme_make_sized_char_string buf (quotient len 4) (not fresh?))) ;; ======================================== ;; Used in connection.rkt; silly hack to keep optimizer from eliminating ref to ;; things that shouldn't be GC'd. Depends on no cross-module inlining. (define (strong-void x) (void)) ;; ======================================== #| Docs at http://msdn.microsoft.com/en-us/library/ms712628%28v=VS.85%29.aspx |# (define odbc-lib (case (system-type) ((windows) (ffi-lib "odbc32.dll")) ((macosx) (ffi-lib "libiodbc" '("2" #f))) ((unix) (ffi-lib "libodbc" '("1" #f))))) (define WCHAR-SIZE (case (system-type) ((windows) ;; Windows ODBC defines wchar_t, thus WCHAR, thus SQLWCHAR, as 16-bit 2) ((macosx) ;; MacOSX uses iodbc, which defines SQLWCHAR as wchar_t, as 32-bit 4) ((unix) ;; unixodbc defines WCHAR as 16-bit for compat w/ Windows ;; (even though Linux wchar_t is 32-bit) 2))) (define-ffi-definer define-odbc odbc-lib) (define (ok-status? n) (or (= n SQL_SUCCESS) (= n SQL_SUCCESS_WITH_INFO))) (define-odbc SQLAllocHandle (_fun (type : _sqlsmallint) (parent : _sqlhandle/null) (handle : (_ptr o _sqlhandle/null)) -> (status : _sqlreturn) -> (values status (cond [handle (cpointer-push-tag! handle (cond [(= type SQL_HANDLE_ENV) sqlhenv-tag] [(= type SQL_HANDLE_DBC) sqlhdbc-tag] [(= type SQL_HANDLE_STMT) sqlhstmt-tag] [else sqlhandle-tag])) handle] [else handle])))) ;; SQLSetEnvAttr ;; must set odbc version env attr before making connection (define-odbc SQLSetEnvAttr (_fun (env : _sqlhenv) (attr : _sqlinteger) (value-buf : _sqlinteger) ;; (the one case we care about takes int, not ptr) (_sqlinteger = 0) -> _sqlreturn)) (define-odbc SQLGetInfo (_fun (handle info) :: (handle : _sqlhdbc) (info : _sqlusmallint) (value : (_ptr o _sqluinteger)) ;; the one case we care about is uint, not char (0 : _sqlsmallint) (#f : _pointer) -> (status : _sqlreturn) -> (values status value))) (define SQLGetInfo-string (get-ffi-obj "SQLGetInfo" odbc-lib (_fun (handle info) :: (handle : _sqlhdbc) (info : _sqlusmallint) (value : _bytes = (make-bytes 250)) (250 : _sqlsmallint) (len : (_ptr o _sqlsmallint)) -> (status : _sqlreturn) -> (values status (and (ok-status? status) (bytes->string/utf-8 value #f 0 len)))))) (define-odbc SQLGetFunctions (_fun (handle : _sqlhdbc) (function-id : _sqlusmallint) (supported? : (_ptr o _sqlusmallint)) -> (status : _sqlreturn) -> (values status (positive? supported?)))) (define-odbc SQLConnect (_fun (handle server user auth) :: (handle : _sqlhdbc) (server : _string) ((string-utf-8-length server) : _sqlsmallint) (user : _string) ((if user (string-utf-8-length user) 0) : _sqlsmallint) (auth : _string) ((if auth (string-utf-8-length auth) 0) : _sqlsmallint) -> _sqlreturn)) (define-odbc SQLDriverConnect (_fun (handle connection driver-completion) :: (handle : _sqlhdbc) (#f : _pointer) (connection : _string) ((if connection (string-utf-8-length connection) 0) : _sqlsmallint) (#f : _bytes) (0 : _sqlsmallint) (out-length : (_ptr o _sqlsmallint)) (driver-completion : _sqlusmallint) -> (status : _sqlreturn) -> status)) (define-odbc SQLBrowseConnect (_fun (handle in-conn-string) :: (handle : _sqlhdbc) (in-conn-string : _string) ((if in-conn-string (string-utf-8-length in-conn-string) 0) : _sqlsmallint) (out-buf : _bytes = (make-bytes 1024)) ((bytes-length out-buf) : _sqlsmallint) (out-len : (_ptr o _sqlsmallint)) -> (status : _sqlreturn) -> (values status (and (ok-status? status) (bytes->string/utf-8 out-buf #f 0 out-len))))) (define-odbc SQLDataSources (_fun (handle direction server-buf descr-buf) :: (handle : _sqlhenv) (direction : _sqlusmallint) (server-buf : _bytes) ((bytes-length server-buf) : _sqlsmallint) (server-length : (_ptr o _sqlsmallint)) (descr-buf : _bytes) ((bytes-length descr-buf) : _sqlsmallint) (descr-length : (_ptr o _sqlsmallint)) -> (status : _sqlreturn) -> (values status (and (ok-status? status) (bytes->string/utf-8 server-buf #f 0 server-length)) (and (ok-status? status) (bytes->string/utf-8 descr-buf #f 0 descr-length))))) (define-odbc SQLDrivers (_fun (handle direction driver-buf attrs-buf) :: (handle : _sqlhenv) (direction : _sqlusmallint) (driver-buf : _bytes) ((bytes-length driver-buf) : _sqlsmallint) (driver-length : (_ptr o _sqlsmallint)) (attrs-buf : _bytes) ((if attrs-buf (bytes-length attrs-buf) 0) : _sqlsmallint) (attrs-length : (_ptr o _sqlsmallint)) -> (status : _sqlreturn) -> (if (ok-status? status) (values status (bytes->string/utf-8 driver-buf #f 0 driver-length) attrs-length) (values status #f #f)))) (define-odbc SQLPrepare (_fun (handle stmt) :: (handle : _sqlhstmt) (stmt : _string) ((string-utf-8-length stmt) : _sqlinteger) -> _sqlreturn)) (define-odbc SQLBindParameter (_fun (handle param-num iomode c-type sql-type column-size digits value len-or-ind) :: (handle : _sqlhstmt) (param-num : _sqlusmallint) (iomode : _sqlsmallint) (c-type : _sqlsmallint) (sql-type : _sqlsmallint) (column-size : _sqlulen) (digits : _sqlsmallint) (value : _pointer) ;; must be pinned until after SQLExecute called ((if (bytes? value) (bytes-length value) 0) : _sqllen) ;; ignored for fixed-length data (len-or-ind : _pointer) ;; _sqllen-pointer) -> _sqlreturn)) (define-odbc SQLExecute (_fun (handle : _sqlhstmt) -> _sqlreturn)) (define-odbc SQLNumParams (_fun (handle : _sqlhstmt) (count : (_ptr o _sqlsmallint)) -> (status : _sqlreturn) -> (values status count))) (define-odbc SQLDescribeParam (_fun (handle : _sqlhstmt) (parameter : _sqlusmallint) (data-type : (_ptr o _sqlsmallint)) (size : (_ptr o _sqlulen)) (digits : (_ptr o _sqlsmallint)) (nullable : (_ptr o _sqlsmallint)) -> (status : _sqlreturn) -> (values status data-type size digits nullable))) (define-odbc SQLNumResultCols (_fun (handle : _sqlhstmt) (count : (_ptr o _sqlsmallint)) -> (status : _sqlreturn) -> (values status count))) (define-odbc SQLDescribeCol (_fun (handle column column-buf) :: (handle : _sqlhstmt) (column : _sqlusmallint) (column-buf : _bytes) ((bytes-length column-buf) : _sqlsmallint) (column-len : (_ptr o _sqlsmallint)) (data-type : (_ptr o _sqlsmallint)) (size : (_ptr o _sqlulen)) (digits : (_ptr o _sqlsmallint)) (nullable : (_ptr o _sqlsmallint)) -> (status : _sqlreturn) -> (values status (and (ok-status? status) (bytes->string/utf-8 column-buf #f 0 column-len)) data-type size digits nullable))) (define-odbc SQLFetch (_fun _sqlhstmt -> _sqlreturn)) (define-odbc SQLGetData (_fun (handle column target-type buffer start) :: (handle : _sqlhstmt) (column : _sqlusmallint) (target-type : _sqlsmallint) ((ptr-add buffer start) : _gcpointer) ((- (bytes-length buffer) start) : _sqllen) (len-or-ind : (_ptr o _sqllen)) -> (status : _sqlreturn) -> (values status len-or-ind))) (define-odbc SQLFreeStmt (_fun (handle : _sqlhstmt) (option : _sqlusmallint) -> _sqlreturn)) (define-odbc SQLCloseCursor (_fun (handle : _sqlhstmt) -> _sqlreturn)) (define-odbc SQLDisconnect (_fun (handle : _sqlhdbc) -> _sqlreturn)) (define-odbc SQLFreeHandle (_fun (handle-type : _sqlsmallint) (handle : _sqlhandle) -> _sqlreturn)) (define-odbc SQLGetDiagRec (_fun (handle-type handle rec-number) :: (handle-type : _sqlsmallint) (handle : _sqlhandle) (rec-number : _sqlsmallint) (sql-state-buf : _bytes = (make-bytes 6)) (native-errcode : (_ptr o _sqlinteger)) (message-buf : _bytes = (make-bytes 1024)) ((bytes-length message-buf) : _sqlsmallint) (message-len : (_ptr o _sqlsmallint)) -> (status : _sqlreturn) -> (values status (and (ok-status? status) (bytes->string/utf-8 sql-state-buf #\? 0 5)) native-errcode (and (ok-status? status) (bytes->string/utf-8 message-buf #\? 0 message-len))))) (define-odbc SQLEndTran (_fun (handle completion-type) :: (_sqlsmallint = SQL_HANDLE_DBC) (handle : _sqlhandle) (completion-type : _sqlsmallint) -> _sqlreturn)) (define-odbc SQLGetConnectAttr (_fun (handle attr) :: (handle : _sqlhdbc) (attr : _sqlinteger) (value : (_ptr o _sqluinteger)) ;; the attrs we care about have uint value (buflen : _sqlinteger = 0) ;; ignored (#f : _pointer) -> (status : _sqlreturn) -> (values status value))) (define-odbc SQLSetConnectAttr (_fun (handle attr value) :: (handle : _sqlhdbc) (attr : _sqlinteger) (value : _sqluinteger) ;; the attrs we care about have uint value (_sqlinteger = 0) -> _sqlreturn)) (define-odbc SQLTables (_fun (handle catalog schema table) :: (handle : _sqlhstmt) (catalog : _string) (_sqlsmallint = (if catalog (string-utf-8-length catalog) 0)) (schema : _string) (_sqlsmallint = (if schema (string-utf-8-length schema) 0)) (table : _string) (_sqlsmallint = (if table (string-utf-8-length table) 0)) (_bytes = #f) (_sqlsmallint = 0) -> _sqlreturn))