r7061: initial property settings
[clsql.git] / db-postgresql / postgresql-sql.lisp
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 ;;;; Programmers:   Kevin M. Rosenberg based on
8 ;;;;                Original code by Pierre R. Mai 
9 ;;;; Date Started:  Feb 2002
10 ;;;;
11 ;;;; $Id$
12 ;;;;
13 ;;;; This file, part of CLSQL, is Copyright (c) 2002 by Kevin M. Rosenberg
14 ;;;; and Copyright (c) 1999-2001 by Pierre R. Mai
15 ;;;;
16 ;;;; CLSQL users are granted the rights to distribute and use this software
17 ;;;; as governed by the terms of the Lisp Lesser GNU Public License
18 ;;;; (http://opensource.franz.com/preamble.html), also known as the LLGPL.
19 ;;;; *************************************************************************
20
21 (in-package #:cl-user)
22
23 (defpackage #:clsql-postgresql
24     (:use #:common-lisp #:clsql-base-sys #:postgresql #:clsql-uffi)
25     (:export #:postgresql-database)
26     (:documentation "This is the CLSQL interface to PostgreSQL."))
27
28 (in-package #:clsql-postgresql)
29
30 ;;; Field conversion functions
31
32 (defun make-type-list-for-auto (num-fields res-ptr)
33   (let ((new-types '()))
34     (dotimes (i num-fields)
35       (declare (fixnum i))
36       (let* ((type (PQftype res-ptr i)))
37         (push
38          (case type
39            ((#.pgsql-ftype#bytea
40              #.pgsql-ftype#int2
41              #.pgsql-ftype#int4)
42             :int32)
43            (#.pgsql-ftype#int8
44             :int64)
45            ((#.pgsql-ftype#float4
46              #.pgsql-ftype#float8)
47             :double)
48            (otherwise
49             t))
50          new-types)))
51       (nreverse new-types)))
52
53 (defun canonicalize-types (types num-fields res-ptr)
54   (if (null types)
55       nil
56       (let ((auto-list (make-type-list-for-auto num-fields res-ptr)))
57         (cond
58           ((listp types)
59            (canonicalize-type-list types auto-list))
60           ((eq types :auto)
61            auto-list)
62           (t
63            nil)))))
64
65 (defun tidy-error-message (message)
66   (unless (stringp message)
67     (setq message (uffi:convert-from-foreign-string message)))
68   (let ((message (string-right-trim '(#\Return #\Newline) message)))
69     (cond
70       ((< (length message) (length "ERROR:"))
71        message)
72       ((string= message "ERROR:" :end1 6)
73        (string-left-trim '(#\Space) (subseq message 6)))
74       (t
75        message))))
76
77 (defmethod database-initialize-database-type ((database-type
78                                                (eql :postgresql)))
79   t)
80
81 (uffi:def-type pgsql-conn-def pgsql-conn)
82 (uffi:def-type pgsql-result-def pgsql-result)
83
84
85 (defclass postgresql-database (database)
86   ((conn-ptr :accessor database-conn-ptr :initarg :conn-ptr
87              :type pgsql-conn-def)))
88
89 (defmethod database-type ((database postgresql-database))
90   :postgresql)
91
92 (defmethod database-name-from-spec (connection-spec (database-type
93                                                      (eql :postgresql)))
94   (check-connection-spec connection-spec database-type
95                          (host db user password &optional port options tty))
96   (destructuring-bind (host db user password &optional port options tty)
97       connection-spec
98     (declare (ignore password options tty))
99     (concatenate 'string 
100       (etypecase host
101         (pathname (namestring host))
102         (string host))
103       (when port 
104         (concatenate 'string
105                      ":"
106                      (etypecase port
107                        (integer (write-to-string port))
108                        (string port))))
109       "/" db "/" user)))
110
111
112 (defmethod database-connect (connection-spec (database-type (eql :postgresql)))
113   (check-connection-spec connection-spec database-type
114                          (host db user password &optional port options tty))
115   (destructuring-bind (host db user password &optional port options tty)
116       connection-spec
117     (uffi:with-cstrings ((host-native host)
118                          (user-native user)
119                          (password-native password)
120                          (db-native db)
121                          (port-native port)
122                          (options-native options)
123                          (tty-native tty))
124       (let ((connection (PQsetdbLogin host-native port-native
125                                       options-native tty-native
126                                       db-native user-native
127                                       password-native)))
128         (declare (type pgsql-conn-def connection))
129         (when (not (eq (PQstatus connection) 
130                        pgsql-conn-status-type#connection-ok))
131           (error 'clsql-connect-error
132                  :database-type database-type
133                  :connection-spec connection-spec
134                  :errno (PQstatus connection)
135                  :error (tidy-error-message 
136                          (PQerrorMessage connection))))
137         (make-instance 'postgresql-database
138                        :name (database-name-from-spec connection-spec
139                                                       database-type)
140                        :connection-spec connection-spec
141                        :conn-ptr connection)))))
142
143
144 (defmethod database-disconnect ((database postgresql-database))
145   (PQfinish (database-conn-ptr database))
146   (setf (database-conn-ptr database) nil)
147   t)
148
149 (defmethod database-query (query-expression (database postgresql-database) types)
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 'clsql-sql-error
156                  :database database
157                  :expression query-expression
158                  :errno nil
159                  :error (tidy-error-message (PQerrorMessage conn-ptr))))
160         (unwind-protect
161             (case (PQresultStatus result)
162               (#.pgsql-exec-status-type#empty-query
163                nil)
164               (#.pgsql-exec-status-type#tuples-ok
165                (let ((num-fields (PQnfields result)))
166                  (setq types
167                    (canonicalize-types types num-fields
168                                              result))
169                  (loop for tuple-index from 0 below (PQntuples result)
170                        collect
171                        (loop for i from 0 below num-fields
172                              collect
173                              (if (zerop (PQgetisnull result tuple-index i))
174                                  (convert-raw-field
175                                   (PQgetvalue result tuple-index i)
176                                   types i)
177                                  nil)))))
178               (t
179                (error 'clsql-sql-error
180                       :database database
181                       :expression query-expression
182                       :errno (PQresultStatus result)
183                       :error (tidy-error-message
184                               (PQresultErrorMessage result)))))
185           (PQclear result))))))
186
187 (defmethod database-execute-command (sql-expression
188                                      (database postgresql-database))
189   (let ((conn-ptr (database-conn-ptr database)))
190     (declare (type pgsql-conn-def conn-ptr))
191     (uffi:with-cstring (sql-native sql-expression)
192       (let ((result (PQexec conn-ptr sql-native)))
193         (when (uffi:null-pointer-p result)
194           (error 'clsql-sql-error
195                  :database database
196                  :expression sql-expression
197                  :errno nil
198                  :error (tidy-error-message (PQerrorMessage conn-ptr))))
199         (unwind-protect
200             (case (PQresultStatus result)
201               (#.pgsql-exec-status-type#command-ok
202                t)
203               ((#.pgsql-exec-status-type#empty-query
204                 #.pgsql-exec-status-type#tuples-ok)
205                (warn "Strange result...")
206                t)
207               (t
208                (error 'clsql-sql-error
209                       :database database
210                       :expression sql-expression
211                       :errno (PQresultStatus result)
212                       :error (tidy-error-message
213                               (PQresultErrorMessage result)))))
214           (PQclear result))))))
215
216 (defstruct postgresql-result-set
217   (res-ptr (uffi:make-null-pointer 'pgsql-result) 
218            :type pgsql-result-def)
219   (types nil) 
220   (num-tuples 0 :type integer)
221   (num-fields 0 :type integer)
222   (tuple-index 0 :type integer))
223
224 (defmethod database-query-result-set (query-expression (database postgresql-database) 
225                                       &key full-set types)
226   (let ((conn-ptr (database-conn-ptr database)))
227     (declare (type pgsql-conn-def conn-ptr))
228     (uffi:with-cstring (query-native query-expression)
229       (let ((result (PQexec conn-ptr query-native)))
230         (when (uffi:null-pointer-p result)
231           (error 'clsql-sql-error
232                  :database database
233                  :expression query-expression
234                  :errno nil
235                  :error (tidy-error-message (PQerrorMessage conn-ptr))))
236         (case (PQresultStatus result)
237           ((#.pgsql-exec-status-type#empty-query
238             #.pgsql-exec-status-type#tuples-ok)
239            (let ((result-set (make-postgresql-result-set
240                         :res-ptr result
241                         :num-fields (PQnfields result)
242                         :num-tuples (PQntuples result)
243                         :types (canonicalize-types 
244                                       types
245                                       (PQnfields result)
246                                       result))))
247              (if full-set
248                  (values result-set
249                          (PQnfields result)
250                          (PQntuples result))
251                  (values result-set
252                          (PQnfields result)))))
253           (t
254            (unwind-protect
255                (error 'clsql-sql-error
256                       :database database
257                       :expression query-expression
258                       :errno (PQresultStatus result)
259                       :error (tidy-error-message
260                               (PQresultErrorMessage result)))
261              (PQclear result))))))))
262   
263 (defmethod database-dump-result-set (result-set (database postgresql-database))
264   (let ((res-ptr (postgresql-result-set-res-ptr result-set))) 
265     (declare (type pgsql-result-def res-ptr))
266     (PQclear res-ptr)
267     t))
268
269 (defmethod database-store-next-row (result-set (database postgresql-database) 
270                                     list)
271   (let ((result (postgresql-result-set-res-ptr result-set))
272         (types (postgresql-result-set-types result-set)))
273     (declare (type pgsql-result-def result))
274     (if (>= (postgresql-result-set-tuple-index result-set)
275             (postgresql-result-set-num-tuples result-set))
276         nil
277       (loop with tuple-index = (postgresql-result-set-tuple-index result-set)
278           for i from 0 below (postgresql-result-set-num-fields result-set)
279           for rest on list
280           do
281             (setf (car rest)
282               (if (zerop (PQgetisnull result tuple-index i))
283                   (convert-raw-field
284                    (PQgetvalue result tuple-index i)
285                    types i)
286                 nil))
287           finally
288             (incf (postgresql-result-set-tuple-index result-set))
289             (return list)))))
290
291 ;;; Large objects support (Marc B)
292
293 (defmethod database-create-large-object ((database postgresql-database))
294   (lo-create (database-conn-ptr database)
295              (logior postgresql::+INV_WRITE+ postgresql::+INV_READ+)))
296
297
298 #+mb-original
299 (defmethod database-write-large-object (object-id (data string) (database postgresql-database))
300   (let ((ptr (database-conn-ptr database))
301         (length (length data))
302         (result nil)
303         (fd nil))
304     (with-transaction (:database database)
305        (unwind-protect
306           (progn 
307             (setf fd (lo-open ptr object-id postgresql::+INV_WRITE+))
308             (when (>= fd 0)
309               (when (= (lo-write ptr fd data length) length)
310                 (setf result t))))
311          (progn
312            (when (and fd (>= fd 0))
313              (lo-close ptr fd))
314            )))
315     result))
316
317 (defmethod database-write-large-object (object-id (data string) (database postgresql-database))
318   (let ((ptr (database-conn-ptr database))
319         (length (length data))
320         (result nil)
321         (fd nil))
322     (database-execute-command "begin" database)
323     (unwind-protect
324         (progn 
325           (setf fd (lo-open ptr object-id postgresql::+INV_WRITE+))
326           (when (>= fd 0)
327             (when (= (lo-write ptr fd data length) length)
328               (setf result t))))
329       (progn
330         (when (and fd (>= fd 0))
331           (lo-close ptr fd))
332         (database-execute-command (if result "commit" "rollback") database)))
333     result))
334
335 ;; (MB) the begin/commit/rollback stuff will be removed when with-transaction wil be implemented
336 ;; (KMR) Can't use with-transaction since that function is in high-level code
337 (defmethod database-read-large-object (object-id (database postgresql-database))
338   (let ((ptr (database-conn-ptr database))
339         (buffer nil)
340         (result nil)
341         (length 0)
342         (fd nil))
343     (unwind-protect
344        (progn
345          (database-execute-command "begin" database)
346          (setf fd (lo-open ptr object-id postgresql::+INV_READ+))
347          (when (>= fd 0)
348            (setf length (lo-lseek ptr fd 0 2))
349            (lo-lseek ptr fd 0 0)
350            (when (> length 0)
351              (setf buffer (uffi:allocate-foreign-string 
352                            length :unsigned t))
353              (when (= (lo-read ptr fd buffer length) length)
354                (setf result (uffi:convert-from-foreign-string
355                              buffer :length length :null-terminated-p nil))))))
356       (progn
357         (when buffer (uffi:free-foreign-object buffer))
358         (when (and fd (>= fd 0)) (lo-close ptr fd))
359         (database-execute-command (if result "commit" "rollback") database)))
360     result))
361
362 (defmethod database-delete-large-object (object-id (database postgresql-database))
363   (lo-unlink (database-conn-ptr database) object-id))
364
365 (when (clsql-base-sys:database-type-library-loaded :postgresql)
366   (clsql-base-sys:initialize-database-type :database-type :postgresql))