#lang racket/base
(require racket/class
racket/contract
"../generic/interfaces.rkt"
"../generic/check-access.rkt"
"connection.rkt"
"dbsystem.rkt"
"ffi.rkt")
(provide odbc-connect
odbc-driver-connect
odbc-data-sources
odbc-drivers
(rename-out [dbsystem odbc-dbsystem]))
(define (odbc-connect #:dsn [dsn #f]
#:database [database #f]
#:user [user #f]
#:password [auth #f]
#:notice-handler [notice-handler void]
#:strict-parameter-types? [strict-parameter-types? #f]
#:character-mode [char-mode 'wchar])
(when (and dsn database)
(uerror 'odbc-connect "cannot give both #:dsn and #:database arguments"))
(unless (or dsn database)
(uerror 'odbc-connect "missing #:dsn argument"))
(let ([dsn (or dsn database)]
[notice-handler (make-handler notice-handler "notice")])
(call-with-env 'odbc-connect
(lambda (env)
(call-with-db 'odbc-connect env
(lambda (db)
(let ([status (SQLConnect db dsn user auth)])
(handle-status* 'odbc-connect status db)
(new connection%
(env env)
(db db)
(notice-handler notice-handler)
(strict-parameter-types? strict-parameter-types?)
(char-mode char-mode)))))))))
(define (odbc-driver-connect connection-string
#:notice-handler [notice-handler void]
#:strict-parameter-types? [strict-parameter-types? #f]
#:character-mode [char-mode 'wchar])
(let ([notice-handler (make-handler notice-handler "notice")])
(call-with-env 'odbc-driver-connect
(lambda (env)
(call-with-db 'odbc-driver-connect env
(lambda (db)
(let ([status (SQLDriverConnect db connection-string SQL_DRIVER_NOPROMPT)])
(handle-status* 'odbc-driver-connect status db)
(new connection%
(env env)
(db db)
(notice-handler notice-handler)
(strict-parameter-types? strict-parameter-types?)
(char-mode char-mode)))))))))
(define (odbc-data-sources)
(define server-buf (make-bytes 1024))
(define descr-buf (make-bytes 1024))
(call-with-env 'odbc-data-sources
(lambda (env)
(begin0
(let loop ()
(let-values ([(status name description)
(SQLDataSources env SQL_FETCH_NEXT server-buf descr-buf)])
(cond [(or (= status SQL_SUCCESS) (= status SQL_SUCCESS_WITH_INFO))
(cons (list name description) (loop))]
[else null])))
(handle-status* 'odbc-data-sources (SQLFreeHandle SQL_HANDLE_ENV env))))))
(define (odbc-drivers)
(define driver-buf (make-bytes 1024))
(call-with-env 'odbc-drivers
(lambda (env)
(let* ([attrlens
(let loop ()
(let-values ([(status name attrlen)
(SQLDrivers env SQL_FETCH_NEXT driver-buf #f)])
(cond [(or (= status SQL_SUCCESS) (= status SQL_SUCCESS_WITH_INFO))
(cons attrlen (loop))]
[else null])))] [attr-buf (make-bytes (+ 1 (apply max 0 attrlens)))] [result
(let loop ()
(let-values ([(status name attrlen) (SQLDrivers env SQL_FETCH_NEXT driver-buf attr-buf)])
(cond [(or (= status SQL_SUCCESS) (= status SQL_SUCCESS_WITH_INFO))
(cons (list name (parse-driver-attrs attr-buf attrlen))
(loop))]
[else null])))]) (handle-status* 'odbc-drivers (SQLFreeHandle SQL_HANDLE_ENV env))
result))))
(define (parse-driver-attrs buf len)
(let* ([attrs (regexp-split #rx#"\0" buf 0 len)])
(for/list ([p (in-list attrs)]
#:when (positive? (bytes-length p)))
(let* ([s (bytes->string/utf-8 p)]
[m (regexp-match-positions #rx"=" s)])
(unless m (error/internal 'odbc-drivers "bad attribute syntax: ~e" s))
(let ([=-pos (caar m)])
(cons (substring s 0 =-pos) (substring s (+ 1 =-pos))))))))
(define (call-with-env fsym proc)
(let-values ([(status env) (SQLAllocHandle SQL_HANDLE_ENV #f)])
(with-handlers ([(lambda (e) #t)
(lambda (e)
(SQLFreeHandle SQL_HANDLE_ENV env)
(raise e))])
(handle-status* fsym status env)
(handle-status* fsym (SQLSetEnvAttr env SQL_ATTR_ODBC_VERSION SQL_OV_ODBC3))
(proc env))))
(define (call-with-db fsym env proc)
(let-values ([(status db) (SQLAllocHandle SQL_HANDLE_DBC env)])
(with-handlers ([(lambda (e) #t)
(lambda (e)
(SQLFreeHandle SQL_HANDLE_DBC db)
(raise e))])
(handle-status* fsym status db)
(proc db))))