1 (in-package :cl-postgres
)
3 ;; For more information about the PostgreSQL scocket protocol, see
4 ;; http://www.postgresql.org/docs/current/interactive/protocol.html
6 (defmacro define-message
(name id
(&rest arglist
) &body parts
)
7 "This macro synthesizes a function to send messages of a specific
8 type. It takes care of the plumbing -- calling writer functions on a
9 stream, keeping track of the length of the message -- so that the
10 message definitions themselves stay readable."
17 (mapcar (lambda (part)
18 (let ((name (gensym)))
21 (incf base-length
(second part
))
22 `(,(integer-writer-name (second part
) nil
) ,socket
,(third part
)))
24 (push `(,name
,(second part
)) strings
)
25 (incf base-length
1) ;; The null terminator
26 (push `(enc-byte-length ,name
) extra-length
)
27 `(write-str ,socket
,name
))
29 (push `(,name
,(second part
)) strings
)
30 (push `(length ,name
) extra-length
)
31 `(write-bytes ,socket
,name
)))))
33 (push `(write-uint4 ,socket
(+ ,base-length
,@extra-length
))
36 (push `(write-uint1 ,socket
,(char-code id
)) writers
))
37 `(defun ,name
,(cons socket arglist
)
38 (declare (type stream
,socket
)
40 (let ,strings
,@writers
))))
42 ;; Try to enable SSL for a connection.
43 (define-message ssl-request-message nil
()
46 ;; Sends the initial message and sets a few parameters.
47 (define-message startup-message nil
(user database
)
48 (uint 4 196608) ;; Identifies protocol 3.0
53 (string "client_encoding")
54 (string *client-encoding
*)
55 (uint 1 0)) ;; Terminates the parameter list
57 ;; Identify a user with a plain-text password.
58 (define-message plain-password-message
#\p
(password)
61 (defun bytes-to-hex-string (bytes)
62 "Convert an array of 0-255 numbers into the corresponding string of
63 \(lowercase) hex codes."
64 (declare (type (vector (unsigned-byte 8)) bytes
)
66 (let ((digits #.
(coerce "0123456789abcdef" 'simple-base-string
))
67 (result (make-string (* (length bytes
) 2) :element-type
'base-char
)))
68 (loop :for byte
:across bytes
69 :for pos
:from
0 :by
2
70 :do
(setf (char result pos
) (aref digits
(ldb (byte 4 4) byte
))
71 (char result
(1+ pos
)) (aref digits
(ldb (byte 4 0) byte
))))
74 (defun md5-password (password user salt
)
75 "Apply the hashing that PostgreSQL expects to a password."
76 (declare (type string user password
)
77 (type (vector (unsigned-byte 8)) salt
)
79 (flet ((md5-and-hex (sequence)
80 (bytes-to-hex-string (md5:md5sum-sequence sequence
))))
81 (let* ((pass1 (md5-and-hex (enc-string-bytes (concatenate 'string password user
))))
82 (pass2 (md5-and-hex (concatenate '(vector (unsigned-byte 8) *) (enc-string-bytes pass1
) salt
))))
83 (concatenate 'string
"md5" pass2
))))
85 ;; Identify a user with an MD5-hashed password.
86 (define-message md5-password-message
#\p
(password user salt
)
87 (string (md5-password password user salt
)))
89 (define-message gss-auth-buffer-message
#\p
(buf)
92 ;; Send a query, the simple way.
93 (define-message query-message
#\Q
(query)
97 (define-message simple-parse-message
#\P
(query)
98 (uint 1 0) ;; Name of the prepared statement
100 (uint 2 0)) ;; Parameter types
102 ;; Parse a query, giving it a name.
103 (define-message parse-message
#\P
(name query
)
108 ;; Close a named parsed query, freeing the name.
109 (define-message close-prepared-message
#\C
(name)
110 (uint 1 #.
(char-code #\S
)) ;; Prepared statement
113 (defun formats-to-bytes (formats)
114 "Formats have to be passed as arrays of 2-byte integers, with 1
115 indicating binary and 0 indicating plain text."
116 (declare (type vector formats
)
118 (let* ((result (make-array (* 2 (length formats
))
119 :element-type
'(unsigned-byte 8)
120 :initial-element
0)))
121 (loop :for format
:across formats
122 :for pos
:from
1 :by
2
123 :do
(when format
(setf (elt result pos
) 1)))
126 ;; Bind the unnamed prepared query, asking for the given result
128 (define-message simple-bind-message
#\B
(formats)
129 (uint 1 0) ;; Name of the portal
130 (uint 1 0) ;; Name of the prepared statement
131 (uint 2 0) ;; Number of parameter format specs
132 (uint 2 0) ;; Number of parameter specifications
133 (uint 2 (length formats
)) ;; Number of result format specifications
134 (bytes (formats-to-bytes formats
))) ;; Result format
136 ;; This one was a bit too complex to put into define-message format,
137 ;; so it does everything by hand.
138 (defun bind-message (socket name result-formats parameters
)
139 "Bind a prepared statement, ask for the given formats, and pass the
140 given parameters, that can be either string or byte vector.
141 \(vector \(unsigned-byte 8)) parameters will be sent as binary data, useful
142 for binding data for binary long object columns."
143 (declare (type stream socket
)
145 (type vector result-formats
)
146 (type list parameters
)
148 (let* ((n-params (length parameters
))
149 (param-formats (make-array n-params
:element-type
'fixnum
))
150 (param-sizes (make-array n-params
:element-type
'fixnum
))
151 (param-values (make-array n-params
))
152 (n-result-formats (length result-formats
)))
153 (declare (type (unsigned-byte 16) n-params n-result-formats
))
154 (loop :for param
:in parameters
156 :do
(flet ((set-param (format size value
)
157 (setf (aref param-formats i
) format
158 (aref param-sizes i
) size
159 (aref param-values i
) value
)))
160 (declare (inline set-param
))
161 (cond ((eq param
:null
)
163 ((typep param
'(vector (unsigned-byte 8)))
164 (set-param 1 (length param
) param
))
166 (unless (typep param
'string
)
167 (setf param
(serialize-for-postgres param
)))
170 (set-param 0 (enc-byte-length param
) param
))
171 ((vector (unsigned-byte 8))
172 (set-param 1 (length param
) param
)))))))
173 (write-uint1 socket
#.
(char-code #\B
))
174 (write-uint4 socket
(+ 12
175 (enc-byte-length name
)
176 (* 6 n-params
) ;; Input formats and sizes
177 (* 2 n-result-formats
)
178 (loop :for size
:of-type fixnum
:across param-sizes
180 (write-uint1 socket
0) ;; Name of the portal
181 (write-str socket name
) ;; Name of the prepared statement
182 (write-uint2 socket n-params
) ;; Number of parameter format specs
183 (loop :for format
:across param-formats
;; Param formats (text/binary)
184 :do
(write-uint2 socket format
))
185 (write-uint2 socket n-params
) ;; Number of parameter specifications
186 (loop :for param
:across param-values
187 :for size
:across param-sizes
188 :do
(write-int4 socket
(if param size -
1))
190 (if (typep param
'(vector (unsigned-byte 8)))
191 (write-sequence param socket
)
192 (enc-write-string param socket
))))
193 (write-uint2 socket n-result-formats
) ;; Number of result formats
194 (loop :for format
:across result-formats
;; Result formats (text/binary)
195 :do
(write-uint2 socket
(if format
1 0)))))
197 ;; Describe the anonymous portal, so we can find out what kind of
198 ;; result types will be passed.
199 (define-message simple-describe-message
#\D
()
200 (uint 1 #.
(char-code #\S
)) ;; This is a statement describe
201 (uint 1 0)) ;; Name of the portal
203 ;; Describe a named portal.
204 (define-message describe-prepared-message
#\D
(name)
205 (uint 1 #.
(char-code #\S
)) ;; This is a statement describe
208 ;; Execute a bound statement.
209 (define-message simple-execute-message
#\E
()
210 (uint 1 0) ;; Name of the portal
211 (uint 4 0)) ;; Max amount of rows (0 = all rows)
213 ;; Flush the sent messages, force server to start responding.
214 (define-message flush-message
#\H
())
216 ;; For re-synchronizing a socket.
217 (define-message sync-message
#\S
())
219 ;; Tell the server we are about to close the connection.
220 (define-message terminate-message
#\X
())
222 ;; To get out of the copy-in protocol.
223 (define-message copy-done-message
#\c
())
225 (defun copy-data-message (socket data
)
226 (declare (type string data
)
228 (write-uint1 socket
100)
229 (write-uint4 socket
(+ 4 (length data
)))
230 (enc-write-string data socket
))
232 (define-message copy-fail-message
#\f (reason)