X-Git-Url: http://git.kpe.io/?p=clsql.git;a=blobdiff_plain;f=db-mysql%2Fmysql-sql.lisp;h=cf85c591e8645d4da4374548e73caa077995b163;hp=c33456c6050fdc33f716b134aaef30c2ecef43ff;hb=8a8ee2d7d791b7a3efaed06420802a925d16fca3;hpb=02c30e92614a52c892cc678933586f71c76fcf30 diff --git a/db-mysql/mysql-sql.lisp b/db-mysql/mysql-sql.lisp index c33456c..cf85c59 100644 --- a/db-mysql/mysql-sql.lisp +++ b/db-mysql/mysql-sql.lisp @@ -14,7 +14,7 @@ ;;;; ************************************************************************* (defpackage #:clsql-mysql - (:use #:common-lisp #:clsql-base-sys #:mysql #:clsql-uffi) + (:use #:common-lisp #:clsql-sys #:mysql #:clsql-uffi) (:export #:mysql-database) (:documentation "This is the CLSQL interface to MySQL.")) @@ -22,14 +22,25 @@ ;;; Field conversion functions +(defun result-field-names (num-fields res-ptr) + (declare (fixnum num-fields)) + (let ((names '()) + (field-vec (mysql-fetch-fields res-ptr))) + (dotimes (i num-fields) + (declare (fixnum i)) + (let* ((field (uffi:deref-array field-vec '(:array mysql-field) i)) + (name (uffi:convert-from-foreign-string + (uffi:get-slot-value field 'mysql-field 'mysql::name)))) + (push name names))) + (nreverse names))) + (defun make-type-list-for-auto (num-fields res-ptr) (declare (fixnum num-fields)) (let ((new-types '()) - #+ignore (field-vec (mysql-fetch-fields res-ptr))) + (field-vec (mysql-fetch-fields res-ptr))) (dotimes (i num-fields) (declare (fixnum i)) - (let* ( (field (mysql-fetch-field-direct res-ptr i)) - #+ignore (field (uffi:deref-array field-vec '(:array mysql-field) i)) + (let* ((field (uffi:deref-array field-vec '(:array mysql-field) i)) (type (uffi:get-slot-value field 'mysql-field 'type))) (push (case type @@ -78,7 +89,9 @@ (check-connection-spec connection-spec database-type (host db user password)) (destructuring-bind (host db user password) connection-spec (declare (ignore password)) - (concatenate 'string host "/" db "/" user))) + (concatenate 'string + (if host host "localhost") + "/" db "/" user))) (defmethod database-connect (connection-spec (database-type (eql :mysql))) (check-connection-spec connection-spec database-type (host db user password)) @@ -112,6 +125,7 @@ (make-instance 'mysql-database :name (database-name-from-spec connection-spec database-type) + :database-type :mysql :connection-spec connection-spec :mysql-ptr mysql-ptr)) (when error-occurred (mysql-close mysql-ptr))))))))) @@ -124,7 +138,7 @@ (defmethod database-query (query-expression (database mysql-database) - types) + result-types field-names) (declare (optimize (speed 3) (safety 0) (debug 0) (space 0))) (let ((mysql-ptr (database-mysql-ptr database))) (uffi:with-cstring (query-native query-expression) @@ -135,63 +149,29 @@ (unwind-protect (let ((num-fields (mysql-num-fields res-ptr))) (declare (fixnum num-fields)) - (setq types (canonicalize-types - types num-fields - res-ptr)) - (loop for row = (mysql-fetch-row res-ptr) - for lengths = (mysql-fetch-lengths res-ptr) - until (uffi:null-pointer-p row) - collect - (do* ((rlist (make-list num-fields)) - (i 0 (1+ i)) - (pos rlist (cdr pos))) - ((= i num-fields) rlist) - (declare (fixnum i)) - (setf (car pos) - (convert-raw-field - (uffi:deref-array row '(:array - (* :unsigned-char)) - i) - types i - (uffi:deref-array lengths '(:array :unsigned-long) - i)))))) - (mysql-free-result res-ptr)) - (error 'clsql-sql-error - :database database - :expression query-expression - :errno (mysql-errno mysql-ptr) - :error (mysql-error-string mysql-ptr)))) - (error 'clsql-sql-error - :database database - :expression query-expression - :errno (mysql-errno mysql-ptr) - :error (mysql-error-string mysql-ptr)))))) - -#+ignore -(defmethod database-query (query-expression (database mysql-database) - types) - (declare (optimize (speed 3) (safety 0) (debug 0) (space 0))) - (let ((mysql-ptr (database-mysql-ptr database))) - (uffi:with-cstring (query-native query-expression) - (if (zerop (mysql-query mysql-ptr query-native)) - (let ((res-ptr (mysql-use-result mysql-ptr))) - (if res-ptr - (unwind-protect - (let ((num-fields (mysql-num-fields res-ptr))) - (declare (fixnum num-fields)) - (setq types (canonicalize-types - types num-fields + (setq result-types (canonicalize-types + result-types num-fields res-ptr)) - (loop for row = (mysql-fetch-row res-ptr) - until (uffi:null-pointer-p row) - collect - (loop for i fixnum from 0 below num-fields - collect - (convert-raw-field - (uffi:deref-array row '(:array - (* :unsigned-char)) - i) - types i)))) + (values + (loop for row = (mysql-fetch-row res-ptr) + for lengths = (mysql-fetch-lengths res-ptr) + until (uffi:null-pointer-p row) + collect + (do* ((rlist (make-list num-fields)) + (i 0 (1+ i)) + (pos rlist (cdr pos))) + ((= i num-fields) rlist) + (declare (fixnum i)) + (setf (car pos) + (convert-raw-field + (uffi:deref-array row '(:array + (* :unsigned-char)) + i) + result-types i + (uffi:deref-array lengths '(:array :unsigned-long) + i))))) + (when field-names + (result-field-names num-fields res-ptr)))) (mysql-free-result res-ptr)) (error 'clsql-sql-error :database database @@ -227,7 +207,7 @@ (defmethod database-query-result-set ((query-expression string) (database mysql-database) - &key full-set types) + &key full-set result-types) (uffi:with-cstring (query-native query-expression) (let ((mysql-ptr (database-mysql-ptr database))) (declare (type mysql-mysql-ptr-def mysql-ptr)) @@ -245,7 +225,7 @@ :full-set full-set :types (canonicalize-types - types num-fields + result-types num-fields res-ptr)))) (if full-set (values result-set @@ -285,7 +265,7 @@ (uffi:deref-array row '(:array (* :unsigned-char)) i) types i - (uffi:deref-array lengths :unsigned-long i)))) + (uffi:deref-array lengths '(:array :unsigned-long) i)))) list))) @@ -294,9 +274,9 @@ (defmethod database-list-tables ((database mysql-database) &key (owner nil)) (declare (ignore owner)) (remove-if #'(lambda (s) - (and (>= (length s) 10) - (string= (subseq s 0 10) "_clsql_seq_"))) - (mapcar #'car (database-query "SHOW TABLES" database nil)))) + (and (>= (length s) 11) + (string-equal (subseq s 0 11) "_CLSQL_SEQ_"))) + (mapcar #'car (database-query "SHOW TABLES" database nil nil)))) ;; MySQL 4.1 does not support views (defmethod database-list-views ((database mysql-database) @@ -308,10 +288,22 @@ &key (owner nil)) (let ((result '())) (dolist (table (database-list-tables database :owner owner) result) - (mapc #'(lambda (index) (push (nth 2 index) result)) - (database-query - (format nil "SHOW INDEX FROM ~A" (string-upcase table)) - database nil))))) + (setq result + (append (database-list-table-indexes table database :owner owner) + result))))) + +(defmethod database-list-table-indexes (table (database mysql-database) + &key (owner nil)) + (declare (ignore owner)) + (do ((results nil) + (rows (database-query + (format nil "SHOW INDEX FROM ~A" (string-upcase table)) + database nil nil) + (cdr rows))) + ((null rows) (nreverse results)) + (let ((col (nth 2 (car rows)))) + (unless (find col results :test #'string-equal) + (push col results))))) (defmethod database-list-attributes ((table string) (database mysql-database) &key (owner nil)) @@ -319,33 +311,37 @@ (mapcar #'car (database-query (format nil "SHOW COLUMNS FROM ~A" table) - database nil))) + database nil nil))) (defmethod database-attribute-type (attribute (table string) (database mysql-database) &key (owner nil)) (declare (ignore owner)) - (let ((result - (mapcar #'cadr - (database-query - (format nil - "SHOW COLUMNS FROM ~A LIKE '~A'" table attribute) - database nil)))) - (let* ((str (car result)) - (end-str (position #\( str)) - (substr (subseq str 0 end-str))) - (if substr - (intern (string-upcase substr) :keyword) nil)))) + (let ((row (car (database-query + (format nil + "SHOW COLUMNS FROM ~A LIKE '~A'" table attribute) + database nil nil)))) + (let* ((raw-type (second row)) + (null (third row)) + (start-length (position #\( raw-type)) + (type (if start-length + (subseq raw-type 0 start-length) + raw-type)) + (length (when start-length + (parse-integer (subseq raw-type (1+ start-length)) + :junk-allowed t)))) + (when type + (values (ensure-keyword type) length nil (if (string-equal null "YES") 1 0)))))) ;;; Sequence functions (defun %sequence-name-to-table (sequence-name) - (concatenate 'string "_clsql_seq_" (sql-escape sequence-name))) + (concatenate 'string "_CLSQL_SEQ_" (sql-escape sequence-name))) (defun %table-name-to-sequence-name (table-name) - (and (>= (length table-name) 10) - (string= (subseq table-name 0 10) "_clsql_seq_") - (subseq table-name 10))) + (and (>= (length table-name) 11) + (string-equal (subseq table-name 0 11) "_CLSQL_SEQ_") + (subseq table-name 11))) (defmethod database-create-sequence (sequence-name (database mysql-database)) @@ -368,9 +364,10 @@ (defmethod database-list-sequences ((database mysql-database) &key (owner nil)) (declare (ignore owner)) - (mapcar #'(lambda (s) (%table-name-to-sequence-name (car s))) - (database-query "SHOW TABLES LIKE '%clsql_seq%'" - database nil))) + (mapcan #'(lambda (s) + (let ((sn (%table-name-to-sequence-name (car s)))) + (and sn (list sn)))) + (database-query "SHOW TABLES" database nil nil))) (defmethod database-set-sequence-position (sequence-name (position integer) @@ -382,37 +379,85 @@ (mysql:mysql-insert-id (clsql-mysql::database-mysql-ptr database))) (defmethod database-sequence-next (sequence-name (database mysql-database)) - (database-execute-command - (concatenate 'string "UPDATE " (%sequence-name-to-table sequence-name) - " SET id=LAST_INSERT_ID(id+1)") - database) - (mysql:mysql-insert-id (clsql-mysql::database-mysql-ptr database))) + (without-interrupts + (database-execute-command + (concatenate 'string "UPDATE " (%sequence-name-to-table sequence-name) + " SET id=LAST_INSERT_ID(id+1)") + database) + (mysql:mysql-insert-id (clsql-mysql::database-mysql-ptr database)))) (defmethod database-sequence-last (sequence-name (database mysql-database)) (declare (ignore sequence-name))) -;; Functions depending upon high-level CommonSQL classes/functions -#| -(defmethod database-output-sql ((expr clsql-sys::sql-typecast-exp) - (database mysql-database)) - (with-slots (clsql-sys::modifier clsql-sys::components) - expr - (if clsql-sys::modifier - (progn - (clsql-sys::output-sql clsql-sys::components database) - (write-char #\: sql-sys::*sql-stream*) - (write-char #\: sql-sys::*sql-stream*) - (write-string (symbol-name clsql-sys::modifier) - clsql-sys::*sql-stream*))))) - -(defmethod database-output-sql-as-type ((type (eql 'integer)) val - (database mysql-database)) - ;; typecast it so it uses the indexes - (when val - (make-instance 'clsql-sys::sql-typecast-exp - :modifier 'int8 - :components val))) -|# - -(when (clsql-base-sys:database-type-library-loaded :mysql) - (clsql-base-sys:initialize-database-type :database-type :mysql)) +(defmethod database-create (connection-spec (type (eql :mysql))) + (destructuring-bind (host name user password) connection-spec + (multiple-value-bind (output status) + (clsql-sys:command-output "mysqladmin create -u~A -p~A -h~A ~A" + user password + (if host host "localhost") + name) + (if (or (not (eql 0 status)) + (and (search "failed" output) (search "error" output))) + (error 'clsql-access-error + :connection-spec connection-spec + :database-type type + :error + (format nil "database-create failed: ~A" output)) + t)))) + +(defmethod database-destroy (connection-spec (type (eql :mysql))) + (destructuring-bind (host name user password) connection-spec + (multiple-value-bind (output status) + (clsql-sys:command-output "mysqladmin drop -f -u~A -p~A -h~A ~A" + user password + (if host host "localhost") + name) + (if (or (not (eql 0 status)) + (and (search "failed" output) (search "error" output))) + (error 'clsql-access-error + :connection-spec connection-spec + :database-type type + :error + (format nil "database-destroy failed: ~A" output)) + t)))) + +(defmethod database-probe (connection-spec (type (eql :mysql))) + (when (find (second connection-spec) (database-list connection-spec type) + :key #'car :test #'string-equal) + t)) + +(defmethod database-list (connection-spec (type (eql :mysql))) + (destructuring-bind (host name user password) connection-spec + (declare (ignore name)) + (let ((database (database-connect (list host "mysql" user password) type))) + (unwind-protect + (progn + (setf (slot-value database 'clsql-sys::state) :open) + (mapcar #'car (database-query "show databases" database :auto nil))) + (progn + (database-disconnect database) + (setf (slot-value database 'clsql-sys::state) :closed)))))) + +;;; Database capabilities + +(defmethod db-type-use-column-on-drop-index? ((db-type (eql :mysql))) + t) + +(defmethod db-type-has-views? ((db-type (eql :mysql))) + ;; MySQL 4.1 will apparently have views, need to check *mysql-client-info* + nil) + +(defmethod db-type-has-subqueries? ((db-type (eql :mysql))) + ;; MySQL 4.1 will apparently have subqueries, need to check *mysql-client-info* + nil) + +(defmethod db-type-has-boolean-where? ((db-type (eql :mysql))) + nil) + +(defmethod db-type-transaction-capable? ((db-type (eql :mysql)) database) + (let ((tuple (car (database-query "SHOW VARIABLES LIKE 'HAVE_INNODB'" database :auto nil)))) + (and tuple (string-equal "YES" (second tuple))))) + +(when (clsql-sys:database-type-library-loaded :mysql) + (clsql-sys:initialize-database-type :database-type :mysql)) +