root/release/4/postgresql/trunk/postgresql.scm @ 14765

Revision 14765, 30.4 KB (checked in by sjamaan, 16 months ago)

Rename to remove obnoxious result- prefix from procedures that happened to take a result object

Line 
1;;; Bindings to the PostgreSQL C library
2;;
3;; Copyright (C) 2008-2009 Peter Bex
4;; Copyright (C) 2004 Johannes Grødem <johs@copyleft.no>
5;; Redistribution and use in source and binary forms, with or without
6;; modification, is permitted.
7;;
8;; THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS
9;; OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
10;; WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
11;; ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE
12;; LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
13;; CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
14;; OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
15;; BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
16;; LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
17;; (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
18;; USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
19;; DAMAGE.
20
21(module postgresql
22 (update-type-parsers! default-type-parsers
23  char-parser bool-parser bytea-parser numeric-parser
24  update-type-unparsers! default-type-unparsers
25  bool-unparser
26 
27  connect reset-connection disconnect connection?
28 
29  exec-simple-queries exec-query
30 
31  result? clear-result! row-count column-count
32  column-index column-name column-names column-format
33  column-type column-type-modifier table-oid table-column-index
34  value-at row-values row-alist column-values affected-rows inserted-oid
35
36  invalid-oid
37 
38  escape-string escape-bytea unescape-bytea
39 
40  query-fold query-fold* query-fold-right query-fold-right*
41  query-for-each query-for-each* query-map query-map*)
42
43(import chicken scheme foreign)
44
45(require-extension srfi-1 srfi-4 srfi-13 srfi-18 srfi-69
46                   extras data-structures sql-null)
47
48(foreign-declare "#include <libpq-fe.h>")
49
50(define-foreign-type pg-polling-status (enum "PostgresPollingStatusType"))
51(define-foreign-variable PGRES_POLLING_FAILED pg-polling-status)
52(define-foreign-variable PGRES_POLLING_READING pg-polling-status)
53(define-foreign-variable PGRES_POLLING_WRITING pg-polling-status)
54(define-foreign-variable PGRES_POLLING_OK pg-polling-status)
55
56(define-foreign-type pg-exec-status (enum "ExecStatusType"))
57(define-foreign-variable PGRES_EMPTY_QUERY pg-exec-status)
58(define-foreign-variable PGRES_COMMAND_OK pg-exec-status)
59(define-foreign-variable PGRES_TUPLES_OK pg-exec-status)
60(define-foreign-variable PGRES_COPY_OUT pg-exec-status)
61(define-foreign-variable PGRES_COPY_IN pg-exec-status)
62(define-foreign-variable PGRES_BAD_RESPONSE pg-exec-status)
63(define-foreign-variable PGRES_NONFATAL_ERROR pg-exec-status)
64(define-foreign-variable PGRES_FATAL_ERROR pg-exec-status)
65
66;(define-foreign-type pgconn* (c-pointer "PGconn"))
67(define-foreign-type pgconn* c-pointer)
68
69(define PQconnectStart (foreign-lambda pgconn* PQconnectStart (const c-string)))
70(define PQconnectPoll (foreign-lambda pg-polling-status PQconnectPoll pgconn*))
71(define PQresetStart (foreign-lambda bool PQresetStart pgconn*))
72(define PQresetPoll (foreign-lambda pg-polling-status PQresetPoll pgconn*))
73(define PQfinish (foreign-lambda void PQfinish pgconn*))
74(define PQstatus (foreign-lambda (enum "ConnStatusType") PQstatus (const pgconn*)))
75(define PQerrorMessage (foreign-lambda c-string PQerrorMessage (const pgconn*)))
76
77;(define-foreign-type oid "Oid")
78(define-foreign-type oid unsigned-int)
79
80(define invalid-oid (foreign-value "InvalidOid" oid))
81
82(define PQisBusy (foreign-lambda bool PQisBusy pgconn*))
83(define PQconsumeInput (foreign-lambda bool PQconsumeInput pgconn*))
84
85(define-foreign-type pgresult* (c-pointer "PGresult"))
86
87(define PQgetResult (foreign-lambda pgresult* PQgetResult pgconn*))
88(define PQresultStatus (foreign-lambda pg-exec-status PQresultStatus (const pgresult*)))
89(define PQresultErrorMessage (foreign-lambda c-string PQresultErrorMessage (const pgresult*)))
90(define PQresultErrorField (foreign-lambda c-string PQresultErrorField (const pgresult*) int))
91
92(define PQclear (foreign-lambda void PQclear pgresult*))
93(define PQntuples (foreign-lambda int PQntuples (const pgresult*)))
94(define PQnfields (foreign-lambda int PQnfields (const pgresult*)))
95(define PQfname (foreign-lambda c-string PQfname (const pgresult*) int))
96(define PQfnumber (foreign-lambda int PQfnumber (const pgresult*) (const c-string)))
97(define PQftable (foreign-lambda oid PQftable (const pgresult*) int))
98(define PQftablecol (foreign-lambda int PQftablecol (const pgresult*) int))
99(define PQfformat (foreign-lambda int PQfformat (const pgresult*) int))
100(define PQftype (foreign-lambda oid PQftype (const pgresult*) int))
101(define PQfmod (foreign-lambda int PQfmod (const pgresult*) int))
102(define PQgetisnull (foreign-lambda bool PQgetisnull (const pgresult*) int int))
103(define PQcmdTuples (foreign-lambda nonnull-c-string PQcmdTuples pgresult*))
104(define PQoidValue (foreign-lambda oid PQoidValue pgresult*))
105
106;; TODO: Create a real callback system?
107(foreign-declare "static void nullNoticeReceiver(void *arg, const PGresult *res){ }")
108
109(define-syntax define-foreign-int
110  (er-macro-transformer
111   (lambda (e r c)
112     ;; cannot rename define-foreign-variable; it's a really special form
113    `(define-foreign-variable ,(cadr e) int ,(conc "(int) " (cadr e))))))
114
115(define-foreign-int PG_DIAG_SEVERITY)
116(define-foreign-int PG_DIAG_SQLSTATE)
117(define-foreign-int PG_DIAG_MESSAGE_PRIMARY)
118(define-foreign-int PG_DIAG_MESSAGE_DETAIL)
119(define-foreign-int PG_DIAG_MESSAGE_HINT)
120(define-foreign-int PG_DIAG_STATEMENT_POSITION)
121(define-foreign-int PG_DIAG_CONTEXT)
122(define-foreign-int PG_DIAG_SOURCE_FILE)
123(define-foreign-int PG_DIAG_SOURCE_LINE)
124(define-foreign-int PG_DIAG_SOURCE_FUNCTION)
125
126(define (postgresql-error loc message . args)
127  (signal (make-pg-condition loc message args: args)))
128
129(define (make-pg-condition loc message #!key (args '()) severity
130                           error-class error-code message-detail
131                           message-hint statement-position context
132                           source-file source-line
133                           source-function)
134  (make-composite-condition
135    (make-property-condition
136     'exn 'location loc 'message message 'arguments args)
137    (make-property-condition
138     'postgresql 'severity severity 'error-class error-class
139     'error-code error-code 'message-detail message-detail
140     'message-hint message-hint 'statement-position statement-position
141     'context context 'source-file source-file 'source-line source-line
142     ;; Might break not-terribly-old versions of postgresql
143     ;;'internal-position internal-position 'internal-query internal-query
144     'source-function source-function)))
145
146;;;;;;;;;;;;;;;;;;;;;;;;
147;;;; Type parsers
148;;;;;;;;;;;;;;;;;;;;;;;;
149
150(define (char-parser str) (string-ref str 0))
151
152(define (bool-parser str) (string=? str "t"))
153
154(define (numeric-parser str)
155  (or (string->number str)
156      (postgresql-error 'numeric-parser "Unable to parse number" str)))
157
158(define (bytea-parser str)
159  (blob->u8vector/shared (string->blob (unescape-bytea str))))
160
161(define default-type-parsers
162  (make-parameter
163   `(("text" . ,identity)
164     ("bytea" . ,bytea-parser)
165     ("char" . ,char-parser)
166     ("bpchar" . ,identity)
167     ("bool" . ,bool-parser)
168     ("int8" . ,numeric-parser)
169     ("int4" . ,numeric-parser)
170     ("int2" . ,numeric-parser)
171     ("float4" . ,numeric-parser)
172     ("float8" . ,numeric-parser)
173     ("numeric" . ,numeric-parser)
174     ("oid" . ,numeric-parser))))
175
176;;;;;;;;;;;;;;;;;;;;;;;
177;;;; Type unparsers
178;;;;;;;;;;;;;;;;;;;;;;;
179
180(define (bool-unparser b)
181  (if b "TRUE" "FALSE"))
182
183(define default-type-unparsers
184  (make-parameter
185   `((,string? . ,identity)
186     (,u8vector? . ,u8vector->blob/shared)
187     (,char? . ,string)
188     (,boolean? . ,bool-unparser)
189     (,number? . ,number->string))))
190
191;; Retrieve type-oids from PostgreSQL:
192(define (update-type-parsers! conn #!optional new-type-parsers)
193  (let ((type-parsers (or new-type-parsers (pg-connection-type-parsers conn)))
194        (ht (make-hash-table))
195        (result '()))
196    ;; Set the parsers now, so that we will be retrieving raw data
197    (pg-connection-oid-parsers-set! conn ht)
198    (pg-connection-type-parsers-set! conn type-parsers)
199    (unless (null? type-parsers)   ; empty IN () clause is not allowed
200      (query-for-each*
201       (lambda (oid typname)
202         (and-let* ((procedure (assoc typname type-parsers)))
203           (hash-table-set! ht (string->number oid) (cdr procedure))))
204       conn
205       (conc "SELECT oid, typname FROM pg_type WHERE typname IN "
206             "('" (string-intersperse
207                   (map (lambda (p) (escape-string conn (car p)))
208                        type-parsers) "', '") "')")))))
209
210(define (update-type-unparsers! conn new-type-unparsers)
211  (pg-connection-type-unparsers-set! conn new-type-unparsers))
212
213;;;;;;;;;;;;;;;;;;;;
214;;;; Connections
215;;;;;;;;;;;;;;;;;;;;
216
217(define-record pg-connection ptr type-parsers oid-parsers type-unparsers)
218(define connection? pg-connection?)
219
220(define (pgsql-connection->fd conn)
221  ((foreign-lambda int PQsocket pgconn*) (pg-connection-ptr conn)))
222
223;; TODO: Add timeout code
224(define (wait-for-connection! conn poll-function)
225  (let ((conn-fd (pgsql-connection->fd conn))
226        (conn-ptr (pg-connection-ptr conn)))
227    (let loop ((result (poll-function conn-ptr)))
228      (cond ((= result PGRES_POLLING_OK) (void))
229            ((= result PGRES_POLLING_FAILED)
230             (let ((error-message (PQerrorMessage conn-ptr)))
231               (disconnect conn)
232               (postgresql-error 'connect
233                                 (conc "Polling Postgres database failed. "
234                                       error-message))))
235            ((member result (list PGRES_POLLING_WRITING PGRES_POLLING_READING))
236             (thread-wait-for-i/o! conn-fd (if (= PGRES_POLLING_READING result)
237                                               #:output
238                                               #:input))
239             (loop (poll-function conn-ptr)))
240            (else
241             (postgresql-error 'connect (conc "Unknown status code!")))))))
242
243(define (alist->connection-spec alist)
244  (string-join
245   (map (lambda (subspec)
246          (sprintf "~A='~A'"
247                   (car subspec) ;; this had better not contain [ =\']
248                   (string-translate* (->string (cdr subspec))
249                                      '(("\\" . "\\\\") ("'" . "\\'")))))
250        alist)))
251
252(define (connect connection-spec
253                 #!optional
254                 (type-parsers (default-type-parsers))
255                 (type-unparsers (default-type-unparsers)))
256  (let* ((connection-spec (if (string? connection-spec)
257                              connection-spec
258                              (alist->connection-spec connection-spec)))
259         (conn-ptr (PQconnectStart connection-spec)))
260    (cond
261     ((not conn-ptr)
262      (postgresql-error 'connect
263                        "Unable to allocate a Postgres connection structure."
264                        connection-spec))
265     ((= (foreign-value "CONNECTION_BAD" int) (PQstatus conn-ptr))
266      (let ((error-message (PQerrorMessage conn-ptr)))
267        (PQfinish conn-ptr)
268        (postgresql-error 'connect
269                          (conc "Connection to Postgres database failed: "
270                                error-message)
271                          connection-spec)))
272     (else
273      (let ((conn (make-pg-connection conn-ptr type-parsers
274                                      (make-hash-table) type-unparsers)))
275        ;; We don't want libpq to piss in our stderr stream
276        ((foreign-lambda* void ((pgconn* conn))
277          "PQsetNoticeReceiver(conn, nullNoticeReceiver, NULL);") conn-ptr)
278        (wait-for-connection! conn PQconnectPoll)
279        (set-finalizer! conn disconnect)
280        ;; Retrieve type-information from PostgreSQL metadata for use by
281        ;; the various value-parsers.
282        (update-type-parsers! conn)
283        conn)))))
284
285(define (reset-connection connection)
286  (let ((conn-ptr (pg-connection-ptr connection)))
287    (if (PQresetStart conn-ptr) ;; Update oid-parsers?
288        (wait-for-connection! connection PQresetPoll)
289        (let ((error-message (PQerrorMessage conn-ptr)))
290          (disconnect connection)
291          (postgresql-error 'reset-connection
292                            (conc "Reset of connection failed " error-message)
293                            connection)))))
294
295(define (disconnect connection)
296  (and-let* ((conn-ptr (pg-connection-ptr connection)))
297    (pg-connection-ptr-set! connection #f)
298    (pg-connection-type-parsers-set! connection #f)
299    (pg-connection-oid-parsers-set! connection #f)
300    (PQfinish conn-ptr))
301  (void))
302
303;;;;;;;;;;;;;;;
304;;;; Results
305;;;;;;;;;;;;;;;
306
307(define-record pg-result ptr value-parsers)
308(define result? pg-result?)
309
310(define (clear-result! result)
311  (and-let* ((result-ptr (pg-result-ptr result)))
312    (pg-result-ptr-set! result #f)
313    (PQclear result-ptr)))
314
315(define (row-count result)
316  (PQntuples (pg-result-ptr result)))
317
318(define (column-count result)
319  (PQnfields (pg-result-ptr result)))
320
321;; Helper procedures for bounds checking; so we can distinguish between
322;; out of bounds and nonexistant columns, and signal it.
323(define (check-column-index! result index location)
324  (when (>= index (column-count result))
325    (postgresql-error
326     location (sprintf "Result column ~A out of bounds" index) result index)))
327
328(define (check-row-index! result index location)
329  (when (>= index (row-count result))
330    (postgresql-error
331     location (sprintf "Result row ~A out of bounds" index) result index)))
332
333(define (column-name result index)
334  (check-column-index! result index 'column-name)
335  (string->symbol (PQfname (pg-result-ptr result) index)))
336
337(define (column-names result)
338  (let ((ptr (pg-result-ptr result)))
339   (let loop ((names '())
340              (column (column-count result)))
341     (if (= column 0)
342         names
343         (loop (cons (string->symbol (PQfname ptr (sub1 column))) names)
344               (sub1 column))))))
345
346(define (column-index result name)
347  (let ((idx (PQfnumber (pg-result-ptr result) (symbol->string name))))
348    (and (>= idx 0) idx)))
349
350(define (table-oid result index)
351  (check-column-index! result index 'table-oid)
352  (let ((oid (PQftable (pg-result-ptr result) index)))
353    (and (not (= oid invalid-oid)) oid)))
354
355;; Fixes the off-by-1 unexpectedness in libpq/the protocol to make it more
356;; consistent with the rest of Scheme.  However, this is inconsistent with
357;; almost all other PostgreSQL interfaces...
358(define (table-column-index result index)
359  (check-column-index! result index 'table-column-index)
360  (let ((idx (PQftablecol (pg-result-ptr result) index)))
361    (and (> idx 0) (sub1 idx))))
362
363(define format-table
364  '((0 . text) (1 . binary)))
365
366(define (format->symbol format)
367  (or (alist-ref format format-table eq?)
368      (postgresql-error 'format->symbol "Unknown format" format)))
369
370(define (symbol->format symbol)
371  (or (and-let* ((res (rassoc symbol format-table eq?)))
372        (car res))
373      (postgresql-error 'format->symbol "Unknown format" symbol)))
374
375(define (column-format result index)
376  (check-column-index! result index 'column-format)
377  (format->symbol (PQfformat (pg-result-ptr result) index)))
378
379(define (column-type result index)
380  (check-column-index! result index 'column-type)
381  (PQftype (pg-result-ptr result) index))
382
383;; This is really not super-useful as it requires intimate knowledge
384;; about the internal implementations of types in PostgreSQL.
385(define (column-type-modifier result index)
386  (check-column-index! result index 'column-type)
387  (let ((mod (PQfmod (pg-result-ptr result) index)))
388    (and (>= mod 0) mod)))
389
390;; Unchecked version, for speed
391(define (value-at* result row column #!key raw)
392  (if (PQgetisnull (pg-result-ptr result) row column)
393      (sql-null)
394      (let ((value ((foreign-safe-lambda*
395                     scheme-object ((c-pointer res) (int row) (int col))
396                     "C_word fin, *str; char *val; int len;"
397                     "len = PQgetlength(res, row, col);"
398                     "str = C_alloc(C_bytestowords(len + sizeof(C_header)));"
399                     "val = PQgetvalue(res, row, col);"
400                     "fin = C_string(&str, len, val);"
401                     "if (PQfformat(res, col) == 1) /* binary? */"
402                     "        C_string_to_bytevector(fin);"
403                     "C_return(fin);")
404                    (pg-result-ptr result) row column)))
405        (if (or raw (blob? value))
406            value
407            ((vector-ref (pg-result-value-parsers result) column) value)))))
408
409(define (value-at result row column #!key raw)
410  (check-row-index! result row 'value)
411  (check-column-index! result column 'value)
412  (value-at* result row column raw: raw))
413
414(define (row-values result row #!key raw)
415  (check-row-index! result row 'row)
416  (let loop ((list '())
417             (column (column-count result)))
418    (if (= column 0)
419        list
420        (loop (cons (value-at* result row (sub1 column) raw: raw) list)
421              (sub1 column)))))
422
423(define (column-values result column #!key raw)
424  (check-column-index! result column 'column)
425  (let loop ((list '())
426             (row (row-count result)))
427    (if (= row 0)
428        list
429        (loop (cons (value-at* result (sub1 row) column raw: raw) list)
430              (sub1 row)))))
431
432;; (define (row-alist result row)
433;;   (map cons (column-names result) (row-values result row)))
434(define (row-alist result row)
435  (check-row-index! result row 'row-alist)
436  (let loop ((alist '())
437             (column (column-count result)))
438    (if (= column 0)
439        alist
440        (loop (cons (cons (string->symbol
441                           (PQfname (pg-result-ptr result) (sub1 column)))
442                          (value-at* result row (sub1 column))) alist)
443              (sub1 column)))))
444
445;;; TODO: Do we want/need PQnparams and PQparamtype bindings?
446
447(define (affected-rows result)
448  (string->number (PQcmdTuples (pg-result-ptr result))))
449
450(define (inserted-oid result)
451  (let ((oid (PQoidValue (pg-result-ptr result))))
452    (and (not (= oid invalid-oid)) oid)))
453
454
455;;;;;;;;;;;;;;;;;;;;;;;;
456;;;; Query procedures
457;;;;;;;;;;;;;;;;;;;;;;;;
458
459;; Buffer all available input, yielding if nothing is available:
460(define (buffer-available-input! conn)
461  (let ((conn-ptr (pg-connection-ptr conn))
462        (conn-fd (pgsql-connection->fd conn)))
463    (let loop ()
464      (if (PQconsumeInput conn-ptr)
465          (when (PQisBusy conn-ptr)
466            (thread-wait-for-i/o! conn-fd #:input)
467            (loop))
468          (postgresql-error 'buffer-available-input!
469                            (conc "Error reading reply from server. "
470                                  (PQerrorMessage conn-ptr))
471                            conn-ptr)))))
472
473(define (make-value-parsers conn pqresult)
474  (let ((nfields (PQnfields pqresult)))
475    (do ([col 0 (+ col 1)]
476         [parsers (make-vector nfields)])
477        ([= col nfields] parsers)
478      (vector-set! parsers col
479                   (hash-table-ref (pg-connection-oid-parsers conn)
480                                   (PQftype pqresult col)
481                                   (lambda () identity))))))
482
483;; Collect the result pointers from the last query.
484;;
485;; A pgresult represents an entire resultset and is always read into memory
486;; all at once.
487(define (collect-results conn)
488  (buffer-available-input! conn)
489  (let loop ((results (list)))
490    (let* ((conn-ptr (pg-connection-ptr conn))
491           (result (PQgetResult conn-ptr)))
492      (if result
493          (cond
494           ((member (PQresultStatus result) (list PGRES_BAD_RESPONSE
495                                                  PGRES_FATAL_ERROR))
496            (let* ((get-error-field (lambda (d) (PQresultErrorField result d)))
497                   (sqlstate (get-error-field PG_DIAG_SQLSTATE))
498                   (maybe-severity (get-error-field PG_DIAG_SEVERITY))
499                   (maybe-statement-position
500                    (get-error-field PG_DIAG_STATEMENT_POSITION))
501                   (condition
502                    (make-pg-condition
503                     'collect-results
504                     (PQresultErrorMessage result)
505                     severity:           (and maybe-severity
506                                              (string->symbol
507                                               (string-downcase maybe-severity)))
508                     error-class:        (and sqlstate (string-take sqlstate 2))
509                     error-code:         sqlstate
510                     message-detail:     (get-error-field PG_DIAG_MESSAGE_DETAIL)
511                     message-hint:       (get-error-field PG_DIAG_MESSAGE_HINT)
512                     statement-position: (and maybe-statement-position
513                                              (string->number
514                                               maybe-statement-position))
515                     context:            (get-error-field PG_DIAG_CONTEXT)
516                     source-file:        (get-error-field PG_DIAG_SOURCE_FILE)
517                     source-line:        (get-error-field PG_DIAG_SOURCE_LINE)
518                     source-function:    (get-error-field PG_DIAG_SOURCE_FUNCTION))))
519              ;; Read out all remaining results (including the current one).
520              ;; TODO: Is this really needed? libpq does it (in pqExecFinish),
521              ;; but ostensibly only to concatenate the error messages for
522              ;; each query.  OTOH, maybe we want to do that, too.
523              (let clean-results! ((result result))
524                (when result
525                  (PQclear result)
526                  (clean-results! (PQgetResult (pg-connection-ptr conn)))))
527              (signal condition)))
528           (else
529            (let ((result-obj (make-pg-result result
530                                              (make-value-parsers conn result))))
531              (set-finalizer! result-obj clear-result!)
532              (loop (cons result-obj results)))))
533          (reverse! results)))))
534
535(define (exec-simple-queries conn query)
536  (if ((foreign-lambda bool PQsendQuery pgconn* (const c-string))
537       (pg-connection-ptr conn) query)
538      (collect-results conn)
539      (postgresql-error 'exec-simple-queries
540                        (conc "Unable to send query to server. "
541                              (PQerrorMessage (pg-connection-ptr conn)))
542                        conn query)))
543
544(define (exec-query conn query #!optional (params '()) #!key (format 'text) raw)
545  (let* ((unparsers (pg-connection-type-unparsers conn))
546         (unparse (lambda (x)
547                    (cond ((find (lambda (parse?)
548                                   ((car parse?) x))
549                                 unparsers) => (lambda (parse)
550                                                 ((cdr parse) x)))
551                          (else x))))
552         (params ;; Check all params and ensure they are proper pairs
553          (map   ;; See if this can be moved into C
554           (lambda (p)
555             (let ((obj (if raw p (unparse p))))
556               (when (and (not (string? obj))
557                          (not (blob? obj))
558                          (not (sql-null? obj)))
559                 (postgresql-error
560                  'exec-query
561                  (sprintf "Param value is not a string, sql-null or blob: ~S" p)
562                  conn query params format))
563               (if (sql-null? obj) #f obj))) params))
564         (send-query
565          (foreign-lambda*
566           bool ((pgconn* conn) (nonnull-c-string query)
567                 (int num) (scheme-object params) (int resfmt))
568           "int res = 0, i = 0, *lens = NULL;"
569           "char **vals = NULL;"
570           "int *fmts = NULL;"
571           "C_word obj, cons;"
572           "if (num > 0) {"
573           "    vals = C_malloc(num * sizeof(char *));"
574           "    lens = C_malloc(num * sizeof(int));"
575           "    fmts = C_malloc(num * sizeof(int));"
576           "}"
577           "for (i=0,cons=params; i < num; ++i,cons=C_u_i_cdr(cons)) {"
578           "    obj = C_u_i_car(cons);"
579           "    if (obj == C_SCHEME_FALSE) {"
580           "        fmts[i] = 0; /* don't care */"
581           "        lens[i] = 0;"
582           "        vals[i] = NULL;"
583           "    } else if (C_header_bits(obj) == C_BYTEVECTOR_TYPE) {"
584           "        fmts[i] = 1; /* binary */"
585           "        lens[i] = C_header_size(obj);"
586           "        vals[i] = C_c_string(obj);"
587           "    } else {"
588           "        /* text needs to be copied; it expects ASCIIZ */"
589           "        fmts[i] = 0; /* text */"
590           "        lens[i] = C_header_size(obj);"
591           "        vals[i] = malloc(lens[i] + 1);"
592           "        memcpy(vals[i], C_c_string(obj), lens[i]);"
593           "        vals[i][lens[i]] = '\\0';"
594           "    }"
595           "}"
596           "res = PQsendQueryParams(conn, query, num, NULL,"
597           "                        vals, lens, fmts, resfmt);"
598           "for (i=0,cons=params; i < num; ++i,cons=C_u_i_cdr(cons)) {"
599           "    obj = C_u_i_car(cons);"
600           "    if (!C_immediatep(obj) && C_header_bits(obj) == C_STRING_TYPE)"
601           "        free(vals[i]); /* Clear copied strings only */"
602           "}"
603           "if (num > 0) {"
604           "    free(fmts);"
605           "    free(lens);"
606           "    free(vals);"
607           "}"
608           "C_return(res);")))
609   (if (send-query (pg-connection-ptr conn) query
610                   (length params) params (symbol->format format))
611       (car (collect-results conn)) ;; assumed to always return one result...
612       (postgresql-error 'exec-query
613                         (conc "Unable to send query to server. "
614                               (PQerrorMessage (pg-connection-ptr conn)))
615                         conn query params format))))
616
617;;;;;;;;;;;;;;;;;;;;;;
618;;;; Value escaping
619;;;;;;;;;;;;;;;;;;;;;;
620
621(define (escape-string conn str)
622  (define %escape-string-conn
623    ;; This could be more efficient by copying straight into a Scheme object.
624    ;; Now it's being copied by PQescapeStringConn, and Chicken copies it again.
625    ;; This can allocate up to twice as much memory than the string actually
626    ;; uses; in extreme cases this could be a problem.
627    (foreign-lambda* c-string* ((pointer conn) (c-string from) (int fromlen))
628                     "int err = 0; char *to;"
629                     "to = malloc(sizeof(char) * (fromlen * 2 + 1));"
630                     "PQescapeStringConn(conn, to, from, fromlen, &err);"
631                     "if (err) {"
632                     "        free(to);"
633                     "        C_return(NULL);"
634                     "}"
635                     "C_return(to);"))
636  (or (%escape-string-conn conn str (string-length str))
637      (postgresql-error 'escape-string
638                        (conc "String escaping failed. "
639                              (PQerrorMessage conn)) conn str)))
640
641(define (escape-bytea conn str)
642  (define %escape-bytea-conn
643    ;; This must copy because libpq returns a malloced ptr...
644    (foreign-safe-lambda* scheme-object ((pointer conn)
645                                         ;; not copied/NUL interpreted:
646                                         ((const unsigned-c-string*) from)
647                                         (int fromlen))
648                     "size_t tolen=0; C_word res, *fin; unsigned char *esc;"
649                     "esc = PQescapeByteaConn(conn, from, (size_t)fromlen, &tolen);"
650                     "if (esc == NULL)"
651                     "        C_return(C_SCHEME_FALSE);"
652                     "fin = C_alloc(C_bytestowords(tolen + sizeof(C_header)));"
653                     "/* tolen includes the resulting NUL byte */"
654                     "res = C_string(&fin, tolen - 1, (char *)esc);"
655                     "PQfreemem(esc);"
656                     "C_return(res);"))
657  (or (%escape-bytea-conn conn str (string-length str))
658      (postgresql-error 'escape-bytea
659                        (conc "Byte array escaping failed. "
660                              (PQerrorMessage conn)) conn str)))
661
662(define (unescape-bytea str)
663  (define %unescape-bytea
664    ;; This must copy because libpq returns a malloced ptr...
665    (foreign-safe-lambda* scheme-object (((const unsigned-c-string*) from))
666                     "size_t tolen=0; C_word res, *fin; unsigned char *unesc;"
667                     "unesc = PQunescapeBytea(from, &tolen);"
668                     "if (unesc == NULL)"
669                     "        C_return(C_SCHEME_FALSE);"
670                     "fin = C_alloc(C_bytestowords(tolen + sizeof(C_header)));"
671                     "res = C_string(&fin, tolen, (char *)unesc);"
672                     "PQfreemem(unesc);"
673                     "C_return(res);"
674                     ))
675  (or (%unescape-bytea str)
676      (postgresql-error 'unescape-bytea
677                        "Byte array unescaping failed (out of memory?)" str)))
678
679
680;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
681;;;; High-level interface
682;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
683
684(define (%query-fold kons knil conn query params)
685  (let* ((result (exec-query conn query params))
686         (rows (row-count result)))
687    (let loop ((seed knil)
688               (row 0))
689      (if (= row rows)
690          seed
691          (loop (kons (row-values result row) seed) (add1 row))))))
692
693(define (%query-fold-right kons knil conn query params)
694  (let* ((result (exec-query conn query params))
695         (rows (row-count result)))
696    (let loop ((seed knil)
697               (row 0))
698      (if (= row rows)
699          seed
700          (kons (row-values result row) (loop seed (add1 row)))))))
701
702(define (query-fold kons knil conn query . params)
703  (%query-fold kons knil conn query params))
704(define (query-fold* kons knil conn query . params)
705  (%query-fold (lambda (values seed) (apply kons (append values (list seed))))
706               knil conn query params))
707(define (query-fold-right kons knil conn query . params)
708  (%query-fold-right kons knil conn query params))
709(define (query-fold-right* kons knil conn query . params)
710  (%query-fold-right (lambda (val seed) (apply kons (append val (list seed))))
711                     knil conn query params))
712
713(define (query-for-each proc conn query . params)
714  (%query-fold (lambda (values seed) (proc values)) #f conn query params)
715  (void))
716
717(define (query-for-each* proc conn query . params)
718  (%query-fold (lambda (values seed) (apply proc values)) #f conn query params)
719  (void))
720
721;; Like regular Scheme map, the order in which the procedure is applied is
722;; undefined.  We make good use of that by traversing the resultset from
723;; the end back to the beginning, thereby avoiding a reverse! on the result.
724(define (%query-map proc conn query params)
725  (let ((result (exec-query conn query params)))
726    (let loop ((lst '())
727               (row (row-count result)))
728      (if (= row 0)
729          lst
730          (loop (cons (proc (row-values result (sub1 row))) lst) (sub1 row))))))
731(define (query-map proc conn query . params)
732  (%query-map proc conn query params))
733
734(define (query-map* proc conn query . params)
735  (%query-map (lambda (values) (apply proc values)) conn query params))
736
737)
Note: See TracBrowser for help on using the browser.