Tuesday, October 28, 2014

Datomic Pull API

Datomic's new Pull API is a declarative way to make hierarchical selections of information about entities. You supply a pattern to specify which attributes of the entity (and nested entities) you want to pull, and db.pull returns a map for each entity.

Pull API vs. Entity API

The Pull API has two important advantages over the existing Entity API:

Pull uses a declarative, data-driven spec, whereas Entity encourages building results via code. Data-driven specs are easier to build, compose, transmit and store. Pull patterns are smaller than entity code that does the same job, and can be easier to understand and maintain.

Pull API results match standard collection interfaces (e.g. Java maps) in programming languages, where Entity results do not. This eliminates the need for an additional allocation/transformation step per entity.

Wildcards

A pull pattern is a list of attribute specifications.  If you want all attributes, you can use the wildcard (*) specification along with an entity identifier.  (In the examples below, entity identifiers such as led-zeppelin are variables defined in the complete code examples in Clojure and Java.)

;; Clojure API
(d/pull db '[*] led-zeppelin)

;; Java API
db.pull("[*]", ledZeppelin)

A pull result is a map per entity, shown here in edn:

;; result
{:artist/sortName "Led Zeppelin", 
 :artist/name "Led Zeppelin", 
 :artist/type {:db/id 17592186045746}, 
 :artist/country {:db/id 17592186045576}, 
 :artist/gid #uuid "678d88b2-87b0-403b-b63d-5da7465aecc3", 
 :artist/endDay 25, 
 :artist/startYear 1968, 
 :artist/endMonth 9, 
 :artist/endYear 1980, 
 :db/id 17592186050305}

Attributes

You can also specify the attributes you want explicitly, as with :artist/name and :artist/gid below:

;; pattern
[:artist/name :artist/gid]

;; input 
led-zeppelin

;; result
{:artist/gid #uuid "678d88b2-87b0-403b-b63d-5da7465aecc3", 
 :artist/name "Led Zeppelin"}

The underscore prefix reverses the direction of an attribute, so :artist/_country pulls all the artists for a particular country:

;; pattern
[:artist/_country]

;; input
greatBritain

;; result
{:artist/_country [{:db/id 17592186045751} 
                   {:db/id 17592186045755} 
                    ...]}

Components

Datomic component attributes are pulled recursively by default, so the :release/media pattern below automatically returns a release's tracks as well:

;; pattern
[:release/media]

;; input
darkSideOfTheMoon

;; result
  {:release/media
   [{:db/id 17592186121277,
     :medium/format {:db/id 17592186045741},
     :medium/position 1,
     :medium/trackCount 10,
     :medium/tracks
     [{:db/id 17592186121278,
       :track/duration 68346,
       :track/name "Speak to Me",
       :track/position 1,
       :track/artists [{:db/id 17592186046909}]}
      {:db/id 17592186121279,
       :track/duration 168720,
       :track/name "Breathe",
       :track/position 2,
       :track/artists [{:db/id 17592186046909}]}
      {:db/id 17592186121280,
       :track/duration 230600,
       :track/name "On the Run",
       :track/position 3,
       :track/artists [{:db/id 17592186046909}]}
      ...]}]}

Map Specifications

Instead of just an attribute name, you can use a nested map specification to pull related entities.  The pattern below pulls the :db/id and :artist/name of each artist:

;; pattern
[:track/name {:track/artists [:db/id :artist/name]}]

;; input
ghostRiders

;; result
{:track/artists [{:db/id 17592186048186, :artist/name "Bob Dylan"}
                 {:db/id 17592186049854, :artist/name "George Harrison"}],
 :track/name "Ghost Riders in the Sky"}

And of course everything nests arbitrarily, in case you need the release's medium's track's names and artists:

 ;; pattern
[{:release/media
  [{:medium/tracks
    [:track/name {:track/artists [:artist/name]}]}]}]

;; input
concertForBanglaDesh

;; result
 [{:medium/tracks
   [{:track/artists
     [{:artist/name "Ravi Shankar"} {:artist/name "George Harrison"}],
     :track/name "George Harrison / Ravi Shankar Introduction"}
    {:track/artists [{:artist/name "Ravi Shankar"}],
     :track/name "Bangla Dhun"}]}
  {:medium/tracks
   [{:track/artists [{:artist/name "George Harrison"}],
     :track/name "Wah-Wah"}
    {:track/artists [{:artist/name "George Harrison"}],
     :track/name "My Sweet Lord"}
    {:track/artists [{:artist/name "George Harrison"}],
     :track/name "Awaiting on You All"}
    {:track/artists [{:artist/name "Billy Preston"}],
     :track/name "That's the Way God Planned It"}]
   ...]}

Try It Out

The Pull API has many other capabilities not shown here.  See the full docs for defaults, limits, bounded and unbounded recursion, and more.  Or check out the examples in Clojure and Java.