;;;;
;;;; Date Started: Feb 2002
;;;;
-;;;; $Id: postgresql-socket-api.cl,v 1.2 2002/03/24 04:01:26 kevin Exp $
+;;;; $Id: postgresql-socket-api.cl,v 1.14 2002/04/07 15:10:01 kevin Exp $
;;;;
;;;; This file, part of CLSQL, is Copyright (c) 2002 by Kevin M. Rosenberg
;;;; and Copyright (c) 1999-2001 by Pierre R. Mai
;;;; - Added necessary (force-output) for socket streams on
;;;; Allegro and Lispworks
;;;; - Added initialization variable
+;;;; - Added field type processing
+
(declaim (optimize (debug 3) (speed 3) (safety 1) (compilation-speed 0)))
(in-package :postgresql-socket)
+(uffi:def-enum pgsql-ftype
+ ((:bytea 17)
+ (:int2 21)
+ (:int4 23)
+ (:int8 20)
+ (:float4 700)
+ (:float8 701)))
(defmethod database-type-library-loaded ((database-type
(eql :postgresql-socket)))
(defun read-socket-sequence (string stream)
"KMR -- Added to support reading from binary stream into a string"
- (declare (optimize (speed 3) (safety 0)))
+ (declare (optimize (speed 3) (safety 0))
+ (string string))
(dotimes (i (length string))
(declare (fixnum i))
(setf (char string i) (code-char (read-byte stream))))
;;; Support for encrypted password transmission
-(defconstant +crypt-library+ "/usr/lib/libcrypt.so"
- "Name of the shared library to load in order to access the crypt
-function named by `*crypt-function-name*'.")
-
(defvar *crypt-library-loaded* nil)
(defun crypt-password (password salt)
"Encrypt a password for transmission to a PostgreSQL server."
(unless *crypt-library-loaded*
- (uffi:load-foreign-library +crypt-library+ :supporting-libaries '("c"))
- (eval (uffi:def-function "crypt"
- ((key :cstring)
- (salt :cstring))
+ (uffi:load-foreign-library
+ (uffi:find-foreign-library "libcrypt"
+ '("/usr/lib/" "/usr/local/lib/" "/lib/"))
+ :supporting-libaries '("c"))
+ (eval '(uffi:def-function "crypt"
+ ((key :cstring)
+ (salt :cstring))
:returning :cstring))
(setq *crypt-library-loaded* t))
(uffi:with-cstring (password-cstring password)
(uffi:with-cstring (salt-cstring salt)
- (uffi:convert-from-cstring (crypt password-cstring salt-cstring)))))
+ (uffi:convert-from-cstring
+ (funcall (fdefinition 'crypt) password-cstring salt-cstring)))))
;;; Condition hierarchy
(define-condition postgresql-condition (condition)
do (setf (aref result index) (ldb (byte 1 weight) byte))))
result))
-(defun read-cursor-row (cursor)
+
+(defun read-field (socket type)
+ (let ((length (- (read-socket-value 'int32 socket) 4)))
+ (case type
+ ((:int32 :int64)
+ (read-integer-from-socket socket length))
+ (:double
+ (read-double-from-socket socket length))
+ (t
+ (let ((result (make-string length)))
+ (read-socket-sequence result socket)
+ result)))))
+
+(uffi:def-constant +char-code-zero+ (char-code #\0))
+(uffi:def-constant +char-code-minus+ (char-code #\-))
+(uffi:def-constant +char-code-plus+ (char-code #\+))
+(uffi:def-constant +char-code-period+ (char-code #\.))
+(uffi:def-constant +char-code-lower-e+ (char-code #\e))
+(uffi:def-constant +char-code-upper-e+ (char-code #\E))
+
+(defun read-integer-from-socket (socket length)
+ (declare (fixnum length))
+ (if (zerop length)
+ nil
+ (let ((val 0)
+ (first-char (read-byte socket))
+ (minusp nil))
+ (declare (fixnum first-char))
+ (decf length) ;; read first char
+ (cond
+ ((= first-char +char-code-minus+)
+ (setq minusp t))
+ ((= first-char +char-code-plus+)
+ ) ;; nothing to do
+ (t
+ (setq val (- first-char +char-code-zero+))))
+
+ (dotimes (i length)
+ (declare (fixnum i))
+ (setq val (+
+ (* 10 val)
+ (- (read-byte socket) +char-code-zero+))))
+ (if minusp
+ (- val)
+ val))))
+
+(defmacro ascii-digit (int)
+ (let ((offset (gensym)))
+ `(let ((,offset (- ,int +char-code-zero+)))
+ (declare (fixnum ,int ,offset))
+ (if (and (>= ,offset 0)
+ (< ,offset 10))
+ ,offset
+ nil))))
+
+(defun read-double-from-socket (socket length)
+ (declare (fixnum length))
+ (let ((before-decimal 0)
+ (after-decimal 0)
+ (decimal-count 0)
+ (exponent 0)
+ (decimalp nil)
+ (minusp nil)
+ (result nil)
+ (char (read-byte socket)))
+ (declare (fixnum char exponent decimal-count))
+ (decf length) ;; already read first character
+ (cond
+ ((= char +char-code-minus+)
+ (setq minusp t))
+ ((= char +char-code-plus+)
+ )
+ ((= char +char-code-period+)
+ (setq decimalp t))
+ (t
+ (setq before-decimal (ascii-digit char))
+ (unless before-decimal
+ (error "Unexpected value"))))
+
+ (block loop
+ (dotimes (i length)
+ (setq char (read-byte socket))
+ ;; (format t "~&len:~D, i:~D, char:~D, minusp:~A, decimalp:~A" length i char minusp decimalp)
+ (let ((weight (ascii-digit char)))
+ (cond
+ ((and weight (not decimalp)) ;; before decimal point
+ (setq before-decimal (+ weight (* 10 before-decimal))))
+ ((and weight decimalp) ;; after decimal point
+ (setq after-decimal (+ weight (* 10 after-decimal)))
+ (incf decimal-count))
+ ((and (= char +char-code-period+))
+ (setq decimalp t))
+ ((or (= char +char-code-lower-e+) ;; E is for exponent
+ (= char +char-code-upper-e+))
+ (setq exponent (read-integer-from-socket socket (- length i 1)))
+ (setq exponent (or exponent 0))
+ (return-from loop))
+ (t
+ (break "Unexpected value"))
+ )
+ )))
+ (setq result (* (+ (coerce before-decimal 'double-float)
+ (* after-decimal
+ (expt 10 (- decimal-count))))
+ (expt 10 exponent)))
+ (if minusp
+ (- result)
+ result)))
+
+
+#+ignore
+(defun read-double-from-socket (socket length)
+ (let ((result (make-string length)))
+ (read-socket-sequence result socket)
+ (let ((*read-default-float-format* 'double-float))
+ (read-from-string result))))
+
+(defun read-cursor-row (cursor types)
(let* ((connection (postgresql-cursor-connection cursor))
(socket (postgresql-connection-socket connection))
(fields (postgresql-cursor-fields cursor)))
with null-vector = (read-null-bit-vector socket count)
repeat count
for null-bit across null-vector
+ for i from 0
for null-p = (zerop null-bit)
if null-p
collect nil
else
collect
- (let* ((length (read-socket-value 'int32 socket))
- (result (make-string (- length 4))))
- (read-socket-sequence result socket)
- result))))
+ (read-field socket (nth i types)))))
(#.+binary-row-message+
(error "NYI"))
(#.+completed-response-message+
(error 'postgresql-fatal-error :connection connection
:message "Received garbled message from backend")))))))
-(defun copy-cursor-row (cursor sequence)
+(defun map-into-indexed (result-seq func seq)
+ (dotimes (i (length seq))
+ (declare (fixnum i))
+ (setf (elt result-seq i)
+ (funcall func (elt seq i) i)))
+ result-seq)
+
+(defun copy-cursor-row (cursor sequence types)
(let* ((connection (postgresql-cursor-connection cursor))
(socket (postgresql-connection-socket connection))
(fields (postgresql-cursor-fields cursor)))
(case code
(#.+ascii-row-message+
(return
- (map-into
+ #+ignore
+ (let* ((count (length sequence))
+ (null-vector (read-null-bit-vector socket count)))
+ (dotimes (i count)
+ (declare (fixnum i))
+ (if (zerop (elt null-vector i))
+ (setf (elt sequence i) nil)
+ (let ((value (read-field socket (nth i types))))
+ (setf (elt sequence i) value)))))
+ (map-into-indexed
sequence
- #'(lambda (null-bit)
+ #'(lambda (null-bit i)
(if (zerop null-bit)
nil
- (let* ((length (read-socket-value 'int32 socket))
- (result (make-string (- length 4))))
- (read-socket-sequence result socket)
- result)))
+ (read-field socket (nth i types))))
(read-null-bit-vector socket (length sequence)))))
(#.+binary-row-message+
(error "NYI"))
(error 'postgresql-fatal-error :connection connection
:message "Received garbled message from backend")))))))
-(defun run-query (connection query)
+(defun run-query (connection query &optional (types nil))
(start-query-execution connection query)
(multiple-value-bind (status cursor)
(wait-for-query-results connection)
(assert (eq status :cursor))
- (loop for row = (read-cursor-row cursor)
+ (loop for row = (read-cursor-row cursor types)
while row
collect row
finally