(module era-private mzscheme
(require (lib "contract.ss")
(all-except (lib "list.ss" "srfi" "1") any))
(require (planet "hash-table.ss" ("untyped" "unlib.plt" 2))
(planet "list.ss" ("untyped" "unlib.plt" 2))
(planet "pipeline.ss" ("untyped" "unlib.plt" 2)))
(require (file "base.ss")
(file "type.ss"))
(define-values
(prop:entity has-entity? struct-entity)
(make-struct-type-property 'entity))
(define persistent-struct? has-entity?)
(define-struct entity
(name constructor getter setter fields save-pipeline insert-pipeline update-pipeline delete-pipeline)
#f)
(define-struct attribute
(name type)
#f)
(define (add-entity-field! entity field)
(set-entity-fields!
entity
(append
(entity-fields entity)
(list field))))
(define (get-id struct)
(let* ((entity (struct-entity struct))
(getter (entity-getter entity)))
(getter struct 0)))
(define (set-id! struct id)
(let* ((entity (struct-entity struct))
(setter (entity-setter entity)))
(setter struct 0 id)))
(define (get-revision struct)
(let* ((entity (struct-entity struct))
(getter (entity-getter entity)))
(getter struct 1)))
(define (set-revision!/internal struct id)
(let* ((entity (struct-entity struct))
(setter (entity-setter entity)))
(setter struct 1 id)))
(define (call-with-entity struct body)
(body (struct-entity struct)))
(define (has-attribute? entity name)
(if (find (lambda (attr)
(eq? (attribute-name attr) name))
(entity-fields entity))
#t
#f))
(define (get-attribute entity name)
(let ((attrs (entity-fields entity)))
(let ([attr
(find (lambda (attr)
(eq? (attribute-name attr) name))
attrs)])
(if attr
attr
(raise-exn exn:fail:snooze
(format "Could not find attribute \"~a\" in entity ~a" name entity))))))
(define (get-attribute-index entity name)
(let loop ([index 0] [attrs (entity-fields entity)]) (cond
[(null? attrs)
(raise-exn exn:fail:snooze
(format "Could not find attribute \"~a\" in entity ~a" name entity))]
[(eq? name (attribute-name (car attrs))) index]
[else (loop (add1 index) (cdr attrs))])))
(define (get-attribute-value struct name)
(call-with-entity struct
(lambda (entity)
(let loop ([names (map attribute-name (entity-fields entity))]
[vals (cdr (vector->list (struct->vector struct)))])
(cond [(null? names)
(raise-exn exn:fail:snooze
(format "Could not find attribute \"~a\" in struct ~a" name struct))]
[(eq? name (car names))
(car vals)]
[else (loop (cdr names) (cdr vals))])))))
(define (set-attribute-value! struct name value)
(call-with-entity struct
(lambda (entity)
(let ([index (get-attribute-index entity name)])
((entity-setter entity) struct index value)))))
(define (map-attributes/entity body entity)
(map
body
(cddr (map attribute-name (entity-fields entity))) (cddr (map attribute-type (entity-fields entity)))))
(define (map-attributes/struct body struct)
(call-with-entity struct
(lambda (entity)
(map
body
(cddr (map attribute-name (entity-fields entity))) (cddr (map attribute-type (entity-fields entity))) (cdddr (vector->list (struct->vector struct)))))))
(define (for-each-attribute/entity body entity)
(for-each
body
(cddr (map attribute-name (entity-fields entity))) (cddr (map attribute-type (entity-fields entity)))))
(define (for-each-attribute/struct body struct)
(call-with-entity struct
(lambda (entity)
(for-each
body
(cddr (map attribute-name (entity-fields entity))) (cddr (map attribute-type (entity-fields entity))) (cdddr (vector->list (struct->vector struct)))))))
(define (persistent-struct->alist struct)
(cons* (cons 'id (get-id struct))
(cons 'revision (get-revision struct))
(map-attributes/struct
(lambda (id type value)
(cons id value))
struct)))
(define (persistent-struct->hash-table struct)
(let ([table (make-hash-table)])
(hash-table-put! table 'id (get-id struct))
(hash-table-put! table 'revision (get-revision struct))
(for-each-attribute/struct
(lambda (id type value)
(hash-table-put! table id value))
struct)
table))
(define (make-blank-persistent-struct entity)
(apply
(entity-constructor entity)
(cons*
(type-null type:id)
(type-null type:revision)
(map-attributes/entity
(lambda (name type)
(type-null type))
entity))))
(define (hash-table->persistent-struct entity table)
(apply
(entity-constructor entity)
(cons*
#f
#f
(map-attributes/entity
(lambda (name type)
(hash-table-get/default table name (type-null type)))
entity))))
(define (set-attributes/alist! struct fields)
(call-with-entity struct
(lambda (entity)
(alist-for-each
(lambda (name value)
(unless (or (eq? name 'id)
(eq? name 'revision)
(not (has-attribute? entity name)))
(set-attribute-value! struct name value)))
fields))))
(define (set-attributes/hash-table! struct table)
(call-with-entity struct
(lambda (entity)
(hash-table-for-each
table
(lambda (name value)
(unless (or (eq? name 'id)
(eq? name 'revision)
(not (has-attribute? entity name)))
(set-attribute-value! struct name value)))))))
(provide prop:entity
has-entity?
persistent-struct?
set-revision!/internal)
(provide/contract
[struct-entity (-> persistent-struct? entity?)]
[struct entity ([name symbol?]
[constructor procedure?]
[getter procedure?]
[setter procedure?]
[fields (listof attribute?)]
[save-pipeline (listof stage?)]
[insert-pipeline (listof stage?)]
[update-pipeline (listof stage?)]
[delete-pipeline (listof stage?)])]
[struct attribute ([name symbol?]
[type type?])]
[get-id (-> persistent-struct? (or/c integer? false/c))]
[set-id! (-> persistent-struct? (or/c integer? false/c) void?)]
[get-revision (-> persistent-struct? (or/c integer? false/c))]
[call-with-entity (-> persistent-struct? (-> entity? any/c) any/c)]
[has-attribute? (-> entity? symbol? boolean?)]
[get-attribute (-> entity? symbol? attribute?)]
[get-attribute-index (-> entity? symbol? integer?)]
[get-attribute-value (-> persistent-struct? symbol? any/c)]
[set-attribute-value! (-> persistent-struct? symbol? any/c void?)]
[map-attributes/entity (-> (-> symbol? type? any/c) entity? list?)]
[map-attributes/struct (-> (-> symbol? type? any/c any/c) persistent-struct? list?)]
[for-each-attribute/entity (-> (-> symbol? type? any/c) entity? any/c)]
[for-each-attribute/struct (-> (-> symbol? type? any/c any/c) persistent-struct? any/c)]
[persistent-struct->alist (-> persistent-struct? (listof (cons/c symbol? any/c)))]
[persistent-struct->hash-table (-> persistent-struct? hash-table?)]
[make-blank-persistent-struct (-> entity? persistent-struct?)]
[hash-table->persistent-struct (-> entity? hash-table? persistent-struct?)]
[set-attributes/hash-table! (-> persistent-struct? hash-table? void?)]
[set-attributes/alist! (-> persistent-struct? (listof (cons/c symbol? any/c)) void?)])
)