I committed a transaction to datomic accidentally and I want to "undo" the whole transaction. I know exactly which transaction it is and I can see its datoms, but I don't know how to get from there to a rolled-back transaction.
Asked
Active
Viewed 2,407 times
2 Answers
22
The basic procedure:
- Retrieve the datoms created in the transaction you want to undo. Use the transaction log to find them.
- Remove datoms related to the transaction entity itself: we don't want to retract transaction metadata.
- Invert the "added" state of all remaining datoms, i.e., if a datom was added, retract it, and if it was retracted, add it.
- Reverse the order of the inverted datoms so the bad-new value is retracted before the old-good value is re-asserted.
- Commit a new transaction.
In Clojure, your code would look like this:
(defn rollback
"Reassert retracted datoms and retract asserted datoms in a transaction,
effectively \"undoing\" the transaction.
WARNING: *very* naive function!"
[conn tx]
(let [tx-log (-> conn d/log (d/tx-range tx nil) first) ; find the transaction
txid (-> tx-log :t d/t->tx) ; get the transaction entity id
newdata (->> (:data tx-log) ; get the datoms from the transaction
(remove #(= (:e %) txid)) ; remove transaction-metadata datoms
; invert the datoms add/retract state.
(map #(do [(if (:added %) :db/retract :db/add) (:e %) (:a %) (:v %)]))
reverse)] ; reverse order of inverted datoms.
@(d/transact conn newdata))) ; commit new datoms.

Francis Avila
- 31,233
- 6
- 58
- 96
-
5I want to clarify that point (4) is not necessary - a transaction is not a procedure and the order of the Datoms inside the transaction doesn't matter. There's also more information on dealing with bad data (and why the record of bad data can be useful from a systems reasoning perspective) in this blog post: http://blog.datomic.com/2014/08/stuff-happens-fixing-bad-data-in-datomic.html – Ben Kamphaus Aug 19 '14 at 19:35
-
2It seems so awful. Revert an entire transaction is something very useful, it should be native, easier. – Felipe Sep 27 '14 at 06:23
-
7@FelipeMicaroniLalli Consider: 1) in most db systems, this is impossible 2) there is no one well-understood meaning of "revert" a tx since the tx itself does not cease to exist. It is the same problem as reverting an old git commit: it may require manual merging to make sense. – Francis Avila Sep 27 '14 at 16:58
0
This is not meant as an answer to the original question, but for those coming here from Google looking for inspiration for how to rollback a datascript transaction. I didn't find documentation about it, so I wrote my own:
(defn rollback
"Takes a transaction result and reasserts retracted
datoms and retracts asserted datoms, effectively
\"undoing\" the transaction."
[{:keys [tx-data]}]
; The passed transaction result looks something like
; this:
;
; {:db-before
; {1 :post/body,
; 1 :post/created-at,
; 1 :post/foo,
; 1 :post/id,
; 1 :post/title},
; :db-after {},
; :tx-data
; [#datascript/Datom [1 :post/body "sdffdsdsf" 536870914 false]
; #datascript/Datom [1 :post/created-at 1576538572631 536870914 false]
; #datascript/Datom [1 :post/foo "foo" 536870914 false]
; #datascript/Datom [1 :post/id #uuid "a21ad816-c509-42fe-a1b7-32ad9d3931ef" 536870914 false]
; #datascript/Datom [1 :post/title "123" 536870914 false]],
; :tempids {:db/current-tx 536870914},
; :tx-meta nil}))))
;
; We want to transform each datom into a new piece of
; a transaction. The last field in each datom indicates
; whether it was added (true) or retracted (false). To
; roll back the datom, this boolean needs to be inverted.
(let [t
(map
(fn [[entity-id attribute value _ added?]]
(if added?
[:db/retract entity-id attribute value]
[:db/add entity-id attribute value]))
tx-data)]
(transact t)))
You use it by first capturing a transaction's return value, then passing that return value to the rollback fn:
(let [tx (transact [...])]
(rollback tx))
Be careful though, I'm new to the datascript/Datomic world, so there might be something I am missing.

Dennis Hackethal
- 13,662
- 12
- 66
- 115