#lang scheme/base
(require (prefix-in dbd: (planet schematics/spgsql/spgsql))
(prefix-in dbd: (planet schematics/spgsql/private/connection))
(prefix-in dbd: (planet schematics/spgsql/private/sql-data))
scheme/class
scheme/match
(planet bzlib/base)
(planet bzlib/dbi)
srfi/19
"array.ss"
)
(define-struct (spgsql-handle handle) (t2s))
(define (filter-t2s/attrs attrs)
(define (helper rest acc t2s)
(cond ((null? rest) (values t2s (reverse acc)))
((equal? (car rest) '#:t2s)
(cond ((null? (cdr rest))
(error 'connect "invalid attribute ~a - no value" (car rest)))
((not (procedure? (cadr rest)))
(error 'connect "#:t2s must take a procedure of (-> string? string?)"))
(else
(helper (cddr rest) acc (cadr rest)))))
(else
(helper (cdr rest) (cons (car rest) acc) t2s))))
(helper attrs '() #f))
(define (spgsql-connect driver . attrs)
(let-values (((loader attrs)
(filter-file-loader/attrs attrs)))
(let-values (((t2s attrs)
(filter-t2s/attrs attrs)))
(let ((handle (make-spgsql-handle driver
(apply* dbd:connect attrs)
(make-immutable-hash-registry)
0
t2s)))
(load-files! handle (if (list? loader) loader
(list loader)))
handle))))
(define (spgsql-disconnect dbh)
(send (handle-conn dbh) disconnect))
(define (spgsql-query dbh stmt (args '()))
(if-it (registry-ref (handle-query dbh) stmt)
(convert-result
(send (handle-conn dbh) query
(send (handle-conn dbh) bind-prepared-statement
(prepared-inner it)
(map cell->sql-cell (phq-map-values (prepared-query it) args)))))
(begin
(spgsql-prepare dbh stmt stmt)
(spgsql-query dbh stmt args))))
(define (spgsql-query/effect dbh stmt (args '()))
(define (helper rs)
(if (dbd:SimpleResult? rs)
(make-effect #f
(if (spgsql-handle-t2s dbh)
(if-it (insert-statement? dbh stmt)
(cell/false dbh
"select currval(?seq::regclass)"
`((seq . ,((spgsql-handle-t2s dbh) it))))
#f)
#f)
#f
#f
(dbd:SimpleResult-command rs)
#f)
rs))
(helper (spgsql-query dbh stmt args)))
(define (spgsql-query/effect-set dbh stmt (args '()))
(result-set-or-effect->result-set (spgsql-query/effect dbh stmt args)))
(define (spgsql-t2s:table_t_table_id_seq table)
(define (id-name table)
(regexp-replace #px"(_t)?$" table "_id_seq"))
(format "~a_~a" table (id-name table)))
(define (insert-statement? handle stmt)
(define (helper stmt)
(if-it (regexp-match #px"(?i:insert\\s+(into\\s+)(\\w+))" stmt)
(caddr it)
#f))
(helper (phq-query (prepared-query (registry-ref (handle-query handle) stmt)))))
(define (spgsql-prepare dbh key stmt)
(let ((query (make-place-holder-query default-converter stmt)))
(registry-set! (handle-query dbh)
key
(make-prepared query
(send (handle-conn dbh) prepare (phq-converted query))))))
(define (convert-result rs)
(if (dbd:SimpleResult? rs)
rs
(cons (FieldInfos->columns (dbd:Recordset-info rs))
(data->records (dbd:Recordset-data rs)))))
(define (FieldInfos->columns fis)
(map dbd:FieldInfo-name fis))
(define (data->records data)
(map (lambda (rec)
(map sql-cell->cell (vector->list rec)))
data))
(define (sql-cell->cell cell)
(cond ((dbd:sql-null? cell) '())
((dbd:sql-timestamp? cell) (sql-timestamp->date cell))
((dbd:sql-date? cell) (sql-date->date cell))
(else cell)))
(define (cell->sql-cell cell)
(cond ((null? cell) dbd:sql-null)
((date? cell) (date->sql-timestamp cell))
((list? cell) (cell->pg-array cell))
((vector? cell) (cell->sql-cell (vector->list cell)))
(else cell)))
(define (cell->pg-array x)
(define (helper rest acc)
(cond ((null? rest) (string-append "{" (string-join acc ",") "}"))
((string? (car rest))
(helper (cdr rest) (cons (format "~s" (car rest)) acc)))
((bytes? (car rest)) (helper (cons (bytes->string/utf-8 (car rest)) (cdr rest))
acc))
((number? (car rest)) (helper (cdr rest) (cons (number->string (car rest)) acc)))
(else (error 'cell->pg-array "unsupported type ~a" (car rest)))))
(helper x '()))
(define (sql-date->date date)
(match date
((struct dbd:sql-date (year month day))
(make-date 0 0 0 0 day month day 0))))
(define (sql-timestamp->date ts)
(match ts
((struct dbd:sql-timestamp (year month day hour minute second nanosecond tz))
(make-date nanosecond second minute hour day month year tz))))
(define (date->sql-timestamp d)
(dbd:make-sql-timestamp (date-year d)
(date-month d)
(date-day d)
(date-hour d)
(date-minute d)
(date-second d)
(date-nanosecond d)
(date-zone-offset d)))
(registry-set! drivers 'spgsql/pass-thru-effect
(make-driver spgsql-connect
spgsql-disconnect
spgsql-query
spgsql-prepare
default-begin
default-commit
default-rollback))
(registry-set! drivers 'spgsql/effect
(make-driver spgsql-connect
spgsql-disconnect
spgsql-query/effect
spgsql-prepare
default-begin
default-commit
default-rollback))
(registry-set! drivers 'spgsql/effect-set
(make-driver spgsql-connect
spgsql-disconnect
spgsql-query/effect-set
spgsql-prepare
default-begin
default-commit
default-rollback))
(registry-set! drivers 'spgsql (registry-ref drivers 'spgsql/effect))
(provide (rename-out (dbd:SimpleResult SimpleResult)
(dbd:SimpleResult-command SimpleResult-command))
)
(provide/contract
(spgsql-t2s:table_t_table_id_seq (-> string? string?))
)