Version 4.0.1
[clsql/s11.git] / db-mysql / mysql-sql.lisp
blob36fc90a9d25525cfaec3336f43985f0fd33ddda0
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 (let* ((db
160 (make-instance 'mysql-database
161 :name (database-name-from-spec connection-spec
162 database-type)
163 :database-type :mysql
164 :connection-spec connection-spec
165 :server-info (uffi:convert-from-cstring
166 (mysql:mysql-get-server-info mysql-ptr))
167 :mysql-ptr mysql-ptr))
168 (cmd "SET SESSION sql_mode='ANSI'"))
169 (uffi:with-cstring (cmd-cs cmd)
170 (if (zerop (mysql-real-query mysql-ptr cmd-cs (expression-length cmd)))
172 (progn
173 (warn "Error setting ANSI mode for MySQL.")
174 db)))))
175 (when error-occurred (mysql-close mysql-ptr)))))))))
178 (defmethod database-disconnect ((database mysql-database))
179 (mysql-close (database-mysql-ptr database))
180 (setf (database-mysql-ptr database) nil)
184 (defmethod database-query (query-expression (database mysql-database)
185 result-types field-names)
186 (declare (optimize (speed 3) (safety 0) (debug 0) (space 0)))
187 (let ((mysql-ptr (database-mysql-ptr database)))
188 (uffi:with-cstring (query-native query-expression)
189 (if (zerop (mysql-real-query mysql-ptr query-native
190 (expression-length query-expression)))
191 (let ((res-ptr (mysql-use-result mysql-ptr)))
192 (if res-ptr
193 (unwind-protect
194 (let ((num-fields (mysql-num-fields res-ptr)))
195 (declare (fixnum num-fields))
196 (setq result-types (canonicalize-types
197 result-types num-fields
198 res-ptr))
199 (values
200 (loop for row = (mysql-fetch-row res-ptr)
201 for lengths = (mysql-fetch-lengths res-ptr)
202 until (uffi:null-pointer-p row)
203 collect
204 (do* ((rlist (make-list num-fields))
205 (i 0 (1+ i))
206 (pos rlist (cdr pos)))
207 ((= i num-fields) rlist)
208 (declare (fixnum i))
209 (setf (car pos)
210 (convert-raw-field
211 (uffi:deref-array row '(:array
212 (* :unsigned-char))
214 result-types i
215 (uffi:deref-array lengths '(:array :unsigned-long)
216 i)))))
217 (when field-names
218 (result-field-names num-fields res-ptr))))
219 (mysql-free-result res-ptr))
220 (error 'sql-database-data-error
221 :database database
222 :expression query-expression
223 :error-id (mysql-errno mysql-ptr)
224 :message (mysql-error-string mysql-ptr))))
225 (error 'sql-database-data-error
226 :database database
227 :expression query-expression
228 :error-id (mysql-errno mysql-ptr)
229 :message (mysql-error-string mysql-ptr))))))
231 (defmethod database-execute-command (sql-expression (database mysql-database))
232 (uffi:with-cstring (sql-native sql-expression)
233 (let ((mysql-ptr (database-mysql-ptr database)))
234 (declare (type mysql-mysql-ptr-def mysql-ptr))
235 (if (zerop (mysql-real-query mysql-ptr sql-native
236 (expression-length sql-expression)))
238 (error 'sql-database-data-error
239 :database database
240 :expression sql-expression
241 :error-id (mysql-errno mysql-ptr)
242 :message (mysql-error-string mysql-ptr))))))
245 (defstruct mysql-result-set
246 (res-ptr (uffi:make-null-pointer 'mysql-mysql-res) :type mysql-mysql-res-ptr-def)
247 (types nil :type list)
248 (num-fields 0 :type fixnum)
249 (full-set nil :type boolean))
252 (defmethod database-query-result-set ((query-expression string)
253 (database mysql-database)
254 &key full-set result-types)
255 (uffi:with-cstring (query-native query-expression)
256 (let ((mysql-ptr (database-mysql-ptr database)))
257 (declare (type mysql-mysql-ptr-def mysql-ptr))
258 (if (zerop (mysql-real-query mysql-ptr query-native
259 (expression-length query-expression)))
260 (let ((res-ptr (if full-set
261 (mysql-store-result mysql-ptr)
262 (mysql-use-result mysql-ptr))))
263 (declare (type mysql-mysql-res-ptr-def res-ptr))
264 (if (not (uffi:null-pointer-p res-ptr))
265 (let* ((num-fields (mysql-num-fields res-ptr))
266 (result-set (make-mysql-result-set
267 :res-ptr res-ptr
268 :num-fields num-fields
269 :full-set full-set
270 :types
271 (canonicalize-types
272 result-types num-fields
273 res-ptr))))
274 (if full-set
275 (values result-set
276 num-fields
277 (mysql-num-rows res-ptr))
278 (values result-set
279 num-fields)))
280 (error 'sql-database-data-error
281 :database database
282 :expression query-expression
283 :error-id (mysql-errno mysql-ptr)
284 :message (mysql-error-string mysql-ptr))))
285 (error 'sql-database-data-error
286 :database database
287 :expression query-expression
288 :error-id (mysql-errno mysql-ptr)
289 :message (mysql-error-string mysql-ptr))))))
291 (defmethod database-dump-result-set (result-set (database mysql-database))
292 (mysql-free-result (mysql-result-set-res-ptr result-set))
296 (defmethod database-store-next-row (result-set (database mysql-database) list)
297 (let* ((res-ptr (mysql-result-set-res-ptr result-set))
298 (row (mysql-fetch-row res-ptr))
299 (lengths (mysql-fetch-lengths res-ptr))
300 (types (mysql-result-set-types result-set)))
301 (declare (type mysql-mysql-res-ptr-def res-ptr)
302 (type mysql-row-def row))
303 (unless (uffi:null-pointer-p row)
304 (loop for i from 0 below (mysql-result-set-num-fields result-set)
305 for rest on list
307 (setf (car rest)
308 (convert-raw-field
309 (uffi:deref-array row '(:array (* :unsigned-char)) i)
310 types
312 (uffi:deref-array lengths '(:array :unsigned-long) i))))
313 list)))
316 ;; Table and attribute introspection
318 (defmethod database-list-tables ((database mysql-database) &key (owner nil))
319 (declare (ignore owner))
320 (cond
321 ((eql #\5 (char (database-server-info database) 0))
322 (loop for (name type) in (database-query "SHOW FULL TABLES" database nil nil)
323 when (and (string-equal type "base table")
324 (not (and (>= (length name) 11)
325 (string-equal (subseq name 0 11) "_CLSQL_SEQ_"))))
326 collect name))
328 (remove-if #'(lambda (s)
329 (and (>= (length s) 11)
330 (string-equal (subseq s 0 11) "_CLSQL_SEQ_")))
331 (mapcar #'car (database-query "SHOW TABLES" database nil nil))))))
333 (defmethod database-list-views ((database mysql-database)
334 &key (owner nil))
335 (declare (ignore owner))
336 (cond
337 ((eql #\5 (char (database-server-info database) 0))
338 (loop for (name type) in (database-query "SHOW FULL TABLES" database nil nil)
339 when (string-equal type "view")
340 collect name))
342 nil)))
344 (defmethod database-list-indexes ((database mysql-database)
345 &key (owner nil))
346 (let ((result '()))
347 (dolist (table (database-list-tables database :owner owner) result)
348 (setq result
349 (append (database-list-table-indexes table database :owner owner)
350 result)))))
352 (defmethod database-list-table-indexes (table (database mysql-database)
353 &key (owner nil))
354 (declare (ignore owner))
355 (do ((results nil)
356 (rows (database-query
357 (format nil "SHOW INDEX FROM ~A" table)
358 database nil nil)
359 (cdr rows)))
360 ((null rows) (nreverse results))
361 (let ((col (nth 2 (car rows))))
362 (unless (find col results :test #'string-equal)
363 (push col results)))))
365 (defmethod database-list-attributes ((table string) (database mysql-database)
366 &key (owner nil))
367 (declare (ignore owner))
368 (mapcar #'car
369 (database-query
370 (format nil "SHOW COLUMNS FROM ~A" table)
371 database nil nil)))
373 (defmethod database-attribute-type (attribute (table string)
374 (database mysql-database)
375 &key (owner nil))
376 (declare (ignore owner))
377 (let ((row (car (database-query
378 (format nil
379 "SHOW COLUMNS FROM ~A LIKE '~A'" table attribute)
380 database nil nil))))
381 (let* ((raw-type (second row))
382 (null (third row))
383 (start-length (position #\( raw-type))
384 (type (if start-length
385 (subseq raw-type 0 start-length)
386 raw-type))
387 (length (when start-length
388 (parse-integer (subseq raw-type (1+ start-length))
389 :junk-allowed t))))
390 (when type
391 (values (ensure-keyword type) length nil (if (string-equal null "YES") 1 0))))))
393 ;;; Sequence functions
395 (defun %sequence-name-to-table (sequence-name)
396 (concatenate 'string "_CLSQL_SEQ_" (sql-escape sequence-name)))
398 (defun %table-name-to-sequence-name (table-name)
399 (and (>= (length table-name) 11)
400 (string-equal (subseq table-name 0 11) "_CLSQL_SEQ_")
401 (subseq table-name 11)))
403 (defmethod database-create-sequence (sequence-name
404 (database mysql-database))
405 (let ((table-name (%sequence-name-to-table sequence-name)))
406 (database-execute-command
407 (concatenate 'string "CREATE TABLE " table-name
408 " (id int NOT NULL PRIMARY KEY AUTO_INCREMENT)")
409 database)
410 (database-execute-command
411 (concatenate 'string "INSERT INTO " table-name
412 " VALUES (-1)")
413 database)))
415 (defmethod database-drop-sequence (sequence-name
416 (database mysql-database))
417 (database-execute-command
418 (concatenate 'string "DROP TABLE " (%sequence-name-to-table sequence-name))
419 database))
421 (defmethod database-list-sequences ((database mysql-database)
422 &key (owner nil))
423 (declare (ignore owner))
424 (mapcan #'(lambda (s)
425 (let ((sn (%table-name-to-sequence-name (car s))))
426 (and sn (list sn))))
427 (database-query "SHOW TABLES" database nil nil)))
429 (defmethod database-set-sequence-position (sequence-name
430 (position integer)
431 (database mysql-database))
432 (database-execute-command
433 (format nil "UPDATE ~A SET id=~A" (%sequence-name-to-table sequence-name)
434 position)
435 database)
436 (mysql:mysql-insert-id (clsql-mysql::database-mysql-ptr database)))
438 (defmethod database-sequence-next (sequence-name (database mysql-database))
439 (without-interrupts
440 (database-execute-command
441 (concatenate 'string "UPDATE " (%sequence-name-to-table sequence-name)
442 " SET id=LAST_INSERT_ID(id+1)")
443 database)
444 (mysql:mysql-insert-id (clsql-mysql::database-mysql-ptr database))))
446 (defmethod database-sequence-last (sequence-name (database mysql-database))
447 (without-interrupts
448 (caar (database-query
449 (concatenate 'string "SELECT id from "
450 (%sequence-name-to-table sequence-name))
451 database :auto nil))))
453 (defmethod database-create (connection-spec (type (eql :mysql)))
454 (destructuring-bind (host name user password) connection-spec
455 (let ((database (database-connect (list host "" user password)
456 type)))
457 (setf (slot-value database 'clsql-sys::state) :open)
458 (unwind-protect
459 (database-execute-command (format nil "create database ~A" name) database)
460 (database-disconnect database)))))
462 (defmethod database-destroy (connection-spec (type (eql :mysql)))
463 (destructuring-bind (host name user password) connection-spec
464 (let ((database (database-connect (list host "" user password)
465 type)))
466 (setf (slot-value database 'clsql-sys::state) :open)
467 (unwind-protect
468 (database-execute-command (format nil "drop database ~A" name) database)
469 (database-disconnect database)))))
471 (defmethod database-probe (connection-spec (type (eql :mysql)))
472 (when (find (second connection-spec) (database-list connection-spec type)
473 :test #'string-equal)
476 (defmethod database-list (connection-spec (type (eql :mysql)))
477 (destructuring-bind (host name user password &optional port) connection-spec
478 (declare (ignore name))
479 (let ((database (database-connect (list host "mysql" user password port) type)))
480 (unwind-protect
481 (progn
482 (setf (slot-value database 'clsql-sys::state) :open)
483 (mapcar #'car (database-query "show databases" database :auto nil)))
484 (progn
485 (database-disconnect database)
486 (setf (slot-value database 'clsql-sys::state) :closed))))))
489 ;;; Prepared statements
491 (defclass mysql-stmt ()
492 ((database :initarg :database :reader database)
493 (stmt :initarg :stmt :accessor stmt)
494 (input-bind :initarg :input-bind :reader input-bind)
495 (output-bind :initarg :output-bind :reader output-bind)
496 (types :initarg :types :reader types)
497 (result-set :initarg :result-set :reader result-set)
498 (num-fields :initarg :num-fields :reader num-fields)
499 (field-names :initarg :field-names :accessor stmt-field-names)
500 (length-ptr :initarg :length-ptr :reader length-ptr)
501 (is-null-ptr :initarg :is-null-ptr :reader is-null-ptr)
502 (result-types :initarg :result-types :reader result-types)))
504 (defun clsql-type->mysql-type (type)
505 (cond
506 ((in type :null) mysql-field-types#null)
507 ((in type :int :integer) mysql-field-types#long)
508 ((in type :short) mysql-field-types#short)
509 ((in type :bigint) mysql-field-types#longlong)
510 ((in type :float :double :number) mysql-field-types#double)
511 ((and (consp type) (in (car type) :char :string :varchar)) mysql-field-types#var-string)
512 ((or (eq type :blob) (and (consp type) (in (car type) :blob))) mysql-field-types#var-string)
514 (error 'sql-user-error
515 :message
516 (format nil "Unknown clsql type ~A." type)))))
518 #+mysql-client-v4.1
519 (defmethod database-prepare (sql-stmt types (database mysql-database) result-types field-names)
520 (let* ((mysql-ptr (database-mysql-ptr database))
521 (stmt (mysql-stmt-init mysql-ptr)))
522 (when (uffi:null-pointer-p stmt)
523 (error 'sql-database-error
524 :error-id (mysql-errno mysql-ptr)
525 :message (mysql-error-string mysql-ptr)))
527 (uffi:with-cstring (native-query sql-stmt)
528 (unless (zerop (mysql-stmt-prepare stmt native-query (expression-length sql-stmt)))
529 (mysql-stmt-close stmt)
530 (error 'sql-database-error
531 :error-id (mysql-errno mysql-ptr)
532 :message (mysql-error-string mysql-ptr))))
534 (unless (= (mysql-stmt-param-count stmt) (length types))
535 (mysql-stmt-close stmt)
536 (error 'sql-database-error
537 :message
538 (format nil "Mysql param count (~D) does not match number of types (~D)"
539 (mysql-stmt-param-count stmt) (length types))))
541 (let ((rs (mysql-stmt-result-metadata stmt)))
542 (when (uffi:null-pointer-p rs)
543 (warn "mysql_stmt_result_metadata returned NULL")
544 #+nil
545 (mysql-stmt-close stmt)
546 #+nil
547 (error 'sql-database-error
548 :message "mysql_stmt_result_metadata returned NULL"))
550 (let ((input-bind (uffi:allocate-foreign-object 'mysql-bind (length types)))
551 (mysql-types (mapcar 'clsql-type->mysql-type types))
552 field-vec num-fields is-null-ptr output-bind length-ptr)
554 (print 'a)
555 (dotimes (i (length types))
556 (let* ((binding (uffi:deref-array input-bind '(:array mysql-bind) i)))
557 (setf (uffi:get-slot-value binding 'mysql-bind 'mysql::buffer-type)
558 (nth i mysql-types))
559 (setf (uffi:get-slot-value binding 'mysql-bind 'mysql::buffer-length) 0)))
561 (print 'b)
562 (unless (uffi:null-pointer-p rs)
563 (setq field-vec (mysql-fetch-fields rs)
564 num-fields (mysql-num-fields rs)
565 is-null-ptr (uffi:allocate-foreign-object :byte num-fields)
566 output-bind (uffi:allocate-foreign-object 'mysql-bind num-fields)
567 length-ptr (uffi:allocate-foreign-object :unsigned-long num-fields))
568 (dotimes (i num-fields)
569 (declare (fixnum i))
570 (let* ((field (uffi:deref-array field-vec '(:array mysql-field) i))
571 (type (uffi:get-slot-value field 'mysql-field 'type))
572 (binding (uffi:deref-array output-bind '(:array mysql-bind) i)))
573 (setf (uffi:get-slot-value binding 'mysql-bind 'mysql::buffer-type) type)
575 (setf (uffi:get-slot-value binding 'mysql-bind 'mysql::buffer-length) 0)
576 #+need-to-allocate-foreign-object-for-this
577 (setf (uffi:get-slot-value binding 'mysql-bind 'mysql::is-null)
578 (+ i (uffi:pointer-address is-null-ptr)))
579 #+need-to-allocate-foreign-object-for-this
580 (setf (uffi:get-slot-value binding 'mysql-bind 'length)
581 (+ (* i 8) (uffi:pointer-address length-ptr)))
583 (case type
584 ((#.mysql-field-types#var-string #.mysql-field-types#string
585 #.mysql-field-types#tiny-blob #.mysql-field-types#blob
586 #.mysql-field-types#medium-blob #.mysql-field-types#long-blob)
587 (setf (uffi:get-slot-value binding 'mysql-bind 'mysql::buffer-length) 1024)
588 (setf (uffi:get-slot-value binding 'mysql-bind 'mysql::buffer)
589 (uffi:allocate-foreign-object :unsigned-char 1024)))
590 (#.mysql-field-types#tiny
591 (setf (uffi:get-slot-value binding 'mysql-bind 'mysql::buffer)
592 (uffi:allocate-foreign-object :byte)))
593 (#.mysql-field-types#short
594 (setf (uffi:get-slot-value binding 'mysql-bind 'mysql::buffer)
595 (uffi:allocate-foreign-object :short)))
596 (#.mysql-field-types#long
597 (setf (uffi:get-slot-value binding 'mysql-bind 'mysql::buffer)
598 ;; segfaults if supply :int on amd64
599 (uffi:allocate-foreign-object :long)))
600 #+64bit
601 (#.mysql-field-types#longlong
602 (setf (uffi:get-slot-value binding 'mysql-bind 'mysql::buffer)
603 (uffi:allocate-foreign-object :long)))
604 (#.mysql-field-types#float
605 (setf (uffi:get-slot-value binding 'mysql-bind 'mysql::buffer)
606 (uffi:allocate-foreign-object :float)))
607 (#.mysql-field-types#double
608 (setf (uffi:get-slot-value binding 'mysql-bind 'mysql::buffer)
609 (uffi:allocate-foreign-object :double)))
610 ((#.mysql-field-types#time #.mysql-field-types#date
611 #.mysql-field-types#datetime #.mysql-field-types#timestamp)
612 (uffi:allocate-foreign-object 'mysql-time))
614 (error "mysql type ~D not supported." type)))))
616 (unless (zerop (mysql-stmt-bind-result stmt output-bind))
617 (mysql-stmt-close stmt)
618 (error 'sql-database-error
619 :error-id (mysql-stmt-errno stmt)
620 :message (uffi:convert-from-cstring
621 (mysql-stmt-error stmt)))))
623 (make-instance 'mysql-stmt
624 :database database
625 :stmt stmt
626 :num-fields num-fields
627 :input-bind input-bind
628 :output-bind output-bind
629 :result-set rs
630 :result-types result-types
631 :length-ptr length-ptr
632 :is-null-ptr is-null-ptr
633 :types mysql-types
634 :field-names field-names)))))
636 #+mysql-client-v4.1
637 (defmethod database-bind-parameter ((stmt mysql-stmt) position value)
638 ;; FIXME: will need to allocate bind structure. This should probably be
639 ;; done in C since the API is not mature and may change
640 (let ((binding (uffi:deref-array (input-bind stmt) '(:array mysql-bind) (1- position)))
641 (type (nth (1- position) (types stmt))))
642 (setf (uffi:get-slot-value binding 'mysql-bind 'length) 0)
643 (cond
644 ((null value)
645 (when (is-null-ptr stmt)
646 (setf (uffi:deref-array (is-null-ptr stmt) '(:array :byte) (1- position)) 1)))
648 (when (is-null-ptr stmt)
649 (setf (uffi:deref-array (is-null-ptr stmt) '(:array :byte) (1- position)) 0))
650 (case type
651 (#.mysql-field-types#long
652 (setf (uffi:get-slot-value binding 'mysql-bind 'mysql::buffer) value))
654 (warn "Unknown input bind type ~D." type))
655 )))))
657 #+mysql-client-v4.1
658 (defmethod database-run-prepared ((stmt mysql-stmt))
659 (print 'a1)
660 (when (input-bind stmt)
661 (unless (zerop (mysql-stmt-bind-param (stmt stmt) (input-bind 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 (print 'a2)
667 (unless (zerop (mysql-stmt-execute (stmt stmt)))
668 (error 'sql-database-error
669 :error-id (mysql-stmt-errno (stmt stmt))
670 :message (uffi:convert-from-cstring
671 (mysql-stmt-error (stmt stmt)))))
672 (print 'a3)
673 (unless (zerop (mysql-stmt-store-result (stmt stmt)))
674 (error 'sql-database-error
675 :error-id (mysql-stmt-errno (stmt stmt))
676 :message (uffi:convert-from-cstring
677 (mysql-stmt-error (stmt stmt)))))
678 (database-fetch-prepared-rows stmt))
680 #+mysql-client-v4.1
681 (defun database-fetch-prepared-rows (stmt)
682 (do ((rc (mysql-stmt-fetch (stmt stmt)) (mysql-stmt-fetch (stmt stmt)))
683 (num-fields (num-fields stmt))
684 (rows '()))
685 ((not (zerop rc)) (nreverse rows))
686 (push
687 (loop for i from 0 below num-fields
688 collect
689 (let ((is-null
690 (not (zerop (uffi:ensure-char-integer
691 (uffi:deref-array (is-null-ptr stmt) '(:array :byte) i))))))
692 (unless is-null
693 (let* ((bind (uffi:deref-array (output-bind stmt) '(:array mysql-bind) i))
694 (type (uffi:get-slot-value bind 'mysql-bind 'mysql::buffer-type))
695 (buffer (uffi:get-slot-value bind 'mysql-bind 'mysql::buffer)))
696 (case type
697 ((#.mysql-field-types#var-string #.mysql-field-types#string
698 #.mysql-field-types#tiny-blob #.mysql-field-types#blob
699 #.mysql-field-types#medium-blob #.mysql-field-types#long-blob)
700 (uffi:convert-from-foreign-string buffer))
701 (#.mysql-field-types#tiny
702 (uffi:ensure-char-integer
703 (uffi:deref-pointer buffer :byte)))
704 (#.mysql-field-types#short
705 (uffi:deref-pointer buffer :short))
706 (#.mysql-field-types#long
707 (uffi:deref-pointer buffer :int))
708 #+64bit
709 (#.mysql-field-types#longlong
710 (uffi:deref-pointer buffer :long))
711 (#.mysql-field-types#float
712 (uffi:deref-pointer buffer :float))
713 (#.mysql-field-types#double
714 (uffi:deref-pointer buffer :double))
715 ((#.mysql-field-types#time #.mysql-field-types#date
716 #.mysql-field-types#datetime #.mysql-field-types#timestamp)
717 (let ((year (uffi:get-slot-value buffer 'mysql-time 'mysql::year))
718 (month (uffi:get-slot-value buffer 'mysql-time 'mysql::month))
719 (day (uffi:get-slot-value buffer 'mysql-time 'mysql::day))
720 (hour (uffi:get-slot-value buffer 'mysql-time 'mysql::hour))
721 (minute (uffi:get-slot-value buffer 'mysql-time 'mysql::minute))
722 (second (uffi:get-slot-value buffer 'mysql-time 'mysql::second)))
723 (db-timestring
724 (make-time :year year :month month :day day :hour hour
725 :minute minute :second second))))
727 (list type)))))))
728 rows)))
733 #+mysql-client-v4.1
734 (defmethod database-free-prepared ((stmt mysql-stmt))
735 (with-slots (stmt) stmt
736 (mysql-stmt-close stmt))
740 ;;; Database capabilities
742 (defmethod db-type-use-column-on-drop-index? ((db-type (eql :mysql)))
745 (defmethod db-type-has-views? ((db-type (eql :mysql)))
746 #+mysql-client-v5 t
747 #-mysql-client-v5 nil)
749 (defmethod db-type-has-subqueries? ((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-boolean-where? ((db-type (eql :mysql)))
754 #+(or mysql-client-v4.1 mysql-client-v5) t
755 #-(or mysql-client-v4.1 mysql-client-v5) nil)
757 (defmethod db-type-has-union? ((db-type (eql :mysql)))
758 (not (eql (schar mysql::*mysql-client-info* 0) #\3)))
760 (defmethod db-type-transaction-capable? ((db-type (eql :mysql)) database)
761 (let ((tuple (car (database-query "SHOW VARIABLES LIKE 'HAVE_INNODB'" database :auto nil))))
762 (and tuple (string-equal "YES" (second tuple)))))
764 (defmethod db-type-has-prepared-stmt? ((db-type (eql :mysql)))
765 #+(or mysql-client-v4.1 mysql-client-v5) t
766 #-(or mysql-client-v4.1 mysql-client-v5) nil)
768 (when (clsql-sys:database-type-library-loaded :mysql)
769 (clsql-sys:initialize-database-type :database-type :mysql))