7e79bd1a1a553d11ea20755cdb4c79e3b4396650
[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.9 2002/03/29 08:28:14 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   (let ((auto-list (make-type-list-for-auto cursor)))
71     (cond
72       ((listp types)
73        (canonicalize-type-list types auto-list))
74       ((eq types :auto)
75        auto-list)
76       (t
77        nil))))
78
79 (defun canonicalize-type-list (types auto-list)
80   "Ensure a field type list meets expectations.
81 Duplicated from clsql-uffi package so that this interface
82 doesn't depend on UFFI."
83   (let ((length-types (length types))
84         (new-types '()))
85     (loop for i from 0 below (length auto-list)
86           do
87           (if (>= i length-types)
88               (push t new-types) ;; types is shorted than num-fields
89               (push
90                (case (nth i types)
91                  (:int
92                   (case (nth i auto-list)
93                     (:int32
94                      :int32)
95                     (:int64
96                      :int64)
97                     (t
98                      t)))
99                  (:double
100                   (case (nth i auto-list)
101                     (:double
102                      :double)
103                     (t
104                      t)))
105                  (t
106                   t))
107                new-types)))
108     (nreverse new-types)))
109
110
111 (defun convert-to-clsql-warning (database condition)
112   (warn 'clsql-database-warning :database database
113         :message (postgresql-condition-message condition)))
114
115 (defun convert-to-clsql-error (database expression condition)
116   (error 'clsql-sql-error :database database
117          :expression expression
118          :errno (type-of condition)
119          :error (postgresql-condition-message condition)))
120
121 (defmacro with-postgresql-handlers
122     ((database &optional expression)
123      &body body)
124   (let ((database-var (gensym))
125         (expression-var (gensym)))
126     `(let ((,database-var ,database)
127            (,expression-var ,expression))
128        (handler-bind ((postgresql-warning
129                        (lambda (c)
130                          (convert-to-clsql-warning ,database-var c)))
131                       (postgresql-error
132                        (lambda (c)
133                          (convert-to-clsql-error
134                           ,database-var ,expression-var c))))
135          ;; KMR - removed double @@
136          ,@body))))
137
138 (defmethod database-initialize-database-type
139     ((database-type (eql :postgresql-socket)))
140   t)
141
142 (defclass postgresql-socket-database (database)
143   ((connection :accessor database-connection :initarg :connection
144                :type postgresql-connection)))
145
146 (defmethod database-name-from-spec
147     (connection-spec (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
156     (connection-spec (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       (:no-error (connection)
175         ;; Success, make instance
176         (make-instance 'postgresql-socket-database
177                        :name (database-name-from-spec connection-spec
178                                                       database-type)
179                        :connection connection))
180       (postgresql-error (c)
181         ;; Connect failed
182         (error 'clsql-connect-error
183                :database-type database-type
184                :connection-spec connection-spec
185                :errno (type-of c)
186                :error (postgresql-condition-message c))))))
187
188 (defmethod database-disconnect ((database postgresql-socket-database))
189   (close-postgresql-connection (database-connection database))
190   t)
191
192 (defmethod database-query (expression (database postgresql-socket-database) types)
193   (let ((connection (database-connection database)))
194     (with-postgresql-handlers (database expression)
195       (start-query-execution connection expression)
196       (multiple-value-bind (status cursor)
197           (wait-for-query-results connection)
198         (unless (eq status :cursor)
199           (close-postgresql-connection connection)
200           (error 'clsql-sql-error
201                  :database database
202                  :expression expression
203                  :errno 'missing-result
204                  :error "Didn't receive result cursor for query."))
205         (setq types (canonicalize-types types cursor))
206         (loop for row = (read-cursor-row cursor types)
207               while row
208               collect row
209               finally
210               (unless (null (wait-for-query-results connection))
211                 (close-postgresql-connection connection)
212                 (error 'clsql-sql-error
213                        :database database
214                        :expression expression
215                        :errno 'multiple-results
216                        :error "Received multiple results for query.")))))))
217
218 (defmethod database-execute-command
219     (expression (database postgresql-socket-database))
220   (let ((connection (database-connection database)))
221     (with-postgresql-handlers (database expression)
222       (start-query-execution connection expression)
223       (multiple-value-bind (status result)
224           (wait-for-query-results connection)
225         (when (eq status :cursor)
226           (loop
227               (multiple-value-bind (row stuff)
228                   (skip-cursor-row result)
229                 (unless row
230                   (setq status :completed result stuff)
231                   (return)))))
232         (cond
233           ((null status)
234            t)
235           ((eq status :completed)
236            (unless (null (wait-for-query-results connection))
237              (close-postgresql-connection connection)
238              (error 'clsql-sql-error
239                     :database database
240                     :expression expression
241                     :errno 'multiple-results
242                     :error "Received multiple results for command."))
243            result)
244           (t
245            (close-postgresql-connection connection)
246            (error 'clsql-sql-error
247                   :database database
248                   :expression expression
249                   :errno 'missing-result
250                   :error "Didn't receive completion for command.")))))))
251
252 (defstruct postgresql-socket-result-set
253   (done nil)
254   (cursor nil)
255   (types nil))
256
257 (defmethod database-query-result-set (expression (database postgresql-socket-database) 
258                                       &key full-set types
259      )
260   (declare (ignore full-set))
261   (let ((connection (database-connection database)))
262     (with-postgresql-handlers (database expression)
263       (start-query-execution connection expression)
264       (multiple-value-bind (status cursor)
265           (wait-for-query-results connection)
266         (unless (eq status :cursor)
267           (close-postgresql-connection connection)
268           (error 'clsql-sql-error
269                  :database database
270                  :expression expression
271                  :errno 'missing-result
272                  :error "Didn't receive result cursor for query."))
273         (values (make-postgresql-socket-result-set
274                  :done nil 
275                  :cursor cursor
276                  :types (canonicalize-types types cursor))
277                 (length (postgresql-cursor-fields cursor)))))))
278
279 (defmethod database-dump-result-set (result-set
280                                      (database postgresql-socket-database))
281   (if (postgresql-socket-result-set-done result-set)
282       t
283       (with-postgresql-handlers (database)
284         (loop while (skip-cursor-row 
285                      (postgresql-socket-result-set-cursor result-set))
286           finally (setf (postgresql-socket-result-set-done result-set) t)))))
287
288 (defmethod database-store-next-row (result-set
289                                     (database postgresql-socket-database)
290                                     list)
291   (let ((cursor (postgresql-socket-result-set-cursor result-set)))
292     (with-postgresql-handlers (database)
293       (if (copy-cursor-row cursor 
294                            list
295                            (postgresql-socket-result-set-types
296                             result-set))
297           t
298           (prog1 nil
299             (setf (postgresql-socket-result-set-done result-set) t)
300             (wait-for-query-results (database-connection database)))))))