(ns metabase.query-processor.middleware.fix-bad-references (:require [clojure.walk :as walk] [metabase.lib.metadata :as lib.metadata] [metabase.lib.util.match :as lib.util.match] [metabase.query-processor.store :as qp.store] [metabase.util :as u] [metabase.util.log :as log])) | |
(defn- find-source-table [{:keys [source-table source-query]}] (or source-table (when source-query (recur source-query)))) | |
(defn- find-join-against-table [{:keys [joins source-query]} table-id] (or (when source-query (find-join-against-table source-query table-id)) (some (fn [join] (when (= (find-source-table join) table-id) join)) joins))) | |
(defn- table [table-id] (when table-id (lib.metadata/table (qp.store/metadata-provider) table-id))) | |
A function to be called on each bad field found by this middleware. Not used except for in tests. | (def ^:dynamic *bad-field-reference-fn* (constantly nil)) |
(defn- fix-bad-references* ([inner-query] (fix-bad-references* inner-query inner-query (find-source-table inner-query))) ([inner-query form source-table & sources] (lib.util.match/replace form ;; don't replace anything inside source metadata. (_ :guard (constantly ((set &parents) :source-metadata))) &match ;; if we have entered a join map and don't have `join-source` info yet, determine that and recurse. (m :guard (every-pred map? :condition (fn [join] (let [join-source (find-source-table join)] (not (contains? (set sources) join-source)))))) (apply fix-bad-references* inner-query m source-table (cons (find-source-table m) sources)) ;; find Field ID fields whose Table IS NOT the source table (or not directly available in some `[:source-query+ ;; :source-table]` path that do not have `:join-alias` info [:field (id :guard (every-pred integer? (fn [id] (let [{:keys [table-id]} (lib.metadata/field (qp.store/metadata-provider) id)] (not (some (partial = table-id) (cons source-table sources))))))) (opts :guard (complement :join-alias))] (let [{:keys [table-id], :as field} (lib.metadata/field (qp.store/metadata-provider) id) {join-alias :alias} (find-join-against-table inner-query table-id)] (log/warn (u/colorize :yellow (str (format "Bad :field clause %s for field %s at %s: clause should have a :join-alias." (pr-str &match) (pr-str (format "%s.%s" (:name (table table-id)) (:name field))) (pr-str &parents)) " " (if join-alias (format "Guessing join %s" (pr-str join-alias)) "Unable to infer an appropriate join. Query may not work as expected.")))) (*bad-field-reference-fn* &match) (if join-alias [:field id (assoc opts :join-alias join-alias)] &match))))) | |
Walk This middleware performs a best-effort DWIM transformation, and isn't smart enough to fix every broken query out there. If the query cannot be fixed, this log a warning and move on. See #19612 for more information. | (defn fix-bad-references [query] (walk/postwalk (fn [form] (if (and (map? form) ((some-fn :source-query :source-table) form) (not (:condition form))) (fix-bad-references* form) form)) query)) |