From 7d4f727df0d4261762d938c906fa992f82afe497 Mon Sep 17 00:00:00 2001 From: Ngoc Khuat <qn.khuat@gmail.com> Date: Tue, 28 Mar 2023 17:02:42 +0700 Subject: [PATCH] `resolve-model, primary-key` handles toucan 2 model (#29559) * use resolve-model with keyword check * do the same for primary-key and fix a check in serialization --- .clj-kondo/config.edn | 4 +++- .../serialization/v2/backfill_ids.clj | 9 +++---- .../serialization/v2/seed_entity_ids.clj | 3 ++- .../serialization/upsert_test.clj | 4 ++-- src/metabase/db/util.clj | 21 +++++++++++++++- src/metabase/models/interface.clj | 3 ++- src/metabase/models/revision.clj | 4 ++-- src/metabase/models/serialization.clj | 24 +++++++++---------- test/metabase/test.clj | 6 ++--- test/metabase/test/util.clj | 8 +++---- 10 files changed, 55 insertions(+), 31 deletions(-) diff --git a/.clj-kondo/config.edn b/.clj-kondo/config.edn index dc2016d70d9..12266d75f68 100644 --- a/.clj-kondo/config.edn +++ b/.clj-kondo/config.edn @@ -156,7 +156,9 @@ toucan.db/transaction {:message "Use t2/with-transaction instead of toucan.db/transaction"} toucan.db/with-call-counting {:message "Use t2/with-call-count instead of toucan.db/with-call-counting"} toucan.db/execute! {:message "Use t2/query-one instead of toucan.db/execute!"} - toucan.db/reducible-query {:message "Use mdb.query/reducible-query instead of toucan.db/reducible-query"}} + toucan.db/reducible-query {:message "Use mdb.query/reducible-query instead of toucan.db/reducible-query"} + toucan.db/resolve-model {:message "Use metabase.db.util/resolve-model instead of toucan.db/resolve-model"} + toucan.models/primary-key {:message "Use metabase.db.util/primary-key instead of toucan.models/primary-key"}} :discouraged-namespace {clojure.tools.logging {:message "Use metabase.util.log instead of clojure.tools.logging directly"} diff --git a/enterprise/backend/src/metabase_enterprise/serialization/v2/backfill_ids.clj b/enterprise/backend/src/metabase_enterprise/serialization/v2/backfill_ids.clj index 125f57dd1f7..a075ece19b3 100644 --- a/enterprise/backend/src/metabase_enterprise/serialization/v2/backfill_ids.clj +++ b/enterprise/backend/src/metabase_enterprise/serialization/v2/backfill_ids.clj @@ -6,11 +6,12 @@ so this should produce identical IDs on all platforms and JVM implementations." (:require [metabase-enterprise.serialization.v2.models :as serdes.models] + [metabase.db.util :as mdb.u] + [metabase.models.interface :as mi] [metabase.models.serialization :as serdes] [metabase.util :as u] [metabase.util.i18n :refer [trs]] [metabase.util.log :as log] - [toucan.db :as db] [toucan.models :as models] [toucan2.core :as t2])) @@ -18,7 +19,7 @@ "Updates all rows of a particular model to have `:entity_id` set, based on the [[serdes/identity-hash]]." [model] (let [missing (t2/select model :entity_id nil) - pk (models/primary-key model)] + pk (mdb.u/primary-key model)] (when (seq missing) (log/info (trs "Backfilling entity_id for {0} rows of {1}" (pr-str (count missing)) (name model))) (doseq [entity missing @@ -27,7 +28,7 @@ (t2/update! model (get entity pk) {:entity_id eid}))))) (defn- has-entity-id? [model] - (:entity_id (models/properties model))) + (::mi/entity-id (models/properties model))) (defn backfill-ids "Updates all rows of all models that are (a) serialized and (b) have `entity_id` columns to have the @@ -35,6 +36,6 @@ row." [] (doseq [model-name (concat serdes.models/exported-models serdes.models/inlined-models) - :let [model (db/resolve-model (symbol model-name))] + :let [model (mdb.u/resolve-model (symbol model-name))] :when (has-entity-id? model)] (backfill-ids-for model))) diff --git a/enterprise/backend/src/metabase_enterprise/serialization/v2/seed_entity_ids.clj b/enterprise/backend/src/metabase_enterprise/serialization/v2/seed_entity_ids.clj index e8ee10932ab..10f19d954f9 100644 --- a/enterprise/backend/src/metabase_enterprise/serialization/v2/seed_entity_ids.clj +++ b/enterprise/backend/src/metabase_enterprise/serialization/v2/seed_entity_ids.clj @@ -3,6 +3,7 @@ [clojure.string :as str] [metabase.db :as mdb] [metabase.db.connection :as mdb.connection] + [metabase.db.util :as mdb.u] [metabase.models] [metabase.models.serialization :as serdes] [metabase.util :as u] @@ -63,7 +64,7 @@ (defn- seed-entity-id-for-instance! [model instance] (try - (let [primary-key (models/primary-key model) + (let [primary-key (mdb.u/primary-key model) pk-value (get instance primary-key)] (when-not (some? pk-value) (throw (ex-info (format "Missing value for primary key column %s" (pr-str primary-key)) diff --git a/enterprise/backend/test/metabase_enterprise/serialization/upsert_test.clj b/enterprise/backend/test/metabase_enterprise/serialization/upsert_test.clj index 531002bfa26..5ca7a2a45a6 100644 --- a/enterprise/backend/test/metabase_enterprise/serialization/upsert_test.clj +++ b/enterprise/backend/test/metabase_enterprise/serialization/upsert_test.clj @@ -3,12 +3,12 @@ [clojure.data :as data] [clojure.test :refer :all] [metabase-enterprise.serialization.upsert :as upsert] + [metabase.db.util :as mdb.u] [metabase.models :refer [Card Collection Dashboard DashboardCard Database Field Metric NativeQuerySnippet Pulse Segment Table User]] [metabase.models.interface :as mi] [metabase.test :as mt] [metabase.util :as u] - [toucan.models :as models] [toucan2.core :as t2])) (def ^:private same? (comp nil? second data/diff)) @@ -94,7 +94,7 @@ ;; create an additional entity so we're sure whe get the right one model [_ (dummy-entity dashboard model e1 1)] model [{id :id} (dummy-entity dashboard model e2 2)]] - (let [e (t2/select-one model (models/primary-key model) id)] + (let [e (t2/select-one model (mdb.u/primary-key model) id)] ;; make sure that all columns in identity-condition actually exist in the model (is (= (set id-cond) (-> e (select-keys id-cond) diff --git a/src/metabase/db/util.clj b/src/metabase/db/util.clj index 8c9044e62a7..f49911d68d4 100644 --- a/src/metabase/db/util.clj +++ b/src/metabase/db/util.clj @@ -5,8 +5,27 @@ [metabase.util.schema :as su] [schema.core :as s] [toucan.db :as db] + [toucan.models :as models] [toucan2.core :as t2])) +(defn primary-key + "Replacement of [[mdb.u/primary-key]], this is used to make the transition to toucan 2 easier. + In toucan2, every keyword can be a model so if `model` is a keyword, returns as is, otherwise calls [[mdb.u/primary-key]]." + [model] + (if (keyword? model) + (first (t2/primary-keys :m/card)) + #_{:clj-kondo/ignore [:discouraged-var]} + (models/primary-key model))) + +(defn resolve-model + "Replacement of [[mb.models/resolve-model]], this is used to make the transition to toucan 2 easier. + In toucan2, every keyword can be a model so if `model` is a keyword, returns as is, otherwise calls [[toucan1.db/resolve-model]]." + [model] + (if (keyword? model) + model + #_{:clj-kondo/ignore [:discouraged-var]} + (db/resolve-model model))) + (defn join "Convenience for generating a HoneySQL `JOIN` clause. @@ -14,7 +33,7 @@ (mdb/join [FieldValues :field_id] [Field :id]) :active true)" [[source-entity fk] [dest-entity pk]] - {:left-join [(t2/table-name (db/resolve-model dest-entity)) + {:left-join [(t2/table-name (resolve-model dest-entity)) [:= (db/qualify source-entity fk) (db/qualify dest-entity pk)]]}) (def ^:private NamespacedKeyword diff --git a/src/metabase/models/interface.clj b/src/metabase/models/interface.clj index d41977158ed..39c85623ff1 100644 --- a/src/metabase/models/interface.clj +++ b/src/metabase/models/interface.clj @@ -8,6 +8,7 @@ [clojure.spec.alpha :as s] [clojure.walk :as walk] [metabase.db.connection :as mdb.connection] + [metabase.db.util :as mdb.u] [metabase.mbql.normalize :as mbql.normalize] [metabase.mbql.schema :as mbql.s] [metabase.models.dispatch :as models.dispatch] @@ -478,7 +479,7 @@ (defn- check-perms-with-fn ([fn-symb read-or-write a-model object-id] (or (current-user-has-root-permissions?) - (check-perms-with-fn fn-symb read-or-write (t2/select-one a-model (models/primary-key a-model) object-id)))) + (check-perms-with-fn fn-symb read-or-write (t2/select-one a-model (mdb.u/primary-key a-model) object-id)))) ([fn-symb read-or-write object] (and object diff --git a/src/metabase/models/revision.clj b/src/metabase/models/revision.clj index 7cc4bc25803..28219454641 100644 --- a/src/metabase/models/revision.clj +++ b/src/metabase/models/revision.clj @@ -1,12 +1,12 @@ (ns metabase.models.revision (:require [clojure.data :as data] + [metabase.db.util :as mdb.u] [metabase.models.interface :as mi] [metabase.models.revision.diff :refer [diff-string]] [metabase.models.user :refer [User]] [metabase.util :as u] [metabase.util.i18n :refer [tru]] - [toucan.db :as db] [toucan.hydrate :refer [hydrate]] [toucan.models :as models] [toucan2.core :as t2])) @@ -68,7 +68,7 @@ [{:keys [model], :as revision}] ;; in some cases (such as tests) we have 'fake' models that cannot be resolved normally; don't fail entirely in ;; those cases - (let [model (u/ignore-exceptions (db/resolve-model (symbol model)))] + (let [model (u/ignore-exceptions (mdb.u/resolve-model (symbol model)))] (cond-> revision model (update :object (partial models/do-post-select model))))) diff --git a/src/metabase/models/serialization.clj b/src/metabase/models/serialization.clj index bb35e8d3d8e..638e62c3b6a 100644 --- a/src/metabase/models/serialization.clj +++ b/src/metabase/models/serialization.clj @@ -15,6 +15,7 @@ [clojure.set :as set] [clojure.string :as str] [medley.core :as m] + [metabase.db.util :as mdb.u] [metabase.mbql.normalize :as mbql.normalize] [metabase.mbql.schema :as mbql.s] [metabase.mbql.util :as mbql.u] @@ -25,7 +26,6 @@ [metabase.util.log :as log] [toucan.db :as db] [toucan.hydrate :refer [hydrate]] - [toucan.models :as models] [toucan2.core :as t2]) (:refer-clojure :exclude [descendants])) @@ -92,8 +92,8 @@ (defn infer-self-path "Returns `{:model \"ModelName\" :id \"id-string\"}`" [model-name entity] - (let [model (db/resolve-model (symbol model-name)) - pk (models/primary-key model)] + (let [model (mdb.u/resolve-model (symbol model-name)) + pk (mdb.u/primary-key model)] {:model model-name :id (or (entity-id model-name entity) (some-> (get entity pk) model identity-hash))})) @@ -197,8 +197,8 @@ Returns the Clojure map." [model-name entity] - (let [model (db/resolve-model (symbol model-name)) - pk (models/primary-key model)] + (let [model (mdb.u/resolve-model (symbol model-name)) + pk (mdb.u/primary-key model)] (-> (into {} entity) (assoc :serdes/meta (generate-path model-name entity)) (dissoc pk :updated_at)))) @@ -246,7 +246,7 @@ (defmethod load-find-local :default [path] (let [{id :id model-name :model} (last path) - model (db/resolve-model (symbol model-name))] + model (mdb.u/resolve-model (symbol model-name))] (when model (lookup-by-id model id)))) @@ -300,8 +300,8 @@ (fn [model _ _] model)) (defmethod load-update! :default [model-name ingested local] - (let [model (db/resolve-model (symbol model-name)) - pk (models/primary-key model) + (let [model (mdb.u/resolve-model (symbol model-name)) + pk (mdb.u/primary-key model) id (get local pk)] (log/tracef "Upserting %s %d: old %s new %s" model-name id (pr-str local) (pr-str ingested)) (t2/update! model id ingested) @@ -445,8 +445,8 @@ [id model] (when id (let [model-name (name model) - model (db/resolve-model (symbol model-name)) - entity (t2/select-one model (models/primary-key model) id) + model (mdb.u/resolve-model (symbol model-name)) + entity (t2/select-one model (mdb.u/primary-key model) id) path (mapv :id (generate-path model-name entity))] (if (= (count path) 1) (first path) @@ -466,13 +466,13 @@ [eid model] (when eid (let [model-name (name model) - model (db/resolve-model (symbol model-name)) + model (mdb.u/resolve-model (symbol model-name)) eid (if (vector? eid) (last eid) eid) entity (lookup-by-id model eid)] (if entity - (get entity (models/primary-key model)) + (get entity (mdb.u/primary-key model)) (throw (ex-info "Could not find foreign key target - bad serdes-dependencies or other serialization error" {:entity_id eid :model (name model)})))))) diff --git a/test/metabase/test.clj b/test/metabase/test.clj index b681934beab..7d2fb15406e 100644 --- a/test/metabase/test.clj +++ b/test/metabase/test.clj @@ -15,6 +15,7 @@ [medley.core :as m] [metabase.actions.test-util :as actions.test-util] [metabase.config :as config] + [metabase.db.util :as mdb.u] [metabase.driver :as driver] [metabase.driver.sql-jdbc.test-util :as sql-jdbc.tu] [metabase.driver.sql.query-processor-test-util :as sql.qp-test-util] @@ -48,7 +49,6 @@ [pjstadig.humane-test-output :as humane-test-output] [potemkin :as p] [toucan.db :as db] - [toucan.models :as models] [toucan.util.test :as tt] [toucan2.core :as t2])) @@ -316,7 +316,7 @@ temp-admin (first (t2/insert-returning-instances! User (merge (with-temp-defaults User) attributes {:is_superuser true}))) - primary-key (models/primary-key User)] + primary-key (mdb.u/primary-key User)] (try (thunk temp-admin) (finally @@ -420,7 +420,7 @@ (fn [toucan-model] (hawk.init/assert-tests-are-not-initializing (list 'object-defaults (symbol (name toucan-model)))) (initialize/initialize-if-needed! :db) - (db/resolve-model toucan-model)))) + (mdb.u/resolve-model toucan-model)))) (defmacro disable-flaky-test-when-running-driver-tests-in-ci "Only run `body` when we're not running driver tests in CI (i.e., `DRIVERS` and `CI` are both not set). Perfect for diff --git a/test/metabase/test/util.clj b/test/metabase/test/util.clj index f79dc664973..fd0523ffdf9 100644 --- a/test/metabase/test/util.clj +++ b/test/metabase/test/util.clj @@ -13,6 +13,7 @@ [hawk.parallel] [java-time :as t] [metabase.db.query :as mdb.query] + [metabase.db.util :as mdb.u] [metabase.models :refer [Card Collection @@ -59,7 +60,6 @@ [metabase.util :as u] [metabase.util.files :as u.files] [methodical.core :as methodical] - [toucan.db :as db] [toucan.models :as models] [toucan.util.test :as tt] [toucan2.core :as t2] @@ -474,7 +474,7 @@ (hawk.parallel/assert-test-is-not-parallel "with-temp-vals-in-db") ;; use low-level `query` and `execute` functions here, because Toucan `select` and `update` functions tend to do ;; things like add columns like `common_name` that don't actually exist, causing subsequent update to fail - (let [model (db/resolve-model model) + (let [model (mdb.u/resolve-model model) [original-column->value] (mdb.query/query {:select (keys column->temp-value) :from [(t2/table-name model)] :where [:= :id (u/the-id object-or-id)]})] @@ -621,7 +621,7 @@ (hawk.parallel/assert-test-is-not-parallel "with-model-cleanup") (initialize/initialize-if-needed! :db) (let [model->old-max-id (into {} (for [model models] - [model (:max-id (t2/select-one [model [(keyword (str "%max." (name (models/primary-key model)))) + [model (:max-id (t2/select-one [model [(keyword (str "%max." (name (mdb.u/primary-key model)))) :max-id]]))]))] (try (testing (str "\n" (pr-str (cons 'with-model-cleanup (map name models))) "\n") @@ -631,7 +631,7 @@ ;; might not have an old max ID if this is the first time the macro is used in this test run. :let [old-max-id (or (get model->old-max-id model) 0) - max-id-condition [:> (models/primary-key model) old-max-id] + max-id-condition [:> (mdb.u/primary-key model) old-max-id] additional-conditions (with-model-cleanup-additional-conditions model)]] (t2/query-one {:delete-from (t2/table-name model) -- GitLab