Find Specifications
Every query so far has used the default :find form, which returns a collection of tuples. Datomic Datalog offers four distinct find specifications, each returning a different shape. Choosing the right one makes downstream code simpler.
Relation (default) — collection of tuples
The standard form. Each variable becomes a column; each matching combination becomes a row. Order among the rows is not guaranteed.
Collection — flat vector of one variable
Wrap a single variable in [?var ...] to get a flat vector of its values instead of a collection of single-element tuples. Useful when you only care about one column and want to pass it directly to other code:
Scalar — single value
Append . after the variable to return exactly one value instead of a collection. When multiple rows match, which one you get is arbitrary, so scalars are most useful with aggregations that already produce a single row, like (max ...):
Without the . this would return [[150]]: a collection containing one single-element tuple. With it you get the bare number 150.
Tuple — single row
Wrap multiple variables in [?a ?b] (no ...) to return exactly one tuple. If the query matches several rows, you get one of them and Datomic does not tell you the others existed, so the query has to be guaranteed to match one row. That takes two things: the lookup goes through a unique attribute, and every other attribute it joins is cardinality one. In this database :pokemon/number is declared :db.unique/identity, and :pokemon/name, :stat/hp and :stat/speed each hold one value per Pokemon:
The result is a plain vector ["Pikachu" 35 90] rather than a collection of tuples, easy to destructure directly in Clojure. Any number that exists in the database gives exactly one row.
What could go wrong
Break either condition and the tuple spec stops being safe. This query looks the Pokemon up by :pokemon/name, which the schema does not make unique, and it joins :pokemon/type, which is cardinality many. Bulbasaur is Grass and Poison, so 2 rows match. Before you run it, predict what the tuple spec returns:
You get a single tuple with one of the two types. There is no error and no warning, and the other type is gone. Swap in "Pikachu" and the same query looks fine, but only because Pikachu happens to have one type.
When you want one entity with everything it has, use pull with the scalar find spec instead. The Pull chapter covers it: a cardinality-many attribute comes back as a collection holding every value.
Look up other Pokemon by number in the safe query, such as "150" or "143". Then change the name in the Bulbasaur query to "Charizard" or "Pikachu" and predict the result each time. Or combine the scalar spec with (min ?speed) to return just the single lowest speed value in the Pokedex.
Under the hood: Peer API vs Client API
Datomic has two Clojure APIs for running queries: the Peer API (datomic.api) and the Client API (datomic.client.api). They handle find specs differently, so it helps to know which one your own code uses.
- Peer API: all four find specs work as described above, and a relation find returns a set of tuples.
- Client API: only relation finds are accepted, and they return a vector of tuples instead of a set. To get the other shapes, reshape the result yourself:
(mapv first result)for a collection,(ffirst result)for a scalar,(first result)for a tuple.
This site runs on Datomic Local, which uses the Client API. So you can still try every form above, the server rewrites each query into a relation find and reshapes the result to match what the Peer API would give you. The one visible difference is the relation example at the top of this chapter, which prints as a vector where the Peer API would print a set.
- Choose the find spec, relation, collection, scalar, or tuple, that matches the shape your code needs.
- Know what makes a tuple spec safe: a unique lookup plus cardinality-one attributes.
- Explain how the Peer and Client APIs differ in the result shapes they return.