Building Queries from Java Data

Datomic queries are expressed as data structures, either as a list:

[:find ?e
 :in $ ?name
 :where [?e :user/firstName ?name]]

Or as a map:

{:find [?e]
 :in [$ ?name]
 :where [[?e :user/firstName ?name]]}

The two forms above are equivalent; both search a database $ and an input ?name to find entities with a given first name. The list form has less collection nesting, and is commonly preferred when the queries are being written and maintained by humans.

Build Queries Programmatically

For Java programs that need to build queries, datomic.Util includes the maplist, and read helper methods. Using them, the query above could be built as:

list(read(":find"), read("?e"),
     read(":in"), read("$"), read("?name"),
     read(":where"), list(read("?e"), read(":user/firstName"), read("?name")));

Pass Serialized edn

As a convenience, you can also pass a serialized edn string as a query. Simply put double quotes around a list or map form:

"{:find [?e]
  :in [$ ?name]
  :where [[?e :user/firstName ?name]]}"

The string form of a query is a convenience for JVM languages that do not support collection literals. This convenience is for human authors only. You should not use the string form in languages (such as Clojure or JRuby) that have good collection literals. More importantly, you should never build queries by string concatenation.

Prefer Collection Binding over Programmatic Query

Using a collection binding form in an :in clause can sometimes eliminate the need for dynamic query generation. Continuing the example above, imagine that you want to find people whose first name is either "Stewart" or "Stuart". Where the scalar binding form ?name would expect a single name as input, the collection binding form [?name …] expects a collection of names, each of which will be bound to ?name:

List names = list("Stewart", "Stuart");
q("[:find ?e :in $ [?name ...] :where [?e :user/firstName ?name]]", 
   db, names));

The collection binding form can be used anywhere. Let's say that you do not know in advance which attribute values you need to find, nor even which attributes. E.g. you want to find all the "Stewart"s and "Stuart"s, but they might appear as a first name *or* a last name. Simply use a collection binding form for both the attribute and value positions:

List names = list("Stewart", "Stuart");
List nameAttributes = list(read(":user/firstName"), read(":user/lastName"));
q("[:find ?e :in $ [?name ...] [?attr ...] :where [?e ?attr ?name]]", 
   db, names, nameAttributes));

The full source for the examples above is available online.

Have more questions? Submit a request

0 Comments

Please sign in to leave a comment.
Powered by Zendesk