2

I am working with clojure's datomic.api. I wish to refactor a somewhat complex datalog query such as this:

(datomic.api/q '[:find [?value ...] :in $ ?uid ?component :where
                [...some clause...]
                [...some other clause...]
                (or-join [?entitlement ?component]
                         (and [...some conditional stuff...])
                         (and [...some other conditional stuff...]))]
                db uid component)

...into something more readable. My desire is to locally bind the (and...) components of the query inside a let and refer to them by a name inside of the datalog list. Like so:

(datomic.api/q '[:find [?value ...] :in $ ?uid ?component :where
                [...some clause...]
                [...some other clause...]
                (or-join [?entitlement ?component]
                         entitled-for-component
                         entitled-for-application)]
                db uid component)

Various quoting in let (and unquoting inside of datomic.api/q's list) have not worked. Any suggestions?

Dave Owens
  • 111
  • 1
  • 5
  • Yep. The quoting/unquoting approach sort-of works if you use syntax quoting, but then you'll end up with things like *and* being replaced by namespace-qualified symbols, which will break it a more different way. Your answer below is the way to go about it. – Tony K. Aug 05 '15 at 17:47
  • I like the answer below, to use rules. To quasiquote in Clojure like you would in other lisps, try the `template` form in [backtick](https://github.com/brandonbloom/backtick). – Greg Hendershott Apr 07 '16 at 12:13

1 Answers1

2

Datomic uses rules to solve this problem.

Datomic datalog allows you to package up sets of :where clauses into named rules. These rules make query logic reusable, and also composable, meaning that you can bind portions of a query's logic at query time.

Rulesets are defined as a list of lists, then used as an additional input with datomic.api/q bound to the % character.

(def rules [[(name-for-id restaurant-id?)
             [restaurant-id? :restaurant/name name?]]])

(datomic.api/q '[:find ?name . :in $ % ?restaurant-id :where
                 (name-for-id restaurant-id?)] db rules 42)

=> "Milliways"

Note that datomic.api/q expects a rule set, passing a single rule will not work.

The first list in a rule defines the rule name as the first item, followed by one or more parameters. The subsequent vectors contain one or more :where clauses.

Also,

As with other where clauses, you may specify a database before the rule-name to scope the rule to that database. Databases cannot be used as arguments in a rule.

Dave Owens
  • 111
  • 1
  • 5
  • You missed a quote: (def rules '[[(name-for-id restaurant-id?) [restaurant-id? :restaurant/name name?]]]) – onetom Feb 18 '22 at 05:53