1 ;;;; -*- Mode: LISP; Syntax: Ansi-Common-Lisp; Base: 10; Package: CL-POSTGRES; -*-
2 (in-package :cl-postgres
)
4 (defun escape-bytes (bytes)
5 "Escape an array of octets in PostgreSQL's horribly inefficient
6 textual format for binary data."
7 (let ((*print-pretty
* nil
))
8 (with-output-to-string (out)
9 (loop :for byte
:of-type fixnum
:across bytes
10 :do
(if (or (< byte
32) (> byte
126) (= byte
39) (= byte
92))
13 (princ (digit-char (ldb (byte 3 6) byte
) 8) out
)
14 (princ (digit-char (ldb (byte 3 3) byte
) 8) out
)
15 (princ (digit-char (ldb (byte 3 0) byte
) 8) out
))
16 (princ (code-char byte
) out
))))))
18 (defun write-ratio-as-floating-point (number stream digit-length-limit
)
19 "Given a ratio, a stream and a digital-length-limit, if
20 *silently-truncate-ratios* is true, will return a potentially truncated ratio.
21 If false and the digital-length-limit is reached, it will throw an error noting
22 the loss of precision and offering to continue or reset
23 *silently-truncate-ratios* to true. Code contributed by Attila Lendvai."
24 (declare #.
*optimize
* (type fixnum digit-length-limit
))
25 (check-type number ratio
)
26 (let ((silently-truncate?
*silently-truncate-ratios
*))
28 (unless silently-truncate?
30 (error 'database-error
:message
31 (format nil
"Can not write the ratio ~A as a floating point number with only ~A available digits. You may want to (setf ~S t) if you don't mind the loss of precision."
32 number digit-length-limit
'*silently-truncate-ratios
*))
34 :report
(lambda (stream)
35 (write-string "Ignore this precision loss and continue"
37 (setf silently-truncate? t
))
39 :report
(lambda (stream)
40 (write-string "Set ~S to true (the precision loss of ratios will be silently ignored in this Lisp VM)." stream
))
41 (setf silently-truncate? t
)
42 (setf *silently-truncate-ratios
* t
))))))
43 (multiple-value-bind (quotient remainder
)
44 (truncate (if (< number
0)
46 (write-char #\- stream
)
49 (let* ((quotient-part (princ-to-string quotient
))
50 (remaining-digit-length (- digit-length-limit
51 (length quotient-part
))))
52 (write-string quotient-part stream
)
53 (when (<= remaining-digit-length
0)
55 (unless (zerop remainder
)
56 (write-char #\. stream
))
58 :for decimal-digits
:upfrom
1
59 :until
(zerop remainder
)
61 (when (> decimal-digits remaining-digit-length
)
64 (multiple-value-bind (quotient rem
) (floor (* remainder
10))
65 (princ quotient stream
)
66 (setf remainder rem
)))))))))
68 (defun write-quoted (string out
)
70 (loop :for ch
:across string
:do
71 (when (member ch
'(#\" #\\))
76 (defgeneric to-sql-string
(arg)
77 (:documentation
"Convert a Lisp value to its textual unescaped SQL
78 representation. Returns a second value indicating whether this value should be
79 escaped if it is to be put directly into a query. Generally any string is going
80 to be designated to be escaped.
82 You can define to-sql-string methods for your own datatypes if you want to be
83 able to pass them to exec-prepared. When a non-NIL second value is returned,
84 this may be T to indicate that the first value should simply be escaped as a
85 string, or a second string providing a type prefix for the value. (This is
86 different from s-sql::to-s-sql-string only in the handling of cons lists.")
87 (:method
((arg string
))
89 (:method
((arg vector
))
90 (if (typep arg
'(vector (unsigned-byte 8)))
91 (values (escape-bytes arg
) t
)
93 (with-output-to-string (out)
95 (loop :for sep
:= "" :then
#\
, :for x
:across arg
:do
97 (multiple-value-bind (string escape
) (to-sql-string x
)
98 (if escape
(write-quoted string out
) (write-string string out
))))
101 (:method
((arg cons
)) ;lists, but not nil
102 (if (alexandria:proper-list-p arg
)
104 (with-output-to-string (out)
106 (loop :for sep
:= "" :then
#\
, :for x
:in arg
:do
108 (multiple-value-bind (string escape
) (to-sql-string x
)
109 (if escape
(write-quoted string out
) (write-string string out
))))
110 (write-char #\
} out
))
112 (error "Value ~S can not be converted to an SQL literal." arg
)))
113 (:method
((arg array
))
115 (with-output-to-string (out)
116 (labels ((recur (dims off
)
119 (let ((factor (reduce #'* (cdr dims
))))
120 (loop :for i
:below
(car dims
) :for sep
:= ""
123 (recur (cdr dims
) (+ off
(* factor i
)))))
124 (loop :for sep
:= "" :then
#\
, :for i
:from off
125 :below
(+ off
(car dims
)) :do
127 (multiple-value-bind (string escape
)
128 (to-sql-string (row-major-aref arg i
))
129 (if escape
(write-quoted string out
)
130 (write-string string out
)))))
131 (write-char #\
} out
)))
132 (recur (array-dimensions arg
) 0)))
134 (:method
((arg integer
))
135 (princ-to-string arg
))
136 (:method
((arg float
))
137 (format nil
"~f" arg
))
138 #-clisp
(:method
((arg double-float
)) ;; CLISP doesn't allow methods on double-float
139 (format nil
"~,,,,,,'EE" arg
))
140 (:method
((arg ratio
))
141 ;; Possible optimization: we could probably build up the same binary
142 ;; structure postgres sends us instead of sending it as a string. See
143 ;; the "numeric" interpreter for more details...
144 (with-output-to-string (result)
145 ;; PostgreSQL happily handles 200+ decimal digits, but the SQL standard
146 ;; only requires 38 digits from the NUMERIC type, and Oracle also doesn't
147 ;; handle more. For practical reasons we also draw the line there. If
148 ;; someone needs full rational numbers then
149 ;; 200 wouldn't help them much more than 38...
150 (write-ratio-as-floating-point arg result
38)))
151 (:method
((arg (eql t
)))
153 (:method
((arg (eql nil
)))
155 (:method
((arg (eql :null
)))
158 (error "Value ~S can not be converted to an SQL literal." arg
)))
160 (defgeneric serialize-for-postgres
(arg)
161 (:documentation
"Conversion function used to turn a lisp value into a value
162 that PostgreSQL understands when sent through its socket connection. May return
163 a string or a (vector (unsigned-byte 8)).")
164 (:method
((arg integer
))
166 #-clisp
(:method
((arg single-float
))
167 (int32-to-vector (cl-postgres-ieee-floats:encode-float32 arg
)))
168 #+clisp
(:method
((arg float
))
169 (int32-to-vector (cl-postgres-ieee-floats:encode-float32 arg
)))
170 #-clisp
(:method
((arg double-float
)) ;; CLISP doesn't allow methods on double-float
171 (int64-to-vector (cl-postgres-ieee-floats:encode-float64 arg
)))
173 (cond ((typep arg
'boolean
)
174 (if arg
(int8-to-vector 1)
176 ;; ((typep arg 'uuid-string) (uuid-to-byte-array arg))
177 (t (to-sql-string arg
)))))