Datomic how-to: Update Cardinality Many Attribute
I recently encountered a slight difficulty with updating cardinality many attributes in Datomic, so I thought I would make a short walkthrough post about it.
The Problem
In Datomic each attribute has a “cardinality”, signifying how many values an attribute is allowed. Cardinality can be either “one” or “many”. Adding values for a cardinality many attribute is fairly straightforward, but updating them to a specific set is more difficult. Let’s take the example of a simple Todo list, where each todo has a title and multiple tags. Our schema looks like this:
(def schema
[{:db/ident :todo/title
:db/valueType :db.type/string
:db/cardinality :db.cardinality/one}
{:db/ident :todo/tags
:db/valueType :db.type/string
:db/cardinality :db.cardinality/many}])
Creating a new todo with a set of tags is fairly straightforward:
(d/transact
conn
[{:todo/title "Do the dishes"
:todo/tags ["whenever"]}])
As is adding a tag to an existing todo:
(d/transact conn [:db/add todo-entity-id :todo/tags "boring"])
However, setting a todo’s tags to a specific set of values is more difficult. There’s no built-in way to do this in Datomic. Let’s say we want to set the tags for a todo to ["important" "today"]
, how would we do that?
The Solution
One solution is to:
- Query the current values of the attribute
- Diff them with the new values
- Use that diff to create transactions to:
- Add any new values
- Retract values we no longer want
- Apply the transactions using
d/transact
Here’s the code to do this:
(defn update-attr-txs [db entity-id attr values]
(let [;; Step 1
current-vals (-> (d/q (vec
(concat
'[:find [?a ...]]
[:where ['?id attr '?a]]
'[:in $ ?id]))
db
entity-id)
(set))
;; Step 2
[added removed] (clojure.data/diff (set values) current-vals)]
;; Step 3
(concat
;; Step 3.1
(->> added
(map #(-> [:db/add entity-id attr %])))
;; Step 3.2
(->> removed
(map #(-> [:db/retract entity-id attr %]))))))
;; Step 4
(->> (update-attr-txs (d/db conn) todo-entity-id :todo/tags #{"important" "today"})
(d/transact conn))
That should be all you need! I’ve created a gist containing the update-attr-txs
function here
. You can find some alternative solutions in this StackOverflow post
, and also this one
.
I hope this helps you out, please don’t hesitate to get in touch if you have any questions.