-
-
Save stuarthalloway/2645453 to your computer and use it in GitHub Desktop.
;; Datomic example code | |
(use '[datomic.api :only (db q) :as d]) | |
;; ?answer binds a scalar | |
(q '[:find ?answer :in ?answer] | |
42) | |
;; of course you can bind more than one of anything | |
(q '[:find ?last ?first :in ?last ?first] | |
"Doe" "John") | |
;; [?last ?first] binds a tuple | |
(q '[:find ?last ?first :in [?last ?first]] | |
["Doe" "John"]) | |
;; [?first ...] binds a collection | |
(q '[:find ?first | |
:in [?first ...]] | |
["John" "Jane" "Phineas"]) | |
;; [[?first ?last]] binds a relation | |
(q '[:find ?first | |
:in [[?first ?last]]] | |
[["John" "Doe"] | |
["Jane" "Doe"]]) | |
;; a database binding name starts with $ instead of ? | |
;; any relation with 4-tuples E/A/V/T can act as a database | |
;; so in Datomic, you can mock a database with a list of lists | |
(q '[:find ?first | |
:in $db | |
:where [$db _ :firstName ?first]] | |
[[1 :firstName "John"]]) | |
;; same as previous, but omit $db for single-database query | |
;; any relation with 4-tuples eavt can act as a database | |
(q '[:find ?first | |
:where [_ :firstName ?first]] | |
[[1 :firstName "John" 42] | |
[1 :lastName "Doe" 42]]) | |
;; simple in-memory join, two tuple bindings | |
(q '[:find ?first ?height | |
:in [?last ?first ?email] [?email ?height]] | |
["Doe" "John" "[email protected]"] | |
["[email protected]" 71]) | |
;; simple in-memory join, two relation bindings | |
;; see next example for a faster approach | |
(q '[:find ?first ?height | |
:in [[?last ?first ?email]] [[?email ?height]]] | |
[["Doe" "John" "[email protected]"] | |
["Doe" "Jane" "[email protected]"]] | |
[["[email protected]" 73] | |
["[email protected]" 71]]) | |
;; same as previous example, but with database expressions | |
;; runs faster than relation bindings (as of July 2012) | |
(q '[:find ?first ?height | |
:in $a $b | |
:where [$a ?last ?first ?email] | |
[$b ?email ?height]] | |
[["Doe" "John" "[email protected]"] | |
["Doe" "Jane" "[email protected]"]] | |
[["[email protected]" 73] | |
["[email protected]" 71]]) | |
;; simple in-memory join, two database bindings | |
(q '[:find ?first ?height | |
:in $db1 $db2 | |
:where [$db1 ?e1 :firstName ?first] | |
[$db1 ?e1 :email ?email] | |
[$db2 ?e2 :email ?email] | |
[$db2 ?e2 :height ?height]] | |
[[1 :firstName "John"] | |
[1 :email "[email protected]"] | |
[2 :firstName "Jane"] | |
[2 :email "[email protected]"]] | |
[[100 :email "[email protected]"] | |
[100 :height 73] | |
[101 :email "[email protected]"] | |
[101 :height 71]]) | |
;; compare to http://stackoverflow.com/questions/3717939/iterating-and-processing-an-arraylist | |
(q '[:find ?car ?speed | |
:in [[?car ?speed]] | |
:where [(> ?speed 100)]] | |
[["Stock" 225] | |
["Spud" 80] | |
["Rocket" 400] | |
["Stock" 225] | |
["Clunker" 40]]) | |
;; compare to http://stackoverflow.com/questions/109383/how-to-sort-a-mapkey-value-on-the-values-in-java | |
(->> (q '[:find ?k ?v | |
:in [[?k ?v] ...]] | |
{:D 67.3 :A 99.5 :B 67.4 :C 67.5}) | |
(sort-by second)) |
I like the idea of using datomic queries over clojure collections.
I am not sure how to use it to solve my problem.
I am not sure if I am approaching this problem correctly/appropriately.
Example:
(def collection [{:key-1 :val-a1 :key-2 :val-a2 ... :key-100 :val-a100}
{:key-1 :val-b1 :key-2 :val-b2 ... :key-100 :val-b100}
...
{:key-1 :val-x1 :key-2 :val-x2 ... :key-100 :val-x100}
{:key-1 :val-z1 :key-2 :val-z2 ... :key-100 :val-z100}])
(defn find-all
"Return the hash where the value for :key-99 is equal to :val-x100."
[collection]
(d/q '[:find ?c
:in [?c ...]
:where
[(-> ?c :key-100 (= :val-x100)) ?f]
[(= ?f true)]]
collection))
(find-all collection)
{[{:key-100 :val-x100, :key-1 :val-x1, :key-2 :val-x2}]}
;; I need to parameterize the value and pass it in as an argument.
;; Currently this does not work.
(defn find-all2
"Return the hash where the value for :key-99 is equal to 'value'."
[value collection]
(d/q '[:find ?c
:in ?v [?c ...]
:where
[(-> ?c :key-100 (= ?v)) ?f]
[(= ?f true)]]
value collection))
(find-all2 :val-x100 collection)
CompilerException java.lang.RuntimeException: Unable to resolve symbol: ?v in this context, compiling:(/private/var/folders/0f/4w3qyc8s5vs9bnv6lfh5pg_80000gn/T/form-init8853993715126212403.clj:7:64)
Has there been any discussion about extending datomic.client.api/q
to allow this sort of query behavior in Datomic Cloud?
(d/q '[:find ?first ?height
:in $a $b
:where [$a ?last ?first ?email]
[$b ?email ?height]]
[["Doe" "John" "[email protected]"]
["Doe" "Jane" "[email protected]"]]
[["[email protected]" 73]
["[email protected]" 71]])
Execution error (ExceptionInfo) at datomic.client.api.impl/incorrect (impl.clj:42).
Query args must include a database
Looks like collections don't satisfy the Queryable
protocol and thus fail.
Now, that we have in-memory dev-local db, we can whip up a dummy db for these kinds of queries:
(let [dummy-opts {:server-type :dev-local
:storage-dir :mem
:system "dummy"
:db-name "dummy"}
dummy-db (-> dummy-opts
d/client
(doto (d/create-database dummy-opts))
(d/connect dummy-opts)
d/db)]
(d/q '[:find ?first ?height
:in $ $a $b
:where [$a ?last ?first ?email]
[$b ?email ?height]]
dummy-db
[["Doe" "John" "[email protected]"]
["Doe" "Jane" "[email protected]"]]
[["[email protected]" 73]
["[email protected]" 71]]))
or maybe this is cleaner, and also makes it easy to reuse the dummy db:
(def dummy-datomic-system
{:server-type :dev-local
:storage-dir :mem
:system "dummy-system"})
(def dummy-db-ref
{:db-name "dummy-db"})
(def dummy-db
(-> dummy-datomic-system
d/client
(doto (d/create-database dummy-db-ref))
(d/connect dummy-db-ref)
d/db))
(d/q '[:find ?first ?height
:in $ $a $b
:where [$a ?last ?first ?email]
[$b ?email ?height]]
dummy-db
[["Doe" "John" "[email protected]"]
["Doe" "Jane" "[email protected]"]]
[["[email protected]" 73]
["[email protected]" 71]])
if we use map-args with d/q
, then the query would look like this
(d/q {:query '[:find ?first ?height
:in $ $a $b
:where [$a ?last ?first ?email]
[$b ?email ?height]]
:args [dummy-db
[["Doe" "John" "[email protected]"]
["Doe" "Jane" "[email protected]"]]
[["[email protected]" 73]
["[email protected]" 71]]]})
as we can see, the dummy-db
and the :in $
goes hand in hand, so we could write a function, which just adds those to queries.
if the query would be in map format, then such function would be easier to implement:
(def query-expecting-a-db
'{:find [?first ?height]
:in [$ $a $b]
:where [[$a ?last ?first ?email]
[$b ?email ?height]]})
(d/q {:query query-expecting-a-db
:args [dummy-db
[["Doe" "John" "[email protected]"]
["Doe" "Jane" "[email protected]"]]
[["[email protected]" 73]
["[email protected]" 71]]]})
then we can write a function, which injects the dummy-db
and doctors the :in
clause:
(def query-non-db-data
'{:find [?first ?height]
:in [$a $b]
:where [[$a ?last ?first ?email]
[$b ?email ?height]]})
(defn with-dummy-db [q & args]
{:query (-> q (update :in #(into '[$] %)))
:args (into [dummy-db] args)})
(-> query-non-db-data
(with-dummy-db
[["Doe" "John" "[email protected]"]
["Doe" "Jane" "[email protected]"]]
[["[email protected]" 73]
["[email protected]" 71]])
d/q)
The age of the question-mark prefixed symbol in Clojure is nigh!