commit 74032e5e0a5a70dc45a6a744d37b9ba24eee8d01 Author: Nicola Mometto Date: Mon May 18 15:36:59 2026 +0100 introduce validate-db-details multimethod (#74343) * feat: introduce validate-db-details multimethod * include mysql * add to changelog diff --git a/enterprise/backend/src/metabase_enterprise/database_routing/api.clj b/enterprise/backend/src/metabase_enterprise/database_routing/api.clj index f69dbe0c0a9..82c8f026a12 100644 --- a/enterprise/backend/src/metabase_enterprise/database_routing/api.clj +++ b/enterprise/backend/src/metabase_enterprise/database_routing/api.clj @@ -3,6 +3,7 @@ [metabase.api.common :as api] [metabase.api.macros :as api.macros] [metabase.api.routes.common :refer [+auth]] + [metabase.driver :as driver] [metabase.driver.util :as driver.u] [metabase.events.core :as events] [metabase.settings.core :as setting] @@ -17,9 +18,9 @@ (api.macros/defendpoint :post "/destination-database" "Create new Destination Databases. - Note that unlike the normal `POST /api/database` endpoint, does NOT check the details before adding the Database. - - This is OK, it's not an invariant that all database details are always valid, but it's something to note." + Note that unlike the normal `POST /api/database` endpoint, this endpoint does not test that the database is actually + reachable before adding it — destination details are not required to be valid at creation time, and an + unreachable destination is fine." [_route-params {:keys [check_connection_details]} :- [:map [:check_connection_details {:optional true} ms/MaybeBooleanValue]] @@ -34,14 +35,17 @@ (api/check-400 (not (t2/exists? :model/Database :router_database_id router_database_id :name [:in (map :name destinations)])) "A destination database with that name already exists.") (let [{:keys [engine auto_run_queries is_on_demand] :as router-db} (t2/select-one :model/Database :id router_database_id)] - (if-let [invalid-destinations (and check_connection_details - (->> destinations - (keep (fn [{details :details n :name}] - (let [details-or-error (api.database/test-connection-details (name engine) details) - valid? (not= (:valid details-or-error) false)] - (when-not valid? - [n (dissoc details-or-error :valid)])))) - seq))] + (if-let [invalid-destinations (->> destinations + (keep (fn [{details :details n :name}] + (try + (driver/validate-db-details! engine details) + (when check_connection_details + (let [details-or-error (api.database/test-connection-details (name engine) details)] + (when (false? (:valid details-or-error)) + [n (dissoc details-or-error :valid)]))) + (catch Throwable e + [n {:message (ex-message e)}])))) + seq)] {:status 400 :body (into {} invalid-destinations)} (u/prog1 (t2/insert-returning-instances! diff --git a/src/metabase/driver/h2.clj b/src/metabase/driver/h2.clj index 3c59200f854..1f538cbab2e 100644 --- a/src/metabase/driver/h2.clj +++ b/src/metabase/driver/h2.clj @@ -115,8 +115,8 @@ bad-markers))] (pred s))) -(defmethod driver/can-connect? :h2 - [driver {:keys [db] :as details}] +(defmethod driver/validate-db-details! :h2 + [_driver {:keys [db]}] (when-not driver.settings/*allow-testing-h2-connections* (throw (ex-info (tru "H2 is not supported as a data warehouse") {:status-code 400}))) (when (string? db) @@ -132,7 +132,11 @@ ;; keys are uppercased by h2 when parsed: ;; https://github.com/h2database/h2database/blob/master/h2/src/main/org/h2/engine/ConnectionInfo.java#L298 (when (contains? properties "INIT") - (throw (ex-info "INIT not allowed" {:keys ["INIT"]}))))) + (throw (ex-info "INIT not allowed" {:keys ["INIT"]})))))) + +(defmethod driver/can-connect? :h2 + [driver details] + (driver/validate-db-details! driver details) (sql-jdbc.conn/can-connect? driver details)) (defmethod driver/db-start-of-week :h2