(module snooze-unit mzscheme
(require (lib "class.ss")
(lib "etc.ss")
(lib "kw.ss")
(lib "unitsig.ss")
(lib "cut.ss" "srfi" "26"))
(require (planet "gen.ss" ("untyped" "unlib.plt" 2))
(planet "parameter.ss" ("untyped" "unlib.plt" 2))
(planet "pipeline.ss" ("untyped" "unlib.plt" 2)))
(require (file "base.ss")
(file "db-sig.ss")
(file "era.ss")
(prefix q: (file "query-lang.ss"))
(file "snooze-sig.ss"))
(provide current-config
snooze@)
(define current-config
(make-parameter #f (make-guard identity "config")))
(define current-connection-cell
(make-thread-cell #f))
(define snooze@
(unit/sig snooze^
(import (db : db^))
(define connect db:connect)
(define disconnect db:disconnect)
(define (call-with-database config thunk)
(dynamic-wind
(lambda ()
(let ([connection (connect config)])
(thread-cell-set! current-connection-cell connection)))
(lambda ()
(thunk))
(lambda ()
(disconnect (thread-cell-ref current-connection-cell))
(thread-cell-set! current-connection-cell #f))))
(define (current-connection)
(let ([connection (thread-cell-ref current-connection-cell)])
(if connection
connection
(raise-exn exn:fail:snooze
(string-append "No current default connection. "
"Use call-with-database to set up the default DB configuration, "
"or specify the connection as an argument to the query.")))))
(define create-table
(case-lambda
((entity)
(create-table (current-connection) entity))
((conn entity)
(db:create-table conn entity))))
(define drop-table
(case-lambda
((entity)
(drop-table (current-connection) entity))
((conn entity)
(db:drop-table conn entity))))
(define save!
(case-lambda
((struct)
(save! (current-connection) struct))
((conn struct)
(let* ([id (get-id struct)]
[revision (get-revision struct)]
[entity (struct-entity struct)]
[pipeline (append (entity-save-pipeline entity)
(if id
(entity-update-pipeline entity)
(entity-insert-pipeline entity)))])
(call-with-transaction
(lambda ()
(if id
(begin (if (and revision (record-exists-with-revision? conn entity id revision))
(begin (call-with-pipeline
(append (entity-save-pipeline entity)
(entity-update-pipeline entity))
(lambda (conn struct)
(set-revision! struct (add1 revision))
(db:update-record conn struct)
struct)
conn
struct))
(raise-exn exn:fail:snooze:revision
"Structure has been revised since it was loaded from the database."
struct)))
(begin (set-revision! struct 0)
(call-with-pipeline
(append (entity-save-pipeline entity)
(entity-insert-pipeline entity))
(lambda (conn struct)
(let ([id (db:insert-record conn struct)])
(set-id! struct id)
struct))
conn
struct)))))))))
(define delete!
(case-lambda
((struct)
(delete! (current-connection) struct))
((conn struct)
(unless (get-id struct)
(raise-exn exn:fail:snooze
(format "Cannot delete a struct that has not been saved to the database: ~a" struct)))
(let* ([id (get-id struct)]
[revision (get-revision struct)]
[entity (struct-entity struct)])
(call-with-transaction conn
(lambda ()
(if (and revision (record-exists-with-revision? conn entity id revision))
(call-with-pipeline
(entity-delete-pipeline entity)
(lambda (conn struct)
(db:delete-record conn (struct-entity struct) (get-id struct))
(set-id! struct #f)
struct)
conn
struct)
(raise-exn exn:fail:snooze:revision
"Database has been revised since structure was loaded."
struct))))))))
(define find-gen
(case-lambda
((select)
(find-gen (current-connection) select))
((conn select)
(db:find-gen conn select))))
(define find-all
(case-lambda
((select)
(find-all (current-connection) select))
((conn select)
(g:collect (find-gen conn select)))))
(define find-one
(case-lambda
((select)
(find-one (current-connection) select))
((conn select)
(let ([result ((find-gen conn select))])
(if (g:end? result)
#f
result)))))
(define g:find find-gen)
(define call-with-transaction
(case-lambda
((body)
(call-with-transaction (current-connection) body))
((conn body)
(db:call-with-transaction conn body))))
(define dump-sql
(lambda/kw (select
#:key
[output-port (current-output-port)]
[format "~a~n"])
(db:dump-sql select output-port format)))
(define find-by-id
(case-lambda
[(entity id)
(find-by-id (current-connection) entity id)]
[(conn entity id)
(cond [(integer? id)
(let ([x (q:entity entity)])
(find-one (q:select #:from x #:where (q:= (q:attr x 'id) id))))]
[(not id)
#f]
[else (raise-exn exn:fail:snooze
(format "Expected (U integer #f), received ~s." id))])]))
(define (record-exists-with-revision? conn entity id revision)
(define x
(q:entity entity))
(define ans
(find-one conn (q:select #:what (q:attr x 'id)
#:from x
#:where (q:and (q:= (q:attr x 'id) id)
(q:= (q:attr x 'revision) revision)))))
(if ans #t #f))
))
)