(ns metabase.lib.metadata.cached-provider (:require #?@(:clj ([pretty.core :as pretty])) [clojure.set :as set] [metabase.lib.metadata.protocols :as lib.metadata.protocols] [metabase.lib.schema.metadata :as lib.schema.metadata] [metabase.util :as u] [metabase.util.log :as log] [metabase.util.malli :as mu])) | |
#?(:clj (set! *warn-on-reflection* true)) | |
(defn- get-in-cache [cache ks] (when-some [cached-value (get-in @cache ks)] (when-not (= cached-value ::nil) cached-value))) | |
(defn- store-in-cache! [cache ks value] (let [value (if (some? value) value ::nil)] (swap! cache assoc-in ks value) (when-not (= value ::nil) value))) | |
(mu/defn- store-metadata! [cache metadata-type :- ::lib.schema.metadata/type id :- pos-int? metadata :- [:multi {:dispatch :lib/type} [:metadata/database ::lib.schema.metadata/database] [:metadata/table ::lib.schema.metadata/table] [:metadata/column ::lib.schema.metadata/column] [:metadata/card ::lib.schema.metadata/card] [:metadata/metric ::lib.schema.metadata/metric] [:metadata/segment ::lib.schema.metadata/segment]]] (let [metadata (-> metadata (update-keys u/->kebab-case-en) (assoc :lib/type metadata-type))] (store-in-cache! cache [metadata-type id] metadata)) true) | |
(defn- get-in-cache-or-fetch [cache ks fetch-thunk] (if-some [cached-value (get-in @cache ks)] (when-not (= cached-value ::nil) cached-value) (store-in-cache! cache ks (fetch-thunk)))) | |
(defn- database [cache metadata-provider] (get-in-cache-or-fetch cache [:metadata/database] #(lib.metadata.protocols/database metadata-provider))) | |
(defn- metadatas [cache uncached-provider metadata-type ids] (when (seq ids) (log/tracef "Getting %s metadata with IDs %s" metadata-type (pr-str (sort ids))) (let [metadata-cache (get @cache metadata-type)] (when-not (every? #(contains? metadata-cache %) ids) (let [existing-ids (set (keys metadata-cache)) missing-ids (set/difference (set ids) existing-ids)] (log/tracef "Already fetched %s: %s" metadata-type (pr-str (sort (set/intersection (set ids) existing-ids)))) (when (seq missing-ids) (log/tracef "Need to fetch %s: %s" metadata-type (pr-str (sort missing-ids))) (let [fetched-metadatas (lib.metadata.protocols/metadatas uncached-provider metadata-type missing-ids) fetched-ids (map :id fetched-metadatas) unfetched-ids (set/difference (set missing-ids) (set fetched-ids))] (when (seq fetched-ids) (log/tracef "Fetched %s: %s" metadata-type (pr-str (sort fetched-ids))) (doseq [instance fetched-metadatas] (store-in-cache! cache [metadata-type (:id instance)] instance))) (when (seq unfetched-ids) (log/tracef "Failed to fetch %s: %s" metadata-type (pr-str (sort unfetched-ids))) (doseq [unfetched-id unfetched-ids] (store-in-cache! cache [metadata-type unfetched-id] ::nil)))))))) (into [] (keep (fn [id] (get-in-cache cache [metadata-type id]))) ids))) | |
(defn- cached-metadatas [cache metadata-type metadata-ids] (into [] (keep (fn [id] (get-in-cache cache [metadata-type id]))) metadata-ids)) | |
(defn- tables [metadata-provider cache] (let [fetched-tables #(lib.metadata.protocols/tables metadata-provider)] (doseq [table fetched-tables] (store-in-cache! cache [:metadata/table (:id table)] table)) fetched-tables)) | |
(defn- metadatas-for-table [metadata-provider cache metadata-type table-id] (let [k (case metadata-type :metadata/column ::table-fields :metadata/metric ::table-metrics :metadata/segment ::table-segments) thunk (fn [] (let [objects (lib.metadata.protocols/metadatas-for-table metadata-provider metadata-type table-id)] (doseq [metadata objects] (store-in-cache! cache [(:lib/type metadata) (:id metadata)] metadata)) objects))] (get-in-cache-or-fetch cache [k table-id] thunk))) | |
(defn- metadatas-for-card [metadata-provider cache metadata-type card-id] (let [k (case metadata-type :metadata/metric ::table-metrics) thunk (fn [] (let [objects (lib.metadata.protocols/metadatas-for-card metadata-provider metadata-type card-id)] (doseq [metadata objects] (store-in-cache! cache [(:lib/type metadata) (:id metadata)] metadata)) objects))] (get-in-cache-or-fetch cache [k card-id] thunk))) | |
(defn- setting [metadata-provider cache setting-key] (get-in-cache-or-fetch cache [::setting (keyword setting-key)] #(lib.metadata.protocols/setting metadata-provider setting-key))) | |
wraps another metadata provider and caches results. Allows warming the cache before use. | (deftype CachedProxyMetadataProvider [cache metadata-provider] lib.metadata.protocols/MetadataProvider (database [_this] (database cache metadata-provider)) (metadatas [_this metadata-type ids] (metadatas cache metadata-provider metadata-type ids)) (tables [_this] (get-in-cache-or-fetch cache [::database-tables] #(tables metadata-provider cache))) (metadatas-for-table [_this metadata-type table-id] (metadatas-for-table metadata-provider cache metadata-type table-id)) (metadatas-for-card [_this metadata-type card-id] (metadatas-for-card metadata-provider cache metadata-type card-id)) (setting [_this setting-key] (setting metadata-provider cache setting-key)) lib.metadata.protocols/CachedMetadataProvider (cached-metadatas [_this metadata-type metadata-ids] (cached-metadatas cache metadata-type metadata-ids)) (store-metadata! [_this a-metadata] (store-metadata! cache (:lib/type a-metadata) (:id a-metadata) a-metadata)) #?(:clj Object :cljs IEquiv) (#?(:clj equals :cljs -equiv) [_this another] (and (instance? CachedProxyMetadataProvider another) (= metadata-provider (.-metadata-provider ^CachedProxyMetadataProvider another)))) #?@(:clj [pretty/PrettyPrintable (pretty [_this] (list `cached-metadata-provider metadata-provider))])) |
Wrap | (defn cached-metadata-provider ^CachedProxyMetadataProvider [metadata-provider] (->CachedProxyMetadataProvider (atom {}) metadata-provider)) |