r10937: Automated commit for Debian build of clsql upstream-version-3.6.0
[clsql/s11.git] / db-postgresql / postgresql-sql.lisp
blob9b4e2503f0b5ac415a64147cad594385c20b6283
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 #:postgresql #: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 (error 'sql-connection-error
131 :database-type database-type
132 :connection-spec connection-spec
133 :error-id (PQstatus connection)
134 :message (tidy-error-message
135 (PQerrorMessage connection))))
136 (make-instance 'postgresql-database
137 :name (database-name-from-spec connection-spec
138 database-type)
139 :database-type :postgresql
140 :connection-spec connection-spec
141 :conn-ptr connection)))))
144 (defmethod database-disconnect ((database postgresql-database))
145 (PQfinish (database-conn-ptr database))
146 (setf (database-conn-ptr database) nil)
149 (defmethod database-query (query-expression (database postgresql-database) result-types field-names)
150 (let ((conn-ptr (database-conn-ptr database)))
151 (declare (type pgsql-conn-def conn-ptr))
152 (uffi:with-cstring (query-native query-expression)
153 (let ((result (PQexec conn-ptr query-native)))
154 (when (uffi:null-pointer-p result)
155 (error 'sql-database-data-error
156 :database database
157 :expression query-expression
158 :message (tidy-error-message (PQerrorMessage conn-ptr))))
159 (unwind-protect
160 (case (PQresultStatus result)
161 ;; User gave a command rather than a query
162 (#.pgsql-exec-status-type#command-ok
163 nil)
164 (#.pgsql-exec-status-type#empty-query
165 nil)
166 (#.pgsql-exec-status-type#tuples-ok
167 (let ((num-fields (PQnfields result)))
168 (when result-types
169 (setq result-types
170 (canonicalize-types result-types num-fields
171 result)))
172 (let ((res (loop for tuple-index from 0 below (PQntuples result)
173 collect
174 (loop for i from 0 below num-fields
175 collect
176 (if (zerop (PQgetisnull result tuple-index i))
177 (convert-raw-field
178 (PQgetvalue result tuple-index i)
179 result-types i)
180 nil)))))
181 (if field-names
182 (values res (result-field-names num-fields result))
183 res))))
185 (error 'sql-database-data-error
186 :database database
187 :expression query-expression
188 :error-id (PQresultStatus result)
189 :message (tidy-error-message
190 (PQresultErrorMessage result)))))
191 (PQclear result))))))
193 (defun result-field-names (num-fields result)
194 "Return list of result field names."
195 (let ((names '()))
196 (dotimes (i num-fields (nreverse names))
197 (declare (fixnum i))
198 (push (uffi:convert-from-cstring (PQfname result i)) names))))
200 (defmethod database-execute-command (sql-expression
201 (database postgresql-database))
202 (let ((conn-ptr (database-conn-ptr database)))
203 (declare (type pgsql-conn-def conn-ptr))
204 (uffi:with-cstring (sql-native sql-expression)
205 (let ((result (PQexec conn-ptr sql-native)))
206 (when (uffi:null-pointer-p result)
207 (error 'sql-database-data-error
208 :database database
209 :expression sql-expression
210 :message (tidy-error-message (PQerrorMessage conn-ptr))))
211 (unwind-protect
212 (case (PQresultStatus result)
213 (#.pgsql-exec-status-type#command-ok
215 ((#.pgsql-exec-status-type#empty-query
216 #.pgsql-exec-status-type#tuples-ok)
217 (warn "Strange result...")
220 (error 'sql-database-data-error
221 :database database
222 :expression sql-expression
223 :error-id (PQresultStatus result)
224 :message (tidy-error-message
225 (PQresultErrorMessage result)))))
226 (PQclear result))))))
228 (defstruct postgresql-result-set
229 (res-ptr (uffi:make-null-pointer 'pgsql-result)
230 :type pgsql-result-def)
231 (types nil)
232 (num-tuples 0 :type integer)
233 (num-fields 0 :type integer)
234 (tuple-index 0 :type integer))
236 (defmethod database-query-result-set ((query-expression string)
237 (database postgresql-database)
238 &key full-set result-types)
239 (let ((conn-ptr (database-conn-ptr database)))
240 (declare (type pgsql-conn-def conn-ptr))
241 (uffi:with-cstring (query-native query-expression)
242 (let ((result (PQexec conn-ptr query-native)))
243 (when (uffi:null-pointer-p result)
244 (error 'sql-database-data-error
245 :database database
246 :expression query-expression
247 :message (tidy-error-message (PQerrorMessage conn-ptr))))
248 (case (PQresultStatus result)
249 ((#.pgsql-exec-status-type#empty-query
250 #.pgsql-exec-status-type#tuples-ok)
251 (let ((result-set (make-postgresql-result-set
252 :res-ptr result
253 :num-fields (PQnfields result)
254 :num-tuples (PQntuples result)
255 :types (canonicalize-types
256 result-types
257 (PQnfields result)
258 result))))
259 (if full-set
260 (values result-set
261 (PQnfields result)
262 (PQntuples result))
263 (values result-set
264 (PQnfields result)))))
266 (unwind-protect
267 (error 'sql-database-data-error
268 :database database
269 :expression query-expression
270 :error-id (PQresultStatus result)
271 :message (tidy-error-message
272 (PQresultErrorMessage result)))
273 (PQclear result))))))))
275 (defmethod database-dump-result-set (result-set (database postgresql-database))
276 (let ((res-ptr (postgresql-result-set-res-ptr result-set)))
277 (declare (type pgsql-result-def res-ptr))
278 (PQclear res-ptr)
281 (defmethod database-store-next-row (result-set (database postgresql-database)
282 list)
283 (let ((result (postgresql-result-set-res-ptr result-set))
284 (types (postgresql-result-set-types result-set)))
285 (declare (type pgsql-result-def result))
286 (if (>= (postgresql-result-set-tuple-index result-set)
287 (postgresql-result-set-num-tuples result-set))
289 (loop with tuple-index = (postgresql-result-set-tuple-index result-set)
290 for i from 0 below (postgresql-result-set-num-fields result-set)
291 for rest on list
293 (setf (car rest)
294 (if (zerop (PQgetisnull result tuple-index i))
295 (convert-raw-field
296 (PQgetvalue result tuple-index i)
297 types i)
298 nil))
299 finally
300 (incf (postgresql-result-set-tuple-index result-set))
301 (return list)))))
303 ;;; Large objects support (Marc B)
305 (defmethod database-create-large-object ((database postgresql-database))
306 (lo-create (database-conn-ptr database)
307 (logior postgresql::+INV_WRITE+ postgresql::+INV_READ+)))
310 #+mb-original
311 (defmethod database-write-large-object (object-id (data string) (database postgresql-database))
312 (let ((ptr (database-conn-ptr database))
313 (length (length data))
314 (result nil)
315 (fd nil))
316 (with-transaction (:database database)
317 (unwind-protect
318 (progn
319 (setf fd (lo-open ptr object-id postgresql::+INV_WRITE+))
320 (when (>= fd 0)
321 (when (= (lo-write ptr fd data length) length)
322 (setf result t))))
323 (progn
324 (when (and fd (>= fd 0))
325 (lo-close ptr fd))
327 result))
329 (defmethod database-write-large-object (object-id (data string) (database postgresql-database))
330 (let ((ptr (database-conn-ptr database))
331 (length (length data))
332 (result nil)
333 (fd nil))
334 (database-execute-command "begin" database)
335 (unwind-protect
336 (progn
337 (setf fd (lo-open ptr object-id postgresql::+INV_WRITE+))
338 (when (>= fd 0)
339 (when (= (lo-write ptr fd data length) length)
340 (setf result t))))
341 (progn
342 (when (and fd (>= fd 0))
343 (lo-close ptr fd))
344 (database-execute-command (if result "commit" "rollback") database)))
345 result))
347 ;; (MB) the begin/commit/rollback stuff will be removed when with-transaction wil be implemented
348 ;; (KMR) Can't use with-transaction since that function is in high-level code
349 (defmethod database-read-large-object (object-id (database postgresql-database))
350 (let ((ptr (database-conn-ptr database))
351 (buffer nil)
352 (result nil)
353 (length 0)
354 (fd nil))
355 (unwind-protect
356 (progn
357 (database-execute-command "begin" database)
358 (setf fd (lo-open ptr object-id postgresql::+INV_READ+))
359 (when (>= fd 0)
360 (setf length (lo-lseek ptr fd 0 2))
361 (lo-lseek ptr fd 0 0)
362 (when (> length 0)
363 (setf buffer (uffi:allocate-foreign-string
364 length :unsigned t))
365 (when (= (lo-read ptr fd buffer length) length)
366 (setf result (uffi:convert-from-foreign-string
367 buffer :length length :null-terminated-p nil))))))
368 (progn
369 (when buffer (uffi:free-foreign-object buffer))
370 (when (and fd (>= fd 0)) (lo-close ptr fd))
371 (database-execute-command (if result "commit" "rollback") database)))
372 result))
374 (defmethod database-delete-large-object (object-id (database postgresql-database))
375 (lo-unlink (database-conn-ptr database) object-id))
378 ;;; Object listing
382 (defmethod database-create (connection-spec (type (eql :postgresql)))
383 (destructuring-bind (host name user password) connection-spec
384 (declare (ignore user password))
385 (multiple-value-bind (output status)
386 (clsql-sys:command-output "createdb -h~A ~A"
387 (if host host "localhost")
388 name)
389 (if (or (not (zerop status))
390 (search "database creation failed: ERROR:" output))
391 (error 'sql-database-error
392 :message
393 (format nil "createdb failed for postgresql backend with connection spec ~A."
394 connection-spec))
395 t))))
397 (defmethod database-destroy (connection-spec (type (eql :postgresql)))
398 (destructuring-bind (host name user password) connection-spec
399 (declare (ignore user password))
400 (multiple-value-bind (output status)
401 (clsql-sys:command-output "dropdb -h~A ~A"
402 (if host host "localhost")
403 name)
404 (if (or (not (zerop status))
405 (search "database removal failed: ERROR:" output))
406 (error 'sql-database-error
407 :message
408 (format nil "dropdb failed for postgresql backend with connection spec ~A."
409 connection-spec))
410 t))))
413 (defmethod database-probe (connection-spec (type (eql :postgresql)))
414 (when (find (second connection-spec) (database-list connection-spec type)
415 :test #'string-equal)
419 (defun %pg-database-connection (connection-spec)
420 (check-connection-spec connection-spec :postgresql
421 (host db user password &optional port options tty))
422 (macrolet ((coerce-string (var)
423 `(unless (typep ,var 'simple-base-string)
424 (setf ,var (coerce ,var 'simple-base-string)))))
425 (destructuring-bind (host db user password &optional port options tty)
426 connection-spec
427 (coerce-string db)
428 (coerce-string user)
429 (let ((connection (PQsetdbLogin host port options tty db user password)))
430 (declare (type postgresql::pgsql-conn-ptr connection))
431 (unless (eq (PQstatus connection)
432 pgsql-conn-status-type#connection-ok)
433 ;; Connect failed
434 (error 'sql-connection-error
435 :database-type :postgresql
436 :connection-spec connection-spec
437 :error-id (PQstatus connection)
438 :message (PQerrorMessage connection)))
439 connection))))
441 (defmethod database-reconnect ((database postgresql-database))
442 (let ((lock (database-lock database)))
443 (with-process-lock (lock "Reconnecting")
444 (with-slots (connection-spec conn-ptr)
445 database
446 (setf conn-ptr (%pg-database-connection connection-spec))
447 database))))
449 ;;; Database capabilities
451 (when (clsql-sys:database-type-library-loaded :postgresql)
452 (clsql-sys:initialize-database-type :database-type :postgresql))