Skip to content

Composable datalog queries

Petter Eriksson edited this page Dec 16, 2017 · 9 revisions

We created a small libraries to easier create and compose datomic queries. Our datomic queries are maps all the way until the query is executed.

Format

{:find <find-pattern> 
 :where <where-clauses>
 :symbols <map-from-symbol-to-value>
 :with <with-symbol>
 :fulltext <seq of maps describing a full text search>
 :rules <seq of functions returning rules>}

Example

;; Given a first name and keywords describing the person, create a
;; query finding users matching the description.
(def query
  {:where    '[[?e :user/first-name  ?first-name]
               [?e :user/description ?description]]
   :symbols  {'?first-name     "Jessica"
              '[?keywords ...] ["clojure" "dev"]}
   :fulltext '[{:attr   :user/description
                :arg    ?keywords
                :return [[?e ?description _ ?score]]}]}

;; Given this query, we can now use it in different ways.

(require '[eponai.common.database :as db])

;; Gets the first match by adding :find clause '[?e .]
(db/one-with db query)

;; Gets all matches by adding :find clause [[?e ...]]
(db/all-with db query)

;; Specify our own find pattern:
(db/find-with db (db/merge-query query 
                                 {:find [[?user ?description (max ?score)]]})

;; Note: db/merge-query can merge any of the keys, so we can for example 
;;       more symbols, where-clauses or fulltext searches, any time we want.

Library properties

  • Queries can be merged with other queries using (merge-query base addition).
  • Values normally passed as extra arguments to datomic.api/q is passed to the :symbols map.
  • Rules are passed as 0-arity functions that return rules. They are deduped before query is executed.
  • Separate and combined functions for pull:
    • (db/pull db pattern id)
    • (db/pull-many db pattern ids)
    • (db/pull-one-with db pattern query)
    • (db/pull-all-with db pattern query)
  • Fulltext search works for both datomic and (normally not supported) datascript. The key takes a list of searches, where each search is a map with #{:attr :arg :return :db} where :db is optional.
    • :attr - the attribute to search values for
    • :arg - symbol providing search string
    • :return - a where clause describing the return value with format [[?entity-id ?value ?tx ?relevance-score]]
    • :db - optional db symbol, defaults to $

Omitting :find from queries

We found that we usually just want to return one or many datoms matching the query, so we usually don't supply a find pattern, and instead call our functions one-with or all-with, which adds the [?e .] or [[?e ...]] find pattern respectively.

Using one-with and all-with reads quite nicely in the code as well. If you want to use your own find-pattern, use (db/find-with db query).

The functions in the entire eponai.common.database library is made to work with both datomic and datascript. The functions will do protocol dispatch on the database-connection or database-value argument.