r9530: updates for mysql prepared statements
authorKevin M. Rosenberg <kevin@rosenberg.net>
Sun, 30 May 2004 15:04:50 +0000 (15:04 +0000)
committerKevin M. Rosenberg <kevin@rosenberg.net>
Sun, 30 May 2004 15:04:50 +0000 (15:04 +0000)
db-mysql/mysql-api.lisp
db-mysql/mysql-package.lisp
db-mysql/mysql-sql.lisp
db-mysql/mysql.c
sql/fdml.lisp
sql/generic-postgresql.lisp

index 96d904c70900954e1b04548100b501d5d1159ac5..31828ca296dd99449ddf35ab19371e36bda7edbb 100644 (file)
@@ -33,7 +33,7 @@
 ;;; Basic Types
 
 (uffi:def-foreign-type mysql-socket :int)
 ;;; Basic Types
 
 (uffi:def-foreign-type mysql-socket :int)
-(uffi:def-foreign-type mysql-bool :char)
+(uffi:def-foreign-type mysql-bool :byte)
 (uffi:def-foreign-type mysql-byte :unsigned-char)
 
 (uffi:def-enum mysql-net-type
 (uffi:def-foreign-type mysql-byte :unsigned-char)
 
 (uffi:def-enum mysql-net-type
      (:long-blob 251)
      (:blob 252)
      (:var-string 253)
      (:long-blob 251)
      (:blob 252)
      (:var-string 253)
-     (:string 254)))
+     (:string 254)
+     (:geometry 255)))
 
 #+mysql-client-v3
 (uffi:def-struct mysql-field
 
 #+mysql-client-v3
 (uffi:def-struct mysql-field
   (handle (:struct-pointer mysql-mysql))
   (eof mysql-bool))
 
   (handle (:struct-pointer mysql-mysql))
   (eof mysql-bool))
 
+#+mysql-client-4.1
+(uffi:def-enum mysql-field-types
+    (:ready 
+     :get-result
+     :use-result))
+
 #+mysql-client-v4.1
 (uffi:def-struct mysql-bind
     (length (* :unsigned-long))
 #+mysql-client-v4.1
 (uffi:def-struct mysql-bind
     (length (* :unsigned-long))
-  (is-null (* :short))
+  (is-null (* mysql-bool))
   (buffer :pointer-void)
   (buffer-type :int)
   (buffer-length :unsigned-long)
   (buffer :pointer-void)
   (buffer-type :int)
   (buffer-length :unsigned-long)
-  ;; remainder of structure is for internal use
-  )
+  ;; internal use
+  (inter_buffer (* :unsigned-char))
+  (offset :unsigned-long)
+  (internal-length :unsigned-long)
+  (param-number :unsigned-int)
+  (pack-length :unsigned-int)
+  (is-signed mysql-bool)
+  (long-data-used mysql-bool)
+  (internal-is-null mysql-bool)
+  (store-param-func :pointer-void)
+  (fetch-result :pointer-void)
+  (skip-result :pointer-void))
 
 ;;;; The Foreign C routines
 (declaim (inline mysql-init))
 
 ;;;; The Foreign C routines
 (declaim (inline mysql-init))
   :module "clsql-mysql"
   :returning mysql-stmt-ptr)
 
   :module "clsql-mysql"
   :returning mysql-stmt-ptr)
 
+#+mysql-client-v4.1
+(uffi:def-function "mysql_stmt_prepare"
+    ((stmt mysql-stmt-ptr)
+     (query :cstring)
+     (length :unsigned-long))
+  :module "clsql-mysql"
+  :returning :int)
+
+#+mysql-client-v4.1
+(uffi:def-function "mysql_stmt_param_count"
+    ((stmt mysql-stmt-ptr))
+  :module "clsql-mysql"
+  :returning :unsigned-int)
+
 #+mysql-client-v4.1
 (uffi:def-function "mysql_stmt_bind_param"
     ((stmt mysql-stmt-ptr)
 #+mysql-client-v4.1
 (uffi:def-function "mysql_stmt_bind_param"
     ((stmt mysql-stmt-ptr)
   :module "clsql-mysql"
   :returning :short)
 
   :module "clsql-mysql"
   :returning :short)
 
+#+mysql-client-v4.1
+(uffi:def-function "mysql_stmt_result_metadata"
+    ((stmt mysql-stmt-ptr))
+  :module "clsql-mysql"
+  :returning (* mysql-mysql-res))
+
+
 #+mysql-client-v4.1
 (uffi:def-function "mysql_stmt_execute"
     ((stmt mysql-stmt-ptr))
 #+mysql-client-v4.1
 (uffi:def-function "mysql_stmt_execute"
     ((stmt mysql-stmt-ptr))
   :module "clsql-mysql"
   :returning :short)
 
   :module "clsql-mysql"
   :returning :short)
 
+#+mysql-client-v4.1
+(uffi:def-function "mysql_stmt_close"
+    ((stmt mysql-stmt-ptr))
+  :module "clsql-mysql"
+  :returning :short)
+
+#+mysql-client-v4.1
+(uffi:def-function "mysql_stmt_errno"
+    ((stmt mysql-stmt-ptr))
+  :module "clsql-mysql"
+  :returning :unsigned-int)
+
+#+mysql-client-v4.1
+(uffi:def-function "mysql_stmt_error"
+    ((stmt mysql-stmt-ptr))
+  :module "clsql-mysql"
+  :returning :cstring)
+
+
 ;;;; Equivalents of C Macro definitions for accessing various fields
 ;;;; in the internal MySQL Datastructures
 
 ;;;; Equivalents of C Macro definitions for accessing various fields
 ;;;; in the internal MySQL Datastructures
 
index 4ecbabfe2989120abd45d7509a6d99f6d3a83b45..752866dc86f521845f238c0e22afe5573e2e281a 100644 (file)
      #:mysql-info-string
      #:mysql-data-seek
      
      #:mysql-info-string
      #:mysql-data-seek
      
+     #:mysql-bind
+     #:mysql-stmt-param-count
+     #:mysql-stmt-prepare
+     #:mysql-stmt-execute
+     #:mysql-stmt-init
+     #:mysql-stmt-close-result
+     #:mysql-stmt-free-result
+     #:mysql-stmt
+     #:mysql-stmt-result-metadata
+     #:mysql-stmt-fetch
+     #:mysql-stmt-bind-param
+     #:mysql-stmt-bind-result
+     #:mysql-stmt-close
+     #:mysql-stmt-errno
+     #:mysql-stmt-error
+     
      #:make-64-bit-integer
      )
     (:documentation "This is the low-level interface MySQL."))
      #:make-64-bit-integer
      )
     (:documentation "This is the low-level interface MySQL."))
index e677c5a05204c683fb28aed70de7e62ace028424..b84431ced8fccd1abd9fd06d1d0f2464e67ca3b2 100644 (file)
          (database-disconnect database)
          (setf (slot-value database 'clsql-sys::state) :closed))))))
 
          (database-disconnect database)
          (setf (slot-value database 'clsql-sys::state) :closed))))))
 
+
+;;; Prepared statements
+
+(defclass mysql-stmt ()
+  ((database :initarg :database :reader database)
+   (stmt :initarg :stmt :accessor stmt)
+   (input-bind :initarg :input-bind :reader input-bind)
+   (output-bind :initarg :output-bind :reader output-bind)
+   (types :initarg :types :reader types)
+   (result-set :initarg :result-set :reader result-set)
+   (num-fields :initarg :num-fields :reader num-fields)
+   (field-names :initarg :field-names :accessor stmt-field-names)
+   (result-types :initarg :result-types :reader result-types)))
+
+(defun clsql-type->mysql-type (type)
+  (cond
+    ((in type :null) mysql-field-types#null)
+    ((in type :int :integer) mysql-field-types#long)
+    ((in type :short) mysql-field-types#short)
+    ((in type :bigint) mysql-field-types#longlong)
+    ((in type :float :double :number) mysql-field-types#double)
+    ((and (consp type) (in (car type) :char :varchar)) mysql-field-types#var-string)
+    (t
+     (error 'sql-user-error 
+           :message 
+           (format nil "Unknown clsql type ~A." type)))))
+
+(defmethod database-prepare (sql-stmt types (database mysql-database) result-types field-names)
+  (let* ((mysql-ptr (database-mysql-ptr database))
+        (stmt (mysql-stmt-init mysql-ptr)))
+    (when (uffi:null-pointer-p stmt)
+      (error 'sql-database-error
+            :error-id (mysql-errno mysql-ptr)
+            :message (mysql-error-string mysql-ptr)))
+
+    (uffi:with-cstring (native-query sql-stmt)
+      (unless (zerop (mysql-stmt-prepare stmt native-query (length sql-stmt)))
+       (error 'sql-database-error
+              :error-id (mysql-errno mysql-ptr)
+              :message (mysql-error-string mysql-ptr))))
+    
+    (unless (= (mysql-stmt-param-count stmt) (length types))
+      (error 'sql-database-error
+            :message 
+            (format nil "Mysql param count (~D) does not match number of types (~D)"
+                    (mysql-stmt-param-count stmt) (length types))))
+
+    (let ((rs (mysql-stmt-result-metadata stmt)))
+      (when (uffi:null-pointer-p rs)
+       (error 'sql-database-error
+              :message "NULL result metadata"))
+      (make-instance 'mysql-stmt
+       :database database
+       :stmt stmt
+       :num-fields (mysql-num-fields rs)
+       :input-bind (uffi:allocate-foreign-object mysql-bind (length types))
+       :output-bind (uffi:allocate-foreign-object mysql-bind (mysql-num-fields rs))
+       :result-set rs
+       :result-types result-types
+       :types (mapcar 'clsql-type->mysql-type types)
+       :field-names field-names))))
+
+(defmethod database-bind-parameter ((stmt mysql-stmt) position value)
+  ;; FIXME: will need to allocate bind structure. This should probably be
+  ;; done in C since the API is not mature and may change
+  (let ((binding (uffi:deref-array (input-bind stmt) '(:array mysql-bind) (1- position)))
+       (type (nth (1- position) (types stmt))))
+    (setf (uffi:get-slot-value binding 'mysql-bind 'length) 0)
+    (cond
+     ((null value)
+      (setf (uffi:get-slot-value binding 'mysql-bind 'is-null) 1))
+     (t
+      (setf (uffi:get-slot-value binding 'mysql-bind 'is-null) 0)
+      (setf (uffi:get-slot-value binding 'mysql-bind 'buffer-type) type)
+      (case type
+       (#.mysql-field-types#long
+        (let ((ptr (uffi:allocate-foreign-object :long)))
+          (setf (uffi:deref-pointer ptr :long) value)
+          (setf (uffi:get-slot-value binding 'mysql-bind 'buffer) ptr)))
+       )))))
+
+(defmethod database-run-prepared ((stmt mysql-stmt))
+  (unless (zerop (mysql-stmt-bind-param (stmt stmt) (input-bind stmt)))
+    (error 'sql-database-error
+          :error-id (mysql-stmt-errno (stmt stmt))
+          :message  (uffi:convert-from-cstring
+                     (mysql-stmt-error (stmt stmt)))))
+  (unless (zerop (mysql-stmt-execute (stmt stmt)))
+    (error 'sql-database-error
+          :error-id (mysql-stmt-errno (stmt stmt))
+          :message  (uffi:convert-from-cstring
+                     (mysql-stmt-error (stmt stmt)))))
+  (let ((field-vec (mysql-fetch-fields (result-set stmt))))
+    (dotimes (i (num-fields (result-set stmt)))
+      (declare (fixnum i))
+      (let* ((field (uffi:deref-array field-vec '(:array mysql-field) i))
+            (type (uffi:get-slot-value field 'mysql-field 'type))
+            (binding (uffi:deref-array (output-bind stmt) '(:array mysql-bind) i)))
+       (setf (uffi:get-slot-value binding 'mysql-bind 'buffer-type) type)
+        (case type
+          (#.mysql-field-types#var-string
+           (setf (uffi:get-slot-value binding 'mysql-bind 'buffer-length) 1024)
+           (setf (uffi:get-slot-value binding 'mysql-bind 'buffer)
+                 (uffi:allocate-foreign-object :unsigned-char 1024)))
+          (t
+           (setf (uffi:get-slot-value binding 'mysql-bind 'buffer-length) 0))))))
+  (unless (zerop (mysql-stmt-bind-result (stmt stmt) (output-bind stmt)))
+    (error 'sql-database-error
+          :error-id (mysql-stmt-errno (stmt stmt))
+          :message  (uffi:convert-from-cstring
+                     (mysql-stmt-error (stmt stmt))))))
+
+
+(defmethod database-free-prepared ((stmt mysql-stmt))
+  (with-slots (stmt) stmt
+    (mysql-stmt-close stmt))
+  )
+
+
 ;;; Database capabilities
 
 (defmethod db-type-use-column-on-drop-index? ((db-type (eql :mysql)))
 ;;; Database capabilities
 
 (defmethod db-type-use-column-on-drop-index? ((db-type (eql :mysql)))
index ea11686c9cf111aff75b67e023fc09d5e8492b04..af359c670ce481996ea9ba03214fd0b353fd9ca9 100644 (file)
@@ -84,3 +84,70 @@ clsql_mysql_insert_id (MYSQL* mysql, unsigned int* pHigh32)
 }
 
 
 }
 
 
+/* Accessor functions to hide the differences across MySQL versions */
+
+DLLEXPORT
+short int
+clsql_mysql_field_type (MYSQL_FIELD* field)
+{
+  return field->type;
+}
+
+DLLEXPORT
+char*
+clsql_mysql_field_name (MYSQL_FIELD* field)
+{
+  return field->name;
+}
+
+DLLEXPORT
+unsigned long
+clsql_mysql_field_length (MYSQL_FIELD* field)
+{
+  return field->length;
+}
+
+DLLEXPORT
+unsigned long
+clsql_mysql_field_max_length (MYSQL_FIELD* field)
+{
+  return field->max_length;
+}
+
+
+#if MYSQL_VERSION_ID >= 40102
+#include <stdlib.h>
+
+DLLEXPORT
+MYSQL_BIND*
+allocate_bind (unsigned int n)
+{
+  return (MYSQL_BIND*) malloc (n * sizeof(MYSQL_BIND));
+}
+
+DLLEXPORT
+void
+bind_param (MYSQL_BIND* bind, unsigned int n, unsigned long length, unsigned short is_null, 
+          void* buffer, unsigned short buffer_type, unsigned long buffer_length)
+{
+  bind[n].length = length;
+  bind[n].is_null = is_null;
+  bind[n].buffer = buffer;
+  bind[n].buffer_type = buffer_type;
+  bind[n].buffer_length = buffer_length;
+}
+
+
+DLLEXPORT
+DLLEXPORT
+unsigned int
+clsql_mysql_stmt_affected_rows (MYSQL_STMT* stmt, unsigned int* pHigh32)
+{
+  my_ulonglong nAffected = mysql_stmt_affected_rows (stmt);
+  *pHigh32 = upper_32bits(nAffected);
+  return lower_32bits(nAffected);
+}
+
+
+#endif
+
index 50ab024ec38129421c3fb784963e2b7ca0e6bd0c..7c8ae7b2c20237ae22ad9b53be17394a2f961d86 100644 (file)
@@ -581,8 +581,15 @@ computed for each field."
 
 (defun prepare-sql (sql-stmt types &key (database *default-database*) (result-types :auto) field-names)
   "Prepares a SQL statement for execution. TYPES contains a
 
 (defun prepare-sql (sql-stmt types &key (database *default-database*) (result-types :auto) field-names)
   "Prepares a SQL statement for execution. TYPES contains a
-list of UFFI primitive types corresponding to the input parameters. Returns a
-prepared-statement object."
+list of types corresponding to the input parameters. Returns a
+prepared-statement object.
+
+A type can be
+  :int
+  :double
+  :null
+  (:string n)
+"
   (unless (db-type-has-prepared-stmt? (database-type database))
     (error 'sql-user-error 
           :message
   (unless (db-type-has-prepared-stmt? (database-type database))
     (error 'sql-user-error 
           :message
index b85da75fb073ea28016095ff97cd1bf1d2a47c97..c387f19c41117b2e6af10d06a904fe72775aea3c 100644 (file)
    (result-types :initarg :result-types :reader result-types)))
 
 (defun clsql-type->postgresql-type (type)
    (result-types :initarg :result-types :reader result-types)))
 
 (defun clsql-type->postgresql-type (type)
-  (case type
-    (:string "VARCHAR")
-    ((:int :integer) "INT4")
-    (:short "INT2")
-    ((:number :numeric :float) "NUMERIC")
-    (:bigint "INT8")))
+  (cond
+    ((in type :int :integer) "INT4")
+    ((in type :short) "INT2")
+    ((in type :bigint) "INT8")
+    ((in type :float :double :number) "NUMERIC")
+    ((and (consp type) (in (car type) :char :varchar)) "VARCHAR")
+    (t
+     (error 'sql-user-error 
+           :message 
+           (format nil "Unknown clsql type ~A." type)))))
 
 (defun prepared-sql-to-postgresql-sql (sql)
   ;; FIXME: Convert #\? to "$n". Don't convert within strings
 
 (defun prepared-sql-to-postgresql-sql (sql)
   ;; FIXME: Convert #\? to "$n". Don't convert within strings