Last active
December 24, 2015 13:39
-
-
Save augustl/6806619 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(ns user) | |
(do | |
(require '[datomic.api :as d]) | |
(require '[datomic-demo.util :as util]) | |
(import 'java.util.Date) | |
(def datomic-url "datomic:mem://baksia-demo") | |
(d/create-database datomic-url) | |
(def datomic-conn (d/connect datomic-url))) | |
(d/transact | |
datomic-conn | |
[{:db/id #db/id[:db.part/db] | |
:db.install/_attribute :db.part/db | |
:db/unique :db.unique/value | |
:db/ident :user/email | |
:db/valueType :db.type/string | |
:db/cardinality :db.cardinality/one} | |
{:db/id #db/id[:db.part/db] | |
:db.install/_attribute :db.part/db | |
:db/ident :user/password-hash | |
:db/valueType :db.type/string | |
:db/cardinality :db.cardinality/one}]) | |
;; Tempids are used to "create" new entities. We can use the tempid multiple times | |
;; to set up relational data for multiple tempids in one transaction, before we have | |
;; actual entity IDs. See product/order/delivery examples below. | |
(def user-tempid (d/tempid :db.part/user)) | |
(def insert-tx | |
(deref | |
(d/transact | |
datomic-conn | |
[[:db/add user-tempid :user/email "august@augustl.com"] | |
[:db/add user-tempid :user/password-hash "123abc"]]))) | |
;; We ask datomic for the actual entity ID that was created for our tempid. | |
(def our-user-eid (d/resolve-tempid (:db-after insert-tx) (:tempids insert-tx) user-tempid)) | |
;; We ask datomic for an entity. Datomic just stores fact, entities aren't stored, but we can derive | |
;; entities from multiple facts about the same entity ID, the "entity" function does this for us. | |
(def our-user (d/entity (:db-after insert-tx) our-user-eid)) | |
;; The value is lazy. Iterate it, or just read out (:user/email our-user), or use "touch" to | |
;; do all of that automatically. Note that this is about decompressing facts from the chunks that | |
;; has probably allready been received from storage - no networking happens, so there's no point in | |
;; doing "touch" to get an "atomic" fetch over the network or w/e. It's just for convenience/debugging. | |
(d/touch our-user) | |
;; Update looks exactly like create, except we use the actual entity ID instead of | |
;; a tempid for our facts. | |
(def update-tx | |
(deref | |
(d/transact | |
datomic-conn | |
[[:db/add (:db/id our-user) :user/email "august@kodemaker.no"]]))) | |
;; Retract an entity | |
(def retract-tx | |
(deref | |
(d/transact | |
datomic-conn | |
[[:db/retract (:db/id our-user) :user/email "august@kodemaker.no"]]))) | |
(def retract-entity-tx | |
(deref | |
(d/transact | |
datomic-conn | |
[[:db.fn/retractEntity (:db/id our-user)]]))) | |
;; At this point, it's fun to look at the DB at different points in time. | |
(def our-user (d/entity (:db-before insert-tx) our-user-eid)) | |
(def our-user (d/entity (:db-after insert-tx) our-user-eid)) | |
(def our-user (d/entity (:db-before update-tx) our-user-eid)) | |
;; and so on | |
;; Note that we don't have this power just because we happen to have the db-before/after values | |
;; from running the transactions, we can at any point ask datomic for, say, the database as of | |
;; two weeks ago, and get an immutable value (the db is an immutable value) that represents the | |
;; value of the database as of that point in time. | |
(comment | |
;; Direct index lookup. | |
;; :eavt = entity, attribute, value, time | |
;; The "datoms" function lets us look up directly from index. It takes a variable | |
;; amount of arguments. | |
(seq (d/datoms (:db-after insert-tx) :eavt ,,,)) | |
;; For :eavt, the first argument is the entity ID. | |
(seq (d/datoms (:db-after insert-tx) :eavt our-user-eid)) | |
;; We can go deeper in the index and get the facts for a specific attribute for an entity ID | |
(seq (d/datoms (:db-after insert-tx) :eavt our-user-eid :user/email)) | |
;; :avet is another index, where we can look up by attribute | |
(seq (d/datoms (:db-after insert-tx) :avet :user/email)) ;; email is "unique" and also indexed. | |
;; As with :avet we can also go deeper. :a is :user/email, :v is "august@augustl.com". This will give | |
;; us a index lookup for all facts with that value (can be multiple if we use "indexed" instead of "unique" in schema) | |
(seq (d/datoms (:db-after insert-tx) :avet :user/email "august@augustl.com")) | |
;; Querying one data source (implicit, see below) | |
(d/q '[:find ?e :where [?e :user/email "august@augustl.com"]] (:db-after insert-tx)) | |
;; Querying with explicit data source (:in $db) | |
(d/q '[:find ?e :in $ :where [?e :user/email "august@augustl.com"]] (:db-after insert-tx)) | |
;; Querying with explicit named data source (:in $db). | |
(d/q '[:find ?e :in $db :where [$db ?e :user/email "august@augustl.com"]] (:db-after insert-tx)) | |
;; Querying with two data sources. Assumes the vector of vectors represents a parsed excel spreadsheet, with | |
;; three colums: the entity ID, the persons name, and the persons balance. | |
(d/q '[:find ?balance :in $db $excel :where [$db ?e :user/email "august@augustl.com"] [$excel ?e ?balance]] (:db-after insert-tx) [[123 "John Doe" 56] [our-user-eid "August" 100]])) | |
(d/transact | |
datomic-conn | |
[{:db/id #db/id[:db.part/db] | |
:db.install/_attribute :db.part/db | |
:db/ident :user/orders | |
:db/valueType :db.type/ref | |
:db/cardinality :db.cardinality/many} | |
{:db/id #db/id[:db.part/db] | |
:db.install/_attribute :db.part/db | |
:db/ident :order/products | |
:db/valueType :db.type/ref | |
:db/cardinality :db.cardinality/many} | |
{:db/id #db/id[:db.part/db] | |
:db.install/_attribute :db.part/db | |
:db/ident :product/name | |
:db/valueType :db.type/string | |
:db/cardinality :db.cardinality/one} | |
{:db/id #db/id[:db.part/db] | |
:db.install/_attribute :db.part/db | |
:db/ident :product/price | |
:db/valueType :db.type/bigdec | |
:db/cardinality :db.cardinality/one} | |
{:db/id #db/id[:db.part/db] | |
:db.install/_attribute :db.part/db | |
:db/ident :delivery/order | |
:db/valueType :db.type/ref | |
:db/cardinality :db.cardinality/one}]) | |
(do | |
(def new-user (util/insert datomic-conn | |
[[:user/email "newuser@foo.com"] | |
[:user/password-hash "123abc"]])) | |
(def product-a (util/insert datomic-conn | |
[[:product/name "Product A"] | |
[:product/price (bigdec 150)]])) | |
(def product-b (util/insert datomic-conn | |
[[:product/name "Product B"] | |
[:product/price (bigdec 100)]])) | |
(def product-c (util/insert datomic-conn | |
[[:product/name "Product C"] | |
[:product/price (bigdec 76)]]))) | |
(let [order-tempid (d/tempid :db.part/user)] | |
(def order-tx | |
@(d/transact | |
datomic-conn | |
[[:db/add (:db/id new-user) :user/orders order-tempid] | |
[:db/add order-tempid :order/products (:db/id product-a)] | |
[:db/add order-tempid :order/products (:db/id product-c)]])) | |
(def order (util/get-inserted-entity order-tx order-tempid))) | |
(let [delivery-tempid (d/tempid :db.part/user)] | |
(def delivery-tx | |
@(d/transact | |
datomic-conn | |
[[:db/add delivery-tempid :delivery/order (:db/id order)]])) | |
(def delivery (util/get-inserted-entity delivery-tx delivery-tempid))) | |
;; At this point we have a problem. The user contacts support and complains that the delivery | |
;; confirmation e-mail was not delivered to newuser-changed@foo.com. | |
(deref | |
(d/transact | |
datomic-conn | |
[[:db/add (:db/id new-user) :user/email "newuser-changed@foo.com"]])) | |
(def our-db (d/db datomic-conn)) | |
(def new-user (d/entity our-db (:db/id new-user))) | |
;; We use over-time queries in datomic to find the users e-mail as of the time of the delivery. | |
(def delivery-txs | |
(d/q '[:find ?tx :in $ ?order-id | |
:where [?order-id _ _ ?tx]] (d/history our-db) (:db/id delivery))) | |
;; We know there's only one. Normally we'd filter based on transaction timestamps (all transactions | |
;; have timestamps) | |
(def delivery-tx (d/touch (d/entity our-db (ffirst delivery-txs)))) | |
;; Voila! | |
(def user-as-of-delivery | |
(d/entity | |
(d/as-of our-db (:db/id delivery-tx)) | |
(:db/id new-user))) | |
;; "Stored procedures" (or transaction functions) written in Java or Clojure allows you to | |
;; consistently do stuff like "add 10 to a field". | |
(d/transact | |
datomic-conn | |
[{:db/id (d/tempid :db.part/user) | |
:db/ident :inc | |
:db/fn (d/function '{:lang "clojure" | |
:params [db eid attr num] | |
:code (let [curr-entity (datomic.api/entity db eid) | |
curr-value (get curr-entity attr) | |
new-val (+ curr-value num)] | |
[[:db/add eid attr new-val]])})} | |
{:db/id #db/id[:db.part/db] | |
:db.install/_attribute :db.part/db | |
:db/ident :user/balance | |
:db/valueType :db.type/long | |
:db/cardinality :db.cardinality/one}]) | |
(def set-balance-tx | |
(deref | |
(d/transact | |
datomic-conn | |
[[:db/add (:db/id new-user) :user/balance 100]]))) | |
(def inc-balance-tx | |
(deref | |
(d/transact | |
datomic-conn | |
[[:inc (:db/id new-user) :user/balance 10]]))) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(ns datomic-demo.util | |
(:require [datomic.api :as d])) | |
(defn get-inserted-entity | |
[tx tempid] | |
(d/touch (d/entity (:db-after tx) (d/resolve-tempid (:db-after tx) (:tempids tx) tempid)))) | |
(defn insert | |
[datomic-conn facts] | |
(let [tempid (d/tempid :db.part/user) | |
tx @(d/transact | |
datomic-conn | |
(map #(concat [:db/add tempid] %) facts))] | |
(get-inserted-entity tx tempid))) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment