|
| 1 | +=== Traversing Datomic Indices |
| 2 | +[role="byline"] |
| 3 | +by Alan Busby and Ryan Neufeld |
| 4 | + |
| 5 | +==== Problem |
| 6 | + |
| 7 | +You want to execute simple Datomic queries with high-performance. |
| 8 | + |
| 9 | +==== Solution |
| 10 | + |
| 11 | +Use the +datomic.api/datoms+ function to directly access the core |
| 12 | +Datomic indices in your database. |
| 13 | + |
| 14 | +To follow along with this recipe, complete the steps in |
| 15 | +<<sec_datomic_connect_to_datomic_solution>> and |
| 16 | +<<sec_datomic_schema_solution>>. After doing this you will have a |
| 17 | +connection, +conn+, and a schema installed against which you can |
| 18 | +insert data. |
| 19 | + |
| 20 | +For example, to quickly find the entities which have the provided attribute and |
| 21 | +value set, invoke +datomic.api/datoms+, specifying the +:avet+ index |
| 22 | +(attribute, value, entity, transaction) and the desired attribute and |
| 23 | +value. |
| 24 | + |
| 25 | +[NOTE] |
| 26 | +==== |
| 27 | +This will only work for attributes where +:db/index+ is true, or |
| 28 | ++:db/unique+ is not nil. |
| 29 | +==== |
| 30 | + |
| 31 | +[source,clojure] |
| 32 | +---- |
| 33 | +(require '[datomic.api :as d]) |
| 34 | +
|
| 35 | +(d/transact conn [{:db/id (d/tempid :db.part/user) |
| 36 | + :user/name "Barney Rubble" |
| 37 | + :user/email "[email protected]"}]) |
| 38 | +
|
| 39 | +(defn entities-with-attr-val |
| 40 | + "Return entities with a given attribute and value." |
| 41 | + [db attr val] |
| 42 | + (->> (d/datoms db :avet attr val) |
| 43 | + (map :e) |
| 44 | + (map (partial d/entity db)))) |
| 45 | +
|
| 46 | +
|
| 47 | +(def barney (first (entities-with-attr-val (d/db conn) |
| 48 | + :user/email |
| 49 | + |
| 50 | +
|
| 51 | +(:user/email barney) |
| 52 | + |
| 53 | +
|
| 54 | +---- |
| 55 | + |
| 56 | +To quickly determine all of the attributes an entity has, use the |
| 57 | ++:eavt+-ordered index. |
| 58 | + |
| 59 | +[source,clojure] |
| 60 | +---- |
| 61 | +(defn entities-attrs |
| 62 | + "Return attrs of an entity" |
| 63 | + [db entity] |
| 64 | + (->> (d/datoms db :eavt (:db/id entity)) |
| 65 | + (map :a) |
| 66 | + (map (partial d/entity db)) |
| 67 | + (map :db/ident))) |
| 68 | +
|
| 69 | +(entities-attrs (d/db conn) barney) |
| 70 | +;; -> (:user/email :user/name) |
| 71 | +---- |
| 72 | + |
| 73 | +To quickly find entities that refer, via +:db.type/ref+, to a provided |
| 74 | +entity, use the +:vaet+-ordered index. |
| 75 | + |
| 76 | +[source,clojure] |
| 77 | +---- |
| 78 | +;; Add a person that refers to a :user.roles/author role |
| 79 | +(d/transact conn [{:db/id (d/tempid :db.part/user) |
| 80 | + :user/name "Ryan Neufeld" |
| 81 | + |
| 82 | + :user/roles [:user.roles/author :user.roles/editor]}]) |
| 83 | +
|
| 84 | +(defn referring-to |
| 85 | + "Find all entities referring to an entity as a certain attribute." |
| 86 | + [db entity] |
| 87 | + (->> (d/datoms db :vaet (:db/id entity) ) |
| 88 | + (map :e) |
| 89 | + (map (partial d/entity db)))) |
| 90 | +
|
| 91 | +(def author-entity (d/entity (d/db conn) :user.roles/author)) |
| 92 | +
|
| 93 | +;; The names of all users with a :user.roles/author role |
| 94 | +(map :user/name (referring-to (d/db conn) author-entity)) |
| 95 | +;; -> ("Ryan Neufeld") |
| 96 | +---- |
| 97 | + |
| 98 | +==== Discussion |
| 99 | + |
| 100 | +For simple lookup queries, like "find by attribute" or "find by |
| 101 | +value", nothing beats Datomic's raw indices in terms of performance. |
| 102 | +The +datomic.api/datoms+ interface provides access to all of Datomic's |
| 103 | +indices, and conveniently lets you dive in any number of levels, |
| 104 | +"biting off" only the data you need. |
| 105 | + |
| 106 | +As with most Datomic functions, +datoms+ takes a +db+ as its first |
| 107 | +argument. You'll not in our examples, and elsewhere in the book, we |
| 108 | +too accept a database as a value, and not a connection--this idiom |
| 109 | +allows API users to perform varying numbers of operations on the same |
| 110 | +database value. You should always try to do this yourself. |
| 111 | + |
| 112 | ++datoms+ second argument is the particular index you want to access. |
| 113 | +Each of these is a permutation of e, entity, a, attribute, v, value, |
| 114 | +and t, transaction. The order of letters in an index indicates how it |
| 115 | +is indexed. For example, +:eavt+ should be traversed by entity, then |
| 116 | +attribute, then so on and so forth. The four indices and what they |
| 117 | +include are as follows: |
| 118 | + |
| 119 | +* +:eavt+ - An entity-first index which includes all datoms. This |
| 120 | + index provides a view over your database very much like a |
| 121 | + traditional relational-database. |
| 122 | +* +:aevt+ - An "attribute, then entity" index which includes all datoms. This |
| 123 | + index provides columnar access to your database, much like a data |
| 124 | + warehouse. |
| 125 | +* +:avet+ - An "attribute-value" index which only includes attributes |
| 126 | + where +:db/index+ is +true+. Incredibly useful as a lookup index |
| 127 | + (e.g. "I need the entity with email of ' [email protected]'"). |
| 128 | +* +:vaet+ - A value-first index which only includes +:db.type/ref+ |
| 129 | + values. A very interesting index that can be used to treat your data |
| 130 | + a bit like a graph database. |
| 131 | + |
| 132 | +After specifying an index ordering, you can optionally provide any |
| 133 | +number of components to pre-traverse the index. This serves to reduce |
| 134 | +the number of elements returned. For example, specifying just an |
| 135 | +attribute component for AVET traversal will return any entity with |
| 136 | +that attribute. Specifying an attribute and a value component, on the |
| 137 | +other hand, will return only entities with that specify attribute and |
| 138 | +value pair. |
| 139 | + |
| 140 | +What is returned by +datoms+ is a stream of +Datum+ objects. Each |
| 141 | +datum responds to +:a+, +:e+ and +:v+ as functions, returning the |
| 142 | +attribute, entity and value of the datom, respectively. |
0 commit comments