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:
-
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
? -
If so, where am I making the mistake?
-
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 liked/transact
? -
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.
-
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”))
eid
=> 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
:where
[?e :loan/balance 100M ?tx true]]
(d/history (d/db conn)) eid)))
tx-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))
db-100
=> 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]]))
tx-new
=> {: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))
db-after
=> 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
:where
[?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