1 ;;;; -*- Mode: LISP; Syntax: ANSI-Common-Lisp; Base: 10 -*-
2 ;;;; *************************************************************************
3 ;;;; FILE IDENTIFICATION
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
11 ;;;; $Id: postgresql-sql.cl,v 1.8 2002/03/25 14:13:41 kevin Exp $
13 ;;;; This file, part of CLSQL, is Copyright (c) 2002 by Kevin M. Rosenberg
14 ;;;; and Copyright (c) 1999-2001 by Pierre R. Mai
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 ;;;; *************************************************************************
21 (declaim (optimize (debug 3) (speed 3) (safety 1) (compilation-speed 0)))
24 (defpackage :clsql-postgresql
25 (:use :common-lisp :clsql-sys :postgresql)
26 (:export #:postgresql-database)
27 (:documentation "This is the CLSQL interface to PostgreSQL."))
29 (in-package :clsql-postgresql)
31 ;;; Field conversion functions
33 (defun canonicalize-field-types (types num-fields res-ptr)
36 (let ((length-types (length types))
38 (loop for i from 0 below num-fields
40 (if (>= i length-types)
41 (push t new-types) ;; types is shorted than num-fields
44 ((:int :long :double t)
49 (nreverse new-types))))
51 (let ((new-types '()))
52 (dotimes (i num-fields)
54 (let* ((type (PQftype res-ptr i)))
61 ((#.pgsql-ftype#float4
67 (nreverse new-types)))
72 (uffi:def-function "atoi"
76 (uffi:def-function "atol"
80 (uffi:def-function "atof"
84 (defun convert-raw-field (char-ptr types index)
85 (let ((type (if (listp types)
96 (uffi:convert-from-foreign-string char-ptr)))))
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)))
104 ((< (length message) (length "ERROR:"))
106 ((string= message "ERROR:" :end1 6)
107 (string-left-trim '(#\Space) (subseq message 6)))
111 (defmethod database-initialize-database-type ((database-type
115 (uffi:def-type pgsql-conn-def pgsql-conn)
116 (uffi:def-type pgsql-result-def pgsql-result)
119 (defclass postgresql-database (database)
120 ((conn-ptr :accessor database-conn-ptr :initarg :conn-ptr
121 :type pgsql-conn-def)))
123 (defmethod database-name-from-spec (connection-spec (database-type
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)
129 (declare (ignore password options tty))
130 (concatenate 'string host (if port ":") (if port port) "/" db "/" user)))
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)
138 (uffi:with-cstrings ((host-native host)
140 (password-native password)
143 (options-native options)
145 (let ((connection (PQsetdbLogin host-native port-native
146 options-native tty-native
147 db-native user-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
161 :conn-ptr connection)))))
164 (defmethod database-disconnect ((database postgresql-database))
165 (PQfinish (database-conn-ptr database))
166 (setf (database-conn-ptr database) nil)
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
177 :expression query-expression
179 :error (tidy-error-message (PQerrorMessage conn-ptr))))
181 (case (PQresultStatus result)
182 (#.pgsql-exec-status-type#empty-query
184 (#.pgsql-exec-status-type#tuples-ok
185 (let ((num-fields (PQnfields result)))
187 (canonicalize-field-types field-types num-fields
189 (loop for tuple-index from 0 below (PQntuples result)
191 (loop for i from 0 below num-fields
193 (if (zerop (PQgetisnull result tuple-index i))
195 (PQgetvalue result tuple-index i)
199 (error 'clsql-sql-error
201 :expression query-expression
202 :errno (PQresultStatus result)
203 :error (tidy-error-message
204 (PQresultErrorMessage result)))))
205 (PQclear result))))))
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
216 :expression sql-expression
218 :error (tidy-error-message (PQerrorMessage conn-ptr))))
220 (case (PQresultStatus result)
221 (#.pgsql-exec-status-type#command-ok
223 ((#.pgsql-exec-status-type#empty-query
224 #.pgsql-exec-status-type#tuples-ok)
225 (warn "Strange result...")
228 (error 'clsql-sql-error
230 :expression sql-expression
231 :errno (PQresultStatus result)
232 :error (tidy-error-message
233 (PQresultErrorMessage result)))))
234 (PQclear result))))))
236 (defstruct postgresql-result-set
237 (res-ptr (uffi:make-null-pointer 'pgsql-result)
238 :type pgsql-result-def)
240 (num-tuples 0 :type integer)
241 (num-fields 0 :type integer)
242 (tuple-index 0 :type integer))
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
253 :expression query-expression
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
261 :num-fields (PQnfields result)
262 :num-tuples (PQntuples result)
263 :field-types (canonicalize-field-types
272 (PQnfields result)))))
275 (error 'clsql-sql-error
277 :expression query-expression
278 :errno (PQresultStatus result)
279 :error (tidy-error-message
280 (PQresultErrorMessage result)))
281 (PQclear result))))))))
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))
289 (defmethod database-store-next-row (result-set (database postgresql-database)
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))
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)
302 (if (zerop (PQgetisnull result tuple-index i))
304 (PQgetvalue result tuple-index i)
308 (incf (postgresql-result-set-tuple-index result-set))