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