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