change permissions
[clsql/s11.git] / db-postgresql / postgresql-sql.lisp
blobb0c6f81c0947dda7cde164aaa3198c803104ee0e
1 ;;;; -*- Mode: LISP; Syntax: ANSI-Common-Lisp; Base: 10 -*-
2 ;;;; *************************************************************************
3 ;;;; FILE IDENTIFICATION
4 ;;;;
5 ;;;; Name: postgresql-sql.lisp
6 ;;;; Purpose: High-level PostgreSQL interface using UFFI
7 ;;;; Date Started: Feb 2002
8 ;;;;
9 ;;;; $Id$
10 ;;;;
11 ;;;; CLSQL users are granted the rights to distribute and use this software
12 ;;;; as governed by the terms of the Lisp Lesser GNU Public License
13 ;;;; (http://opensource.franz.com/preamble.html), also known as the LLGPL.
14 ;;;; *************************************************************************
16 (in-package #:cl-user)
18 (defpackage #:clsql-postgresql
19 (:use #:common-lisp #:clsql-sys #:pgsql #:clsql-uffi)
20 (:export #:postgresql-database)
21 (:documentation "This is the CLSQL interface to PostgreSQL."))
23 (in-package #:clsql-postgresql)
25 ;;; Field conversion functions
27 (defun make-type-list-for-auto (num-fields res-ptr)
28 (let ((new-types '()))
29 (dotimes (i num-fields)
30 (declare (fixnum i))
31 (let* ((type (PQftype res-ptr i)))
32 (push
33 (case type
34 ((#.pgsql-ftype#bytea
35 #.pgsql-ftype#int2
36 #.pgsql-ftype#int4)
37 :int32)
38 (#.pgsql-ftype#int8
39 :int64)
40 ((#.pgsql-ftype#float4
41 #.pgsql-ftype#float8)
42 :double)
43 (otherwise
44 t))
45 new-types)))
46 (nreverse new-types)))
48 (defun canonicalize-types (types num-fields res-ptr)
49 (if (null types)
50 nil
51 (let ((auto-list (make-type-list-for-auto num-fields res-ptr)))
52 (cond
53 ((listp types)
54 (canonicalize-type-list types auto-list))
55 ((eq types :auto)
56 auto-list)
58 nil)))))
60 (defun tidy-error-message (message)
61 (unless (stringp message)
62 (setq message (uffi:convert-from-foreign-string message)))
63 (let ((message (string-right-trim '(#\Return #\Newline) message)))
64 (cond
65 ((< (length message) (length "ERROR:"))
66 message)
67 ((string= message "ERROR:" :end1 6)
68 (string-left-trim '(#\Space) (subseq message 6)))
70 message))))
72 (defmethod database-initialize-database-type ((database-type
73 (eql :postgresql)))
76 (uffi:def-type pgsql-conn-def pgsql-conn)
77 (uffi:def-type pgsql-result-def pgsql-result)
80 (defclass postgresql-database (generic-postgresql-database)
81 ((conn-ptr :accessor database-conn-ptr :initarg :conn-ptr
82 :type pgsql-conn-def)
83 (lock
84 :accessor database-lock
85 :initform (make-process-lock "conn"))))
87 (defmethod database-type ((database postgresql-database))
88 :postgresql)
90 (defmethod database-name-from-spec (connection-spec (database-type
91 (eql :postgresql)))
92 (check-connection-spec connection-spec database-type
93 (host db user password &optional port options tty))
94 (destructuring-bind (host db user password &optional port options tty)
95 connection-spec
96 (declare (ignore password options tty))
97 (concatenate 'string
98 (etypecase host
99 (null "localhost")
100 (pathname (namestring host))
101 (string host))
102 (when port
103 (concatenate 'string
105 (etypecase port
106 (integer (write-to-string port))
107 (string port))))
108 "/" db "/" user)))
111 (defmethod database-connect (connection-spec (database-type (eql :postgresql)))
112 (check-connection-spec connection-spec database-type
113 (host db user password &optional port options tty))
114 (destructuring-bind (host db user password &optional port options tty)
115 connection-spec
116 (uffi:with-cstrings ((host-native host)
117 (user-native user)
118 (password-native password)
119 (db-native db)
120 (port-native port)
121 (options-native options)
122 (tty-native tty))
123 (let ((connection (PQsetdbLogin host-native port-native
124 options-native tty-native
125 db-native user-native
126 password-native)))
127 (declare (type pgsql-conn-def connection))
128 (when (not (eq (PQstatus connection)
129 pgsql-conn-status-type#connection-ok))
130 (let ((pqstatus (PQstatus connection))
131 (pqmessage (tidy-error-message (PQerrorMessage connection))))
132 (PQfinish connection)
133 (error 'sql-connection-error
134 :database-type database-type
135 :connection-spec connection-spec
136 :error-id pqstatus
137 :message pqmessage)))
138 (make-instance 'postgresql-database
139 :name (database-name-from-spec connection-spec
140 database-type)
141 :database-type :postgresql
142 :connection-spec connection-spec
143 :conn-ptr connection)))))
146 (defmethod database-disconnect ((database postgresql-database))
147 (PQfinish (database-conn-ptr database))
148 (setf (database-conn-ptr database) nil)
151 (defmethod database-query (query-expression (database postgresql-database) result-types field-names)
152 (let ((conn-ptr (database-conn-ptr database)))
153 (declare (type pgsql-conn-def conn-ptr))
154 (uffi:with-cstring (query-native query-expression)
155 (let ((result (PQexec conn-ptr query-native)))
156 (when (uffi:null-pointer-p result)
157 (error 'sql-database-data-error
158 :database database
159 :expression query-expression
160 :message (tidy-error-message (PQerrorMessage conn-ptr))))
161 (unwind-protect
162 (case (PQresultStatus result)
163 ;; User gave a command rather than a query
164 (#.pgsql-exec-status-type#command-ok
165 nil)
166 (#.pgsql-exec-status-type#empty-query
167 nil)
168 (#.pgsql-exec-status-type#tuples-ok
169 (let ((num-fields (PQnfields result)))
170 (when result-types
171 (setq result-types
172 (canonicalize-types result-types num-fields
173 result)))
174 (let ((res (loop for tuple-index from 0 below (PQntuples result)
175 collect
176 (loop for i from 0 below num-fields
177 collect
178 (if (zerop (PQgetisnull result tuple-index i))
179 (convert-raw-field
180 (PQgetvalue result tuple-index i)
181 result-types i)
182 nil)))))
183 (if field-names
184 (values res (result-field-names num-fields result))
185 res))))
187 (error 'sql-database-data-error
188 :database database
189 :expression query-expression
190 :error-id (PQresultErrorField result +PG-DIAG-SQLSTATE+)
191 :message (tidy-error-message
192 (PQresultErrorMessage result)))))
193 (PQclear result))))))
195 (defun result-field-names (num-fields result)
196 "Return list of result field names."
197 (let ((names '()))
198 (dotimes (i num-fields (nreverse names))
199 (declare (fixnum i))
200 (push (uffi:convert-from-cstring (PQfname result i)) names))))
202 (defmethod database-execute-command (sql-expression
203 (database postgresql-database))
204 (let ((conn-ptr (database-conn-ptr database)))
205 (declare (type pgsql-conn-def conn-ptr))
206 (uffi:with-cstring (sql-native sql-expression)
207 (let ((result (PQexec conn-ptr sql-native)))
208 (when (uffi:null-pointer-p result)
209 (error 'sql-database-data-error
210 :database database
211 :expression sql-expression
212 :message (tidy-error-message (PQerrorMessage conn-ptr))))
213 (unwind-protect
214 (case (PQresultStatus result)
215 (#.pgsql-exec-status-type#command-ok
217 ((#.pgsql-exec-status-type#empty-query
218 #.pgsql-exec-status-type#tuples-ok)
219 (warn "Strange result...")
222 (error 'sql-database-data-error
223 :database database
224 :expression sql-expression
225 :error-id (PQresultErrorField result +PG-DIAG-SQLSTATE+)
226 :message (tidy-error-message
227 (PQresultErrorMessage result)))))
228 (PQclear result))))))
230 (defstruct postgresql-result-set
231 (res-ptr (uffi:make-null-pointer 'pgsql-result)
232 :type pgsql-result-def)
233 (types nil)
234 (num-tuples 0 :type integer)
235 (num-fields 0 :type integer)
236 (tuple-index 0 :type integer))
238 (defmethod database-query-result-set ((query-expression string)
239 (database postgresql-database)
240 &key full-set result-types)
241 (let ((conn-ptr (database-conn-ptr database)))
242 (declare (type pgsql-conn-def conn-ptr))
243 (uffi:with-cstring (query-native query-expression)
244 (let ((result (PQexec conn-ptr query-native)))
245 (when (uffi:null-pointer-p result)
246 (error 'sql-database-data-error
247 :database database
248 :expression query-expression
249 :message (tidy-error-message (PQerrorMessage conn-ptr))))
250 (case (PQresultStatus result)
251 ((#.pgsql-exec-status-type#empty-query
252 #.pgsql-exec-status-type#tuples-ok)
253 (let ((result-set (make-postgresql-result-set
254 :res-ptr result
255 :num-fields (PQnfields result)
256 :num-tuples (PQntuples result)
257 :types (canonicalize-types
258 result-types
259 (PQnfields result)
260 result))))
261 (if full-set
262 (values result-set
263 (PQnfields result)
264 (PQntuples result))
265 (values result-set
266 (PQnfields result)))))
268 (unwind-protect
269 (error 'sql-database-data-error
270 :database database
271 :expression query-expression
272 :error-id (PQresultErrorField result +PG-DIAG-SQLSTATE+)
273 :message (tidy-error-message
274 (PQresultErrorMessage result)))
275 (PQclear result))))))))
277 (defmethod database-dump-result-set (result-set (database postgresql-database))
278 (let ((res-ptr (postgresql-result-set-res-ptr result-set)))
279 (declare (type pgsql-result-def res-ptr))
280 (PQclear res-ptr)
283 (defmethod database-store-next-row (result-set (database postgresql-database)
284 list)
285 (let ((result (postgresql-result-set-res-ptr result-set))
286 (types (postgresql-result-set-types result-set)))
287 (declare (type pgsql-result-def result))
288 (if (>= (postgresql-result-set-tuple-index result-set)
289 (postgresql-result-set-num-tuples result-set))
291 (loop with tuple-index = (postgresql-result-set-tuple-index result-set)
292 for i from 0 below (postgresql-result-set-num-fields result-set)
293 for rest on list
295 (setf (car rest)
296 (if (zerop (PQgetisnull result tuple-index i))
297 (convert-raw-field
298 (PQgetvalue result tuple-index i)
299 types i)
300 nil))
301 finally
302 (incf (postgresql-result-set-tuple-index result-set))
303 (return list)))))
305 ;;; Large objects support (Marc B)
307 (defmethod database-create-large-object ((database postgresql-database))
308 (lo-create (database-conn-ptr database)
309 (logior pgsql::+INV_WRITE+ pgsql::+INV_READ+)))
312 #+mb-original
313 (defmethod database-write-large-object (object-id (data string) (database postgresql-database))
314 (let ((ptr (database-conn-ptr database))
315 (length (length data))
316 (result nil)
317 (fd nil))
318 (with-transaction (:database database)
319 (unwind-protect
320 (progn
321 (setf fd (lo-open ptr object-id pgsql::+INV_WRITE+))
322 (when (>= fd 0)
323 (when (= (lo-write ptr fd data length) length)
324 (setf result t))))
325 (progn
326 (when (and fd (>= fd 0))
327 (lo-close ptr fd))
329 result))
331 (defmethod database-write-large-object (object-id (data string) (database postgresql-database))
332 (let ((ptr (database-conn-ptr database))
333 (length (length data))
334 (result nil)
335 (fd nil))
336 (database-execute-command "begin" database)
337 (unwind-protect
338 (progn
339 (setf fd (lo-open ptr object-id pgsql::+INV_WRITE+))
340 (when (>= fd 0)
341 (when (= (lo-write ptr fd data length) length)
342 (setf result t))))
343 (progn
344 (when (and fd (>= fd 0))
345 (lo-close ptr fd))
346 (database-execute-command (if result "commit" "rollback") database)))
347 result))
349 ;; (MB) the begin/commit/rollback stuff will be removed when with-transaction wil be implemented
350 ;; (KMR) Can't use with-transaction since that function is in high-level code
351 (defmethod database-read-large-object (object-id (database postgresql-database))
352 (let ((ptr (database-conn-ptr database))
353 (buffer nil)
354 (result nil)
355 (length 0)
356 (fd nil))
357 (unwind-protect
358 (progn
359 (database-execute-command "begin" database)
360 (setf fd (lo-open ptr object-id pgsql::+INV_READ+))
361 (when (>= fd 0)
362 (setf length (lo-lseek ptr fd 0 2))
363 (lo-lseek ptr fd 0 0)
364 (when (> length 0)
365 (setf buffer (uffi:allocate-foreign-string
366 length :unsigned t))
367 (when (= (lo-read ptr fd buffer length) length)
368 (setf result (uffi:convert-from-foreign-string
369 buffer :length length :null-terminated-p nil))))))
370 (progn
371 (when buffer (uffi:free-foreign-object buffer))
372 (when (and fd (>= fd 0)) (lo-close ptr fd))
373 (database-execute-command (if result "commit" "rollback") database)))
374 result))
376 (defmethod database-delete-large-object (object-id (database postgresql-database))
377 (lo-unlink (database-conn-ptr database) object-id))
380 ;;; Object listing
384 (defmethod database-create (connection-spec (type (eql :postgresql)))
385 (destructuring-bind (host name user password) connection-spec
386 (let ((database (database-connect (list host "postgres" user password)
387 type)))
388 (setf (slot-value database 'clsql-sys::state) :open)
389 (unwind-protect
390 (database-execute-command (format nil "create database ~A" name) database)
391 (database-disconnect database)))))
393 (defmethod database-destroy (connection-spec (type (eql :postgresql)))
394 (destructuring-bind (host name user password) connection-spec
395 (let ((database (database-connect (list host "postgres" user password)
396 type)))
397 (setf (slot-value database 'clsql-sys::state) :open)
398 (unwind-protect
399 (database-execute-command (format nil "drop database ~A" name) database)
400 (database-disconnect database)))))
403 (defmethod database-probe (connection-spec (type (eql :postgresql)))
404 (when (find (second connection-spec) (database-list connection-spec type)
405 :test #'string-equal)
409 (defun %pg-database-connection (connection-spec)
410 (check-connection-spec connection-spec :postgresql
411 (host db user password &optional port options tty))
412 (macrolet ((coerce-string (var)
413 `(unless (typep ,var 'simple-base-string)
414 (setf ,var (coerce ,var 'simple-base-string)))))
415 (destructuring-bind (host db user password &optional port options tty)
416 connection-spec
417 (coerce-string db)
418 (coerce-string user)
419 (let ((connection (PQsetdbLogin host port options tty db user password)))
420 (declare (type pgsql::pgsql-conn-ptr connection))
421 (unless (eq (PQstatus connection)
422 pgsql-conn-status-type#connection-ok)
423 ;; Connect failed
424 (error 'sql-connection-error
425 :database-type :postgresql
426 :connection-spec connection-spec
427 :error-id (PQstatus connection)
428 :message (PQerrorMessage connection)))
429 connection))))
431 (defmethod database-reconnect ((database postgresql-database))
432 (let ((lock (database-lock database)))
433 (with-process-lock (lock "Reconnecting")
434 (with-slots (connection-spec conn-ptr)
435 database
436 (setf conn-ptr (%pg-database-connection connection-spec))
437 database))))
439 ;;; Database capabilities
441 (when (clsql-sys:database-type-library-loaded :postgresql)
442 (clsql-sys:initialize-database-type :database-type :postgresql))