datalevin db/id vs db/ident - clojure

I want to use Datalevin as a database for my app. The README mentions :db/id as the identifier of entities, and I see these do get an autoincremented integer value on insertion.
However, there are also multiple mentions of :db/ident in the source code, for example for implicit constants.
What are the purposes of the two keywords and what are the differences?

:db/ident are called Entity
Identifiers
in Datomic.
They are used to allow for easier "pointing" to other, well known
things; like an enum. So you can have the enum values as datums, but
still be able to reference them via a keyword (no need to look them up
every time before using them).
E.g.
(def conn (d/create-conn "./tst" {}))
(d/transact! conn
[{:db/id 1, :customer-type/name "Fictional", :db/ident :fictional}
{:db/id 2, :customer/name "ACME", :customer/type :fictional}])
(d/pull #conn
[:db/id :customer/name {:customer/type [:db/id :customer-type/name]}]
2)
; ⇒ {:db/id 2, :customer/name "ACME",
; :customer/type {:db/id :fictional,
; :customer-type/name "Fictional"}}

Related

Trouble implementing tagging in a datomic db

I'm trying to set up a tagging systems in my db, I have added two attrs to handle this:
{:db/ident :metadata/tags
:db/valueType :db.type/ref
:db/cardinality :db.cardinality/many
:db/isComponent true}
{:db/ident :metadata/tag
:db/valueType :db.type/keyword
:db/cardinality :db.cardinality/one
:db/index true}
This works more or less in terms of assigning tags, but how I am encountering some problems in filtering for tags. I think when doing this I assumed that I'd be able to reference the tags the way one would idents, as a keyword, e.g. :tag1. But it's proving a bit more challenging.
My first approach has been to use a query filter expression, something like this (non-working attempt):
'[:find (pull ?doc [*])
:in $ ?tags
:where [?doc :arb/metadata ?meta]
[?doc :metadata/tags ?tagset]
[(every? (set ?tags) ?tagset)]]
where :tags is a supplied list of tag keywords, e.g. [:tag1 :tag2 ...]. But the result of the pull doesn't quite work here because for each tag the pull returns a map including the :db/id as well as the tag e.g.:
[#:arb{:metadata
[{:db/id 17592186045497,
:metadata/tags
[{:db/id 17592186045498, :metadata/tag :tag1}
{:db/id 17592186045499, :metadata/tag :tag2}],
I thought that I could still work with this by mapping over the ?tagset to extract just the tag keywords, but inside the map expression '?tagset` ends up being out of scope. So I'm a bit stumped about how I should approach this problem, which I feel must be somewhat common... Any tips?

Inconsistency between in (mem) and out (dev,sql,etc) of process Transactor in Datomic:

I've discovered an inconsistency between the way db.type/instant values are handled in the in (mem) and out (dev,sql...) of process transactor.
When using the in process mem transactor the underlying type of a db.type/instant value is preserved when transacting and retrieving. For example, (type-printer uri) below shows that a transacted java.util.TimeStamp is retrieved as a java.sql.TimeStamp. However in the out of process transactors it is retrieved as a java.util.Date.
In most cases this inconsistency does not cause problems, however when dealing with bad Date implementations it can cause all sorts of problems. In my case the fact that java.sql.TimeStamp does not have a symmetric equals method, meant that a bug in my code only appeared when I ran it in an out of process transactor...
Obviously Datomic does not have any control over external implementations of java.util.Date, so I think the 'Date sanitisation' approach of the out of process transactors is correct.
Really all I want is a sanity check before I raise it as an issue with the Datomic guys so if someone could take a look and see if you agree it would be greatly appreciated.
Thanks,
Matt.
(require '[datomic.api :as d])
(import '(java.sql Timestamp))
(defn type-printer [uri]
"Prints the type of a transacted java.sql.TimeStamp when retrieved from the databse"
(d/create-database uri)
(let [ts-att-schema {:db/id (d/tempid :db.part/db)
:db/ident :entity/ts
:db/valueType :db.type/instant
:db/cardinality :db.cardinality/one
:db.install/_attribute :db.part/db}
ts (Timestamp. 1000000000000)
entity {:db/id (d/tempid :db.part/user)
:entity/ts ts}
conn (d/connect uri)]
;transact schema and then entity with associated TimeStamp value
#(d/transact conn [ts-att-schema])
#(d/transact conn [entity])
(let [type (->> (d/db conn)
(d/q '[:find ?ts :where[?e :entity/ts ?ts]])
(first)
(first)
(type)
)]
(println "java.sql.TimeStamp is retrived as type" type))))
;TimeStamp type is preserved when being transacted and then retrieved
(type-printer "datomic:mem://test-db")
;TimeStamp is sanitised in the database and retrieved as a java.util.Date. This case obviously relies on a
;dev transactor running on localhost:4334. Have only verified for dev and sql transactors.
(type-printer "datomic:dev://localhost:4334/test-db")

Test for equality between a clojure PersistentArrayMap and a datomic EntityMap?

I'm writing some tests on my datomic code. I create a transaction map:
(def tx-map
{:db/id #db/id[:db.part/user -1000235]
:some-ns/my-attr {:db/id 17592186045421}}
It transacts fine. I query for my newly added entity:
(def ent
"Query result."
{:db/id 17592186045425})
Test to see if all is well:
(= (:some-ns/my-attr tx-map) ; {:db/id 17592186045421}
(:some-ns/my-attr ent)) ; {:db/id 17592186045421}
=> false ; All is not well
The reason being?
(type (:some-ns/my-attr tx-map)) => clojure.lang.PersistentArrayMap
(type (:some-ns/my-attr ent)) => datomic.query.EntityMap
How do I correctly test for equality between a clojure PersistentArrayMap and a datomic EntityMap?
Entities are lazy maps, so they cannot be compared with maps.
http://docs.datomic.com/entities.html
But they are associative and we can get the values for each attributes. So you can compare the ids :
(== (:db/id (:some-ns/my-attr tx-map)) ; 17592186045421
(:db/id ent)) ; 17592186045421
Beware the ==, we compare two numbers... ;)
So only the :db/id will be selected. Perhaps the ent function should give you the id.
(def ent
":db/id of query result."
17592186045425)
And the test becomes :
(== (:db/id (:some-ns/my-attr tx-map)) ; 17592186045421
ent) ; 17592186045421
Sorry for the late answer, I was very buzy.
Warning, it's only according to the documentation, I don't have an runnable installed datomic version now...
Assoc'ing and the other Clojure map interface capabilities aren't currently supported on Datomic EntityMaps, but this feature is under consideration.
To convert an entity to a Clojure map, simply use (into).
(into {} some-entity)
Reference : https://groups.google.com/d/msg/datomic/IzRSK9e7VJo/YGejFLl3vxYJ
But it's not the problem here, as this gives the whole attributes that would be returned if we realize the whole map.

can we alter datomic enums or can we add any new values to datomic enums?

For example I have following structure
{:db/id #db/id[:db.part/db]
:db/ident :persons/gender
:db/valueType :db.type/ref
:db/cardinality :db.cardinality/one
:db/doc "A person's gender enum reference"
:db.install/_attribute :db.part/db}
;; :persons/gender enum values
{:db/id #db/id[:db.part/user]
:db/ident :persons.gender/male}
{:db/id #db/id[:db.part/user]
:db/ident :persons.gender/female}
and after designing i want to add one more attribute to existing structure
is it possible with datomic..?
The short answer is yes, you aren't really altering the DB schema, just adding a new allowed value so it doesn't conflict with any existing data. This case is not described as schema alteration as you can check here http://docs.datomic.com/schema.html
You may find that Tupelo Datomic can help with your use case. It is a user-friendly library to make interacting with Datomic easier and more effortless.

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" } ] ] )))