├── .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 |
4 | 41 |
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 | --------------------------------------------------------------------------------