Skip to content

Instantly share code, notes, and snippets.

Created Jun 25, 2017
What would you like to do?
terse datomic schema vectors. based on ideas from this presentation at clj-syd
(require '[clojure.pprint :refer [pprint]])
(require '[datomic.api :as d])
(require '[clojure.spec :as s])
(require '[clojure.spec.test :as st])
(s/def ::optional-attributes (s/* (s/alt :db/fulltext (s/and (partial = :fulltext)
; conformer should be last so that it returns the transformed value
(s/conformer (constantly true)))
:db/index (s/and (partial = :index)
(s/conformer (constantly true)))
:db/unique (s/and #{:value :identity}
(s/conformer {:value :db.unique/value
:identity :db.unique/identity}))
:db/doc string?)))
(s/def ::attribute (s/cat :db/ident keyword?
:db/cardinality (s/and #{:one :many}
(s/conformer (fn [card]
(keyword "db.cardinality" (name card)))))
:db/valueType (s/conformer (fn [type]
(keyword "db.type" (name type))))
:optional ::optional-attributes))
(s/def ::enum (s/cat :db/ident keyword?))
(s/def ::schema (s/or :attribute ::attribute
:enum ::enum))
(defn expand
"converts a terse Datomic attribute vector into a full schema map required for a transaction"
(if (s/valid? ::schema terse)
(let [[type conformed] (s/conform ::schema terse)]
(case type
:attribute (let [sorter (sorted-map-by (fn [k1 k2]
(let [order {:db/id 1
:db/ident 2
:db/valueType 3
:db/cardinality 4
:db/index 5
:db/fulltext 6
:db/unique 7
:db/doc 8}
c (compare (get order k1 100) (get order k2 100))]
(if (zero? c) 1 c))))
optional (into {} (:optional conformed))]
(-> conformed
(assoc :db/id (d/tempid :db.part/db)
:db.install/_attribute :db.part/db)
(dissoc :optional)
(merge optional)
(->> (into sorter))))
:enum conformed))
(throw (ex-info "incorrect schema" {:explanation (s/explain-data ::schema terse)}))))
(s/fdef expand :args (s/cat :terse ::schema))
(let [uri "datomic:mem://terse-schema"
_ (d/create-database uri)
conn (d/connect uri)
; the schema below is the point of this gist. This vector is compact and easier to read than maps.
terse-schema [[:user/first-name :one :string :fulltext :index "A users first name"]
[:user/status :one :ref :index]
[:user/ACTIVE] [:user/BLOCKED]]
verbose-schema (map expand terse-schema)
verbose-data [{:db/id (d/tempid :db.part/user)
:user/first-name "Sam"
:user/status [:db/ident :user/ACTIVE]}]]
@(d/transact conn verbose-schema)
@(d/transact conn verbose-data)
(let [db (d/db conn)]
(pprint (d/q '[:find (pull ?u [:user/first-name {:user/status [:db/ident]}])
:in $
[?u :user/first-name _]]
(d/delete-database uri))

This comment has been minimized.

Copy link
Owner Author

@stevebuik stevebuik commented Jun 25, 2017

This syntax is similar to the schema DSL in Vase but uses schema/conform instead of reader macros.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment