Initial commit of newLISP.
[newlisp.git] / modules / odbc.lsp
blob0db4cf2078fa721f698777aac9ec30ae3babd465
1 ;; @module odbc.lsp
2 ;; @description ODBC database interface
3 ;; @version 1.7 - comments redone for automatic documentation
4 ;; @author Lutz Mueller, 2003
5 ;;
6 ;; <h2>OCBC Interface functions</h2>
7 ;;
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:
11 ;; <pre>
12 ;; (load "odbc.lsp")
13 ;; </pre>
14 ;;
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
19 ;; data source setup.
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>
26 ;; <pre>
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
36 ;; </pre>
38 (context 'ODBC)
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)
67 (define SQL_NTS -3)
69 (define SQL_CHAR 1)
70 (define SQL_C_CHAR SQL_CHAR)
73 ; Import functions
74 ; there are many more, which are not used here, goto microsoft.com and unixodbc.org for
75 ; more information on ODBC SQLxxx API
78 (set 'funcs '(
79 "SQLAllocHandle"
80 "SQLSetEnvAttr"
81 "SQLFreeHandle"
82 "SQLSetConnectAttr"
83 "SQLConnect"
84 "SQLDisconnect"
85 "SQLGetDiagRec"
86 "SQLExecDirect"
87 "SQLNumResultCols"
88 "SQLRowCount"
89 "SQLBindCol"
90 "SQLFetch"
91 "SQLDescribeCol"
92 "SQLTables"
93 "SQLColumns"))
95 (dolist (fun funcs)
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 ------------------------------------
112 ; check result code
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
121 (define (init)
122 (and
123 ; get environment handle
124 (set 'odbc-result (SQLAllocHandle SQL_HANDLE_ENV SQL_NULL_HANDLE ptr-odbc-env))
126 (if (is-error-result)
127 (begin
128 (println "Error allocating env handle")
129 nil) true)
131 (set 'odbc-env (get-int ptr-odbc-env))
133 ; register version
134 (set 'odbc-result (SQLSetEnvAttr odbc-env SQL_ATTR_ODBC_VERSION SQL_OV_ODBC3 0))
136 (if (is-error-result)
137 (begin
138 (println "Error setting ODBC environment")
139 (SQLFreeHandle SQL_HANDLE_ENV odbc-env)
140 nil) true))
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
152 (define (error type)
153 (set 'diag-status " ")
154 (set 'diag-err " ")
155 (set 'diag-mlen " ")
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))
182 true)
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.
198 ;; @example
199 ;; (ODBC:connect "mydatabase" "johndoe" "secret")
201 (define (ODBC:connect data-source user password)
203 (and
204 (init)
206 ; allocate connection handle
207 (set 'odbc-result (SQLAllocHandle SQL_HANDLE_DBC odbc-env ptr-odbc-conn))
209 (if (is-error-result)
210 (begin
211 (println "Error allocating conn handle")
212 (SQLFreeHandle SQL_HANDLE_ENV odbc-env)
213 nil) true)
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
222 user SQL_NTS
223 password SQL_NTS))
225 (if (is-error-result)
226 (begin
227 (println "Could not connect")
228 (SQLFreeHandle SQL_HANDLE_DBC odbc-conn)
229 (SQLFreeHandle SQL_HANDLE_ENV odbc-env)
230 nil) true))
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
240 ;; @example
241 ;; (query "select * from someTable")
242 ;; (query "delete from addresses")
243 ;; (query "insert into fruits values ('apples', 11)")
245 (define (ODBC:query sql-string)
246 (and
247 ; is stmt handle exists free it
248 (if odbc-stmt (begin
249 (SQLFreeHandle SQL_HANDLE_STMT odbc-stmt)
250 (set 'odbc-stmt nil)
251 true) true)
253 ; allocate statement handle
254 (set 'odbc-result (SQLAllocHandle SQL_HANDLE_STMT odbc-conn ptr-odbc-stmt))
256 (if (is-error-result)
257 (begin
258 (println "could not allocate statement handle")
259 nil)
260 (set 'odbc-stmt (get-int ptr-odbc-stmt)))
262 ; do the query
263 (set 'odbc-result (SQLExecDirect odbc-stmt sql-string SQL_NTS))
264 (if (is-error-result)
265 (begin
266 (println "query failed")
267 nil)
268 true)
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)
276 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.
298 ;; @example
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)
313 col-name-out 32
314 ptr-name-len
315 ptr-data-type
316 ptr-col-size
317 ptr-dec-dig
318 ptr-nullable)))
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
331 ;; number.
333 ;; @example
334 ;; (ODBC:fetch-row) => ("apples" "11")
336 (define (fetch-row , row)
337 (bind-columns)
338 (set 'odbc-result (& 0xFFFF (SQLFetch odbc-stmt)))
339 (if (is-error-result)
341 (begin
342 (for (x result-cols 1) (push (eval (nth x vars)) row))
343 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.
360 (define (tables)
361 (if (and
362 ; is stmt handle exists free it
363 (if odbc-stmt (begin
364 (SQLFreeHandle SQL_HANDLE_STMT odbc-stmt)
365 (set 'odbc-stmt nil)
366 true) true)
368 ; allocate statement handle
369 (set 'odbc-result (SQLAllocHandle SQL_HANDLE_STMT odbc-conn ptr-odbc-stmt))
370 (if (is-error-result)
371 (begin
372 (println "could not allocate statement handle")
373 nil)
374 (set 'odbc-stmt (get-int ptr-odbc-stmt)))
376 ; do the query
377 (set 'odbc-result (SQLTables odbc-stmt 0 SQL_NTS 0 SQL_NTS "%" SQL_NTS 0 SQL_NTS))
378 (if (is-error-result)
379 (begin
380 (println "query failed")
381 nil)
382 true)
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)
391 (begin
392 (set 'names nil)
393 (while (set 'row (ODBC:fetch-row))
394 (push (nth 2 row) names -1))
395 true)
396 ) names)
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)
404 (if (and
405 ; is stmt handle exists free it
406 (if odbc-stmt (begin
407 (SQLFreeHandle SQL_HANDLE_STMT odbc-stmt)
408 (set 'odbc-stmt nil)
409 true) true)
411 ; allocate statement handle
412 (set 'odbc-result (SQLAllocHandle SQL_HANDLE_STMT odbc-conn ptr-odbc-stmt))
414 (if (is-error-result)
415 (begin
416 (println "could not allocate statement handle")
417 nil)
418 (set 'odbc-stmt (get-int ptr-odbc-stmt)))
420 ; do the query
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)
424 (begin
425 (println "query failed")
426 nil)
427 true)
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)
436 (begin
437 (set 'names nil)
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))
441 true)
442 ) names)
446 ;; @syntax (ODBC:close-db)
447 ;; @return 'true' on success, 'nil' on failure.
449 ;; Closes a database connection.
451 (define (close-db)
452 (if odbc-stmt (SQLFreeHandle SQL_HANDLE_STMT odbc-stmt))
453 (set 'odbc-stmt nil)
454 (if odbc-conn (begin
455 (SQLDisconnect odbc-conn)
456 (SQLFreeHandle SQL_HANDLE_DBC odbc-conn)
457 (set 'odbc-conn nil)))
458 true)
461 (context 'MAIN)
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
475 ; created.
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
480 ; manually.
482 ; A sample of test-db.mdb can be found at:
483 ; http://newlisp.org/downloads/Other/
485 ; example:
486 ; (test-odbc)
491 (define (test-odbc)
493 ; Note, on MS-Access must create table fruits manually first
494 ; else you could do:
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))
518 (println row))
519 (println)
522 (ODBC:query "delete from fruits")
523 (println "rows deleted: " (ODBC:affected-rows))
525 (println "\nclosing database")
526 (ODBC:close-db)
531 ; eof ;