(ns metabase.model-persistence.models.persisted-info (:require [buddy.core.codecs :as codecs] [clojure.string :as str] [metabase.lib.schema.common :as lib.schema.common] [metabase.lib.schema.metadata :as lib.schema.metadata] [metabase.models.interface :as mi] [metabase.premium-features.core :refer [defenterprise]] [metabase.query-processor.util :as qp.util] [metabase.util :as u] [metabase.util.malli :as mu] [methodical.core :as methodical] [toucan2.core :as t2])) | |
----------------------------------------------- Entity & Lifecycle ----------------------------------------------- | |
(methodical/defmethod t2/table-name :model/PersistedInfo [_model] :persisted_info) | |
(derive :model/PersistedInfo :metabase/model) | |
Parse the value of | (defn transform-definition-out [definition] (when-let [definition (not-empty (mi/json-out-with-keywordization definition))] (update definition :field-definitions (fn [field-definitions] (mapv #(update % :base-type keyword) field-definitions))))) |
(t2/deftransforms :model/PersistedInfo {:definition {:in mi/json-in :out transform-definition-out}}) | |
Map containing the type and name of fields for dll. The type is :base-type and uses the effectivetype else basetype of a field. | (defn- field-metadata->field-defintion [{field-name :name :keys [base_type effective_type]}] {:field-name field-name :base-type (or effective_type base_type)}) |
Spec for metadata. Just asserting we have base types and names, not the full metadata of the qp. | (def ^:private Metadata [:maybe [:sequential [:map [:name :string] [:base_type ::lib.schema.common/base-type] [:effective_type {:optional true} ::lib.schema.common/base-type]]]]) |
(mu/defn metadata->definition :- ::lib.schema.metadata/persisted-info.definition "Returns a ddl definition datastructure. A :table-name and :field-deifinitions vector of field-name and base-type." [metadata :- Metadata table-name] {:table-name table-name :field-definitions (mapv field-metadata->field-defintion metadata)}) | |
Base64 string of the hash of a query. | (mu/defn query-hash [query :- :map] (String. ^bytes (codecs/bytes->b64 (qp.util/query-hash query)))) |
Allow persisted substitution. When refreshing, set this to nil to ensure that all underlying queries are used to rebuild the persisted table. | (def ^:dynamic *allow-persisted-substitution* true) |
Whether to allow persisted substitution. When refreshing, set this to nil to ensure that all underlying queries are used to rebuild the persisted table. | (defn allow-persisted-substitution? [] *allow-persisted-substitution*) |
Execute | (defmacro with-persisted-substituion-disabled [& body] `(binding [*allow-persisted-substitution* false] ~@body)) |
A slug from a card suitable for a table name. This slug is not intended to be unique but to be human guide if looking
at schemas. Persisted table names will follow the pattern | (defn- slug-name [nom] (->> (str/replace (u/lower-case-en nom) #"\s+" "_") (take 10) (apply str))) |
States of 'off' needs to be handled here even though setting the state to off is only possible with :cache-granular-controls enabled. A model could still have state=off if the instance previously had the feature flag, then downgraded to not have it. In that case models with state=off were previously prunable when the feature flag enabled, but they should be refreshable with the feature flag disabled. | (defenterprise refreshable-states metabase-enterprise.cache.config [] ;; meant to be the same as the enterprise version except that "off" is not honored and is refreshed #{"refreshing" "creating" "persisted" "error" "off"}) |
States of | (defenterprise prunable-states metabase-enterprise.cache.config [] #{"deletable"}) |
(mi/define-batched-hydration-method persisted? :persisted "Hydrate a card :is_persisted for the frontend." [cards] (when (seq cards) (let [existing-ids (t2/select-fn-set :card_id :model/PersistedInfo :card_id [:in (map :id cards)] :state [:in (refreshable-states)])] (map (fn [{id :id :as card}] (assoc card :persisted (contains? existing-ids id))) cards)))) | |
Marks PersistedInfo as | (defn mark-for-pruning! ([conditions-map] (mark-for-pruning! conditions-map "deletable")) ([conditions-map state] (t2/update! :model/PersistedInfo conditions-map {:active false, :state state, :state_change_at :%now}))) |
Invalidates any caches corresponding to the | (defn invalidate! [conditions-map] ;; We do not immediately delete the cached table, it will get clobbered during the next refresh cycle. (t2/update! :model/PersistedInfo (merge {:active true} conditions-map) ;; TODO perhaps we should immediately kick off a recalculation of these caches {:active false, :state "creating", :state_change_at :%now})) |
The default state for a new PersistedInfo record. Defaults to 'creating' for OSS | (defenterprise default-persistent-info-state metabase-enterprise.cache.config [] "creating") |
Creates a mew PersistedInfo with the default state. If | (defn- create-row [user-id card] (let [slug (-> card :name slug-name) default-state (default-persistent-info-state) {:keys [database_id]} card card-id (u/the-id card)] {:card_id card-id :database_id database_id :question_slug slug :table_name (format "model_%s_%s" card-id slug) :active false :refresh_begin :%now :refresh_end nil :state default-state :state_change_at :%now :creator_id user-id})) |
Looks for all new models in database and creates a persisted-info ready to be synced. | (defn ready-unpersisted-models! [database-id] (let [cards (t2/select :model/Card {:where [:and [:= :database_id database-id] [:= :type "model"] [:not [:exists {:select [1] :from [:persisted_info] :where [:= :persisted_info.card_id :report_card.id]}]]]})] (t2/insert! :model/PersistedInfo (map #(create-row nil %) cards)))) |
Marks PersistedInfo as | (defn turn-on-model! [user-id card] (let [card-id (u/the-id card) existing-persisted-info (t2/select-one :model/PersistedInfo :card_id card-id) persisted-info (cond (not existing-persisted-info) (first (t2/insert-returning-instances! :model/PersistedInfo (create-row user-id card))) (contains? #{"deletable" "off"} (:state existing-persisted-info)) (do (t2/update! :model/PersistedInfo (u/the-id existing-persisted-info) {:active false, :state "creating", :state_change_at :%now}) (t2/select-one :model/PersistedInfo :card_id card-id)))] persisted-info)) |
Sets PersistedInfo state to the default state for models without a PeristedInfo or those in a | (defn ready-database! [database-id] (t2/query-one {:update [:persisted_info] :where [:and [:= :database_id database-id] [:= :state "deletable"]] :set {:active false, :state (default-persistent-info-state), :state_change_at :%now}}) (ready-unpersisted-models! database-id)) |