r10077: * multiple: Apply patch from Joerg Hoehle with multiple
[clsql.git] / db-postgresql / postgresql-sql.lisp
index dd15e86b17c383d37fb90354c5f1fe41f234ab11..6a8c7c83290c6852deff730f229e432e4b9661d8 100644 (file)
@@ -16,7 +16,7 @@
 (in-package #:cl-user)
 
 (defpackage #:clsql-postgresql
-    (:use #:common-lisp #:clsql-base-sys #:postgresql #:clsql-uffi)
+    (:use #:common-lisp #:clsql-sys #:postgresql #:clsql-uffi)
     (:export #:postgresql-database)
     (:documentation "This is the CLSQL interface to PostgreSQL."))
 
@@ -77,7 +77,7 @@
 (uffi:def-type pgsql-result-def pgsql-result)
 
 
-(defclass postgresql-database (database)
+(defclass postgresql-database (generic-postgresql-database)
   ((conn-ptr :accessor database-conn-ptr :initarg :conn-ptr
             :type pgsql-conn-def)
    (lock
        (declare (type pgsql-conn-def connection))
        (when (not (eq (PQstatus connection) 
                       pgsql-conn-status-type#connection-ok))
-         (error 'clsql-connect-error
+         (error 'sql-connection-error
                 :database-type database-type
                 :connection-spec connection-spec
-                :errno (PQstatus connection)
-                :error (tidy-error-message 
-                        (PQerrorMessage connection))))
+                :error-id (PQstatus connection)
+                :message (tidy-error-message 
+                          (PQerrorMessage connection))))
        (make-instance 'postgresql-database
                       :name (database-name-from-spec connection-spec
                                                      database-type)
   (setf (database-conn-ptr database) nil)
   t)
 
-(defmethod database-query (query-expression (database postgresql-database) result-types)
+(defmethod database-query (query-expression (database postgresql-database) result-types field-names)
   (let ((conn-ptr (database-conn-ptr database)))
     (declare (type pgsql-conn-def conn-ptr))
     (uffi:with-cstring (query-native query-expression)
       (let ((result (PQexec conn-ptr query-native)))
         (when (uffi:null-pointer-p result)
-          (error 'clsql-sql-error
+          (error 'sql-database-data-error
                  :database database
                  :expression query-expression
-                 :errno nil
-                 :error (tidy-error-message (PQerrorMessage conn-ptr))))
+                 :message (tidy-error-message (PQerrorMessage conn-ptr))))
         (unwind-protect
             (case (PQresultStatus result)
+             ;; User gave a command rather than a query 
+              (#.pgsql-exec-status-type#command-ok
+               nil)
               (#.pgsql-exec-status-type#empty-query
                nil)
               (#.pgsql-exec-status-type#tuples-ok
               (let ((num-fields (PQnfields result)))
-                (setq result-types
-                  (canonicalize-types result-types num-fields
-                                            result))
-                (loop for tuple-index from 0 below (PQntuples result)
-                      collect
-                      (loop for i from 0 below num-fields
-                            collect
-                            (if (zerop (PQgetisnull result tuple-index i))
-                                (convert-raw-field
-                                 (PQgetvalue result tuple-index i)
-                                 result-types i)
-                                nil)))))
+                (when result-types
+                  (setq result-types
+                    (canonicalize-types result-types num-fields
+                                        result)))
+                (let ((res (loop for tuple-index from 0 below (PQntuples result)
+                               collect
+                                 (loop for i from 0 below num-fields
+                                     collect
+                                       (if (zerop (PQgetisnull result tuple-index i))
+                                           (convert-raw-field
+                                            (PQgetvalue result tuple-index i)
+                                            result-types i)
+                                         nil)))))
+                  (if field-names
+                      (values res (result-field-names num-fields result))
+                    res))))
               (t
-               (error 'clsql-sql-error
+               (error 'sql-database-data-error
                       :database database
                       :expression query-expression
-                      :errno (PQresultStatus result)
-                      :error (tidy-error-message
-                              (PQresultErrorMessage result)))))
+                      :error-id (PQresultStatus result)
+                      :message (tidy-error-message
+                               (PQresultErrorMessage result)))))
           (PQclear result))))))
 
+(defun result-field-names (num-fields result)
+  "Return list of result field names."
+  (let ((names '()))
+    (dotimes (i num-fields (nreverse names))
+      (declare (fixnum i))
+      (push (uffi:convert-from-cstring (PQfname result i)) names))))
+
 (defmethod database-execute-command (sql-expression
                                      (database postgresql-database))
   (let ((conn-ptr (database-conn-ptr database)))
     (uffi:with-cstring (sql-native sql-expression)
       (let ((result (PQexec conn-ptr sql-native)))
         (when (uffi:null-pointer-p result)
-          (error 'clsql-sql-error
+          (error 'sql-database-data-error
                  :database database
                  :expression sql-expression
-                 :errno nil
-                 :error (tidy-error-message (PQerrorMessage conn-ptr))))
+                 :message (tidy-error-message (PQerrorMessage conn-ptr))))
         (unwind-protect
             (case (PQresultStatus result)
               (#.pgsql-exec-status-type#command-ok
                (warn "Strange result...")
                t)
               (t
-               (error 'clsql-sql-error
+               (error 'sql-database-data-error
                       :database database
                       :expression sql-expression
-                      :errno (PQresultStatus result)
-                      :error (tidy-error-message
-                              (PQresultErrorMessage result)))))
+                      :error-id (PQresultStatus result)
+                      :message (tidy-error-message
+                               (PQresultErrorMessage result)))))
           (PQclear result))))))
 
 (defstruct postgresql-result-set
     (uffi:with-cstring (query-native query-expression)
       (let ((result (PQexec conn-ptr query-native)))
         (when (uffi:null-pointer-p result)
-          (error 'clsql-sql-error
+          (error 'sql-database-data-error
                  :database database
                  :expression query-expression
-                 :errno nil
-                 :error (tidy-error-message (PQerrorMessage conn-ptr))))
+                 :message (tidy-error-message (PQerrorMessage conn-ptr))))
         (case (PQresultStatus result)
           ((#.pgsql-exec-status-type#empty-query
             #.pgsql-exec-status-type#tuples-ok)
                         (PQnfields result)))))
          (t
           (unwind-protect
-               (error 'clsql-sql-error
+               (error 'sql-database-data-error
                       :database database
                       :expression query-expression
-                      :errno (PQresultStatus result)
-                      :error (tidy-error-message
-                              (PQresultErrorMessage result)))
+                      :error-id (PQresultStatus result)
+                      :message (tidy-error-message
+                               (PQresultErrorMessage result)))
              (PQclear result))))))))
   
 (defmethod database-dump-result-set (result-set (database postgresql-database))
 
 ;;; Object listing
 
-(defun owner-clause (owner)
-  (cond 
-   ((stringp owner)
-    (format
-     nil
-     " AND (relowner=(SELECT usesysid FROM pg_user WHERE (usename='~A')))" 
-     owner))
-   ((null owner)
-    (format nil " AND (NOT (relowner=1))"))
-   (t "")))
-
-(defun database-list-objects-of-type (database type owner)
-  (mapcar #'car
-         (database-query
-          (format nil
-                  "SELECT relname FROM pg_class WHERE (relkind = '~A')~A"
-                  type
-                  (owner-clause owner))
-          database nil)))
-
-(defmethod database-list-tables ((database postgresql-database)
-                                 &key (owner nil))
-  (database-list-objects-of-type database "r" owner))
-  
-(defmethod database-list-views ((database postgresql-database)
-                                &key (owner nil))
-  (database-list-objects-of-type database "v" owner))
-  
-(defmethod database-list-indexes ((database postgresql-database)
-                                  &key (owner nil))
-  (database-list-objects-of-type database "i" owner))
-
-
-(defmethod database-list-table-indexes (table (database postgresql-database)
-                                       &key (owner nil))
-  (let ((indexrelids
-        (database-query
-         (format 
-          nil
-          "select indexrelid from pg_index where indrelid=(select relfilenode from pg_class where relname='~A'~A)"
-          (string-downcase table)
-          (owner-clause owner))
-         database :auto))
-       (result nil))
-    (dolist (indexrelid indexrelids (nreverse result))
-      (push 
-       (caar (database-query
-             (format nil "select relname from pg_class where relfilenode='~A'"
-                     (car indexrelid))
-             database
-             nil))
-       result))))
-
-(defmethod database-list-attributes ((table string)
-                                    (database postgresql-database)
-                                     &key (owner nil))
-  (let* ((owner-clause
-          (cond ((stringp owner)
-                 (format nil " AND (relowner=(SELECT usesysid FROM pg_user WHERE usename='~A'))" owner))
-                ((null owner) " AND (not (relowner=1))")
-                (t "")))
-         (result
-         (mapcar #'car
-                 (database-query
-                  (format nil "SELECT attname FROM pg_class,pg_attribute WHERE pg_class.oid=attrelid AND relname='~A'~A"
-                           (string-downcase table)
-                           owner-clause)
-                   database nil))))
-    (if result
-       (reverse
-         (remove-if #'(lambda (it) (member it '("cmin"
-                                                "cmax"
-                                                "xmax"
-                                                "xmin"
-                                               "oid"
-                                                "ctid"
-                                               ;; kmr -- added tableoid
-                                               "tableoid") :test #'equal)) 
-                   result)))))
-
-(defmethod database-attribute-type (attribute (table string)
-                                   (database postgresql-database)
-                                    &key (owner nil))
-  (let* ((owner-clause
-          (cond ((stringp owner)
-                 (format nil " AND (relowner=(SELECT usesysid FROM pg_user WHERE usename='~A'))" owner))
-                ((null owner) " AND (not (relowner=1))")
-                (t "")))
-         (result
-         (mapcar #'car
-                 (database-query
-                  (format nil "SELECT pg_type.typname FROM pg_type,pg_class,pg_attribute WHERE pg_class.oid=pg_attribute.attrelid AND pg_class.relname='~A' AND pg_attribute.attname='~A' AND pg_attribute.atttypid=pg_type.oid~A"
-                          (string-downcase table)
-                           (string-downcase attribute)
-                           owner-clause)
-                  database nil))))
-    (when result
-      (intern (string-upcase (car result)) :keyword))))
-
-(defmethod database-create-sequence (sequence-name
-                                    (database postgresql-database))
-  (database-execute-command
-   (concatenate 'string "CREATE SEQUENCE " (sql-escape sequence-name))
-   database))
-
-(defmethod database-drop-sequence (sequence-name
-                                  (database postgresql-database))
-  (database-execute-command
-   (concatenate 'string "DROP SEQUENCE " (sql-escape sequence-name)) database))
-
-(defmethod database-list-sequences ((database postgresql-database)
-                                    &key (owner nil))
-  (database-list-objects-of-type database "S" owner))
-
-(defmethod database-set-sequence-position (name (position integer)
-                                                (database postgresql-database))
-  (values
-   (parse-integer
-    (caar
-     (database-query
-      (format nil "SELECT SETVAL ('~A', ~A)" name position)
-      database nil)))))
-
-(defmethod database-sequence-next (sequence-name 
-                                  (database postgresql-database))
-  (values
-   (parse-integer
-    (caar
-     (database-query
-      (concatenate 'string "SELECT NEXTVAL ('" (sql-escape sequence-name) "')")
-      database nil)))))
-
-(defmethod database-sequence-last (sequence-name (database postgresql-database))
-  (values
-   (parse-integer
-    (caar
-     (database-query
-      (concatenate 'string "SELECT LAST_VALUE ('" sequence-name "')")
-      database nil)))))
+
   
 (defmethod database-create (connection-spec (type (eql :postgresql)))
   (destructuring-bind (host name user password) connection-spec
     (declare (ignore user password))
     (multiple-value-bind (output status)
-       (clsql-base-sys:command-output "createdb -h~A ~A"
+       (clsql-sys:command-output "createdb -h~A ~A"
                                       (if host host "localhost")
                                       name)
       (if (or (not (zerop status))
              (search "database creation failed: ERROR:" output))
-         (error 'clsql-access-error
-                :connection-spec connection-spec
-                :database-type type
-                :error 
-                (format nil "database-create failed: ~A" 
-                        output))
+         (error 'sql-database-error
+                :message
+                (format nil "createdb failed for postgresql backend with connection spec ~A."
+                        connection-spec))
        t))))
 
 (defmethod database-destroy (connection-spec (type (eql :postgresql)))
   (destructuring-bind (host name user password) connection-spec
     (declare (ignore user password))
     (multiple-value-bind (output status)
-       (clsql-base-sys:command-output "dropdb -h~A ~A"
+       (clsql-sys:command-output "dropdb -h~A ~A"
                                       (if host host "localhost")
                                       name)
       (if (or (not (zerop status))
              (search "database removal failed: ERROR:" output))
-         (error 'clsql-access-error
-                :connection-spec connection-spec
-                :database-type type
-                :error 
-                (format nil "database-destory failed: ~A" 
-                        output))
+         (error 'sql-database-error
+                :message
+                (format nil "dropdb failed for postgresql backend with connection spec ~A."
+                        connection-spec))
        t))))
 
 
              :key #'car :test #'string-equal)
     t))
 
-(defmethod database-list (connection-spec (type (eql :postgresql)))
-  (destructuring-bind (host name user password) connection-spec
-    (declare (ignore name))
-    (let ((database (database-connect (list host "template1" user password)
-                                     type)))
-      (unwind-protect
-          (progn
-            (setf (slot-value database 'clsql-base-sys::state) :open)
-            (mapcar #'car (database-query "select datname from pg_database" 
-                                          database nil)))
-       (progn
-         (database-disconnect database)
-         (setf (slot-value database 'clsql-base-sys::state) :closed))))))
-
-(defmethod database-describe-table ((database postgresql-database) table)
-  (database-query 
-   (format nil "select a.attname, t.typname
-                               from pg_class c, pg_attribute a, pg_type t
-                               where c.relname = '~a'
-                                   and a.attnum > 0
-                                   and a.attrelid = c.oid
-                                   and a.atttypid = t.oid"
-           (sql-escape (string-downcase table)))
-   database :auto))
 
 (defun %pg-database-connection (connection-spec)
   (check-connection-spec connection-spec :postgresql
         connection-spec
       (coerce-string db)
       (coerce-string user)
-      (let ((connection (pqsetdblogin host port options tty db user password)))
+      (let ((connection (PQsetdbLogin host port options tty db user password)))
         (declare (type postgresql::pgsql-conn-ptr connection))
-        (unless (eq (pqstatus connection) :connection-ok)
+        (unless (eq (PQstatus connection)
+                   pgsql-conn-status-type#connection-ok)
           ;; Connect failed
-          (error 'clsql-connect-error
+          (error 'sql-connection-error
                  :database-type :postgresql
                  :connection-spec connection-spec
-                 :errno (pqstatus connection)
-                 :error (pqerrormessage connection)))
+                 :error-id (PQstatus connection)
+                 :message (PQerrorMessage connection)))
         connection))))
 
 (defmethod database-reconnect ((database postgresql-database))
 
 ;;; Database capabilities
 
-(defmethod db-type-has-fancy-math? ((db-type (eql :postgresql)))
-  t)
-
-(defmethod db-type-default-case ((db-type (eql :postgresql)))
-  :lower)
-
-(when (clsql-base-sys:database-type-library-loaded :postgresql)
-  (clsql-base-sys:initialize-database-type :database-type :postgresql))
+(when (clsql-sys:database-type-library-loaded :postgresql)
+  (clsql-sys:initialize-database-type :database-type :postgresql))