diff --git a/resources/migrations/001_update_migrations.yaml b/resources/migrations/001_update_migrations.yaml index a818c18ba144839a7e19c7e40a5adc3f5ae8abf5..85e06338b48e7c8d4e23732d28b2c0d715363283 100644 --- a/resources/migrations/001_update_migrations.yaml +++ b/resources/migrations/001_update_migrations.yaml @@ -8719,7 +8719,7 @@ databaseChangeLog: - not: - foreignKeyConstraintExists: foreignKeyName: fk_pulse_channel_channel_id - foreignKyeTableName: pulse_channel + foreignKeyTableName: pulse_channel changes: - addForeignKeyConstraint: @@ -9503,6 +9503,64 @@ databaseChangeLog: WHERE "KEY" = 'embedding-app-origin'; rollback: # not needed + - changeSet: + id: v51.2024-10-24T14:23:58 + author: noahmoss + comment: Drop not null constraint on persisted_info.card_id + changes: + - dropNotNullConstraint: + tableName: persisted_info + columnName: card_id + columnDataType: int + rollback: + - addNotNullConstraint: + tableName: persisted_info + columnName: card_id + columnDataType: int + + - changeSet: + id: v51.2024-10-24T14:24:59 + author: noahmoss + comment: Drop foreign key constraint on persisted_info.card_id + preConditions: + - foreignKeyConstraintExists: + foreignKeyName: fk_persisted_info_card_id + foreignKeyTableName: persisted_info + changes: + - dropForeignKeyConstraint: + constraintName: fk_persisted_info_card_id + baseTableName: persisted_info + rollback: + - addForeignKeyConstraint: + baseTableName: persisted_info + baseColumnNames: card_id + referencedTableName: report_card + referencedColumnNames: id + constraintName: fk_persisted_info_card_id + onDelete: CASCADE + + - changeSet: + id: v51.2024-10-24T14:25:01 + author: noahmoss + comment: Re-add nullable foreign key constraint on persisted_info.card_id + preConditions: + - not: + - foreignKeyConstraintExists: + foreignKeyName: fk_persisted_info_card_id + foreignKeyTableName: persisted_info + changes: + - addForeignKeyConstraint: + baseTableName: persisted_info + baseColumnNames: card_id + referencedTableName: report_card + referencedColumnNames: id + constraintName: fk_persisted_info_card_id + onDelete: SET NULL + rollback: + - dropForeignKeyConstraint: + constraintName: fk_persisted_info_card_id + baseTableName: persisted_info + # >>>>>>>>>> DO NOT ADD NEW MIGRATIONS BELOW THIS LINE! ADD THEM ABOVE <<<<<<<<<< ######################################################################################################################## diff --git a/src/metabase/task/persist_refresh.clj b/src/metabase/task/persist_refresh.clj index 3d126719037489f629de955b35cd0c2abe423260..21fad995b29002a6c93fdb2fe1d601fc7eaa4afe 100644 --- a/src/metabase/task/persist_refresh.clj +++ b/src/metabase/task/persist_refresh.clj @@ -13,9 +13,7 @@ [metabase.email.messages :as messages] [metabase.models.card :refer [Card]] [metabase.models.database :refer [Database]] - [metabase.models.persisted-info - :as persisted-info - :refer [PersistedInfo]] + [metabase.models.persisted-info :as persisted-info] [metabase.models.task-history :as task-history] [metabase.public-settings :as public-settings] [metabase.query-processor.middleware.limit :as limit] @@ -56,12 +54,12 @@ (defn- refresh-with-stats! [refresher database stats persisted-info] ;; Since this could be long running, double check state just before refreshing - (when (contains? (persisted-info/refreshable-states) (t2/select-one-fn :state PersistedInfo :id (:id persisted-info))) + (when (contains? (persisted-info/refreshable-states) (t2/select-one-fn :state :model/PersistedInfo :id (:id persisted-info))) (log/infof "Attempting to refresh persisted model %s." (:card_id persisted-info)) (let [card (t2/select-one Card :id (:card_id persisted-info)) definition (persisted-info/metadata->definition (:result_metadata card) (:table_name persisted-info)) - _ (t2/update! PersistedInfo (u/the-id persisted-info) + _ (t2/update! :model/PersistedInfo (u/the-id persisted-info) {:definition definition, :query_hash (persisted-info/query-hash (:dataset_query card)) :active false, @@ -75,7 +73,7 @@ (log/infof e "Error refreshing persisting model with card-id %s" (:card_id persisted-info)) {:state :error :error (ex-message e)}))] - (t2/update! PersistedInfo (u/the-id persisted-info) + (t2/update! :model/PersistedInfo (u/the-id persisted-info) {:active (= state :success), :refresh_end :%now, :state (if (= state :success) "persisted" "error") @@ -98,7 +96,7 @@ (try (let [error-details (error-details task-details) error-details-by-id (m/index-by :persisted-info-id error-details) - persisted-infos (->> (t2/hydrate (t2/select PersistedInfo :id [:in (keys error-details-by-id)]) + persisted-infos (->> (t2/hydrate (t2/select :model/PersistedInfo :id [:in (keys error-details-by-id)]) [:card :collection] :database) (map #(assoc % :error (get-in error-details-by-id [(:id %) :error]))))] (messages/send-persistent-model-error-email! @@ -130,7 +128,7 @@ unpersist-fn (fn [] (reduce (fn [stats persisted-info] ;; Since this could be long running, double check state just before deleting - (let [current-state (t2/select-one-fn :state PersistedInfo :id (:id persisted-info)) + (let [current-state (t2/select-one-fn :state :model/PersistedInfo :id (:id persisted-info)) card-info (t2/select-one [Card :archived :type] :id (:card_id persisted-info))] (if (or (contains? (persisted-info/prunable-states) current-state) @@ -141,7 +139,7 @@ (try (unpersist! refresher database persisted-info) (when-not (= "off" current-state) - (t2/delete! PersistedInfo :id (:id persisted-info))) + (t2/delete! :model/PersistedInfo :id (:id persisted-info))) (update stats :success inc) (catch Exception e (log/infof e "Error unpersisting model with card-id %s" (:card_id persisted-info)) @@ -154,9 +152,10 @@ (defn- deletable-models "Returns persisted info records that can be unpersisted. Will select records that have moved into a deletable state after a sufficient delay to ensure no queries are running against them and to allow changing mind. Also selects - persisted info records pointing to cards that are no longer models and archived cards/models." + persisted info records pointing to cards that are no longer models, archived cards/models, and all records where the corresponding + card or database has been permanently deleted." [] - (t2/select PersistedInfo + (t2/select :model/PersistedInfo {:select [:p.*] :from [[:persisted_info :p]] :left-join [[:report_card :c] [:= :c.id :p.card_id]] @@ -170,12 +169,14 @@ [:< :state_change_at (sql.qp/add-interval-honeysql-form (mdb/db-type) :%now -1 :hour)]] [:= :c.type "question"] - [:= :c.archived true]]})) + [:= :c.archived true] + ;; card_id is set to null when the corresponding card is deleted + [:= :p.card_id nil]]})) (defn- refreshable-models "Returns refreshable models for a database id. Must still be models and not archived." [database-id] - (t2/select PersistedInfo + (t2/select :model/PersistedInfo {:select [:p.* :c.type :c.archived :c.name] :from [[:persisted_info :p]] :left-join [[:report_card :c] [:= :c.id :p.card_id]] @@ -211,7 +212,7 @@ (defn- refresh-individual! "Refresh an individual model based on [[PersistedInfo]]." [persisted-info-id refresher] - (let [persisted-info (t2/select-one PersistedInfo :id persisted-info-id) + (let [persisted-info (t2/select-one :model/PersistedInfo :id persisted-info-id) database (when persisted-info (t2/select-one Database :id (:database_id persisted-info)))] (if (and persisted-info database) diff --git a/test/metabase/task/persist_refresh_test.clj b/test/metabase/task/persist_refresh_test.clj index b7ce361fa59d6b2422a9adbd950bfd0ddc1b52b9..617b782eecf2c732780443e1eb550a6355d95696 100644 --- a/test/metabase/task/persist_refresh_test.clj +++ b/test/metabase/task/persist_refresh_test.clj @@ -193,7 +193,9 @@ PersistedInfo deletable {:card_id (u/the-id model3) :database_id (u/the-id db) :state "deletable" ;; need an "old enough" state change - :state_change_at (t/minus (t/local-date-time) (t/hours 2))}] + :state_change_at (t/minus (t/local-date-time) (t/hours 2))} + ;; Record not in "deletable" state, but with nil card_id + PersistedInfo deletable2 {:card_id nil :database_id (u/the-id db)}] (let [called-on (atom #{}) test-refresher (reify task.persist-refresh/Refresher (refresh! [_ _ _ _] @@ -204,19 +206,20 @@ (let [queued-for-deletion (into #{} (map :id) (#'task.persist-refresh/deletable-models))] (doseq [deletable-persisted [deletable punmodeled parchived]] (is (contains? queued-for-deletion (u/the-id deletable-persisted)))))) - ;; we manually pass in the deleteable ones to not catch others in a running instance + ;; we manually pass in the deleteable ones to not catch others in a running instance (#'task.persist-refresh/prune-deletables! test-refresher [deletable parchived punmodeled]) (testing "We delete persisted_info records for all of the pruned" (let [persisted-records (t2/select :model/PersistedInfo :id [:in (map :id [parchived punmodeled deletable])]) existing (map (comp (update-keys {parchived 'parchived punmodeled 'punmodeled - deletable 'deletable} + deletable 'deletable + deletable2 'deletable2} :id) :id) persisted-records)] (is (= [] existing)))) - ;; don't assert equality if there are any deletable in the app db + ;; don't assert equality if there are any deletable in the app db (doseq [deletable-persisted [deletable punmodeled parchived]] (is (contains? @called-on (u/the-id deletable-persisted)))) (is (partial= {:task "unpersist-tables"