Inserting data into Datomic partition - clojure

Looking at the tutorial documents, I've tried creating a partition by loading my schema from a file, which, among other things, contains the following:
{:db/id #db/id[:db.part/db],
:db/ident :account,
:db.install/_partition :db.part/db}
If I try inserting data with the following:
(d/transact conn
[{:db/id #db/id[:db.part/user -1]
:validation/email email
:validation/code code}])
Everthing works as expected. But if I replace "user" with my partitions name "account", like this:
(d/transact conn
[{:db/id #db/id[:db.part/account -1]
:validation/email email
:validation/code code}])
I get this error:
IllegalArgumentExceptionInfo :db.error/not-a-db-id Invalid db/id: #db/id[:db.part/account -1] datomic.error/arg (error.clj:57)
What am I doing wrong? How can I even be sure that I've created the partitions?

your partition name is :account, not :db.part/account.
this code below should work.
(d/transact conn
[{:db/id #db/id[:account -1]
:validation/email email
:validation/code code}])
You can query for list of installed partitions like this
(d/q '[:find [?ident ...]
:where
[?e :db/ident ?ident]
[_ :db.install/partition ?e]]
db)
=> [:account :db.part/tx :db.part/user]

Related

How to query against attributes of multiple values?

Tested on datascript 1.3.0
datoms:
[{:db/id -1 :name "Oliver Smith" :hobbies ["reading" "sports" "music"]}]
tried to run the query below to find who like sports, but the empty set returned.
'[:find ?name
:where
[?p :name ?name]
[?p :hobbies ?hobbies]
[(some #{"sports"} ?hobbies)]]
How to formulate the query correctly to get the expected result below?
#{[Oliver Smith]}
We have to explicitly define the schema with cardinality/many against the attribute of multiple values to solve the problem since schemaless doesn't work here.
(require '[datascript.core :as d])
(def schema {:hobbies {:db/cardinality db.cardinality/many}})
(def conn (d/create-conn schema))
(def datoms [{:db/id -1 :name "Oliver Smith" :hobbies ["reading" "sports" "music"]}])
(d/transact! conn datoms)
(def query '[:find ?name :where [?p :name ?name] [?p :hobbies "sports"]])
(-> (d/q query #conn) println)

Does anyone use Datomic to get the structure and entities separately?

So I use queries to filter data and then use pull to get the information out from the Datomic database.
(def rules
[[[search ?txt ?id] [(fulltext $ :artist/name ?txt) [[?id]]]]
[[search ?txt ?id] [(fulltext $ :track/name ?txt) [[?id]]]]])
(d/q
'[:find [(pull ?id [* {:track/artists [:db/id :track/name] :track/_artists [:db/id :artist/name] }]) ...]
:in $ % ?query
:where [search ?query ?id]]
db rules "John Lennon")
And sometimes these queries can get recursive, so for example I can change the pull to:
(d/q
'[:find [(pull ?id [* {:track/artists [:db/id :track/name] :track/_artists [* {:track/artists [:db/id :track/name]}]}]) ...]
:in $ % ?query
:where [search ?query ?id]]
db rules "John Lennon")
Now what I'd like to do is ensure that unique entities are being returned along with the :db/id structure as I don't want to return duplicate data as much as possible.
For example: (results elided with ...)
{:entities [{:db/id 1 :track/name "..." ...} {:db/id 2 :track/name "..." ...} {:db/id 3 :artist/name "..." ...}]
:structure [{:db/id 1 :track/artists [{:db/id 3}]} {:db/id 2 :track/artists [{:db/id 3}]}]}
Can this be done at the query level? Or do I need to walk the structure after the query returns and modify it? I'm happy to walk the structure at present, I'm just wondering if anyone has worked out a better approach?

How to keep track of the entity id for the stuff that was just added to the db?

On a html-post page a user can input various fields and hit submit,
My router.clj code looks like
(POST "/postGO" [ post-title post-input post-tags :as request ]
(def email (get-in request [:session :ze-auth-email]))
;; connect to datomic and write in the request
(dbm/add-ze-post post-title post-input post-tags email) ;; db insert
{:status 200,
:body "successfully added the post to the database",
:headers {"Content-Type" "text/plain"}}) ;;generic return page
It works well, but I want to redirect the user afterwards to a page that can show them their uploaded post. To do this, it would be very helpful to have the eid of the entity just transacted.
;; code from my dbm file for playing directly with the database
;; id est: the db transact code
(defn add-ze-blurb [title, content, tags, useremail]
(d/transact conn [{:db/id (d/tempid :db.part/user),
:post/title title,
:post/content content,
:post/tag tags,
:author/email useremail}]))
Is there any way to have datomic return the eid as soon as something is added to the DB successfully, or should I use another query right afterward to make sure it's there?
For simple cases, simply deref the future returned by d/transact and use :tx-data. Full example:
(require '[datomic.api :refer [db q] :as d])
(def uri "datomic:mem://test")
(d/create-database uri)
(def conn (d/connect uri))
(def schema [{:db/id (d/tempid :db.part/db)
:db/ident :foo
:db/valueType :db.type/string
:db/cardinality :db.cardinality/one
:db.install/_attribute :db.part/db}])
#(d/transact conn schema)
(def tempid (d/tempid :db.part/user))
(def tx [{:db/id tempid :foo "bar"}])
(def result #(d/transact conn tx)) ;;
(def eid (:e (second (:tx-data result))))
(assert (= "bar" (:foo (d/entity (db conn) eid))))
You can alternatively use d/resolve-tempid:
(def eid (d/resolve-tempid (db conn) (:tempids result) tempid))
(assert (= "bar" (:foo (d/entity (db conn) eid))))
As described in the Datomic documentation, after the deref the transaction will have been applied by the transactor and the data returned reflects the new value of the database.

inserting multiple ordinality attributes/values to datomic db

So far my add-post-to-datomic method looks like
(defn add-post-to-datomic [title, content, useremail]
(d/transact conn [{:db/id (d/tempid :db.part/user),
:post/title title,
:post/content content,
:author/email useremail}]))
I would really like to add functionality to add a potentially variable number of tags.
In my awesome-schema.edn I have
{:db/id #db/id[:db.part/db]
:db/ident :post/tag
:db/valueType :db.type/string
:db/cardinality :db.cardinality/many
:db/doc "tag applied to a post"
:db.install/_attribute :db.part/db}
So it's okay if there are multiple ones in the db thanks to the cardinality set to many.
How can I write the above method to also work for a variable number of tags ?
Look at the Transactions Docs under "Cardinality many transactions". As long as you pass a set of tags this should work:
(defn add-post-to-datomic [title content user-email tag-set]
(d/transact conn [{:db/id (d/tempid :db.part/user)
:post/title title
:post/content content
:post/tag tag-set
:author/email user-email}]))

How to get all ref attribute values?

I touch an entity and get many entity ids. I want all the attribute values instead of the ids while keeping the nested structure.
(d/touch (d/entity (get-db) (ffirst (find-all-families))))
=> {:family/parent #{{:db/id 17592186045423}
{:db/id 17592186045424}
{:db/id 17592186045426}
{:db/id 17592186045427}},
:family/child #{{:db/id 17592186045420}
{:db/id 17592186045421}},
:family/address {:db/id 17592186045428},
:family/email "someemail#gmail.com",
:db/id 17592186045429}
Thought about using something like simply touching all the entity ids but seems like complexity creeps up if I want all of them:
(map d/touch (:family/parent (d/touch (d/entity (get-db) (ffirst (find-all-families))))))
Not sure what the idiomatic approach is: finding a way to do it more through the querying side or through clojure.
The idiomatic way to do this in Datomic is to declare components in your schema. touch will touch all of the attributes of the entity, including any components recursively
You will probably wish to use the Datomic Pull API for this purpose. It can recursively return the attr/value pairs for all sub-entities that the user designates as "component". An example:
(def dark-side-of-the-moon [:release/gid #uuid "24824319-9bb8-3d1e-a2c5-b8b864dafd1b"])
(d/pull db [:release/media] dark-side-of-the-moon)
; result
{:release/media
[{:db/id 17592186121277,
:medium/format {:db/id 17592186045741},
:medium/position 1,
:medium/trackCount 10,
:medium/tracks
[{:db/id 17592186121278,
:track/duration 68346,
:track/name "Speak to Me",
:track/position 1,
:track/artists [{:db/id 17592186046909}]}
{:db/id 17592186121279,
:track/duration 168720,
:track/name "Breathe",
:track/position 2,
:track/artists [{:db/id 17592186046909}]}
{:db/id 17592186121280,
:track/duration 230600,
:track/name "On the Run",
:track/position 3,
:track/artists [{:db/id 17592186046909}]}
...]}]}
You may also use the Tupelo Datomic Pull API, which I think is nicer. As an example:
; If you wish to retain duplicate results on output, you must use td/query-pull and the Datomic
; Pull API to return a list of results (instead of a set).
(let [result-pull (td/query-pull :let [$ (live-db)] ; $ is the implicit db name
:find [ (pull ?eid [:location]) ] ; output :location for each ?eid found
:where [ [?eid :location] ] ) ; find any ?eid with a :location attr
result-sort (sort-by #(-> % first :location) result-pull)
]
(is (s/validate [ts/TupleMap] result-pull)) ; a list of tuples of maps
(is (= result-sort [ [ {:location "Caribbean"} ]
[ {:location "London" } ]
[ {:location "London" } ] ] )))