Middleware for substituting parameters in queries. | (ns metabase.query-processor.middleware.parameters (:require [clojure.data :as data] [clojure.set :as set] [medley.core :as m] [metabase.legacy-mbql.normalize :as mbql.normalize] [metabase.legacy-mbql.schema :as mbql.s] [metabase.legacy-mbql.util :as mbql.u] [metabase.lib.util.match :as lib.util.match] [metabase.query-processor.middleware.parameters.mbql :as qp.mbql] [metabase.query-processor.middleware.parameters.native :as qp.native] [metabase.util :as u] [metabase.util.log :as log] [metabase.util.malli :as mu])) |
(defn- join? [m] (:condition m)) | |
(defn- nested-native? [m] (and (:source-query m) (:native (:source-query m)))) | |
(mu/defn- move-join-condition-to-source-query :- mbql.s/Join "Joins aren't allowed to have `:filter` clauses, generated by the `expand-mbql-params` function below. Move the filter clause into the `:source-query`, converting `:source-table` to a source query if needed." [{:keys [source-table], filter-clause :filter, :as join}] (if-not filter-clause join (if source-table (-> (assoc join :source-query {:source-table source-table, :filter filter-clause}) (dissoc :source-table :filter)) ;; putting parameters in a join that has a `:source-query` is a little wacky (just add them to `:parameters` in ;; the source query itself), but we'll allow it for now (-> (update-in join [:source-query :filter] mbql.u/combine-filter-clauses filter-clause) (dissoc :filter))))) | |
(mu/defn- move-native-filter-to-parent-stage :- mbql.s/MBQLQuery "Native stages aren't allowed to have `:filter` clauses, generated by the `expand-mbql-params` function below. Move the filter clause into the parent stage, if needed." [{{native-query :native, filter-clause :filter} :source-query, :as parent-stage}] (if-not (and native-query filter-clause) parent-stage (-> parent-stage (update :source-query dissoc :filter) (update :filter mbql.u/combine-filter-clauses filter-clause)))) | |
(defn- expand-mbql-params [outer-query {:keys [parameters], :as m}] ;; HACK `qp.mbql/expand` assumes it's operating on an outer query so wrap `m` to look like an outer query. TODO ;; - fix `qp.mbql` to operate on abitrary maps instead of only on top-level queries. (let [wrapped (assoc outer-query :query m) {expanded :query} (qp.mbql/expand (dissoc wrapped :parameters) parameters)] (cond-> expanded (join? m) move-join-condition-to-source-query (nested-native? m) move-native-filter-to-parent-stage))) | |
Expand | (defn- expand-one [outer-query {:keys [source-table source-query parameters], :as m}] ;; HACK - normalization does not yet operate on `:parameters` that aren't at the top level, so double-check that ;; they're normalized properly before proceeding. (let [m (cond-> m (seq parameters) (update :parameters (partial mbql.normalize/normalize-fragment [:parameters]))) expanded (if (or source-table source-query) (expand-mbql-params outer-query m) (qp.native/expand-inner m))] (dissoc expanded :parameters :template-tags))) |
Expand all | (defn- expand-all ([outer-query] (expand-all outer-query outer-query)) ([outer-query m] (lib.util.match/replace m (_ :guard (every-pred map? (some-fn :parameters :template-tags))) (let [expanded (expand-one outer-query &match)] ;; now recursively expand any remaining maps that contain `:parameters` (expand-all outer-query expanded))))) |
Move any top-level parameters to the same level (i.e., 'inner query') as the query they affect. | (mu/defn- move-top-level-params-to-inner-query [{:keys [info parameters], query-type :type, :as outer-query} :- [:map [:type [:enum :query :native]]]] (cond-> (set/rename-keys outer-query {:parameters :user-parameters}) ;; TODO: Native models should be within scope of dashboard filters, by applying the filter on an outer stage. ;; That doesn't work, so the logic below requires MBQL queries only to fix the regression. ;; Native models don't actual get filtered even when linked to dashboard filters, but that's not a regression. ;; This can be fixed properly once this middleware is powered by MLv2. See #40011. (and (seq parameters) (:metadata/model-metadata info) (= query-type :query)) (update query-type (fn [inner-query] {:source-query inner-query})) (seq parameters) (assoc-in [query-type :parameters] parameters))) |
Expand parameters in the | (defn- expand-parameters [outer-query] (let [pivot-original-query (get-in outer-query [:info :pivot/original-query])] (cond-> outer-query pivot-original-query (m/dissoc-in [:info :pivot/original-query]) true move-top-level-params-to-inner-query true expand-all pivot-original-query (assoc-in [:info :pivot/original-query] pivot-original-query)))) |
(mu/defn- substitute-parameters* :- :map "If any parameters were supplied then substitute them into the query." [query] (u/prog1 (expand-parameters query) (when (not= <> query) (when-let [diff (second (data/diff query <>))] (log/tracef "\n\nSubstituted params:\n%s\n" (u/pprint-to-str 'cyan diff)))))) | |
(defn- assoc-db-in-snippet-tag [db template-tags] (update-vals template-tags (fn [v] (cond-> v (= (:type v) :snippet) (assoc :database db))))) | |
Assocs the | (defn- hoist-database-for-snippet-tags [query] (u/update-in-if-exists query [:native :template-tags] (partial assoc-db-in-snippet-tag (:database query)))) |
Substitute Dashboard or Card-supplied parameters in a query, replacing the param placeholers with appropriate values
and/or modifiying the query as appropriate. This looks for maps that have the key A SQL query with a param like | (defn substitute-parameters [query] (-> query hoist-database-for-snippet-tags substitute-parameters*)) |