r5585: *** empty log message ***
[clsql.git] / db-postgresql-socket / postgresql-socket-sql.lisp
1 ;;;; -*- Mode: LISP; Syntax: ANSI-Common-Lisp; Base: 10 -*-
2 ;;;; *************************************************************************
3 ;;;; FILE IDENTIFICATION
4 ;;;;
5 ;;;; Name:          postgresql-socket-sql.sql
6 ;;;; Purpose:       High-level PostgreSQL interface using socket
7 ;;;; Programmers:   Kevin M. Rosenberg based on
8 ;;;;                Original code by Pierre R. Mai 
9 ;;;; Date Started:  Feb 2002
10 ;;;;
11 ;;;; $Id: postgresql-socket-sql.lisp,v 1.3 2003/08/31 08:22:16 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-socket
25     (:use :common-lisp :clsql-base-sys :postgresql-socket)
26     (:export #:postgresql-socket-database)
27     (:documentation "This is the CLSQL socket interface to PostgreSQL."))
28
29 (in-package :clsql-postgresql-socket)
30
31 ;; interface foreign library loading routines
32
33
34 (clsql-base-sys:database-type-load-foreign :postgresql-socket)
35
36
37 ;; Field type conversion
38
39 (defun make-type-list-for-auto (cursor)
40   (let* ((fields (postgresql-cursor-fields cursor))
41          (num-fields (length fields))
42          (new-types '()))
43     (dotimes (i num-fields)
44       (declare (fixnum i))
45       (push (canonical-field-type fields i) new-types))
46     (nreverse new-types)))
47
48 (defun canonical-field-type (fields index)
49   "Extracts canonical field type from fields list"
50   (let ((oid (cadr (nth index fields))))
51     (case oid
52       ((#.pgsql-ftype#bytea
53         #.pgsql-ftype#int2
54         #.pgsql-ftype#int4)
55        :int32)
56       (#.pgsql-ftype#int8
57        :int64)
58       ((#.pgsql-ftype#float4
59         #.pgsql-ftype#float8)
60        :double)
61       (otherwise
62        t))))
63
64 (defun canonicalize-types (types cursor)
65   (if (null types)
66       nil
67       (let ((auto-list (make-type-list-for-auto cursor)))
68         (cond
69           ((listp types)
70            (canonicalize-type-list types auto-list))
71           ((eq types :auto)
72            auto-list)
73           (t
74            nil)))))
75
76 (defun canonicalize-type-list (types auto-list)
77   "Ensure a field type list meets expectations.
78 Duplicated from clsql-uffi package so that this interface
79 doesn't depend on UFFI."
80   (let ((length-types (length types))
81         (new-types '()))
82     (loop for i from 0 below (length auto-list)
83           do
84           (if (>= i length-types)
85               (push t new-types) ;; types is shorted than num-fields
86               (push
87                (case (nth i types)
88                  (:int
89                   (case (nth i auto-list)
90                     (:int32
91                      :int32)
92                     (:int64
93                      :int64)
94                     (t
95                      t)))
96                  (:double
97                   (case (nth i auto-list)
98                     (:double
99                      :double)
100                     (t
101                      t)))
102                  (t
103                   t))
104                new-types)))
105     (nreverse new-types)))
106
107
108 (defun convert-to-clsql-warning (database condition)
109   (warn 'clsql-database-warning :database database
110         :message (postgresql-condition-message condition)))
111
112 (defun convert-to-clsql-error (database expression condition)
113   (error 'clsql-sql-error :database database
114          :expression expression
115          :errno (type-of condition)
116          :error (postgresql-condition-message condition)))
117
118 (defmacro with-postgresql-handlers
119     ((database &optional expression)
120      &body body)
121   (let ((database-var (gensym))
122         (expression-var (gensym)))
123     `(let ((,database-var ,database)
124            (,expression-var ,expression))
125        (handler-bind ((postgresql-warning
126                        (lambda (c)
127                          (convert-to-clsql-warning ,database-var c)))
128                       (postgresql-error
129                        (lambda (c)
130                          (convert-to-clsql-error
131                           ,database-var ,expression-var c))))
132          ;; KMR - removed double @@
133          ,@body))))
134
135 (defmethod database-initialize-database-type ((database-type
136                                                (eql :postgresql-socket)))
137   t)
138
139 (defclass postgresql-socket-database (database)
140   ((connection :accessor database-connection :initarg :connection
141                :type postgresql-connection)))
142
143 (defmethod database-type ((database postgresql-socket-database))
144   :postgresql-socket)
145
146 (defmethod database-name-from-spec (connection-spec
147                                     (database-type (eql :postgresql-socket)))
148   (check-connection-spec connection-spec database-type
149                          (host db user password &optional port options tty))
150   (destructuring-bind (host db user password &optional port options tty)
151       connection-spec
152     (declare (ignore password options tty))
153     (concatenate 'string 
154       (etypecase host
155         (pathname (namestring host))
156         (string host))
157       (when port 
158         (concatenate ":"
159           (etypecase port
160             (integer (write-to-string port))
161             (string port))))
162       "/" db "/" user)))
163
164 (defmethod database-connect (connection-spec 
165                              (database-type (eql :postgresql-socket)))
166   (check-connection-spec connection-spec database-type
167                          (host db user password &optional port options tty))
168   (destructuring-bind (host db user password &optional
169                             (port +postgresql-server-default-port+)
170                             (options "") (tty ""))
171       connection-spec
172     (handler-case
173         (handler-bind ((postgresql-warning
174                         (lambda (c)
175                           (warn 'clsql-simple-warning
176                                 :format-control "~A"
177                                 :format-arguments
178                                 (list (princ-to-string c))))))
179           (open-postgresql-connection :host host :port port
180                                       :options options :tty tty
181                                       :database db :user user
182                                       :password password))
183       (postgresql-error (c)
184         ;; Connect failed
185         (error 'clsql-connect-error
186                :database-type database-type
187                :connection-spec connection-spec
188                :errno (type-of c)
189                :error (postgresql-condition-message c)))
190       (:no-error (connection)
191                  ;; Success, make instance
192                  (make-instance 'postgresql-socket-database
193                                 :name (database-name-from-spec connection-spec
194                                                                database-type)
195                                 :connection-spec connection-spec
196                                 :connection connection)))))
197
198 (defmethod database-disconnect ((database postgresql-socket-database))
199   (close-postgresql-connection (database-connection database))
200   t)
201
202 (defmethod database-query (expression (database postgresql-socket-database) types)
203   (let ((connection (database-connection database)))
204     (with-postgresql-handlers (database expression)
205       (start-query-execution connection expression)
206       (multiple-value-bind (status cursor)
207           (wait-for-query-results connection)
208         (unless (eq status :cursor)
209           (close-postgresql-connection connection)
210           (error 'clsql-sql-error
211                  :database database
212                  :expression expression
213                  :errno 'missing-result
214                  :error "Didn't receive result cursor for query."))
215         (setq types (canonicalize-types types cursor))
216         (loop for row = (read-cursor-row cursor types)
217               while row
218               collect row
219               finally
220               (unless (null (wait-for-query-results connection))
221                 (close-postgresql-connection connection)
222                 (error 'clsql-sql-error
223                        :database database
224                        :expression expression
225                        :errno 'multiple-results
226                        :error "Received multiple results for query.")))))))
227
228 (defmethod database-execute-command
229     (expression (database postgresql-socket-database))
230   (let ((connection (database-connection database)))
231     (with-postgresql-handlers (database expression)
232       (start-query-execution connection expression)
233       (multiple-value-bind (status result)
234           (wait-for-query-results connection)
235         (when (eq status :cursor)
236           (loop
237               (multiple-value-bind (row stuff)
238                   (skip-cursor-row result)
239                 (unless row
240                   (setq status :completed result stuff)
241                   (return)))))
242         (cond
243           ((null status)
244            t)
245           ((eq status :completed)
246            (unless (null (wait-for-query-results connection))
247              (close-postgresql-connection connection)
248              (error 'clsql-sql-error
249                     :database database
250                     :expression expression
251                     :errno 'multiple-results
252                     :error "Received multiple results for command."))
253            result)
254           (t
255            (close-postgresql-connection connection)
256            (error 'clsql-sql-error
257                   :database database
258                   :expression expression
259                   :errno 'missing-result
260                   :error "Didn't receive completion for command.")))))))
261
262 (defstruct postgresql-socket-result-set
263   (done nil)
264   (cursor nil)
265   (types nil))
266
267 (defmethod database-query-result-set (expression (database postgresql-socket-database) 
268                                       &key full-set types
269      )
270   (declare (ignore full-set))
271   (let ((connection (database-connection database)))
272     (with-postgresql-handlers (database expression)
273       (start-query-execution connection expression)
274       (multiple-value-bind (status cursor)
275           (wait-for-query-results connection)
276         (unless (eq status :cursor)
277           (close-postgresql-connection connection)
278           (error 'clsql-sql-error
279                  :database database
280                  :expression expression
281                  :errno 'missing-result
282                  :error "Didn't receive result cursor for query."))
283         (values (make-postgresql-socket-result-set
284                  :done nil 
285                  :cursor cursor
286                  :types (canonicalize-types types cursor))
287                 (length (postgresql-cursor-fields cursor)))))))
288
289 (defmethod database-dump-result-set (result-set
290                                      (database postgresql-socket-database))
291   (if (postgresql-socket-result-set-done result-set)
292       t
293       (with-postgresql-handlers (database)
294         (loop while (skip-cursor-row 
295                      (postgresql-socket-result-set-cursor result-set))
296           finally (setf (postgresql-socket-result-set-done result-set) t)))))
297
298 (defmethod database-store-next-row (result-set
299                                     (database postgresql-socket-database)
300                                     list)
301   (let ((cursor (postgresql-socket-result-set-cursor result-set)))
302     (with-postgresql-handlers (database)
303       (if (copy-cursor-row cursor 
304                            list
305                            (postgresql-socket-result-set-types
306                             result-set))
307           t
308           (prog1 nil
309             (setf (postgresql-socket-result-set-done result-set) t)
310             (wait-for-query-results (database-connection database)))))))
311
312 (when (clsql-base-sys:database-type-library-loaded :postgresql-socket)
313   (clsql-base-sys:initialize-database-type :database-type :postgresql-socket))