public
Last active

  • Download Gist
repl_session.clj
Clojure
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239
(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]])))
util.clj
Clojure
1 2 3 4 5 6 7 8 9 10 11 12 13 14
(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)))

Please sign in to comment on this gist.

Something went wrong with that request. Please try again.