r4733: *** 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.2 2003/05/02 03:05:54 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 host (if port ":") (if port port) "/" db "/" user)))
154
155 (defmethod database-connect (connection-spec 
156                              (database-type (eql :postgresql-socket)))
157   (check-connection-spec connection-spec database-type
158                          (host db user password &optional port options tty))
159   (destructuring-bind (host db user password &optional
160                             (port +postgresql-server-default-port+)
161                             (options "") (tty ""))
162       connection-spec
163     (handler-case
164         (handler-bind ((postgresql-warning
165                         (lambda (c)
166                           (warn 'clsql-simple-warning
167                                 :format-control "~A"
168                                 :format-arguments
169                                 (list (princ-to-string c))))))
170           (open-postgresql-connection :host host :port port
171                                       :options options :tty tty
172                                       :database db :user user
173                                       :password password))
174       (postgresql-error (c)
175         ;; Connect failed
176         (error 'clsql-connect-error
177                :database-type database-type
178                :connection-spec connection-spec
179                :errno (type-of c)
180                :error (postgresql-condition-message c)))
181       (:no-error (connection)
182                  ;; Success, make instance
183                  (make-instance 'postgresql-socket-database
184                                 :name (database-name-from-spec connection-spec
185                                                                database-type)
186                                 :connection-spec connection-spec
187                                 :connection connection)))))
188
189 (defmethod database-disconnect ((database postgresql-socket-database))
190   (close-postgresql-connection (database-connection database))
191   t)
192
193 (defmethod database-query (expression (database postgresql-socket-database) types)
194   (let ((connection (database-connection database)))
195     (with-postgresql-handlers (database expression)
196       (start-query-execution connection expression)
197       (multiple-value-bind (status cursor)
198           (wait-for-query-results connection)
199         (unless (eq status :cursor)
200           (close-postgresql-connection connection)
201           (error 'clsql-sql-error
202                  :database database
203                  :expression expression
204                  :errno 'missing-result
205                  :error "Didn't receive result cursor for query."))
206         (setq types (canonicalize-types types cursor))
207         (loop for row = (read-cursor-row cursor types)
208               while row
209               collect row
210               finally
211               (unless (null (wait-for-query-results connection))
212                 (close-postgresql-connection connection)
213                 (error 'clsql-sql-error
214                        :database database
215                        :expression expression
216                        :errno 'multiple-results
217                        :error "Received multiple results for query.")))))))
218
219 (defmethod database-execute-command
220     (expression (database postgresql-socket-database))
221   (let ((connection (database-connection database)))
222     (with-postgresql-handlers (database expression)
223       (start-query-execution connection expression)
224       (multiple-value-bind (status result)
225           (wait-for-query-results connection)
226         (when (eq status :cursor)
227           (loop
228               (multiple-value-bind (row stuff)
229                   (skip-cursor-row result)
230                 (unless row
231                   (setq status :completed result stuff)
232                   (return)))))
233         (cond
234           ((null status)
235            t)
236           ((eq status :completed)
237            (unless (null (wait-for-query-results connection))
238              (close-postgresql-connection connection)
239              (error 'clsql-sql-error
240                     :database database
241                     :expression expression
242                     :errno 'multiple-results
243                     :error "Received multiple results for command."))
244            result)
245           (t
246            (close-postgresql-connection connection)
247            (error 'clsql-sql-error
248                   :database database
249                   :expression expression
250                   :errno 'missing-result
251                   :error "Didn't receive completion for command.")))))))
252
253 (defstruct postgresql-socket-result-set
254   (done nil)
255   (cursor nil)
256   (types nil))
257
258 (defmethod database-query-result-set (expression (database postgresql-socket-database) 
259                                       &key full-set types
260      )
261   (declare (ignore full-set))
262   (let ((connection (database-connection database)))
263     (with-postgresql-handlers (database expression)
264       (start-query-execution connection expression)
265       (multiple-value-bind (status cursor)
266           (wait-for-query-results connection)
267         (unless (eq status :cursor)
268           (close-postgresql-connection connection)
269           (error 'clsql-sql-error
270                  :database database
271                  :expression expression
272                  :errno 'missing-result
273                  :error "Didn't receive result cursor for query."))
274         (values (make-postgresql-socket-result-set
275                  :done nil 
276                  :cursor cursor
277                  :types (canonicalize-types types cursor))
278                 (length (postgresql-cursor-fields cursor)))))))
279
280 (defmethod database-dump-result-set (result-set
281                                      (database postgresql-socket-database))
282   (if (postgresql-socket-result-set-done result-set)
283       t
284       (with-postgresql-handlers (database)
285         (loop while (skip-cursor-row 
286                      (postgresql-socket-result-set-cursor result-set))
287           finally (setf (postgresql-socket-result-set-done result-set) t)))))
288
289 (defmethod database-store-next-row (result-set
290                                     (database postgresql-socket-database)
291                                     list)
292   (let ((cursor (postgresql-socket-result-set-cursor result-set)))
293     (with-postgresql-handlers (database)
294       (if (copy-cursor-row cursor 
295                            list
296                            (postgresql-socket-result-set-types
297                             result-set))
298           t
299           (prog1 nil
300             (setf (postgresql-socket-result-set-done result-set) t)
301             (wait-for-query-results (database-connection database)))))))
302
303 (when (clsql-base-sys:database-type-library-loaded :postgresql-socket)
304   (clsql-base-sys:initialize-database-type :database-type :postgresql-socket))