Skip to content

Instantly share code, notes, and snippets.

@danielneal
Created September 22, 2017 16:29
Show Gist options
  • Save danielneal/b632d310d4b04cba6f9932511e0f1155 to your computer and use it in GitHub Desktop.
Save danielneal/b632d310d4b04cba6f9932511e0f1155 to your computer and use it in GitHub Desktop.
(ns compound.indexes
(:require [clojure.set :as set]))
(defprotocol IIndexPrimary
(-get-primary [this ks])
(-add [this items])
(-remove [this ks]))
(defprotocol IIndexSecondary
(-get-secondary [this ks])
(-on-add [this added])
(-on-remove [this removed]))
(defmulti make-index
(fn [index-def] (get index-def :compound.index/type)))
;; ----------------- Primary Indexes
(defrecord IndexPrimary [index index-def])
(extend-type IndexPrimary
IIndexPrimary
(-get-primary [this ks] (let [{:keys [index]} this]
(into #{} (comp (map #(get index %))
(remove nil?)) ks)))
(-add [this items] (let [{:keys [index index-def]} this
{:keys [:compound.index/key-fn]} index-def
[new-index added] (reduce (fn add-items [[index added] item]
(let [k (key-fn item)]
[(assoc! index k item)
(assoc! added k item)]))
[(transient index) (transient {})]
items)]
[(->IndexPrimary (persistent! new-index) index-def) (persistent! added)]))
(-remove [this ks] (let [{:keys [index index-def]} this
[new-index removed] (reduce (fn remove-items [[index removed] k]
(let [item (get index k)]
[(dissoc! index k)
(assoc! removed k item)]))
[(transient index) (transient {})]
ks)]
[(->IndexPrimary (persistent! new-index) index-def) (persistent! removed)])))
(defmethod make-index :compound.index.types/primary
[index-def]
(->IndexPrimary {} index-def))
;; ----------------- Unique Secondary Index
(defrecord IndexUnique [index index-def])
(extend-type IndexUnique
IIndexSecondary
(-get-secondary [this ks] (let [{:keys [index]} this]
(into #{} (comp (map #(get index %))
(remove nil?)) ks)))
(-on-add [this added] (let [{:keys [index index-def]} this
{:keys [:compound.index/key-fn]} index-def
new-index (reduce (fn add-items [index [pk item]]
(let [k (key-fn item)]
(assoc! index k pk)))
(transient index)
added)]
(->IndexUnique (persistent! new-index) index-def)))
(-on-remove [this removed] (let [{:keys [index index-def]} this
new-index (reduce (fn remove-items [index [pk item]]
(dissoc! index pk))
(transient index)
removed)]
(->IndexUnique (persistent! new-index) index-def))))
(defmethod make-index :compound.index.types/unique
[index-def]
(->IndexUnique {} index-def))
;; ----------------- Non unique multi secondary index
(defrecord IndexMulti [index index-def])
(extend-type IndexMulti
IIndexSecondary
(-get-secondary [this ks] (let [{:keys [index]} this]
(into #{} (comp (mapcat #(get index %))
(remove nil?)) ks)))
(-on-add [this added] (let [{:keys [index index-def]} this
{:keys [:compound.index/key-fn]} index-def
new-index (reduce (fn add-items [index [pk item]]
(let [k (key-fn item)
pks (get index k #{})]
(assoc! index k (conj pks pk))))
(transient index)
added)]
(->IndexUnique (persistent! new-index) index-def)))
(-on-remove [this removed] (let [{:keys [index index-def]} this
{:keys [:compound.index/key-fn]} index-def
new-index (reduce (fn remove-items [index [pk item]]
(let [k (key-fn item)
pks (get index k #{})]
(assoc! index k (disj! pks pk))))
(transient index)
removed)]
(->IndexUnique (persistent! new-index) index-def))))
(defmethod make-index :compound.index.types/multi
[index-def]
(->IndexMulti {} index-def))
(ns compound.core
(:require [compound.indexes :as ci]))
(defprotocol ICompound
(-get [c index-id k])
(-add [c items])
(-remove [c index-id ks]))
(defrecord Compound [indexes-by-id primary-index-id])
;; Note: don't use transients here because index-defs will be small maps
;; and normal reduce-kv this works better
;; see https://gist.github.com/nathanmarz/bf571c9ed86bfad09816e17b9b6e59e3
(extend-type Compound
ICompound
(-add [c items] (let [{:keys [indexes-by-id primary-index-id]} c
primary-index (get indexes-by-id primary-index-id)
secondary-indexes (dissoc indexes-by-id primary-index-id)]
(let [[new-primary-index added] (ci/-add primary-index items)
new-secondary-indexes (reduce-kv (fn [m k v]
(assoc m k (ci/-on-add v added)))
{} secondary-indexes)]
(->Compound (assoc new-secondary-indexes primary-index-id new-primary-index) primary-index-id))))
(-remove [c index-id ks] (let [{:keys [indexes-by-id primary-index-id]} c
primary-index (get indexes-by-id primary-index-id)
secondary-indexes (dissoc indexes-by-id primary-index-id)
pks (if (= primary-index-id index-id)
ks
(-get c index-id ks))
[new-primary-index removed] (ci/-remove primary-index ks)
new-secondary-indexes (reduce-kv (fn [m k v]
(assoc m k (ci/-on-remove v removed)))
{} secondary-indexes)]
(->Compound (assoc new-secondary-indexes primary-index-id new-primary-index) primary-index-id)))
(-get [c index-id ks] (let [{:keys [indexes-by-id primary-index-id]} c
primary-index (get indexes-by-id primary-index-id)]
(if (= primary-index-id index-id)
(ci/-get-primary primary-index ks)
(let [secondary-index (get indexes-by-id index-id)
pks (ci/-get-secondary secondary-index ks)]
(println ks)
(println pks)
(into #{} (remove nil?) (ci/-get-primary primary-index pks)))))))
;; public api
(defn compound
"Creates a new compound with the given index definitions"
[index-defs]
(let [[primary-index-def & more] (filter #(= (:compound.index/type %) :compound.index.types/primary) index-defs)
{primary-index-id :compound.index/id} primary-index-def]
(assert (nil? more) "Compound must not define more than one primary index")
(assert (not (nil? primary-index-def)) "Compound must define a primary index")
(let [indexes-by-id (reduce (fn [m index-def]
(let [{:keys [:compound.index/id]} index-def]
(assoc m id (ci/make-index index-def))))
{}
index-defs)]
(->Compound indexes-by-id primary-index-id))))
(defn get-one [compound index k]
(first (-get compound index [k])))
(defn get-many [compound index ks]
(-get compound index ks))
(defn add-items [compound items]
(-add compound items))
(defn remove-by-key [compound index ks]
(-remove compound index ks))
(defn update-by-key [compound index k f]
(let [changed-items (map f (get-many compound index [k]))]
(-> (remove-by-key compound index k)
(add-items changed-items))))
(-> (compound [#:compound.index{:key-fn :a
:type :compound.index.types/primary
:id :a}
#:compound.index{:key-fn :b
:type :compound.index.types/unique
:id :b}
#:compound.index{:key-fn :c
:type :compound.index.types/multi
:id :c}
#:compound.index{:key-fn (juxt :a :b)
:type :compound.index.types/multi
:id :compound-index}])
(add-items [{:a 1 :b 2}
{:a 2 :b 3 :c 4}
{:a 3 :b 4 :c 5}
{:a 4 :b 5 :c 6}
{:a 5 :b 6 :c 5}])
#_(-get :c [4]))
(-> (compound [#:compound.index{:key-fn :a
:type :compound.index.types/primary
:id :a}
#:compound.index{:key-fn :b
:type :compound.index.types/unique
:id :b}
#:compound.index{:key-fn :c
:type :compound.index.types/multi
:id :c}
#:compound.index{:key-fn (juxt :a :b)
:type :compound.index.types/multi
:id :compound-index}])
(add-items [{:a 1 :b 2}
{:a 2 :b 3 :c 4}
{:a 3 :b 4 :c 5}
{:a 4 :b 5 :c 6}
{:a 5 :b 6 :c 5}])
(update-by-key :b 4 #(assoc :c 7))
(remove-by-key :a [1 2 3 ])
(get-one :c 6))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment