Skip to content

Instantly share code, notes, and snippets.

@samroberton
Last active February 25, 2021 07:10
Show Gist options
  • Save samroberton/d72cedaf225526d9007a to your computer and use it in GitHub Desktop.
Save samroberton/d72cedaf225526d9007a to your computer and use it in GitHub Desktop.
Database 'unjoin'
(defn unjoin
"Given a seq `rows` as retrieved from a database select involving a join,
return a vector of nested entities, where the top-level entities have keys
`parent-keys`, plus a key for each key of `child-map`, where the value for
that key is a vector of values specified by the value in `child-map`.
Where the value in `child-map` is a simple keyword, the resulting vector will
contain the corresponding values for that keyword in `rows`.
Eg (unjoin [{:id 1, :a :foo} {:id 1, :a :bar}] [:id] {:vals :a}) =>
[{:id 1, :vals [:foo :bar]}]
Where the value in `child-map` is a vector of keywords, the resulting vector
will contain maps, each with the specified keys selected from `rows`.
Eg (unjoin [{:id 1, :a :foo, :b :bar} {:id 1, :a :baz, :b :quux}] [:id]
{:vals [:a :b]}) =>
[{:id 1, :vals [{:a :foo, :b :bar}, {:a :baz, :b :quux}]}]
Where the value in `child-map` is a vector of keywords, but with a map as the
last entry in the vector, `unjoin` will be called recursively, with the keys
in the vector as `parent-keys`, and with the map as `child-map`, resulting in
a second level of nesting with the result."
[rows parent-keys child-map]
(mapv (fn [child-rows]
(into (select-keys (first child-rows) parent-keys)
(for [[k child-action] child-map]
(let [val (cond (and (vector? child-action)
(map? (last child-action)))
;; [:a :b {:c :foo}] => recursively unjoin
(filter #(some (fn [x] (if (sequential? x)
(not-empty x)
x))
(vals %))
(unjoin child-rows
(vec (butlast child-action))
(last child-action)))
(vector? child-action)
;; vector of keys to select
(filter #(some identity (vals %))
(map #(select-keys % child-action) child-rows))
(ifn? child-action)
(filter identity
(map child-action child-rows)))]
[k (vec val)]))))
(partition-by #(select-keys % parent-keys) rows)))
(deftest test-unjoin
(is (= [{:id 1, :a :b, :c :d, :e-vec [:f :g :h]}]
(unjoin [{:id 1, :a :b, :c :d, :e :f}
{:id 1, :a :b, :c :d, :e :g}
{:id 1, :a :b, :c :d, :e :h}]
[:id :a :c]
{:e-vec :e})))
(is (= [{:id 1, :a :b, :c :d, :e-vec [:f :g]}
{:id 2, :a :b, :c :d, :e-vec [:h]}]
(unjoin [{:id 1, :a :b, :c :d, :e :f}
{:id 1, :a :b, :c :d, :e :g}
{:id 2, :a :b, :c :d, :e :h}]
[:id :a :c]
{:e-vec :e})))
(is (= [{:id 1, :a :b, :coll [{:c :foo, :d :bar}
{:c :baz, :d :quux}]}]
(unjoin [{:id 1, :a :b, :c :foo, :d :bar}
{:id 1, :a :b, :c :baz, :d :quux}]
[:id :a]
{:coll [:c :d]})))
(is (= [{:id 1, :a :b, :coll-1 [{:c :d, :x 1, :coll-2 [:foo :bar :baz]}
{:c :e, :x 2, :coll-2 [:quux :blort]}]}
{:id 2, :a :b, :coll-1 []}]
(unjoin [{:id 1, :a :b, :c :d, :x 1, :val :foo}
{:id 1, :a :b, :c :d, :x 1, :val :bar}
{:id 1, :a :b, :c :d, :x 1, :val :baz}
{:id 1, :a :b, :c :e, :x 2, :val :quux}
{:id 1, :a :b, :c :e, :x 2, :val :blort}
{:id 2, :a :b, :c nil, :x nil, :val nil}]
[:id :a]
{:coll-1 [:c :x {:coll-2 :val}]}))))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment