When To Use :db/id

This is sorta similar to a previous question I asked (here) but I’m examining some schemas (schemae? schemata?) and noticing some are defined with one attribute having :db/id while others are defined with ALL the attributes having :db/id.

One db/id:

{:db/id                 #db/id[:db.part/db -1]
:db/ident              :message/sender
:db/valueType          :db.type/ref
:db/cardinality        :db.cardinality/one
:db/doc                "The sender's ID"
:db.install/_attribute :db.part/db}
   {
:db/ident              :message/content
:db/valueType          :db.type/string
:db/cardinality        :db.cardinality/one
:db/doc                "The message body"
:db.install/_attribute :db.part/db}
   ...many more attributes, no more :db/ids...

And with ALL :db/ids:

{:db/id #db/id[:db.part/db]
:db/ident :event/uuid
:db/valueType :db.type/uuid
:db/cardinality :db.cardinality/one
:db/unique :db.unique/identity
:db/doc “An event’s unique ID”
:db.install/_attribute :db.part/db}

{:db/id #db/id[:db.part/db]
:db/ident :event/name
:db/valueType :db.type/string
:db/cardinality :db.cardinality/one
:db/unique :db.unique/identity
:db/doc “The name of an event”
:db.install/_attribute :db.part/db}
…many more attributes, ALL beginning with :db/id…

Now, I inherited a macro which is rather complex, and which I’m moving away from using (because it’s complex and introduces certain subtle bugs), but when I use on a simple entity with just two fields (both references), I see no :db/id at all:

(def
schema
(datomic-schema.schema/generate-schema
[(datomic-schema.schema/schema*
“relationship”
[{:fields {“relation” [:ref #{}], “party” [:ref #{}]}}])]))
nil))

Ah, I see this depends on the datomic-schema library which looks to put a :db/id in every field.

But I guess what I don’t get is why it would ever be necessary. Wouldn’t every field have to have an ID? How else could you query an attribute? Does it harm anything by being there? Can I get rid of it?

:dizzy:

When you set :db/id to a value of the form #db/id[...], you’re specifying which partition the entity will be stored in (see here). That link also says:

The attributes you create for your schema must live in the :db.part/db partition.

So that is likely why datomic-schema adds #db/id[:db.part/db] to the schema it generates. But as mentioned in the previous question you linked to, doing so is no longer required, so it’s safe to take them out. If you’re using datomic cloud, you’ll have to take them out since setting the partition explicitly is only allowed in on-prem.

Whether you set the partition explicitly or not, datomic will create a new ID and set :db/id to that.

Thank you very much!

It’s unfortunate that the documentation at that link all shows “:db.part/db” if it’s not necessary/potentially harmful.

So I’m trying to use a component and I notice that if I try to transact a component against an entity when the component contains only the attributes of that schema, I get an error message:

:db.error/invalid-nested-entity Nested entity is not a component and has no :db/id

That is to say, if I have an adventurer entity@17592186045425 and try to give him a goal like:

(d/transact (conn) [{:db/id 17592186045425 :user/goals {:goal/goal "Kill the wabbit!" :goal/points 25}}])

I get complaints, but if I attach an arbitrary :db/id to that goal:

(d/transact (conn) [{:db/id 17592186045425 :user/goals {:db/id "What's up, doc?" :goal/goal "Kill the wabbit!" :goal/points 25}}])

Then it works, although obviously said arbitrary :db/id is not actually used. I’m guessing this is a way to tell datomic that it is an entity (and maybe a convenient way to group attributes to entities where the :db/id isn’t known). Is the value used genuinely arbitrary or should I watch out for what I put in there? (I mean, putting in a bigint would seem to be asking for trouble but other than that?)

You’re correct that there is a requirement for an explicit :db/id in non-component nested entities. This is intended to help prevent creation of “orphan” entities (e.g. entities that have no identity attribute and whose parent entity has been retracted).

Your choice of the value for your tempid (which is what you’re making when you provide an explicit :db/id) can be an arbitrary string. The value of the tempid and its mapping to the real entity ID created when the entity is stored is returned in the :tempids map of the return value of the call to transact.

Thank you, Marshall. That explains a lot.