Created
June 25, 2017 07:37
-
-
Save stevebuik/17ed50824f1bb814fab9e556a37cf18a to your computer and use it in GitHub Desktop.
terse datomic schema vectors. based on ideas from this presentation at clj-syd https://boogler.gitbooks.io/data-macros/content/
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(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" | |
[terse] | |
(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)) | |
(st/instrument) | |
(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 $ | |
:where | |
[?u :user/first-name _]] | |
db))) | |
(d/delete-database uri)) | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
This syntax is similar to the schema DSL in Vase but uses schema/conform instead of reader macros.