Changing data

The last part of the walkthrough makes smaller changes: it creates a partition, adds and retracts values, retracts a whole entity, and watches transactions through the report queue.

Creating a partition

Partitions group entities, which in Datomic affects how their datoms sort together. A partition is an entity with a :db/ident, installed through :db.install/_partition:

(def partition-report
  @(d/transact conn [{:db/id (d/tempid :db.part/db)
                      :db/ident :communities
                      :db.install/_partition :db.part/db}]))
(show "partition installed" (contains? (set (map :v (:tx-data partition-report)))
                                       :communities))
partition installed: true

d/tempid creates a tempid in a partition, like a #db/id literal does. :db.install/_partition :db.part/db is a reverse attribute in a map: it asserts [:db.part/db :db.install/partition <new entity>].

Creating an entity in the partition

(def easton-report
  @(d/transact conn [{:db/id (d/tempid :communities)
                      :community/name "Easton"}]))
(def easton-created (d/q '[:find ?id . :where [?id :community/name "Easton"]] (d/db conn)))
(show "Easton partition is :communities"
      (= (d/part easton-created) (d/entid (d/db conn) :communities)))
Easton partition is :communities: true

d/part returns the partition of an entity id, and d/entid turns an ident into its entity id.

Adding a value

To add to an existing entity, transact a map with its :db/id:

(def belltown-id (d/q '[:find ?id .
                        :where
                        [?id :community/name "belltown"]]
                      (d/db conn)))

@(d/transact conn [{:db/id belltown-id
                    :community/category "free stuff"}])
(:community/category (d/entity (d/db conn) belltown-id))
belltown categories after add: ("events" "free stuff" "news")

:community/category is cardinality many, so the new value joins the existing ones. For a cardinality-one attribute, the new value would replace the old one (Datomic retracts the old value for you).

Retracting a value

The list form [:db/retract e a v] retracts one value:

@(d/transact conn [[:db/retract belltown-id :community/category "free stuff"]])
(:community/category (d/entity (d/db conn) belltown-id))
belltown categories after retract: ("events" "news")

The list form [:db/add e a v] is the counterpart for assertions.

Retracting an entity

:db.fn/retractEntity (also spelled :db/retractEntity) retracts every attribute of an entity, and every reference to it:

(def easton-id (d/q '[:find ?id .
                      :where
                      [?id :community/name "Easton"]]
                    (d/db conn)))

@(d/transact conn [[:db.fn/retractEntity easton-id]])
(d/q '[:find ?id . :where [?id :community/name "Easton"]] (d/db conn))
Easton after retractEntity: nil

A retraction is a new fact, not a deletion: the history still records that Easton existed, and d/as-of an earlier time still finds it.

Watching transactions: the tx report queue

d/tx-report-queue returns a queue that receives the report of every transaction committed on the connection after it was created:

(def queue (d/tx-report-queue conn))

@(d/transact conn [{:db/id (d/tempid :communities)
                    :community/name "Easton"}])

.poll takes the next report, or returns nil if there is none. The report has the same keys as a transact result. The walkthrough queries its :tx-data directly: a collection of datoms can be a query input, bound here as a relation of [e a v tx added]:

(when-let [report (.poll queue)]
  (show-sorted "tx report from queue"
               (map (fn [[e aname v added]]
                      [(if (= 3 (d/part e)) :tx (d/part e)) aname
                       (if (inst? v) :inst v) added])
                    (d/q '[:find ?e ?aname ?v ?added
                           :in $ [[?e ?a ?v _ ?added]]
                           :where
                           [?e ?a ?v _ ?added]
                           [?a :db/ident ?aname]]
                         (:db-after report)
                         (:tx-data report)))))
(show "queue empty after poll" (.poll queue))
tx report from queue: ([83 :community/name "Easton" true] [:tx :db/txInstant :inst true])
queue empty after poll: nil

The transaction produced two datoms: the new community's name, on an entity in partition 83 (the :communities partition), and the transaction's own :db/txInstant, on the transaction entity in partition 3. After one poll the queue is empty.

nitomic's queue also supports .take, .peek, .isEmpty and .size, and d/remove-tx-report-queue detaches it.

That's the walkthrough

The program ends with (System/exit 0). On the JVM this shuts down Datomic's background threads; natively it simply exits.

From here, the What works page lists the rest of the API, and test/features.clj in the repository exercises it, including the error cases.