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].