trydatomic

Chapters

Pull Expressions

Every query so far has listed each desired attribute as a separate :find variable. Pull lets you retrieve a whole entity, or a chosen subset of its attributes, as a single map, without enumerating every binding explicitly.

The syntax is (pull ?e pattern) inside :find, where pattern is a vector of attribute keywords (or [*] for all of them).

Selective pull

List only the attributes you care about. Here we fetch name and speed for every Pokemon faster than 100, getting back one map per entity instead of a two-column tuple:

Query
[:find (pull ?e [:pokemon/name :stat/speed]) :where [?e :stat/speed ?speed] [(> ?speed 100)]]

Each result row contains a single map. This is especially useful when returning wide entities. Without pull, every attribute needs its own variable in :find and its own clause in :where, and each row is a flat tuple:

Query
[:find ?name ?hp ?attack ?defense ?sp-attack ?sp-defense ?speed :where [?e :pokemon/name ?name] [?e :stat/hp ?hp] [?e :stat/attack ?attack] [?e :stat/defense ?defense] [?e :stat/sp-attack ?sp-attack] [?e :stat/sp-defense ?sp-defense] [?e :stat/speed ?speed] [(> ?speed 100)]]

With pull, the same Pokemon and attributes need one expression, and each row is a map that names its own values:

Query
[:find (pull ?e [:pokemon/name :stat/hp :stat/attack :stat/defense :stat/sp-attack :stat/sp-defense :stat/speed]) :where [?e :stat/speed ?speed] [(> ?speed 100)]]

Pull keeps entities that a join would drop

The Modeling data chapter showed that a join silently drops any entity missing one of its attributes. Only 4 Pokemon have a :pokemon/category, so joining it returns only those 4 names:

Query
[:find ?name :where [?e :pokemon/name ?name] [?e :pokemon/category ?category]]

Pull instead keeps every entity, whether or not it has the attribute, with the key simply absent when there is nothing to pull. Wrapping the pull in a collection find spec ([...]) returns one map per entity, the form you will reach for most often in real code:

Query
[:find [(pull ?e [:pokemon/name :pokemon/category]) ...] :where [?e :pokemon/name _]]

All 151 Pokemon come back, most without a :pokemon/category key at all. get-else, from Function clauses, fills in a default for a join; pull's own :default option below does the same.

Wildcard pull

Use [*] to pull every attribute on the entity, including Datomic's own :db/id:

Query
[:find (pull ?e [*]) :where [?e :pokemon/name "Pikachu"]]

The wildcard is handy for exploration and debugging. In production code, prefer listing attributes explicitly so the shape of your data is clear and stable.

Pikachu evolves, so its wildcard pull also returns :evolution/next. That attribute is a reference to another entity, so all a wildcard gives you is its :db/id. The sections below show how to follow it.

Pull with scalar find spec

Combine pull with the scalar find spec (.) to get a single entity map with no wrapping collection or tuple:

Query
[:find (pull ?e [:pokemon/name :pokemon/number :pokemon/type :stat/hp :stat/speed]) . :where [?e :pokemon/name "Pikachu"]]

The result is a bare map, easy to pass directly to the rest of your application. Note that :pokemon/type comes back as ["Electric"], a vector, even though Pikachu has only one. Pull always returns a cardinality-many attribute as a collection, no matter how many values the entity actually has.

Attribute options

Wrap an attribute in a list to give it options. The first is :as, which renames the key in the result:

Query
[:find (pull ?e [(:pokemon/name :as "name") :stat/speed]) :where [?e :pokemon/name "Bulbasaur"]]

:limit caps how many values a cardinality-many attribute returns. Bulbasaur has two types, but this returns only one of them:

Query
[:find (pull ?e [:pokemon/name (:pokemon/type :limit 1)]) :where [?e :pokemon/name "Bulbasaur"]]

:default supplies a value when the entity doesn't have the attribute at all. Bulbasaur has no :pokemon/category, so we get the default. Without one, the key would be left out of the map:

Query
[:find (pull ?e [:pokemon/name (:pokemon/category :default :none)]) :where [?e :pokemon/name "Bulbasaur"]]

Options can be combined in one pattern, each on its own attribute:

Query
[:find (pull ?e [(:pokemon/name :as "name") (:pokemon/type :limit 1) (:pokemon/category :default :none)]) :where [?e :pokemon/name "Bulbasaur"]]

Your turn. Pull Pikachu's name under the key "name", and its :evolution/next with a default of :none. Then try the same for Raichu.

Query

Following references

:evolution/next is a reference: its value is another entity. To pull that entity too, put the attribute in a map with a pattern for the entity it points at:

Query
[:find (pull ?e [:pokemon/name {:evolution/next [:pokemon/name :pokemon/number]}]) :where [?e :pokemon/name "Charmander"]]

The value of :evolution/next is now a collection of maps, one for each Pokemon Charmander evolves into, and each map follows the pattern we gave it.

A pattern can follow several references at once, each with its own pattern. Here is everything the database knows about Fire's matchups:

Query
[:find (pull ?t [:type/name {:type/strong-against [:type/name]} {:type/weak-against [:type/name]} {:type/no-effect-on [:type/name]}]) :where [?t :type/name "Fire"]]

Reverse lookups

Put an underscore in front of the attribute name to follow a reference backwards. :evolution/_next finds the entities whose :evolution/next points at this one:

Query
[:find (pull ?e [:pokemon/name {:evolution/_next [:pokemon/name]}]) :where [?e :pokemon/name "Charizard"]]

Charizard evolves from Charmeleon. Nothing had to be stored twice: a reference can be followed in either direction.

Recursion

Use ... instead of a pattern to follow the same reference again and again, for as long as there is something to follow. A number, like {:evolution/next 2}, limits how many levels to follow:

Query
[:find (pull ?e [:pokemon/name {:evolution/next ...}]) :where [?e :pokemon/name "Charmander"]]

This gives Charmander's whole evolution line as a nested map.

TRY

Change "Charmander" to "Eevee" in the recursive pull to see it branch three ways. Or add :stat/attack and :stat/defense to the selective pull pattern at the top of this page.

You can now
  • Retrieve a whole entity, or a chosen subset of attributes, as one map with pull.
  • Follow a reference forward or backward, and recurse across a chain of them with ....
  • Rename, limit, or default an attribute's value with pull's per-attribute options.