The Query Processor Store caches resolved Tables and Fields for the duration of a query execution. Certain middleware handles resolving things like the query's source Table and any Fields that are referenced in a query, and saves the referenced objects in the store; other middleware and driver-specific query processor implementations use functions in the store to fetch those objects as needed. For example, a driver might be converting a Field ID clause (e.g. (qp.store/field 10) ;; get Field 10 Of course, it would be entirely possible to call | (ns metabase.query-processor.store (:require [medley.core :as m] [metabase.lib.core :as lib] [metabase.lib.metadata :as lib.metadata] [metabase.lib.metadata.jvm :as lib.metadata.jvm] [metabase.lib.schema.id :as lib.schema.id] [metabase.lib.schema.metadata :as lib.schema.metadata] [metabase.query-processor.error-type :as qp.error-type] [metabase.util :as u] [metabase.util.i18n :refer [tru]] [metabase.util.malli :as mu] [metabase.util.malli.registry :as mr])) |
(set! *warn-on-reflection* true) | |
(def ^:private uninitialized-store (reify clojure.lang.IDeref (deref [_this] (throw (ex-info "Error: Query Processor store is not initialized. Initialize it with qp.store/with-metadata-provider" {}))))) | |
Dynamic var used as the QP store for a given query execution. | (def ^:private ^:dynamic *store* uninitialized-store) |
This is only for tests! When enabled, [[with-metadata-provider]] can completely replace the current metadata provider (and cache) with a new one. This is reset to false after the QP store is replaced the first time. | (def ^:dynamic *TESTS-ONLY-allow-replacing-metadata-provider* false) |
Is the QP store currently initialized? TODO -- rename this to something like | (defn initialized? [] (not (identical? *store* uninitialized-store))) |
Store a miscellaneous value in a the cache. Persists for the life of this QP invocation, including for recursive calls. | (mu/defn store-miscellaneous-value! [ks :- [:sequential :any] v] (swap! *store* assoc-in ks v)) |
Fetch a miscellaneous value from the cache. Unlike other Store functions, does not throw if value is not found. | (mu/defn miscellaneous-value ([ks] (miscellaneous-value ks nil)) ([ks :- [:sequential :any] not-found] (get-in @*store* ks not-found))) |
Attempt to fetch a miscellaneous value from the cache using key sequence See also | (defn cached-fn [ks thunk] (let [cached-value (miscellaneous-value ks ::not-found)] (if-not (= cached-value ::not-found) cached-value (let [v (thunk)] (store-miscellaneous-value! ks v) v)))) |
Cache the value of Note that each use of ;; cache lookups of Card.dataset_query (qp.store/cached card-id (t2/select-one-fn :dataset_query Card :id card-id)) | (defmacro cached {:style/indent 1} [k-or-ks & body] ;; for the unique key use a gensym prefixed by the namespace to make for easier store debugging if needed (let [ks (into [(list 'quote (gensym (str (name (ns-name *ns*)) "/misc-cache-")))] (u/one-or-many k-or-ks))] `(cached-fn ~ks (fn [] ~@body)))) |
(mu/defn metadata-provider :- ::lib.schema.metadata/metadata-provider "Get the [[metabase.lib.metadata.protocols/MetadataProvider]] that should be used inside the QP. " [] (or (miscellaneous-value [::metadata-provider]) (throw (ex-info "QP Store Metadata Provider is not initialized yet; initialize it with `qp.store/with-metadata-provider`." {})))) | |
(mr/def ::database-id-or-metadata-providerable [:or ::lib.schema.id/database ::lib.schema.metadata/metadata-providerable]) | |
(mu/defn- ->metadata-provider :- ::lib.schema.metadata/metadata-provider [database-id-or-metadata-providerable :- ::database-id-or-metadata-providerable] (if (integer? database-id-or-metadata-providerable) (lib.metadata.jvm/application-database-metadata-provider database-id-or-metadata-providerable) database-id-or-metadata-providerable)) | |
Impl for [[with-metadata-provider]]; if there's already a provider, just make sure we're not trying to change the Database. We don't need to replace it. | (mu/defn- validate-existing-provider [database-id-or-metadata-providerable :- ::database-id-or-metadata-providerable] (let [old-provider (miscellaneous-value [::metadata-provider])] (when-not (identical? old-provider database-id-or-metadata-providerable) (let [new-database-id (if (integer? database-id-or-metadata-providerable) database-id-or-metadata-providerable (throw (ex-info "Cannot replace MetadataProvider with another one after it has been bound" {:old-provider old-provider, :new-provider database-id-or-metadata-providerable}))) existing-database-id (u/the-id (lib.metadata/database old-provider))] (when-not (= new-database-id existing-database-id) (throw (ex-info (tru "Attempting to initialize metadata provider with new Database {0}. Queries can only reference one Database. Already referencing: {1}" (pr-str new-database-id) (pr-str existing-database-id)) {:existing-id existing-database-id :new-id new-database-id :type qp.error-type/invalid-query}))))))) |
Create a new metadata provider and save it. | (mu/defn- set-metadata-provider! [database-id-or-metadata-providerable :- ::database-id-or-metadata-providerable] (let [new-provider (->metadata-provider database-id-or-metadata-providerable)] ;; validate the new provider. (try (lib.metadata/database new-provider) (catch Throwable e (throw (ex-info (format "Invalid MetadataProvider, failed to return valid Database: %s" (ex-message e)) {:metadata-provider new-provider} e)))) (store-miscellaneous-value! [::metadata-provider] new-provider))) |
Implementation for [[with-metadata-provider]]. | (mu/defn do-with-metadata-provider [database-id-or-metadata-providerable :- ::database-id-or-metadata-providerable thunk :- [:=> [:cat] :any]] (cond (or (not (initialized?)) *TESTS-ONLY-allow-replacing-metadata-provider*) (binding [*store* (atom {}) *TESTS-ONLY-allow-replacing-metadata-provider* false] (do-with-metadata-provider database-id-or-metadata-providerable thunk)) ;; existing provider (miscellaneous-value [::metadata-provider]) (do (validate-existing-provider database-id-or-metadata-providerable) (thunk)) :else (do (set-metadata-provider! database-id-or-metadata-providerable) (thunk)))) |
Execute If a MetadataProvider is already bound, this is a no-op. | (defmacro with-metadata-provider {:style/indent [:defn]} [database-id-or-metadata-providerable & body] `(do-with-metadata-provider ~database-id-or-metadata-providerable (^:once fn* [] ~@body))) |
DEPRECATED STUFF | |
For compatibility: convert MLv2-style metadata as returned by [[metabase.lib.metadata.protocols]]
or [[metabase.lib.metadata]] functions
(with Try to avoid using this, we would like to remove this in the near future. | (defn ->legacy-metadata {:deprecated "0.48.0"} [mlv2-metadata] (let [model (case (:lib/type mlv2-metadata) :metadata/database :model/Database :metadata/table :model/Table :metadata/column :model/Field)] (-> mlv2-metadata (dissoc :lib/type) (update-keys u/->snake_case_en) (vary-meta assoc :type model) ;; TODO: This is converting a freestanding field ref into legacy form. Then again, the `:field_ref` on MLv2 ;; metadata is already in legacy form. (m/update-existing :field_ref lib/->legacy-MBQL)))) |