remove changelog, update debian standards version
[clsql/s11.git] / db-postgresql-socket / postgresql-socket-sql.lisp
blob4f8457bfbdfd5a06d62ac8500f0cff857f5a4fb9
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-2007 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 ;;;; *************************************************************************
20 (in-package #:cl-user)
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."))
27 (in-package #:clsql-postgresql-socket)
29 ;; interface foreign library loading routines
32 (clsql-sys:database-type-load-foreign :postgresql-socket)
35 ;; Field type conversion
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)))
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))))
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)
72 nil)))))
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)
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)
93 t)))
94 (:double
95 (case (nth i auto-list)
96 (:double
97 :double)
99 t)))
102 new-types)))
103 (nreverse new-types)))
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
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)))
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))))
142 (defmethod database-initialize-database-type ((database-type
143 (eql :postgresql-socket)))
146 (defclass postgresql-socket-database (generic-postgresql-database)
147 ((connection :accessor database-connection :initarg :connection
148 :type postgresql-connection)))
150 (defmethod database-type ((database postgresql-socket-database))
151 :postgresql-socket)
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
169 (etypecase port
170 (integer (write-to-string port))
171 (string port))))
172 "/" db "/" user)))
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)))))
209 (defmethod database-disconnect ((database postgresql-socket-database))
210 (close-postgresql-connection (database-connection database))
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))))))))
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)
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)
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.")))))))
276 (defstruct postgresql-socket-result-set
277 (done nil)
278 (cursor nil)
279 (types nil))
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)))))))
303 (defmethod database-dump-result-set (result-set
304 (database postgresql-socket-database))
305 (if (postgresql-socket-result-set-done result-set)
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)))))
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))
322 (prog1 nil
323 (setf (postgresql-socket-result-set-done result-set) t)
324 (wait-for-query-results (database-connection database)))))))
326 (defmethod database-create (connection-spec (type (eql :postgresql-socket)))
327 (destructuring-bind (host name user password &optional port options tty) connection-spec
328 (let ((database (database-connect (list host "postgres" user password)
329 type)))
330 (setf (slot-value database 'clsql-sys::state) :open)
331 (unwind-protect
332 (database-execute-command (format nil "create database ~A" name) database)
333 (database-disconnect database)))))
335 (defmethod database-destroy (connection-spec (type (eql :postgresql-socket)))
336 (destructuring-bind (host name user password &optional port optional tty) connection-spec
337 (let ((database (database-connect (list host "postgres" user password)
338 type)))
339 (setf (slot-value database 'clsql-sys::state) :open)
340 (unwind-protect
341 (database-execute-command (format nil "drop database ~A" name) database)
342 (database-disconnect database)))))
345 (defmethod database-probe (connection-spec (type (eql :postgresql-socket)))
346 (when (find (second connection-spec) (database-list connection-spec type)
347 :test #'string-equal)
351 ;; Database capabilities
353 (defmethod db-backend-has-create/destroy-db? ((db-type (eql :postgresql-socket)))
354 nil)
356 (defmethod db-type-has-fancy-math? ((db-type (eql :postgresql-socket)))
359 (defmethod db-type-default-case ((db-type (eql :postgresql-socket)))
360 :lower)
362 (defmethod database-underlying-type ((database postgresql-socket-database))
363 :postgresql)
365 (when (clsql-sys:database-type-library-loaded :postgresql-socket)
366 (clsql-sys:initialize-database-type :database-type :postgresql-socket))