(ns metabase.driver.sql.parameters.substitute
(:require
[clojure.string :as str]
[metabase.driver :as driver]
[metabase.driver.common.parameters :as params]
[metabase.driver.sql.parameters.substitution
:as sql.params.substitution]
[metabase.query-processor.error-type :as qp.error-type]
[metabase.util :as u]
[metabase.util.i18n :refer [tru]]
[metabase.util.log :as log])) | |
(defn- substitute-field-filter [[sql args missing] in-optional? k {:keys [_field value], :as v}]
(if (and (= params/no-value value) in-optional?)
;; no-value field filters inside optional clauses are ignored, and eventually emitted entirely
[sql args (conj missing k)]
;; otherwise no values get replaced with `1 = 1` and other values get replaced normally
(let [{:keys [replacement-snippet prepared-statement-args]}
(sql.params.substitution/->replacement-snippet-info driver/*driver* v)]
[(str sql replacement-snippet) (concat args prepared-statement-args) missing]))) | |
(defn- substitute-card-query [[sql args missing] v]
(let [{:keys [replacement-snippet prepared-statement-args]}
(sql.params.substitution/->replacement-snippet-info driver/*driver* v)]
[(str sql replacement-snippet) (concat args prepared-statement-args) missing])) | |
(defn- substitute-native-query-snippet [[sql args missing] v]
(let [{:keys [replacement-snippet]} (sql.params.substitution/->replacement-snippet-info driver/*driver* v)]
[(str sql replacement-snippet) args missing])) | |
(defn- substitute-param [param->value [sql args missing] in-optional? {:keys [k]}]
(if-not (contains? param->value k)
[sql args (conj missing k)]
(let [v (get param->value k)]
(cond
(params/FieldFilter? v)
(substitute-field-filter [sql args missing] in-optional? k v)
(params/ReferencedCardQuery? v)
(substitute-card-query [sql args missing] v)
(params/ReferencedQuerySnippet? v)
(substitute-native-query-snippet [sql args missing] v)
(= params/no-value v)
[sql args (conj missing k)]
:else
(let [{:keys [replacement-snippet prepared-statement-args]}
(sql.params.substitution/->replacement-snippet-info driver/*driver* v)]
[(str sql replacement-snippet) (concat args prepared-statement-args) missing]))))) | |
(declare substitute*) | |
(defn- substitute-optional [param->value [sql args missing] {subclauses :args}]
(let [[opt-sql opt-args opt-missing] (substitute* param->value subclauses true)]
(if (seq opt-missing)
[sql args missing]
[(str sql opt-sql) (concat args opt-args) missing]))) | |
Returns a sequence of | (defn- substitute*
[param->value parsed in-optional?]
(reduce
(fn [[sql args missing] x]
(cond
(string? x)
[(str sql x) args missing]
(params/Param? x)
(substitute-param param->value [sql args missing] in-optional? x)
(params/Optional? x)
(substitute-optional param->value [sql args missing] x)))
nil
parsed)) |
Substitute (substitute ["select * from foobars where birdtype = " (param "birdtype")] {"bird_type" "Steller's Jay"}) ;; -> ["select * from foobars where bird_type = ?" ["Steller's Jay"]] | (defn substitute
[parsed-query param->value]
(log/tracef "Substituting params\n%s\nin query:\n%s" (u/pprint-to-str param->value) (u/pprint-to-str parsed-query))
(let [[sql args missing] (try
(substitute* param->value parsed-query false)
(catch Throwable e
(throw (ex-info (tru "Unable to substitute parameters: {0}" (ex-message e))
{:type (or (:type (ex-data e)) qp.error-type/qp)
:params param->value
:parsed-query parsed-query}
e))))]
(log/tracef "=>%s\n%s" sql (pr-str args))
(when (seq missing)
(throw (ex-info (tru "Cannot run the query: missing required parameters: {0}" (set missing))
{:type qp.error-type/missing-required-parameter
:missing missing})))
[(str/trim sql) args])) |