`:db/ensure` doesn't seem virtual

I just tried using :db/ensure, but when I pull the entity, which I’ve transacted with a :db/ensure attribute, I did get back that :db/ensure attribute.

(ns xxx
  (:require [datomic.client.api :as d]
            [clojure.pprint :refer [pprint]]))

(set! *print-namespace-maps* false)
(declare conn)
(defn tx! [tx-data] (d/transact conn {:tx-data tx-data}))
(defn valid? [& [db-val eid :as args]]
  (pprint args)
  (pprint (d/pull db-val ['*] eid))
  true)

(comment
  @(def dc (d/client {:server-type :dev-local :storage-dir :mem :system "sys"}))
  (def db-ref {:db-name "lab"})
  (d/delete-database dc db-ref)
  (d/create-database dc db-ref)
  @(def conn (d/connect dc db-ref))
  (tx! [{:db/ident :valid-entity :db.entity/preds `valid?}])

  (tx! [{:db/ident :entity-1 :db/doc "initial value"}])
  (d/pull (d/db conn) ['* {:db/ensure ['*]}] :entity-1)

; => {:db/id 92358976733258, :db/ident :entity-1, :db/doc "initial value"}

  (tx! [{:db/ident :entity-1 :db/ensure :valid-entity}])

  ;;; ======== this is printed by the `valid?` predicate ========
  ;
  ; (#datomic.core.db.Db{:id "1c28def8-121a-4c47-9877-555abc4e0e2d", :basisT 8, :indexBasisT 0, :index-root-id nil, :asOfT nil, :sinceT nil, :raw nil}
  ; 92358976733258)
  ;
  ;{:db/id 92358976733258,
  ; :db/ident :entity-1,
  ; :db/doc "initial value",
  ; :db/ensure [{:db/id 74766790688841, :db/ident :valid-entity}]}

  ;=>
  ;{:db-before #datomic.core.db.Db{:id "1c28def8-121a-4c47-9877-555abc4e0e2d",
  ;                                :basisT 7,
  ;                                :indexBasisT 0,
  ;                                :index-root-id nil,
  ;                                :asOfT nil,
  ;                                :sinceT nil,
  ;                                :raw nil},
  ; :db-after #datomic.core.db.Db{:id "1c28def8-121a-4c47-9877-555abc4e0e2d",
  ;                               :basisT 8,
  ;                               :indexBasisT 0,
  ;                               :index-root-id nil,
  ;                               :asOfT nil,
  ;                               :sinceT nil,
  ;                               :raw nil},
  ; :tx-data [#datom[13194139533320 50 #inst"2023-07-12T06:30:04.071-00:00" 13194139533320 true]
  ;           #datom[92358976733258 69 74766790688841 13194139533320 true]],
  ; :tempids {}}

  (d/pull (d/db conn) ['* {:db/ensure ['*]}] :entity-1)

  ;=>
  ;{:db/id 92358976733258,
  ; :db/ident :entity-1,
  ; :db/doc "initial value",
  ; :db/ensure [{:db/id 74766790688841, :db/ident :valid-entity, :db.entity/preds [xxx/valid?]}]}

  (tx! [{:db/ident :entity-1 :db/doc "NEW value" :db/ensure :valid-entity}])
  (d/pull (d/db conn) ['* {:db/ensure ['*]}] :entity-1)
  )

As we can see, there is indeed a datom for the :db/ensure attribute (eid 69) in the transaction receipt!

According to Schema Data Reference | Datomic

:db/ensure is a virtual attribute.
It is not added in the database;
instead it triggers checks based on the named entity.

So either the docs is incorrect or the dev-local implementation is incorrect.

Either way, I’m a bit reluctant to introduce it to production, without understanding whether I’m doing something wrong or there is a bug.

I tried it on a Datomic Cloud system too:

{:server-type :ion,
 :region "ap-southeast-1",
 :endpoint "http://entry.xxx-dcs.ap-southeast-1.datomic.net:8182/"},

result is the same; i’m getting back and entity map with a :db/ensure attribute:

(d/pull (d/db conn) ['* {:db/ensure ['*]}] :entity-1)
=>
{:db/id 87960930222154,
 :db/ident :entity-1,
 :db/doc "initial value",
 :db/ensure [{:db/id 87960930222153,
              :db/ident :valid-entity,
              :db.entity/preds [xxx.datomic.component/loophole]}]}

and the entity predicate function is called indeed, since i’m seeing the debug print out from it, so it’s not like the :db/ensure is just blindly transacted as a regular attribute.

I’m accessing the ion env via a REPLion and redirecting the *out* after nREPL client connection, to see what’s printed by the entity predicate function:

  (alter-var-root #'*out* (constantly *out*))

I’ve cross-posted this here Why does :db/ensure appear as an attribute in my entities? - Datomic Knowledgebase

Hey @onetom I am looking into this and hope to have an update for you later today.

1 Like