I'd like to know how to I can find the id of the entity that was modified/created/deleted by the latest transaction in Datomic. How can I do this?
For this kind of read pattern (temporal-based), you'll want to use the Log API. Note that:
there may be more than one entity that was affected by the last transaction.
actually, the transaction itself is represented by an entity which is created for that transaction, which you may want to filter out of your result.
Here's a sample implementation:
(defn affected-entities
"Given a Datomic connection, returns the set of entity ids that were affected
by the last transaction (in e position), excluding the entity representing the
transaction itself."
[conn]
(let [db (d/db conn)]
(->>
(d/q '[:find [?e ...] :in ?log ?t1 ?t2 :where
[(tx-ids ?log ?t1 ?t2) [?tx ...]] ;; binds the last tx
[(tx-data ?log ?tx) [[?e]]]]
(d/log conn) (d/basis-t db) (d/next-t db))
;; filtering out the transaction entity
(remove (fn [eid]
(->> eid d/part (d/ident db) (= :db.part/tx))))
set)))
Related
I have a Datascript db that's like this:
{:block/id {:db/unique :db.unique/identity}
:block/children {:db/cardinality :db.cardinality/many}
}
:block/children contains :block/id of other blocks
I've been trying to write a query to find which block has another block as its child.
Here's an example of what I've tried:
(ds/q '[:find ?parent-ds-id
:where
[1100 :block/id ?block-id]
[?parent-ds-id :block/children ?block-id]]
#conn)
I just get the empty set back in return. How am I supposed to dress up ?block-id so that I get back the entity ID of the block who has ?block-id as one of its children? (Every block only ever has one parent)
This problem could be related to the data you are using. This is an
working example of what you are after:
(let [schema {:block/id {:db/unique :db.unique/identity}
:block/children {:db/valueType :db.type/ref
:db/cardinality :db.cardinality/many}}
conn (d/create-conn schema)
idify (fn [id] (* 100 id)) ; just fake some id
block (fn [id] {:db/id (idify id) :block/id id})
rel (fn [parent-id child-id] {:db/id (idify parent-id) :block/children (idify child-id)})]
(d/transact! conn [(block 1)
(block 2)
(block 3)
(rel 1 2)
(rel 1 3)])
(d/q
'[:find ?parent-id
:in $ ?child-id
:where
[?child :block/id ?child-id]
[?parent :block/children ?child]
[?parent :block/id ?parent-id]]
#conn
2))
; → #{[1]}
Although your block-ids are most likely already the unique ids you want
to use for your entities, this uses different entity ids just to make it
clear, how the data can be combined.
This query establishes:
we want the child entity with the block id given (2 via parameters)
we want the parent entity, that references the child entity
we want the block id of the parent entity
I need to all the values of a particular attribute in an entity (for datomic schema). The retract function requires the attribute's value to be passed as argument but they are way too many, and I just require them to be replaced with new set of values. Is it possible to achieve via clojure?
You can either query all values and generate the desired retraction in your peer or, if you wish to ensure an "empty attrib" before new values are written, do the same from within a transaction function.
(map (fn [v] [:db/retract eid attrib v])
(d/q '[:find [?v ...]
:in $ ?e ?a
:where [?e ?a ?v]
db
eid
attrib))
When I query for a list of Datomic entities, e.g like in the example below:
'[:find ?e
:where
[?e :category/name]]
Usually, I'd like to create a list of maps that represent the full entities, i.e
#{[1234] [2223]} => [{:category/name "x" :db/id 1234}, {:category/name "y" :db/id 2223}]
Here is my approach at the moment, in the form of a helper function.
(defn- db-ids->entity-maps
"Takes a list of datomic entity ids retrieves and returns
a list of hydrated entities in the form of a list of maps."
[db-conn db-ids]
(->>
db-ids
seq
flatten
(map #(->>
%
;; id -> lazy entity map
(d/entity (d/db db-conn))
;; realize all values, except for db/id
d/touch
(into {:db/id %})))))
Is there a better way?
With the pull api, this is pretty easy now.
'[:find [(pull ?e [*]) ...]
:in $ [[?e] ...]
:where [?e]]
I used to take this approach to save queries to the DB, the code is probably less reusable but it depends on what is more critical in your current scenario. I haven't a Datomic instance configured as I am not working with it right now so it may contain syntax error but I hope you get the idea.
(def query-result '[:find ?cat-name ?id
:where
[?cat-name :category/name
[?id :db/id]])
=>
#{["x" 1234] ["x" 2223]}
(defn- describe-values
"Adds proper keys to the given values."
[keys-vec query-result]
(vec (map #(zipmap keys-vec %) query-result))
(describe-values [:category/name :db/id] query-result)
=>
[{:db/id 2223, :category/name "x"} {:db/id 1234, :category/name "x"}]
I want to find the most recent transaction made to a connection. The following does not seem to give the correct date:
(require '[datomic.api :as datomic])
(-> conn datomic/db datomic/basis-t datomic/t->tx (java.util.Date.))
I figured it out:
(defn last-transaction-time [db]
(let [t (-> db datomic/basis-t)]
[t (ffirst (datomic/q '[:find ?t
:in $ ?tx
:where [?tx :db/txInstant ?t]]
db
(datomic/t->tx t)))]))
You would probably want to let the result of your thread function up to the datomic/t->tx. Then use that to query for the transaction entity (implicitly created entity for each transaction). Each transaction entity has a :db/txInstant attribute that is implicitly added during a transaction. The value of that attribute is what you would want to pass to the java.util.Date. static method.
Should be much easier
(d/q
'[:find (max 1 ?tx)
:where
[?tx :db/txInstant]]
db)
I'm interested in entities and their timestamps. Essentially, I want a time-sorted list of entities.
To that end, I've composed the following functions:
(defn return-posts
"grabs all posts from Datomic"
[]
(d/q '[:find ?title ?body ?slug
:where
[?e :post/title ?title]
[?e :post/slug ?slug]
[?e :post/body ?body]] (d/db connection)))
(defn get-postid-from-slug
[slug]
(d/q '[:find ?e
:in $ ?slug
:where [?e :post/slug ?slug]] (d/db connection) slug))
(defn get-post-timestamp
"given an entid, returns the most recent timestamp"
[entid]
(->
(d/q '[:find ?ts
:in $ ?e
:where
[?e _ _ _]
[?e :db/txInstant ?ts]] (d/db connection) entid)
(sort)
(reverse)
(first)))
Which I feel must be a hack rooted in ignorance.
Would someone more well-versed in idiomatic Datomic usage chime in and upgrade my paradigms?
I was bothered by the idea of adding additional timestamps to a database that nominally understands time as a first-class principle and so (after a night of mulling on the approaches outlined by Ulrik Sandberg) evolved the following function:
(defn return-posts
"grabs all posts from Datomic"
[uri]
(d/q '[:find ?title ?body ?slug ?ts
:where
[?e :post/title ?title ?tx]
[?e :post/slug ?slug]
[?e :post/body ?body]
[?tx :db/txInstant ?ts]] (d/db (d/connect uri))))
It's idiomatic in Datalog to omit the binding to the transaction ID itself as we typically don't care. In this situation, we very definitely care and in the words of August Lileaas, wish to "traverse the transaction" (there are situations in which we'd want the post creation time, but for this application the transaction time will suffice for ordering entities).
A notable downside to this approach is that recently edited entries will be bumped up in the list. To that end, I'll have to do something later on in order to get their "first appearance" in Datomic for blog-standard post history.
To summarize:
I have bound the transaction entity ID per "post" entity ID, and then looked up the transaction timestamp with this function for later sorting.
There isn't really a more elegant way to do this than traversing the transactions. This is why I prefer to have a separate domain specific attribute for timestamps, instead of relying on the transaction timestamps from Datomic. One example where this is necessary is merging: let's say you have a wiki, and you want to merge two wiki pages. In that case, you probably want to control the timestamp yourself, and not use the timestamp from the transaction.
I like to have the attributes :created-at and :changed-at. When I transact new entities:
[[:db/add tempid :post/slug "..."]
[:db/add tempid :post/title "A title"]
[:db/add tempid :created-at (java.util.Date.)]
[:db/add tempid :changed-at (java.util.Date.)]]
Then for updates:
[[:db/add post-eid :post/title "An updated title"]
[:db/add post-eid :changed-at (java.util.Date.)]]
That way all I have to do is to read out the :created-at attribute of the entity, which will be ready and waiting in the index.
(defmacro find-one-entity
"Returns entity when query matches, otherwise nil"
[q db & args]
`(when-let [eid# (ffirst (d/q ~q ~db ~#args))]
(d/entity ~db eid#)))
(defn find-post-by-slug
[db slug]
(find-one-entity
'[:find ?e
:in $ ?slug
:where
[?e :post/slug ?slug]]
db
slug))
;; Get timestamp
(:created-at (find-post-by-slug db "my-post-slug"))