#lang scheme/unit
(require (only-in srfi/1 find)
srfi/26
"../base.ss"
"era-dummy.ss"
"era-internal.ss"
"era-sig.ss"
"era-struct.ss"
"transaction-sig.ss")
(import transaction^)
(export era^)
(define entity:persistent-struct
(make-entity 'persistent-struct
'persistent-struct
#f
(make-dummy-constructor 'persistent-struct) (make-dummy-predicate 'persistent-struct) (make-dummy-accessor 'persistent-struct) (make-dummy-mutator 'persistent-struct) null null null null null))
(define-values (struct:persistent-struct
make-persistent-struct
persistent-struct?
persistent-struct-ref
persistent-struct-set!)
(make-struct-type
'persistent-struct #f 2 0 (void) (list (cons prop:entity entity:persistent-struct)) #f))
(define (make-persistent-struct-field-accessor struct-ref index attribute-name)
(make-struct-field-accessor struct-ref index attribute-name))
(define (make-persistent-struct-field-mutator struct-set! index attribute-name)
(define set! (make-struct-field-mutator struct-set! index attribute-name))
(lambda (struct value)
(store-transaction-backup! struct)
(set! struct value)))
(define-values (struct-id set-struct-id! struct-revision set-struct-revision!)
(values (make-persistent-struct-field-accessor persistent-struct-ref 0 'id)
(make-persistent-struct-field-mutator persistent-struct-set! 0 'id)
(make-persistent-struct-field-accessor persistent-struct-ref 1 'revision)
(make-persistent-struct-field-mutator persistent-struct-set! 1 'revision)))
(define (struct-saved? struct)
(and (struct-id struct) #t))
(define attr:struct-id
(make-attribute 'id
'id
entity:persistent-struct
0
struct-id
set-struct-id!
type:id))
(define attr:struct-revision
(make-attribute 'revision
'revision
entity:persistent-struct
1
struct-revision
set-struct-revision!
type:revision))
(set-entity-struct-type! entity:persistent-struct struct:persistent-struct)
(set-entity-constructor! entity:persistent-struct make-persistent-struct)
(set-entity-predicate! entity:persistent-struct persistent-struct?)
(set-entity-accessor! entity:persistent-struct persistent-struct-ref)
(set-entity-mutator! entity:persistent-struct persistent-struct-set!)
(set-entity-attributes! entity:persistent-struct (list attr:struct-id attr:struct-revision))
(define (struct-guid struct)
(make-guid (struct-entity struct)
(struct-id struct)))
(define (struct-has-attribute? struct name+attr)
(entity-has-attribute? (struct-entity struct) name+attr))
(define (struct-attribute struct name+attr)
(define entity
(struct-entity struct))
(define attr
(if (attribute? name+attr)
name+attr
(entity-attribute entity name+attr)))
(define ref
(attribute-accessor attr))
(ref struct))
(define (struct-attributes struct)
(if (persistent-struct? struct)
(cdr (vector->list (struct->vector struct)))
(raise-exn exn:fail:contract
(format "Expected persistent-struct, received ~s" struct))))
(define (set-struct-attribute! struct name+attr value)
(define entity (struct-entity struct))
(define attr
(if (attribute? name+attr)
name+attr
(entity-attribute entity name+attr)))
(define set! (attribute-mutator attr))
(set! struct value))
(define (set-struct-attributes! struct values)
(define entity (struct-entity struct))
(define attrs (entity-attributes entity))
(if (= (length values) (length attrs))
(for-each (lambda (attr value)
((attribute-mutator attr) struct value))
attrs
values)
(raise-exn exn:fail:snooze
(format "Expected list of ~a field values, received ~s" (length attrs) values))))
(define (make-persistent-struct/defaults entity . args)
(define attributes (entity-attributes entity))
(define-values (arg-attrs arg-vals)
(check-attribute-keywords entity args))
(apply (entity-constructor entity)
(map (lambda (attr)
(attribute-keyword-get attr arg-attrs arg-vals (type-default (attribute-type attr))))
attributes)))
(define (copy-persistent-struct old-struct . args)
(define entity (struct-entity old-struct))
(define attributes (entity-attributes entity))
(define existing (struct-attributes old-struct))
(define-values (arg-attrs arg-vals)
(check-attribute-keywords entity args))
(apply (entity-constructor entity)
(map (cut attribute-keyword-get <> arg-attrs arg-vals <>)
attributes
existing)))
(define (update-persistent-struct-from-copy! struct copy)
(define entity (struct-entity struct))
(unless (equal? entity (struct-entity copy))
(raise-exn exn:fail:snooze
(format "Expected two arguments of the same type, received ~s ~s" struct copy)))
(for-each (lambda (attr)
(define ref (attribute-accessor attr))
(define set! (attribute-mutator attr))
(set! struct (ref copy)))
(entity-attributes entity)))
(define (attribute-name-equal? attr1 attr2)
(equal? (attribute-name attr1)
(attribute-name attr2)))
(define (check-attribute-keywords entity args)
(let loop ([even? #f] [args args] [attrs-accum null] [vals-accum null])
(if even?
(if (null? args)
(raise-exn exn:fail:contract
(format "No value for ~s" (car attrs-accum)))
(let ([attr (car attrs-accum)]
[val (car args)])
(cond [(keyword? val) (raise-exn exn:fail:contract (format "Keyword arguments are deprecated: ~s" val))]
[(attribute? val) (raise-exn exn:fail:contract (format "No value for ~s" attr))]
[else (loop (not even?) (cdr args) attrs-accum (cons val vals-accum))])))
(if (null? args)
(values (reverse attrs-accum)
(reverse vals-accum))
(let* ([attr+name (car args)]
[attr (cond [(attribute? attr+name) (entity-attribute entity attr+name)]
[(symbol? attr+name) (entity-attribute entity attr+name)]
[else (raise-exn exn:fail:contract
(if (null? attrs-accum)
(format "No attribute for value: ~s" attr+name)
(format "Multiple values for ~s" (car attrs-accum))))])])
(if (find (cut attribute-name-equal? attr <>) attrs-accum)
(raise-exn exn:fail:contract
(format "Attribute specified more than once: ~s" attr))
(loop (not even?) (cdr args) (cons attr attrs-accum) vals-accum)))))))
(define (attribute-keyword-get needle attrs vals default)
(let/ec return
(for ([attr attrs] [val vals])
(when (eq? needle attr)
(return val)))
default))