Skip to content

Instantly share code, notes, and snippets.

@statonjr
Last active April 14, 2021 22:28
Show Gist options
  • Star 5 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save statonjr/9b000197ed7bde72029c to your computer and use it in GitHub Desktop.
Save statonjr/9b000197ed7bde72029c to your computer and use it in GitHub Desktop.
Datomic Dynamic Find Clause

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.

@tangrammer
Copy link

Thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment