Use :verbose nil for asdf:operate invocation
[clsql/s11.git] / db-mysql / mysql-sql.lisp
blob8f39471257c2a38ee6feaeea0faf371ecc48ca09
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 ;;;; This file, part of CLSQL, is Copyright (c) 2002-2009 by Kevin M. Rosenberg
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 (res-ptr)
32 (let ((names '()))
33 (mysql-field-seek res-ptr 0)
34 (loop
35 (let ((field (mysql-fetch-field res-ptr)))
36 (when (uffi:null-pointer-p field) (return))
37 (push (uffi:convert-from-cstring (clsql-mysql-field-name field)) names)))
38 (nreverse names)))
40 (defun make-type-list-for-auto (res-ptr)
41 (let ((new-types '()))
42 (mysql-field-seek res-ptr 0)
43 (loop
44 (let ((field (mysql-fetch-field res-ptr)))
45 (when (uffi:null-pointer-p field) (return))
46 (let* ((flags (clsql-mysql-field-flags field))
47 (unsigned (plusp (logand flags 32)))
48 (type (clsql-mysql-field-type field)))
49 (push
50 (case type
51 ((#.mysql-field-types#tiny
52 #.mysql-field-types#short
53 #.mysql-field-types#int24)
54 (if unsigned
55 :uint32
56 :int32))
57 (#.mysql-field-types#long
58 (if unsigned
59 :uint
60 :int))
61 (#.mysql-field-types#longlong
62 (if unsigned
63 :uint64
64 :int64))
65 ((#.mysql-field-types#double
66 #.mysql-field-types#float
67 #.mysql-field-types#decimal)
68 :double)
69 (otherwise
70 t))
71 new-types))))
72 (nreverse new-types)))
74 (defun canonicalize-types (types res-ptr)
75 (when types
76 (let ((auto-list (make-type-list-for-auto res-ptr)))
77 (cond
78 ((listp types)
79 (canonicalize-type-list types auto-list))
80 ((eq types :auto)
81 auto-list)
83 nil)))))
85 (defmethod database-initialize-database-type ((database-type (eql :mysql)))
88 ;;(uffi:def-type mysql-mysql-ptr-def (* mysql-mysql))
89 ;;(uffi:def-type mysql-mysql-res-ptr-def (* mysql-mysql-res))
90 (uffi:def-type mysql-mysql-ptr-def mysql-mysql)
91 (uffi:def-type mysql-mysql-res-ptr-def mysql-mysql-res)
92 (uffi:def-type mysql-row-def mysql-row)
94 (defclass mysql-database (database)
95 ((mysql-ptr :accessor database-mysql-ptr :initarg :mysql-ptr
96 :type mysql-mysql-ptr-def)
97 (server-info :accessor database-server-info :initarg :server-info
98 :type string)))
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 (let* ((db
158 (make-instance 'mysql-database
159 :name (database-name-from-spec connection-spec
160 database-type)
161 :database-type :mysql
162 :connection-spec connection-spec
163 :server-info (uffi:convert-from-cstring
164 (mysql:mysql-get-server-info mysql-ptr))
165 :mysql-ptr mysql-ptr))
166 (cmd "SET SESSION sql_mode='ANSI'"))
167 (uffi:with-cstring (cmd-cs cmd)
168 (if (zerop (mysql-real-query mysql-ptr cmd-cs (expression-length cmd)))
170 (progn
171 (warn "Error setting ANSI mode for MySQL.")
172 db)))))
173 (when error-occurred (mysql-close mysql-ptr)))))))))
176 (defmethod database-disconnect ((database mysql-database))
177 (mysql-close (database-mysql-ptr database))
178 (setf (database-mysql-ptr database) nil)
182 (defmethod database-query (query-expression (database mysql-database)
183 result-types field-names)
184 (declare (optimize (speed 3) (safety 0) (debug 0) (space 0)))
185 (let ((mysql-ptr (database-mysql-ptr database)))
186 (uffi:with-cstring (query-native query-expression)
187 (if (zerop (mysql-real-query mysql-ptr query-native
188 (expression-length query-expression)))
189 (let ((res-ptr (mysql-use-result mysql-ptr)))
190 (if res-ptr
191 (unwind-protect
192 (let ((num-fields (mysql-num-fields res-ptr)))
193 (declare (fixnum num-fields))
194 (setq result-types (canonicalize-types
195 result-types res-ptr))
196 (values
197 (loop for row = (mysql-fetch-row res-ptr)
198 for lengths = (mysql-fetch-lengths res-ptr)
199 until (uffi:null-pointer-p row)
200 collect
201 (do* ((rlist (make-list num-fields))
202 (i 0 (1+ i))
203 (pos rlist (cdr pos)))
204 ((= i num-fields) rlist)
205 (declare (fixnum i))
206 (setf (car pos)
207 (convert-raw-field
208 (uffi:deref-array row '(:array
209 (* :unsigned-char))
211 result-types i
212 (uffi:deref-array lengths '(:array :unsigned-long)
213 i)))))
214 (when field-names
215 (result-field-names res-ptr))))
216 (mysql-free-result res-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))))
222 (error 'sql-database-data-error
223 :database database
224 :expression query-expression
225 :error-id (mysql-errno mysql-ptr)
226 :message (mysql-error-string mysql-ptr))))))
228 (defmethod database-execute-command (sql-expression (database mysql-database))
229 (uffi:with-cstring (sql-native sql-expression)
230 (let ((mysql-ptr (database-mysql-ptr database)))
231 (declare (type mysql-mysql-ptr-def mysql-ptr))
232 (if (zerop (mysql-real-query mysql-ptr sql-native
233 (expression-length sql-expression)))
235 (error 'sql-database-data-error
236 :database database
237 :expression sql-expression
238 :error-id (mysql-errno mysql-ptr)
239 :message (mysql-error-string mysql-ptr))))))
242 (defstruct mysql-result-set
243 (res-ptr (uffi:make-null-pointer 'mysql-mysql-res) :type mysql-mysql-res-ptr-def)
244 (types nil :type list)
245 (num-fields 0 :type fixnum)
246 (full-set nil :type boolean))
249 (defmethod database-query-result-set ((query-expression string)
250 (database mysql-database)
251 &key full-set result-types)
252 (uffi:with-cstring (query-native query-expression)
253 (let ((mysql-ptr (database-mysql-ptr database)))
254 (declare (type mysql-mysql-ptr-def mysql-ptr))
255 (if (zerop (mysql-real-query mysql-ptr query-native
256 (expression-length query-expression)))
257 (let ((res-ptr (if full-set
258 (mysql-store-result mysql-ptr)
259 (mysql-use-result mysql-ptr))))
260 (declare (type mysql-mysql-res-ptr-def res-ptr))
261 (if (not (uffi:null-pointer-p res-ptr))
262 (let* ((num-fields (mysql-num-fields res-ptr))
263 (result-set (make-mysql-result-set
264 :res-ptr res-ptr
265 :num-fields num-fields
266 :full-set full-set
267 :types
268 (canonicalize-types
269 result-types res-ptr))))
270 (if full-set
271 (values result-set
272 num-fields
273 (mysql-num-rows res-ptr))
274 (values result-set
275 num-fields)))
276 (error 'sql-database-data-error
277 :database database
278 :expression query-expression
279 :error-id (mysql-errno mysql-ptr)
280 :message (mysql-error-string mysql-ptr))))
281 (error 'sql-database-data-error
282 :database database
283 :expression query-expression
284 :error-id (mysql-errno mysql-ptr)
285 :message (mysql-error-string mysql-ptr))))))
287 (defmethod database-dump-result-set (result-set (database mysql-database))
288 (mysql-free-result (mysql-result-set-res-ptr result-set))
292 (defmethod database-store-next-row (result-set (database mysql-database) list)
293 (let* ((res-ptr (mysql-result-set-res-ptr result-set))
294 (row (mysql-fetch-row res-ptr))
295 (lengths (mysql-fetch-lengths res-ptr))
296 (types (mysql-result-set-types result-set)))
297 (declare (type mysql-mysql-res-ptr-def res-ptr)
298 (type mysql-row-def row))
299 (unless (uffi:null-pointer-p row)
300 (loop for i from 0 below (mysql-result-set-num-fields result-set)
301 for rest on list
303 (setf (car rest)
304 (convert-raw-field
305 (uffi:deref-array row '(:array (* :unsigned-char)) i)
306 types
308 (uffi:deref-array lengths '(:array :unsigned-long) i))))
309 list)))
312 ;; Table and attribute introspection
314 (defmethod database-list-tables ((database mysql-database) &key (owner nil))
315 (declare (ignore owner))
316 (cond
317 ((eql #\5 (char (database-server-info database) 0))
318 (loop for (name type) in (database-query "SHOW FULL TABLES" database nil nil)
319 when (and (string-equal type "base table")
320 (not (and (>= (length name) 11)
321 (string-equal (subseq name 0 11) "_CLSQL_SEQ_"))))
322 collect name))
324 (remove-if #'(lambda (s)
325 (and (>= (length s) 11)
326 (string-equal (subseq s 0 11) "_CLSQL_SEQ_")))
327 (mapcar #'car (database-query "SHOW TABLES" database nil nil))))))
329 (defmethod database-list-views ((database mysql-database)
330 &key (owner nil))
331 (declare (ignore owner))
332 (cond
333 ((eql #\5 (char (database-server-info database) 0))
334 (loop for (name type) in (database-query "SHOW FULL TABLES" database nil nil)
335 when (string-equal type "view")
336 collect name))
338 nil)))
340 (defmethod database-list-indexes ((database mysql-database)
341 &key (owner nil))
342 (let ((result '()))
343 (dolist (table (database-list-tables database :owner owner) result)
344 (setq result
345 (append (database-list-table-indexes table database :owner owner)
346 result)))))
348 (defmethod database-list-table-indexes (table (database mysql-database)
349 &key (owner nil))
350 (declare (ignore owner))
351 (do ((results nil)
352 (rows (database-query
353 (format nil "SHOW INDEX FROM ~A" table)
354 database nil nil)
355 (cdr rows)))
356 ((null rows) (nreverse results))
357 (let ((col (nth 2 (car rows))))
358 (unless (find col results :test #'string-equal)
359 (push col results)))))
361 (defmethod database-list-attributes ((table string) (database mysql-database)
362 &key (owner nil))
363 (declare (ignore owner))
364 (mapcar #'car
365 (database-query
366 (format nil "SHOW COLUMNS FROM ~A" table)
367 database nil nil)))
369 (defmethod database-attribute-type (attribute (table string)
370 (database mysql-database)
371 &key (owner nil))
372 (declare (ignore owner))
373 (let ((row (car (database-query
374 (format nil
375 "SHOW COLUMNS FROM ~A LIKE '~A'" table attribute)
376 database nil nil))))
377 (let* ((raw-type (second row))
378 (null (third row))
379 (start-length (position #\( raw-type))
380 (type (if start-length
381 (subseq raw-type 0 start-length)
382 raw-type))
383 (length (when start-length
384 (parse-integer (subseq raw-type (1+ start-length))
385 :junk-allowed t))))
386 (when type
387 (values (ensure-keyword type) length nil (if (string-equal null "YES") 1 0))))))
389 ;;; Sequence functions
391 (defun %sequence-name-to-table (sequence-name)
392 (concatenate 'string "_CLSQL_SEQ_" (sql-escape sequence-name)))
394 (defun %table-name-to-sequence-name (table-name)
395 (and (>= (length table-name) 11)
396 (string-equal (subseq table-name 0 11) "_CLSQL_SEQ_")
397 (subseq table-name 11)))
399 (defmethod database-create-sequence (sequence-name
400 (database mysql-database))
401 (let ((table-name (%sequence-name-to-table sequence-name)))
402 (database-execute-command
403 (concatenate 'string "CREATE TABLE " table-name
404 " (id int NOT NULL PRIMARY KEY AUTO_INCREMENT)")
405 database)
406 (database-execute-command
407 (concatenate 'string "INSERT INTO " table-name
408 " VALUES (-1)")
409 database)))
411 (defmethod database-drop-sequence (sequence-name
412 (database mysql-database))
413 (database-execute-command
414 (concatenate 'string "DROP TABLE " (%sequence-name-to-table sequence-name))
415 database))
417 (defmethod database-list-sequences ((database mysql-database)
418 &key (owner nil))
419 (declare (ignore owner))
420 (mapcan #'(lambda (s)
421 (let ((sn (%table-name-to-sequence-name (car s))))
422 (and sn (list sn))))
423 (database-query "SHOW TABLES" database nil nil)))
425 (defmethod database-set-sequence-position (sequence-name
426 (position integer)
427 (database mysql-database))
428 (database-execute-command
429 (format nil "UPDATE ~A SET id=~A" (%sequence-name-to-table sequence-name)
430 position)
431 database)
432 (mysql:mysql-insert-id (clsql-mysql::database-mysql-ptr database)))
434 (defmethod database-sequence-next (sequence-name (database mysql-database))
435 (without-interrupts
436 (database-execute-command
437 (concatenate 'string "UPDATE " (%sequence-name-to-table sequence-name)
438 " SET id=LAST_INSERT_ID(id+1)")
439 database)
440 (mysql:mysql-insert-id (clsql-mysql::database-mysql-ptr database))))
442 (defmethod database-sequence-last (sequence-name (database mysql-database))
443 (without-interrupts
444 (caar (database-query
445 (concatenate 'string "SELECT id from "
446 (%sequence-name-to-table sequence-name))
447 database :auto nil))))
449 (defmethod database-create (connection-spec (type (eql :mysql)))
450 (destructuring-bind (host name user password) connection-spec
451 (let ((database (database-connect (list host "" user password)
452 type)))
453 (setf (slot-value database 'clsql-sys::state) :open)
454 (unwind-protect
455 (database-execute-command (format nil "create database ~A" name) database)
456 (database-disconnect database)))))
458 (defmethod database-destroy (connection-spec (type (eql :mysql)))
459 (destructuring-bind (host name user password) connection-spec
460 (let ((database (database-connect (list host "" user password)
461 type)))
462 (setf (slot-value database 'clsql-sys::state) :open)
463 (unwind-protect
464 (database-execute-command (format nil "drop database ~A" name) database)
465 (database-disconnect database)))))
467 (defmethod database-probe (connection-spec (type (eql :mysql)))
468 (when (find (second connection-spec) (database-list connection-spec type)
469 :test #'string-equal)
472 (defmethod database-list (connection-spec (type (eql :mysql)))
473 (destructuring-bind (host name user password &optional port) connection-spec
474 (let ((database (database-connect (list host (or name "mysql")
475 user password port) type)))
476 (unwind-protect
477 (progn
478 (setf (slot-value database 'clsql-sys::state) :open)
479 (mapcar #'car (database-query "show databases" database :auto nil)))
480 (progn
481 (database-disconnect database)
482 (setf (slot-value database 'clsql-sys::state) :closed))))))
485 ;;; Prepared statements
487 (defclass mysql-stmt ()
488 ((database :initarg :database :reader database)
489 (stmt :initarg :stmt :accessor stmt)
490 (input-bind :initarg :input-bind :reader input-bind)
491 (output-bind :initarg :output-bind :reader output-bind)
492 (types :initarg :types :reader types)
493 (result-set :initarg :result-set :reader result-set)
494 (num-fields :initarg :num-fields :reader num-fields)
495 (field-names :initarg :field-names :accessor stmt-field-names)
496 (length-ptr :initarg :length-ptr :reader length-ptr)
497 (is-null-ptr :initarg :is-null-ptr :reader is-null-ptr)
498 (result-types :initarg :result-types :reader result-types)))
500 (defun clsql-type->mysql-type (type)
501 (cond
502 ((in type :null) mysql-field-types#null)
503 ((in type :int :integer) mysql-field-types#long)
504 ((in type :short) mysql-field-types#short)
505 ((in type :bigint) mysql-field-types#longlong)
506 ((in type :float :double :number) mysql-field-types#double)
507 ((and (consp type) (in (car type) :char :string :varchar)) mysql-field-types#var-string)
508 ((or (eq type :blob) (and (consp type) (in (car type) :blob))) mysql-field-types#var-string)
510 (error 'sql-user-error
511 :message
512 (format nil "Unknown clsql type ~A." type)))))
514 #+mysql-client-v4.1
515 (defmethod database-prepare (sql-stmt types (database mysql-database) result-types field-names)
516 (let* ((mysql-ptr (database-mysql-ptr database))
517 (stmt (mysql-stmt-init mysql-ptr)))
518 (when (uffi:null-pointer-p stmt)
519 (error 'sql-database-error
520 :error-id (mysql-errno mysql-ptr)
521 :message (mysql-error-string mysql-ptr)))
523 (uffi:with-cstring (native-query sql-stmt)
524 (unless (zerop (mysql-stmt-prepare stmt native-query (expression-length sql-stmt)))
525 (mysql-stmt-close stmt)
526 (error 'sql-database-error
527 :error-id (mysql-errno mysql-ptr)
528 :message (mysql-error-string mysql-ptr))))
530 (unless (= (mysql-stmt-param-count stmt) (length types))
531 (mysql-stmt-close stmt)
532 (error 'sql-database-error
533 :message
534 (format nil "Mysql param count (~D) does not match number of types (~D)"
535 (mysql-stmt-param-count stmt) (length types))))
537 (let ((rs (mysql-stmt-result-metadata stmt)))
538 (when (uffi:null-pointer-p rs)
539 (warn "mysql_stmt_result_metadata returned NULL")
540 #+nil
541 (mysql-stmt-close stmt)
542 #+nil
543 (error 'sql-database-error
544 :message "mysql_stmt_result_metadata returned NULL"))
546 (let ((input-bind (uffi:allocate-foreign-object 'mysql-bind (length types)))
547 (mysql-types (mapcar 'clsql-type->mysql-type types))
548 field-vec num-fields is-null-ptr output-bind length-ptr)
550 (print 'a)
551 (dotimes (i (length types))
552 (let* ((binding (uffi:deref-array input-bind '(:array mysql-bind) i)))
553 (setf (uffi:get-slot-value binding 'mysql-bind 'mysql::buffer-type)
554 (nth i mysql-types))
555 (setf (uffi:get-slot-value binding 'mysql-bind 'mysql::buffer-length) 0)))
557 (print 'b)
558 (unless (uffi:null-pointer-p rs)
559 (setq field-vec (mysql-fetch-fields rs)
560 num-fields (mysql-num-fields rs)
561 is-null-ptr (uffi:allocate-foreign-object :byte num-fields)
562 output-bind (uffi:allocate-foreign-object 'mysql-bind num-fields)
563 length-ptr (uffi:allocate-foreign-object :unsigned-long num-fields))
564 (dotimes (i num-fields)
565 (declare (fixnum i))
566 (let* ((field (uffi:deref-array field-vec '(:array mysql-field) i))
567 (type (uffi:get-slot-value field 'mysql-field 'type))
568 (binding (uffi:deref-array output-bind '(:array mysql-bind) i)))
569 (setf (uffi:get-slot-value binding 'mysql-bind 'mysql::buffer-type) type)
571 (setf (uffi:get-slot-value binding 'mysql-bind 'mysql::buffer-length) 0)
572 #+need-to-allocate-foreign-object-for-this
573 (setf (uffi:get-slot-value binding 'mysql-bind 'mysql::is-null)
574 (+ i (uffi:pointer-address is-null-ptr)))
575 #+need-to-allocate-foreign-object-for-this
576 (setf (uffi:get-slot-value binding 'mysql-bind 'length)
577 (+ (* i 8) (uffi:pointer-address length-ptr)))
579 (case type
580 ((#.mysql-field-types#var-string #.mysql-field-types#string
581 #.mysql-field-types#tiny-blob #.mysql-field-types#blob
582 #.mysql-field-types#medium-blob #.mysql-field-types#long-blob)
583 (setf (uffi:get-slot-value binding 'mysql-bind 'mysql::buffer-length) 1024)
584 (setf (uffi:get-slot-value binding 'mysql-bind 'mysql::buffer)
585 (uffi:allocate-foreign-object :unsigned-char 1024)))
586 (#.mysql-field-types#tiny
587 (setf (uffi:get-slot-value binding 'mysql-bind 'mysql::buffer)
588 (uffi:allocate-foreign-object :byte)))
589 (#.mysql-field-types#short
590 (setf (uffi:get-slot-value binding 'mysql-bind 'mysql::buffer)
591 (uffi:allocate-foreign-object :short)))
592 (#.mysql-field-types#long
593 (setf (uffi:get-slot-value binding 'mysql-bind 'mysql::buffer)
594 ;; segfaults if supply :int on amd64
595 (uffi:allocate-foreign-object :long)))
596 #+64bit
597 (#.mysql-field-types#longlong
598 (setf (uffi:get-slot-value binding 'mysql-bind 'mysql::buffer)
599 (uffi:allocate-foreign-object :long)))
600 (#.mysql-field-types#float
601 (setf (uffi:get-slot-value binding 'mysql-bind 'mysql::buffer)
602 (uffi:allocate-foreign-object :float)))
603 (#.mysql-field-types#double
604 (setf (uffi:get-slot-value binding 'mysql-bind 'mysql::buffer)
605 (uffi:allocate-foreign-object :double)))
606 ((#.mysql-field-types#time #.mysql-field-types#date
607 #.mysql-field-types#datetime #.mysql-field-types#timestamp)
608 (uffi:allocate-foreign-object 'mysql-time))
610 (error "mysql type ~D not supported." type)))))
612 (unless (zerop (mysql-stmt-bind-result stmt output-bind))
613 (mysql-stmt-close stmt)
614 (error 'sql-database-error
615 :error-id (mysql-stmt-errno stmt)
616 :message (uffi:convert-from-cstring
617 (mysql-stmt-error stmt)))))
619 (make-instance 'mysql-stmt
620 :database database
621 :stmt stmt
622 :num-fields num-fields
623 :input-bind input-bind
624 :output-bind output-bind
625 :result-set rs
626 :result-types result-types
627 :length-ptr length-ptr
628 :is-null-ptr is-null-ptr
629 :types mysql-types
630 :field-names field-names)))))
632 #+mysql-client-v4.1
633 (defmethod database-bind-parameter ((stmt mysql-stmt) position value)
634 ;; FIXME: will need to allocate bind structure. This should probably be
635 ;; done in C since the API is not mature and may change
636 (let ((binding (uffi:deref-array (input-bind stmt) '(:array mysql-bind) (1- position)))
637 (type (nth (1- position) (types stmt))))
638 (setf (uffi:get-slot-value binding 'mysql-bind 'length) 0)
639 (cond
640 ((null value)
641 (when (is-null-ptr stmt)
642 (setf (uffi:deref-array (is-null-ptr stmt) '(:array :byte) (1- position)) 1)))
644 (when (is-null-ptr stmt)
645 (setf (uffi:deref-array (is-null-ptr stmt) '(:array :byte) (1- position)) 0))
646 (case type
647 (#.mysql-field-types#long
648 (setf (uffi:get-slot-value binding 'mysql-bind 'mysql::buffer) value))
650 (warn "Unknown input bind type ~D." type))
651 )))))
653 #+mysql-client-v4.1
654 (defmethod database-run-prepared ((stmt mysql-stmt))
655 (print 'a1)
656 (when (input-bind stmt)
657 (unless (zerop (mysql-stmt-bind-param (stmt stmt) (input-bind stmt)))
658 (error 'sql-database-error
659 :error-id (mysql-stmt-errno (stmt stmt))
660 :message (uffi:convert-from-cstring
661 (mysql-stmt-error (stmt stmt))))))
662 (print 'a2)
663 (unless (zerop (mysql-stmt-execute (stmt stmt)))
664 (error 'sql-database-error
665 :error-id (mysql-stmt-errno (stmt stmt))
666 :message (uffi:convert-from-cstring
667 (mysql-stmt-error (stmt stmt)))))
668 (print 'a3)
669 (unless (zerop (mysql-stmt-store-result (stmt stmt)))
670 (error 'sql-database-error
671 :error-id (mysql-stmt-errno (stmt stmt))
672 :message (uffi:convert-from-cstring
673 (mysql-stmt-error (stmt stmt)))))
674 (database-fetch-prepared-rows stmt))
676 #+mysql-client-v4.1
677 (defun database-fetch-prepared-rows (stmt)
678 (do ((rc (mysql-stmt-fetch (stmt stmt)) (mysql-stmt-fetch (stmt stmt)))
679 (num-fields (num-fields stmt))
680 (rows '()))
681 ((not (zerop rc)) (nreverse rows))
682 (push
683 (loop for i from 0 below num-fields
684 collect
685 (let ((is-null
686 (not (zerop (uffi:ensure-char-integer
687 (uffi:deref-array (is-null-ptr stmt) '(:array :byte) i))))))
688 (unless is-null
689 (let* ((bind (uffi:deref-array (output-bind stmt) '(:array mysql-bind) i))
690 (type (uffi:get-slot-value bind 'mysql-bind 'mysql::buffer-type))
691 (buffer (uffi:get-slot-value bind 'mysql-bind 'mysql::buffer)))
692 (case type
693 ((#.mysql-field-types#var-string #.mysql-field-types#string
694 #.mysql-field-types#tiny-blob #.mysql-field-types#blob
695 #.mysql-field-types#medium-blob #.mysql-field-types#long-blob)
696 (uffi:convert-from-foreign-string buffer))
697 (#.mysql-field-types#tiny
698 (uffi:ensure-char-integer
699 (uffi:deref-pointer buffer :byte)))
700 (#.mysql-field-types#short
701 (uffi:deref-pointer buffer :short))
702 (#.mysql-field-types#long
703 (uffi:deref-pointer buffer :int))
704 #+64bit
705 (#.mysql-field-types#longlong
706 (uffi:deref-pointer buffer :long))
707 (#.mysql-field-types#float
708 (uffi:deref-pointer buffer :float))
709 (#.mysql-field-types#double
710 (uffi:deref-pointer buffer :double))
711 ((#.mysql-field-types#time #.mysql-field-types#date
712 #.mysql-field-types#datetime #.mysql-field-types#timestamp)
713 (let ((year (uffi:get-slot-value buffer 'mysql-time 'mysql::year))
714 (month (uffi:get-slot-value buffer 'mysql-time 'mysql::month))
715 (day (uffi:get-slot-value buffer 'mysql-time 'mysql::day))
716 (hour (uffi:get-slot-value buffer 'mysql-time 'mysql::hour))
717 (minute (uffi:get-slot-value buffer 'mysql-time 'mysql::minute))
718 (second (uffi:get-slot-value buffer 'mysql-time 'mysql::second)))
719 (db-timestring
720 (make-time :year year :month month :day day :hour hour
721 :minute minute :second second))))
723 (list type)))))))
724 rows)))
729 #+mysql-client-v4.1
730 (defmethod database-free-prepared ((stmt mysql-stmt))
731 (with-slots (stmt) stmt
732 (mysql-stmt-close stmt))
736 ;;; Database capabilities
738 (defmethod db-type-use-column-on-drop-index? ((db-type (eql :mysql)))
741 (defmethod db-type-has-views? ((db-type (eql :mysql)))
742 #+mysql-client-v5 t
743 #-mysql-client-v5 nil)
745 (defmethod db-type-has-subqueries? ((db-type (eql :mysql)))
746 #+(or mysql-client-v4.1 mysql-client-v5) t
747 #-(or mysql-client-v4.1 mysql-client-v5) nil)
749 (defmethod db-type-has-boolean-where? ((db-type (eql :mysql)))
750 #+(or mysql-client-v4.1 mysql-client-v5) t
751 #-(or mysql-client-v4.1 mysql-client-v5) nil)
753 (defmethod db-type-has-union? ((db-type (eql :mysql)))
754 (not (eql (schar mysql::*mysql-client-info* 0) #\3)))
756 (defmethod db-type-transaction-capable? ((db-type (eql :mysql)) database)
757 (let ((tuple (car (database-query "SHOW VARIABLES LIKE 'HAVE_INNODB'" database :auto nil))))
758 (and tuple (string-equal "YES" (second tuple)))))
760 (defmethod db-type-has-prepared-stmt? ((db-type (eql :mysql)))
761 #+(or mysql-client-v4.1 mysql-client-v5) t
762 #-(or mysql-client-v4.1 mysql-client-v5) nil)
764 (when (clsql-sys:database-type-library-loaded :mysql)
765 (clsql-sys:initialize-database-type :database-type :mysql))