Datomic component ids - clojure

I want to transact a deeply nested tree structure into Datomic. An example data structure:
{:tree/id (d/tempid :db.part/user),
:tree/nodes [
{:node/name "Node1",
:node/parent "root-node-ref",
:node/tasks {"task-entities-map"}},
{:node/name "Node2",
:node/parent "node1-ref",
:node/tasks {"task-entities-map"}}]}
Here Node2 is a child of Node1, and Node1 is a child of some root node.
Datomic docs at http://blog.datomic.com/2013/06/component-entities.html indicate that it's not necessary to specify temp ids for nested maps, as they will be created automatically (given that :db/isComponent is set to true for :tree/nodes, :node/tasks etc.).
The question is: how do i specify the parent-child relations here, as given by :node/parent attributes? I want to avoid having to specify node children e.g. by :node/children attribute. And will Datomic automatically specify temp ids for the entities in :node/tasks lists?
Thanks in advance.

{:db/id (d/tempid :db.part/user),
:tree/nodes
[{:db/id (d/tempid :db.part/user -1),
:db/name "Node1",
:node/parent (:db/id root-node)} ;; assuming you have queried root-node
{:node/name "Node2",
:node/parent (d/tempid :db.part/user -1)}]
Docstring of d/tempid:
Within the scope of a single transaction, tempids map consistently to
permanent ids. Values of n from -1 to -1000000, inclusive, are
reserved for user-created tempids.
Find children of Node1 like this
(d/q '[:find ?children
:where [?node-1 :node/name "Node1"]
[?children :node/parent ?node-1]]
(d/db conn))
Or, assuming that you have queried node1, find its children via
(:node/_parent node1)

Related

Datomic: `:db.error/tempid-not-an-entity` tempid used only as value in transaction

When I try to transact this entity using a string tempid against datomic-free v0.9.5656, I get the following exception:
(def tx1 {:db/id "user"
:contact/full-name "John Wayne"})
(def tx2 {:db/id "other"
:some-ref "user"
(let [!rx (d/transact conn [tx2])]
(prn (:tempids #!rx))
=>
datomic.impl.Exceptions$IllegalArgumentExceptionInfo: :db.error/tempid-not-an-entity tempid used only as value in transaction
data: {#object[clojure.lang.Keyword 0x74af59e7 ":db/error"] #object[clojure.lang.Keyword 0x57972b49 ":db.error/tempid-not-an-entity"]}
java.util.concurrent.ExecutionException: java.lang.IllegalArgumentException: :db.error/tempid-not-an-entity tempid used only as value in transaction
The documentation shows I should be able to use strings as tempids. Am I missing a reader macro to tell it about a partition?
Turns out I was referring to a tempid of an entity that I was not included in the transaction.
I wish the error was clearer, e.g. "You refer to tempid 'user', but the only tempids in this tx are: #{"other"}" And then I'd spot the error immediately.
Another way to get this error message is if you set an attribute to an empty vector. Presumably [] is being interpreted as a tempid, and there's no corresponding :db/id [] to be found in the transaction.
Similar reasoning for an empty map {} - where's the tempid in the transaction with value {}?

Reasons not to use use a wildcard pull?

Are there any reasons not to use a wildcard pull?
(defn pull-wild
"Pulls all attributes of a single entity."
[db ent-id]
(d/pull db '[*] ent-id))
It's much more convenient than explicitly stating the attributes.
It depends on which attributes you need to have in your application and if it's data intensive or whether you want to pull lots of entities.
In case you use the client-library, you might want to minimize the data that needs to be send over the wire.
I guess there are lots of other thoughts about that.
But as long as it's fast enough I would pull the wildcard.
fricke
You may also be interested in the entity-map function from Tupelo Datomic. Given an EID (or a Lookup Ref) it will return the full record as a regular Clojure map:
(let [
; Retrieve James' attr-val pairs as a map. An entity can be referenced either by EID or by a
; LookupRef, which is a unique attribute-value pair expressed as a vector.
james-map (td/entity-map (live-db) james-eid) ; lookup by EID
james-map2 (td/entity-map (live-db) [:person/name "James Bond"] ) ; lookup by LookupRef
]
(is (= james-map james-map2
{:person/name "James Bond" :location "London" :weapon/type #{:weapon/wit :weapon/gun} } ))

What is the difference between the user-specified transaction temp-id and the one returned from a transaction, in Datomic?

I have the following clojure function which transacts to a Datomic database:
(defn demo-tran [term description]
(d/transact conn
[{:db/id (d/tempid :db.part/utility -10034)
:utility.tag/uuid (d/squuid)
:utility.tag/term term
:utility.tag/description description}]))
I then run this in the repl:
(demo-tran "Moo" "A bovine beast")
This succeeds and I am given back a 'transaction map':
{:db-before datomic.db.Db,
#f4c9aa60 :db-after,
datomic.db.Db #908ec69f,
:tx-data [#datom[13194139534424 50 #inst"2016-04-01T09:16:50.945-00:00" 13194139534424 true]
#datom[668503069688921 153 #uuid"56fe3c82-8dbd-4a0d-9f62-27b570cbb14c" 13194139534424 true]
#datom[668503069688921 154 "Moo" 13194139534424 true]
#datom[668503069688921 155 "A bovine beast" 13194139534424 true]],
:tempids {-9222699135738586930 668503069688921}}
I have specified the tempid for this transaction as '-10034' so I would expect to find that negative number in the :tempids map. Instead I find -9222699135738586930. This is confusing. What is going on here?
I was hoping to be able to have the demo-tran function return the new EntityID but (other than guessing the position in the :tempids map) there is no way, given my inputs, to get to this value.
As one commenter mentions (via link), you need to use resolve-tempid, as documented here and demonstrated in the day of datomic project here.
In your case this would be something like:
(let [my-tempid (d/tempid :db.part/utility -100034)
tx-result #(d/transact conn [{:db/id my-tempid
:your "transaction"}])
db-after (:db-after tx-result)
tempids (:tempids tx-result)]
(d/resolve-tempid db-after tempids my-tempid))

Filtering or matching in nested list

My data structure was original a big Map. But I read that we should not use too big maps, to not run out of atoms. So my new data structure looks like that.
countries = [[{'name', 'Germany'}, {'code', 'DE'}], [{'name', 'Austria'}, {'code', 'AT'}]]
I want to make a filter_by/3 method, to filter this nested list for the country list by attributes name or code
Should I transform the Tuples to Maps or is there another way to filter this?
You could use a list of maps. Maps are very performant when retrieving elements, especially when the keys in a map are very few.
In your example:
countries = [%{name: "Germany", code: "DE"},
%{name: "Austria", code: "AT"}]
Note that even if you'll use thousands of such maps in a list, you'll never run out of atoms since :name and :code will always be the only two allocated atoms (since each atom is exactly is value, so writing :a and :a is like writing 3 and 3).
Once you have a similar list in place, you can filter it with a function like:
def filter_by(countries, key, value) do
Enum.filter(countries, fn(country) -> country[key] == value end)
end
filter_by(countries, :code, "AT")

clojure - add item to nested set

Say I have a set like this:
#{#{"a"} #{"b"} #{"c"}}
Say I wanted to updated the middle set to make s become:
#{#{"a"} #{"be"} #{"c"}}
How would I achieve this?
(-> #{#{"a"} #{"b"} #{"c"}} (disj #{"b"}) (conj #{"be"}))
=> #{#{"a"} #{"be"} #{"c"}}
(of course there's no ordering in sets, it could well be shown in any order).