2 ;; @description ODBC database interface
3 ;; @version 1.7 - comments redone for automatic documentation
4 ;; @author Lutz Mueller, 2003
6 ;; <h2>OCBC Interface functions</h2>
8 ;; This module has only been tested on Win32 but should work on UNIX too
9 ;; with few modifications. At the beginning of the program file include
10 ;; a 'load' statement for the module:
15 ;; Some of the code assumes Intel (low -> high) little-endian byte order.
17 ;; See the end of file for a test function 'test-odbc', which demonstrates the
18 ;; usage of the module and can be used to test a correct ODBC installation and
21 ;; <h2>Requirements</h2>
22 ;; On Win32 platforms required 'odbc32.dll' is part of the OS's installations.
23 ;; There is no UNIX function import tested or adapted for this ODBC module.
25 ;; <h2>Function overview</h2>
27 ;; (ODBC:connect data-source-name-str user-name-str password-str) ; connect to a data source
28 ;; (ODBC:query sql-str) ; perform a SQL statement
29 ;; (ODBC:num-cols) ; number of columns in a query result set from 'select'
30 ;; (ODBC:column-atts col) ; retrieve columns attributes
31 ;; (ODBC:fetch-row) ; fetch a row of data after a sql query with 'select'
32 ;; (ODBC:affected-rows) ; number of rows affected by a sql query: 'delete', 'update' etc.
33 ;; (ODBC:tables) ; return a list of tables in the current database
34 ;; (ODBC:columns table-name) ; return an array of column attributes in table-name
35 ;; (ODBC:close-db) ; close database connection
40 ; ----------------- import functions from DLL -------------------
43 ; set to the appropiate library on Unix or Win32
44 (define ODBC-library
"odbc32.dll")
46 ; Constants used, make sure these constants are Ok on your Operating System or Platform.
47 ; Note, that (define var value) is the same as as saying (set 'var value), it is here more
48 ; of a visual distinction, documenting that values are constants and shouldn't be changed.
49 ; Most of these are defned in sql.h, sqltypes.h and sqlext.h of your platform.
50 ; The following definitions come from c:\Borland\BCC\Include
52 (define SQL_HANDLE_ENV
1)
53 (define SQL_HANDLE_DBC
2)
54 (define SQL_HANDLE_STMT
3)
55 (define SQL_HANDLE_DESC
4)
57 (define SQL_NULL_HANDLE
0)
59 (define SQL_SUCCESS
0)
60 (define SQL_SUCCESS_WITH_INFO
1)
62 (define SQL_OV_ODBC3
3)
63 (define SQL_ATTR_ODBC_VERSION
200)
65 (define SQL_LOGIN_TIMEOUT
103)
70 (define SQL_C_CHAR SQL_CHAR
)
74 ; there are many more, which are not used here, goto microsoft.com and unixodbc.org for
75 ; more information on ODBC SQLxxx API
96 (import ODBC-library fun
))
98 ; ------------------------------- reserve space for global pointers ----------------------------
100 (set 'ptr-odbc-env
" ") ; pointer to environment handle
101 (set 'ptr-odbc-conn
" ") ; pointer to connection handle
102 (set 'ptr-result-cols
" ") ; pointer to number of columns in result
103 (set 'ptr-odbc-stmt
" ") ; pointer to handle for sql statement
104 (set 'ptr-result-rows
" ") ; pointer to number of affected rows from sql statement
106 (set 'odbc-stmt nil
) ; statement handle
107 (set 'odbc-conn nil
) ; connection handle
108 (set 'result-cols
0) ; contains the number of rows resulting from a 'select' qery
110 ; -------------------------------------- AUXILIARY ROUTINES ------------------------------------
114 (define (is-error-result)
115 ;result is 16bit, disregard upper 16 bits
116 (set 'odbc-result
(& 0xFFFF odbc-result
))
117 (and (!= odbc-result SQL_SUCCESS
) (!= odbc-result SQL_SUCCESS_WITH_INFO
)))
119 ; initialize and make connection
123 ; get environment handle
124 (set 'odbc-result
(SQLAllocHandle SQL_HANDLE_ENV SQL_NULL_HANDLE ptr-odbc-env
))
126 (if (is-error-result)
128 (println "Error allocating env handle")
131 (set 'odbc-env
(get-int ptr-odbc-env
))
134 (set 'odbc-result
(SQLSetEnvAttr odbc-env SQL_ATTR_ODBC_VERSION SQL_OV_ODBC3
0))
136 (if (is-error-result)
138 (println "Error setting ODBC environment")
139 (SQLFreeHandle SQL_HANDLE_ENV odbc-env
)
143 ; get diagnostic record
145 ; retrieve error info after last failed ODBC request
147 ; type is one of the following:
149 ; SQL_HANDLE_ENV, SQL_HANDLE_DBC, SQL_HANDLE_STMT, SQL_HANDLE_DESC
153 (set 'diag-status
" ")
156 (set 'diag-message
" ")
157 (SQLGetDiagRec type odbc-conn
1 diag-status diag-err diag-message
64 diag-mlen
)
158 (string diag-message
" " diag-status
(get-int diag-err
)))
160 ; bind all columns to string output
162 ; before fetching rows string variables are configured with sufficient long string buffers
163 ; for the 'fetch' statement.
166 (set 'vars
'(var0 var1 var2 var3 var4 var5 var6 var7 var8 var9
167 var10 var11 var12 var13 var14 var15 var16 var17 var18 var19
168 var20 var21 var22 var23 var24 var25 var26 var27 var28 var29
169 var30 var32 var32 var33 var34 var35 var36 var37 var38 var39
170 var40 var41 var42 var43 var44 var45 var46 var47 var48 var49
171 var50 var51 var52 var53 var54 var55 var56 var57 var58 var59
172 var60 var51 var62 var63 var64
))
175 (define (bind-columns)
176 (set 'ptr-result-err
" ")
177 (for (v 1 result-cols
)
178 (set 'w
(+ (last (column-atts v
)) 1))
179 (set (nth v vars
) (format (string "%" w
"s") ""))
180 (SQLBindCol odbc-stmt
(int v
) SQL_C_CHAR
(eval (nth v vars
)) w ptr-result-err
))
185 ;==================================== USER ROUTINES ========================================
188 ;; @syntax (ODBC:connect <str-data-source> <str-user> <str-password>)
189 ;; @param <str-data-source> The ODBC dara source.
190 ;; @param <str-user> The user name.
191 ;; @param <str-password> The password of the user.
192 ;; @return 'true' on success, 'nil' on failure.
194 ;; Connect to a data-source with a user name and password.
195 ;; The data-source name must be configured first via ODBC
196 ;; administrative tools, i.e. a control applet on Win32.
199 ;; (ODBC:connect "mydatabase" "johndoe" "secret")
201 (define (ODBC:connect data-source user password
)
206 ; allocate connection handle
207 (set 'odbc-result
(SQLAllocHandle SQL_HANDLE_DBC odbc-env ptr-odbc-conn
))
209 (if (is-error-result)
211 (println "Error allocating conn handle")
212 (SQLFreeHandle SQL_HANDLE_ENV odbc-env
)
215 (set 'odbc-conn
(get-int ptr-odbc-conn
))
217 ; set timeout for connection
218 (SQLSetConnectAttr odbc-conn SQL_LOGIN_TIMEOUT
5 0)
220 ; connect to a data source
221 (set 'odbc-result
(SQLConnect odbc-conn data-source SQL_NTS
225 (if (is-error-result)
227 (println "Could not connect")
228 (SQLFreeHandle SQL_HANDLE_DBC odbc-conn
)
229 (SQLFreeHandle SQL_HANDLE_ENV odbc-env
)
234 ;; @syntax (ODBC:query <str-sql>)
235 ;; @param <str-sql> The SQL statement string.
236 ;; @return 'true' on success, 'nil' on failure.
238 ;; Send and SQL string for database manipulation
241 ;; (query "select * from someTable")
242 ;; (query "delete from addresses")
243 ;; (query "insert into fruits values ('apples', 11)")
245 (define (ODBC:query sql-string
)
247 ; is stmt handle exists free it
249 (SQLFreeHandle SQL_HANDLE_STMT odbc-stmt
)
253 ; allocate statement handle
254 (set 'odbc-result
(SQLAllocHandle SQL_HANDLE_STMT odbc-conn ptr-odbc-stmt
))
256 (if (is-error-result)
258 (println "could not allocate statement handle")
260 (set 'odbc-stmt
(get-int ptr-odbc-stmt
)))
263 (set 'odbc-result
(SQLExecDirect odbc-stmt sql-string SQL_NTS
))
264 (if (is-error-result)
266 (println "query failed")
270 ; find number of columns in result set
271 (SQLNumResultCols odbc-stmt ptr-result-cols
)
272 (set 'result-cols
(& 0xFFFF (get-int ptr-result-cols
)))
274 ; bind colums to string vars for fetching
275 (if (not (= result-cols
0)) (bind-columns) true
)
282 ;; @syntax (ODBC:num-cols)
283 ;; @return Number of columns in the result set.
285 (define (num-cols) result-cols
)
288 ;; @syntax (ODBC:columns-atts <num-col>)
289 ;; @param <num-col> The number of the column, starting witth 1 for the first.
290 ;; @return A list of attributes for a column in a result set.
292 ;; Returns a list with the columname SQL, data type number and required column size
293 ;; when displaying in a string. For the data type number and SQL data type see
294 ;; the file 'sql.h' on your platform OS, i.e. 'SQL_VARCHAR', 'SQL_INTEGER' etc.
296 ;; before using 'ODBC:column-atts' a query has to be performed.
299 ;; (ODBC:column-atts 1) => ("name" 12 20)
301 ;; The first column has the header '"name"' with data type 'SQL_VARCHAR' (12)
302 ;; and a maximum display width of 20 characters.
304 (define (column-atts col
)
305 (set 'col-name-out
" ")
306 (set 'ptr-name-len
" ")
307 (set 'ptr-data-type
" ")
308 (set 'ptr-col-size
" ")
309 (set 'ptr-dec-dig
" ")
310 (set 'ptr-nullable
" ")
312 (set 'odbc-result
(& 0xFFFF (SQLDescribeCol odbc-stmt
(int col
)
319 (list col-name-out
(& 0xFFFF (get-int ptr-data-type
)) (get-int ptr-col-size
)))
323 ;; @syntax (ODBC:fetch-row)
324 ;; @return A list of items of a result set row.
326 ;; Fetches a row of data after a previously executed 'ODBC:query'. Each data is formatted as
327 ;; a string, and can be converted using newLISP conversion functions
328 ;; like: 'int', 'float' or 'string'.
330 ;; If data types are unknown then 'ODBC:column-atts' can be used to retrieve the data type
334 ;; (ODBC:fetch-row) => ("apples" "11")
336 (define (fetch-row , row
)
338 (set 'odbc-result
(& 0xFFFF (SQLFetch odbc-stmt
)))
339 (if (is-error-result)
342 (for (x result-cols
1) (push (eval (nth x vars
)) row
))
346 ;; @syntax (ODBC:affected-rows)
347 ;; @return Number of rows affected by the last SQL statement.
349 ;; Returns the number of rows affected by an 'insert', 'update' or 'delete', 'ODBX:query'
350 ;; operation. After a 'select' operation the number -1 will be returned.
352 (define (affected-rows)
353 (set 'odbc-result
(& 0xFFFF (SQLRowCount odbc-stmt ptr-result-rows
)))
354 (if (is-error-result) 0 (get-int ptr-result-rows
)))
357 ;; @syntax (ODBC:tables)
358 ;; @return A list of tables in the current database connection.
362 ; is stmt handle exists free it
364 (SQLFreeHandle SQL_HANDLE_STMT odbc-stmt
)
368 ; allocate statement handle
369 (set 'odbc-result
(SQLAllocHandle SQL_HANDLE_STMT odbc-conn ptr-odbc-stmt
))
370 (if (is-error-result)
372 (println "could not allocate statement handle")
374 (set 'odbc-stmt
(get-int ptr-odbc-stmt
)))
377 (set 'odbc-result
(SQLTables odbc-stmt
0 SQL_NTS
0 SQL_NTS
"%" SQL_NTS
0 SQL_NTS
))
378 (if (is-error-result)
380 (println "query failed")
384 ;; find number of columns in result set
385 (SQLNumResultCols odbc-stmt ptr-result-cols
)
386 (set 'result-cols
(& 0xFFFF (get-int ptr-result-cols
)))
388 ;; bind colums to string vars for fetching
389 (if (not (= result-cols
0)) (bind-columns) true
)
393 (while (set 'row
(ODBC:fetch-row
))
394 (push (nth 2 row
) names -
1))
399 ;; @syntax (ODBC:columns <str-table-name>)
400 ;; @param <str-table-name> The name of the table.
401 ;; @return A list of list of columns and their attributes.
403 (define (ODBC:columns table
)
405 ; is stmt handle exists free it
407 (SQLFreeHandle SQL_HANDLE_STMT odbc-stmt
)
411 ; allocate statement handle
412 (set 'odbc-result
(SQLAllocHandle SQL_HANDLE_STMT odbc-conn ptr-odbc-stmt
))
414 (if (is-error-result)
416 (println "could not allocate statement handle")
418 (set 'odbc-stmt
(get-int ptr-odbc-stmt
)))
421 (set 'odbc-result
(SQLColumns odbc-stmt
0 SQL_NTS
0 SQL_NTS
422 table SQL_NTS
0 SQL_NTS
))
423 (if (is-error-result)
425 (println "query failed")
429 ; find number of columns in result set
430 (SQLNumResultCols odbc-stmt ptr-result-cols
)
431 (set 'result-cols
(& 0xFFFF (get-int ptr-result-cols
)))
433 ; bind colums to string vars for fetching
434 (if (not (= result-cols
0)) (bind-columns) true
)
438 (while (set 'col
(ODBC:fetch-row
))
439 (set 'attr
(list (nth 3 col
) (nth 5 col
) (nth 6 col
) (nth 8 col
)))
440 (push attr names -
1))
446 ;; @syntax (ODBC:close-db)
447 ;; @return 'true' on success, 'nil' on failure.
449 ;; Closes a database connection.
452 (if odbc-stmt
(SQLFreeHandle SQL_HANDLE_STMT odbc-stmt
))
455 (SQLDisconnect odbc-conn
)
456 (SQLFreeHandle SQL_HANDLE_DBC odbc-conn
)
457 (set 'odbc-conn nil
)))
462 ;=================================== test =================================================
464 ; Note: before performing this test a database with name 'test'
465 ; and data source name 'test' should be created. The data base
466 ; should contain a table described by the following SQL statement:
468 ; create table fruits (name CHAR(20), qty INT(3))
470 ; For this configure an Access database: 'test-db' with table 'fruits'
471 ; and a text field 'name' width 20 and field 'qty' as type integer.
472 ; Make the 'User Data Source' connection with the ODBC control applet
473 ; in control-panel/administrative-tools for the MS Access *.mdb driver
474 ; and pick as a data source name and database location the test-db.mdb i
477 ; On some systems the table can also be created with an SQL statement
478 ; (ODBC:query "create ....")
479 ; On MS-Acces this will not work and the table has to be created
482 ; A sample of test-db.mdb can be found at:
483 ; http://newlisp.org/downloads/Other/
493 ; Note, on MS-Access must create table fruits manually first
495 ; (ODBC:query "create table fruits (name CHAR(20), qty INT(3))")
496 ; for "aUser" and "secret" you may just put empty strings ""
497 ; i.e. (ODBC:connect "test" "" "")
498 ; when on Windows on the same machine
500 (if (not (ODBC:connect
"test-db" "" "")) (exit))
502 (println "connected ...")
504 (ODBC:query
"insert into fruits values ('apples', 11)")
505 (ODBC:query
"insert into fruits values ('oranges', 22)")
506 (ODBC:query
"insert into fruits values ('bananas', 33)")
508 (println "inserted 3 records")
510 (ODBC:query
"select * from fruits")
512 (println "performed a query")
514 (println (ODBC:num-cols
) " columns in result set")
515 (println "fetching rows ...")
516 (while (set 'row
(ODBC:fetch-row
))
517 (set 'row
(map trim row
))
522 (ODBC:query
"delete from fruits")
523 (println "rows deleted: " (ODBC:affected-rows
))
525 (println "\nclosing database")