getting items by eid from datomic, too few inputs? - clojure

So I'm trying to use the entity id to retrieve items recently transacted to the datomic database.
However, when invoking (get-post-by-eid zzzzzzzzz) I get an error
IllegalArgumentExceptionInfo :db.error/too-few-inputs Query expected 2 inputs but received 1 datomic.error/arg (error.clj:57)
(defn get-post-by-eid [eid]
(d/q '[:find ?title ?content ?tags ?eid
:in $ ?eid
:where
[?eid post/title ?title]
[?eid post/content ?content]
[?eid post/tag ?tags]] (d/db conn)))
So I figure my query string must be malformed..
I've been looking at http://www.learndatalogtoday.org/chapter/3 but still not sure where I'm going astray.
Any help is appreciated (=

Your :in clause specifies that you're expecting two data sources to be passed to the q function. $ ?eid means that you're saying you're going to pass in a database (bound to $), and some other value, which will be bound to ?eid.
Like this:
(defn get-post-by-eid [eid]
(d/q '[:find ?title ?content ?tags ?eid
:in $ ?eid
:where
[?eid post/title ?title]
[?eid post/content ?content]
[?eid post/tag ?tags]]
(d/db conn)
eid))
Otherwise, there's no way for your eid parameter to actually get "into" the query. You have to pass it explicitly, there's no magic there.

Related

How to find only one record from query in datomic?

I'm doing a query on datomic using datomic.api like the following:
(d/q
'[:find [(pull ?a [*]) ...]
:in $ ?title
:where
[?a :movie/title ?title]]
db title)
This query is returning almost the expected value, but as an array, like this:
[ {:db/id 17592186045442, :movie/title "Test", :movie/year 1984, :movie/director #:db{:id 17592186045439 }} ]
I want this query to return only the first match, and not all the results. What I'm doing wrong?
I found a solution for my specific case. The real issue was that I was not understanding the datomic query correctly.
[:find [(pull ?a [*]) ...]
This part is telling datomic to retrieve more than one result.
I changed the query to the following one:
(d/q
'[:find (pull ?a [*]) .
:in $ ?title
:where
[?a :movie/title ?title]]
db title)
And it worked!
The key thing was to remove the "[" after :find keyword, and switch the "..." for only ".".
If this doesn't work for you, look on the link that #EugenePakhomov posted on the comments: Equivalent of SQL "limit" clause in Datomic
It is documented in the official Datomic documentation:
Find Spec
:find ?a ?b relation (Collection of Lists)
:find [?a …] collection (Collection)
:find [?a ?b] single tuple (List)
:find ?a . single scalar (Scalar Value)

Error returning a Map instead of a Vector using Datomic

I'm doing some query in Datomic using Clojure, I'm trying to return a Map with keys instead of a Vector, if I don't try to return a Map with the ":keys" keyword in the query it works fine.
I tried to have equal and different names between the :find and :keys.
If I remove the :keys line bellow it works fine.
I'm using [org.clojure/clojure "1.10.0"] with [com.datomic/client-pro "0.8.28"].
(def get-links
'[:find ?e ?url ?description ?createdat ?order ?postedby
:keys e url description createdat order postedby
:in $ ?filter ?skip ?skip-plus-first
:where [?e :link/url ?url]
[?e :link/description ?description]
[?e :link/createdat ?createdat]
[?e :link/postedby ?e2]
[?e :link/order ?order]
[?e2 :user/name ?postedby]
[(.contains ?url ?filter)]
[(> ?order ?skip) ]
[(<= ?order ?skip-plus-first)]])
Here is how I'm calling it:
(d/q get-links db filter skip (+ first skip))
The exact error is:
Execution error (ExceptionInfo) at datomic.client.api.async/ares (async.clj:56).
"Argument :keys in :find is not a variable"
Below is Datomic examples, in their docs.
[:find ?artist-name ?release-name
:keys artist release
:where [?release :release/name ?release-name]
[?release :release/artists ?artist]
[?artist :artist/name ?artist-name]]
I think that you are using an older version of the client that doesn't know the :keys option yet.

Datomic - how to retract all values of an attribute

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))

how to get the datetime of the last transaction in a datomic db?

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)

Retrieve Most Recent Entity from Datomic

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"))