Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Datomic update examples against a social news database
;; Datomic example code
;; demonstrates various update scenarios, using a news database
;; that contains stories, users, and upvotes
;; grab an in memory database
(use '[datomic.api :only (q db) :as d])
(def uri "datomic:mem://foo")
(d/create-database uri)
(def conn (d/connect uri))
;; schema for stories
(d/transact
conn
[{:db/id #db/id[:db.part/db]
:db/ident :story/title
:db/valueType :db.type/string
:db/cardinality :db.cardinality/one
:db/fulltext true
:db/index true
:db.install/_attribute :db.part/db}
{:db/id #db/id[:db.part/db]
:db/ident :story/url
:db/valueType :db.type/string
:db/cardinality :db.cardinality/one
:db.install/_attribute :db.part/db}
{:db/id #db/id[:db.part/db]
:db/ident :story/slug
:db/valueType :db.type/string
:db/cardinality :db.cardinality/one
:db.install/_attribute :db.part/db}])
;; schema for comments
(d/transact
conn
[{:db/id #db/id[:db.part/db]
:db/ident :comments
:db/valueType :db.type/ref
:db/cardinality :db.cardinality/many
:db/isComponent true
:db.install/_attribute :db.part/db}
{:db/id #db/id[:db.part/db]
:db/ident :comment/body
:db/valueType :db.type/string
:db/cardinality :db.cardinality/one
:db.install/_attribute :db.part/db}
{:db/id #db/id[:db.part/db]
:db/ident :comment/author
:db/valueType :db.type/ref
:db/cardinality :db.cardinality/one
:db.install/_attribute :db.part/db}])
;; schema for users
(d/transact
conn
[{:db/id #db/id[:db.part/db]
:db/ident :user/firstName
:db/index true
:db/valueType :db.type/string
:db/cardinality :db.cardinality/one
:db.install/_attribute :db.part/db}
{:db/id #db/id[:db.part/db]
:db/ident :user/lastName
:db/index true
:db/valueType :db.type/string
:db/cardinality :db.cardinality/one
:db.install/_attribute :db.part/db}
{:db/id #db/id[:db.part/db]
:db/ident :user/email
:db/index true
:db/unique :db.unique/identity
:db/valueType :db.type/string
:db/cardinality :db.cardinality/one
:db.install/_attribute :db.part/db}
{:db/id #db/id[:db.part/db]
:db/ident :user/upVotes
:db/valueType :db.type/ref
:db/cardinality :db.cardinality/many
:db.install/_attribute :db.part/db}])
;; adding new stories:
;; create new ids with #db/id literals
(d/transact
conn
[{:db/id #db/id [:db.part/user]
:story/title "Teach Yourself Programming in Ten Years"
:story/url "http://norvig.com/21-days.html"}
{:db/id #db/id [:db.part/user]
:story/title "Clojure Rationale"
:story/url "http://clojure.org/rationale"}
{:db/id #db/id [:db.part/user]
:story/title "Beating the Averages"
:story/url "http://www.paulgraham.com/avg.html"}])
;; adding new user who upvotes existing stories:
;; query to find existing story ids
;; create new user id with an explicit #db/id literal
;; e.g #db/id [:db.part/user -1]
;; associate existing stories with the new user id
(def all-stories (q '[:find ?e :where [?e :story/url]] (db conn)))
(let [user-id #db/id [:db.part/user -1]]
(d/transact
conn
(conj
(map
(fn [[story-id]] [:db/add user-id :user/upVotes story-id])
all-stories)
{:db/id user-id
:user/email "john@example.com"
:user/firstName "John"
:user/lastName "Doe"})))
;; updating user's name (without touching upvotes or stories)
;; user/email is a :db.unique/identity attribute, so upsert is
;; implicit (you do not have to know the :db/id in advance)
(d/transact
conn
[{:user/email "john@example.com" ;; this finds the existing entity
:db/id #db/id [:db.part/user] ;; will be replaced by exiting id
:user/firstName "Johnathan"}])
;; for variety, we will grab John's id and stop relying on upsert
(def john
(->> (q '[:find ?e :where [?e :user/email "john@example.com"]]
(db conn))
ffirst))
;; grab the id of single upvote
(def johns-upvote-for-pg
(->> (q '[:find ?story
:in $ ?e
:where [?e :user/upVotes ?story]
[?story :story/url "http://www.paulgraham.com/avg.html"]]
(db conn)
john)
ffirst))
;; remove a single upvote (John doesn't like pg anymore? ...)
(d/transact
conn
[[:db/retract john :user/upVotes johns-upvote-for-pg]])
;; double check that john still has two upvotes
(-> (d/entity (db conn) john)
(get :user/upVotes))
;; remove all of John's upvotes
;; create a query that *makes transaction data*
;; then submit the transaction at any later point
;; possibly compsing it with other data
(def data-that-retracts-johns-upvotes
(q '[:find ?op ?e ?a ?v
:in $ ?op ?e ?a
:where [?e ?a ?v]]
(db conn)
:db/retract
john
:user/upVotes))
(d/transact
conn
(seq data-that-retracts-johns-upvotes))
;; double check that john has no more upvotes
(-> (d/entity (db conn) john)
(get :user/upVotes))
(def all-story-ids
(->> (q '[:find ?e :where [?e :story/url]] (db conn))
(map first)))
(defn choose-some
"Pick zero or more items at random from a collection"
[coll]
(take (rand-int (count coll))
(shuffle coll)))
;; sample data generator
(defn example-users
"Make data for example users, possibly with upvotes"
[prefix n]
(mapcat
(fn [n]
(let [user-id (d/tempid :db.part/user)
upvotes (map (fn [id] [:db/add user-id :user/upVotes id])
(choose-some all-story-ids))]
(conj
upvotes
{:db/id user-id
:user/email (str prefix "-" n "@example.com")})))
(range n)))
;; test example-users
(pprint
(example-users "user" 2))
;; make 10 new users
(d/transact conn (example-users "user" 10))
;; how many users should there be now?
(count (q '[:find ?e :where [?e :user/email]] (db conn)))
;; how many users have upvoted something?
(count (q '[:find ?e
:where [?e :user/email]
[?e :user/upVotes]]
(db conn)))
;; find all users, and print their emails plus their upvotes
;; (This shows why Datomic does not need left joins:
;; query and entity navigation are separate.)
(let [dbval (db conn)] ;; shared basis
(->> (q '[:find ?e :where [?e :user/email]] ;; find all
dbval)
(map (fn [[id]] (d/entity dbval id))) ;; entify
(map (fn [ent] ;; navigate
{:email (:user/email ent)
:upvoted (mapv :story/url (:user/upVotes ent))}))
pprint))
;; schema for publication date
(d/transact
conn
[{:db/id #db/id[:db.part/db]
:db/ident :publication/date
:db/valueType :db.type/instant
:db/cardinality :db.cardinality/one
:db/index true
:db.install/_attribute :db.part/db}])
;; find that pg story
(def beating-the-averages
(->> (q '[:find ?e
:where [?e :story/url "http://www.paulgraham.com/avg.html"]]
(db conn))
ffirst))
;; associate story with pub date
(import java.text.SimpleDateFormat)
(def april-04
(-> (java.text.SimpleDateFormat. "yyyy-MM") (.parse "2001-04")))
(d/transact
conn
[[:db/add beating-the-averages :publication/date april-04]])
;; retrieve pub date (domain specific) and transaction time
;; (intrinsic to Datomic)
(let [[pub-time tx-time]
(first (q '[:find ?pub-time ?tx-time
:in $ ?e
:where
[?e :story/url _ ?tx]
[?tx :db/txInstant ?tx-time]
[?e :publication/date ?pub-time]]
(db conn)
beating-the-averages))]
(println "The essay _Beating the Averages was published " pub-time
" but that fact was added to the database on " tx-time))
;; schema for information source
(d/transact
conn
[{:db/id #db/id[:db.part/db]
:db/ident :audit/app
:db/valueType :db.type/keyword
:db/cardinality :db.cardinality/one
:db/index true
:db.install/_attribute :db.part/db}])
;; use the db.part/tx partition to assert facts about the the
;; current transaction
(d/transact
conn
[{:db/id #db/id [:db.part/user]
:story/title "Are We There Yet?"
:story/url "http://www.infoq.com/presentations/Are-We-There-Yet-Rich-Hickey"}
[:db/add (d/tempid :db.part/tx) :audit/app :admin/shell]])
;; find stories added by the admin/shell app
(q '[:find ?url
:where [_ :story/url ?url ?tx]
[?tx :audit/app :admin/shell]]
(db conn))
@jonase

This comment has been minimized.

Copy link

commented Jun 18, 2012

The query on line 131 takes john as a second input, but there is no :in-clause for that particular query.

@stuarthalloway

This comment has been minimized.

Copy link
Owner Author

commented Jun 18, 2012

Fixed -- thanks

@ghost

This comment has been minimized.

Copy link

commented Oct 12, 2012

you could click the Edit button (not the text) and rename "Datomic News Updates" to "Datomic News Updates.clj" and have syntax highlighting ;)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.