r11026: 14 Aug 2006 Kevin Rosenberg <kevin@rosenberg.net>
[clsql/s11.git] / db-mysql / mysql-sql.lisp
bloba0e21c8b536980c9963ffb741002930c23857f9a
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)
99 (server-info :accessor database-server-info :initarg :server-info
100 :type string)))
102 (defmethod database-type ((database mysql-database))
103 :mysql)
105 (defmethod database-name-from-spec (connection-spec (database-type (eql :mysql)))
106 (check-connection-spec connection-spec database-type
107 (host db user password &optional port))
108 (destructuring-bind (host db user password &optional port) connection-spec
109 (declare (ignore password))
110 (concatenate 'string
111 (etypecase host
112 (null "localhost")
113 (pathname (namestring host))
114 (string host))
115 (if port
116 (concatenate 'string
118 (etypecase port
119 (integer (write-to-string port))
120 (string port)))
122 "/" db "/" user)))
124 (defmethod database-connect (connection-spec (database-type (eql :mysql)))
125 (check-connection-spec connection-spec database-type
126 (host db user password &optional port))
127 (destructuring-bind (host db user password &optional port) connection-spec
128 (let ((mysql-ptr (mysql-init (uffi:make-null-pointer 'mysql-mysql)))
129 (socket nil))
130 (if (uffi:null-pointer-p mysql-ptr)
131 (error 'sql-connection-error
132 :database-type database-type
133 :connection-spec connection-spec
134 :error-id (mysql-errno mysql-ptr)
135 :message (mysql-error-string mysql-ptr))
136 (uffi:with-cstrings ((host-native host)
137 (user-native user)
138 (password-native password)
139 (db-native db)
140 (socket-native socket))
141 (let ((error-occurred nil))
142 (unwind-protect
143 (if (uffi:null-pointer-p
144 (mysql-real-connect
145 mysql-ptr host-native user-native password-native
146 db-native
147 (etypecase port
148 (null 0)
149 (integer port)
150 (string (parse-integer port)))
151 socket-native 0))
152 (progn
153 (setq error-occurred t)
154 (error 'sql-connection-error
155 :database-type database-type
156 :connection-spec connection-spec
157 :error-id (mysql-errno mysql-ptr)
158 :message (mysql-error-string mysql-ptr)))
159 (make-instance 'mysql-database
160 :name (database-name-from-spec connection-spec
161 database-type)
162 :database-type :mysql
163 :connection-spec connection-spec
164 :server-info (uffi:convert-from-cstring
165 (mysql:mysql-get-server-info mysql-ptr))
166 :mysql-ptr mysql-ptr))
167 (when error-occurred (mysql-close mysql-ptr)))))))))
170 (defmethod database-disconnect ((database mysql-database))
171 (mysql-close (database-mysql-ptr database))
172 (setf (database-mysql-ptr database) nil)
176 (defmethod database-query (query-expression (database mysql-database)
177 result-types field-names)
178 (declare (optimize (speed 3) (safety 0) (debug 0) (space 0)))
179 (let ((mysql-ptr (database-mysql-ptr database)))
180 (uffi:with-cstring (query-native query-expression)
181 (if (zerop (mysql-real-query mysql-ptr query-native
182 (expression-length query-expression)))
183 (let ((res-ptr (mysql-use-result mysql-ptr)))
184 (if res-ptr
185 (unwind-protect
186 (let ((num-fields (mysql-num-fields res-ptr)))
187 (declare (fixnum num-fields))
188 (setq result-types (canonicalize-types
189 result-types num-fields
190 res-ptr))
191 (values
192 (loop for row = (mysql-fetch-row res-ptr)
193 for lengths = (mysql-fetch-lengths res-ptr)
194 until (uffi:null-pointer-p row)
195 collect
196 (do* ((rlist (make-list num-fields))
197 (i 0 (1+ i))
198 (pos rlist (cdr pos)))
199 ((= i num-fields) rlist)
200 (declare (fixnum i))
201 (setf (car pos)
202 (convert-raw-field
203 (uffi:deref-array row '(:array
204 (* :unsigned-char))
206 result-types i
207 (uffi:deref-array lengths '(:array :unsigned-long)
208 i)))))
209 (when field-names
210 (result-field-names num-fields res-ptr))))
211 (mysql-free-result res-ptr))
212 (error 'sql-database-data-error
213 :database database
214 :expression query-expression
215 :error-id (mysql-errno mysql-ptr)
216 :message (mysql-error-string mysql-ptr))))
217 (error 'sql-database-data-error
218 :database database
219 :expression query-expression
220 :error-id (mysql-errno mysql-ptr)
221 :message (mysql-error-string mysql-ptr))))))
223 (defmethod database-execute-command (sql-expression (database mysql-database))
224 (uffi:with-cstring (sql-native sql-expression)
225 (let ((mysql-ptr (database-mysql-ptr database)))
226 (declare (type mysql-mysql-ptr-def mysql-ptr))
227 (if (zerop (mysql-real-query mysql-ptr sql-native
228 (expression-length sql-expression)))
230 (error 'sql-database-data-error
231 :database database
232 :expression sql-expression
233 :error-id (mysql-errno mysql-ptr)
234 :message (mysql-error-string mysql-ptr))))))
237 (defstruct mysql-result-set
238 (res-ptr (uffi:make-null-pointer 'mysql-mysql-res) :type mysql-mysql-res-ptr-def)
239 (types nil :type list)
240 (num-fields 0 :type fixnum)
241 (full-set nil :type boolean))
244 (defmethod database-query-result-set ((query-expression string)
245 (database mysql-database)
246 &key full-set result-types)
247 (uffi:with-cstring (query-native query-expression)
248 (let ((mysql-ptr (database-mysql-ptr database)))
249 (declare (type mysql-mysql-ptr-def mysql-ptr))
250 (if (zerop (mysql-real-query mysql-ptr query-native
251 (expression-length query-expression)))
252 (let ((res-ptr (if full-set
253 (mysql-store-result mysql-ptr)
254 (mysql-use-result mysql-ptr))))
255 (declare (type mysql-mysql-res-ptr-def res-ptr))
256 (if (not (uffi:null-pointer-p res-ptr))
257 (let* ((num-fields (mysql-num-fields res-ptr))
258 (result-set (make-mysql-result-set
259 :res-ptr res-ptr
260 :num-fields num-fields
261 :full-set full-set
262 :types
263 (canonicalize-types
264 result-types num-fields
265 res-ptr))))
266 (if full-set
267 (values result-set
268 num-fields
269 (mysql-num-rows res-ptr))
270 (values result-set
271 num-fields)))
272 (error 'sql-database-data-error
273 :database database
274 :expression query-expression
275 :error-id (mysql-errno mysql-ptr)
276 :message (mysql-error-string mysql-ptr))))
277 (error 'sql-database-data-error
278 :database database
279 :expression query-expression
280 :error-id (mysql-errno mysql-ptr)
281 :message (mysql-error-string mysql-ptr))))))
283 (defmethod database-dump-result-set (result-set (database mysql-database))
284 (mysql-free-result (mysql-result-set-res-ptr result-set))
288 (defmethod database-store-next-row (result-set (database mysql-database) list)
289 (let* ((res-ptr (mysql-result-set-res-ptr result-set))
290 (row (mysql-fetch-row res-ptr))
291 (lengths (mysql-fetch-lengths res-ptr))
292 (types (mysql-result-set-types result-set)))
293 (declare (type mysql-mysql-res-ptr-def res-ptr)
294 (type mysql-row-def row))
295 (unless (uffi:null-pointer-p row)
296 (loop for i from 0 below (mysql-result-set-num-fields result-set)
297 for rest on list
299 (setf (car rest)
300 (convert-raw-field
301 (uffi:deref-array row '(:array (* :unsigned-char)) i)
302 types
304 (uffi:deref-array lengths '(:array :unsigned-long) i))))
305 list)))
308 ;; Table and attribute introspection
310 (defmethod database-list-tables ((database mysql-database) &key (owner nil))
311 (declare (ignore owner))
312 (cond
313 ((eql #\5 (char (database-server-info database) 0))
314 (loop for (name type) in (database-query "SHOW FULL TABLES" database nil nil)
315 when (and (string-equal type "base table")
316 (not (and (>= (length name) 11)
317 (string-equal (subseq name 0 11) "_CLSQL_SEQ_"))))
318 collect name))
320 (remove-if #'(lambda (s)
321 (and (>= (length s) 11)
322 (string-equal (subseq s 0 11) "_CLSQL_SEQ_")))
323 (mapcar #'car (database-query "SHOW TABLES" database nil nil))))))
325 (defmethod database-list-views ((database mysql-database)
326 &key (owner nil))
327 (declare (ignore owner))
328 (cond
329 ((eql #\5 (char (database-server-info database) 0))
330 (loop for (name type) in (database-query "SHOW FULL TABLES" database nil nil)
331 when (string-equal type "view")
332 collect name))
334 nil)))
336 (defmethod database-list-indexes ((database mysql-database)
337 &key (owner nil))
338 (let ((result '()))
339 (dolist (table (database-list-tables database :owner owner) result)
340 (setq result
341 (append (database-list-table-indexes table database :owner owner)
342 result)))))
344 (defmethod database-list-table-indexes (table (database mysql-database)
345 &key (owner nil))
346 (declare (ignore owner))
347 (do ((results nil)
348 (rows (database-query
349 (format nil "SHOW INDEX FROM ~A" (string-upcase table))
350 database nil nil)
351 (cdr rows)))
352 ((null rows) (nreverse results))
353 (let ((col (nth 2 (car rows))))
354 (unless (find col results :test #'string-equal)
355 (push col results)))))
357 (defmethod database-list-attributes ((table string) (database mysql-database)
358 &key (owner nil))
359 (declare (ignore owner))
360 (mapcar #'car
361 (database-query
362 (format nil "SHOW COLUMNS FROM ~A" table)
363 database nil nil)))
365 (defmethod database-attribute-type (attribute (table string)
366 (database mysql-database)
367 &key (owner nil))
368 (declare (ignore owner))
369 (let ((row (car (database-query
370 (format nil
371 "SHOW COLUMNS FROM ~A LIKE '~A'" table attribute)
372 database nil nil))))
373 (let* ((raw-type (second row))
374 (null (third row))
375 (start-length (position #\( raw-type))
376 (type (if start-length
377 (subseq raw-type 0 start-length)
378 raw-type))
379 (length (when start-length
380 (parse-integer (subseq raw-type (1+ start-length))
381 :junk-allowed t))))
382 (when type
383 (values (ensure-keyword type) length nil (if (string-equal null "YES") 1 0))))))
385 ;;; Sequence functions
387 (defun %sequence-name-to-table (sequence-name)
388 (concatenate 'string "_CLSQL_SEQ_" (sql-escape sequence-name)))
390 (defun %table-name-to-sequence-name (table-name)
391 (and (>= (length table-name) 11)
392 (string-equal (subseq table-name 0 11) "_CLSQL_SEQ_")
393 (subseq table-name 11)))
395 (defmethod database-create-sequence (sequence-name
396 (database mysql-database))
397 (let ((table-name (%sequence-name-to-table sequence-name)))
398 (database-execute-command
399 (concatenate 'string "CREATE TABLE " table-name
400 " (id int NOT NULL PRIMARY KEY AUTO_INCREMENT)")
401 database)
402 (database-execute-command
403 (concatenate 'string "INSERT INTO " table-name
404 " VALUES (-1)")
405 database)))
407 (defmethod database-drop-sequence (sequence-name
408 (database mysql-database))
409 (database-execute-command
410 (concatenate 'string "DROP TABLE " (%sequence-name-to-table sequence-name))
411 database))
413 (defmethod database-list-sequences ((database mysql-database)
414 &key (owner nil))
415 (declare (ignore owner))
416 (mapcan #'(lambda (s)
417 (let ((sn (%table-name-to-sequence-name (car s))))
418 (and sn (list sn))))
419 (database-query "SHOW TABLES" database nil nil)))
421 (defmethod database-set-sequence-position (sequence-name
422 (position integer)
423 (database mysql-database))
424 (database-execute-command
425 (format nil "UPDATE ~A SET id=~A" (%sequence-name-to-table sequence-name)
426 position)
427 database)
428 (mysql:mysql-insert-id (clsql-mysql::database-mysql-ptr database)))
430 (defmethod database-sequence-next (sequence-name (database mysql-database))
431 (without-interrupts
432 (database-execute-command
433 (concatenate 'string "UPDATE " (%sequence-name-to-table sequence-name)
434 " SET id=LAST_INSERT_ID(id+1)")
435 database)
436 (mysql:mysql-insert-id (clsql-mysql::database-mysql-ptr database))))
438 (defmethod database-sequence-last (sequence-name (database mysql-database))
439 (without-interrupts
440 (caar (database-query
441 (concatenate 'string "SELECT id from "
442 (%sequence-name-to-table sequence-name))
443 database :auto nil))))
445 (defmethod database-create (connection-spec (type (eql :mysql)))
446 (destructuring-bind (host name user password &optional port) connection-spec
447 (multiple-value-bind (output status)
448 (clsql-sys:command-output "mysqladmin create -u~A -p~A -h~A~@[ -P~A~] ~A"
449 user password
450 (if host host "localhost")
451 port name
452 name)
453 (if (or (not (eql 0 status))
454 (and (search "failed" output) (search "error" output)))
455 (error 'sql-database-error
456 :message
457 (format nil "mysql database creation failed with connection-spec ~A."
458 connection-spec))
459 t))))
461 (defmethod database-destroy (connection-spec (type (eql :mysql)))
462 (destructuring-bind (host name user password &optional port) connection-spec
463 (multiple-value-bind (output status)
464 (clsql-sys:command-output "mysqladmin drop -f -u~A -p~A -h~A~@[ -P~A~] ~A"
465 user password
466 (if host host "localhost")
467 port name)
468 (if (or (not (eql 0 status))
469 (and (search "failed" output) (search "error" output)))
470 (error 'sql-database-error
471 :message
472 (format nil "mysql database deletion failed with connection-spec ~A."
473 connection-spec))
474 t))))
476 (defmethod database-probe (connection-spec (type (eql :mysql)))
477 (when (find (second connection-spec) (database-list connection-spec type)
478 :test #'string-equal)
481 (defmethod database-list (connection-spec (type (eql :mysql)))
482 (destructuring-bind (host name user password &optional port) connection-spec
483 (declare (ignore name))
484 (let ((database (database-connect (list host "mysql" user password port) type)))
485 (unwind-protect
486 (progn
487 (setf (slot-value database 'clsql-sys::state) :open)
488 (mapcar #'car (database-query "show databases" database :auto nil)))
489 (progn
490 (database-disconnect database)
491 (setf (slot-value database 'clsql-sys::state) :closed))))))
494 ;;; Prepared statements
496 (defclass mysql-stmt ()
497 ((database :initarg :database :reader database)
498 (stmt :initarg :stmt :accessor stmt)
499 (input-bind :initarg :input-bind :reader input-bind)
500 (output-bind :initarg :output-bind :reader output-bind)
501 (types :initarg :types :reader types)
502 (result-set :initarg :result-set :reader result-set)
503 (num-fields :initarg :num-fields :reader num-fields)
504 (field-names :initarg :field-names :accessor stmt-field-names)
505 (length-ptr :initarg :length-ptr :reader length-ptr)
506 (is-null-ptr :initarg :is-null-ptr :reader is-null-ptr)
507 (result-types :initarg :result-types :reader result-types)))
509 (defun clsql-type->mysql-type (type)
510 (cond
511 ((in type :null) mysql-field-types#null)
512 ((in type :int :integer) mysql-field-types#long)
513 ((in type :short) mysql-field-types#short)
514 ((in type :bigint) mysql-field-types#longlong)
515 ((in type :float :double :number) mysql-field-types#double)
516 ((and (consp type) (in (car type) :char :string :varchar)) mysql-field-types#var-string)
517 ((or (eq type :blob) (and (consp type) (in (car type) :blob))) mysql-field-types#var-string)
519 (error 'sql-user-error
520 :message
521 (format nil "Unknown clsql type ~A." type)))))
523 #+mysql-client-v4.1
524 (defmethod database-prepare (sql-stmt types (database mysql-database) result-types field-names)
525 (let* ((mysql-ptr (database-mysql-ptr database))
526 (stmt (mysql-stmt-init mysql-ptr)))
527 (when (uffi:null-pointer-p stmt)
528 (error 'sql-database-error
529 :error-id (mysql-errno mysql-ptr)
530 :message (mysql-error-string mysql-ptr)))
532 (uffi:with-cstring (native-query sql-stmt)
533 (unless (zerop (mysql-stmt-prepare stmt native-query (expression-length sql-stmt)))
534 (mysql-stmt-close stmt)
535 (error 'sql-database-error
536 :error-id (mysql-errno mysql-ptr)
537 :message (mysql-error-string mysql-ptr))))
539 (unless (= (mysql-stmt-param-count stmt) (length types))
540 (mysql-stmt-close stmt)
541 (error 'sql-database-error
542 :message
543 (format nil "Mysql param count (~D) does not match number of types (~D)"
544 (mysql-stmt-param-count stmt) (length types))))
546 (let ((rs (mysql-stmt-result-metadata stmt)))
547 (when (uffi:null-pointer-p rs)
548 (warn "mysql_stmt_result_metadata returned NULL")
549 #+nil
550 (mysql-stmt-close stmt)
551 #+nil
552 (error 'sql-database-error
553 :message "mysql_stmt_result_metadata returned NULL"))
555 (let ((input-bind (uffi:allocate-foreign-object 'mysql-bind (length types)))
556 (mysql-types (mapcar 'clsql-type->mysql-type types))
557 field-vec num-fields is-null-ptr output-bind length-ptr)
559 (print 'a)
560 (dotimes (i (length types))
561 (let* ((binding (uffi:deref-array input-bind '(:array mysql-bind) i)))
562 (setf (uffi:get-slot-value binding 'mysql-bind 'mysql::buffer-type)
563 (nth i mysql-types))
564 (setf (uffi:get-slot-value binding 'mysql-bind 'mysql::buffer-length) 0)))
566 (print 'b)
567 (unless (uffi:null-pointer-p rs)
568 (setq field-vec (mysql-fetch-fields rs)
569 num-fields (mysql-num-fields rs)
570 is-null-ptr (uffi:allocate-foreign-object :byte num-fields)
571 output-bind (uffi:allocate-foreign-object 'mysql-bind num-fields)
572 length-ptr (uffi:allocate-foreign-object :unsigned-long num-fields))
573 (dotimes (i num-fields)
574 (declare (fixnum i))
575 (let* ((field (uffi:deref-array field-vec '(:array mysql-field) i))
576 (type (uffi:get-slot-value field 'mysql-field 'type))
577 (binding (uffi:deref-array output-bind '(:array mysql-bind) i)))
578 (setf (uffi:get-slot-value binding 'mysql-bind 'mysql::buffer-type) type)
580 (setf (uffi:get-slot-value binding 'mysql-bind 'mysql::buffer-length) 0)
581 #+need-to-allocate-foreign-object-for-this
582 (setf (uffi:get-slot-value binding 'mysql-bind 'mysql::is-null)
583 (+ i (uffi:pointer-address is-null-ptr)))
584 #+need-to-allocate-foreign-object-for-this
585 (setf (uffi:get-slot-value binding 'mysql-bind 'length)
586 (+ (* i 8) (uffi:pointer-address length-ptr)))
588 (case type
589 ((#.mysql-field-types#var-string #.mysql-field-types#string
590 #.mysql-field-types#tiny-blob #.mysql-field-types#blob
591 #.mysql-field-types#medium-blob #.mysql-field-types#long-blob)
592 (setf (uffi:get-slot-value binding 'mysql-bind 'mysql::buffer-length) 1024)
593 (setf (uffi:get-slot-value binding 'mysql-bind 'mysql::buffer)
594 (uffi:allocate-foreign-object :unsigned-char 1024)))
595 (#.mysql-field-types#tiny
596 (setf (uffi:get-slot-value binding 'mysql-bind 'mysql::buffer)
597 (uffi:allocate-foreign-object :byte)))
598 (#.mysql-field-types#short
599 (setf (uffi:get-slot-value binding 'mysql-bind 'mysql::buffer)
600 (uffi:allocate-foreign-object :short)))
601 (#.mysql-field-types#long
602 (setf (uffi:get-slot-value binding 'mysql-bind 'mysql::buffer)
603 ;; segfaults if supply :int on amd64
604 (uffi:allocate-foreign-object :long)))
605 #+64bit
606 (#.mysql-field-types#longlong
607 (setf (uffi:get-slot-value binding 'mysql-bind 'mysql::buffer)
608 (uffi:allocate-foreign-object :long)))
609 (#.mysql-field-types#float
610 (setf (uffi:get-slot-value binding 'mysql-bind 'mysql::buffer)
611 (uffi:allocate-foreign-object :float)))
612 (#.mysql-field-types#double
613 (setf (uffi:get-slot-value binding 'mysql-bind 'mysql::buffer)
614 (uffi:allocate-foreign-object :double)))
615 ((#.mysql-field-types#time #.mysql-field-types#date
616 #.mysql-field-types#datetime #.mysql-field-types#timestamp)
617 (uffi:allocate-foreign-object 'mysql-time))
619 (error "mysql type ~D not supported." type)))))
621 (unless (zerop (mysql-stmt-bind-result stmt output-bind))
622 (mysql-stmt-close stmt)
623 (error 'sql-database-error
624 :error-id (mysql-stmt-errno stmt)
625 :message (uffi:convert-from-cstring
626 (mysql-stmt-error stmt)))))
628 (make-instance 'mysql-stmt
629 :database database
630 :stmt stmt
631 :num-fields num-fields
632 :input-bind input-bind
633 :output-bind output-bind
634 :result-set rs
635 :result-types result-types
636 :length-ptr length-ptr
637 :is-null-ptr is-null-ptr
638 :types mysql-types
639 :field-names field-names)))))
641 #+mysql-client-v4.1
642 (defmethod database-bind-parameter ((stmt mysql-stmt) position value)
643 ;; FIXME: will need to allocate bind structure. This should probably be
644 ;; done in C since the API is not mature and may change
645 (let ((binding (uffi:deref-array (input-bind stmt) '(:array mysql-bind) (1- position)))
646 (type (nth (1- position) (types stmt))))
647 (setf (uffi:get-slot-value binding 'mysql-bind 'length) 0)
648 (cond
649 ((null value)
650 (when (is-null-ptr stmt)
651 (setf (uffi:deref-array (is-null-ptr stmt) '(:array :byte) (1- position)) 1)))
653 (when (is-null-ptr stmt)
654 (setf (uffi:deref-array (is-null-ptr stmt) '(:array :byte) (1- position)) 0))
655 (case type
656 (#.mysql-field-types#long
657 (setf (uffi:get-slot-value binding 'mysql-bind 'mysql::buffer) value))
659 (warn "Unknown input bind type ~D." type))
660 )))))
662 #+mysql-client-v4.1
663 (defmethod database-run-prepared ((stmt mysql-stmt))
664 (print 'a1)
665 (when (input-bind stmt)
666 (unless (zerop (mysql-stmt-bind-param (stmt stmt) (input-bind stmt)))
667 (error 'sql-database-error
668 :error-id (mysql-stmt-errno (stmt stmt))
669 :message (uffi:convert-from-cstring
670 (mysql-stmt-error (stmt stmt))))))
671 (print 'a2)
672 (unless (zerop (mysql-stmt-execute (stmt stmt)))
673 (error 'sql-database-error
674 :error-id (mysql-stmt-errno (stmt stmt))
675 :message (uffi:convert-from-cstring
676 (mysql-stmt-error (stmt stmt)))))
677 (print 'a3)
678 (unless (zerop (mysql-stmt-store-result (stmt stmt)))
679 (error 'sql-database-error
680 :error-id (mysql-stmt-errno (stmt stmt))
681 :message (uffi:convert-from-cstring
682 (mysql-stmt-error (stmt stmt)))))
683 (database-fetch-prepared-rows stmt))
685 #+mysql-client-v4.1
686 (defun database-fetch-prepared-rows (stmt)
687 (do ((rc (mysql-stmt-fetch (stmt stmt)) (mysql-stmt-fetch (stmt stmt)))
688 (num-fields (num-fields stmt))
689 (rows '()))
690 ((not (zerop rc)) (nreverse rows))
691 (push
692 (loop for i from 0 below num-fields
693 collect
694 (let ((is-null
695 (not (zerop (uffi:ensure-char-integer
696 (uffi:deref-array (is-null-ptr stmt) '(:array :byte) i))))))
697 (unless is-null
698 (let* ((bind (uffi:deref-array (output-bind stmt) '(:array mysql-bind) i))
699 (type (uffi:get-slot-value bind 'mysql-bind 'mysql::buffer-type))
700 (buffer (uffi:get-slot-value bind 'mysql-bind 'mysql::buffer)))
701 (case type
702 ((#.mysql-field-types#var-string #.mysql-field-types#string
703 #.mysql-field-types#tiny-blob #.mysql-field-types#blob
704 #.mysql-field-types#medium-blob #.mysql-field-types#long-blob)
705 (uffi:convert-from-foreign-string buffer))
706 (#.mysql-field-types#tiny
707 (uffi:ensure-char-integer
708 (uffi:deref-pointer buffer :byte)))
709 (#.mysql-field-types#short
710 (uffi:deref-pointer buffer :short))
711 (#.mysql-field-types#long
712 (uffi:deref-pointer buffer :int))
713 #+64bit
714 (#.mysql-field-types#longlong
715 (uffi:deref-pointer buffer :long))
716 (#.mysql-field-types#float
717 (uffi:deref-pointer buffer :float))
718 (#.mysql-field-types#double
719 (uffi:deref-pointer buffer :double))
720 ((#.mysql-field-types#time #.mysql-field-types#date
721 #.mysql-field-types#datetime #.mysql-field-types#timestamp)
722 (let ((year (uffi:get-slot-value buffer 'mysql-time 'mysql::year))
723 (month (uffi:get-slot-value buffer 'mysql-time 'mysql::month))
724 (day (uffi:get-slot-value buffer 'mysql-time 'mysql::day))
725 (hour (uffi:get-slot-value buffer 'mysql-time 'mysql::hour))
726 (minute (uffi:get-slot-value buffer 'mysql-time 'mysql::minute))
727 (second (uffi:get-slot-value buffer 'mysql-time 'mysql::second)))
728 (db-timestring
729 (make-time :year year :month month :day day :hour hour
730 :minute minute :second second))))
732 (list type)))))))
733 rows)))
738 #+mysql-client-v4.1
739 (defmethod database-free-prepared ((stmt mysql-stmt))
740 (with-slots (stmt) stmt
741 (mysql-stmt-close stmt))
745 ;;; Database capabilities
747 (defmethod db-type-use-column-on-drop-index? ((db-type (eql :mysql)))
750 (defmethod db-type-has-views? ((db-type (eql :mysql)))
751 #+mysql-client-v5 t
752 #-mysql-client-v5 nil)
754 (defmethod db-type-has-subqueries? ((db-type (eql :mysql)))
755 #+(or mysql-client-v4.1 mysql-client-v5) t
756 #-(or mysql-client-v4.1 mysql-client-v5) nil)
758 (defmethod db-type-has-boolean-where? ((db-type (eql :mysql)))
759 #+(or mysql-client-v4.1 mysql-client-v5) t
760 #-(or mysql-client-v4.1 mysql-client-v5) nil)
762 (defmethod db-type-has-union? ((db-type (eql :mysql)))
763 (not (eql (schar mysql::*mysql-client-info* 0) #\3)))
765 (defmethod db-type-transaction-capable? ((db-type (eql :mysql)) database)
766 (let ((tuple (car (database-query "SHOW VARIABLES LIKE 'HAVE_INNODB'" database :auto nil))))
767 (and tuple (string-equal "YES" (second tuple)))))
769 (defmethod db-type-has-prepared-stmt? ((db-type (eql :mysql)))
770 #+(or mysql-client-v4.1 mysql-client-v5) t
771 #-(or mysql-client-v4.1 mysql-client-v5) nil)
773 (when (clsql-sys:database-type-library-loaded :mysql)
774 (clsql-sys:initialize-database-type :database-type :mysql))