2 ;; @description MySQL v.5 interface
3 ;; @version 2.6 - addition for mysql_escape_string (Jeff)
4 ;; @author Lutz Mueller 2003-2006, Gordon Fischer 2005, Jeff Ober 2007
6 ;; This MySQL 5 interface module has been tested on version 5.0.19
7 ;; of mysql from @link http://www.mysql.com www.mysql.com
9 ;; This implementation supports a maximum of 2,147,483,647
10 ;; rows in a database table. Now automatically adjusts row indexes to
11 ;; endian type of host CPU, but higher 32 bits are treated as 0 for now.
13 ;; <h3>Requirements</h3>
14 ;; At the beginning of the program file include a 'load' statment for the module:
16 ;; (load "/usr/share/newlisp/mysql5.lsp")
19 ;; A version of 'libmysqlclient' for a specific platform is required:
21 ;; on LINUX/UNIX: '/usr/local/mysql/libmysqlclient.15.so' <br>
22 ;; on Mac OS X: '/usr/local/mysql/libmysqlclient.15.dylib'
24 ;; To compile MySQL with client libraries use:
26 ;; './configure --prefix=/usr/local --enable-shared'
28 ;; This library might be in a different location on a particular
29 ;; installation of MySQL or have a different name.
30 ;; Change accordingly in the code at the beginning.
32 ;; The MySQL server itself may reside on a different machine
33 ;; on the network. The library 'libmysqlclient' will communicate
34 ;; with that server. The correct connection is created using
35 ;; the 'MySQL:connect' call.
37 ;; At the bottom of the module file 'mysql5,lsp' a test routine 'test-mysql'
38 ;; is included to test for correct installation of MySQL.
40 ;; In the 'MySQL:connect' call of that test routine the correct parameters
41 ;; for the MySQL server location and user and password have to be inserted.
43 ;; <h3>Adapting mysql.lsp to other versions of MySQL</h3>
44 ;; Some of the functions like 'mysql_num_rows()' cannot be imported
45 ;; because they are really macros extracting data from structures
46 ;; like 'MYSQL' or 'MYSQL_RES'. See the file 'mysql.h' in your MySQL distribution.
48 ;; The file 'sql.c' in the newLISP distribution contains a program
49 ;; calculating the offsets of the most important fields in these
50 ;; structures. These offsets are used here to retrieve values for
51 ;; the number of rows in a result set, etc. Using these offsets
52 ;; and the information found in 'mysql.h' and 'mysql_com.h', other
53 ;; functions can be imported and wrappers built around them.
54 ;; In this case one needs to install the developer's version of
55 ;; MySQL to get the header files mentioned.
57 ;; <h3>Functions available</h3>
59 ;; MySQL:init ................ get a database handle MYSQL
60 ;; MySQL:connect ............. connect to a database
61 ;; MySQL:query ............... execute a SQL statement
62 ;; MySQL:num-rows ............ rows in result of query
63 ;; MySQL:num-fields .......... columns in result of query
64 ;; MySQL:fetch-row ........... get row from the query result
65 ;; MySQL:fetch-all ........... get all rows from the last query
66 ;; MySQL:database ............ return all database names
67 ;; MySQL:tables .............. return all tables names
68 ;; MySQL:fields .............. return all fields in a table
69 ;; MySQL:data-seek ........... position in result for fetching
70 ;; MySQL:affected-rows ....... number of affected rows from operation
71 ;; MySQL:inserted-id ......... last value of auto increment id operation
72 ;; MySQL:escape .............. escapes SQL input string using mysql_real_escape_string
73 ;; MySQL:error ............... get error message
74 ;; MySQL:close-db ............ close database connection
77 ;; <h3>A typical MySQL session</h3>
78 ;; The following code piece outlines a typical MySQL session:
80 ;; (load "mysql5.lsp) ; load the module file
82 ;; (MySQL:init) ; initialize
83 ;; (MySQL:connect "192.168.1.10" "auser" "secret" "mydb") ; logon
84 ;; (MySQL:query "select ...;") ; SQL query
85 ;; (MySQL:query "insert ...;") ; SQL query
89 ;; The database server is listening on IP 192.168.1.10. The program
90 ;; connects with username '"auser"' password '"secret"' to a database with
91 ;; the name '"mydb"'. After connecting SQL statements are performed and
92 ;; finally the program disconnects from the server.
97 "/usr/lib/libmysqlclient.so" ; Linux, UNIX
98 "/usr/local/mysql/lib/libmysqlclient.dylib" ; MacOS X
101 (set 'libmysqlclient
(files (or
102 (find true
(map file? files
))
103 (begin (println "cannot find libmysqlclient library") (exit)))))
105 (import libmysqlclient
"mysql_init")
106 (import libmysqlclient
"mysql_real_connect")
107 (import libmysqlclient
"mysql_get_host_info")
108 (import libmysqlclient
"mysql_real_escape_string")
109 (import libmysqlclient
"mysql_query")
110 (import libmysqlclient
"mysql_real_query")
111 (import libmysqlclient
"mysql_store_result")
112 (import libmysqlclient
"mysql_free_result")
113 (import libmysqlclient
"mysql_data_seek")
114 (import libmysqlclient
"mysql_fetch_row")
115 (import libmysqlclient
"mysql_close")
116 (import libmysqlclient
"mysql_fetch_field_direct")
117 (import libmysqlclient
"mysql_insert_id")
119 ; following constant offsets into 'C' data structures are different on each mayor MySQL
120 ; version compile and run util/sql.c from the distribution to obtain these numbers
122 ; check endianess of the host CPU
123 (set 'big-endian
(= (pack ">ld" 1) (pack "ld" 1)))
125 (constant 'NUM_ROWS_OFFSET
(if big-endian
4 0))
126 (constant 'NUM_FIELDS_OFFSET
60)
127 (constant 'ERROR_OFFSET
85)
128 (constant 'INSERT_ID_OFFSET
(if big-endian
708 704))
129 (constant 'AFFECTED_ROWS_OFFSET
(if big-endian
700 696))
131 ;; @syntax (MySQL:init)
132 ;; @return 'true' on success, 'nil' on failure.
135 (set 'MYSQL
(mysql_init 0))
136 (if (= MYSQL
0) (set 'MYSQL nil
))
139 ;; @syntax (MySQL:connect <str-server> <str-userID> <str-password> <str-db>)
140 ;; @param <str-server> The host name or IP address or <tt>0</tt> for localhost.
141 ;; @param <str-userID> The user ID for authentication.
142 ;; @param <str-password> The password for authentication.
143 ;; @param <str-db> The name of the database to connect to.
144 ;; @return 'true' for success or 'nil' for failure.
146 ;; Connects to a database on server and authenticates a user ID.
147 ;; '(MySQL:init)' must have been called previously.
149 (define (connect host user passw database
)
150 (not(= (mysql_real_connect MYSQL host user passw database
0 0 0) 0)))
152 ;; @syntax (MySQL:query <str-sql>)
153 ;; @param <str-sql> A valid SQL query string.
154 ;; @return For 'insert' queries rerturns the inserted ID else 'true'
155 ;; for success or 'nil' for failure.
157 ;; Sends a SQL query string to the database server for evaluation.
159 (define (MySQL:query sql
)
160 (if MYSQL_RES
(mysql_free_result MYSQL_RES
))
161 (set 'result
(= (mysql_query MYSQL sql
) 0))
162 (set 'MYSQL_RES
(mysql_store_result MYSQL
))
163 (if (= MYSQL_RES
0) (set 'MYSQL_RES nil
))
164 (if (and result
(find "insert into" sql
1)) (set 'result
(inserted-id)))
167 ;; @syntax (MySQL:num-rows)
168 ;; @return Number of rows from last query.
171 (if MYSQL_RES
(get-int (int (+ MYSQL_RES NUM_ROWS_OFFSET
)))))
173 ;; @syntax (MySQL:num-fields)
174 ;; @return Number of columns from last query.
177 (if MYSQL_RES
(get-int (int (+ MYSQL_RES NUM_FIELDS_OFFSET
)))))
180 ; format the result based on the field type.
183 (define (keep-type res_ptr field_addr column_num
, data
)
184 (set 'type_ptr
(mysql_fetch_field_direct res_ptr
(int column_num
)))
185 ; The field type is the 20th field of the MySQL_FIELD structure
186 ; since fields 1-19 are all 4 byte fields we get the enum value
188 (set 'data
(get-int (int (+ type_ptr
(* 19 4)))))
189 ; Consult 'enum_field_types' in mysql_com.h for values
190 (if (= data
1) ;; boolean
191 (get-string field_addr
)
192 (= data
3) ;; integer
193 (int (get-string field_addr
))
194 (= data
12) ;; datetime
195 (apply date-value
(map int
(parse (get-string field_addr
) "[-: ]" 0)))
197 (float (get-string field_addr
))
198 ; else (will handle TEXT type 252)
199 (get-string field_addr
)
203 ;; @syntax (MySQL:fetch-row)
204 ;; @return A list of field elements.
206 ;; Fetches a row from a previous SQL 'MySQL:query' 'select' statement.
207 ;; Subsequent calls fetch row by row from the result table until the
208 ;; end of the table is reached.
212 (set 'rdata
(mysql_fetch_row MYSQL_RES
))
217 (dotimes (field (num-fields))
218 (set 'field_addr
(get-int (int (+ rdata
(* field
4)))))
220 (push nil row -
1) ;; what to do when the field contains NULL
221 (push (keep-type MYSQL_RES field_addr field
) row -
1)))
224 ;; @syntax (MySQL:fetch-all)
225 ;; @return All rows/fields from the last query.
227 ;; The whole result set from the query is returned at once as a list of row lists.
231 (dotimes (x (num-rows)) (push (fetch-row) all
))
234 ;; @syntax (MySQL:databases)
235 ;; @return A list of databases.
237 ;; Performs a 'show databases;' query.
240 (query "show databases;")
243 ;; @syntax (MySQL:table)
244 ;; @return A list of tables in the database.
246 ;; Performs a 'show tables;' query.
249 (query "show tables;")
252 ;; @syntax (MySQL:fields <str-table>)
253 ;; @param <str-table> The name of the table.
254 ;; @return A list of field description lists.
256 ;; For each field name in the table a list of specifications
257 ;; for that field is returned. The list starts with the name
258 ;; for the field followed by the type size/precision and
259 ;; other optional field descriptions.
261 (define (fields table
)
262 (query (append "show fields from " table
";"))
265 ;; @syntax (MySQL:data-seek <num-offset>)
266 ;; @param <num-offset> The <tt>0</tt> based offset to position inside the data set.
267 ;; @return Always 'true'.
269 ;; Positions in the result set at a zero based offset
270 ;; for a subsequent 'MySQL:fetch-row' call. If the offset
271 ;; is out of the allowed range for the result set a subsequent
272 ;; fetch-row will return 'nil'.
274 (define (data-seek offset
)
277 (mysql_data_seek MYSQL_RES
0 (int offset
))
278 (mysql_data_seek MYSQL_RES
(int offset
) 0)))
282 ;; @syntax (MySQL:error)
283 ;; @return Text info about the last error which occured.
286 (if MYSQL
(get-string (+ MYSQL ERROR_OFFSET
))))
289 ;; @syntax (MySQL:affected-rows)
290 ;; @return Number of affected rows by the last 'MySQL:query' operation.
292 (define (affected-rows)
294 (get-int (int (+ MYSQL AFFECTED_ROWS_OFFSET
)))))
296 ;; @syntax (MySQL:inserted-id)
297 ;; @return Last insert ID from an auto increment field.
299 (define (inserted-id)
300 ; (if MYSQL (mysql_insert_id MYSQL))
301 (if MYSQL
(get-int (int (+ MYSQL INSERT_ID_OFFSET
)))))
303 ;; @syntax (MySQL:escape <str-sql>)
304 ;; @return escaped string
306 ;; This function will escape special characters in <str-sql>, so that it
307 ;; is safe to place it in a MySQL query.
308 (define (escape value
, safe-value
)
309 (set 'safe-value
(dup " " (+ 1 (length value
))))
310 (MySQL:mysql_real_escape_string MySQL
:MYSQL safe-value value
(length value
))
313 ;; @syntax (MySQL:close-db)
314 ;; @return Always 'true'.
316 ;; Closes database access. For new database acess, both 'MySQL:init' and
317 ;; 'MySQL:connect' functions have to be called.
320 (if MYSQL_RES
(mysql_free_result MYSQL_RES
))
321 (if MYSQL
(mysql_close MYSQL
))
328 ; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
330 ; test data base functions
335 (MySQL:connect
0 "" 0 "test")
337 (println "databases:")
338 (MySQL:query
"show databases;")
340 (dotimes (x (MySQL:num-rows
)) (println (MySQL:fetch-row
)))
343 (MySQL:query
"create table fruits (name TEXT(2000),
345 num INT(4) AUTO_INCREMENT UNIQUE);")
347 (MySQL:query
"insert into fruits values ('apples', 11, null);")
348 (println "inserted-id: " (MySQL:inserted-id
))
349 (MySQL:query
"insert into fruits values ('oranges', 22, null);")
350 (println "inserted-id: " (MySQL:inserted-id
))
351 (MySQL:query
"insert into fruits values ('bananas', 33, null);")
352 (println "inserted-id: " (MySQL:inserted-id
))
354 (println "inserted into fruits:")
355 (MySQL:query
"select * from fruits;")
356 (println "\n" (MySQL:affected-rows
) " affected rows in query select")
357 (dotimes (x (MySQL:num-rows
)) (println (MySQL:fetch-row
)))
359 (println "no rows = " (MySQL:num-rows
) " no fields = " (MySQL:num-fields
))
360 (println "fields = " (MySQL:fields
"fruits"))
364 (MySQL:query
"show tables;")
365 (dotimes (x (MySQL:num-rows
)) (println (MySQL:fetch-row
)))
368 (MySQL:query
"select * from fruits;")
371 (println "data-seek to offset 2:")
372 (println (MySQL:fetch-row
))
375 (println "fetch-all:")
376 (println (MySQL:query
"select * from fruits;"))
377 (println (MySQL:fetch-all
))
379 (MySQL:query
"drop table fruits;")