├── .gitignore
├── CHANGELOG.md
├── LICENSE
├── README.md
├── README.org
├── doc
└── table.gif
├── project.clj
├── src
└── datomic_tutorial
│ └── core.clj
└── test
└── datomic_tutorial
└── core_test.clj
/.gitignore:
--------------------------------------------------------------------------------
1 | /target
2 | /classes
3 | /checkouts
4 | pom.xml
5 | pom.xml.asc
6 | *.jar
7 | *.class
8 | /.lein-*
9 | /.nrepl-port
10 | .hgignore
11 | .hg/
12 | /README.html
13 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Change Log
2 | All notable changes to this project will be documented in this file. This change log follows the conventions of [keepachangelog.com](http://keepachangelog.com/).
3 |
4 | ## [Unreleased]
5 | ### Changed
6 | - Add a new arity to `make-widget-async` to provide a different widget shape.
7 |
8 | ## [0.1.1] - 2017-03-14
9 | ### Changed
10 | - Documentation on how to make the widgets.
11 |
12 | ### Removed
13 | - `make-widget-sync` - we're all async, all the time.
14 |
15 | ### Fixed
16 | - Fixed widget maker to keep working when daylight savings switches over.
17 |
18 | ## 0.1.0 - 2017-03-14
19 | ### Added
20 | - Files from the new template.
21 | - Widget maker public API - `make-widget-sync`.
22 |
23 | [Unreleased]: https://github.com/your-name/datomic-tutorial/compare/0.1.1...HEAD
24 | [0.1.1]: https://github.com/your-name/datomic-tutorial/compare/0.1.0...0.1.1
25 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE PUBLIC
2 | LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION OF THE PROGRAM
3 | CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT.
4 |
5 | 1. DEFINITIONS
6 |
7 | "Contribution" means:
8 |
9 | a) in the case of the initial Contributor, the initial code and
10 | documentation distributed under this Agreement, and
11 |
12 | b) in the case of each subsequent Contributor:
13 |
14 | i) changes to the Program, and
15 |
16 | ii) additions to the Program;
17 |
18 | where such changes and/or additions to the Program originate from and are
19 | distributed by that particular Contributor. A Contribution 'originates' from
20 | a Contributor if it was added to the Program by such Contributor itself or
21 | anyone acting on such Contributor's behalf. Contributions do not include
22 | additions to the Program which: (i) are separate modules of software
23 | distributed in conjunction with the Program under their own license
24 | agreement, and (ii) are not derivative works of the Program.
25 |
26 | "Contributor" means any person or entity that distributes the Program.
27 |
28 | "Licensed Patents" mean patent claims licensable by a Contributor which are
29 | necessarily infringed by the use or sale of its Contribution alone or when
30 | combined with the Program.
31 |
32 | "Program" means the Contributions distributed in accordance with this
33 | Agreement.
34 |
35 | "Recipient" means anyone who receives the Program under this Agreement,
36 | including all Contributors.
37 |
38 | 2. GRANT OF RIGHTS
39 |
40 | a) Subject to the terms of this Agreement, each Contributor hereby grants
41 | Recipient a non-exclusive, worldwide, royalty-free copyright license to
42 | reproduce, prepare derivative works of, publicly display, publicly perform,
43 | distribute and sublicense the Contribution of such Contributor, if any, and
44 | such derivative works, in source code and object code form.
45 |
46 | b) Subject to the terms of this Agreement, each Contributor hereby grants
47 | Recipient a non-exclusive, worldwide, royalty-free patent license under
48 | Licensed Patents to make, use, sell, offer to sell, import and otherwise
49 | transfer the Contribution of such Contributor, if any, in source code and
50 | object code form. This patent license shall apply to the combination of the
51 | Contribution and the Program if, at the time the Contribution is added by the
52 | Contributor, such addition of the Contribution causes such combination to be
53 | covered by the Licensed Patents. The patent license shall not apply to any
54 | other combinations which include the Contribution. No hardware per se is
55 | licensed hereunder.
56 |
57 | c) Recipient understands that although each Contributor grants the licenses
58 | to its Contributions set forth herein, no assurances are provided by any
59 | Contributor that the Program does not infringe the patent or other
60 | intellectual property rights of any other entity. Each Contributor disclaims
61 | any liability to Recipient for claims brought by any other entity based on
62 | infringement of intellectual property rights or otherwise. As a condition to
63 | exercising the rights and licenses granted hereunder, each Recipient hereby
64 | assumes sole responsibility to secure any other intellectual property rights
65 | needed, if any. For example, if a third party patent license is required to
66 | allow Recipient to distribute the Program, it is Recipient's responsibility
67 | to acquire that license before distributing the Program.
68 |
69 | d) Each Contributor represents that to its knowledge it has sufficient
70 | copyright rights in its Contribution, if any, to grant the copyright license
71 | set forth in this Agreement.
72 |
73 | 3. REQUIREMENTS
74 |
75 | A Contributor may choose to distribute the Program in object code form under
76 | its own license agreement, provided that:
77 |
78 | a) it complies with the terms and conditions of this Agreement; and
79 |
80 | b) its license agreement:
81 |
82 | i) effectively disclaims on behalf of all Contributors all warranties and
83 | conditions, express and implied, including warranties or conditions of title
84 | and non-infringement, and implied warranties or conditions of merchantability
85 | and fitness for a particular purpose;
86 |
87 | ii) effectively excludes on behalf of all Contributors all liability for
88 | damages, including direct, indirect, special, incidental and consequential
89 | damages, such as lost profits;
90 |
91 | iii) states that any provisions which differ from this Agreement are offered
92 | by that Contributor alone and not by any other party; and
93 |
94 | iv) states that source code for the Program is available from such
95 | Contributor, and informs licensees how to obtain it in a reasonable manner on
96 | or through a medium customarily used for software exchange.
97 |
98 | When the Program is made available in source code form:
99 |
100 | a) it must be made available under this Agreement; and
101 |
102 | b) a copy of this Agreement must be included with each copy of the Program.
103 |
104 | Contributors may not remove or alter any copyright notices contained within
105 | the Program.
106 |
107 | Each Contributor must identify itself as the originator of its Contribution,
108 | if any, in a manner that reasonably allows subsequent Recipients to identify
109 | the originator of the Contribution.
110 |
111 | 4. COMMERCIAL DISTRIBUTION
112 |
113 | Commercial distributors of software may accept certain responsibilities with
114 | respect to end users, business partners and the like. While this license is
115 | intended to facilitate the commercial use of the Program, the Contributor who
116 | includes the Program in a commercial product offering should do so in a
117 | manner which does not create potential liability for other Contributors.
118 | Therefore, if a Contributor includes the Program in a commercial product
119 | offering, such Contributor ("Commercial Contributor") hereby agrees to defend
120 | and indemnify every other Contributor ("Indemnified Contributor") against any
121 | losses, damages and costs (collectively "Losses") arising from claims,
122 | lawsuits and other legal actions brought by a third party against the
123 | Indemnified Contributor to the extent caused by the acts or omissions of such
124 | Commercial Contributor in connection with its distribution of the Program in
125 | a commercial product offering. The obligations in this section do not apply
126 | to any claims or Losses relating to any actual or alleged intellectual
127 | property infringement. In order to qualify, an Indemnified Contributor must:
128 | a) promptly notify the Commercial Contributor in writing of such claim, and
129 | b) allow the Commercial Contributor tocontrol, and cooperate with the
130 | Commercial Contributor in, the defense and any related settlement
131 | negotiations. The Indemnified Contributor may participate in any such claim
132 | at its own expense.
133 |
134 | For example, a Contributor might include the Program in a commercial product
135 | offering, Product X. That Contributor is then a Commercial Contributor. If
136 | that Commercial Contributor then makes performance claims, or offers
137 | warranties related to Product X, those performance claims and warranties are
138 | such Commercial Contributor's responsibility alone. Under this section, the
139 | Commercial Contributor would have to defend claims against the other
140 | Contributors related to those performance claims and warranties, and if a
141 | court requires any other Contributor to pay any damages as a result, the
142 | Commercial Contributor must pay those damages.
143 |
144 | 5. NO WARRANTY
145 |
146 | EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS PROVIDED ON
147 | AN "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER
148 | EXPRESS OR IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR
149 | CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A
150 | PARTICULAR PURPOSE. Each Recipient is solely responsible for determining the
151 | appropriateness of using and distributing the Program and assumes all risks
152 | associated with its exercise of rights under this Agreement , including but
153 | not limited to the risks and costs of program errors, compliance with
154 | applicable laws, damage to or loss of data, programs or equipment, and
155 | unavailability or interruption of operations.
156 |
157 | 6. DISCLAIMER OF LIABILITY
158 |
159 | EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT NOR ANY
160 | CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL,
161 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION
162 | LOST PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
163 | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
164 | ARISING IN ANY WAY OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE
165 | EXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY
166 | OF SUCH DAMAGES.
167 |
168 | 7. GENERAL
169 |
170 | If any provision of this Agreement is invalid or unenforceable under
171 | applicable law, it shall not affect the validity or enforceability of the
172 | remainder of the terms of this Agreement, and without further action by the
173 | parties hereto, such provision shall be reformed to the minimum extent
174 | necessary to make such provision valid and enforceable.
175 |
176 | If Recipient institutes patent litigation against any entity (including a
177 | cross-claim or counterclaim in a lawsuit) alleging that the Program itself
178 | (excluding combinations of the Program with other software or hardware)
179 | infringes such Recipient's patent(s), then such Recipient's rights granted
180 | under Section 2(b) shall terminate as of the date such litigation is filed.
181 |
182 | All Recipient's rights under this Agreement shall terminate if it fails to
183 | comply with any of the material terms or conditions of this Agreement and
184 | does not cure such failure in a reasonable period of time after becoming
185 | aware of such noncompliance. If all Recipient's rights under this Agreement
186 | terminate, Recipient agrees to cease use and distribution of the Program as
187 | soon as reasonably practicable. However, Recipient's obligations under this
188 | Agreement and any licenses granted by Recipient relating to the Program shall
189 | continue and survive.
190 |
191 | Everyone is permitted to copy and distribute copies of this Agreement, but in
192 | order to avoid inconsistency the Agreement is copyrighted and may only be
193 | modified in the following manner. The Agreement Steward reserves the right to
194 | publish new versions (including revisions) of this Agreement from time to
195 | time. No one other than the Agreement Steward has the right to modify this
196 | Agreement. The Eclipse Foundation is the initial Agreement Steward. The
197 | Eclipse Foundation may assign the responsibility to serve as the Agreement
198 | Steward to a suitable separate entity. Each new version of the Agreement will
199 | be given a distinguishing version number. The Program (including
200 | Contributions) may always be distributed subject to the version of the
201 | Agreement under which it was received. In addition, after a new version of
202 | the Agreement is published, Contributor may elect to distribute the Program
203 | (including its Contributions) under the new version. Except as expressly
204 | stated in Sections 2(a) and 2(b) above, Recipient receives no rights or
205 | licenses to the intellectual property of any Contributor under this
206 | Agreement, whether expressly, by implication, estoppel or otherwise. All
207 | rights in the Program not expressly granted under this Agreement are
208 | reserved.
209 |
210 | This Agreement is governed by the laws of the State of New York and the
211 | intellectual property laws of the United States of America. No party to this
212 | Agreement will bring a legal action under this Agreement more than one year
213 | after the cause of action arose. Each party waives its rights to a jury trial
214 | in any resulting litigation.
215 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
Table of Contents
3 |
42 |
43 |
44 | # About Missing Link Tutorials
45 |
46 | I find a lot of technology doesn't have documentation that I can
47 | relate to. The style of Missing Link Tutorials is to be clear,
48 | succint, easy, and to cover concepts and essential practical aspects
49 | of the topic. Without further ado…
50 |
51 | # Datomic Tutorial
52 |
53 | This tutorial was written for Datomic version: 0.9.5561, which was
54 | released on February 13, 2017.
55 |
56 | ## Data Shape
57 |
58 | You can think about the data that is stored in datomic as just a bunch
59 | of maps. Datomic doesn't have tables, like a relational database.
60 | Records (rows) are just maps chucked into a large pile. Datomic calls
61 | these maps Entities. The keys that these maps use are special, as we'll
62 | explain later.
63 |
64 | Below is an explicit visual sample of how you can conceive of a
65 | datomic database:
66 |
67 | [{:db/id 1
68 | :car/make "toyota"
69 | :car/model "tacoma"
70 | :year 2014}
71 |
72 | {:db/id 2
73 | :car/make "BMW"
74 | :car/model "325xi"
75 | :year 2001}
76 |
77 | {:db/id 3
78 | :user/name "ftravers"
79 | :user/age 54
80 | :cars [{:db/id 1}
81 | {:db/id 2}]}]
82 |
83 | So this datomic database has 3 entries (entities/maps/rows). A user,
84 | `ftravers`, who owns 2 cars.
85 |
86 | Every map (entity) will get a `:db/id`. This is what uniquely
87 | identifies that entity to datomic. Datomic `:db/id`'s are actually
88 | very large integers, so the data above is actually a bit fake, but I
89 | keep it simple to communicate the concept.
90 |
91 | As we can see in the above example, the `:cars` field (key) of the
92 | user `ftravers` points (refers/links) to the cars he owns using the
93 | `:db/id` field. The `:db/id` field allows one entity to refer to
94 | another entity, or as in this case, multiple other entities.
95 |
96 | ## Map Fields
97 |
98 | Entities (maps) in datomic, like idiomatic clojure, use keywords for
99 | its keys (fields).
100 |
101 | Looking at all three records (maps/entities), in our sample database
102 | we can see that the collective set of keys (fields) used are:
103 |
104 | :db/id
105 | :car/make
106 | :car/model
107 | :year
108 | :user/name
109 | :user/age
110 | :cars
111 |
112 | Datomic doesn't allow you to just go ahead and pick any old keyword as
113 | a field (or key) to entity maps, like you could in plain old clojure.
114 | Rather we have to specify ahead of time which keywords entities in
115 | datomic are allowed to use. Defining which keys (fields) can be used
116 | by maps (entities) is the process of creating a datomic *schema*.
117 |
118 | In the SQL world, creating a schema means defining table names, column
119 | names and column data types.
120 |
121 | In datomic, we do away with the concept of a table. You could say
122 | datomic ONLY specifies 'columns'. Additionally, these columns have no
123 | relationship to (or grouping with) any other column. Contrast this
124 | with an RDBMS which groups columns with the concept of a table.
125 |
126 | Here a column in SQL is equivalent to a field in datomic. When we
127 | specify a column in SQL we give it a name, and we indicate what it
128 | will hold, a string, integer, etc…
129 |
130 | In datomic we do the same thing, we define fields stating what their
131 | name is and what type of data they hold. In Datomic nomenclature
132 | fields are referred to as **attributes**.
133 |
134 | So this is a bit of a big deal. In RDBMS our columns are stuck
135 | together in a table. In datomic we define a bunch of fields that
136 | don't necessarily have anything to do with one another. We can
137 | randomly use any field we define for any record (map/entity) we want
138 | to store! Remember datomic is just a big pile of maps.
139 |
140 | Again, we can only use fields that have been predefined (the schema),
141 | but other than that, we can create maps with any combinations of those
142 | fields. We'll revist this idea later on.
143 |
144 | One more note, I've used both namespace qualified keyword fields like:
145 | `:user/name` and non-namespace qualified keyword fields like:
146 | `:cars`. I do this just to show that the keywords dont need to be
147 | namespace qualified, but it is best practice to do so. Why you may
148 | ask? One person suggested that they can be easier to refactor since
149 | they are more specific.
150 |
151 | Okay enough concepts, let's see how to define a field.
152 |
153 | ## Basic Schema
154 |
155 | Here we create a field (define an attribute) in datomic. We'll start
156 | with creating just one field. This field will *hold* an email value.
157 |
158 | (def schema
159 | [{:db/doc "A users email."
160 | :db/ident :user/email
161 | :db/valueType :db.type/string
162 | :db/cardinality :db.cardinality/one
163 | :db.install/_attribute :db.part/db}])
164 |
165 | `:db/ident` is the name of the field. So when we want to use this
166 | field to store data, this is the keyword that you would use.
167 |
168 | `:db/valueType` is the type of data that this field will hold. Here
169 | we use the `string` datatype to store an email string.
170 |
171 | `:db/cardinality` can be either `one` or `many`. Basically should
172 | this field hold a single item or a list of items.
173 |
174 | Those are the important fields to understand conceptually. `:db/doc`
175 | is a documentation string, `:db.install/_attribute` instructs datomic
176 | to treat this data as schema field creation data.
177 |
178 | Before we can start adding schema to a database, we need to create the
179 | database!
180 |
181 | (def db-url "datomic:free://127.0.0.1:4334/omn-dev")
182 | (d/create-database db-url)
183 |
184 | Now we can load this schema definition into the database by
185 | transacting it like so:
186 |
187 | (d/transact (d/connect db-url) schema)
188 |
189 | or written a bit more functionally
190 |
191 | (-> db-url
192 | d/connect
193 | (d/transact schema))
194 |
195 | ## Testdata
196 |
197 | Now that we've defined a field, let's make use of it by
198 | creating/inserting an entity that makes use of the newly created
199 | field. Remember data inside datomic is just a map, so let's just
200 | create that map:
201 |
202 | (def test-data
203 | [{:user/email "fred.jones@gmail.com"}])
204 |
205 | Let's transact this data into the DB:
206 |
207 | (-> db-url
208 | d/connect
209 | (d/transact test-data))
210 |
211 | ## Blow away and recreate DB
212 |
213 | When experimenting with datomic, I like to blow the database away, so
214 | I know I'm starting with a clean slate each time.
215 |
216 | (d/delete-database db-url)
217 | (d/create-database db-url)
218 | (d/transact (d/connect db-url) schema)
219 | (d/transact (d/connect db-url) test-data)
220 |
221 | Here I blow it away, recreate a blank DB, recreate the connection,
222 | transact the schema and testdata.
223 |
224 | Working code can be found under the
225 |
226 | GIT BRANCH: basic-schema-insert
227 |
228 | ## Better Testdata
229 |
230 | Okay a DB with only one record (row/entity/map) in it is pretty
231 | boring. Also a db with only one string column (field) is next to
232 | useless! Let's create a DB with two entities (records/maps) in it.
233 | Also let's create a second field, age, so we can query the database for
234 | people 21 and older!
235 |
236 | The schema:
237 |
238 | (def schema
239 | [{:db/doc "A users email."
240 | :db/ident :user/email
241 | :db/valueType :db.type/string
242 | :db/cardinality :db.cardinality/one
243 | :db.install/_attribute :db.part/db}
244 |
245 | {:db/doc "A users age."
246 | :db/ident :user/age
247 | :db/valueType :db.type/long
248 | :db/cardinality :db.cardinality/one
249 | :db.install/_attribute :db.part/db}])
250 |
251 | So we've added another field, age, that is type: `:db.type/long`. Now
252 | let's add some actual data:
253 |
254 | (def test-data
255 | [{:user/email "sally.jones@gmail.com"
256 | :user/age 34}
257 |
258 | {:user/email "franklin.rosevelt@gmail.com"
259 | :user/age 14}])
260 |
261 | GIT BRANCH: better-testdata
262 |
263 | **REMEMBER** to transact this schema and testdata into your cleaned up
264 | DB! Otherwise you'll get an error for trying to add the `:user/email`
265 | field twice.
266 |
267 | # Query the database
268 |
269 | ## Concept
270 |
271 | Now we have seen how to add data to datomic, the interesting part is
272 | the querying of the data. A query might be: "Give me the users who
273 | are over 21", if you are making an app to see who is legal to drink
274 | in the United States, for example.
275 |
276 | In regular RDBMS we compare rows of a table based on the values in a
277 | given column. The SQL query might look like:
278 |
279 | SELECT email FROM users WHERE age > 21
280 |
281 | In datomic we don't have tables, just a bunch of maps. So we don't
282 | have a `FROM` clause. In our case we want to inspect the `:user/age`
283 | field. This means, ANY entity (map), which has the `:user/age` field
284 | will be included in our query. This is a very important idea which we
285 | will revisit later to re-inforce.
286 |
287 | Let's reinforce this concept. When maps use the same field, then any
288 | query on that field will pull in those maps. It **doesn't** matter if
289 | they have **ANY** other fields in common.
290 |
291 | Contrast this with an RDBMS. First of all, all rows that belong to a
292 | given table will by definition have **ALL** the same exact fields.
293 | Second, if you had a column in another table that you'd like to apply
294 | the same query to, well there isn't a reasonable way to do that.
295 |
296 | Often you'll find rows in an RDBMS that have `null` values, because
297 | for whatever reason, for those rows, having a value in that column
298 | doesn't make sense. This sometimes becomes a problem with modeling
299 | data in an RDBMS. If you have objects that have some fields in common
300 | but not other fields, you often have to break this up into multiple
301 | tables, and life gets complex. Like you might have a user table, an
302 | administrator table, a customer table, a person table, etc… This
303 | rigidity of RDBMS, can often make modeling data very counter-intuitive.
304 |
305 | What do we gain by having this restriction? I would argue nothing.
306 | Datomic does away with this needless restriction of tables. Removing
307 | unneccessary restrictions IMO, is always a good thing.
308 |
309 | ## Breaking down a datomic query
310 |
311 | A query takes *datalog* for its first argument and a *database* to
312 | execute that datalog on, as the second argument. Let's just look at
313 | the datalog part first:
314 |
315 | [:find ?e
316 | :where [?e :user/email]]
317 |
318 | Datalog at a minimum has a `:find` part, and a `:where` part. First
319 | we'll examine the where part.
320 |
321 | ## Datalog :where
322 |
323 | The query (`:where`) part selects (narrows down) the records
324 | (entities). This is truly the querying part. So this corresponds to
325 | the `WHERE` clause in SQL.
326 |
327 | The `:find` part, is basically dictates what to show from the found
328 | records. So this naturally corresponds to the `SELECT` part of SQL.
329 | Let's focus on the `:where` part first.
330 |
331 | Where clauses take one or more vector clauses that are of the form:
332 |
333 | [entity field-name field-value]
334 |
335 | or in datomic speak:
336 |
337 | [entity attribute value]
338 |
339 | Working backwards in our example `[?e :user/email]`, it only specifies
340 | the entity and attribute (field) aspects. It doesn't specify a
341 | field-value. What this means, is that the field-value doesn't matter,
342 | we dont care what it is, it can be anything.
343 |
344 | Next we say we want maps (entities) that have the field (attribute):
345 | `:user/email`.
346 |
347 | Finally, the `?e`, means each entity (map) we find, store it in the
348 | variable `?e`, because we are going to use it in the `:find` part of
349 | our datalog.
350 |
351 | In summary this query reads like: "Get us all the entities in the DB
352 | that have the field: `:user/email`.
353 |
354 | ## Datalog :find
355 |
356 | Finally we have the `:find` part of the datalog. The correlates
357 | directly to the `SELECT` aspect of SQL, and it basically indicates
358 | what fields of the found records to return.
359 |
360 | We just say: `:find ?e`, which can be read as: "Just return the entity
361 | itself to me." Datomic, kind of makes a short cut at this point and
362 | actually returns the entity-id instead of the entity itself. We will
363 | show later how to convert an entity-id, which is just an integer, into
364 | a clojure map that better reflects what that entity actually consists
365 | of.
366 |
367 | ## Tangent, getting the database
368 |
369 | This line:
370 |
371 | (-> db-url d/connect d/db)
372 |
373 | Is a tricky way to get the database. It basically reads, starting
374 | with the the database URL, pass that to the function `d/connect` which
375 | returns a database connection, then pass that connection to the
376 | function `d/db` which returns the actual database. The *thread first*
377 | `->` saves us from having to type:
378 |
379 | (d/db (d/connect db-url))
380 |
381 | which we kind of have to read inside out to make sense.
382 |
383 | ## Back to our query, tangent over!
384 |
385 | Here is the full query,
386 |
387 | (defn query1 [db]
388 | (d/q '[:find ?e
389 | :where
390 | [?e :user/email]]
391 | db))
392 |
393 | and the result of running it:
394 |
395 | datomic-tutorial.core> (query1 (-> db-url d/connect d/db))
396 | #{[17592186045418] [17592186045419]}
397 |
398 | GIT BRANCH: simple-first-query
399 |
400 | Hmmm… Okay this is kind of far from what we put in. Below is the
401 | original data we trasacted into the DB:
402 |
403 | (def test-data
404 | [{:user/email "sally.jones@gmail.com"
405 | :user/age 34}
406 |
407 | {:user/email "franklin.rosevelt@gmail.com"
408 | :user/age 14}])
409 |
410 | The numbers returned by the query are the entity id's (`:db/id`) of
411 | the two records (maps) we transacted into the database.
412 |
413 | We are going to convert these entity ids into familiar clojure maps
414 | using two approaches. The first approach is a bit more instinctive,
415 | and the second approach is more enlightened (elegant).
416 |
417 | Instinctively, I'd look for an API to convert a `:db/id` into the
418 | actual entity that the id represents. So datomic has a function:
419 | `(entity db entity-id)`, which is documented like so:
420 |
421 | "Returns a dynamic map of the entity's attributes for the given id"
422 |
423 | Okay that looks promising. A bit more research on google reveals the
424 | following works:
425 |
426 | datomic-tutorial.core> (map #(seq (d/entity (d/db @db-conn) (first %))) (query1 (-> db-url d/connect d/db)))
427 | (([:user/email "sally.jones@gmail.com"] [:user/age 34])
428 | ([:user/email "franklin.rosevelt@gmail.com"] [:user/age 14]))
429 |
430 | Okay, that is the instinctual approach to extract the data we are
431 | looking for, but it isn't very elegant. Now let me introduce a more
432 | enlightened approach, **pull syntax**!
433 |
434 | ## Pull Syntax
435 |
436 | Instead of having the find clause look like:
437 |
438 | :find ?e
439 |
440 | we can convert that into pull syntax like so:
441 |
442 | :find (pull ?e [:user/email :user/age])
443 |
444 | and our output will now look like:
445 |
446 | datomic-tutorial.core> (query1 (-> db-url d/connect d/db))
447 | [[#:user{:email "sally.jones@gmail.com", :age 34}]
448 | [#:user{:email "franklin.rosevelt@gmail.com", :age 14}]]
449 |
450 | Okay, that looks a lot nicer!
451 |
452 | The way to understand pull syntax is that the first argument is the
453 | entity that you want to apply a pull pattern to. The second part is
454 | the **pull pattern**.
455 |
456 | Let's remind ourselves of the shape of the data in the DB:
457 |
458 | (def test-data
459 | [{:user/email "sally.jones@gmail.com"
460 | :user/age 34}
461 |
462 | {:user/email "franklin.rosevelt@gmail.com"
463 | :user/age 14}])
464 |
465 | The pull pattern we use is: `[:user/email :user/age]`. Here we
466 | declare the fields from the entity that we want returned to us. Once
467 | again the result of the pull syntax:
468 |
469 | datomic-tutorial.core> (query1 (-> db-url d/connect d/db))
470 | [[#:user{:email "sally.jones@gmail.com", :age 34}]
471 | [#:user{:email "franklin.rosevelt@gmail.com", :age 14}]]
472 |
473 | Much more user friendly!
474 |
475 | Our query is a little boring, let's make a query that is more
476 | interesting than just "get all entities who have the `:user/email`
477 | field!
478 |
479 | Let's modify this query to only return people who are 21 and over.
480 | Franklin, you aren't allowed to drink!
481 |
482 | To achieve this we use the following TWO where clauses:
483 |
484 | :where
485 | [?e :user/age ?age]
486 | [(>= ?age 21)]
487 |
488 | The first thing to note about this :where query is that it contains
489 | two clauses. Where clauses are implicitly **AND**-ed together. So both
490 | criteria need to be true for a given entity to be included in the
491 | results.
492 |
493 | Let's breakdown the first part of the query:
494 |
495 | [?e :user/age ?age]
496 |
497 | Remember where clauses are in the format: [entity field-name
498 | field-value] or in datomic nomeclature [entity attribute value].
499 |
500 | The `[?e :user/age ?age]` where clause reads like: "Find all entities
501 | that have the field (attribute) `:user/age`, and stick the entity into
502 | the variable `?e` and stick the value of the attribute `:user/age`,
503 | into the variable `?age`.
504 |
505 | So for each entity that meets this criteria will have the entity
506 | stored in the `?e` variable, and the age in the `?age` variable. Now
507 | we can make use of the age value in the second where clause:
508 |
509 | [(>= ?age 21)]
510 |
511 | Okay this is a special, and super cool variant on normal where
512 | clauses. We can run **ANY** function here that returns a boolean
513 | result. We know the function `>=` is a boolean value returning
514 | function, so its legit.
515 |
516 | Second, for each entity, the users age will be stored in the variable
517 | `?age`, so we can simply pass the value of that variable into the
518 | function to get our bool result! This just says, we want "entities who
519 | have an age >= 21". Great!
520 |
521 | So here is the full new query:
522 |
523 | (defn query1 [db]
524 | (d/q '[:find (pull ?e [:user/email :user/age])
525 | :where
526 | [?e :user/age ?age]
527 | [(>= ?age 21)]]
528 | db))
529 |
530 | And now we get the desired result, nicely formatted by our pull
531 | syntax:
532 |
533 | datomic-tutorial.core> (query1 (-> db-url d/connect d/db))
534 | [[#:user{:email "sally.jones@gmail.com", :age 34}]]
535 |
536 | GIT BRANCH: query-pull-filter
537 |
538 | # Parent Child Data
539 |
540 | Often we have data that owns other data. For example going back to
541 | our slightly modified first example:
542 |
543 | [{:db/id "taco"
544 | :car/make "toyota"
545 | :car/model "tacoma"
546 | :year 2014}
547 |
548 | {:db/id "325"
549 | :car/make "BMW"
550 | :car/model "325xi"
551 | :year 2001}
552 |
553 | {:db/id 3
554 | :user/name "ftravers"
555 | :user/age 54
556 | :cars [{:db/id "taco"}
557 | {:db/id "325"}]}]
558 |
559 | Because the `ftravers` entity map needs to refer to the `toyota` and
560 | `BMW` entity maps, we include a `:db/id` field. You can put in any
561 | string here for your convenience. After transacting into datomic,
562 | they'll get converted to large integers as we've seen before.
563 |
564 | This data says `ftravers`, owns two cars, a `toyota` and a `BMW`
565 | . So how do we model this? First we start with the schema. We'll
566 | need to define the fields: `:car/make`, `:car/model`, `:year`,
567 | `:user/name`, `:user/age`, and `:cars`.
568 |
569 | `:car/make`, `:car/model`, and `:user/name` are all of type `string`
570 | and cardinality one. For `:year` and `:user/age` we can use integers.
571 | `:cars` is the new one.
572 |
573 | The field `:cars` has a cardinality of `many`; also the type that it
574 | will hold is of a type that points to other entities. We'll need a
575 | type that is like a pointer, reference or link.
576 |
577 | Let's look only at the schema for `:cars`. You should be able to piece
578 | together the other fields from previous schema examples, or just look
579 | at the:
580 |
581 | GIT BRANCH: parent-child-modeling
582 |
583 | ## Many Refs Schema
584 |
585 | For the `:cars` field, the schema definition will look like:
586 |
587 | {:db/doc "List of cars a user owns"
588 | :db/ident :cars
589 | :db/valueType :db.type/ref
590 | :db/cardinality :db.cardinality/many
591 | :db.install/_attribute :db.part/db}
592 |
593 | Take special note of the values for `cardinality` and `valueType`.
594 |
595 | We have used a `valueType` of `:db.type/ref`. This is how we point to
596 | (refer/link) to other entities in the DB. This is the critical
597 | difference between a database and regular old clojure data structures
598 | that don't support references.
599 |
600 | The second thing to note is that the `cardinality` is set to `many`.
601 | That means this field will hold a list of values, not just a single
602 | value.
603 |
604 | ## Testdata
605 |
606 | Now let's make some testdata that can be transacted into the DB:
607 |
608 | (def test-data
609 | [{:db/id "taco"
610 | :car/make "toyota"
611 | :car/model "tacoma"
612 | :year 2014}
613 |
614 | {:db/id "325"
615 | :car/make "BMW"
616 | :car/model "325xi"
617 | :year 2001}
618 |
619 | {:db/id 3
620 | :user/name "ftravers"
621 | :user/age 54
622 | :cars [{:db/id "taco"}
623 | {:db/id "325"}]}])
624 |
625 | GIT BRANCH: parent-child-modeling
626 |
627 | Now that we have some parent/child data in the DB, let's see how to
628 | query and display it nicely.
629 |
630 | ## Querying Parent Child Data
631 |
632 | First we'll find the record we care about with a where clause that
633 | looks like:
634 |
635 | [?e :user/name "ftravers"]
636 |
637 | This reads: "find all the entities that have the `:user/name`
638 | attribute that has as its value `ftravers`".
639 |
640 | Now let's demonstrate how to format the results nicely with a slightly
641 | more advance pull pattern.
642 |
643 | ## Parent Child Pull Syntax
644 |
645 | We have already learned how to extract entity fields with a basic pull
646 | pattern:
647 |
648 | (pull ?e [:user/name :user/age])
649 |
650 | retrieves the `:user/name` and `:user/age` fields from the found,
651 | `?e`, entity/entities. Again the result of this look like:
652 |
653 | datomic-tutorial.core> (query1 (-> db-url d/connect d/db))
654 | [[#:user{:name "ftravers", :age 54}]]
655 |
656 | but what we really want is something that looks like:
657 |
658 | datomic-tutorial.core> (query1 (-> db-url d/connect d/db))
659 | [[{:user/name "ftravers",
660 | :user/age 54,
661 | :cars
662 | [#:car{:make "toyota", :model "tacoma"}
663 | #:car{:make "BMW", :model "325xi"}]}]]
664 |
665 | So we want more than just the simple fields that an entity has, but we
666 | want to follow any references it has to other entities and get values
667 | from those entities.
668 |
669 | To get the above we change the pull pattern to be:
670 |
671 | [:user/name
672 | :user/age
673 | {:cars [:car/make :car/model]}]
674 |
675 | So to get the children, and print out their fields, you start a new
676 | map, whose key is the parent field that points to the child. In our
677 | case `:cars`. Then you start a vector and list the properties of the
678 | child you wish to grab.
679 |
680 | This is an extremely elegant way to extract arbitrary levels of data
681 | from datomic. Just imagine the mess this would look like with SQL.
682 | Maybe here is a stab just for comparison.
683 |
684 | SELECT users.id users.name, users.age, cars.make, cars.model, cars.year
685 | FROM users cars
686 | WHERE users.id == cars.userid AND users.name == "ftravers"
687 |
688 | And this would produce a result like:
689 |
690 | [[1 ftravers 54 "toyota" "tacoma" 2013]
691 | [1 ftravers 54 "BMW" "325xi" 2001]]
692 |
693 | for comparison the equivalent datalog is:
694 |
695 | '[:find (pull ?e
696 | [:user/name
697 | :user/age
698 | {:cars [:car/make :car/model]}])
699 | :where [?e :user/name "ftravers"]]
700 |
701 | and its result, is nicely normalized:
702 |
703 | [[{:user/name "ftravers",
704 | :user/age 54,
705 | :cars
706 | [#:car{:make "toyota", :model "tacoma"}
707 | #:car{:make "BMW", :model "325xi"}]}]]
708 |
709 | # Deeper Understanding
710 |
711 | ## Fields cross SQL Table boundaries
712 |
713 | So pretend we have two entities like:
714 |
715 | {:user/name "ftravers"
716 | :year 1945}
717 |
718 | {:car/make "BMW 325xi"
719 | :year 2001}
720 |
721 | In datomic we can compare these two seemingly quite different objects
722 | with each other because they share a field: `:year`. So I could write
723 | a query that returns **ALL THINGS** that are older than 35 years old.
724 | As I write this, it is 2017, so a 35 year old thing would be born
725 | (made) in approximately the year: 1982. So the where clause would
726 | look like:
727 |
728 | [?e :year ?year]
729 | [(<= ?year 1982)]
730 |
731 | In RDBMS you normally are only ever comparing things that exist in the
732 | same table. So it'd be awkward to try a similar thing in an RDBMS.
733 | Primarily because they wouldn't have a combined index for fields in
734 | two separate tables. So your performance would die. In datomic each
735 | field has its own index, so a query like the above, would still be
736 | performant.
737 |
--------------------------------------------------------------------------------
/README.org:
--------------------------------------------------------------------------------
1 | * About Missing Link Tutorials
2 |
3 | I find a lot of technology doesn't have documentation that I can
4 | relate to. The style of Missing Link Tutorials is to be clear,
5 | succint, easy, and to cover concepts and essential practical aspects
6 | of the topic. Without further ado...
7 |
8 | * Datomic Tutorial
9 |
10 | This tutorial was written for Datomic version: 0.9.5561, which was
11 | released on February 13, 2017.
12 |
13 | ** Data Shape
14 |
15 | You can think about the data that is stored in datomic as just a bunch
16 | of maps. Datomic doesn't have tables, like a relational database.
17 | Records (rows) are just maps chucked into a large pile. Datomic calls
18 | these maps Entities. The keys that these maps use are special, as we'll
19 | explain later.
20 |
21 | Below is an explicit visual sample of how you can conceive of a
22 | datomic database:
23 |
24 | #+BEGIN_SRC clojure
25 | [{:db/id 1
26 | :car/make "toyota"
27 | :car/model "tacoma"
28 | :year 2014}
29 |
30 | {:db/id 2
31 | :car/make "BMW"
32 | :car/model "325xi"
33 | :year 2001}
34 |
35 | {:db/id 3
36 | :user/name "ftravers"
37 | :user/age 54
38 | :cars [{:db/id 1}
39 | {:db/id 2}]}]
40 | #+END_SRC
41 |
42 | So this datomic database has 3 entries (entities/maps/rows). A user,
43 | ~ftravers~, who owns 2 cars.
44 |
45 | Every map (entity) will get a ~:db/id~. This is what uniquely
46 | identifies that entity to datomic. Datomic ~:db/id~'s are actually
47 | very large integers, so the data above is actually a bit fake, but I
48 | keep it simple to communicate the concept.
49 |
50 | As we can see in the above example, the ~:cars~ field (key) of the
51 | user ~ftravers~ points (refers/links) to the cars he owns using the
52 | ~:db/id~ field. The ~:db/id~ field allows one entity to refer to
53 | another entity, or as in this case, multiple other entities.
54 |
55 | ** Map Fields
56 |
57 | Entities (maps) in datomic, like idiomatic clojure, use keywords for
58 | its keys (fields).
59 |
60 | Looking at all three records (maps/entities), in our sample database
61 | we can see that the collective set of keys (fields) used are:
62 |
63 | #+BEGIN_SRC clojure
64 | :db/id
65 | :car/make
66 | :car/model
67 | :year
68 | :user/name
69 | :user/age
70 | :cars
71 | #+END_SRC
72 |
73 | Datomic doesn't allow you to just go ahead and pick any old keyword as
74 | a field (or key) to entity maps, like you could in plain old clojure.
75 | Rather we have to specify ahead of time which keywords entities in
76 | datomic are allowed to use. Defining which keys (fields) can be used
77 | by maps (entities) is the process of creating a datomic /schema/.
78 |
79 | In the SQL world, creating a schema means defining table names, column
80 | names and column data types.
81 |
82 | In datomic, we do away with the concept of a table. You could say
83 | datomic ONLY specifies 'columns'. Additionally, these columns have no
84 | relationship to (or grouping with) any other column. Contrast this
85 | with an RDBMS which groups columns with the concept of a table.
86 |
87 | Here a column in SQL is equivalent to a field in datomic. When we
88 | specify a column in SQL we give it a name, and we indicate what it
89 | will hold, a string, integer, etc...
90 |
91 | In datomic we do the same thing, we define fields stating what their
92 | name is and what type of data they hold. In Datomic nomenclature
93 | fields are referred to as *attributes*.
94 |
95 | So this is a bit of a big deal. In RDBMS our columns are stuck
96 | together in a table. In datomic we define a bunch of fields that
97 | don't necessarily have anything to do with one another. We can
98 | randomly use any field we define for any record (map/entity) we want
99 | to store! Remember datomic is just a big pile of maps.
100 |
101 | Again, we can only use fields that have been predefined (the schema),
102 | but other than that, we can create maps with any combinations of those
103 | fields. We'll revist this idea later on.
104 |
105 | One more note, I've used both namespace qualified keyword fields like:
106 | ~:user/name~ and non-namespace qualified keyword fields like:
107 | ~:cars~. I do this just to show that the keywords dont need to be
108 | namespace qualified, but it is best practice to do so. Why you may
109 | ask? One person suggested that they can be easier to refactor since
110 | they are more specific.
111 |
112 | Okay enough concepts, let's see how to define a field.
113 |
114 | ** Basic Schema
115 |
116 | Here we create a field (define an attribute) in datomic. We'll start
117 | with creating just one field. This field will /hold/ an email value.
118 |
119 | #+BEGIN_SRC clojure
120 | (def schema
121 | [{:db/doc "A users email."
122 | :db/ident :user/email
123 | :db/valueType :db.type/string
124 | :db/cardinality :db.cardinality/one
125 | :db.install/_attribute :db.part/db}])
126 | #+END_SRC
127 |
128 | ~:db/ident~ is the name of the field. So when we want to use this
129 | field to store data, this is the keyword that you would use.
130 |
131 | ~:db/valueType~ is the type of data that this field will hold. Here
132 | we use the ~string~ datatype to store an email string.
133 |
134 | ~:db/cardinality~ can be either ~one~ or ~many~. Basically should
135 | this field hold a single item or a list of items.
136 |
137 | Those are the important fields to understand conceptually. ~:db/doc~
138 | is a documentation string, ~:db.install/_attribute~ instructs datomic
139 | to treat this data as schema field creation data.
140 |
141 | Before we can start adding schema to a database, we need to create the
142 | database!
143 |
144 | #+BEGIN_SRC clojure
145 | (def db-url "datomic:free://127.0.0.1:4334/omn-dev")
146 | (d/create-database db-url)
147 | #+END_SRC
148 |
149 | Now we can load this schema definition into the database by
150 | transacting it like so:
151 |
152 | #+BEGIN_SRC clojure
153 | (d/transact (d/connect db-url) schema)
154 | #+END_SRC
155 |
156 | or written a bit more functionally
157 |
158 | #+BEGIN_SRC clojure
159 | (-> db-url
160 | d/connect
161 | (d/transact schema))
162 | #+END_SRC
163 |
164 | ** Testdata
165 |
166 | Now that we've defined a field, let's make use of it by
167 | creating/inserting an entity that makes use of the newly created
168 | field. Remember data inside datomic is just a map, so let's just
169 | create that map:
170 |
171 | #+BEGIN_SRC clojure
172 | (def test-data
173 | [{:user/email "fred.jones@gmail.com"}])
174 | #+END_SRC
175 |
176 | Let's transact this data into the DB:
177 |
178 | #+BEGIN_SRC clojure
179 | (-> db-url
180 | d/connect
181 | (d/transact test-data))
182 | #+END_SRC
183 |
184 | ** Blow away and recreate DB
185 |
186 | When experimenting with datomic, I like to blow the database away, so
187 | I know I'm starting with a clean slate each time.
188 |
189 | #+BEGIN_SRC clojure
190 | (d/delete-database db-url)
191 | (d/create-database db-url)
192 | (d/transact (d/connect db-url) schema)
193 | (d/transact (d/connect db-url) test-data)
194 | #+END_SRC
195 |
196 | Here I blow it away, recreate a blank DB, recreate the connection,
197 | transact the schema and testdata.
198 |
199 | Working code can be found under the
200 |
201 | GIT BRANCH: basic-schema-insert
202 |
203 | ** Better Testdata
204 |
205 | Okay a DB with only one record (row/entity/map) in it is pretty
206 | boring. Also a db with only one string column (field) is next to
207 | useless! Let's create a DB with two entities (records/maps) in it.
208 | Also let's create a second field, age, so we can query the database for
209 | people 21 and older!
210 |
211 | The schema:
212 |
213 | #+BEGIN_SRC clojure
214 | (def schema
215 | [{:db/doc "A users email."
216 | :db/ident :user/email
217 | :db/valueType :db.type/string
218 | :db/cardinality :db.cardinality/one
219 | :db.install/_attribute :db.part/db}
220 |
221 | {:db/doc "A users age."
222 | :db/ident :user/age
223 | :db/valueType :db.type/long
224 | :db/cardinality :db.cardinality/one
225 | :db.install/_attribute :db.part/db}])
226 | #+END_SRC
227 |
228 | So we've added another field, age, that is type: ~:db.type/long~. Now
229 | let's add some actual data:
230 |
231 | #+BEGIN_SRC clojure
232 | (def test-data
233 | [{:user/email "sally.jones@gmail.com"
234 | :user/age 34}
235 |
236 | {:user/email "franklin.rosevelt@gmail.com"
237 | :user/age 14}])
238 | #+END_SRC
239 |
240 | GIT BRANCH: better-testdata
241 |
242 | *REMEMBER* to transact this schema and testdata into your cleaned up
243 | DB! Otherwise you'll get an error for trying to add the ~:user/email~
244 | field twice.
245 |
246 | * Query the database
247 |
248 | ** Concept
249 |
250 | Now we have seen how to add data to datomic, the interesting part is
251 | the querying of the data. A query might be: "Give me the users who
252 | are over 21", if you are making an app to see who is legal to drink
253 | in the United States, for example.
254 |
255 | In regular RDBMS we compare rows of a table based on the values in a
256 | given column. The SQL query might look like:
257 |
258 | #+BEGIN_SRC SQL
259 | SELECT email FROM users WHERE age > 21
260 | #+END_SRC
261 |
262 | In datomic we don't have tables, just a bunch of maps. So we don't
263 | have a ~FROM~ clause. In our case we want to inspect the ~:user/age~
264 | field. This means, ANY entity (map), which has the ~:user/age~ field
265 | will be included in our query. This is a very important idea which we
266 | will revisit later to re-inforce.
267 |
268 | Let's reinforce this concept. When maps use the same field, then any
269 | query on that field will pull in those maps. It *doesn't* matter if
270 | they have *ANY* other fields in common.
271 |
272 | Contrast this with an RDBMS. First of all, all rows that belong to a
273 | given table will by definition have *ALL* the same exact fields.
274 | Second, if you had a column in another table that you'd like to apply
275 | the same query to, well there isn't a reasonable way to do that.
276 |
277 | Often you'll find rows in an RDBMS that have ~null~ values, because
278 | for whatever reason, for those rows, having a value in that column
279 | doesn't make sense. This sometimes becomes a problem with modeling
280 | data in an RDBMS. If you have objects that have some fields in common
281 | but not other fields, you often have to break this up into multiple
282 | tables, and life gets complex. Like you might have a user table, an
283 | administrator table, a customer table, a person table, etc... This
284 | rigidity of RDBMS, can often make modeling data very counter-intuitive.
285 |
286 | What do we gain by having this restriction? I would argue nothing.
287 | Datomic does away with this needless restriction of tables. Removing
288 | unneccessary restrictions IMO, is always a good thing.
289 |
290 | ** Breaking down a datomic query
291 |
292 | A query takes /datalog/ for its first argument and a /database/ to
293 | execute that datalog on, as the second argument. Let's just look at
294 | the datalog part first:
295 |
296 | #+BEGIN_SRC clojure
297 | [:find ?e
298 | :where [?e :user/email]]
299 | #+END_SRC
300 |
301 | Datalog at a minimum has a ~:find~ part, and a ~:where~ part. First
302 | we'll examine the where part.
303 |
304 | ** Datalog :where
305 |
306 | The query (~:where~) part selects (narrows down) the records
307 | (entities). This is truly the querying part. So this corresponds to
308 | the ~WHERE~ clause in SQL.
309 |
310 | The ~:find~ part, is basically dictates what to show from the found
311 | records. So this naturally corresponds to the ~SELECT~ part of SQL.
312 | Let's focus on the ~:where~ part first.
313 |
314 | Where clauses take one or more vector clauses that are of the form:
315 |
316 | #+BEGIN_SRC clojure
317 | [entity field-name field-value]
318 | #+END_SRC
319 |
320 | or in datomic speak:
321 |
322 | #+BEGIN_SRC clojure
323 | [entity attribute value]
324 | #+END_SRC
325 |
326 | Working backwards in our example ~[?e :user/email]~, it only specifies
327 | the entity and attribute (field) aspects. It doesn't specify a
328 | field-value. What this means, is that the field-value doesn't matter,
329 | we dont care what it is, it can be anything.
330 |
331 | Next we say we want maps (entities) that have the field (attribute):
332 | ~:user/email~.
333 |
334 | Finally, the ~?e~, means each entity (map) we find, store it in the
335 | variable ~?e~, because we are going to use it in the ~:find~ part of
336 | our datalog.
337 |
338 | In summary this query reads like: "Get us all the entities in the DB
339 | that have the field: ~:user/email~.
340 |
341 | ** Datalog :find
342 |
343 | Finally we have the ~:find~ part of the datalog. The correlates
344 | directly to the ~SELECT~ aspect of SQL, and it basically indicates
345 | what fields of the found records to return.
346 |
347 | We just say: ~:find ?e~, which can be read as: "Just return the entity
348 | itself to me." Datomic, kind of makes a short cut at this point and
349 | actually returns the entity-id instead of the entity itself. We will
350 | show later how to convert an entity-id, which is just an integer, into
351 | a clojure map that better reflects what that entity actually consists
352 | of.
353 |
354 | ** Tangent, getting the database
355 |
356 | This line:
357 |
358 | #+BEGIN_SRC clojure
359 | (-> db-url d/connect d/db)
360 | #+END_SRC
361 |
362 | Is a tricky way to get the database. It basically reads, starting
363 | with the the database URL, pass that to the function ~d/connect~ which
364 | returns a database connection, then pass that connection to the
365 | function ~d/db~ which returns the actual database. The /thread first/
366 | ~->~ saves us from having to type:
367 |
368 | #+BEGIN_SRC clojure
369 | (d/db (d/connect db-url))
370 | #+END_SRC
371 |
372 | which we kind of have to read inside out to make sense.
373 |
374 | ** Back to our query, tangent over!
375 |
376 | Here is the full query,
377 |
378 | #+BEGIN_SRC clojure
379 | (defn query1 [db]
380 | (d/q '[:find ?e
381 | :where
382 | [?e :user/email]]
383 | db))
384 | #+END_SRC
385 |
386 | and the result of running it:
387 |
388 | #+BEGIN_SRC clojure
389 | datomic-tutorial.core> (query1 (-> db-url d/connect d/db))
390 | #{[17592186045418] [17592186045419]}
391 | #+END_SRC
392 |
393 | GIT BRANCH: simple-first-query
394 |
395 | Hmmm... Okay this is kind of far from what we put in. Below is the
396 | original data we trasacted into the DB:
397 |
398 | #+BEGIN_SRC clojure
399 | (def test-data
400 | [{:user/email "sally.jones@gmail.com"
401 | :user/age 34}
402 |
403 | {:user/email "franklin.rosevelt@gmail.com"
404 | :user/age 14}])
405 | #+END_SRC
406 |
407 | The numbers returned by the query are the entity id's (~:db/id~) of
408 | the two records (maps) we transacted into the database.
409 |
410 | We are going to convert these entity ids into familiar clojure maps
411 | using two approaches. The first approach is a bit more instinctive,
412 | and the second approach is more enlightened (elegant).
413 |
414 | Instinctively, I'd look for an API to convert a ~:db/id~ into the
415 | actual entity that the id represents. So datomic has a function:
416 | ~(entity db entity-id)~, which is documented like so:
417 |
418 | "Returns a dynamic map of the entity's attributes for the given id"
419 |
420 | Okay that looks promising. A bit more research on google reveals the
421 | following works:
422 |
423 | #+BEGIN_SRC clojure
424 | datomic-tutorial.core> (map #(seq (d/entity (d/db @db-conn) (first %))) (query1 (-> db-url d/connect d/db)))
425 | (([:user/email "sally.jones@gmail.com"] [:user/age 34])
426 | ([:user/email "franklin.rosevelt@gmail.com"] [:user/age 14]))
427 | #+END_SRC
428 |
429 | Okay, that is the instinctual approach to extract the data we are
430 | looking for, but it isn't very elegant. Now let me introduce a more
431 | enlightened approach, *pull syntax*!
432 |
433 | ** Pull Syntax
434 |
435 | Instead of having the find clause look like:
436 |
437 | #+BEGIN_SRC clojure
438 | :find ?e
439 | #+END_SRC
440 |
441 | we can convert that into pull syntax like so:
442 |
443 | #+BEGIN_SRC clojure
444 | :find (pull ?e [:user/email :user/age])
445 | #+END_SRC
446 |
447 | and our output will now look like:
448 |
449 | #+BEGIN_SRC clojure
450 | datomic-tutorial.core> (query1 (-> db-url d/connect d/db))
451 | [[#:user{:email "sally.jones@gmail.com", :age 34}]
452 | [#:user{:email "franklin.rosevelt@gmail.com", :age 14}]]
453 | #+END_SRC
454 |
455 | Okay, that looks a lot nicer!
456 |
457 | The way to understand pull syntax is that the first argument is the
458 | entity that you want to apply a pull pattern to. The second part is
459 | the *pull pattern*.
460 |
461 | Let's remind ourselves of the shape of the data in the DB:
462 |
463 | #+BEGIN_SRC clojure
464 | (def test-data
465 | [{:user/email "sally.jones@gmail.com"
466 | :user/age 34}
467 |
468 | {:user/email "franklin.rosevelt@gmail.com"
469 | :user/age 14}])
470 | #+END_SRC
471 |
472 | The pull pattern we use is: ~[:user/email :user/age]~. Here we
473 | declare the fields from the entity that we want returned to us. Once
474 | again the result of the pull syntax:
475 |
476 | #+BEGIN_SRC clojure
477 | datomic-tutorial.core> (query1 (-> db-url d/connect d/db))
478 | [[#:user{:email "sally.jones@gmail.com", :age 34}]
479 | [#:user{:email "franklin.rosevelt@gmail.com", :age 14}]]
480 | #+END_SRC
481 |
482 | Much more user friendly!
483 |
484 | Our query is a little boring, let's make a query that is more
485 | interesting than just "get all entities who have the ~:user/email~
486 | field!
487 |
488 | Let's modify this query to only return people who are 21 and over.
489 | Franklin, you aren't allowed to drink!
490 |
491 | To achieve this we use the following TWO where clauses:
492 |
493 | #+BEGIN_SRC clojure
494 | :where
495 | [?e :user/age ?age]
496 | [(>= ?age 21)]
497 | #+END_SRC
498 |
499 | The first thing to note about this :where query is that it contains
500 | two clauses. Where clauses are implicitly *AND*-ed together. So both
501 | criteria need to be true for a given entity to be included in the
502 | results.
503 |
504 | Let's breakdown the first part of the query:
505 |
506 | #+BEGIN_SRC clojure
507 | [?e :user/age ?age]
508 | #+END_SRC
509 |
510 | Remember where clauses are in the format: [entity field-name
511 | field-value] or in datomic nomeclature [entity attribute value].
512 |
513 | The ~[?e :user/age ?age]~ where clause reads like: "Find all entities
514 | that have the field (attribute) ~:user/age~, and stick the entity into
515 | the variable ~?e~ and stick the value of the attribute ~:user/age~,
516 | into the variable ~?age~.
517 |
518 | So for each entity that meets this criteria will have the entity
519 | stored in the ~?e~ variable, and the age in the ~?age~ variable. Now
520 | we can make use of the age value in the second where clause:
521 |
522 | #+BEGIN_SRC clojure
523 | [(>= ?age 21)]
524 | #+END_SRC
525 |
526 | Okay this is a special, and super cool variant on normal where
527 | clauses. We can run *ANY* function here that returns a boolean
528 | result. We know the function ~>=~ is a boolean value returning
529 | function, so its legit.
530 |
531 | Second, for each entity, the users age will be stored in the variable
532 | ~?age~, so we can simply pass the value of that variable into the
533 | function to get our bool result! This just says, we want "entities who
534 | have an age >= 21". Great!
535 |
536 | So here is the full new query:
537 |
538 | #+BEGIN_SRC clojure
539 | (defn query1 [db]
540 | (d/q '[:find (pull ?e [:user/email :user/age])
541 | :where
542 | [?e :user/age ?age]
543 | [(>= ?age 21)]]
544 | db))
545 | #+END_SRC
546 |
547 | And now we get the desired result, nicely formatted by our pull
548 | syntax:
549 |
550 | #+BEGIN_SRC clojure
551 | datomic-tutorial.core> (query1 (-> db-url d/connect d/db))
552 | [[#:user{:email "sally.jones@gmail.com", :age 34}]]
553 | #+END_SRC
554 |
555 | GIT BRANCH: query-pull-filter
556 |
557 | * Parent Child Data
558 |
559 | Often we have data that owns other data. For example going back to
560 | our slightly modified first example:
561 |
562 | #+BEGIN_SRC clojure
563 | [{:db/id "taco"
564 | :car/make "toyota"
565 | :car/model "tacoma"
566 | :year 2014}
567 |
568 | {:db/id "325"
569 | :car/make "BMW"
570 | :car/model "325xi"
571 | :year 2001}
572 |
573 | {:db/id 3
574 | :user/name "ftravers"
575 | :user/age 54
576 | :cars [{:db/id "taco"}
577 | {:db/id "325"}]}]
578 | #+END_SRC
579 |
580 | Because the ~ftravers~ entity map needs to refer to the ~toyota~ and
581 | ~BMW~ entity maps, we include a ~:db/id~ field. You can put in any
582 | string here for your convenience. After transacting into datomic,
583 | they'll get converted to large integers as we've seen before.
584 |
585 | This data says ~ftravers~, owns two cars, a ~toyota~ and a ~BMW~
586 | . So how do we model this? First we start with the schema. We'll
587 | need to define the fields: ~:car/make~, ~:car/model~, ~:year~,
588 | ~:user/name~, ~:user/age~, and ~:cars~.
589 |
590 | ~:car/make~, ~:car/model~, and ~:user/name~ are all of type ~string~
591 | and cardinality one. For ~:year~ and ~:user/age~ we can use integers.
592 | ~:cars~ is the new one.
593 |
594 | The field ~:cars~ has a cardinality of ~many~; also the type that it
595 | will hold is of a type that points to other entities. We'll need a
596 | type that is like a pointer, reference or link.
597 |
598 | Let's look only at the schema for ~:cars~. You should be able to piece
599 | together the other fields from previous schema examples, or just look
600 | at the:
601 |
602 | GIT BRANCH: parent-child-modeling
603 |
604 | ** Many Refs Schema
605 |
606 | For the ~:cars~ field, the schema definition will look like:
607 |
608 | #+BEGIN_SRC clojure
609 | {:db/doc "List of cars a user owns"
610 | :db/ident :cars
611 | :db/valueType :db.type/ref
612 | :db/cardinality :db.cardinality/many
613 | :db.install/_attribute :db.part/db}
614 | #+END_SRC
615 |
616 | Take special note of the values for ~cardinality~ and ~valueType~.
617 |
618 | We have used a ~valueType~ of ~:db.type/ref~. This is how we point to
619 | (refer/link) to other entities in the DB. This is the critical
620 | difference between a database and regular old clojure data structures
621 | that don't support references.
622 |
623 | The second thing to note is that the ~cardinality~ is set to ~many~.
624 | That means this field will hold a list of values, not just a single
625 | value.
626 |
627 | ** Testdata
628 |
629 | Now let's make some testdata that can be transacted into the DB:
630 |
631 | #+BEGIN_SRC clojure
632 | (def test-data
633 | [{:db/id "taco"
634 | :car/make "toyota"
635 | :car/model "tacoma"
636 | :year 2014}
637 |
638 | {:db/id "325"
639 | :car/make "BMW"
640 | :car/model "325xi"
641 | :year 2001}
642 |
643 | {:db/id 3
644 | :user/name "ftravers"
645 | :user/age 54
646 | :cars [{:db/id "taco"}
647 | {:db/id "325"}]}])
648 | #+END_SRC
649 |
650 | GIT BRANCH: parent-child-modeling
651 |
652 | Now that we have some parent/child data in the DB, let's see how to
653 | query and display it nicely.
654 |
655 | ** Querying Parent Child Data
656 |
657 | First we'll find the record we care about with a where clause that
658 | looks like:
659 |
660 | #+BEGIN_SRC clojure
661 | [?e :user/name "ftravers"]
662 | #+END_SRC
663 |
664 | This reads: "find all the entities that have the ~:user/name~
665 | attribute that has as its value ~ftravers~".
666 |
667 | Now let's demonstrate how to format the results nicely with a slightly
668 | more advance pull pattern.
669 |
670 | ** Parent Child Pull Syntax
671 |
672 | We have already learned how to extract entity fields with a basic pull
673 | pattern:
674 |
675 | #+BEGIN_SRC clojure
676 | (pull ?e [:user/name :user/age])
677 | #+END_SRC
678 |
679 | retrieves the ~:user/name~ and ~:user/age~ fields from the found,
680 | ~?e~, entity/entities. Again the result of this look like:
681 |
682 | #+BEGIN_SRC clojure
683 | datomic-tutorial.core> (query1 (-> db-url d/connect d/db))
684 | [[#:user{:name "ftravers", :age 54}]]
685 | #+END_SRC
686 |
687 | but what we really want is something that looks like:
688 |
689 | #+BEGIN_SRC clojure
690 | datomic-tutorial.core> (query1 (-> db-url d/connect d/db))
691 | [[{:user/name "ftravers",
692 | :user/age 54,
693 | :cars
694 | [#:car{:make "toyota", :model "tacoma"}
695 | #:car{:make "BMW", :model "325xi"}]}]]
696 | #+END_SRC
697 |
698 | So we want more than just the simple fields that an entity has, but we
699 | want to follow any references it has to other entities and get values
700 | from those entities.
701 |
702 | To get the above we change the pull pattern to be:
703 |
704 | #+BEGIN_SRC clojure
705 | [:user/name
706 | :user/age
707 | {:cars [:car/make :car/model]}]
708 | #+END_SRC
709 |
710 | So to get the children, and print out their fields, you start a new
711 | map, whose key is the parent field that points to the child. In our
712 | case ~:cars~. Then you start a vector and list the properties of the
713 | child you wish to grab.
714 |
715 | This is an extremely elegant way to extract arbitrary levels of data
716 | from datomic. Just imagine the mess this would look like with SQL.
717 | Maybe here is a stab just for comparison.
718 |
719 | #+BEGIN_SRC sql
720 | SELECT users.id users.name, users.age, cars.make, cars.model, cars.year
721 | FROM users cars
722 | WHERE users.id == cars.userid AND users.name == "ftravers"
723 | #+END_SRC
724 |
725 | And this would produce a result like:
726 |
727 | #+BEGIN_SRC clojure
728 | [[1 ftravers 54 "toyota" "tacoma" 2013]
729 | [1 ftravers 54 "BMW" "325xi" 2001]]
730 | #+END_SRC
731 |
732 | for comparison the equivalent datalog is:
733 |
734 | #+BEGIN_SRC clojure
735 | '[:find (pull ?e
736 | [:user/name
737 | :user/age
738 | {:cars [:car/make :car/model]}])
739 | :where [?e :user/name "ftravers"]]
740 | #+END_SRC
741 |
742 | and its result, is nicely normalized:
743 |
744 | #+BEGIN_SRC clojure
745 | [[{:user/name "ftravers",
746 | :user/age 54,
747 | :cars
748 | [#:car{:make "toyota", :model "tacoma"}
749 | #:car{:make "BMW", :model "325xi"}]}]]
750 | #+END_SRC
751 |
752 | * Deeper Understanding
753 |
754 | ** Fields cross SQL Table boundaries
755 |
756 | So pretend we have two entities like:
757 |
758 | #+BEGIN_SRC clojure
759 | {:user/name "ftravers"
760 | :year 1945}
761 |
762 | {:car/make "BMW 325xi"
763 | :year 2001}
764 | #+END_SRC
765 |
766 | In datomic we can compare these two seemingly quite different objects
767 | with each other because they share a field: ~:year~. So I could write
768 | a query that returns *ALL THINGS* that are older than 35 years old.
769 | As I write this, it is 2017, so a 35 year old thing would be born
770 | (made) in approximately the year: 1982. So the where clause would
771 | look like:
772 |
773 | #+BEGIN_SRC clojure
774 | [?e :year ?year]
775 | [(<= ?year 1982)]
776 | #+END_SRC
777 |
778 | In RDBMS you normally are only ever comparing things that exist in the
779 | same table. So it'd be awkward to try a similar thing in an RDBMS.
780 | Primarily because they wouldn't have a combined index for fields in
781 | two separate tables. So your performance would die. In datomic each
782 | field has its own index, so a query like the above, would still be
783 | performant.
784 |
785 |
--------------------------------------------------------------------------------
/doc/table.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ftravers/datomic-tutorial/b3f2ca2b5ee94becae2c2114ed4421d2d6c142af/doc/table.gif
--------------------------------------------------------------------------------
/project.clj:
--------------------------------------------------------------------------------
1 | (defproject datomic-tutorial "0.1.0-SNAPSHOT"
2 | :description "Cubic Zirconia Tutorials - Small, detailed, clear and cheap. The Datomic Tutorial"
3 | :url "http://example.com/FIXME"
4 | :license {:name "Eclipse Public License"
5 | :url "http://www.eclipse.org/legal/epl-v10.html"}
6 | :dependencies [[org.clojure/clojure "1.9.0-alpha14"]
7 | [com.datomic/datomic-free "0.9.5561" :exclusions [joda-time org.slf4j/slf4j-nop]]]
8 | :main ^:skip-aot datomic-tutorial.core
9 | :target-path "target/%s"
10 | :profiles {:uberjar {:aot :all}})
11 |
--------------------------------------------------------------------------------
/src/datomic_tutorial/core.clj:
--------------------------------------------------------------------------------
1 | (ns datomic-tutorial.core
2 | (:require [datomic.api :as d]))
3 |
4 | (def db-url "datomic:free://127.0.0.1:4334/datomic-tutorial")
5 |
6 | (def schema [{:db/doc "The username."
7 | :db/id #db/id[:db.part/db]
8 | :db/ident :user/name
9 | :db/valueType :db.type/string
10 | :db/cardinality :db.cardinality/one
11 | :db.install/_attribute :db.part/db}
12 |
13 | {:db/doc "List of cars a user owns"
14 | :db/id #db/id[:db.part/db]
15 | :db/ident :cars
16 | :db/valueType :db.type/ref
17 | :db/cardinality :db.cardinality/many
18 | :db.install/_attribute :db.part/db}
19 |
20 | {:db/doc "Car make"
21 | :db/id #db/id[:db.part/db]
22 | :db/ident :car/make
23 | :db/valueType :db.type/string
24 | :db/cardinality :db.cardinality/one
25 | :db.install/_attribute :db.part/db}
26 |
27 | {:db/doc "Car model"
28 | :db/id #db/id[:db.part/db]
29 | :db/ident :car/model
30 | :db/valueType :db.type/string
31 | :db/cardinality :db.cardinality/one
32 | :db.install/_attribute :db.part/db}
33 |
34 | {:db/doc "Year"
35 | :db/id #db/id[:db.part/db]
36 | :db/ident :year
37 | :db/valueType :db.type/long
38 | :db/cardinality :db.cardinality/one
39 | :db.install/_attribute :db.part/db}
40 |
41 | {:db/doc "Person age"
42 | :db/id #db/id[:db.part/db]
43 | :db/ident :user/age
44 | :db/valueType :db.type/long
45 | :db/cardinality :db.cardinality/one
46 | :db.install/_attribute :db.part/db}])
47 |
48 | (def test-data
49 | [{:db/id #db/id[:db.part/user -1]
50 | :car/make "toyota"
51 | :car/model "tacoma"
52 | :year 2014}
53 |
54 | {:db/id #db/id[:db.part/user -2]
55 | :car/make "BMW"
56 | :car/model "325xi"
57 | :year 2001}
58 |
59 | {:db/id #db/id[:db.part/user -3]
60 | :user/name "ftravers"
61 | :user/age 54
62 | :cars [{:db/id #db/id[:db.part/user -1]}
63 | {:db/id #db/id[:db.part/user -2]}]}])
64 |
65 | (defn reload-dbs []
66 | (d/delete-database db-url)
67 | (d/create-database db-url)
68 | (d/transact (d/connect db-url) schema)
69 | (d/transact (d/connect db-url) test-data))
70 |
71 | (defn query1 [db]
72 | (d/q '[:find
73 | (pull ?e
74 | [:user/name
75 | :user/age
76 | {:cars [:car/make :car/model]}])
77 | :where
78 | [?e :user/name "ftravers"]]
79 | db))
80 |
81 | (query1 (-> db-url d/connect d/db))
82 |
83 |
84 |
--------------------------------------------------------------------------------
/test/datomic_tutorial/core_test.clj:
--------------------------------------------------------------------------------
1 | (ns datomic-tutorial.core-test
2 | (:require [clojure.test :refer :all]
3 | [datomic-tutorial.core :refer :all]))
4 |
5 | (deftest a-test
6 | (testing "FIXME, I fail."
7 | (is (= 0 1))))
8 |
--------------------------------------------------------------------------------