#lang scheme/base
(require mzlib/etc
mzlib/pregexp
scheme/class
scheme/match
srfi/26/cut
(prefix-in postgresql: (planet schematics/spgsql:2/spgsql))
(planet untyped/unlib:3/gen)
(planet untyped/unlib:3/symbol)
"../base.ss"
"../extract.ss"
"../era/era.ss"
"../generic/connection.ss"
"../generic/database.ss"
"../generic/snooze-reraise.ss"
"../sql/sql-struct.ss"
"sql.ss")
(define database%
(class* object% (database<%>)
(init-field server port database username password ssl ssl-encrypt)
(super-new)
(define/public (connect)
(with-snooze-reraise (exn:fail? "Could not connect to database")
(define conn (postgresql:connect '#:server server
'#:port port
'#:database database
'#:user username
'#:password password
'#:ssl ssl
'#:ssl-encrypt ssl-encrypt))
(send conn exec "SET client_min_messages TO warning;")
(send conn exec "SET datestyle TO iso;")
(send conn exec "SET regex_flavor TO extended;")
(make-connection conn #f)))
(define/public (disconnect conn)
(with-snooze-reraise (exn:fail? "Could not disconnect from database")
(send (connection-back-end conn) disconnect)))
(define/public (create-table conn entity)
(with-snooze-reraise (exn:fail? (format "Could not create table for ~a" entity))
(for-each (cut send (connection-back-end conn) exec <>)
(map (cut string-append <> ";")
(pregexp-split #px";" (create-sql entity))))))
(define/public (drop-table conn entity)
(with-snooze-reraise (exn:fail? (format "Could not drop table for ~a" entity))
(for-each (cut send (connection-back-end conn) exec <>)
(map (cut string-append <> ";")
(pregexp-split #px";" (drop-sql entity))))))
(define/public (insert-record conn struct)
(define sequence-name
(symbol-append (entity-table-name (struct-entity struct)) '_seq))
(with-snooze-reraise (exn:fail? (format "Could not insert database record for ~a" struct))
(send (connection-back-end conn) exec (insert-sql struct))
(parse-value type:id (send (connection-back-end conn) query-value
(string-append "SELECT currval('" (escape-name sequence-name) "');")))))
(define/public (insert-record/id conn struct)
(with-snooze-reraise (exn:fail? (format "Could not insert database record for ~a" struct))
(send (connection-back-end conn) exec (insert-sql struct #t))
(void)))
(define/public (update-record conn struct)
(with-snooze-reraise (exn:fail? (format "Could not update database record for ~a" struct))
(send (connection-back-end conn) exec (update-sql struct))
(void)))
(define/public (delete-record conn guid)
(with-snooze-reraise (exn:fail? (format "Could not delete database record for ~a" guid))
(send (connection-back-end conn) exec (delete-sql guid))
(void)))
(define/public (g:find conn query)
(define sql (query-sql query))
(with-snooze-reraise (exn:fail? (format "Could not execute SELECT query:~n~a" (query-sql query)))
(g:map (make-struct-extractor (query-extract-info query))
(g:map (make-parser (map expression-type (query-what query)))
(g:list (send (connection-back-end conn) map sql vector))))))
(define/public (transaction-allowed? conn)
#t)
(define/public (call-with-transaction conn body)
(define savepoint (gensym 'savepoint))
(define outermost? (not (connection-in-transaction? conn)))
(define escaped-savepoint (escape-name savepoint))
(define complete? #f)
(dynamic-wind
(lambda ()
(when outermost?
(send (connection-back-end conn) exec "BEGIN;")
(set-connection-in-transaction?! conn #t))
(send (connection-back-end conn) exec (string-append "SAVEPOINT " escaped-savepoint ";")))
(lambda ()
(begin0 (body)
(set! complete? #t)))
(lambda ()
(if complete?
(send (connection-back-end conn) exec (string-append "RELEASE SAVEPOINT " escaped-savepoint ";"))
(send (connection-back-end conn) exec (string-append "ROLLBACK TO SAVEPOINT " escaped-savepoint ";")))
(when outermost?
(set-connection-in-transaction?! conn #f)
(send (connection-back-end conn) exec "COMMIT;")))))
(define/public (table-names conn)
(map (cut parse-value type:symbol <>)
(send (connection-back-end conn) query-list
"SELECT tablename FROM pg_tables WHERE schemaname = 'public' ORDER BY tablename;")))
(define/public (table-exists? conn table)
(define sql
(format "SELECT relname FROM pg_class WHERE relname=~a;"
(cond [(entity? table) (escape-value type:symbol (entity-table-name table))]
[(symbol? table) (escape-value type:symbol table)]
[else (raise-exn exn:fail:snooze
(format "Expected (U entity symbol), recevied ~s" table))])))
(define result (send (connection-back-end conn) query-list sql))
(not (null? result)))
(define/public (dump-sql query [output-port (current-output-port)] [format "~a"])
(fprintf output-port format (query-sql query))
query)
))
(provide database%)