We're working on a project that uses the Datomic Pull API to pull specific attributes out of Datomic entities. Here's an example of query that uses the Pull API:
(d/q '[:find [(pull ?e [:sales/deal_number :sales/deal_close_date :sales/state]) ...]
:in $ ?date
:where [?e :sales/deal_close_date ?d _ _] [(> ?d ?date)]]
db
(days-ago-at-midnight 1))
Datomic will return the :sales/deal_number
, :sales/deal_close_date
, and :sales/state
attributes for each found entity. This works if you know the attributes ahead of time. But what if you want to pass in the attributes at runtime? Suppose we want to pass the following vector of attributes to the query:
(def sales-attrs [:sales/deal_number :sales/deal_close_date :sales/state])
We need to replace the attributes in [:find [(pull ?e [:sales/deal_number :sales/deal_close_date :sales/state]) ...]
with sales-attrs
while keeping the remaining clauses:
[:find [(pull ?e sales-attrs) ...]
:in $ ?date
:where [?e :sales/contract_deal_date ?d _ _] [(> ?d ?date)]]
Because queries in Datomic are data, we can build them up from smaller pieces of data and wrap them in a function.
(defn find-by-attrs [attrs]
(apply conj '[:find] [(list 'pull '?e attrs) '...]
'[:in $ ?date :where [?e :sales/deal_close_date ?d _ _] [(> ?d ?date)]]))
Here we create three pieces of data: '[:find]
, [(list 'pull '?e attrs) '...]
, '[:in $ ?date :where [?e :sales/deal_close_date ?d _ _] [(> ?d ?date)]]
, and then apply conj to the data to create another piece of data. If we call this function with sales-attrs
we get what we want:
user=> (find-by-attrs sales-attrs)
[:find [(pull ?e [:sales/deal_number :sales/deal_close_date :sales/state]) ...] :in $ ?date :where [?e :sales/contract_deal_date ?d _ _] [(> ?d ?date)]]
To execute the query, just use datomic.api/q
as usual:
(d/q (find-by-attrs sales-attrs) db (days-ago-at-midnight 1))
You could also use syntax-quote
and unquote
to achieve the same effect. That felt a little too magical for me. I ran into namespace issues and the original solution worked.
Thanks to @kbaribeau, @bostonaholic, and @jballanc for helping me figure this out.
Thanks!