Created
September 22, 2017 16:29
-
-
Save danielneal/b632d310d4b04cba6f9932511e0f1155 to your computer and use it in GitHub Desktop.
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
(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