713 lines
28 KiB
Scheme
713 lines
28 KiB
Scheme
|
;;; Sam Thibault
|
||
|
;;; ODBC/SQL interface for scsh
|
||
|
;;; Spring 1999
|
||
|
|
||
|
;;; This is file: odbc2.scm
|
||
|
|
||
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||
|
;;;
|
||
|
;;; General Comments
|
||
|
;;; ----------------
|
||
|
;;; This file contains the functions used by scsh-sql.scm. These functions
|
||
|
;;; are the original define-foreigns for the ODBC functions (from odbc0.scm)
|
||
|
;;; enclosed in the scsh-sql error system (in odbc1.scm). These functions also
|
||
|
;;; handling all the neccesary memory allocation the ODBC functions require.
|
||
|
;;;
|
||
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||
|
|
||
|
;;; Values defined by ODBC
|
||
|
|
||
|
(define sql-param-input 1)
|
||
|
(define sql-c-default 99)
|
||
|
|
||
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||
|
;;;
|
||
|
;;; Scheme Names
|
||
|
;;; ------------
|
||
|
;;; The define-foreigns for ODBC library functions (in odbc0.scm) rename the
|
||
|
;;; functions by replacing the beginning "SQL" with "%" and hyphenating the
|
||
|
;;; function name. For example "SQLAllocConnect" becomes "%alloc-connect".
|
||
|
;;; The function used by scsh-sql.scm is the name without the "%". These
|
||
|
;;; functions are created below by encapsulating the %-name within a translate-
|
||
|
;;; return and doing neccesary memory allocation within the function. This
|
||
|
;;; provides a clean Scheme-like interface for scsh-sql.scm
|
||
|
;;;
|
||
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||
|
|
||
|
;;; alloc-connect allocates memory for a connection handle within the
|
||
|
;;; environment returned from server-env.
|
||
|
|
||
|
(define (alloc-connect)
|
||
|
(let ((con (make-connection))
|
||
|
(env (de-ref-env (server-env))))
|
||
|
(translate-return (%alloc-connect env con)
|
||
|
'alloc-connect
|
||
|
*environment*
|
||
|
con
|
||
|
#f)
|
||
|
con))
|
||
|
|
||
|
;;; server-env checks if an environment handle has been created. If there is
|
||
|
;;; a current henv it returns it; otherwise, server-env allocates a new henv
|
||
|
;;; sets it as the current henv, and returns the henv.
|
||
|
;;;
|
||
|
;;; server-env was renamed from alloc-env because this function only allocates
|
||
|
;;; a new environment handle if none exists.
|
||
|
|
||
|
(define (server-env)
|
||
|
(if (not *environment*)
|
||
|
(let ((env (make-environment)))
|
||
|
(translate-return (%alloc-env env)
|
||
|
'server-env
|
||
|
env
|
||
|
#f
|
||
|
#f)
|
||
|
(set! *environment* env)))
|
||
|
*environment*)
|
||
|
|
||
|
;;; alloc-stmt allocates memory for a statement handle and associates the
|
||
|
;;; statement handle with the specified connection. alloc-stmt must be called
|
||
|
;;; before executing any SQL statements.
|
||
|
|
||
|
(define (alloc-stmt con)
|
||
|
(let ((stmt (make-statement)))
|
||
|
(translate-return (%alloc-stmt (de-ref-con con)
|
||
|
stmt)
|
||
|
'alloc-stmt
|
||
|
#f ; env handle ignored by sql-error
|
||
|
con
|
||
|
stmt)
|
||
|
stmt))
|
||
|
|
||
|
;;; bind-col assigns storage and data type for a column in a result set. The
|
||
|
;;; newly created storage, "target" is returned.
|
||
|
|
||
|
(define (bind-col cmd icol type precision)
|
||
|
(let ((p (make-SDWORD))
|
||
|
(stmt (sql-command:statement cmd)))
|
||
|
(assign-SDWORD p precision)
|
||
|
(let* ((target-size p)
|
||
|
(target (make-storage target-size)))
|
||
|
(translate-return (%bind-col (de-ref-stmt stmt)
|
||
|
icol
|
||
|
(if (or (= type sql/integer)
|
||
|
(= type sql/smallint))
|
||
|
type
|
||
|
sql/char)
|
||
|
target
|
||
|
(if (or (= type sql/integer)
|
||
|
(= type sql/smallint))
|
||
|
(extract-SDWORD target-size)
|
||
|
(+ 1 (extract-SDWORD target-size)))
|
||
|
target-size)
|
||
|
'bind-col
|
||
|
#f ; env handle ignored by sql-error
|
||
|
#f ; con handle ignored by sql-error
|
||
|
stmt)
|
||
|
target)))
|
||
|
|
||
|
;;; bind-parameter binds a buffer to a parameter marker in an SQL statement.
|
||
|
|
||
|
(define (bind-parameter cmd coln data-type precision scale param)
|
||
|
;; create buffers
|
||
|
(receive (target-size)
|
||
|
(cond ((= data-type sql/char)
|
||
|
(assign-SDWORD (make-SDWORD) (+ 1 precision)))
|
||
|
((or (= data-type sql/numeric)
|
||
|
(= data-type sql/decimal)
|
||
|
(= data-type sql/float)
|
||
|
(= data-type sql/real)
|
||
|
(= data-type sql/double))
|
||
|
(assign-SDWORD (make-SDWORD)
|
||
|
(+ 1 (string-length (number->string param)))))
|
||
|
((or (= data-type sql/integer)
|
||
|
(= data-type sql/smallint))
|
||
|
(assign-SDWORD (make-SDWORD) precision))
|
||
|
((= data-type sql/varchar)
|
||
|
(assign-SDWORD (make-SDWORD) (string-length param)))
|
||
|
(else (error "unsupported parameter type: "
|
||
|
(type-val->string data-type))))
|
||
|
(receive (target)
|
||
|
(cond ((= data-type sql/char)
|
||
|
(fixed-string->void* param precision
|
||
|
(make-storage target-size)))
|
||
|
((or (= data-type sql/numeric)
|
||
|
(= data-type sql/decimal)
|
||
|
(= data-type sql/float)
|
||
|
(= data-type sql/real)
|
||
|
(= data-type sql/double))
|
||
|
(number->void* param (make-storage target-size)))
|
||
|
((or (= data-type sql/integer)
|
||
|
(= data-type sql/smallint))
|
||
|
(integer->void* param (make-storage target-size)))
|
||
|
((= data-type sql/varchar)
|
||
|
(string->void* param (make-storage target-size)))
|
||
|
(else (error "unsupported parameter type: "
|
||
|
(type-val->string data-type))))
|
||
|
;; do it
|
||
|
(let ((stmt (sql-command:statement cmd)))
|
||
|
(translate-return (%bind-parameter (de-ref-stmt stmt)
|
||
|
coln
|
||
|
sql-param-input
|
||
|
sql-c-default
|
||
|
data-type
|
||
|
precision
|
||
|
scale
|
||
|
target ;storage-pointer
|
||
|
(extract-SDWORD target-size) ;buf-lng
|
||
|
target-size ;available-bytes
|
||
|
'bind-parameter
|
||
|
#f ; env handle ignored by sql-error
|
||
|
#f ; con handle ignored by sql-error
|
||
|
stmt))))))
|
||
|
|
||
|
;;; cancel cancels the processing on a statement.
|
||
|
|
||
|
(define (cancel cmd)
|
||
|
(let ((stmt (sql-command:statement cmd)))
|
||
|
(translate-return (%cancel (de-ref-stmt stmt))
|
||
|
'cancel
|
||
|
#f ; env handle ignored by sql-error
|
||
|
#f ; con handle ignored by sql-error
|
||
|
stmt)))
|
||
|
|
||
|
;;; col-attributes returns descriptor information for a cloumn in a result set.
|
||
|
|
||
|
;;; col-attributes descriptor types:
|
||
|
(define column-auto-increment 11) ; pfDesc (int return in desriptor-pointer)
|
||
|
(define column-case-sensitive 12) ; pfDesc
|
||
|
(define column-count 0) ; pfDesc
|
||
|
(define column-display-size 6) ; pfDesc
|
||
|
(define column-length 3) ; pfDesc
|
||
|
(define column-money 9) ; pfDesc
|
||
|
(define column-name 1) ; rgbDesc (string return in storage-pointer)
|
||
|
(define column-nullable 7) ; pfDesc
|
||
|
(define column-precision 4) ; pfDesc
|
||
|
(define column-scale 5) ; pfDesc
|
||
|
(define column-searchable 13) ; pfDesc
|
||
|
(define column-data-type 2) ; pfDesc
|
||
|
(define column-type-name 14) ; pfDesc
|
||
|
(define column-unsigned 8) ; pfDesc
|
||
|
(define column-updatable 10) ; pfDesc
|
||
|
|
||
|
(define (col-attributes stmt column-number descriptor-type storage-pointer
|
||
|
buffer-length available-bytes descriptor-pointer)
|
||
|
(translate-return (%col-attributes stmt column-number descriptor-type
|
||
|
storage-pointer buffer-length
|
||
|
available-bytes descriptor-pointer)
|
||
|
'col-attributes
|
||
|
#f ; env handle ignored by sql-error
|
||
|
#f ; con handle ignored by sql-error
|
||
|
stmt))
|
||
|
|
||
|
;;; connect creates a new connection, loads a driver, and establishes a
|
||
|
;;; connection to the data source using the new connection.
|
||
|
|
||
|
(define (connect source-name user-name password)
|
||
|
(let ((con (alloc-connect)))
|
||
|
(translate-return (%connect con
|
||
|
source-name (string-length source-name)
|
||
|
user-name (string-length user-name)
|
||
|
password (string-length password))
|
||
|
'connect
|
||
|
#f
|
||
|
con
|
||
|
#f)
|
||
|
con))
|
||
|
|
||
|
;;; connect! loads a driver, and establishes a connection to the data source
|
||
|
;;; using the supplied connection.
|
||
|
|
||
|
(define (connect! con source-name user-name password)
|
||
|
(translate-return (%connect (de-ref-con con)
|
||
|
source-name (string-length source-name)
|
||
|
user-name (string-length user-name)
|
||
|
password (string-length password))
|
||
|
'connect
|
||
|
*environment*
|
||
|
con
|
||
|
#f))
|
||
|
|
||
|
;;; describe-col returns the result descriptor for one column in a result set.
|
||
|
|
||
|
(define (describe-col cmd icol)
|
||
|
(let ((stmt (sql-command:statement cmd))
|
||
|
(name (make-string 1))
|
||
|
(name-byte-size (make-SWORD))
|
||
|
(data-type (make-SWORD))
|
||
|
(precision (make-UDWORD))
|
||
|
(scale (make-SWORD))
|
||
|
(nullable (make-SWORD)))
|
||
|
(with-sql-info-handler*
|
||
|
(lambda (func code mess henv hdbc hstmt) #t)
|
||
|
(lambda ()
|
||
|
(translate-return (%describe-col (de-ref-stmt stmt) icol name
|
||
|
(+ 1 (string-length name))
|
||
|
name-byte-size data-type precision
|
||
|
scale nullable)
|
||
|
'describe-col
|
||
|
#f ; env handle ignored by sql-error
|
||
|
#f ; con handle ignored by sql-error
|
||
|
stmt)))
|
||
|
(set! name (make-string (extract-SWORD name-byte-size)))
|
||
|
(translate-return (%describe-col (de-ref-stmt stmt) icol name
|
||
|
(+ 1 (string-length name))
|
||
|
name-byte-size data-type precision
|
||
|
scale nullable)
|
||
|
'describe-col
|
||
|
#f ; env handle ignored by sql-error
|
||
|
#f ; con handle ignored by sql-error
|
||
|
stmt)
|
||
|
(values name (extract-SWORD name-byte-size) (extract-SWORD data-type)
|
||
|
(extract-UDWORD precision) (extract-SWORD scale)
|
||
|
(extract-SWORD nullable))))
|
||
|
|
||
|
;;; describe-param returns the description of a parameter marker associated
|
||
|
;;; with a prepared SQL statement.
|
||
|
|
||
|
(define (describe-param cmd coln)
|
||
|
(let ((stmt (sql-command:statement cmd))
|
||
|
(data-type (make-SWORD))
|
||
|
(precision (make-UDWORD))
|
||
|
(scale (make-SWORD))
|
||
|
(nullable (make-SWORD)))
|
||
|
(translate-return (%describe-param (de-ref-stmt stmt) coln data-type
|
||
|
precision scale nullable)
|
||
|
'describe-param
|
||
|
#f ; env handle ignored by sql-error
|
||
|
#f ; con handle ignored by sql-error
|
||
|
stmt)
|
||
|
(values (extract-SWORD data-type)
|
||
|
(extract-UDWORD precision)
|
||
|
(extract-SWORD scale)
|
||
|
(extract-SWORD nullable))))
|
||
|
|
||
|
;;; disconnect closes the connection associated with a specific connection
|
||
|
;;; handle.
|
||
|
|
||
|
(define (disconnect con)
|
||
|
(translate-return (%disconnect (de-ref-con con))
|
||
|
'disconnect
|
||
|
#f
|
||
|
con
|
||
|
#f))
|
||
|
|
||
|
;;; sql-error: see error-system in file odbc1.scm
|
||
|
|
||
|
;;; execdirect executes a preparable statement.
|
||
|
|
||
|
(define (exec-direct stmt sql-string)
|
||
|
(translate-return (%exec-direct stmt sql-string (string-length sql-string))
|
||
|
'exec-direct
|
||
|
#f ; env handle ignored by sql-error
|
||
|
#f ; con handle ignored by sql-error
|
||
|
stmt))
|
||
|
|
||
|
;;; execute executes a prepared statement.
|
||
|
|
||
|
(define (execute stmt)
|
||
|
(translate-return (%execute (de-ref-stmt stmt))
|
||
|
'execute
|
||
|
#f ; env handle ignored by sql-error
|
||
|
#f ; con handle ignored by sql-error
|
||
|
stmt))
|
||
|
|
||
|
;;; fetch fetches a row of data from a result set. The driver returns data
|
||
|
;;; for all columns that were bound to storage locations with bind-col.
|
||
|
|
||
|
(define (fetch cursor)
|
||
|
(if (not (translate-return (%fetch (de-ref-stmt (cursor:stmt cursor)))
|
||
|
'fetch
|
||
|
#f ; env handle ignored by sql-error
|
||
|
#f ; con handle ignored by sql-error
|
||
|
(cursor:stmt cursor)))
|
||
|
;; close the cursor if no more rows
|
||
|
(begin (close-cursor cursor)
|
||
|
#f) ; #f for multi-fetch operations
|
||
|
|
||
|
;; otherwise build the row
|
||
|
(let* ((ncols (cursor:ncols cursor))
|
||
|
(row (make-vector ncols))
|
||
|
(cd (cursor:col-data cursor)))
|
||
|
(let get-cols ((i 0))
|
||
|
(cond ((< i ncols)
|
||
|
(vector-set!
|
||
|
row i ((cond
|
||
|
((or (= (column-type cd i) sql/char)
|
||
|
(= (column-type cd i) sql/varchar))
|
||
|
void*->string)
|
||
|
|
||
|
((or (= (column-type cd i) sql/numeric)
|
||
|
(= (column-type cd i) sql/decimal)
|
||
|
(= (column-type cd i) sql/float)
|
||
|
(= (column-type cd i) sql/real)
|
||
|
(= (column-type cd i) sql/double))
|
||
|
void*->number)
|
||
|
|
||
|
((or (= (column-type cd i) sql/integer)
|
||
|
(= (column-type cd i) sql/smallint))
|
||
|
void*->integer)
|
||
|
|
||
|
((= (column-type cd i) sql/date)
|
||
|
void*->date)
|
||
|
|
||
|
((= (column-type cd i) sql/time)
|
||
|
void*->time)
|
||
|
|
||
|
((= (column-type cd i) sql/timestamp)
|
||
|
void*->timestamp)
|
||
|
|
||
|
(else (error "can't convert type: fetch-rows")))
|
||
|
(column-target cd i)))
|
||
|
(get-cols (+ i 1)))))
|
||
|
row)))
|
||
|
|
||
|
;;; free-connect releases a connection handle and frees all memory associated
|
||
|
;;; with the handle.
|
||
|
|
||
|
(define (free-connect con)
|
||
|
(translate-return (%free-connect (de-ref-con con))
|
||
|
'free-connect
|
||
|
#f
|
||
|
con
|
||
|
#f)
|
||
|
(free-connection con))
|
||
|
|
||
|
;;; free-env frees the environment handle and frees all memory associated
|
||
|
;;; with the environment handle and sets the *environment* variable to #f.
|
||
|
|
||
|
(define (free-env)
|
||
|
(let ((env (if *environment*
|
||
|
*environment*
|
||
|
(error "no current environment"))))
|
||
|
(translate-return (%free-env (de-ref-env env))
|
||
|
'free-env
|
||
|
env
|
||
|
#f
|
||
|
#f)
|
||
|
(free-environment env)
|
||
|
(set! *environment* #f)
|
||
|
#t))
|
||
|
|
||
|
;;; free-stmt stops processing associated with a specific statement handle,
|
||
|
;;; closes any open cursors, discards pending results, and, optionally, frees
|
||
|
;;; all resources associated with the statement handle.
|
||
|
;;;
|
||
|
;;; SQLFreeStmt Flags:
|
||
|
;;;
|
||
|
;;; flag = means this
|
||
|
;;; -----=-----------------
|
||
|
;;; 0 = SQL_CLOSE
|
||
|
;;; 1 = SQL_DROP
|
||
|
;;; 2 = SQL_UNBIND
|
||
|
;;; 3 = SQL_RESET_PARAMS
|
||
|
|
||
|
(define (free-stmt/close stmt)
|
||
|
(translate-return (%free-stmt (de-ref-stmt stmt) 0)
|
||
|
'free-stmt
|
||
|
#f
|
||
|
#f
|
||
|
stmt)
|
||
|
(free-statement stmt)
|
||
|
#t)
|
||
|
|
||
|
(define free-stmt free-stmt/close)
|
||
|
|
||
|
(define (free-stmt/drop stmt)
|
||
|
(translate-return (%free-stmt (de-ref-stmt stmt) 1)
|
||
|
'free-stmt
|
||
|
#f
|
||
|
#f
|
||
|
stmt)
|
||
|
(free-statement stmt)
|
||
|
#t)
|
||
|
|
||
|
(define (free-stmt/unbind stmt)
|
||
|
(translate-return (%free-stmt (de-ref-stmt stmt) 2)
|
||
|
'free-stmt
|
||
|
#f
|
||
|
#f
|
||
|
stmt))
|
||
|
|
||
|
(define (free-stmt/reset stmt)
|
||
|
(translate-return (%free-stmt (de-ref-stmt stmt) 3)
|
||
|
'free-stmt
|
||
|
#f
|
||
|
#f
|
||
|
stmt))
|
||
|
|
||
|
;;; get-cursor-name returns the cursor name associated with a specified
|
||
|
;;; statement handle.
|
||
|
|
||
|
(define (get-cursor-name statement cursor-name name-length bytes-available)
|
||
|
(translate-return (%get-cursor-name statement cursor-name name-length
|
||
|
bytes-available)
|
||
|
'get-cursor-name
|
||
|
#f ; env handle ignored by sql-error
|
||
|
#f ; con handle ignored by sql-error
|
||
|
statement))
|
||
|
|
||
|
(define (cursor-name? cursor)
|
||
|
(let ((c-name (make-string 65))
|
||
|
(c-avail (make-SWORD)))
|
||
|
(assign-SWORD c-avail 64)
|
||
|
(get-cursor-name (de-ref-stmt (cursor:stmt cursor)) c-name 64 c-avail)
|
||
|
c-name))
|
||
|
|
||
|
;;; num-result-cols returns the number of columns in a result set.
|
||
|
|
||
|
(define (num-result-cols cmd)
|
||
|
(let ((stmt (sql-command:statement cmd))
|
||
|
(ncols (make-SWORD)))
|
||
|
(translate-return (%num-result-cols (de-ref-stmt stmt) ncols)
|
||
|
'num-result-cols
|
||
|
#f ; env handle ignored by sql-error
|
||
|
#f ; con handle ignored by sql-error
|
||
|
stmt)
|
||
|
(extract-SWORD ncols)))
|
||
|
|
||
|
;;; prepare allocates a new connection and statement handle and prepares an
|
||
|
;;; SQL string for execution with that hstmt. The new hstmt is returned.
|
||
|
|
||
|
(define (prepare sql-str db)
|
||
|
(let* ((con (db:con db))
|
||
|
(stmt (alloc-stmt con)))
|
||
|
(translate-return (%prepare (de-ref-stmt stmt) sql-str
|
||
|
(string-length sql-str))
|
||
|
'prepare
|
||
|
#f ; env handle ignored by sql-error
|
||
|
#f ; con handle ignored by sql-error
|
||
|
stmt)
|
||
|
stmt))
|
||
|
|
||
|
|
||
|
;;; prepare! prepares an SQL string for execution with the provided hstmt.
|
||
|
|
||
|
(define (prepare! stmt sql-str)
|
||
|
(translate-return (%prepare (de-ref-stmt stmt) sql-str
|
||
|
(string-length sql-str))
|
||
|
'prepare
|
||
|
#f ; env handle ignored by sql-error
|
||
|
#f ; con handle ignored by sql-error
|
||
|
stmt))
|
||
|
|
||
|
;;; row-count returns the number of rows affected by an UPDATE, INSERT, or
|
||
|
;;; DELETE statement.
|
||
|
|
||
|
(define (row-count cmd)
|
||
|
(let ((stmt (sql-command:statement cmd))
|
||
|
(rows (make-SDWORD)))
|
||
|
(translate-return (%row-count (de-ref-stmt stmt) rows)
|
||
|
'row-count
|
||
|
#f ; env handle ignored by sql-error
|
||
|
#f ; con handle ignored by sql-error
|
||
|
stmt)
|
||
|
(extract-SDWORD rows)))
|
||
|
|
||
|
;;; set-cursor-name associates a cursor name with an active statement handle.
|
||
|
;;; If an application does not call set-cursor-name, the driver generates
|
||
|
;;; cursor names as needed for SQL statement processing.
|
||
|
|
||
|
(define (set-cursor-name! cursor cursor-name)
|
||
|
(translate-return (%set-cursor-name (de-ref-stmt (cursor:stmt cursor))
|
||
|
cursor-name
|
||
|
(string-length cursor-name))
|
||
|
'set-cursor-name!
|
||
|
#f ; env handle ignored by sql-error
|
||
|
#f ; con handle ignored by sql-error
|
||
|
(cursor:stmt cursor)))
|
||
|
|
||
|
;;; transact requests a commit or rollback operation for all active operations
|
||
|
;;; on all statement handles associated with a connection.
|
||
|
|
||
|
(define (transact con option)
|
||
|
(let ((env (de-ref-env (server-env))))
|
||
|
(translate-return (%transact env (de-ref-con con) option)
|
||
|
'transact
|
||
|
*environment*
|
||
|
con
|
||
|
#f)))
|
||
|
;;; transact OPTION must be one of these:
|
||
|
(define sql-commit 0)
|
||
|
(define sql-rollback 1)
|
||
|
|
||
|
(define *current-db* (make-fluid #f))
|
||
|
(define (current-db) (fluid *current-db*))
|
||
|
|
||
|
(define (with-current-db* db thunk)
|
||
|
(let-fluid *current-db* db
|
||
|
thunk))
|
||
|
|
||
|
(define-syntax with-current-db
|
||
|
(syntax-rules ()
|
||
|
((with-current-db db body1 body2 ...)
|
||
|
(with-current-db* db (lambda () body1 body2 ...)))))
|
||
|
|
||
|
(define (call/db host user password proc)
|
||
|
(let ((db (open-db host user password)))
|
||
|
(dynamic-wind (lambda () #t)
|
||
|
(lambda () (proc db))
|
||
|
(lambda () (close-db db)))))
|
||
|
|
||
|
(define (with-open-db* host user password thunk)
|
||
|
(call/db host user password (lambda (db) (let-fluid *current-db* db thunk))))
|
||
|
|
||
|
(define-syntax with-open-db
|
||
|
(syntax-rules ()
|
||
|
((with-open-db dbname user pwd body1 body2 ...)
|
||
|
(with-open-db* dbname user pwd (lambda () body1 body2 ...)))))
|
||
|
|
||
|
(define-record sql-command
|
||
|
sql-string ;actual string of sql query, e.g. "select * from ..."
|
||
|
statement ;ODBC statement handle
|
||
|
prep) ;#t if statement has been prepared by odbc command PREPARE
|
||
|
|
||
|
(define (string->sql-command sql-string)
|
||
|
(make-sql-command sql-string
|
||
|
#f
|
||
|
#f))
|
||
|
|
||
|
(define-record db
|
||
|
con) ; ODBC connection handle
|
||
|
|
||
|
(define (open-db host user password)
|
||
|
(let ((con (alloc-connect)))
|
||
|
(connect! con host user password)
|
||
|
(make-db con)))
|
||
|
|
||
|
(define (set-current-db! d)
|
||
|
(if (not (db? d))
|
||
|
(error "Error: set-current-db! must be called with a db as argument.")
|
||
|
(set-fluid! *current-db* d))
|
||
|
d)
|
||
|
|
||
|
(define (close-db . maybe-d)
|
||
|
(let* ((db (:optional maybe-d (current-db)))
|
||
|
(con (db:con db)))
|
||
|
(disconnect con)
|
||
|
(free-connect con)
|
||
|
#t))
|
||
|
|
||
|
(define (execute-sql command . args)
|
||
|
(receive (db params) (if (null? args)
|
||
|
(values (current-db) '())
|
||
|
(values (car args) (cdr args)))
|
||
|
(let* ((con (db:con db))
|
||
|
(cmd (if (sql-command? command)
|
||
|
(begin (if (not (sql-command:statement command))
|
||
|
(set-sql-command:statement
|
||
|
command (alloc-stmt con)))
|
||
|
command)
|
||
|
(make-sql-command command (alloc-stmt con) #f)))
|
||
|
(stmt (sql-command:statement cmd)))
|
||
|
|
||
|
;; Prepare the statement if it's not already prepared.
|
||
|
(if (not (sql-command:prep cmd))
|
||
|
(begin (prepare! stmt (sql-command:sql-string cmd))
|
||
|
(set-sql-command:prep cmd #t)))
|
||
|
|
||
|
;; Do it.
|
||
|
(let ((ncols (num-result-cols cmd)))
|
||
|
(if (not (null? params))
|
||
|
(bind-params cmd params))
|
||
|
(execute stmt)
|
||
|
(let* ((nrows (row-count cmd))
|
||
|
(answer (cond ((> nrows 0) nrows) ;delete,insert,update
|
||
|
((= ncols 0) #t) ;make table
|
||
|
(else ;select
|
||
|
(let ((cursor (prepare-cursor cmd ncols)))
|
||
|
(set-sql-command:prep cmd #f)
|
||
|
(set-sql-command:statement cmd #f)
|
||
|
cursor)))))
|
||
|
;;(if (not (cursor? answer)) ;different free-stmts
|
||
|
;; (free-stmt stmt))
|
||
|
answer)))))
|
||
|
|
||
|
(define (bind-params cmd params)
|
||
|
(let iter ((coln 1) (prms params))
|
||
|
(if (null? prms)
|
||
|
cmd
|
||
|
;; get info for each column
|
||
|
(let ((val (car prms)))
|
||
|
(receive (data-type precision scale nullable)
|
||
|
(describe-param cmd coln)
|
||
|
;; bind it
|
||
|
(bind-parameter cmd
|
||
|
coln
|
||
|
data-type
|
||
|
precision
|
||
|
scale
|
||
|
(cond ((date? val)
|
||
|
(sql-date->string (date->sql-date val)))
|
||
|
((and (= data-type
|
||
|
(or sql/date
|
||
|
sql/time
|
||
|
sql/timestamp))
|
||
|
(integer? val))
|
||
|
(sql-date->string
|
||
|
(date->sql-date (date val))))
|
||
|
(else val)))
|
||
|
;; bind next parameter
|
||
|
(iter (+ 1 coln) (cdr prms)))))))
|
||
|
|
||
|
;; prepare to fetch rows of data
|
||
|
|
||
|
(define (prepare-cursor cmd ncols)
|
||
|
(let ((col-data (make-table-desc (sql-command:statement cmd)
|
||
|
(make-vector ncols))))
|
||
|
(let iter ((icol 1))
|
||
|
(if (<= icol ncols)
|
||
|
;; get info for column
|
||
|
(receive (name name-size data-type precision scale nullable)
|
||
|
(describe-col cmd icol)
|
||
|
;; bind it
|
||
|
(vector-set! (table-desc:cols col-data)
|
||
|
(- icol 1)
|
||
|
(make-column-desc
|
||
|
name name-size data-type precision scale nullable
|
||
|
(bind-col cmd icol data-type precision)))
|
||
|
;; bind the next column
|
||
|
(iter (+ icol 1)))))
|
||
|
(make-cursor col-data ncols (sql-command:statement cmd) cmd)))
|
||
|
|
||
|
;; fetching rows of data
|
||
|
|
||
|
(define fetch-row fetch)
|
||
|
|
||
|
(define (fetch-rows cursor nrows)
|
||
|
(let recur ((nrows nrows))
|
||
|
(if (zero? nrows) '()
|
||
|
(let ((row (fetch-row cursor)))
|
||
|
(if row
|
||
|
(cons row (recur (- nrows 1)))
|
||
|
'())))))
|
||
|
|
||
|
(define (fetch-all cursor)
|
||
|
(let ((row (fetch-row cursor)))
|
||
|
(if row
|
||
|
(cons row (fetch-all cursor))
|
||
|
'())))
|
||
|
|
||
|
;; closing a cursor to recycle statement handle
|
||
|
|
||
|
(define (cursor-closed? cursor)
|
||
|
(not (cursor:cmd cursor)))
|
||
|
|
||
|
(define (close-cursor cursor)
|
||
|
(or (cursor-closed? cursor) ;check if already closed
|
||
|
(let ((stmt (cursor:stmt cursor))
|
||
|
(cmd (cursor:cmd cursor)))
|
||
|
(set-cursor:cmd cursor #f)
|
||
|
(if (sql-command:statement cmd)
|
||
|
(free-stmt/drop stmt)
|
||
|
(set-sql-command:statement cmd (free-stmt/close stmt)))))
|
||
|
#t)
|
||
|
|
||
|
;; commit or rollback
|
||
|
|
||
|
(define (commit db)
|
||
|
(transact (db:con db) sql-commit))
|
||
|
|
||
|
(define (rollback db)
|
||
|
(transact (db:con db) sql-rollback))
|