Created
April 11, 2020 03:52
-
-
Save mdhaney/8c42dcc63e3ff726e87c3dd8cf4ca4ab to your computer and use it in GitHub Desktop.
pull-many implementation for Datomic Cloud
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(defn pull-many | |
"like Datomic pull, but takes a collection of ids and returns | |
a collection of entity maps" | |
([db pull-spec ids] | |
;; IMPLEMENTATION NOTE | |
;; | |
;; We use a Datomic query with a collection binding on the input to simulate pull-many. | |
;; This performs well even in distributed/development mode, as it's only 1 request over | |
;; the wire. If that was all there was to it, this function would be like 3 lines long, | |
;; but unfortunately it's a bit more difficult because the collection binding doesn't | |
;; guarantee order, so the results may not be in the same order as the inputs. | |
;; | |
;; To fix this we have to do some additional processing. First, we assume that the list of | |
;; input ids is homogenous, specifically all the ids are either entity ids OR lookup refs. | |
;; So if the input list is a mixture of eids and lookup refs, this won't work (although | |
;; I'm sure it's possible to do by extending the approach below, it just didn't seem worth | |
;; the effort). | |
;; | |
;; So given our list of homogenous ids, we first build a map that keeps track of their order. | |
;; Then we make sure that either the lookup ref (e.g. :account/id, etc.) or :db/id are added | |
;; to the pull-spec if not already there (and ONLY if not already there, because Datomic | |
;; will error if you have duplicate keywords in the spec). This make sure that returned maps | |
;; will have the data we need to match them against the inputs. Then we use the order map we | |
;; created earlier to sort the result list and voila! | |
;; | |
;; One thing we don't do is remove the :db/id or lookup-ref we added to get the extra data. | |
;; That could certainly be done, but we already brought the data over the wire (which is the | |
;; most expensive part of the operation) and pathom will just throw away the extra data if the | |
;; client didn't request it, so no harm in leaving it there and save the time of another iteration | |
;; through the list. | |
;; | |
;; If you really need the data removed for some reason, it's not hard - after sorting, check if | |
;; `missing-ref?` is true and if so then map over the list and dissoc `sort-kw` from each record | |
;; | |
(let [lookup-ref? (vector? (first ids)) | |
order-map (if lookup-ref? | |
(into {} (map vector (map second ids) (range))) | |
(into {} (map vector ids (range)))) | |
sort-kw (if lookup-ref? (ffirst ids) :db/id) | |
missing-ref? (nil? (seq (filter #(= sort-kw %) pull-spec))) | |
pull-spec (if missing-ref? | |
(conj pull-spec sort-kw) | |
pull-spec)] | |
(->> pull-spec | |
(q '[:find (pull ?id pattern) | |
:in $ [?id ...] pattern] | |
db | |
ids) | |
(map first) | |
(sort-by #(order-map (get % sort-kw))) | |
vec)))) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment