Search code examples
clojuredatomicdatascript

Does Datomic's tempid provide unique entity id?


I have some doubts about datomic.api/tempid fn that provides entity id.

It produces some long value, not UUID String and long is 64 bit which makes me think about it's uniqueness after some point I might reach long's limit. It would be harder with UUID instead.

When I write some code like this, I ask myself "Does this reach the entity id limit and cause a problem when adding new entities?"

@(d/transact
   conn
   [{:db/id   (d/tempid :db.part/user)
     :city/district "BEYKOZ"}])

Solution

  • As the name suggests, tempid only provides a temporary identifier; these are to be used within the context of a single transaction. So you're not going to run out of IDs unless you exceed 2^64 new records within a single transaction—and that kind of transaction would be way too large to run anyway.

    The primary purpose of tempids is to allow us to reference newly-created entities in multiple places within a transaction.

    In fact, the map-based transaction format is a shorthand for the vector-based format; if we want to create a city with a couple attributes, internally, Datomic is doing something more like:

    @(d/transact
       conn
       (let [city-id (d/tempid :db.part/user)]
         [[:db/add city-id :city/district "BEYKOZ"]
          [:db/add city-id :city/population 220364]]))
    

    ...which is only possible with some kind of shared identifier, but we don't know the permanent, insert ID until we've round-tripped from the database.

    Tempids also allow us a way to find these permanent IDs via the return value of transact, by looking into a map of temp IDs to permanent IDs. e.g.

    (let [tempid (d/tempid :db.part/user)
          tx     [{:db/id tempid, :city/district "BEYKOZ"}]
          result @(d/transact conn tx)]
      (d/resolve-tempid (:db-after result) (:tempids result) tempid))
    

    It's also worth noting that as of Datomic 0.9.5530, you don't need to add tempids manually to new records (simply leave the :db/id out of the map), and you can also use strings as tempids. So, for example, we could rewrite the city transaction as:

    @(d/transact
       conn
       [[:db/add "beykoz" :city/district "BEYKOZ"]
        [:db/add "beykoz" :city/population 220364]])