Create a gist now

Instantly share code, notes, and snippets.

What would you like to do?
Using database functions in Datomic transactions and annotating transaction history
(use '[datomic.api :only [q db] :as d])
(def uri "datomic:mem://accounts")
;; create database
(d/create-database uri)
;; connect to database
(def conn (d/connect uri))
;; parse schema dtm file
(def schema-tx (read-string (slurp "db/schema.dtm")))
;; submit schema transaction
@(d/transact conn schema-tx)
;; parse seed data dtm file
(def data-tx (read-string (slurp "db/accounts.dtm")))
;; submit seed data transaction
@(d/transact conn data-tx)
(defn decorate
"Simple function to pull out all the attributes of an entity into a map"
[id]
(let [ db (d/db conn)
e (d/entity db id)]
(select-keys e (keys e))))
(defn decorate-results
"maps through a result set where each item is a single entity and decorates it"
[r]
(map #(decorate (first %)) r))
(defn accounts
"returns all accounts"
[]
(d/q '[:find ?a :where [?a :account/balance _]] (d/db conn)))
(defn history
"Returns all transactions"
([] (d/q '[:find ?tx :in $ :where [?tx :ot/amount _]] (d/db conn)))
([acct] (let [rules '[[(party ?t ?a)
[?t :ot/from ?a]]
[(party ?t ?a)
[?t :ot/to ?a ]]]]
(d/q '[ :find ?t :in $ % ?a :where (party ?t ?a)]
(d/db conn) rules acct))))
(defn transfer [ from to amount note]
(let [txid (datomic.api/tempid :db.part/tx)]
(d/transact conn [[:transfer from to amount]
{:db/id txid, :db/doc note :ot/from from :ot/to to :ot/amount amount}])))
(defn credit [ to amount ]
(d/transact conn [[:credit to amount]]))
(def issuer (ffirst (d/q '[:find ?e :where [?e :account/name "issuer"]] (d/db conn))))
(def bob (ffirst (d/q '[:find ?e :where [?e :account/name "bob"]] (d/db conn))))
(def alice (ffirst (d/q '[:find ?e :where [?e :account/name "alice"]] (d/db conn))))
(transfer issuer alice 77M "Issuance to Alice")
(transfer issuer bob 23M "Issuance to Bob")
(transfer alice bob 7M "Tomatoes")
(prn (decorate-results (accounts)))
(println "All transactions")
(prn (decorate-results (history)))
(println "Issuer's transactions")
(prn (decorate-results (history issuer)))
(prn (decorate issuer))
(println)
(println "Bob's transactions")
(prn (decorate-results (history bob)))
(prn (decorate bob))
(println)
(println "Alice's transactions")
(prn (decorate-results (history alice)))
(prn (decorate alice))
(println)
;; Throws an exception
(transfer alice bob 71M "Tomatoes")
[
{:db/id #db/id[:db.part/user -1000001], :account/name "issuer", :account/balance 0M, :account/min-balance -1000M}
{:db/id #db/id[:db.part/user -1000002], :account/name "bob", :account/balance 0M, :account/min-balance 0M}
{:db/id #db/id[:db.part/user -1000003], :account/name "alice", :account/balance 0M, :account/min-balance 0M}
]
[
;; accounts
{ :db/id #db/id[:db.part/db]
:db/ident :account/name
:db/valueType :db.type/string
:db/cardinality :db.cardinality/one
:db/fulltext true
:db/unique :db.unique/value
:db/doc "An account's name"
:db.install/_attribute :db.part/db}
{ :db/id #db/id[:db.part/db]
:db/ident :account/balance
:db/cardinality :db.cardinality/one
:db/valueType :db.type/bigdec
:db/doc "The accounts balance"
:db.install/_attribute :db.part/db}
{ :db/id #db/id[:db.part/db]
:db/ident :account/min-balance
:db/cardinality :db.cardinality/one
:db/valueType :db.type/bigdec
:db/doc "The accounts maximum balance"
:db.install/_attribute :db.part/db}
{ :db/id #db/id[:db.part/db]
:db/ident :ot/amount
:db/valueType :db.type/bigdec
:db/cardinality :db.cardinality/one
:db/doc "Amount transacted"
:db.install/_attribute :db.part/db}
{ :db/id #db/id[:db.part/db]
:db/ident :ot/from
:db/valueType :db.type/ref
:db/cardinality :db.cardinality/one
:db/doc "Transferee"
:db.install/_attribute :db.part/db}
{ :db/id #db/id[:db.part/db]
:db/ident :ot/to
:db/valueType :db.type/ref
:db/cardinality :db.cardinality/one
:db/doc "Recipient"
:db.install/_attribute :db.part/db}
{ :db/id #db/id [:db.part/user]
:db/ident :credit
:db/fn #db/fn { :lang "clojure"
:params [db id amount]
:code "(let [ e (datomic.api/entity db id)
min-balance (:account/min-balance e 0)
balance (+ (:account/balance e 0) amount) ]
(if (>= balance min-balance)
[[:db/add id :account/balance balance ]]
(throw (Exception. \"Insufficient funds\"))))" }}
{ :db/id #db/id [:db.part/user]
:db/ident :transfer
:db/fn #db/fn { :lang "clojure"
:params [db from to amount]
:code "[[:credit from (- amount)]
[:credit to amount]]"}}
]

ioRekz commented May 19, 2016 edited

Would you be able to go from transaction to entity ?
Let's say I have other attribute on the bank account that I need to access while querying all the transactions like in your example.
I didn't find a way to do that so I keep adding data to the transaction itself but that does not feel right.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment