(ns metabase.query-processor.middleware.resolve-referenced
  (:require
   [metabase.lib.core :as lib]
   [metabase.lib.metadata :as lib.metadata]
   [metabase.lib.schema :as lib.schema]
   [metabase.lib.schema.common :as lib.schema.common]
   [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.query-processor.middleware.fetch-source-query :as qp.fetch-source-query]
   [metabase.query-processor.middleware.resolve-fields :as qp.resolve-fields]
   [metabase.query-processor.middleware.resolve-source-table :as qp.resolve-source-table]
   [metabase.util.i18n :refer [tru]]
   [metabase.util.malli :as mu]
   [weavejester.dependency :as dep])
  (:import
   (clojure.lang ExceptionInfo)))

Done for side effects; warm the MetadataProvider.

(mu/defn- resolve-referenced-card-resources*
  [query :- ::lib.schema/query]
  (doseq [referenced-card (lib/template-tags-referenced-cards query)
          :let            [referenced-query (->> referenced-card
                                                 (qp.fetch-source-query/normalize-card-query query)
                                                 :dataset-query)
                           resolved-query   (qp.fetch-source-query/resolve-source-cards referenced-query)]]
    (qp.resolve-source-table/resolve-source-tables resolved-query)
    (qp.resolve-fields/resolve-fields resolved-query)))
(mu/defn- card-subquery-graph
  [metadata-providerable :- ::lib.schema.metadata/metadata-providerable
   graph                 :- :map
   card-id               :- ::lib.schema.id/card]
  (let [card-query (->> (or (lib.metadata/card metadata-providerable card-id)
                            (throw (ex-info (tru "Card {0} does not exist, or is from a different Database." (pr-str card-id))
                                            {:type qp.error-type/invalid-query, :card-id card-id})))
                        (qp.fetch-source-query/normalize-card-query metadata-providerable)
                        :dataset-query)]
    (reduce
     (fn [g sub-card-id]
       (card-subquery-graph metadata-providerable
                            (dep/depend g card-id sub-card-id)
                            sub-card-id))
     graph
     (lib/template-tag-card-ids card-query))))
(mu/defn- circular-ref-error :- ::lib.schema.common/non-blank-string
  [metadata-providerable :- ::lib.schema.metadata/metadata-providerable
   from-card             :- ::lib.schema.id/card
   to-card               :- ::lib.schema.id/card]
  (let [cards     (into {}
                        (map (juxt :id :name))
                        (lib.metadata/bulk-metadata metadata-providerable :metadata/card #{from-card to-card}))
        from-name (or (get cards from-card)
                      (throw (ex-info (tru "Referenced query is from a different database")
                                      {:type qp.error-type/invalid-query, :card-id from-card})))
        to-name   (or (get cards to-card)
                      (throw (ex-info (tru "Referenced query is from a different database")
                                      {:type qp.error-type/invalid-query, :card-id to-card})))]
    (str
     (tru "This query has circular referencing sub-queries. ")
     (tru "These questions seem to be part of the problem: \"{0}\" and \"{1}\"." from-name to-name))))

Done for side effects; [[card-subquery-graph]] will throw if there are circular references.

(mu/defn- check-for-circular-references
  [query :- ::lib.schema/query]
  (try
    (reduce (partial card-subquery-graph query)
            (dep/graph)
            (lib/template-tag-card-ids query))
    (catch ExceptionInfo e
      (let [{:keys [reason node dependency]} (ex-data e)]
        (if (= reason :weavejester.dependency/circular-dependency)
          (throw (ex-info (circular-ref-error query node dependency) {:original-exception e}))
          (throw e))))))
(mu/defn resolve-referenced-card-resources :- ::lib.schema/query
  "Resolves tables and fields referenced in card query template tags."
  [query :- ::lib.schema/query]
  (check-for-circular-references query)
  (resolve-referenced-card-resources* query)
  query)