(require '[datomic.api :as d]) (def uri "datomic:mem://test") (d/create-database uri) (def conn (d/connect uri)) (d/transact conn [{:db/id #db/id [:db.part/db] :db/ident :enum/ns :db/valueType :db.type/string :db/cardinality :db.cardinality/one :db/doc "Enum's namespace. Help enforce fk constraints on :db.type/ref enum references" :db.install/_attribute :db.part/db}]) (d/transact conn [{:db/id #db/id[:db.part/db] :db/ident :community/name :db/valueType :db.type/string :db/cardinality :db.cardinality/one :db/fulltext true :db/doc "A community's name" :db.install/_attribute :db.part/db} {:db/id #db/id[:db.part/db] :db/ident :community/orgtype :db/valueType :db.type/ref :db/cardinality :db.cardinality/one :db/doc "A community orgtype enum value" :db.install/_attribute :db.part/db ;; used by the DB function below to enforce the fk constraint :enum/ns "community.orgtype"}]) (d/transact conn [[:db/add #db/id[:db.part/user] :db/ident :community.orgtype/community] [:db/add #db/id[:db.part/user] :db/ident :community.orgtype/commercial] [:db/add #db/id[:db.part/user] :db/ident :community.orgtype/nonprofit] [:db/add #db/id[:db.part/user] :db/ident :community.orgtype/personal] ;; not a orgtype enum value [:db/add #db/id[:db.part/user] :db/ident :community.type/email-list]]) ;; defines an DB function (def enum-fk-entity "Defines a database function (itself an entity) that enforces a foreign key constraint on :db.type/ref attributes that reference enum values. Those attributes must add a :enum/ns attribute holding the namespace of the enum idents, e.g: [:add-fk community-id :community/orgtype :community.orgtype/personal] is equivalent to [:db/add community-id :community/orgtype :community.orgtype/personal] but with a foreign-key constraint enforced by the database." [{:db/id #db/id [:db.part/user] :db/ident :add-fk :db/fn (d/function '{:lang :clojure :params [db e a v] :code (let [;; get value's symbolic keyword ident (if (keyword? v) v (d/ident db v)) ;; get enum's constraint aent (d/entity db a) aname (:db/ident aent) enum-ns (:enum/ns aent) ;; find all possible enum idents allowed (if (nil? enum-ns) (throw (Exception. (str "Cannot check fk constraint. " aname " has no :enum/ns attribute"))) (d/q '[:find ?ident :in $ ?enum-ns :where [_ :db/ident ?ident] [(namespace ?ident) ?ns] [(= ?enum-ns ?ns)]] db enum-ns))] ;; enforce constraint (if (contains? allowed [ident]) [[:db/add e a v]] (throw (Exception. (str v " is not one of " allowed)))))})}]) (d/transact conn enum-fk-entity) ;; will succeed (d/transact conn [[:db/add #db/id [:db.part/user -100] :community/name "15th Ave Community"] [:add-fk #db/id [:db.part/user -100] :community/orgtype :community.orgtype/personal]]) ;; will fail (d/transact conn [[:db/add #db/id [:db.part/user -100] :community/name "15th Ave Community"] [:add-fk #db/id [:db.part/user -100] :community/orgtype :community.type/email-list]]) ;; java.lang.Exception: :community.type/email-list is not one of ;; [[:community.orgtype/community], [:community.orgtype/commercial], ;; [:community.orgtype/personal], [:community.orgtype/nonprofit]]