Sam Thibault
ODBC interface for scsh
Spring 1999

1 Scsh ODBC

Scsh ODBC is an ODBC interface for scsh providing the complete
functionality for all ODBC Core 1.0 functions.  The procedures that
Scsh ODBC exports allow a clean, scheme-style interface for executing
SQL statements with a RDBMS.  The tools include simple management of
open connections, SQL statement execution with or without parameters,
and multiple fetch operations.  Scsh ODBC also possesses a small error
system that raises conditions specific to ODBC errors and errors
returned from the RDBMS.

2 Exported Interface

The procedures and syntaxes exported by Scsh ODBC provide all the functionality
neccesary to conduct SQL interaction with a RDBMS. 

2.1 A Note on Data Types

Scsh ODBC makes use of three abstract data types to keep the interface clean and
straight-forward.  These three data types are explained here:

     db:
          A DB is an opened connection to a RDBMS.  In addition, Scsh ODBC also
          allows a program to keep track of a "current db".  The current db
          will be used as the default connection in some procedure calls where
          the db argument is optional.

     command:
          The COMMAND data type keeps track of an SQL statement (e.g. "select *
          from foo"), its associated statement handle, the preparation state
          of the command, and allows statement handle re-use (see CURSORS 
          below).  In Scsh ODBC a command can be passed to procedures executing
          SQL statements rather than just a string.  This helps performance for
          SQL statements that are repeatedly executed.

     cursor:
          After executing a select statement, Scsh ODBC will return a CURSOR.
          The cursor may then be passed to a fetching procedure to get values 
          from a result table.  Once all the resulting rows have been fetched
          or the cursor has bee closed (by calling close-cursor), the cursor's
          associated statement handle will be freed and cycled into the
          originating command for re-use.

2.2 Managing Connections

(open-db host user password) => db                                    procedure

     This procedure opens a connection to a RDBMS.  The arguments passed are 
     all strings.  HOST will probably contain port information, such as: 

          "my-rdbms.foo.bar tcpip 1313"

     USER is the username you would like to use for this connection; 
     PASSWORD is the password for the username given.

     The return value DB is an abstract data type that will be passed to the 
     other procedures at this level.

(set-current-db! db) => undefined                                     procedure

     Once a connection has been opened, it can be set as the "current-db".
     Any procedure with an optional db argument will use this current-db if
     the db argument is not supplied. (see close-db below)

(current-db) => db | #f                                               procedure

     Calling this procedure will return the current-db if one has been set.  If
     a current-db has not been set, this procedure returns #f.

(close-db [db]) => #t                                                 procedure

     This procedure is used to close an opened connection DB.  If a db is
     supplied in DB, that connection will be closed.  Otherwise, close-db
     will close the db assigned to be the current db.

(call/db host user password proc) => value(s) of proc                 procedure

     This procedure opens a connection with the HOST USER and PASSWORD
     provided (see open-db).  PROC is then called with one argument, the newly
     opened connection.  The connections is closed when the value(s) of proc 
     are returned. 

(with-current-db* db thunk) => value(s) of thunk                      procedure

     This procedure evaluates THUNK within a dynamic scope that binds the 
     connection DB to be the current db.  The value of (CURRENT-DB) returns to
     its previous value when this procedure returns.  In the event of a
     non-local exit (throwing to an outer continuation, or raising an
     exception), the current db is also reset.

(with-open-db* host user password thunk) => value(s) of thunk         procedure

     This procedure opens a connection with the HOST USER and PASSWORD
     provided (see open-db).  THUNK is then evaluated within a scope that
     binds the newly opened connection to be the current-db.  The connection
     is closed when the value(s) are returned.  Like with-current-db*, the
     current-db resumes its prior value.

(with-current-db db body1 body2 ...) => value(s) of body                 syntax
(with-open-db host user password body1 body2 ...) => value(s) of body    syntax

     These two syntaxes are macro versions of with-current-db* and 
     with-open-db*.

2.3 Executing SQL Statements

(string->sql-command sql-string) => command                           procedure

     This procedure will create an abstract "command" datatype from the given 
     string.  A command record is useful because the SQL string passed
     will only be prepared once.  If the SQL statement contains parameters,
     the new parameter arguments can be supplied again without re-preparing
     the SQL string.  

(execute-sql command [db params]) => #t | integer | cursor            procedure

     This procedure will execute any SQL command.  The COMMAND argument can
     be either a command record (made with sql->command) or a string
     containing a properly formed SQL statement.  A connection may be supplied
     in DB.  If no connection is given, the current-db will be used.
     Any number of parameters for the SQL statement may then be given.

     The value returned by execute-sql depends on the type of SQL statement.
     Commands such as "create" or "drop" will return #t.  Commands modifying
     rows, like "insert" "update" or "delete", will return an integer - the
     number of rows modified.  A select statement will cause execute-sql
     to return a cursor record.  This cursor record may be passed to any of
     the fetch procedures (fetch-row, fetch-rows, fetch-all) to retrieve
     data from the selected table.

     Recycling of statements...

2.4 Fetching Results

(fetch-row cursor) => vector | #f                                     procedure
(fetch-rows cursor nrows) => list of vectors | #f                     procedure
(fetch-all cursor) => list of vectors | #f                            procedure

     These fetch operations will retrieve data from a table using a cursor 
     record returned from execute-sql.  If there are no more rows in the table
     these procedures will return #f.  Otherwise, fetch-row will return the
     first row of data in the result set.  Fetch-rows will return NROWS rows
     from the table (NROWS is an integer) or all remaining rows if less than
     NROWS rows remain in the result set.  Fetch-all will return all the
     remaining rows in the result set.  If all rows in a cursor have been
     fetched, close-cursor will be called on CURSOR.

     Each row is returned as a vector; each element of the vector corresponds
     to a column of the table.  The element's order matches the order of the
     the columns in the result set.  Vectors in a list (of multiple fetched
     rows) are in fetched order, with the first row fetched begin the first row
     in the list.

2.5 Cursors

(close-cursor cursor) => #t                                           procedure

     This procedure calls free-stmt/close on the cursor's associated statement
     handle.  Next, it will recycle the statement back into the originating
     command for reuse.  If the command already has a statament handle for use,
     the freed statement handle will be discarded.

(cursor-name? cursor) => string                                       procedure

     This procedure returns the cursor name associated with CURSOR.

(set-cursor-name cursor name-string) => undefined                     procedure

     Set-cursor-name associates the cursor name [name-string] with the active 
     statement handle [odbc-hstmt].  If an application does not call 
     set-cursor-name, the driver generates cursor names as needed for SQL 
     statement processing.

2.6 Transaction Control

(cancel command) => #t                                                procedure

     This procedure cancels all processing on the statement handle associated
     with COMMAND.

(commit db) => #t                                                     procedure

     This procedure performs a commit operation for all active operations on 
     all statement handles associated with the connection DB.

(rollback db) => #t                                                   procedure

     This procedure performs a rollback operation for all active operations on 
     all statement handles associated with the connection DB.

2.7 Column Information and Binding

(bind-col command icol type precision) => c-storage                procedure

     Bind-col allocates a storage buffer to receieve data returned in fetches
     from a result set.  The buffer will correspond to the select performed
     using the statement handle COMMAND, and a particular column of that
     select as specified by ICOL, an integer value.  The size of the buffer
     will depend on the final two arguments.  TYPE is one of the data type
     symbols such as 'sql/integer' (not a string), and PRECISION is an
     integer that the table should have defined for that column.  The ODBC
     documentation specifies how the precision value relates to each ODBC
     data type.

(bind-parameter command icol data-type 
                precision scale param) => #t                          procedure

     Bind-parameter creates a storage buffer containing the value of a
     parameter for an SQL statement associated with the statement handle 
     COMMAND.  The ICOL argument names the parameter corresponding to
     this buffer.  So if ICOL is the value '2', this buffer will hold the
     value for the second parameter argument ('?') in the SQL statement.
     DATA_TYPE is one of the defined data-type symbols like 'SQL/Integer'.
     PRECISION and SCALE are integer arguments that affect different
     data types in the manner specified by the ODBC documentation.  PARAM
     is the actual string, integer, etc. that will be bound and used in the 
     execution of the SQL statement.

(describe-col command icol)
     => values: name name-size data-type precision scale nullable     procedure

     Describe-col returns information about one column (specified by ICOL)
     in the result set associated with the statement handle of COMMAND.

     All the return values are integers, except for the column name which is a
     string.  The interpretation of the integer arguments is specified in the
     ODBC documentation concerning data types.

(describe-param command icol)
     => values: data-type precision scale nullable                    procedure

     Describe-param returns the description of a parameter marker (specified by
     the integer ICOL) associated with the SQL statement prepared using the
     statement handle command.  The return values are all integers.

(num-result-cols command) => integer                                  procedure

     This procedure returns the number of columns in the result set associated
     with the statement handle [odbc-hstmt].

(row-count command) => integer                                        procedure

     Row-count returns the number of rows affected by the UPDATE, INSERT, or
     DELETE statement associated with the statement handle of COMMAND.

2.8 Freeing Resources

(free-env) => #t                                                      procedure

     Free-env frees the environment handle and frees all memory associated
     with the environment handle.  This procedure should only be called
     after the application is completely finished and *all* connections have
     been closed.

3 Error System

3.1 Calling ODBC Functions

     When a call to an ODBC function occurs - whether initiated from a 
top-level or mid-level procedure - the ODBC function has the possibility of
raising several different error conditions.  The Scsh ODBC interface provides
procedures to handle the possible conditions that may arise.  Below are the
error conditions associated with each ODBC return value:

     odbc return type      = scheme condition
     ----------------------=--------------------
     SQL_INVALID_HANDLE    = sql-invalid-error
     SQL_ERROR             = sql-error
     SQL_SUCCESS           = #t
     SQL_SUCCESS_WITH_INFO = sql-info-warning
     SQL_STILL_EXECUTING   = sql-busy-exception
     SQL_NEED_DATA         = sql-param-exception
     SQL_NO_DATA_FOUND     = #f
     ----------------------=--------------------

     SQL_INVALID_HANDLE:
	Function failed due to invalid environment, connection, or statement
        handles.  This indicates a programming error.

     SQL_ERROR:
	Function failed.  The error message and error code will be returned in
        the condition raised.

     SQL_SUCCESS:
	Function completed successfully; no additional information is
        available. 

     SQL_SUCCESS_WITH_INFO:
	Function completed successfully; possibly with a nonfatal error.  The
        warning message and error code can be caught using 
        with-sql-info-handler* described below.

     SQL_STILL_EXECUTING:
	A function that was started asynchronously is still executing.

     SQL_NEED_DATA:
	The application failed to send parameter data values for the statement
        being processed.

     SQL_NO_DATA_FOUND:
	All rows from the result set have been fetched.

3.2 Scheme Error Procedures

(sql-invalid-error? condition) => boolean                             procedure
(sql-error? condition) => boolean                                     procedure
(sql-info-warning? condition) => boolean                              procedure
(sql-busy-exception? condition) => boolean                            procedure
(sql-param-exception? condition) => boolean                           procedure

     These five procedures check a condition to see if it corresponds to the 
     particular condition contained in the procedure name. (sql-invalid-error 
     for example)

(with-sql-invalid-handler* handler thunk) => value(s) of thunk        procedure
(with-sql-error-handler* handler thunk) => value(s) of thunk          procedure
(with-sql-info-handler* handler thunk) => value(s) of thunk           procedure
(with-sql-busy-handler* handler thunk) => value(s) of thunk           procedure
(with-sql-param-handler* handler thunk) => value(s) of thunk          procedure

     Programs can use the five procedures above to handle the specific 
     condition specified in the name of the handler. (sql-invalid-error for 
     example)  If the specific condition is signalled while the thunk is 
     executing, handler will be called with six arguments:

	  (handler function error-code error-message henv hdbc hstmt)

     The ERROR-CODE and ERROR-MESSAGE are strings automatically retrieved by
     Scsh ODBC.  The [function] is a symbol name of the ODBC function in which 
     the error occured, such as 'Execute.

(with-sql-handler* handler thunk) => value(s) of thunk                procedure

     With-sql-handler* will call the handler for all of the five conditions
     above.

-------------------------------------------------------------------------------

4 Other Stuff

4.1 Notes About this Level

     This level of the interface contains all of the ODBC Core 1.0 functions.
Each ODBC function has been wrapped up inside a Scheme procedure which
manages all the neccesary resources for the ODBC function (by allocating
memory and converting datatypes using procedure in the Low Level Scsh ODBC
Interface) and then calls the ODBC function (these procedures actually call
procedures in the Low Level Interface that link to C stubs that call to the
ODBC library).  These Scheme "wrappers" will raise any conditions resulting
from the ODBC call (see Error System below).  Finally these procedures will 
return a useful Scheme value approproate to the ODBC function called.

4.2 ODBC Core 1.0


          sql/char
	  sql/numeric
          sql/decimal
	  sql/integer
	  sql/smallint
	  sql/float
	  sql/real
	  sql/double
	  sql/varchar
	  sql/date
	  sql/time
	  sql/timestamp
	  sql/longvarchar
	  sql/binary
	  sql/varbinary
	  sql/longvarbinary
	  sql/bigint
	  sql/tinyint
	  sql/bit

(type-val->string type)                                               procedure

     This procedure merely returns a string representation of the given data
     type such as "sql/integer" useful for error messages.

(col-attributes odbc-hstmt icol descriptor-type) => integer | string  procedure

     Col-attributes returns information about one attribute associated with
     a column (selected by the integer ICOL) in a result set. (specified
     by the statement handle ODBC-HSTMT).  The desired attribute is
     specified in DESCRIPTOR-TYPE using one of the following defined symbols:

          column-auto-increment
	  column-case-sensitive
	  column-count
	  column-display-size
	  column-length
	  column-money
	  column-name
	  column-nullable
	  column-precision
	  column-scale
	  column-searchable 
	  column-type
	  column-type-name
	  column-unsigned
	  column-updatable

(describe-col odbc-hstmt icol)
     => values: name name-size data-type precision scale nullable     procedure

     Describe-col returns information about one column (specified by ICOL)
     in the result set associated with the statement handle ODBC-HSTMT.

     All the return values are integers, except for the column name which is a
     string.  The interpretation of the integer arguments is specified in the
     ODBC documentation concerning data types.

(describe-param odbc-hstmt icol)
     => values: data-type precision scale nullable                    procedure

     Describe-param returns the description of a parameter marker (specified by
     the integer ICOL) associated with the SQL statement prepared using the
     statement handle odbc-hstmt.  The return values are all integers.

(num-result-cols odbc-hstmt) => integer                               procedure

     This procedure returns the number of columns in the result set associated
     with the statement handle [odbc-hstmt].

(row-count odbc-hstmt) => integer                                     procedure

     Row-count returns the number of rows affected by the UPDATE, INSERT, or
     DELETE statement associated with the statement handle [odbc-hstmt].

(alloc-connect) => odbc-hdbc                                          procedure

     This procedure allocates space for a new database connection without 
     performing the actual connection.  The new connection handle, the
     ODBC-HDBC that is returned, is allocated within the allocated 
     environment.  If the environment handle has not yet been created,
     alloc-connect will create the environment handle by calling server-env.

(alloc-stmt odbc-hdbc) => odbc-hstmt                                  procedure

     This procedure allocates a new statement handle using the supplied 
     connection handle.  The newly allocated statement handle is then returned.

(bind-col odbc-hstmt icol type precision) => c-storage                procedure

     Bind-col allocates a storage buffer to receieve data returned in fetches
     from a result set.  The buffer will correspond to the select performed
     using the statement handle ODBC-HSTMT, and a particular column of that
     select as specified by ICOL, an integer value.  The size of the buffer
     will depend on the final two arguments.  TYPE is one of the data type
     symbols such as 'sql/integer' (not a string), and PRECISION is an
     integer that the table should have defined for that column.  The ODBC
     documentation specifies how the precision value relates to each ODBC
     data type.

(bind-parameter odbc-hstmt icol data-type 
                precision scale param) => #t                          procedure

     Bind-parameter creates a storage buffer containing the value of a
     parameter for an SQL statement associated with the statement handle 
     ODBC-HSTMT.  The ICOL argument names the parameter corresponding to
     this buffer.  So if ICOL is the value '2', this buffer will hold the
     value for the second parameter argument ('?') in the SQL statement.
     DATA_TYPE is one of the defined data-type symbols like 'SQL/Integer'.
     PRECISION and SCALE are integer arguments that affect different
     data types in the manner specified by the ODBC documentation.  PARAM
     is the actual string, integer, etc. that will be bound and used in the 
     execution of the SQL statement.

(connect host user password) => odbc-hdbc                             procedure

     Connect allocates a new connection handle (using alloc-connect) and opens
     a connection to the data source specified by the three string arguments
     HOST, USER, and PASSWORD.  The connection handle associated with
     the newly opened connection is returned.

(connect! odbc-hdbc host user password) => undefined                  procedure

     Connect! uses the already allocated connection handle given in ODBC-HDBC
     and connects to the data source specified by the three string arguments
     HOST, USER, and PASSWORD.

(disconnect odbc-hdbc) => #t                                          procedure

     Disconnect closes the connection associated with the connection handle
     ODBC-HDBC.

(exec-direct odbc-hstmt sql-string) => #t                             procedure

     Given an allocated statement handle ODBC-HSTMT, exec-direct executes
     the preparable SQL statement given as a string in SQL-STRING.

(execute odbc-hstmt) => #t                                            procedure

     This procedure executes the prepared SQL statment associated with the
     statement handle ODBC-HSTMT.

(fetch cursor-record) => vector | #f                                  procedure

     Fetch retrieves a row of data from a result set using the information in
     the given CURSOR-RECORD.  The driver returns data for all columns that 
     were bound to storage locations with bind-col and combines the returned
     values in a vector.  If there were no more rows to be fetched, #f is
     returned.

     Note: Fetch needs to be changed so it acts like the other mid-level
           functions.  Right now it is exactly fetch-row.

(free-connection odbc-hdbc) => #t                                     procedure

     Free-connection releases the connection handle [odbc-hdbc] and frees all
     the memory associated with that connection handle.

(free-stmt/close cursor) => #t                                        procedure
(free-stmt/drop (command | cursor)) => #t                             procedure
(free-stmt/unbind command) => #t                                      procedure
(free-stmt/reset command) => #t                                       procedure

     These procedures stop processing associated with a specific statement 
     handle, close any open cursors, discards pending results, and, optionally,
     frees all resources associated with the statement handle.  The four 
     options for this ODBC function have been separated into four different 
     procedures as described below:

     free-stmt/close:  Closes the cursor associated with the given CURSOR,
                       discarding pending results.  The cursor may be reopened
                       by executing a "select" statement.

     free-stmt/drop:   Frees all resorces associated with the COMMAND or
                       CURSOR's statement handle.  The statement handle will
                       no longer be accesible.

     free-stmt/unbind: Any buffers bound to ODBC-HSTMT using bind-col are
                       released.

     free-stmt/reset:  Any buffers bound to ODBC-HSTMT using bind-param are
                       released.

(prepare sql-string [db]) => odbc-hstmt                               procedure

    Prepare uses the given DB, and opens a connction to the 
    data source specified by [host], [user], and [password].  Then prepare
    allocates a statement handle and prepares the statement given as 
    [sql-string] for execution.  The new statement handle is returned.

(prepare! odbc-hstmt sql-string) => undefined                         procedure

    Using the connection handle given as [odbc-hstmt], this procedure prepares
    the sql-string given as [sql-string].

