Logic for updating FieldValues for fields in a database.

(ns metabase.sync.field-values
  (:require
   [java-time.api :as t]
   [metabase.db :as mdb]
   [metabase.driver.sql.query-processor :as sql.qp]
   [metabase.models.field :refer [Field]]
   [metabase.models.field-values :as field-values :refer [FieldValues]]
   [metabase.sync.interface :as i]
   [metabase.sync.util :as sync-util]
   [metabase.util :as u]
   [metabase.util.log :as log]
   [metabase.util.malli :as mu]
   [toucan2.core :as t2]))
(mu/defn- clear-field-values-for-field!
  [field :- i/FieldInstance]
  (when (t2/exists? FieldValues :field_id (u/the-id field))
    (log/debug (format "Based on cardinality and/or type information, %s should no longer have field values.\n"
                       (sync-util/name-for-logging field))
               "Deleting FieldValues...")
    (field-values/clear-field-values-for-field! field)
    ::field-values/fv-deleted))
(mu/defn- update-field-values-for-field!
  [field :- i/FieldInstance]
  (log/debug (u/format-color 'green "Looking into updating FieldValues for %s" (sync-util/name-for-logging field)))
  (let [field-values (field-values/get-latest-full-field-values (u/the-id field))]
    (if (field-values/inactive? field-values)
      (log/debugf "Field %s has not been used since %s. Skipping..."
                  (sync-util/name-for-logging field) (t/format "yyyy-MM-dd" (t/local-date-time (:last_used_at field-values))))
      (field-values/create-or-update-full-field-values! field :field-values field-values))))
(defn- update-field-value-stats-count [counts-map result]
  (if (instance? Exception result)
    (update counts-map :errors inc)
    (case result
      ::field-values/fv-created
      (update counts-map :created inc)
      ::field-values/fv-updated
      (update counts-map :updated inc)
      ::field-values/fv-deleted
      (update counts-map :deleted inc)
      counts-map)))
(defn- table->fields-to-scan
  [table]
  (t2/select Field :table_id (u/the-id table), :active true, :visibility_type "normal"))

Update the FieldValues for all Fields (as needed) for table.

(mu/defn update-field-values-for-table!
  [table :- i/TableInstance]
  (reduce (fn [fv-change-counts field]
            (let [result (sync-util/with-error-handling (format "Error updating field values for %s" (sync-util/name-for-logging field))
                           (if (field-values/field-should-have-field-values? field)
                             (update-field-values-for-field! field)
                             (clear-field-values-for-field! field)))]
              (update-field-value-stats-count fv-change-counts result)))
          {:errors 0, :created 0, :updated 0, :deleted 0}
          (table->fields-to-scan table)))
(mu/defn- update-field-values-for-database!
  [database :- i/DatabaseInstance]
  (let [tables (sync-util/reducible-sync-tables database)]
    (transduce (map update-field-values-for-table!) (partial merge-with +) tables)))
(defn- update-field-values-summary [{:keys [created updated deleted errors]}]
  (format "Updated %d field value sets, created %d, deleted %d with %d errors"
          updated created deleted errors))
(defn- delete-expired-advanced-field-values-summary [{:keys [deleted]}]
  (format "Deleted %d expired advanced fieldvalues" deleted))
(defn- delete-expired-advanced-field-values-for-field!
  [field]
  (sync-util/with-error-handling (format "Error deleting expired advanced field values for %s" (sync-util/name-for-logging field))
    (let [conditions [:field_id   (:id field)
                      :type       [:in field-values/advanced-field-values-types]
                      :created_at [:< (sql.qp/add-interval-honeysql-form
                                       (mdb/db-type)
                                       :%now
                                       (- (t/as field-values/advanced-field-values-max-age :days))
                                       :day)]]
          rows-count (apply t2/count FieldValues conditions)]
      (apply t2/delete! FieldValues conditions)
      rows-count)))

Delete all expired advanced FieldValues for a table and returns the number of deleted rows. For more info about advanced FieldValues, check the docs in [[metabase.models.field-values/field-values-types]]

(mu/defn delete-expired-advanced-field-values-for-table!
  [table :- i/TableInstance]
  (->> (table->fields-to-scan table)
       (map delete-expired-advanced-field-values-for-field!)
       (reduce +)))
(mu/defn- delete-expired-advanced-field-values-for-database!
  [database :- i/DatabaseInstance]
  (let [tables (sync-util/reducible-sync-tables database)]
    {:deleted (transduce (comp (map delete-expired-advanced-field-values-for-table!)
                               (map (fn [result]
                                      (if (instance? Throwable result)
                                        (throw result)
                                        result))))
                         +
                         0
                         tables)}))
(def ^:private sync-field-values-steps
  [(sync-util/create-sync-step "delete-expired-advanced-field-values"
                               delete-expired-advanced-field-values-for-database!
                               delete-expired-advanced-field-values-summary)
   (sync-util/create-sync-step "update-field-values"
                               update-field-values-for-database!
                               update-field-values-summary)])

Update the advanced FieldValues (distinct values for categories and certain other fields that are shown in widgets like filters) for the Tables in database (as needed).

(mu/defn update-field-values!
  [database :- i/DatabaseInstance]
  (sync-util/sync-operation :cache-field-values database (format "Cache field values in %s"
                                                                 (sync-util/name-for-logging database))
    (sync-util/run-sync-operation "field values scanning" database sync-field-values-steps)))