aaa5fcbfc22b7701e57ae9b2904ca8b9af78a116
[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 ;;;; Authors:  Kevin M. Rosenberg based on original code by Pierre R. Mai
8 ;;;; Created:  Feb 2002
9 ;;;;
10 ;;;; $Id$
11 ;;;;
12 ;;;; This file, part of CLSQL, is Copyright (c) 2002-2004 by Kevin M. Rosenberg
13 ;;;; and Copyright (c) 1999-2001 by Pierre R. Mai
14 ;;;;
15 ;;;; CLSQL users are granted the rights to distribute and use this software
16 ;;;; as governed by the terms of the Lisp Lesser GNU Public License
17 ;;;; (http://opensource.franz.com/preamble.html), also known as the LLGPL.
18 ;;;; *************************************************************************
19
20 (in-package #:cl-user)
21
22 (defpackage :clsql-postgresql-socket
23     (:use #:common-lisp #:clsql-sys #:postgresql-socket)
24     (:export #:postgresql-socket-database)
25     (:documentation "This is the CLSQL socket interface to PostgreSQL."))
26
27 (in-package #:clsql-postgresql-socket)
28
29 ;; interface foreign library loading routines
30
31
32 (clsql-sys:database-type-load-foreign :postgresql-socket)
33
34
35 ;; Field type conversion
36
37 (defun make-type-list-for-auto (cursor)
38   (let* ((fields (postgresql-cursor-fields cursor))
39          (num-fields (length fields))
40          (new-types '()))
41     (dotimes (i num-fields)
42       (declare (fixnum i))
43       (push (canonical-field-type fields i) new-types))
44     (nreverse new-types)))
45
46 (defun canonical-field-type (fields index)
47   "Extracts canonical field type from fields list"
48   (let ((oid (cadr (nth index fields))))
49     (case oid
50       ((#.pgsql-ftype#bytea
51         #.pgsql-ftype#int2
52         #.pgsql-ftype#int4)
53        :int32)
54       (#.pgsql-ftype#int8
55        :int64)
56       ((#.pgsql-ftype#float4
57         #.pgsql-ftype#float8)
58        :double)
59       (otherwise
60        t))))
61
62 (defun canonicalize-types (types cursor)
63   (if (null types)
64       nil
65       (let ((auto-list (make-type-list-for-auto cursor)))
66         (cond
67           ((listp types)
68            (canonicalize-type-list types auto-list))
69           ((eq types :auto)
70            auto-list)
71           (t
72            nil)))))
73
74 (defun canonicalize-type-list (types auto-list)
75   "Ensure a field type list meets expectations.
76 Duplicated from clsql-uffi package so that this interface
77 doesn't depend on UFFI."
78   (let ((length-types (length types))
79         (new-types '()))
80     (loop for i from 0 below (length auto-list)
81           do
82           (if (>= i length-types)
83               (push t new-types) ;; types is shorted than num-fields
84               (push
85                (case (nth i types)
86                  (:int
87                   (case (nth i auto-list)
88                     (:int32
89                      :int32)
90                     (:int64
91                      :int64)
92                     (t
93                      t)))
94                  (:double
95                   (case (nth i auto-list)
96                     (:double
97                      :double)
98                     (t
99                      t)))
100                  (t
101                   t))
102                new-types)))
103     (nreverse new-types)))
104
105
106 (defun convert-to-clsql-warning (database condition)
107   (ecase *backend-warning-behavior*
108     (:warn
109      (warn 'sql-database-warning :database database
110            :message (postgresql-condition-message condition)))
111     (:error
112      (error 'sql-database-error :database database
113             :message (format nil "Warning upgraded to error: ~A"
114                              (postgresql-condition-message condition))))
115     ((:ignore nil)
116      ;; do nothing
117      )))
118
119 (defun convert-to-clsql-error (database expression condition)
120   (error 'sql-database-data-error
121          :database database
122          :expression expression
123          :error-id (type-of condition)
124          :message (postgresql-condition-message condition)))
125
126 (defmacro with-postgresql-handlers
127     ((database &optional expression)
128      &body body)
129   (let ((database-var (gensym))
130         (expression-var (gensym)))
131     `(let ((,database-var ,database)
132            (,expression-var ,expression))
133        (handler-bind ((postgresql-warning
134                        (lambda (c)
135                          (convert-to-clsql-warning ,database-var c)))
136                       (postgresql-error
137                        (lambda (c)
138                          (convert-to-clsql-error
139                           ,database-var ,expression-var c))))
140          ,@body))))
141
142 (defmethod database-initialize-database-type ((database-type
143                                                (eql :postgresql-socket)))
144   t)
145
146 (defclass postgresql-socket-database (generic-postgresql-database)
147   ((connection :accessor database-connection :initarg :connection
148                :type postgresql-connection)))
149
150 (defmethod database-type ((database postgresql-socket-database))
151   :postgresql-socket)
152
153 (defmethod database-name-from-spec (connection-spec
154                                     (database-type (eql :postgresql-socket)))
155   (check-connection-spec connection-spec database-type
156                          (host db user password &optional port options tty))
157   (destructuring-bind (host db user password &optional port options tty)
158       connection-spec
159     (declare (ignore password options tty))
160     (concatenate 'string
161       (etypecase host
162         (null
163          "localhost")
164         (pathname (namestring host))
165         (string host))
166       (when port
167         (concatenate 'string
168                      ":"
169                      (etypecase port
170                        (integer (write-to-string port))
171                        (string port))))
172       "/" db "/" user)))
173
174 (defmethod database-connect (connection-spec
175                              (database-type (eql :postgresql-socket)))
176   (check-connection-spec connection-spec database-type
177                          (host db user password &optional port options tty))
178   (destructuring-bind (host db user password &optional
179                             (port +postgresql-server-default-port+)
180                             (options "") (tty ""))
181       connection-spec
182     (handler-case
183         (handler-bind ((postgresql-warning
184                         (lambda (c)
185                           (warn 'sql-warning
186                                 :format-control "~A"
187                                 :format-arguments
188                                 (list (princ-to-string c))))))
189           (open-postgresql-connection :host host :port port
190                                       :options options :tty tty
191                                       :database db :user user
192                                       :password password))
193       (postgresql-error (c)
194         ;; Connect failed
195         (error 'sql-connection-error
196                :database-type database-type
197                :connection-spec connection-spec
198                :error-id (type-of c)
199                :message (postgresql-condition-message c)))
200       (:no-error (connection)
201                  ;; Success, make instance
202                  (make-instance 'postgresql-socket-database
203                                 :name (database-name-from-spec connection-spec
204                                                                database-type)
205                                 :database-type :postgresql-socket
206                                 :connection-spec connection-spec
207                                 :connection connection)))))
208
209 (defmethod database-disconnect ((database postgresql-socket-database))
210   (close-postgresql-connection (database-connection database))
211   t)
212
213 (defmethod database-query (expression (database postgresql-socket-database) result-types field-names)
214   (let ((connection (database-connection database)))
215     (with-postgresql-handlers (database expression)
216       (start-query-execution connection expression)
217       (multiple-value-bind (status cursor)
218           (wait-for-query-results connection)
219         (unless (eq status :cursor)
220           (close-postgresql-connection connection)
221           (error 'sql-database-data-error
222                  :database database
223                  :expression expression
224                  :error-id "missing-result"
225                  :message "Didn't receive result cursor for query."))
226         (setq result-types (canonicalize-types result-types cursor))
227         (values
228          (loop for row = (read-cursor-row cursor result-types)
229                while row
230                collect row
231                finally
232                (unless (null (wait-for-query-results connection))
233                  (close-postgresql-connection connection)
234                  (error 'sql-database-data-error
235                         :database database
236                         :expression expression
237                         :error-id "multiple-results"
238                         :message "Received multiple results for query.")))
239          (when field-names
240            (mapcar #'car (postgresql-cursor-fields cursor))))))))
241
242 (defmethod database-execute-command
243     (expression (database postgresql-socket-database))
244   (let ((connection (database-connection database)))
245     (with-postgresql-handlers (database expression)
246       (start-query-execution connection expression)
247       (multiple-value-bind (status result)
248           (wait-for-query-results connection)
249         (when (eq status :cursor)
250           (loop
251             (multiple-value-bind (row stuff)
252                 (skip-cursor-row result)
253               (unless row
254                 (setq status :completed result stuff)
255                 (return)))))
256         (cond
257          ((null status)
258           t)
259          ((eq status :completed)
260           (unless (null (wait-for-query-results connection))
261              (close-postgresql-connection connection)
262              (error 'sql-database-data-error
263                     :database database
264                     :expression expression
265                     :error-id "multiple-results"
266                     :message "Received multiple results for command."))
267           result)
268           (t
269            (close-postgresql-connection connection)
270            (error 'sql-database-data-error
271                   :database database
272                   :expression expression
273                   :errno "missing-result"
274                   :message "Didn't receive completion for command.")))))))
275
276 (defstruct postgresql-socket-result-set
277   (done nil)
278   (cursor nil)
279   (types nil))
280
281 (defmethod database-query-result-set ((expression string)
282                                       (database postgresql-socket-database)
283                                       &key full-set result-types)
284   (declare (ignore full-set))
285   (let ((connection (database-connection database)))
286     (with-postgresql-handlers (database expression)
287       (start-query-execution connection expression)
288       (multiple-value-bind (status cursor)
289           (wait-for-query-results connection)
290         (unless (eq status :cursor)
291           (close-postgresql-connection connection)
292           (error 'sql-database-data-error
293                  :database database
294                  :expression expression
295                  :error-id "missing-result"
296                  :message "Didn't receive result cursor for query."))
297         (values (make-postgresql-socket-result-set
298                  :done nil
299                  :cursor cursor
300                  :types (canonicalize-types result-types cursor))
301                 (length (postgresql-cursor-fields cursor)))))))
302
303 (defmethod database-dump-result-set (result-set
304                                      (database postgresql-socket-database))
305   (if (postgresql-socket-result-set-done result-set)
306       t
307       (with-postgresql-handlers (database)
308         (loop while (skip-cursor-row
309                      (postgresql-socket-result-set-cursor result-set))
310           finally (setf (postgresql-socket-result-set-done result-set) t)))))
311
312 (defmethod database-store-next-row (result-set
313                                     (database postgresql-socket-database)
314                                     list)
315   (let ((cursor (postgresql-socket-result-set-cursor result-set)))
316     (with-postgresql-handlers (database)
317       (if (copy-cursor-row cursor
318                            list
319                            (postgresql-socket-result-set-types
320                             result-set))
321           t
322           (prog1 nil
323             (setf (postgresql-socket-result-set-done result-set) t)
324             (wait-for-query-results (database-connection database)))))))
325
326 (defmethod database-create (connection-spec (type (eql :postgresql-socket)))
327   (destructuring-bind (host name user password) connection-spec
328     (let ((database (database-connect (list host "template1" user password)
329                                       type)))
330       (unwind-protect
331            (execute-command (format nil "create database ~A" name))
332         (database-disconnect database)))))
333
334 (defmethod database-destroy (connection-spec (type (eql :postgresql-socket)))
335   (destructuring-bind (host name user password) connection-spec
336     (let ((database (database-connect (list host "template1" user password)
337                                       type)))
338       (unwind-protect
339           (execute-command (format nil "drop database ~A" name))
340         (database-disconnect database)))))
341
342
343 (defmethod database-probe (connection-spec (type (eql :postgresql-socket)))
344   (when (find (second connection-spec) (database-list connection-spec type)
345               :test #'string-equal)
346     t))
347
348
349 ;; Database capabilities
350
351 (defmethod db-backend-has-create/destroy-db? ((db-type (eql :postgresql-socket)))
352   nil)
353
354 (defmethod db-type-has-fancy-math? ((db-type (eql :postgresql-socket)))
355   t)
356
357 (defmethod db-type-default-case ((db-type (eql :postgresql-socket)))
358   :lower)
359
360 (defmethod database-underlying-type ((database postgresql-socket-database))
361   :postgresql)
362
363 (when (clsql-sys:database-type-library-loaded :postgresql-socket)
364   (clsql-sys:initialize-database-type :database-type :postgresql-socket))