From 09e16f6fc8fddd37a7c30d49f429c549dbcdb7d3 Mon Sep 17 00:00:00 2001 From: Cal Herries <39073188+calherries@users.noreply.github.com> Date: Wed, 17 Apr 2024 18:36:05 +0300 Subject: [PATCH] Use `fk-metadata` instead of `table-fk-metadata` everywhere (#41375) --- src/metabase/driver.clj | 3 +- src/metabase/sync/fetch_metadata.clj | 51 +++++++++++-------- src/metabase/sync/sync_metadata/fks.clj | 24 +++------ .../sync_metadata/sync_table_privileges.clj | 5 +- src/metabase/sync/util.clj | 29 ++++++----- 5 files changed, 57 insertions(+), 55 deletions(-) diff --git a/src/metabase/driver.clj b/src/metabase/driver.clj index aac5b659b2b..1938ec16a7a 100644 --- a/src/metabase/driver.clj +++ b/src/metabase/driver.clj @@ -366,7 +366,8 @@ (defmulti describe-fks "Returns a reducible collection of maps, each containing information about foreign keys. - Takes keyword arguments to narrow down the results to a set of `schema-names` or `table-names`. + Takes optional keyword arguments to narrow down the results to a set of `schema-names` + and `table-names`. Results match [[metabase.sync.interface/FKMetadataEntry]]. Results are optionally filtered by `schema-names` and `table-names` provided. diff --git a/src/metabase/sync/fetch_metadata.clj b/src/metabase/sync/fetch_metadata.clj index efd43e7bbe8..ed056750b08 100644 --- a/src/metabase/sync/fetch_metadata.clj +++ b/src/metabase/sync/fetch_metadata.clj @@ -7,6 +7,7 @@ [metabase.driver.sql-jdbc.sync :as sql-jdbc.sync] [metabase.driver.util :as driver.u] [metabase.sync.interface :as i] + [metabase.sync.util :as sync-util] [metabase.util.log :as log] [metabase.util.malli :as mu] [metabase.util.malli.fn :as mu.fn])) @@ -45,32 +46,40 @@ (set (fields-metadata database :table-names [(:name table)] :schema-names [(:schema table)])) (:fields (driver/describe-table (driver.u/database->driver database) database table))))) -(mu/defn fk-metadata - "Effectively a wrapper for [[metabase.driver/describe-fks]] that also validates the output against the schema." - [database :- i/DatabaseInstance & {:as args}] - (log-if-error "fk-metadata" - (cond->> (driver/describe-fks (driver.u/database->driver database) database args) - ;; This is a workaround for the fact that [[mu/defn]] can't check reducible collections yet - (mu.fn/instrument-ns? *ns*) - (eduction (map #(mu.fn/validate-output {} i/FKMetadataEntry %)))))) - -(mu/defn table-fk-metadata :- [:maybe [:sequential i/FKMetadataEntry]] - "Get information about the foreign keys belonging to `table`." - [database :- i/DatabaseInstance - table :- i/TableInstance] - (log-if-error "table-fk-metadata" - (let [driver (driver.u/database->driver database)] - (when (driver/database-supports? driver :foreign-keys database) - (if (driver/database-supports? driver :describe-fks database) - (vec (driver/describe-fks driver database :table-names [(:name table)] :schema-names [(:schema table)])) - #_{:clj-kondo/ignore [:deprecated-var]} - (vec (for [x (driver/describe-table-fks driver database table)] +(defn backwards-compatible-describe-fks + "Replaces [[metabase.driver/describe-fks]] for drivers that haven't implemented it. Uses [[driver/describe-table-fks]] + which is deprecated." + [driver database & {:keys [schema-names table-names]}] + (let [tables (sync-util/db->reducible-sync-tables database :schema-names schema-names :table-names table-names)] + (eduction + (mapcat (fn [table] + #_{:clj-kondo/ignore [:deprecated-var]} + (for [x (driver/describe-table-fks driver database table)] {:fk-table-name (:name table) :fk-table-schema (:schema table) :fk-column-name (:fk-column-name x) :pk-table-name (:name (:dest-table x)) :pk-table-schema (:schema (:dest-table x)) - :pk-column-name (:dest-column-name x)}))))))) + :pk-column-name (:dest-column-name x)}))) + tables))) + +(mu/defn fk-metadata + "Effectively a wrapper for [[metabase.driver/describe-fks]] that also validates the output against the schema. + If the driver doesn't support [[metabase.driver/describe-fks]] it uses [[driver/describe-table-fks]] instead. + This will be deprecated in " + [database :- i/DatabaseInstance & {:as args}] + (log-if-error "fk-metadata" + (let [driver (driver.u/database->driver database)] + (when (driver/database-supports? driver :foreign-keys database) + (let [describe-fks-fn (if (driver/database-supports? driver :describe-fks database) + driver/describe-fks + ;; In version 52 we'll remove [[driver/describe-table-fks]] + ;; and we'll just use [[driver/describe-fks]] here + backwards-compatible-describe-fks)] + (cond->> (describe-fks-fn (driver.u/database->driver database) database args) + ;; This is a workaround for the fact that [[mu/defn]] can't check reducible collections yet + (mu.fn/instrument-ns? *ns*) + (eduction (map #(mu.fn/validate-output {} i/FKMetadataEntry %))))))))) (mu/defn nfc-metadata :- [:maybe [:set i/TableMetadataField]] "Get information about the nested field column fields within `table`." diff --git a/src/metabase/sync/sync_metadata/fks.clj b/src/metabase/sync/sync_metadata/fks.clj index bf5279eb235..df9546a3e69 100644 --- a/src/metabase/sync/sync_metadata/fks.clj +++ b/src/metabase/sync/sync_metadata/fks.clj @@ -12,8 +12,7 @@ [metabase.util :as u] [metabase.util.log :as log] [metabase.util.malli :as mu] - [toucan2.core :as t2] - [toucan2.realize :as t2.realize])) + [toucan2.core :as t2])) (defn ^:private mark-fk-sql "Returns [sql & params] for [[mark-fk!]] according to the application DB's dialect." @@ -97,7 +96,9 @@ "Sync the foreign keys for a `database`." [database :- i/DatabaseInstance] (sync-util/with-error-handling (format "Error syncing FKs for %s" (sync-util/name-for-logging database)) - (let [schema-names (sync-util/db->sync-schemas database) + (let [driver (driver.u/database->driver database) + schema-names (when (driver/database-supports? driver :schemas database) + (sync-util/db->sync-schemas database)) fk-metadata (fetch-metadata/fk-metadata database :schema-names schema-names)] (transduce (map (fn [x] (let [[updated failed] (try [(mark-fk! database x) 0] @@ -121,7 +122,9 @@ ([database :- i/DatabaseInstance table :- i/TableInstance] (sync-util/with-error-handling (format "Error syncing FKs for %s" (sync-util/name-for-logging table)) - (let [fk-metadata (fetch-metadata/table-fk-metadata database table)] + (let [schema-names (when (driver/database-supports? (driver.u/database->driver database) :schemas database) + [(:schema table)]) + fk-metadata (into [] (fetch-metadata/fk-metadata database :schema-names schema-names :table-names [(:name table)]))] {:total-fks (count fk-metadata) :updated-fks (sync-util/sum-numbers #(mark-fk! database %) fk-metadata)})))) @@ -133,18 +136,7 @@ This function also sets all the tables that should be synced to have `initial-sync-status=complete` once the sync is done." [database :- i/DatabaseInstance] - (u/prog1 (if (driver/database-supports? (driver.u/database->driver database) :describe-fks database) - (sync-fks-for-db! database) - (reduce (fn [update-info table] - (let [table (t2.realize/realize table) - table-fk-info (sync-fks-for-table! database table)] - (if (instance? Exception table-fk-info) - (update update-info :total-failed inc) - (merge-with + update-info table-fk-info)))) - {:total-fks 0 - :updated-fks 0 - :total-failed 0} - (sync-util/db->reducible-sync-tables database))) + (u/prog1 (sync-fks-for-db! database) ;; Mark the table as done with its initial sync once this step is done even if it failed, because only ;; sync-aborting errors should be surfaced to the UI (see ;; `:metabase.sync.util/exception-classes-not-to-retry`). diff --git a/src/metabase/sync/sync_metadata/sync_table_privileges.clj b/src/metabase/sync/sync_metadata/sync_table_privileges.clj index 8df930fda35..f3822a314d4 100644 --- a/src/metabase/sync/sync_metadata/sync_table_privileges.clj +++ b/src/metabase/sync/sync_metadata/sync_table_privileges.clj @@ -14,10 +14,7 @@ This is a cache of the data returned from `driver/table-privileges`, but it's stored in the database for performance." [database :- (ms/InstanceOf :model/Database)] (let [driver (driver.u/database->driver database)] - (when (and (not= :redshift driver) - ;; redshift does support table-privileges, but we don't want to sync it now because table privileges are - ;; meant to enhance action features, but redshift does not support actions for now, so we skip it here. - (driver/database-supports? driver :table-privileges database)) + (when (driver/database-supports? driver :table-privileges database) (let [rows (driver/current-user-table-privileges driver database) schema+table->id (t2/select-fn->pk (fn [t] {:schema (:schema t), :table (:name t)}) :model/Table :db_id (:id database)) rows-with-table-id (keep (fn [row] diff --git a/src/metabase/sync/util.clj b/src/metabase/sync/util.clj index 57a45fcc418..f830f86709e 100644 --- a/src/metabase/sync/util.clj +++ b/src/metabase/sync/util.clj @@ -61,7 +61,6 @@ ;; new function that will execute the original in whatever context or with whatever side effects appropriate for that ;; step. - ;; This looks something like {:sync #{1 2}, :cache #{2 3}} when populated. ;; Key is a type of sync operation, e.g. `:sync` or `:cache`; vals are sets of DB IDs undergoing that operation. ;; @@ -100,10 +99,10 @@ (keyword (or (namespace event-name-prefix) "event") (str (name prefix) suffix)))] (with-sync-events - (event-keyword event-name-prefix "-begin") - (event-keyword event-name-prefix "-end") - database-or-id - f))) + (event-keyword event-name-prefix "-begin") + (event-keyword event-name-prefix "-end") + database-or-id + f))) ([begin-event-name :- Topic end-event-name :- Topic @@ -129,8 +128,8 @@ _ (log-fn (u/format-color 'magenta "STARTING: %s" message)) result (f)] (log-fn (u/format-color 'magenta "FINISHED: %s (%s)" - message - (u/format-nanoseconds (- (System/nanoTime) start-time)))) + message + (u/format-nanoseconds (- (System/nanoTime) start-time)))) result)) (defn- with-start-and-finish-logging @@ -226,7 +225,6 @@ [operation database message & body] `(do-sync-operation ~operation ~database ~message (fn [] ~@body))) - ;;; +----------------------------------------------------------------------------------------------------------------+ ;;; | EMOJI PROGRESS METER | ;;; +----------------------------------------------------------------------------------------------------------------+ @@ -341,11 +339,16 @@ (defn db->reducible-sync-tables "Returns a reducible of all the Tables that should go through the sync processes for `database-or-id`." - [database-or-id] - (t2/reducible-select :model/Table, :db_id (u/the-id database-or-id), {:where sync-tables-clause})) + [database-or-id & {:keys [schema-names table-names]}] + (t2/reducible-select :model/Table + :db_id (u/the-id database-or-id) + {:where [:and sync-tables-clause + (when (seq schema-names) [:in :schema schema-names]) + (when (seq table-names) [:in :name table-names])]})) (defn db->sync-schemas - "Returns all the Schemas that have their metadata sync'd for `database-or-id`." + "Returns all the Schemas that have their metadata sync'd for `database-or-id`. + Assumes the database supports schemas." [database-or-id] (vec (map :schema (t2/query {:select-distinct [:schema] :from [:metabase_table] @@ -362,7 +365,7 @@ mi/model) (defmethod name-for-logging :model/Database - [{database-name :name, id :id, engine :engine,}] + [{database-name :name, id :id, engine :engine}] (format "%s Database %s ''%s''" (name engine) (str (or id "")) database-name)) (defn table-name-for-logging @@ -495,7 +498,7 @@ "# %s\n" "# %s\n" (when log-summary-fn - (format "# %s\n" (log-summary-fn step-info)))) + (format "# %s\n" (log-summary-fn step-info)))) [(format "Completed step ''%s''" step-name) (format "Start: %s" (u.date/format start-time)) (format "End: %s" (u.date/format end-time)) -- GitLab