r9448: * db-odbc/odbc-sql.lisp, db-aodbc/aodbc-sql.lisp: Move common code to
[clsql.git] / sql / generic-postgresql.lisp
1 ;;;; -*- Mode: LISP; Syntax: ANSI-Common-Lisp; Base: 10 -*-
2 ;;;; *************************************************************************
3 ;;;;
4 ;;;; $Id: $
5 ;;;;
6 ;;;; Generic postgresql layer, used by db-postgresql and db-postgresql-socket
7 ;;;;
8 ;;;; This file is part of CLSQL.
9 ;;;;
10 ;;;; CLSQL users are granted the rights to distribute and use this software
11 ;;;; as governed by the terms of the Lisp Lesser GNU Public License
12 ;;;; (http://opensource.franz.com/preamble.html), also known as the LLGPL.
13 ;;;; *************************************************************************
14
15 (in-package #:clsql-sys)
16
17 (defclass generic-postgresql-database (database)
18   ()
19   (:documentation "Encapsulate same behavior across postgresql and postgresql-socket backends."))
20
21
22
23 ;; Object functions
24
25 (defmethod database-get-type-specifier (type args database
26                                         (db-type (eql :postgresql)))
27   (declare (ignore type args database))
28   "VARCHAR")
29
30 (defmethod database-get-type-specifier ((type (eql 'simple-base-string)) args database
31                                         (db-type (eql :postgresql)))
32   (declare (ignore database))
33   (if args
34       (format nil "VARCHAR(~A)" (car args))
35       "VARCHAR"))
36
37 (defmethod database-get-type-specifier ((type (eql 'simple-string)) args database
38                                         (db-type (eql :postgresql)))
39   (declare (ignore database))
40   (if args
41       (format nil "VARCHAR(~A)" (car args))
42       "VARCHAR"))
43
44 (defmethod database-get-type-specifier ((type (eql 'string)) args database
45                                         (db-type (eql :postgresql)))
46   (declare (ignore database))
47   (if args
48       (format nil "VARCHAR(~A)" (car args))
49       "VARCHAR"))
50
51 (defmethod database-get-type-specifier ((type (eql 'wall-time)) args database
52                                         (db-type (eql :postgresql)))
53   (declare (ignore args database))
54   "TIMESTAMP WITHOUT TIME ZONE")
55
56
57 ;;; Backend functions
58
59 (defun owner-clause (owner)
60   (cond 
61    ((stringp owner)
62     (format
63      nil
64      " AND (relowner=(SELECT usesysid FROM pg_user WHERE (usename='~A')))" 
65      owner))
66    ((null owner)
67     (format nil " AND (NOT (relowner=1))"))
68    (t "")))
69
70 (defun database-list-objects-of-type (database type owner)
71   (mapcar #'car
72           (database-query
73            (format nil
74                    "SELECT relname FROM pg_class WHERE (relkind = '~A')~A"
75                    type
76                    (owner-clause owner))
77            database nil nil)))
78
79 (defmethod database-list-tables ((database generic-postgresql-database)
80                                  &key (owner nil))
81   (database-list-objects-of-type database "r" owner))
82   
83 (defmethod database-list-views ((database generic-postgresql-database)
84                                 &key (owner nil))
85   (database-list-objects-of-type database "v" owner))
86   
87 (defmethod database-list-indexes ((database generic-postgresql-database)
88                                   &key (owner nil))
89   (database-list-objects-of-type database "i" owner))
90
91
92 (defmethod database-list-table-indexes (table (database generic-postgresql-database)
93                                         &key (owner nil))
94   (let ((indexrelids
95          (database-query
96           (format 
97            nil
98            "select indexrelid from pg_index where indrelid=(select relfilenode from pg_class where relname='~A'~A)"
99            (string-downcase table)
100            (owner-clause owner))
101           database :auto nil))
102         (result nil))
103     (dolist (indexrelid indexrelids (nreverse result))
104       (push 
105        (caar (database-query
106               (format nil "select relname from pg_class where relfilenode='~A'"
107                       (car indexrelid))
108               database nil nil))
109        result))))
110
111 (defmethod database-list-attributes ((table string)
112                                      (database generic-postgresql-database)
113                                      &key (owner nil))
114   (let* ((owner-clause
115           (cond ((stringp owner)
116                  (format nil " AND (relowner=(SELECT usesysid FROM pg_user WHERE usename='~A'))" owner))
117                 ((null owner) " AND (not (relowner=1))")
118                 (t "")))
119          (result
120           (mapcar #'car
121                   (database-query
122                    (format nil "SELECT attname FROM pg_class,pg_attribute WHERE pg_class.oid=attrelid AND relname='~A'~A"
123                            (string-downcase table)
124                            owner-clause)
125                    database nil nil))))
126     (if result
127         (remove-if #'(lambda (it) (member it '("cmin"
128                                                "cmax"
129                                                "xmax"
130                                                "xmin"
131                                                "oid"
132                                                "ctid"
133                                                ;; kmr -- added tableoid
134                                                "tableoid") :test #'equal)) 
135                    result))))
136
137 (defmethod database-attribute-type (attribute (table string)
138                                     (database generic-postgresql-database)
139                                     &key (owner nil))
140   (let ((row (car (database-query
141                    (format nil "SELECT pg_type.typname,pg_attribute.attlen,pg_attribute.atttypmod,pg_attribute.attnotnull 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"
142                            (string-downcase table)
143                            (string-downcase attribute)
144                            (owner-clause owner))
145                    database nil nil))))
146     (when row
147       (values
148        (ensure-keyword (first row))
149        (if (string= "-1" (second row))
150            (- (parse-integer (third row) :junk-allowed t) 4)
151          (parse-integer (second row)))
152        nil
153        (if (string-equal "f" (fourth row))
154            1
155          0)))))
156
157 (defmethod database-create-sequence (sequence-name
158                                      (database generic-postgresql-database))
159   (database-execute-command
160    (concatenate 'string "CREATE SEQUENCE " (sql-escape sequence-name))
161    database))
162
163 (defmethod database-drop-sequence (sequence-name
164                                    (database generic-postgresql-database))
165   (database-execute-command
166    (concatenate 'string "DROP SEQUENCE " (sql-escape sequence-name)) database))
167
168 (defmethod database-list-sequences ((database generic-postgresql-database)
169                                     &key (owner nil))
170   (database-list-objects-of-type database "S" owner))
171
172 (defmethod database-set-sequence-position (name (position integer)
173                                                 (database generic-postgresql-database))
174   (values
175    (parse-integer
176     (caar
177      (database-query
178       (format nil "SELECT SETVAL ('~A', ~A)" name position)
179       database nil nil)))))
180
181 (defmethod database-sequence-next (sequence-name 
182                                    (database generic-postgresql-database))
183   (values
184    (parse-integer
185     (caar
186      (database-query
187       (concatenate 'string "SELECT NEXTVAL ('" (sql-escape sequence-name) "')")
188       database nil nil)))))
189
190 (defmethod database-sequence-last (sequence-name (database generic-postgresql-database))
191   (values
192    (parse-integer
193     (caar
194      (database-query
195       (concatenate 'string "SELECT LAST_VALUE ('" sequence-name "')")
196       database nil nil)))))
197
198 (defun postgresql-database-list (connection-spec type)
199   (destructuring-bind (host name user password) connection-spec
200     (declare (ignore name))
201     (let ((database (database-connect (list host "template1" user password)
202                                       type)))
203       (unwind-protect
204            (progn
205              (setf (slot-value database 'clsql-sys::state) :open)
206              (mapcar #'car (database-query "select datname from pg_database" 
207                                            database nil nil)))
208         (progn
209           (database-disconnect database)
210           (setf (slot-value database 'clsql-sys::state) :closed))))))
211
212 (defmethod database-list (connection-spec (type (eql :postgresql)))
213   (postgresql-database-list connection-spec type))
214
215 (defmethod database-list (connection-spec (type (eql :postgresql-socket)))
216   (postgresql-database-list connection-spec type))
217
218
219 (defmethod database-describe-table ((database generic-postgresql-database) table)
220   (database-query 
221    (format nil "select a.attname, t.typname
222                                from pg_class c, pg_attribute a, pg_type t
223                                where c.relname = '~a'
224                                    and a.attnum > 0
225                                    and a.attrelid = c.oid
226                                    and a.atttypid = t.oid"
227            (sql-escape (string-downcase table)))
228    database :auto nil))
229
230
231 ;; Capabilities
232
233 (defmethod db-type-has-fancy-math? ((db-type (eql :postgresql)))
234   t)
235
236 (defmethod db-type-default-case ((db-type (eql :postgresql)))
237   :lower)
238