Skip to content

Instantly share code, notes, and snippets.

@seantempesta
Created May 21, 2017 08:49
Show Gist options
  • Save seantempesta/a501d114b28a0449e83755628cb5e21e to your computer and use it in GitHub Desktop.
Save seantempesta/a501d114b28a0449e83755628cb5e21e to your computer and use it in GitHub Desktop.
(ns datascript-diff
(:require [datascript.core :as d]
[datascript.btset :as btset]))
(defn- keys-eq? [a b]
(or (identical? (.-keys a) (.-keys b))
(let [achunk (btset/iter-chunk a)
bchunk (btset/iter-chunk b)]
(and (== (count achunk) (count bchunk))
(every? #(= (nth achunk %)
(nth bchunk %))
(range (count achunk)))))))
(defn datom-exists? [db datom]
(let [e (.-e datom)
a (.-a datom)
v (.-v datom)]
(not-empty (d/datoms db :eavt e a v))))
(defn add-retract [action datom]
[action (.-e datom) (.-a datom) (.-v datom)])
(defn datoms-diff
"Given two datascript dbs, return the :db/add :db/retract statements to bring them in sync"
([a-db b-db] (datoms-diff a-db b-db (d/datoms a-db :eavt) (d/datoms b-db :eavt) []))
([a-db b-db a b accum]
(if (and (nil? a) (nil? b))
accum
(if (and (some? a) (some? b) (keys-eq? a b))
(recur a-db b-db (btset/iter-chunked-next a) (btset/iter-chunked-next b) accum)
(let [achunk (btset/iter-chunk a)
bchunk (btset/iter-chunk b)
diff (map (fn [x]
(let [a-datom (nth achunk x)
b-datom (nth bchunk x)
same? (= a-datom b-datom)]
(when-not same?
(cond-> []
;; If a exists, but b doesn't, then we need to add a
(and a-datom (nil? b-datom) (not (datom-exists? b-db a-datom)))
(conj (add-retract :db/add a-datom))
;; if b exists, but a doesn't, then we need to add b
(and b-datom (nil? a-datom) (not (datom-exists? a-db b-datom)))
(conj (add-retract :db/add b-datom))
;; if a and b exist, but a isn't in b-db, then issue a retraction
(and a-datom b-datom (not (datom-exists? b-db a-datom)))
(conj (add-retract :db/retract a-datom))
;; if a and b exist, but b isn't in a-db, then issue an addition
(and a-datom b-datom (not (datom-exists? a-db b-datom)))
(conj (add-retract :db/add b-datom))))))
(range 25))
filtered (filter not-empty diff)
merged (mapcat into filtered)]
(recur a-db b-db (btset/iter-chunked-next a) (btset/iter-chunked-next b) (into accum merged)))))))
(defn simple-test []
(let [sample-db (-> (d/empty-db {:first-name {:db/index true}
:last-name {:db/index true}
:phone {:db/index true}
:email {:db/index true}})
(d/db-with [[:db/add 1 :first-name "Sean"]
[:db/add 1 :last-name "Tempesta"]
[:db/add 1 :phone "555-5555"]]))
changes [[:db/add 1 :email "sean.tempesta@gmail.com"]
[:db/retract 1 :last-name "Tempesta"]
[:db/retract 1 :phone "555-5555"]
[:db/add 1 :phone "555-1234"]]
changed-db (d/db-with sample-db changes)]
(= changes (datoms-diff sample-db changed-db))))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment