(ns metabase.query-processor.middleware.add-source-metadata (:require [clojure.walk :as walk] [metabase.legacy-mbql.schema :as mbql.s] [metabase.lib.metadata :as lib.metadata] [metabase.lib.util.match :as lib.util.match] [metabase.query-processor.interface :as qp.i] [metabase.query-processor.store :as qp.store] [metabase.request.core :as request] [metabase.util.log :as log] [metabase.util.malli :as mu])) | |
Whether this source query itself has a nested source query, and will have the exact same fields in the results as its
nested source. If this is the case, we can return the | (defn- has-same-fields-as-nested-source? [{nested-source-query :source-query nested-source-metadata :source-metadata breakouts :breakout aggregations :aggregation fields :fields}] (when nested-source-query (and (every? empty? [breakouts aggregations]) (or (empty? fields) (and (= (count fields) (count nested-source-metadata)) (every? #(lib.util.match/match-one % [:field (_ :guard string?) _]) fields)))))) |
(mu/defn- native-source-query->metadata :- [:maybe [:sequential mbql.s/SourceQueryMetadata]] "Given a `source-query`, return the source metadata that should be added at the parent level (i.e., at the same level where this `source-query` was present.) This metadata is used by other middleware to determine what Fields to expect from the source query." [{nested-source-metadata :source-metadata, :as source-query} :- mbql.s/SourceQuery] ;; If the source query has a nested source with metadata and does not change the fields that come back, return ;; metadata as-is (if (has-same-fields-as-nested-source? source-query) nested-source-metadata ;; Otherwise we cannot determine the metadata automatically; usually, this is because the source query itself has ;; a native source query (do (when-not qp.i/*disable-qp-logging* (log/warn "Cannot infer `:source-metadata` for source query with native source query without source metadata." {:source-query source-query})) nil))) | |
(mu/defn mbql-source-query->metadata :- [:maybe [:sequential mbql.s/SourceQueryMetadata]] "Preprocess a `source-query` so we can determine the result columns." [source-query :- mbql.s/MBQLQuery] (try (let [cols (request/as-admin ((requiring-resolve 'metabase.query-processor.preprocess/query->expected-cols) {:database (:id (lib.metadata/database (qp.store/metadata-provider))) :type :query ;; don't add remapped columns to the source metadata for the source query, otherwise we're going ;; to end up adding it again when the middleware runs at the top level :query (assoc-in source-query [:middleware :disable-remaps?] true)}))] (for [col cols :when (not (:remapped_from col))] (select-keys col [:name :id :table_id :display_name :base_type :effective_type :coercion_strategy :semantic_type :unit :fingerprint :settings :source_alias :field_ref :nfc_path :parent_id]))) (catch Throwable e (log/errorf e "Error determining expected columns for query: %s" (ex-message e)) nil))) | |
(mu/defn- add-source-metadata :- [:map [:source-metadata {:optional true} [:maybe [:sequential mbql.s/SourceQueryMetadata]]]] [{{native-source-query? :native, :as source-query} :source-query, :as inner-query} :- :map] (let [metadata ((if native-source-query? native-source-query->metadata mbql-source-query->metadata) source-query)] (cond-> inner-query (seq metadata) (assoc :source-metadata metadata)))) | |
Whether this source metadata is legacy source metadata from < 0.38.0. Legacy source metadata did not include
| (defn- legacy-source-metadata? [source-metadata] (and (seq source-metadata) (every? nil? (map :field_ref source-metadata)))) |
Should we add
| (defn- should-add-source-metadata? [{{native-source-query? :native source-query-has-source-metadata? :source-metadata :as source-query} :source-query :keys [source-metadata]}] (and source-query (or (not source-metadata) (legacy-source-metadata? source-metadata)) (or (not native-source-query?) source-query-has-source-metadata?))) |
(defn- maybe-add-source-metadata [x] (if (and (map? x) (should-add-source-metadata? x)) (add-source-metadata x) x)) | |
(defn- add-source-metadata-at-all-levels [inner-query] (walk/postwalk maybe-add-source-metadata inner-query)) | |
Middleware that attempts to recursively add
| (defn add-source-metadata-for-source-queries [{query-type :type, :as query}] (if-not (= query-type :query) query (update query :query add-source-metadata-at-all-levels))) |