Using d/with on past database value from d/as-of

I have a use-case where I’d like to be able to go back to a past value of the database, and speculatively apply some transactions to get a “what-if” version of the database that I can query.

My understanding is that d/as-of gives me a historical value of the database, and then using d/with should allow me to speculatively apply some transactions, and then using the :db-after key in the result I should be able to query the resulting database.

However, this doesn’t work, as demonstrated in the code below. I have a few questions:

  1. Is my understanding correct, that I should be able to go back to a past value of the db and create a new (temporary) “branch” with d/with?

  2. If so, where am I making the mistake?

  3. If not, then why does d/with take a db as an argument, if it only ever works with the current database? Wouldn’t it be clearer for it to take a connection like d/transact?

  4. It seems like this should be possible if database values had the same immutable semantics as regular clojure data structures. Is this possible in some other way? It would be a very useful capability.

  5. In the returned data from trying d/with there is a :db-before and a :db-after key that look like they have different values, but the database appears to be the same… is this intended?

    (require '[datomic.api :as d])

    ;; Set up database
    (def uri “datomic:dev://localhost:4334/smalldemo”)
    (d/create-database uri)
    (def conn (d/connect uri))

    ;; Load schema
    @(d/transact conn [{:db/id #db/id[:db.part/db]
    :db/ident :loan/balance
    :db/valueType :db.type/bigdec
    :db/cardinality :db.cardinality/one
    :db/doc “The balance for the loan”
    :db.install/_attribute :db.part/db}])

    ;; Create a loan entity
    (def tx @(d/transact conn [{:db/id “new-loan”, :loan/balance 0M}]))
    (def eid (get (:tempids tx) “new-loan”))
    => 17592186045418

    ;; Change the balance a few times
    @(d/transact conn [[:db/add eid :loan/balance 100M]])
    @(d/transact conn [[:db/add eid :loan/balance 200M]])
    @(d/transact conn [[:db/add eid :loan/balance 300M]])

    ;; Show the current loan entity and balance
    (d/touch (d/entity (d/db conn) eid))
    => {:db/id 17592186045418, :loan/balance 300M}

    ;; Get the transaction where an intermediate balance was set
    (def tx-eid (ffirst (d/q '[:find ?tx
    :in $ ?e
    [?e :loan/balance 100M ?tx true]]
    (d/history (d/db conn)) eid)))
    => 13194139534315

    ;; Show that this is a transaction entity
    (d/touch (d/entity (d/db conn) tx-eid))
    => #:db{:id 13194139534315, :txInstant #inst “2020-05-03T08:22:51.991-00:00”}

    ;; Show the t-value of that transaction
    (d/tx->t tx-eid)
    => 1003

    ;; Show the current basis
    (d/basis-t (d/db conn))
    => 1005

    ;; Get the database as-of the transaction where loan balance is 100M
    (def db-100 (d/as-of (d/db conn) tx-eid))
    => datomic.db.Db@c174a32a

    ;; Show that the t-value of this database is what we wanted
    (d/as-of-t db-100)
    => 1003

    ;; Show that the loan balance is 100M for that database
    (d/touch (d/entity db-100 eid))
    => {:db/id 17592186045418, :loan/balance 100M}

    ;; Starting from that old db value, apply another transaction changing the balance
    (def tx-new (d/with db-100 [[:db/add eid :loan/balance 9999M]]))
    => {:db-before datomic.db.Db@c174a32a,
    :db-after datomic.db.Db@259f805b,
    :tx-data [#datom[13194139534318 50 #inst “2020-05-03T08:54:26.343-00:00” 13194139534318 true]
    #datom[17592186045418 72 9999M 13194139534318 true]
    #datom[17592186045418 72 300M 13194139534318 false]],
    :tempids {}}

    ;; Get the db value that includes this transaction
    (def db-after (:db-after tx-new))
    => datomic.db.Db@259f805b

    ;; Qet the resulting loan entity from that database and check the balance
    (d/touch (d/entity db-after eid))
    => {:db/id 17592186045418, :loan/balance 100M}

    ;; Write it as a query
    (d/q '[:find ?bal
    :in $ ?e
    [?e :loan/balance ?bal]]
    db-after eid)
    => {[100M]}

    ;; Show that the t-value doesn’t appear to have progressed
    (d/as-of-t db-after)
    => 1003

    ;; Show that the db-before value has the same t, even though it is a different object, which
    ;; seems like it violates value semantics
    (d/as-of-t (:db-before tx-new))
    => 1003