(ns metabase.models.query-analysis
  (:require
   [honey.sql.helpers :as sql.helpers]
   [metabase.util :as u]
   [methodical.core :as methodical]
   [toucan2.core :as t2]))
(methodical/defmethod t2/table-name :model/QueryAnalysis [_model] :query_analysis)
(doto :model/QueryAnalysis
  (derive :metabase/model))
(defn- coerce-boolean [x]
  (cond
    (= 0 x) false
    (= 0M x) false
    :else x))
(defn- coerce-booleans [m]
  (update-vals m coerce-boolean))

Given some HoneySQL query map with :model/Card bound as :c, restrict this query to only return cards with invalid references (problematic query fields or query tables).

(defn cards-with-reference-errors
  [card-query-map]
  (-> card-query-map
      (sql.helpers/with
       [:problematic_query_fields
        {:select [:qf.card_id]
         :from [[(t2/table-name :model/QueryField) :qf]]
         :left-join [[(t2/table-name :model/Field) :f]
                     [:= :qf.field_id :f.id]]
         :where [:and
                 [:= :qf.explicit_reference true]
                 [:not= :qf.table nil]
                 [:or
                  [:= :f.id nil]
                  [:= :f.active false]]]}]
       [:problematic_query_tables
        {:select [:qt.card_id]
         :from [[(t2/table-name :model/QueryTable) :qt]]
         :left-join [[(t2/table-name :model/Table) :t]
                     [:= :qt.table_id :t.id]]
         :where [:or
                 [:= :qt.table_id nil]
                 [:= :t.active false]]}])
      (sql.helpers/where
       [:or
        [:exists {:select [1]
                  :from [[:problematic_query_fields :pqf]]
                  :where [:= :c.id :pqf.card_id]}]
        [:exists {:select [1]
                  :from [[:problematic_query_tables :pqt]]
                  :where [:= :c.id :pqt.card_id]}]])))
(defn- group-errors [errors]
  (reduce (fn [acc [id error]]
            (update acc id u/conjv error))
          {}
          errors))

Given a seq of cards, return a map of card-id => field reference errors

(defn- field-reference-errors
  [cards]
  (when (seq cards)
    (->> (t2/query {:select    [:qf.card_id
                                [:qf.column :field]
                                [:qf.table :table]
                                [[:= :f.id nil] :field_unknown]
                                [[:not [:coalesce :f.active true]] :field_inactive]]
                    :from      [[(t2/table-name :model/QueryField) :qf]]
                    :left-join [[(t2/table-name :model/Field) :f] [:= :f.id :qf.field_id]
                                [(t2/table-name :model/Table) :t] [:= :t.id :qf.table_id]]
                    :where     [:and
                                [:= :qf.explicit_reference true]
                                [:= :t.active true]
                                [:or
                                 [:= :f.id nil]
                                 [:= :f.active false]]
                                [:in :card_id (map :id cards)]]
                    :order-by  [:qf.card_id :table :field]})
         (map coerce-booleans)
         (map (fn [{:keys [card_id table field
                           field_unknown
                           field_inactive]}]
                [card_id {:type  (cond
                                   field_unknown  :unknown-field
                                   field_inactive :inactive-field
                                   ;; This shouldn't be reachable
                                   :else          :unknown-error)
                          :table table
                          :field field}]))
         group-errors)))

Given a seq of cards, return a map of card-id => table reference errors

(defn- table-reference-errors
  [cards]
  (when (seq cards)
    (->> (t2/query {:select    [:qt.card_id
                                [:qt.table :table]
                                [[:= :t.id nil] :table_unknown]
                                [[:not [:coalesce :t.active true]] :table_inactive]]
                    :from      [[(t2/table-name :model/QueryTable) :qt]]
                    :left-join [[(t2/table-name :model/Table) :t] [:= :t.id :qt.table_id]]
                    :where     [:and
                                [:or
                                 [:= :t.id nil]
                                 [:= :t.active false]]
                                [:in :card_id (map :id cards)]]
                    :order-by  [:qt.card_id :table]})
         (map coerce-booleans)
         (map (fn [{:keys [card_id table
                           table_unknown
                           table_inactive]}]
                [card_id {:type  (cond
                                   table_unknown  :unknown-table
                                   table_inactive :inactive-table
                                   ;; This shouldn't be reachable
                                   :else          :unknown-error)
                          :table table}]))
         group-errors)))

Given a seq of cards, return a map of card-id => reference errors

(defn reference-errors
  [cards]
  (merge-with concat
              (field-reference-errors cards)
              (table-reference-errors cards)))