(in-package #:clsql-sys)
(defclass generic-postgresql-database (database)
- ()
+ ((has-table-pg_roles :type boolean :reader has-table-pg_roles :initform nil))
(:documentation "Encapsulate same behavior across postgresql and postgresql-socket backends."))
(declare (ignore database))
(if args
(format nil "CHAR(~A)" (car args))
- "VARCHAR"))
+ "VARCHAR"))
+
+(defmethod database-get-type-specifier ((type (eql 'tinyint)) args database
+ (db-type (eql :postgresql)))
+ (declare (ignore args database))
+ "INT2")
+
+(defmethod database-get-type-specifier ((type (eql 'smallint)) args database
+ (db-type (eql :postgresql)))
+ (declare (ignore args database))
+ "INT2")
(defmethod database-get-type-specifier ((type (eql 'wall-time)) args database
(db-type (eql :postgresql)))
(declare (ignore args database))
"TIMESTAMP WITHOUT TIME ZONE")
+(defmethod database-get-type-specifier ((type (eql 'number)) args database
+ (db-type (eql :postgresql)))
+ (declare (ignore database))
+ (cond
+ ((and (consp args) (= (length args) 2))
+ (format nil "NUMERIC(~D,~D)" (first args) (second args)))
+ ((and (consp args) (= (length args) 1))
+ (format nil "NUMERIC(~D)" (first args)))
+ (t
+ "NUMERIC")))
;;; Backend functions
(defun owner-clause (owner)
- (cond
+ (cond
((stringp owner)
(format
nil
- " AND (relowner=(SELECT usesysid FROM pg_user WHERE (usename='~A')))"
+ " AND (relowner=(SELECT usesysid FROM pg_user WHERE (usename='~A')))"
owner))
((null owner)
- (format nil " AND (NOT (relowner=1))"))
+ (format nil " AND (relowner<>(SELECT usesysid FROM pg_user WHERE usename='postgres'))"))
(t "")))
+(defun has-table (name database)
+ (let ((name-retrieved
+ (caar (database-query
+ (format nil "SELECT relname FROM pg_class WHERE relname='~A'"
+ name)
+ database nil nil))))
+ (if (and (stringp name-retrieved) (plusp (length name-retrieved)))
+ t
+ nil)))
+
+(defmethod slot-unbound (class (obj generic-postgresql-database)
+ (slot (eql 'has-table-pg_roles)))
+ ;; Lazily cache slot value
+ (declare (ignore class))
+ (setf (slot-value obj 'has-table-pg_roles) (has-table "pg_roles" obj)))
+
(defun database-list-objects-of-type (database type owner)
(mapcar #'car
(database-query
(format nil
- "SELECT relname FROM pg_class WHERE (relkind = '~A')~A"
+ (if (and (has-table-pg_roles database)
+ (not (eq owner :all)))
+ "
+ SELECT c.relname
+ FROM pg_catalog.pg_class c
+ LEFT JOIN pg_catalog.pg_roles r ON r.oid = c.relowner
+ LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
+ WHERE c.relkind IN ('~A','')
+ AND n.nspname NOT IN ('pg_catalog', 'pg_toast')
+ AND pg_catalog.pg_table_is_visible(c.oid)
+ ~A"
+ "SELECT relname FROM pg_class WHERE (relkind =
+'~A')~A")
type
(owner-clause owner))
database nil nil)))
(defmethod database-list-tables ((database generic-postgresql-database)
&key (owner nil))
(database-list-objects-of-type database "r" owner))
-
+
(defmethod database-list-views ((database generic-postgresql-database)
&key (owner nil))
(database-list-objects-of-type database "v" owner))
-
+
(defmethod database-list-indexes ((database generic-postgresql-database)
&key (owner nil))
(database-list-objects-of-type database "i" owner))
&key (owner nil))
(let ((indexrelids
(database-query
- (format
+ (format
nil
"select indexrelid from pg_index where indrelid=(select relfilenode from pg_class where relname='~A'~A)"
(string-downcase table)
database :auto nil))
(result nil))
(dolist (indexrelid indexrelids (nreverse result))
- (push
+ (push
(caar (database-query
(format nil "select relname from pg_class where relfilenode='~A'"
(car indexrelid))
(result
(mapcar #'car
(database-query
- (format nil "SELECT attname FROM pg_class,pg_attribute WHERE pg_class.oid=attrelid AND relname='~A'~A"
+ (format nil "SELECT attname FROM pg_class,pg_attribute WHERE pg_class.oid=attrelid AND attisdropped = FALSE AND relname='~A'~A"
(string-downcase table)
owner-clause)
database nil nil))))
"oid"
"ctid"
;; kmr -- added tableoid
- "tableoid") :test #'equal))
+ "tableoid") :test #'equal))
result))))
(defmethod database-attribute-type (attribute (table string)
(owner-clause owner))
database nil nil))))
(when row
- (values
- (ensure-keyword (first row))
- (if (string= "-1" (second row))
- (- (parse-integer (third row) :junk-allowed t) 4)
- (parse-integer (second row)))
- nil
- (if (string-equal "f" (fourth row))
- 1
- 0)))))
+ (destructuring-bind (typname attlen atttypmod attnull) row
+
+ (setf attlen (parse-integer attlen :junk-allowed t)
+ atttypmod (parse-integer atttypmod :junk-allowed t))
+
+ (let ((coltype (ensure-keyword typname))
+ (colnull (if (string-equal "f" attnull) 1 0))
+ collen
+ colprec)
+ (setf (values collen colprec)
+ (case coltype
+ ((:numeric :decimal)
+ (if (= -1 atttypmod)
+ (values nil nil)
+ (values (ash (- atttypmod 4) -16)
+ (boole boole-and (- atttypmod 4) #xffff))))
+ (otherwise
+ (values
+ (cond ((and (= -1 attlen) (= -1 atttypmod)) nil)
+ ((= -1 attlen) (- atttypmod 4))
+ (t attlen))
+ nil))))
+ (values coltype collen colprec colnull))))))
(defmethod database-create-sequence (sequence-name
(database generic-postgresql-database))
(format nil "SELECT SETVAL ('~A', ~A)" name position)
database nil nil)))))
-(defmethod database-sequence-next (sequence-name
+(defmethod database-sequence-next (sequence-name
(database generic-postgresql-database))
(values
(parse-integer
(parse-integer
(caar
(database-query
- (concatenate 'string "SELECT LAST_VALUE ('" sequence-name "')")
+ (concatenate 'string "SELECT LAST_VALUE FROM " sequence-name)
database nil nil)))))
(defun postgresql-database-list (connection-spec type)
(unwind-protect
(progn
(setf (slot-value database 'clsql-sys::state) :open)
- (mapcar #'car (database-query "select datname from pg_database"
+ (mapcar #'car (database-query "select datname from pg_database"
database nil nil)))
(progn
(database-disconnect database)
(defmethod database-list (connection-spec (type (eql :postgresql-socket)))
(postgresql-database-list connection-spec type))
-
+#+nil
(defmethod database-describe-table ((database generic-postgresql-database) table)
- (database-query
+ ;; MTP: LIST-ATTRIBUTE-TYPES currently executes separate queries for
+ ;; each attribute. It would be more efficient to have a single SQL
+ ;; query return the type data for all attributes. This code is
+ ;; retained as an example of how to do this for PostgreSQL.
+ (database-query
(format nil "select a.attname, t.typname
from pg_class c, pg_attribute a, pg_type t
where c.relname = '~a'
(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
(setq in-str (not in-str))
(write-char c out))
((and (char= c #\?) (not in-str))
- (write-char #\$ out)
+ (write-char #\$ out)
(write-string (write-to-string (incf param)) out))
(t
(write-char c out)))))))
:bindings (make-list (length types)))))
(defmethod database-bind-parameter ((stmt postgresql-stmt) position value)
- (setf (nth (1- position) (bindings stmt)) value))
+ (setf (nth (1- position) (bindings stmt)) value))
(defun binding-to-param (binding)
(typecase binding