r10561: 07 Jun 2005 Kevin Rosenberg <kevin@rosenberg.net>
[clsql/s11.git] / db-mysql / mysql-sql.lisp
blobce81abe7e9ae0049643ac6f2e9b6c69d4157e01c
1 ;;;; -*- Mode: LISP; Syntax: ANSI-Common-Lisp; Base: 10 -*-
2 ;;;; *************************************************************************
3 ;;;; FILE IDENTIFICATION
4 ;;;;
5 ;;;; Name: mysql-sql.lisp
6 ;;;; Purpose: High-level MySQL interface using UFFI
7 ;;;; Date Started: Feb 2002
8 ;;;;
9 ;;;; $Id$
10 ;;;;
11 ;;;; CLSQL users are granted the rights to distribute and use this software
12 ;;;; as governed by the terms of the Lisp Lesser GNU Public License
13 ;;;; (http://opensource.franz.com/preamble.html), also known as the LLGPL.
14 ;;;; *************************************************************************
16 (defpackage #:clsql-mysql
17 (:use #:common-lisp #:clsql-sys #:mysql #:clsql-uffi)
18 (:export #:mysql-database)
19 (:documentation "This is the CLSQL interface to MySQL."))
21 (in-package #:clsql-mysql)
23 ;; if we have :sb-unicode, UFFI will treat :cstring as a UTF-8 string
24 (defun expression-length (query-expression)
25 (length #+sb-unicode (sb-ext:string-to-octets query-expression
26 :external-format :utf8)
27 #-sb-unicode query-expression))
29 ;;; Field conversion functions
31 (defun result-field-names (num-fields res-ptr)
32 (declare (fixnum num-fields))
33 (let ((names '())
34 (field-vec (mysql-fetch-fields res-ptr)))
35 (dotimes (i num-fields)
36 (declare (fixnum i))
37 (let* ((field (uffi:deref-array field-vec '(:array mysql-field) i))
38 (name (uffi:convert-from-foreign-string
39 (uffi:get-slot-value field 'mysql-field 'mysql::name))))
40 (push name names)))
41 (nreverse names)))
43 (defun make-type-list-for-auto (num-fields res-ptr)
44 (declare (fixnum num-fields))
45 (let ((new-types '())
46 (field-vec (mysql-fetch-fields res-ptr)))
47 (dotimes (i num-fields)
48 (declare (fixnum i))
49 (let* ((field (uffi:deref-array field-vec '(:array mysql-field) i))
50 (flags (uffi:get-slot-value field 'mysql-field 'mysql::flags))
51 (unsigned (plusp (logand flags 32)))
52 (type (uffi:get-slot-value field 'mysql-field 'type)))
53 (push
54 (case type
55 ((#.mysql-field-types#tiny
56 #.mysql-field-types#short
57 #.mysql-field-types#int24)
58 (if unsigned
59 :uint32
60 :int32))
61 (#.mysql-field-types#long
62 (if unsigned
63 :uint
64 :int))
65 (#.mysql-field-types#longlong
66 (if unsigned
67 :uint64
68 :int64))
69 ((#.mysql-field-types#double
70 #.mysql-field-types#float
71 #.mysql-field-types#decimal)
72 :double)
73 (otherwise
74 t))
75 new-types)))
76 (nreverse new-types)))
78 (defun canonicalize-types (types num-fields res-ptr)
79 (when types
80 (let ((auto-list (make-type-list-for-auto num-fields res-ptr)))
81 (cond
82 ((listp types)
83 (canonicalize-type-list types auto-list))
84 ((eq types :auto)
85 auto-list)
87 nil)))))
89 (defmethod database-initialize-database-type ((database-type (eql :mysql)))
92 (uffi:def-type mysql-mysql-ptr-def (* mysql-mysql))
93 (uffi:def-type mysql-row-def mysql-row)
94 (uffi:def-type mysql-mysql-res-ptr-def (* mysql-mysql-res))
96 (defclass mysql-database (database)
97 ((mysql-ptr :accessor database-mysql-ptr :initarg :mysql-ptr
98 :type mysql-mysql-ptr-def)))
100 (defmethod database-type ((database mysql-database))
101 :mysql)
103 (defmethod database-name-from-spec (connection-spec (database-type (eql :mysql)))
104 (check-connection-spec connection-spec database-type
105 (host db user password &optional port))
106 (destructuring-bind (host db user password &optional port) connection-spec
107 (declare (ignore password))
108 (concatenate 'string
109 (etypecase host
110 (null "localhost")
111 (pathname (namestring host))
112 (string host))
113 (if port
114 (concatenate 'string
116 (etypecase port
117 (integer (write-to-string port))
118 (string port)))
120 "/" db "/" user)))
122 (defmethod database-connect (connection-spec (database-type (eql :mysql)))
123 (check-connection-spec connection-spec database-type
124 (host db user password &optional port))
125 (destructuring-bind (host db user password &optional port) connection-spec
126 (let ((mysql-ptr (mysql-init (uffi:make-null-pointer 'mysql-mysql)))
127 (socket nil))
128 (if (uffi:null-pointer-p mysql-ptr)
129 (error 'sql-connection-error
130 :database-type database-type
131 :connection-spec connection-spec
132 :error-id (mysql-errno mysql-ptr)
133 :message (mysql-error-string mysql-ptr))
134 (uffi:with-cstrings ((host-native host)
135 (user-native user)
136 (password-native password)
137 (db-native db)
138 (socket-native socket))
139 (let ((error-occurred nil))
140 (unwind-protect
141 (if (uffi:null-pointer-p
142 (mysql-real-connect
143 mysql-ptr host-native user-native password-native
144 db-native
145 (etypecase port
146 (null 0)
147 (integer port)
148 (string (parse-integer port)))
149 socket-native 0))
150 (progn
151 (setq error-occurred t)
152 (error 'sql-connection-error
153 :database-type database-type
154 :connection-spec connection-spec
155 :error-id (mysql-errno mysql-ptr)
156 :message (mysql-error-string mysql-ptr)))
157 (make-instance 'mysql-database
158 :name (database-name-from-spec connection-spec
159 database-type)
160 :database-type :mysql
161 :connection-spec connection-spec
162 :mysql-ptr mysql-ptr))
163 (when error-occurred (mysql-close mysql-ptr)))))))))
166 (defmethod database-disconnect ((database mysql-database))
167 (mysql-close (database-mysql-ptr database))
168 (setf (database-mysql-ptr database) nil)
172 (defmethod database-query (query-expression (database mysql-database)
173 result-types field-names)
174 (declare (optimize (speed 3) (safety 0) (debug 0) (space 0)))
175 (let ((mysql-ptr (database-mysql-ptr database)))
176 (uffi:with-cstring (query-native query-expression)
177 (if (zerop (mysql-real-query mysql-ptr query-native
178 (expression-length query-expression)))
179 (let ((res-ptr (mysql-use-result mysql-ptr)))
180 (if res-ptr
181 (unwind-protect
182 (let ((num-fields (mysql-num-fields res-ptr)))
183 (declare (fixnum num-fields))
184 (setq result-types (canonicalize-types
185 result-types num-fields
186 res-ptr))
187 (values
188 (loop for row = (mysql-fetch-row res-ptr)
189 for lengths = (mysql-fetch-lengths res-ptr)
190 until (uffi:null-pointer-p row)
191 collect
192 (do* ((rlist (make-list num-fields))
193 (i 0 (1+ i))
194 (pos rlist (cdr pos)))
195 ((= i num-fields) rlist)
196 (declare (fixnum i))
197 (setf (car pos)
198 (convert-raw-field
199 (uffi:deref-array row '(:array
200 (* :unsigned-char))
202 result-types i
203 (uffi:deref-array lengths '(:array :unsigned-long)
204 i)))))
205 (when field-names
206 (result-field-names num-fields res-ptr))))
207 (mysql-free-result res-ptr))
208 (error 'sql-database-data-error
209 :database database
210 :expression query-expression
211 :error-id (mysql-errno mysql-ptr)
212 :message (mysql-error-string mysql-ptr))))
213 (error 'sql-database-data-error
214 :database database
215 :expression query-expression
216 :error-id (mysql-errno mysql-ptr)
217 :message (mysql-error-string mysql-ptr))))))
219 (defmethod database-execute-command (sql-expression (database mysql-database))
220 (uffi:with-cstring (sql-native sql-expression)
221 (let ((mysql-ptr (database-mysql-ptr database)))
222 (declare (type mysql-mysql-ptr-def mysql-ptr))
223 (if (zerop (mysql-real-query mysql-ptr sql-native
224 (expression-length sql-expression)))
226 (error 'sql-database-data-error
227 :database database
228 :expression sql-expression
229 :error-id (mysql-errno mysql-ptr)
230 :message (mysql-error-string mysql-ptr))))))
233 (defstruct mysql-result-set
234 (res-ptr (uffi:make-null-pointer 'mysql-mysql-res) :type mysql-mysql-res-ptr-def)
235 (types nil :type list)
236 (num-fields 0 :type fixnum)
237 (full-set nil :type boolean))
240 (defmethod database-query-result-set ((query-expression string)
241 (database mysql-database)
242 &key full-set result-types)
243 (uffi:with-cstring (query-native query-expression)
244 (let ((mysql-ptr (database-mysql-ptr database)))
245 (declare (type mysql-mysql-ptr-def mysql-ptr))
246 (if (zerop (mysql-real-query mysql-ptr query-native
247 (expression-length query-expression)))
248 (let ((res-ptr (if full-set
249 (mysql-store-result mysql-ptr)
250 (mysql-use-result mysql-ptr))))
251 (declare (type mysql-mysql-res-ptr-def res-ptr))
252 (if (not (uffi:null-pointer-p res-ptr))
253 (let* ((num-fields (mysql-num-fields res-ptr))
254 (result-set (make-mysql-result-set
255 :res-ptr res-ptr
256 :num-fields num-fields
257 :full-set full-set
258 :types
259 (canonicalize-types
260 result-types num-fields
261 res-ptr))))
262 (if full-set
263 (values result-set
264 num-fields
265 (mysql-num-rows res-ptr))
266 (values result-set
267 num-fields)))
268 (error 'sql-database-data-error
269 :database database
270 :expression query-expression
271 :error-id (mysql-errno mysql-ptr)
272 :message (mysql-error-string mysql-ptr))))
273 (error 'sql-database-data-error
274 :database database
275 :expression query-expression
276 :error-id (mysql-errno mysql-ptr)
277 :message (mysql-error-string mysql-ptr))))))
279 (defmethod database-dump-result-set (result-set (database mysql-database))
280 (mysql-free-result (mysql-result-set-res-ptr result-set))
284 (defmethod database-store-next-row (result-set (database mysql-database) list)
285 (let* ((res-ptr (mysql-result-set-res-ptr result-set))
286 (row (mysql-fetch-row res-ptr))
287 (lengths (mysql-fetch-lengths res-ptr))
288 (types (mysql-result-set-types result-set)))
289 (declare (type mysql-mysql-res-ptr-def res-ptr)
290 (type mysql-row-def row))
291 (unless (uffi:null-pointer-p row)
292 (loop for i from 0 below (mysql-result-set-num-fields result-set)
293 for rest on list
295 (setf (car rest)
296 (convert-raw-field
297 (uffi:deref-array row '(:array (* :unsigned-char)) i)
298 types
300 (uffi:deref-array lengths '(:array :unsigned-long) i))))
301 list)))
304 ;; Table and attribute introspection
306 (defmethod database-list-tables ((database mysql-database) &key (owner nil))
307 (declare (ignore owner))
308 (remove-if #'(lambda (s)
309 (and (>= (length s) 11)
310 (string-equal (subseq s 0 11) "_CLSQL_SEQ_")))
311 (mapcar #'car (database-query "SHOW TABLES" database nil nil))))
313 ;; MySQL 4.1 does not support views
314 (defmethod database-list-views ((database mysql-database)
315 &key (owner nil))
316 (declare (ignore owner))
317 nil)
319 (defmethod database-list-indexes ((database mysql-database)
320 &key (owner nil))
321 (let ((result '()))
322 (dolist (table (database-list-tables database :owner owner) result)
323 (setq result
324 (append (database-list-table-indexes table database :owner owner)
325 result)))))
327 (defmethod database-list-table-indexes (table (database mysql-database)
328 &key (owner nil))
329 (declare (ignore owner))
330 (do ((results nil)
331 (rows (database-query
332 (format nil "SHOW INDEX FROM ~A" (string-upcase table))
333 database nil nil)
334 (cdr rows)))
335 ((null rows) (nreverse results))
336 (let ((col (nth 2 (car rows))))
337 (unless (find col results :test #'string-equal)
338 (push col results)))))
340 (defmethod database-list-attributes ((table string) (database mysql-database)
341 &key (owner nil))
342 (declare (ignore owner))
343 (mapcar #'car
344 (database-query
345 (format nil "SHOW COLUMNS FROM ~A" table)
346 database nil nil)))
348 (defmethod database-attribute-type (attribute (table string)
349 (database mysql-database)
350 &key (owner nil))
351 (declare (ignore owner))
352 (let ((row (car (database-query
353 (format nil
354 "SHOW COLUMNS FROM ~A LIKE '~A'" table attribute)
355 database nil nil))))
356 (let* ((raw-type (second row))
357 (null (third row))
358 (start-length (position #\( raw-type))
359 (type (if start-length
360 (subseq raw-type 0 start-length)
361 raw-type))
362 (length (when start-length
363 (parse-integer (subseq raw-type (1+ start-length))
364 :junk-allowed t))))
365 (when type
366 (values (ensure-keyword type) length nil (if (string-equal null "YES") 1 0))))))
368 ;;; Sequence functions
370 (defun %sequence-name-to-table (sequence-name)
371 (concatenate 'string "_CLSQL_SEQ_" (sql-escape sequence-name)))
373 (defun %table-name-to-sequence-name (table-name)
374 (and (>= (length table-name) 11)
375 (string-equal (subseq table-name 0 11) "_CLSQL_SEQ_")
376 (subseq table-name 11)))
378 (defmethod database-create-sequence (sequence-name
379 (database mysql-database))
380 (let ((table-name (%sequence-name-to-table sequence-name)))
381 (database-execute-command
382 (concatenate 'string "CREATE TABLE " table-name
383 " (id int NOT NULL PRIMARY KEY AUTO_INCREMENT)")
384 database)
385 (database-execute-command
386 (concatenate 'string "INSERT INTO " table-name
387 " VALUES (-1)")
388 database)))
390 (defmethod database-drop-sequence (sequence-name
391 (database mysql-database))
392 (database-execute-command
393 (concatenate 'string "DROP TABLE " (%sequence-name-to-table sequence-name))
394 database))
396 (defmethod database-list-sequences ((database mysql-database)
397 &key (owner nil))
398 (declare (ignore owner))
399 (mapcan #'(lambda (s)
400 (let ((sn (%table-name-to-sequence-name (car s))))
401 (and sn (list sn))))
402 (database-query "SHOW TABLES" database nil nil)))
404 (defmethod database-set-sequence-position (sequence-name
405 (position integer)
406 (database mysql-database))
407 (database-execute-command
408 (format nil "UPDATE ~A SET id=~A" (%sequence-name-to-table sequence-name)
409 position)
410 database)
411 (mysql:mysql-insert-id (clsql-mysql::database-mysql-ptr database)))
413 (defmethod database-sequence-next (sequence-name (database mysql-database))
414 (without-interrupts
415 (database-execute-command
416 (concatenate 'string "UPDATE " (%sequence-name-to-table sequence-name)
417 " SET id=LAST_INSERT_ID(id+1)")
418 database)
419 (mysql:mysql-insert-id (clsql-mysql::database-mysql-ptr database))))
421 (defmethod database-sequence-last (sequence-name (database mysql-database))
422 (without-interrupts
423 (caar (database-query
424 (concatenate 'string "SELECT id from "
425 (%sequence-name-to-table sequence-name))
426 database :auto nil))))
428 (defmethod database-create (connection-spec (type (eql :mysql)))
429 (destructuring-bind (host name user password &optional port) connection-spec
430 (multiple-value-bind (output status)
431 (clsql-sys:command-output "mysqladmin create -u~A -p~A -h~A~@[ -P~A~] ~A"
432 user password
433 (if host host "localhost")
434 port name
435 name)
436 (if (or (not (eql 0 status))
437 (and (search "failed" output) (search "error" output)))
438 (error 'sql-database-error
439 :message
440 (format nil "mysql database creation failed with connection-spec ~A."
441 connection-spec))
442 t))))
444 (defmethod database-destroy (connection-spec (type (eql :mysql)))
445 (destructuring-bind (host name user password &optional port) connection-spec
446 (multiple-value-bind (output status)
447 (clsql-sys:command-output "mysqladmin drop -f -u~A -p~A -h~A~@[ -P~A~] ~A"
448 user password
449 (if host host "localhost")
450 port name)
451 (if (or (not (eql 0 status))
452 (and (search "failed" output) (search "error" output)))
453 (error 'sql-database-error
454 :message
455 (format nil "mysql database deletion failed with connection-spec ~A."
456 connection-spec))
457 t))))
459 (defmethod database-probe (connection-spec (type (eql :mysql)))
460 (when (find (second connection-spec) (database-list connection-spec type)
461 :key #'car :test #'string-equal)
464 (defmethod database-list (connection-spec (type (eql :mysql)))
465 (destructuring-bind (host name user password &optional port) connection-spec
466 (declare (ignore name))
467 (let ((database (database-connect (list host "mysql" user password port) type)))
468 (unwind-protect
469 (progn
470 (setf (slot-value database 'clsql-sys::state) :open)
471 (mapcar #'car (database-query "show databases" database :auto nil)))
472 (progn
473 (database-disconnect database)
474 (setf (slot-value database 'clsql-sys::state) :closed))))))
477 ;;; Prepared statements
479 (defclass mysql-stmt ()
480 ((database :initarg :database :reader database)
481 (stmt :initarg :stmt :accessor stmt)
482 (input-bind :initarg :input-bind :reader input-bind)
483 (output-bind :initarg :output-bind :reader output-bind)
484 (types :initarg :types :reader types)
485 (result-set :initarg :result-set :reader result-set)
486 (num-fields :initarg :num-fields :reader num-fields)
487 (field-names :initarg :field-names :accessor stmt-field-names)
488 (length-ptr :initarg :length-ptr :reader length-ptr)
489 (is-null-ptr :initarg :is-null-ptr :reader is-null-ptr)
490 (result-types :initarg :result-types :reader result-types)))
492 (defun clsql-type->mysql-type (type)
493 (cond
494 ((in type :null) mysql-field-types#null)
495 ((in type :int :integer) mysql-field-types#long)
496 ((in type :short) mysql-field-types#short)
497 ((in type :bigint) mysql-field-types#longlong)
498 ((in type :float :double :number) mysql-field-types#double)
499 ((and (consp type) (in (car type) :char :string :varchar)) mysql-field-types#var-string)
500 ((or (eq type :blob) (and (consp type) (in (car type) :blob))) mysql-field-types#var-string)
502 (error 'sql-user-error
503 :message
504 (format nil "Unknown clsql type ~A." type)))))
506 #+mysql-client-v4.1
507 (defmethod database-prepare (sql-stmt types (database mysql-database) result-types field-names)
508 (let* ((mysql-ptr (database-mysql-ptr database))
509 (stmt (mysql-stmt-init mysql-ptr)))
510 (when (uffi:null-pointer-p stmt)
511 (error 'sql-database-error
512 :error-id (mysql-errno mysql-ptr)
513 :message (mysql-error-string mysql-ptr)))
515 (uffi:with-cstring (native-query sql-stmt)
516 (unless (zerop (mysql-stmt-prepare stmt native-query (expression-length sql-stmt)))
517 (mysql-stmt-close stmt)
518 (error 'sql-database-error
519 :error-id (mysql-errno mysql-ptr)
520 :message (mysql-error-string mysql-ptr))))
522 (unless (= (mysql-stmt-param-count stmt) (length types))
523 (mysql-stmt-close stmt)
524 (error 'sql-database-error
525 :message
526 (format nil "Mysql param count (~D) does not match number of types (~D)"
527 (mysql-stmt-param-count stmt) (length types))))
529 (let ((rs (mysql-stmt-result-metadata stmt)))
530 (when (uffi:null-pointer-p rs)
531 (warn "mysql_stmt_result_metadata returned NULL")
532 #+nil
533 (mysql-stmt-close stmt)
534 #+nil
535 (error 'sql-database-error
536 :message "mysql_stmt_result_metadata returned NULL"))
538 (let ((input-bind (uffi:allocate-foreign-object 'mysql-bind (length types)))
539 (mysql-types (mapcar 'clsql-type->mysql-type types))
540 field-vec num-fields is-null-ptr output-bind length-ptr)
542 (print 'a)
543 (dotimes (i (length types))
544 (let* ((binding (uffi:deref-array input-bind '(:array mysql-bind) i)))
545 (setf (uffi:get-slot-value binding 'mysql-bind 'mysql::buffer-type)
546 (nth i mysql-types))
547 (setf (uffi:get-slot-value binding 'mysql-bind 'mysql::buffer-length) 0)))
549 (print 'b)
550 (unless (uffi:null-pointer-p rs)
551 (setq field-vec (mysql-fetch-fields rs)
552 num-fields (mysql-num-fields rs)
553 is-null-ptr (uffi:allocate-foreign-object :byte num-fields)
554 output-bind (uffi:allocate-foreign-object 'mysql-bind num-fields)
555 length-ptr (uffi:allocate-foreign-object :unsigned-long num-fields))
556 (dotimes (i num-fields)
557 (declare (fixnum i))
558 (let* ((field (uffi:deref-array field-vec '(:array mysql-field) i))
559 (type (uffi:get-slot-value field 'mysql-field 'type))
560 (binding (uffi:deref-array output-bind '(:array mysql-bind) i)))
561 (setf (uffi:get-slot-value binding 'mysql-bind 'mysql::buffer-type) type)
563 (setf (uffi:get-slot-value binding 'mysql-bind 'mysql::buffer-length) 0)
564 #+need-to-allocate-foreign-object-for-this
565 (setf (uffi:get-slot-value binding 'mysql-bind 'mysql::is-null)
566 (+ i (uffi:pointer-address is-null-ptr)))
567 #+need-to-allocate-foreign-object-for-this
568 (setf (uffi:get-slot-value binding 'mysql-bind 'length)
569 (+ (* i 8) (uffi:pointer-address length-ptr)))
571 (case type
572 ((#.mysql-field-types#var-string #.mysql-field-types#string
573 #.mysql-field-types#tiny-blob #.mysql-field-types#blob
574 #.mysql-field-types#medium-blob #.mysql-field-types#long-blob)
575 (setf (uffi:get-slot-value binding 'mysql-bind 'mysql::buffer-length) 1024)
576 (setf (uffi:get-slot-value binding 'mysql-bind 'mysql::buffer)
577 (uffi:allocate-foreign-object :unsigned-char 1024)))
578 (#.mysql-field-types#tiny
579 (setf (uffi:get-slot-value binding 'mysql-bind 'mysql::buffer)
580 (uffi:allocate-foreign-object :byte)))
581 (#.mysql-field-types#short
582 (setf (uffi:get-slot-value binding 'mysql-bind 'mysql::buffer)
583 (uffi:allocate-foreign-object :short)))
584 (#.mysql-field-types#long
585 (setf (uffi:get-slot-value binding 'mysql-bind 'mysql::buffer)
586 ;; segfaults if supply :int on amd64
587 (uffi:allocate-foreign-object :long)))
588 #+64bit
589 (#.mysql-field-types#longlong
590 (setf (uffi:get-slot-value binding 'mysql-bind 'mysql::buffer)
591 (uffi:allocate-foreign-object :long)))
592 (#.mysql-field-types#float
593 (setf (uffi:get-slot-value binding 'mysql-bind 'mysql::buffer)
594 (uffi:allocate-foreign-object :float)))
595 (#.mysql-field-types#double
596 (setf (uffi:get-slot-value binding 'mysql-bind 'mysql::buffer)
597 (uffi:allocate-foreign-object :double)))
598 ((#.mysql-field-types#time #.mysql-field-types#date
599 #.mysql-field-types#datetime #.mysql-field-types#timestamp)
600 (uffi:allocate-foreign-object 'mysql-time))
602 (error "mysql type ~D not supported." type)))))
604 (unless (zerop (mysql-stmt-bind-result stmt output-bind))
605 (mysql-stmt-close stmt)
606 (error 'sql-database-error
607 :error-id (mysql-stmt-errno stmt)
608 :message (uffi:convert-from-cstring
609 (mysql-stmt-error stmt)))))
611 (make-instance 'mysql-stmt
612 :database database
613 :stmt stmt
614 :num-fields num-fields
615 :input-bind input-bind
616 :output-bind output-bind
617 :result-set rs
618 :result-types result-types
619 :length-ptr length-ptr
620 :is-null-ptr is-null-ptr
621 :types mysql-types
622 :field-names field-names)))))
624 #+mysql-client-v4.1
625 (defmethod database-bind-parameter ((stmt mysql-stmt) position value)
626 ;; FIXME: will need to allocate bind structure. This should probably be
627 ;; done in C since the API is not mature and may change
628 (let ((binding (uffi:deref-array (input-bind stmt) '(:array mysql-bind) (1- position)))
629 (type (nth (1- position) (types stmt))))
630 (setf (uffi:get-slot-value binding 'mysql-bind 'length) 0)
631 (cond
632 ((null value)
633 (when (is-null-ptr stmt)
634 (setf (uffi:deref-array (is-null-ptr stmt) '(:array :byte) (1- position)) 1)))
636 (when (is-null-ptr stmt)
637 (setf (uffi:deref-array (is-null-ptr stmt) '(:array :byte) (1- position)) 0))
638 (case type
639 (#.mysql-field-types#long
640 (setf (uffi:get-slot-value binding 'mysql-bind 'mysql::buffer) value))
642 (warn "Unknown input bind type ~D." type))
643 )))))
645 #+mysql-client-v4.1
646 (defmethod database-run-prepared ((stmt mysql-stmt))
647 (print 'a1)
648 (when (input-bind stmt)
649 (unless (zerop (mysql-stmt-bind-param (stmt stmt) (input-bind stmt)))
650 (error 'sql-database-error
651 :error-id (mysql-stmt-errno (stmt stmt))
652 :message (uffi:convert-from-cstring
653 (mysql-stmt-error (stmt stmt))))))
654 (print 'a2)
655 (unless (zerop (mysql-stmt-execute (stmt stmt)))
656 (error 'sql-database-error
657 :error-id (mysql-stmt-errno (stmt stmt))
658 :message (uffi:convert-from-cstring
659 (mysql-stmt-error (stmt stmt)))))
660 (print 'a3)
661 (unless (zerop (mysql-stmt-store-result (stmt stmt)))
662 (error 'sql-database-error
663 :error-id (mysql-stmt-errno (stmt stmt))
664 :message (uffi:convert-from-cstring
665 (mysql-stmt-error (stmt stmt)))))
666 (database-fetch-prepared-rows stmt))
668 #+mysql-client-v4.1
669 (defun database-fetch-prepared-rows (stmt)
670 (do ((rc (mysql-stmt-fetch (stmt stmt)) (mysql-stmt-fetch (stmt stmt)))
671 (num-fields (num-fields stmt))
672 (rows '()))
673 ((not (zerop rc)) (nreverse rows))
674 (push
675 (loop for i from 0 below num-fields
676 collect
677 (let ((is-null
678 (not (zerop (uffi:ensure-char-integer
679 (uffi:deref-array (is-null-ptr stmt) '(:array :byte) i))))))
680 (unless is-null
681 (let* ((bind (uffi:deref-array (output-bind stmt) '(:array mysql-bind) i))
682 (type (uffi:get-slot-value bind 'mysql-bind 'mysql::buffer-type))
683 (buffer (uffi:get-slot-value bind 'mysql-bind 'mysql::buffer)))
684 (case type
685 ((#.mysql-field-types#var-string #.mysql-field-types#string
686 #.mysql-field-types#tiny-blob #.mysql-field-types#blob
687 #.mysql-field-types#medium-blob #.mysql-field-types#long-blob)
688 (uffi:convert-from-foreign-string buffer))
689 (#.mysql-field-types#tiny
690 (uffi:ensure-char-integer
691 (uffi:deref-pointer buffer :byte)))
692 (#.mysql-field-types#short
693 (uffi:deref-pointer buffer :short))
694 (#.mysql-field-types#long
695 (uffi:deref-pointer buffer :int))
696 #+64bit
697 (#.mysql-field-types#longlong
698 (uffi:deref-pointer buffer :long))
699 (#.mysql-field-types#float
700 (uffi:deref-pointer buffer :float))
701 (#.mysql-field-types#double
702 (uffi:deref-pointer buffer :double))
703 ((#.mysql-field-types#time #.mysql-field-types#date
704 #.mysql-field-types#datetime #.mysql-field-types#timestamp)
705 (let ((year (uffi:get-slot-value buffer 'mysql-time 'mysql::year))
706 (month (uffi:get-slot-value buffer 'mysql-time 'mysql::month))
707 (day (uffi:get-slot-value buffer 'mysql-time 'mysql::day))
708 (hour (uffi:get-slot-value buffer 'mysql-time 'mysql::hour))
709 (minute (uffi:get-slot-value buffer 'mysql-time 'mysql::minute))
710 (second (uffi:get-slot-value buffer 'mysql-time 'mysql::second)))
711 (db-timestring
712 (make-time :year year :month month :day day :hour hour
713 :minute minute :second second))))
715 (list type)))))))
716 rows)))
721 #+mysql-client-v4.1
722 (defmethod database-free-prepared ((stmt mysql-stmt))
723 (with-slots (stmt) stmt
724 (mysql-stmt-close stmt))
728 ;;; Database capabilities
730 (defmethod db-type-use-column-on-drop-index? ((db-type (eql :mysql)))
733 (defmethod db-type-has-views? ((db-type (eql :mysql)))
734 #+mysql-client-v5.1 t
735 #-mysql-client-v5.1 nil)
737 (defmethod db-type-has-subqueries? ((db-type (eql :mysql)))
738 #+mysql-client-v4.1 t
739 #-mysql-client-v4.1 nil)
741 (defmethod db-type-has-boolean-where? ((db-type (eql :mysql)))
742 #+mysql-client-v4.1 t
743 #-mysql-client-v4.1 nil)
745 (defmethod db-type-has-union? ((db-type (eql :mysql)))
746 (not (eql (schar mysql::*mysql-client-info* 0) #\3)))
748 (defmethod db-type-transaction-capable? ((db-type (eql :mysql)) database)
749 (let ((tuple (car (database-query "SHOW VARIABLES LIKE 'HAVE_INNODB'" database :auto nil))))
750 (and tuple (string-equal "YES" (second tuple)))))
752 (defmethod db-type-has-prepared-stmt? ((db-type (eql :mysql)))
753 #+mysql-client-v4.1 t
754 #-mysql-client-v4.1 nil)
756 (when (clsql-sys:database-type-library-loaded :mysql)
757 (clsql-sys:initialize-database-type :database-type :mysql))