(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)) |