r1657: Auto :types implemented for mysql,postgresql
[clsql.git] / interfaces / postgresql / postgresql-sql.cl
1 ;;;; -*- Mode: LISP; Syntax: ANSI-Common-Lisp; Base: 10 -*-
2 ;;;; *************************************************************************
3 ;;;; FILE IDENTIFICATION
4 ;;;;
5 ;;;; Name:          postgresql-sql.sql
6 ;;;; Purpose:       High-level PostgreSQL interface using UFFI
7 ;;;; Programmers:   Kevin M. Rosenberg based on
8 ;;;;                Original code by Pierre R. Mai 
9 ;;;; Date Started:  Feb 2002
10 ;;;;
11 ;;;; $Id: postgresql-sql.cl,v 1.8 2002/03/25 14:13:41 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
25     (:use :common-lisp :clsql-sys :postgresql)
26     (:export #:postgresql-database)
27     (:documentation "This is the CLSQL interface to PostgreSQL."))
28
29 (in-package :clsql-postgresql)
30
31 ;;; Field conversion functions
32
33 (defun canonicalize-field-types (types num-fields res-ptr)
34   (cond
35    ((if (listp types)
36         (let ((length-types (length types))
37               (new-types '()))
38           (loop for i from 0 below num-fields
39               do
40                 (if (>= i length-types)
41                     (push t new-types) ;; types is shorted than num-fields
42                   (push
43                    (case (nth i types)
44                      ((:int :long :double t)
45                       (nth i types))
46                      (t
47                       t))
48                    new-types)))
49           (nreverse new-types))))
50    ((eq types :auto)
51     (let ((new-types '()))
52       (dotimes (i num-fields)
53         (declare (fixnum i))
54         (let* ((type (PQftype res-ptr i)))
55           (push
56            (case type
57              ((#.pgsql-ftype#bytea
58                #.pgsql-ftype#int2
59                #.pgsql-ftype#int4)
60               :int)
61              ((#.pgsql-ftype#float4
62                #.pgsql-ftype#float8)
63               :double)
64              (otherwise
65               t))
66            new-types)))
67       (nreverse new-types)))
68    (t
69     nil)))
70
71
72 (uffi:def-function "atoi"
73     ((str :cstring))
74   :returning :int)
75
76 (uffi:def-function "atol"
77     ((str :cstring))
78   :returning :long)
79
80 (uffi:def-function "atof"
81     ((str :cstring))
82   :returning :double)
83
84 (defun convert-raw-field (char-ptr types index)
85   (let ((type (if (listp types)
86                   (nth index types)
87                   types)))
88     (case type
89       (:int
90        (atoi char-ptr))
91       (:long
92        (atol char-ptr))
93       (:double
94        (atof char-ptr))
95       (otherwise
96        (uffi:convert-from-foreign-string char-ptr)))))
97
98
99 (defun tidy-error-message (message)
100   (unless (stringp message)
101     (setq message (uffi:convert-from-foreign-string message)))
102   (let ((message (string-right-trim '(#\Return #\Newline) message)))
103     (cond
104       ((< (length message) (length "ERROR:"))
105        message)
106       ((string= message "ERROR:" :end1 6)
107        (string-left-trim '(#\Space) (subseq message 6)))
108       (t
109        message))))
110
111 (defmethod database-initialize-database-type ((database-type
112                                                (eql :postgresql)))
113   t)
114
115 (uffi:def-type pgsql-conn-def pgsql-conn)
116 (uffi:def-type pgsql-result-def pgsql-result)
117
118
119 (defclass postgresql-database (database)
120   ((conn-ptr :accessor database-conn-ptr :initarg :conn-ptr
121              :type pgsql-conn-def)))
122
123 (defmethod database-name-from-spec (connection-spec (database-type
124                                                      (eql :postgresql)))
125   (check-connection-spec connection-spec database-type
126                          (host db user password &optional port options tty))
127   (destructuring-bind (host db user password &optional port options tty)
128       connection-spec
129     (declare (ignore password options tty))
130     (concatenate 'string host (if port ":") (if port port) "/" db "/" user)))
131
132
133 (defmethod database-connect (connection-spec (database-type (eql :postgresql)))
134   (check-connection-spec connection-spec database-type
135                          (host db user password &optional port options tty))
136   (destructuring-bind (host db user password &optional port options tty)
137       connection-spec
138     (uffi:with-cstrings ((host-native host)
139                          (user-native user)
140                          (password-native password)
141                          (db-native db)
142                          (port-native port)
143                          (options-native options)
144                          (tty-native tty))
145       (let ((connection (PQsetdbLogin host-native port-native
146                                       options-native tty-native
147                                       db-native user-native
148                                       password-native)))
149         (declare (type pgsql-conn-def connection))
150         (when (not (eq (PQstatus connection) 
151                        pgsql-conn-status-type#connection-ok))
152           (error 'clsql-connect-error
153                  :database-type database-type
154                  :connection-spec connection-spec
155                  :errno (PQstatus connection)
156                  :error (tidy-error-message 
157                          (PQerrorMessage connection))))
158         (make-instance 'postgresql-database
159                        :name (database-name-from-spec connection-spec
160                                                       database-type)
161                        :conn-ptr connection)))))
162
163
164 (defmethod database-disconnect ((database postgresql-database))
165   (PQfinish (database-conn-ptr database))
166   (setf (database-conn-ptr database) nil)
167   t)
168
169 (defmethod database-query (query-expression (database postgresql-database) field-types)
170   (let ((conn-ptr (database-conn-ptr database)))
171     (declare (type pgsql-conn-def conn-ptr))
172     (uffi:with-cstring (query-native query-expression)
173       (let ((result (PQexec conn-ptr query-native)))
174         (when (uffi:null-pointer-p result)
175           (error 'clsql-sql-error
176                  :database database
177                  :expression query-expression
178                  :errno nil
179                  :error (tidy-error-message (PQerrorMessage conn-ptr))))
180         (unwind-protect
181             (case (PQresultStatus result)
182               (#.pgsql-exec-status-type#empty-query
183                nil)
184               (#.pgsql-exec-status-type#tuples-ok
185                (let ((num-fields (PQnfields result)))
186                  (setq field-types
187                    (canonicalize-field-types field-types num-fields
188                                              result))
189                  (loop for tuple-index from 0 below (PQntuples result)
190                        collect
191                        (loop for i from 0 below num-fields
192                              collect
193                              (if (zerop (PQgetisnull result tuple-index i))
194                                  (convert-raw-field
195                                   (PQgetvalue result tuple-index i)
196                                   field-types i)
197                                  nil)))))
198               (t
199                (error 'clsql-sql-error
200                       :database database
201                       :expression query-expression
202                       :errno (PQresultStatus result)
203                       :error (tidy-error-message
204                               (PQresultErrorMessage result)))))
205           (PQclear result))))))
206
207 (defmethod database-execute-command (sql-expression
208                                      (database postgresql-database))
209   (let ((conn-ptr (database-conn-ptr database)))
210     (declare (type pgsql-conn-def conn-ptr))
211     (uffi:with-cstring (sql-native sql-expression)
212       (let ((result (PQexec conn-ptr sql-native)))
213         (when (uffi:null-pointer-p result)
214           (error 'clsql-sql-error
215                  :database database
216                  :expression sql-expression
217                  :errno nil
218                  :error (tidy-error-message (PQerrorMessage conn-ptr))))
219         (unwind-protect
220             (case (PQresultStatus result)
221               (#.pgsql-exec-status-type#command-ok
222                t)
223               ((#.pgsql-exec-status-type#empty-query
224                 #.pgsql-exec-status-type#tuples-ok)
225                (warn "Strange result...")
226                t)
227               (t
228                (error 'clsql-sql-error
229                       :database database
230                       :expression sql-expression
231                       :errno (PQresultStatus result)
232                       :error (tidy-error-message
233                               (PQresultErrorMessage result)))))
234           (PQclear result))))))
235
236 (defstruct postgresql-result-set
237   (res-ptr (uffi:make-null-pointer 'pgsql-result) 
238            :type pgsql-result-def)
239   (field-types nil) 
240   (num-tuples 0 :type integer)
241   (num-fields 0 :type integer)
242   (tuple-index 0 :type integer))
243
244 (defmethod database-query-result-set (query-expression (database postgresql-database) 
245                                       &key full-set field-types)
246   (let ((conn-ptr (database-conn-ptr database)))
247     (declare (type pgsql-conn-def conn-ptr))
248     (uffi:with-cstring (query-native query-expression)
249       (let ((result (PQexec conn-ptr query-native)))
250         (when (uffi:null-pointer-p result)
251           (error 'clsql-sql-error
252                  :database database
253                  :expression query-expression
254                  :errno nil
255                  :error (tidy-error-message (PQerrorMessage conn-ptr))))
256         (case (PQresultStatus result)
257           ((#.pgsql-exec-status-type#empty-query
258             #.pgsql-exec-status-type#tuples-ok)
259            (let ((result-set (make-postgresql-result-set
260                         :res-ptr result
261                         :num-fields (PQnfields result)
262                         :num-tuples (PQntuples result)
263                         :field-types (canonicalize-field-types 
264                                       field-types
265                                       (PQnfields result)
266                                       result))))
267              (if full-set
268                  (values result-set
269                          (PQnfields result)
270                          (PQntuples result))
271                  (values result-set
272                          (PQnfields result)))))
273           (t
274            (unwind-protect
275                (error 'clsql-sql-error
276                       :database database
277                       :expression query-expression
278                       :errno (PQresultStatus result)
279                       :error (tidy-error-message
280                               (PQresultErrorMessage result)))
281              (PQclear result))))))))
282   
283 (defmethod database-dump-result-set (result-set (database postgresql-database))
284   (let ((res-ptr (postgresql-result-set-res-ptr result-set))) 
285     (declare (type pgsql-result-def res-ptr))
286     (PQclear res-ptr)
287     t))
288
289 (defmethod database-store-next-row (result-set (database postgresql-database) 
290                                     list)
291   (let ((result (postgresql-result-set-res-ptr result-set))
292         (field-types (postgresql-result-set-field-types result-set)))
293     (declare (type pgsql-result-def result))
294     (if (>= (postgresql-result-set-tuple-index result-set)
295             (postgresql-result-set-num-tuples result-set))
296         nil
297       (loop with tuple-index = (postgresql-result-set-tuple-index result-set)
298           for i from 0 below (postgresql-result-set-num-fields result-set)
299           for rest on list
300           do
301             (setf (car rest)
302               (if (zerop (PQgetisnull result tuple-index i))
303                   (convert-raw-field
304                    (PQgetvalue result tuple-index i)
305                    field-types i)
306                 nil))
307           finally
308             (incf (postgresql-result-set-tuple-index result-set))
309             (return list)))))