(ns metabase.types.coercion-hierarchies (:require [clojure.set :as set])) | |
these need to be defonce so we don't drop our hierarchies, but defonce doesn't support docstrings: https://clojure.atlassian.net/browse/CLJ-1148 | |
Map of | (defonce ^:private
strategy->allowed-base-types
(atom {})) |
Map of coercion strategy -> resulting effective-type | (defonce ^:private
strategy->effective-type
(atom {})) |
Map of base-type -> #{strategy} which are not inheritable. Eg, binary fields are marked | (defonce ^:private
non-descending-base-type->strategy
(atom {})) |
Get a map of strategies -> allowed-base-types. These must live outside of the hierarchy. | (defn non-descending-strategies [] @non-descending-base-type->strategy) |
Gets the effective type for strategy. Essentially a getter over the private strategy->effective-type. | (defn effective-type-for-strategy [strategy] (get @strategy->effective-type strategy)) |
Ensure x is a sequential collection. Copied from metabase.util as that namespace is not amenable to cljc. | (defn- one-or-many [x] (if ((some-fn sequential? set? nil?) x) x [x])) |
Define the | (defn define-types!
[coercion-strategy base-type-or-types effective-type]
(let [base-types (set (one-or-many base-type-or-types))]
(swap! strategy->allowed-base-types assoc coercion-strategy base-types))
(swap! strategy->effective-type assoc coercion-strategy effective-type)) |
Define coercion strategies that should not exist for all of the descendants of base-type-or-types. | (defn define-non-inheritable-type!
[coercion-strategy base-type-or-types effective-type]
(swap! non-descending-base-type->strategy
(partial merge-with set/union)
(zipmap (one-or-many base-type-or-types) (repeat #{coercion-strategy})))
(swap! strategy->effective-type assoc coercion-strategy effective-type)) |
(defn- build-hierarchy [pairs]
(reduce
(fn [h [tag parent]]
(derive h tag parent))
#?(:clj @#'clojure.core/global-hierarchy
:cljs @(#'clojure.core/get-global-hierarchy))
pairs)) | |
atom is nil => rebuild the hierarchy | |
(def ^:private base-type-hierarchy* (atom nil)) | |
The global hierarchy, with coercion strategies added as ancestors of their allowed base type(s). | (defn base-type-hierarchy
[]
(when-not @base-type-hierarchy*
(locking base-type-hierarchy*
(when-not @base-type-hierarchy*
(reset! base-type-hierarchy* (build-hierarchy (for [[strategy base-types] @strategy->allowed-base-types
base-type base-types]
[base-type strategy]))))))
@base-type-hierarchy*) |
(def ^:private effective-type-hierarchy* (atom nil)) | |
The global hierarchy, with coercion strategies added as children of their resulting effective type. | (defn effective-type-hierarchy
[]
(when-not @effective-type-hierarchy*
(locking effective-type-hierarchy*
(when-not @effective-type-hierarchy*
(reset! effective-type-hierarchy* (build-hierarchy (seq @strategy->effective-type))))))
@effective-type-hierarchy*) |
rebuild coercion hierarchies if the global hierarchy changes | (add-watch
#?(:clj #'clojure.core/global-hierarchy
:cljs (#'clojure.core/get-global-hierarchy))
::rebuild-hierarchies
(fn [_key _ref old new]
(when-not (= old new)
(reset! base-type-hierarchy* nil)
(reset! effective-type-hierarchy* nil)))) |
rebuild coercion hierarchies if the type map atoms change | |
(add-watch
strategy->allowed-base-types
::rebuild-hierarchies
(fn [_key _ref old new]
(when-not (= old new)
(reset! base-type-hierarchy* nil)))) | |
(add-watch
strategy->effective-type
::rebuild-hierarchies
(fn [_key _ref old new]
(when-not (= old new)
(reset! effective-type-hierarchy* nil)))) | |