;;;; *************************************************************************
;;;; FILE IDENTIFICATION
;;;;
-;;;; Name: postgresql-socket-api.lisp
-;;;; Purpose: Low-level PostgreSQL interface using sockets
-;;;; Programmers: Kevin M. Rosenberg based on
-;;;; Original code by Pierre R. Mai
-;;;;
-;;;; Date Started: Feb 2002
+;;;; Name: postgresql-socket-api.lisp
+;;;; Purpose: Low-level PostgreSQL interface using sockets
+;;;; Authors: Kevin M. Rosenberg based on original code by Pierre R. Mai
+;;;; Created: Feb 2002
;;;;
-;;;; $Id: postgresql-socket-api.lisp,v 1.4 2003/05/02 03:05:54 kevin Exp $
+;;;; $Id$
;;;;
-;;;; This file, part of CLSQL, is Copyright (c) 2002 by Kevin M. Rosenberg
+;;;; This file, part of CLSQL, is Copyright (c) 2002-2004 by Kevin M. Rosenberg
;;;; and Copyright (c) 1999-2001 by Pierre R. Mai
;;;;
;;;; CLSQL users are granted the rights to distribute and use this software
;;;; (http://opensource.franz.com/preamble.html), also known as the LLGPL.
;;;; *************************************************************************
-
-;;;; Changes by Kevin Rosenberg
-;;;; - Added socket open functions for Allegro and Lispworks
-;;;; - Changed CMUCL FFI to UFFI
-;;;; - 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)
+(in-package #:postgresql-socket)
(uffi:def-enum pgsql-ftype
((:bytea 17)
(:float4 700)
(:float8 701)))
-(defmethod clsql-base-sys:database-type-library-loaded ((database-type
+(defmethod clsql-sys:database-type-library-loaded ((database-type
(eql :postgresql-socket)))
"T if foreign library was able to be loaded successfully. Always true for
socket interface"
t)
-(defmethod clsql-base-sys:database-type-load-foreign ((database-type (eql :postgresql-socket)))
+(defmethod clsql-sys:database-type-load-foreign ((database-type (eql :postgresql-socket)))
t)
(defun send-socket-value-string (socket value)
(declare (type stream socket)
(type string value))
+ #-sb-unicode
(loop for char across value
- for code = (char-code char)
- do (write-byte code socket)
- finally (write-byte 0 socket))
+ for code = (char-code char)
+ do (write-byte code socket)
+ finally (write-byte 0 socket))
+ #+sb-unicode
+ (write-sequence (sb-ext:string-to-octets value :null-terminate t) socket)
nil)
(defun send-socket-value-limstring (socket value limit)
(defun read-socket-value-string (socket)
(declare (type stream socket))
+ #-sb-unicode
(with-output-to-string (out)
(loop for code = (read-byte socket)
- until (zerop code)
- do (write-char (code-char code) out))))
+ until (zerop code)
+ do (write-char (code-char code) out)))
+ #+sb-unicode
+ (let ((bytes (make-array 64
+ :element-type '(unsigned-byte 8)
+ :adjustable t
+ :fill-pointer 0)))
+ (loop for code = (read-byte socket)
+ until (zerop code)
+ do (vector-push-extend code bytes))
+ (sb-ext:octets-to-string bytes)))
(defmacro define-message-sender (name (&rest args) &rest clauses)
(int32 key))
-(defun read-socket-sequence (string stream)
- "KMR -- Added to support reading from binary stream into a string"
- (declare (string string)
- (stream stream)
+(defun read-socket-sequence (stream length &optional (allow-wide t))
+ (declare (stream stream)
(optimize (speed 3) (safety 0)))
- (dotimes (i (length string))
- (declare (fixnum i))
- (setf (char string i) (code-char (read-byte stream))))
- string)
-
+ #-sb-unicode
+ (let ((result (make-string length)))
+ (dotimes (i length result)
+ (declare (fixnum i))
+ (setf (char result i) (code-char (read-byte stream)))))
+ #+sb-unicode
+ (let ((bytes (make-array length :element-type '(unsigned-byte 8))))
+ (declare (type (simple-array (unsigned-byte 8) (*)) bytes))
+ (read-sequence bytes stream)
+ (if allow-wide
+ (sb-ext:octets-to-string bytes)
+ (map 'string #'code-char bytes))))
;;; Support for encrypted password transmission
#-scl
-(eval-when (compile eval load)
+(eval-when (:compile-toplevel :load-toplevel :execute)
(defvar *crypt-library-loaded* nil)
(unless *crypt-library-loaded*
- (uffi:load-foreign-library
+ (uffi:load-foreign-library
(uffi:find-foreign-library "libcrypt"
- '("/usr/lib/" "/usr/local/lib/" "/lib/"))
+ '(#+(or 64bit x86-64) "/usr/lib64/"
+ "/usr/lib/" "/usr/local/lib/" "/lib/"))
:supporting-libraries '("c"))
(setq *crypt-library-loaded* t)))
(in-package :postgresql-socket)
-(uffi:def-function "crypt"
+(uffi:def-function ("crypt" crypt)
((key :cstring)
(salt :cstring))
:returning :cstring)
"Encrypt a password for transmission to a PostgreSQL server."
(uffi:with-cstring (password-cstring password)
(uffi:with-cstring (salt-cstring salt)
- (uffi:convert-from-cstring
+ (uffi:convert-from-cstring
(crypt password-cstring salt-cstring)))))
\f
(defvar *postgresql-server-socket-timeout* 60
"Timeout in seconds for reads from the PostgreSQL server.")
-
#+(or cmu scl)
(defun open-postgresql-socket (host port)
(etypecase host
(string
(ext:connect-to-inet-socket host port))))
+#+sbcl
+(defun open-postgresql-socket (host port)
+ (etypecase host
+ (pathname
+ ;; Directory to unix-domain socket
+ (let ((sock (make-instance 'sb-bsd-sockets:local-socket
+ :type :stream)))
+ (sb-bsd-sockets:socket-connect
+ sock
+ (namestring
+ (make-pathname :name ".s.PGSQL" :type (princ-to-string port)
+ :defaults host)))
+ sock))
+ (string
+ (let ((sock (make-instance 'sb-bsd-sockets:inet-socket
+ :type :stream
+ :protocol :tcp)))
+ (sb-bsd-sockets:socket-connect
+ sock
+ (sb-bsd-sockets:host-ent-address
+ (sb-bsd-sockets:get-host-by-name host))
+ port)
+ sock))))
+
#+(or cmu scl)
(defun open-postgresql-socket-stream (host port)
(system:make-fd-stream
:buffering :none
:timeout *postgresql-server-socket-timeout*))
+
+#+sbcl
+(defun open-postgresql-socket-stream (host port)
+ (sb-bsd-sockets:socket-make-stream
+ (open-postgresql-socket host port) :input t :output t
+ :element-type '(unsigned-byte 8)))
+
+
#+allegro
(defun open-postgresql-socket-stream (host port)
(etypecase host
(mp:with-timeout (*postgresql-server-socket-timeout* (error "connect failed"))
(socket:make-socket :type :stream :address-family :internet
:remote-port port :remote-host host
- :connect :active :nodelay t))))
- ))
+ :connect :active :nodelay t))))))
+
+#+openmcl
+(defun open-postgresql-socket-stream (host port)
+ (etypecase host
+ (pathname
+ (let ((path (namestring
+ (make-pathname :name ".s.PGSQL" :type (princ-to-string port)
+ :defaults host))))
+ (ccl:make-socket :type :stream :address-family :file
+ :connect :active
+ :remote-filename path :local-filename path)))
+ (string
+ (ccl:make-socket :type :stream :address-family :internet
+ :remote-port port :remote-host host
+ :connect :active :nodelay t))))
#+lispworks
(defun open-postgresql-socket-stream (host port)
:read-timeout *postgresql-server-socket-timeout*))
))
+
+#+clisp
+(defun open-postgresql-socket-stream (host port)
+ (etypecase host
+ (pathname
+ (error "Not supported"))
+ (string
+ (socket:socket-connect
+ port host
+ :element-type '(unsigned-byte 8)
+ :timeout *postgresql-server-socket-timeout*))))
+
+
;;; Interface Functions
(defun open-postgresql-connection (&key (host (cmucl-compat:required-argument))
(defun encrypt-md5 (plaintext salt)
(string-downcase
(format nil "~{~2,'0X~}"
- (coerce (md5:md5sum-sequence (concatenate 'string plaintext salt)) 'list))))
+ (coerce (md5sum-sequence (concatenate 'string plaintext salt)) 'list))))
(defun reopen-postgresql-connection (connection)
"Reopen the given PostgreSQL connection. Closes any existing
connection, if it is still open."
(when (postgresql-connection-open-p connection)
(close-postgresql-connection connection))
- (let ((socket (open-postgresql-socket-stream
+ (let ((socket (open-postgresql-socket-stream
(postgresql-connection-host connection)
(postgresql-connection-port connection))))
(unwind-protect
(3
(send-unencrypted-password-message
socket
- (postgresql-connection-password connection)))
+ (postgresql-connection-password connection))
+ (force-output socket))
(4
- (let ((salt (make-string 2)))
- (read-socket-sequence salt socket)
+ (let ((salt (read-socket-sequence socket 2 nil)))
(send-encrypted-password-message
socket
(crypt-password
- (postgresql-connection-password connection) salt))))
+ (postgresql-connection-password connection) salt)))
+ (force-output socket))
(5
- (let ((salt (make-string 4)))
- (read-socket-sequence salt socket)
+ (let ((salt (read-socket-sequence socket 4 nil)))
(let* ((pwd2 (encrypt-md5 (postgresql-connection-password connection)
(postgresql-connection-user connection)))
(pwd (encrypt-md5 pwd2 salt)))
(send-encrypted-password-message
socket
- (concatenate 'string "md5" pwd)))))
+ (concatenate 'string "md5" pwd))))
+ (force-output socket))
(t
(error 'postgresql-login-error
:connection connection
while (listen socket)
do
(case (read-socket-value-int8 socket)
+ (#.+ready-for-query-message+)
(#.+notice-response-message+
(let ((message (read-socket-value-string socket)))
(warn 'postgresql-warning :connection connection
:connection connection :message message))))
(#.+notice-response-message+
(let ((message (read-socket-value-string socket)))
- (warn 'postgresql-warning
- :connection connection :message message)))
+ (unless (eq :ignore clsql-sys:*backend-warning-behavior*)
+ (warn 'postgresql-warning
+ :connection connection :message message))))
(#.+notification-response-message+
(let ((pid (read-socket-value-int32 socket))
(message (read-socket-value-string socket)))
(:double
(read-double-from-socket socket length))
(t
- (let ((result (make-string length)))
- (read-socket-sequence result socket)
- result)))))
+ (read-socket-sequence socket length)))))
(uffi:def-constant +char-code-zero+ (char-code #\0))
(uffi:def-constant +char-code-minus+ (char-code #\-))
) ;; nothing to do
(t
(setq val (- first-char +char-code-zero+))))
-
+
(dotimes (i length)
(declare (fixnum i))
(setq val (+
(< ,offset 10))
,offset
nil))))
-
+
(defun read-double-from-socket (socket length)
(declare (fixnum length))
(let ((before-decimal 0)
(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
+ (cond
((and weight (not decimalp)) ;; before decimal point
(setq before-decimal (+ weight (* 10 before-decimal))))
((and weight decimalp) ;; after decimal point
(setq exponent (read-integer-from-socket socket (- length i 1)))
(setq exponent (or exponent 0))
(return-from loop))
- (t
+ (t
(break "Unexpected value"))
)
)))
(setq result (* (+ (coerce before-decimal 'double-float)
- (* after-decimal
+ (* 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)))
(error 'postgresql-fatal-error :connection connection
:message "Received garbled message from backend")))))))
-(defun run-query (connection query &optional (types nil))
+(defun run-query (connection query &optional (result-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 types)
+ (loop for row = (read-cursor-row cursor result-types)
while row
collect row
finally