Skip to content

Instantly share code, notes, and snippets.

@mdhaney
Created April 11, 2020 03:52
Show Gist options
  • Save mdhaney/8c42dcc63e3ff726e87c3dd8cf4ca4ab to your computer and use it in GitHub Desktop.
Save mdhaney/8c42dcc63e3ff726e87c3dd8cf4ca4ab to your computer and use it in GitHub Desktop.
pull-many implementation for Datomic Cloud
(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