diff --git a/.clj-kondo/config.edn b/.clj-kondo/config.edn
index d6b354d5f9ac2331ad74e4d759d2a64395ab3e1f..0422b6e03482069f4345d1420268424c8e444e39 100644
--- a/.clj-kondo/config.edn
+++ b/.clj-kondo/config.edn
@@ -14,6 +14,7 @@
   :redundant-fn-wrapper         {:level :warning}
   :refer-all                    {:level :warning, :exclude [clojure.test]}
   :single-key-in                {:level :warning}
+  :unsorted-required-namespaces {:level :warning}
   :unused-referred-var          {:exclude {compojure.core [GET DELETE POST PUT]}}
   :use                          {:level :warning}
   :warn-on-reflection           {:level :warning}
@@ -22,6 +23,7 @@
   :metabase/i-like-making-cams-eyes-bleed-with-horrifically-long-tests {:level :warning}
   :metabase/mbql-query-first-arg                                       {:level :warning}
   :metabase/missing-test-expr-requires-in-cljs                         {:level :warning}
+  :metabase/require-shape-checker                                      {:level :warning}
   :metabase/test-helpers-use-non-thread-safe-functions                 {:level :warning}
   :metabase/validate-deftest                                           {:level :warning}
   :metabase/validate-logging                                           {:level :warning}
@@ -122,7 +124,138 @@
   {:exclude
    [colorize.core]}
 
-  :unsorted-required-namespaces {:level :warning}
+  ;; A "module" is any `metabase.x` namespace in `src`.
+  :metabase/ns-module-checker
+  {:level :warning
+
+   ;; Map of module name => the "core" external public-facing API namespace(s). You have three options here:
+   ;;
+   ;; 1. Special sentinel value `:any` means means this module does not (yet) have external public-facing API
+   ;;    namespace(s). This is mostly a temporary placeholder until we go in and create module namespaces, which means
+   ;;    you should go create one.
+   ;;
+   ;; 2. A set of namespace symbols. All namespaces in other modules will only be allowed ;; to use namespaces from
+   ;;    this set. Ideally this set should only have one namespace, but restricting it to a set of several is still
+   ;;    better than `:any`.
+   ;;
+   ;; 3. `nil` or not listed here -- we default to assuming there is one API namespace that matches the name of the
+   ;;    module itself, e.g. the module namespace for `metabase.setup` would be `metabase.setup`.
+   ;;
+   ;; `nil` or an empty set  Otherwise this should be a set of namespace symbols.
+   :api-namespaces
+   {metabase.actions               :any
+    metabase.analytics             :any
+    metabase.analyze               :any
+    metabase.api                   :any
+    metabase.async                 :any
+    metabase.automagic-dashboards  :any
+    metabase.bootstrap             #{metabase.bootstrap}
+    metabase.cmd                   :any
+    metabase.config                #{metabase.config}
+    metabase.core                  :any
+    metabase.db                    #{metabase.db
+                                     metabase.db.metadata-queries ; FIXME, this should probably be separate from metabase.db
+                                     metabase.db.query            ; FIXME, this is mostly util stuff like `metabase.db.query/query` that we don't even need anymore.
+                                     metabase.db.setup}           ; FIXME, these are only calling `metabase.db.setup/setup-db!` and there's a slightly different version in `metabase.db`
+    metabase.domain-entities       :any
+    metabase.driver                :any
+    metabase.email                 :any
+    metabase.embed                 :any
+    metabase.events                :any
+    metabase.formatter             :any
+    metabase.integrations          :any
+    metabase.legacy-mbql           :any
+    metabase.lib                   :any
+    metabase.logger                #{metabase.logger}
+    metabase.metabot               :any
+    metabase.models                :any
+    metabase.moderation            #{metabase.moderation}
+    metabase.native-query-analyzer :any
+    metabase.permissions           :any
+    metabase.plugins               :any
+    metabase.public-settings       :any
+    metabase.pulse                 :any
+    metabase.query-processor       :any
+    metabase.related               #{metabase.related}
+    metabase.sample-data           #{metabase.sample-data}
+    metabase.search                :any
+    metabase.server                :any
+    metabase.setup                 #{metabase.setup}
+    metabase.shared                :any
+    metabase.sync                  :any
+    metabase.task                  :any
+    metabase.transforms            :any
+    metabase.troubleshooting       #{metabase.troubleshooting}
+    metabase.types                 #{metabase.types}
+    metabase.upload                :any
+    metabase.util                  :any}
+
+   ;; Map of module => other modules you're allowed to use there. You have two options here:
+   ;;
+   ;; 1. `:any` means namespaces in this module are allowed to use any other module -- allowed modules are not
+   ;;    enforced for this module. Module API namespaces for modules that have them defined are still enforced. For
+   ;;    ones that are `nil`, please go in and add a list of allowed modules. `:any` is mostly meant a temporary
+   ;;    placeholder until we can fill these all out, so feel free to fix these.
+   ;;
+   ;; 2. A set of module symbols. This is the list of modules that are allowed to be referenced. An empty set means no
+   ;;    other modules are allowed to be referenced; this is the default for any modules that aren't listed here.
+   :allowed-modules
+   {metabase.actions               :any
+    metabase.analytics             :any
+    metabase.analyze               :any
+    metabase.api                   :any
+    metabase.async                 :any
+    metabase.automagic-dashboards  :any
+    metabase.bootstrap             #{}
+    metabase.cmd                   :any
+    metabase.config                #{metabase.plugins}
+    metabase.core                  :any
+    metabase.db                    :any
+    metabase.domain-entities       :any
+    metabase.driver                :any
+    metabase.email                 :any
+    metabase.embed                 :any
+    metabase.events                :any
+    metabase.formatter             :any
+    metabase.integrations          :any
+    metabase.legacy-mbql           :any
+    metabase.lib                   :any
+    metabase.logger                #{metabase.config
+                                     metabase.plugins}
+    metabase.metabot               :any
+    metabase.models                :any
+    metabase.moderation            :any
+    metabase.native-query-analyzer :any
+    metabase.permissions           :any
+    metabase.plugins               :any
+    metabase.public-settings       :any
+    metabase.pulse                 :any
+    metabase.query-processor       :any
+    metabase.related               :any
+    metabase.sample-data           :any
+    metabase.search                :any
+    metabase.server                :any
+    metabase.setup                 :any
+    metabase.shared                :any
+    metabase.sync                  :any
+    metabase.task                  :any
+    metabase.transforms            :any
+    metabase.troubleshooting       :any
+    metabase.types                 #{metabase.util}
+    metabase.upload                :any
+    metabase.util                  :any}
+
+   ;; namespaces matching these patterns (with `re-find`) are excluded from the module linter. Since regex literals
+   ;; aren't allowed in EDN just used the `(str <regex>)` version i.e. two slashes instead of one.
+   ;;
+   ;; this is mostly intended for excluding test namespaces or those rare 'glue' namespaces that glue multiple modules
+   ;; together, e.g. `metabase.lib.metadata.jvm`.
+   :ignored-namespace-patterns
+   #{"-test$"                     ; anything ending in `-test`
+     "test[-.]util"               ; anything that contains `test.util` or `test-util`
+     "test\\.impl"                ; anything that contains `test.impl`
+     "^metabase\\.test"           ; anything starting with `metabase.test`
+     "^metabase\\.http-client$"}} ; `metabase.http-client` which is a test-only namespace despite its name.
 
   :consistent-alias
   {:aliases
@@ -491,7 +624,8 @@
 
  :hooks
  {:analyze-call
-  {cljs.test/deftest                                                                                                         hooks.clojure.test/deftest
+  {clojure.core/ns                                                                                                           hooks.clojure.core/lint-ns
+   cljs.test/deftest                                                                                                         hooks.clojure.test/deftest
    cljs.test/is                                                                                                              hooks.clojure.test/is
    cljs.test/use-fixtures                                                                                                    hooks.clojure.test/use-fixtures
    clojure.test/deftest                                                                                                      hooks.clojure.test/deftest
diff --git a/.clj-kondo/hooks/clojure/core.clj b/.clj-kondo/hooks/clojure/core.clj
index 39a6a2cd0b564517ca8c9b401bc3560e5017bc9c..71e45d1205e3ae4118b3012d646ad3eae28399dc 100644
--- a/.clj-kondo/hooks/clojure/core.clj
+++ b/.clj-kondo/hooks/clojure/core.clj
@@ -1,6 +1,7 @@
 (ns hooks.clojure.core
   (:require
    [clj-kondo.hooks-api :as hooks]
+   [clojure.set :as set]
    [clojure.string :as str]))
 
 (defn- node->qualified-symbol [node]
@@ -15,7 +16,7 @@
    (catch Exception _
      nil)))
 
-(def ^:private white-card-symbols
+(def ^:private symbols-allowed-in-fns-not-ending-in-an-exclamation-point
   '#{;; these toucan methods might actually set global values if it's used outside of a transaction,
      ;; but since mt/with-temp runs in a transaction, so we'll ignore them in this case.
      toucan2.core/delete!
@@ -159,7 +160,7 @@
                 (walk f child)))]
       (walk (fn [form]
               (when-let [qualified-symbol (node->qualified-symbol form)]
-                (when (and (not (contains? white-card-symbols qualified-symbol))
+                (when (and (not (contains? symbols-allowed-in-fns-not-ending-in-an-exclamation-point qualified-symbol))
                            (end-with-exclamation? qualified-symbol))
                   (hooks/reg-finding! (assoc (meta form-name)
                                              :message (format "The name of this %s should end with `!` because it contains calls to non thread safe form `%s`."
@@ -203,3 +204,123 @@
        :findings))
 
  (do (non-thread-safe-form-should-end-with-exclamation* (hooks/parse-string form)) nil))
+
+(defn- ns-form-node->require-node [ns-form-node]
+  (some (fn [node]
+          (when (and (hooks/list-node? node)
+                     (let [first-child (first (:children node))]
+                       (and (hooks/keyword-node? first-child)
+                            (= (hooks/sexpr first-child) :require))))
+            node))
+        (:children ns-form-node)))
+
+(defn- lint-require-shapes [ns-form-node]
+  (doseq [node (-> ns-form-node
+                   ns-form-node->require-node
+                   :children
+                   rest)]
+    (cond
+      (not (hooks/vector-node? node))
+      (hooks/reg-finding! (assoc (meta node)
+                                 :message "All :required namespaces should be wrapped in vectors [:metabase/require-shape-checker]"
+                                 :type    :metabase/require-shape-checker))
+
+      (hooks/vector-node? (second (:children node)))
+      (hooks/reg-finding! (assoc (meta node)
+                                 :message "Don't use prefix forms inside :require [:metabase/require-shape-checker]"
+                                 :type    :metabase/require-shape-checker)))))
+
+(defn- require-node->namespace-symb-nodes [require-node]
+  (let [[_ns & args] (:children require-node)]
+    (into []
+          ;; prefixed namespace forms are NOT SUPPORTED!!!!!!!!1
+          (keep (fn [node]
+                  (cond
+                    (hooks/vector-node? node)
+                    (first (:children node))
+
+                    (hooks/token-node? node)
+                    node
+
+                    :else
+                    (printf "Don't know how to figure out what namespace is being required in %s\n" (pr-str node)))))
+          args)))
+
+(defn- ns-form-node->ns-symb [ns-form-node]
+  (some-> (some (fn [node]
+                  (when (and (hooks/token-node? node)
+                             (not= (hooks/sexpr node) 'ns))
+                    node))
+                (:children ns-form-node))
+          hooks/sexpr))
+
+(defn- module
+  "E.g.
+
+    (module 'metabase.qp.middleware.wow) => 'metabase.qp"
+  [ns-symb]
+  (some-> (re-find #"^metabase\.[^.]+" (str ns-symb)) symbol))
+
+(defn- ignored-namespace? [ns-symb config]
+  (some
+   (fn [pattern-str]
+     (re-find (re-pattern pattern-str) (str ns-symb)))
+   (:ignored-namespace-patterns config)))
+
+(defn- module-api-namespaces
+  "Set API namespaces for a given module. `:any` means you can use anything, there are no API namespaces for this
+  module (yet). If unspecified, the default is just the namespace with the same name as the module e.g.
+  `metabase.db`."
+  [module config]
+  (let [module-config (get-in config [:api-namespaces module])]
+    (cond
+      (= module-config :any)
+      nil
+
+      (set? module-config)
+      module-config
+
+      :else
+      #{module})))
+
+(defn- lint-modules [ns-form-node config]
+  (let [ns-symb (ns-form-node->ns-symb ns-form-node)]
+    (when-not (ignored-namespace? ns-symb config)
+      (when-let [current-module (module ns-symb)]
+        (let [allowed-modules               (get-in config [:allowed-modules current-module])
+              required-namespace-symb-nodes (-> ns-form-node
+                                                ns-form-node->require-node
+                                                require-node->namespace-symb-nodes)]
+          (doseq [node  required-namespace-symb-nodes
+                  :let  [required-namespace (hooks/sexpr node)
+                         required-module    (module required-namespace)]
+                ;; ignore stuff not in a module i.e. non-Metabase stuff.
+                  :when required-module
+                  :let  [in-current-module? (= required-module current-module)]
+                  :when (not in-current-module?)
+                  :let  [allowed-module?           (or (= allowed-modules :any)
+                                                       (contains? (set allowed-modules) required-module))
+                         module-api-namespaces     (module-api-namespaces required-module config)
+                         allowed-module-namespace? (or (empty? module-api-namespaces)
+                                                       (contains? module-api-namespaces required-namespace))]]
+            (when-let [error (cond
+                               (not allowed-module?)
+                               (format "Module %s should not be used in the %s module. [:metabase/ns-module-checker :allowed-modules %s]"
+                                       required-module
+                                       current-module
+                                       current-module)
+
+                               (not allowed-module-namespace?)
+                               (format "Namespace %s is not an allowed external API namespace for the %s module. [:metabase/ns-module-checker :api-namespaces %s]"
+                                       required-namespace
+                                       required-module
+                                       required-module))]
+              (hooks/reg-finding! (assoc (meta node)
+                                         :message error
+                                         :type    :metabase/ns-module-checker)))))))))
+
+
+(defn lint-ns [x]
+  (lint-require-shapes (:node x))
+  (lint-modules (:node x) (get-in x [:config :linters :metabase/ns-module-checker]))
+  x)
diff --git a/src/metabase/api/collection.clj b/src/metabase/api/collection.clj
index 142c60639d8461e838d4fbc0b9402ad68df1511f..8b060092e4185e773c6d05ea174c715a3595dd84 100644
--- a/src/metabase/api/collection.clj
+++ b/src/metabase/api/collection.clj
@@ -205,9 +205,9 @@
                                           (update acc (if (= (keyword card-type) :model) :dataset :card) conj collection-id))
                                         {:dataset #{}
                                          :card    #{}}
-                                        (mdb.query/reducible-query {:select-distinct [:collection_id :type]
-                                                                    :from            [:report_card]
-                                                                    :where           [:= :archived false]}))
+                                        (t2/reducible-query {:select-distinct [:collection_id :type]
+                                                             :from            [:report_card]
+                                                             :where           [:= :archived false]}))
             collections-with-details (map collection/personal-collection-with-ui-details collections)]
         (collection/collections->tree collection-type-ids collections-with-details)))))
 
@@ -563,11 +563,11 @@
                   (update acc (if (= (keyword card-type) :model) :dataset :card) conj collection-id))
                 {:dataset #{}
                  :card    #{}}
-                (mdb.query/reducible-query {:select-distinct [:collection_id :type]
-                                            :from            [:report_card]
-                                            :where           [:and
-                                                              [:= :archived false]
-                                                              [:in :collection_id descendant-collection-ids]]}))
+                (t2/reducible-query {:select-distinct [:collection_id :type]
+                                     :from            [:report_card]
+                                     :where           [:and
+                                                       [:= :archived false]
+                                                       [:in :collection_id descendant-collection-ids]]}))
 
         collections-containing-dashboards
         (->> (t2/query {:select-distinct [:collection_id]
diff --git a/src/metabase/api/database.clj b/src/metabase/api/database.clj
index 8059c9e26b7cc167fffba6c219d7fdce866c30b0..8a78ba1f89698bfb542025d2026fc977e9c7615e 100644
--- a/src/metabase/api/database.clj
+++ b/src/metabase/api/database.clj
@@ -160,27 +160,27 @@
            xform)
      (completing conj #(t2/hydrate % :collection))
      []
-     (mdb.query/reducible-query {:select   [:name :description :database_id :dataset_query :id :collection_id :result_metadata
-                                            [{:select   [:status]
-                                              :from     [:moderation_review]
-                                              :where    [:and
-                                                         [:= :moderated_item_type "card"]
-                                                         [:= :moderated_item_id :report_card.id]
-                                                         [:= :most_recent true]]
-                                              :order-by [[:id :desc]]
-                                              :limit    1}
-                                             :moderated_status]]
-                                 :from     [:report_card]
-                                 :where    (into [:and
-                                                  [:not= :result_metadata nil]
-                                                  [:= :archived false]
-                                                  [:= :type (u/qualified-name card-type)]
-                                                  [:in :database_id ids-of-dbs-that-support-source-queries]
-                                                  (collection/visible-collection-ids->honeysql-filter-clause
-                                                   (collection/permissions-set->visible-collection-ids
-                                                    @api/*current-user-permissions-set*))]
-                                                 additional-constraints)
-                                 :order-by [[:%lower.name :asc]]}))))
+     (t2/reducible-query {:select   [:name :description :database_id :dataset_query :id :collection_id :result_metadata
+                                     [{:select   [:status]
+                                       :from     [:moderation_review]
+                                       :where    [:and
+                                                  [:= :moderated_item_type "card"]
+                                                  [:= :moderated_item_id :report_card.id]
+                                                  [:= :most_recent true]]
+                                       :order-by [[:id :desc]]
+                                       :limit    1}
+                                      :moderated_status]]
+                          :from     [:report_card]
+                          :where    (into [:and
+                                           [:not= :result_metadata nil]
+                                           [:= :archived false]
+                                           [:= :type (u/qualified-name card-type)]
+                                           [:in :database_id ids-of-dbs-that-support-source-queries]
+                                           (collection/visible-collection-ids->honeysql-filter-clause
+                                            (collection/permissions-set->visible-collection-ids
+                                             @api/*current-user-permissions-set*))]
+                                          additional-constraints)
+                          :order-by [[:%lower.name :asc]]}))))
 
 (mu/defn ^:private source-query-cards-exist?
   "Truthy if a single Card that can be used as a source query exists."
diff --git a/src/metabase/api/search.clj b/src/metabase/api/search.clj
index ad80dc7c7255059e20c27e9f240182bfebde0823..95ba7afc01f310cedaf3acf5f09f44cd4d2cbabd 100644
--- a/src/metabase/api/search.clj
+++ b/src/metabase/api/search.clj
@@ -27,6 +27,7 @@
    [metabase.util.malli.schema :as ms]
    [toucan2.core :as t2]
    [toucan2.instance :as t2.instance]
+   [toucan2.jdbc.options :as t2.jdbc.options]
    [toucan2.realize :as t2.realize]))
 
 (set! *warn-on-reflection* true)
@@ -499,7 +500,10 @@
         to-toucan-instance (fn [row]
                              (let [model (-> row :model search.config/model-to-db-model :db-model)]
                                (t2.instance/instance model row)))
-        reducible-results  (mdb.query/reducible-query search-query :max-rows search.config/*db-max-results*)
+        reducible-results  (reify clojure.lang.IReduceInit
+                             (reduce [_this rf init]
+                               (binding [t2.jdbc.options/*options* (assoc t2.jdbc.options/*options* :max-rows search.config/*db-max-results*)]
+                                 (reduce rf init (t2/reducible-query search-query)))))
         xf                 (comp
                             (map t2.realize/realize)
                             (map to-toucan-instance)
diff --git a/src/metabase/cmd/copy.clj b/src/metabase/cmd/copy.clj
index 5d7f30cf33870f47d9b95802c72ec5260694ce33..b000d3dc1431a7d766b42a155bd21cc30553a161 100644
--- a/src/metabase/cmd/copy.clj
+++ b/src/metabase/cmd/copy.clj
@@ -111,7 +111,7 @@
   ;; should be ok now that #16344 is resolved -- we might be able to remove this code entirely now. Quoting identifiers
   ;; is still a good idea tho.)
   (let [source-keys (keys (first objs))
-        quote-fn    (partial mdb.setup/quote-for-application-db (mdb/quoting-style target-db-type))
+        quote-fn    (partial mdb/quote-for-application-db (mdb/quoting-style target-db-type))
         dest-keys   (for [k source-keys]
                       (quote-fn (name k)))]
     {:cols dest-keys
diff --git a/src/metabase/cmd/copy/h2.clj b/src/metabase/cmd/copy/h2.clj
index e2eea7f4a23bd4d0dd2e6a46752b14104480e77f..2884f197fe9e6295caa718f648d4718de36a6d39 100644
--- a/src/metabase/cmd/copy/h2.clj
+++ b/src/metabase/cmd/copy/h2.clj
@@ -3,7 +3,7 @@
   (:require
    [clojure.java.io :as io]
    [clojure.string :as str]
-   [metabase.db.data-source :as mdb.data-source]
+   [metabase.db :as mdb]
    [metabase.util :as u]
    [metabase.util.log :as log]))
 
@@ -25,7 +25,7 @@
   "Create a [[javax.sql.DataSource]] for the H2 database with `h2-filename`."
   ^javax.sql.DataSource [h2-filename]
   (let [h2-filename (add-file-prefix-if-needed h2-filename)]
-    (mdb.data-source/broken-out-details->DataSource :h2 {:db h2-filename})))
+    (mdb/broken-out-details->DataSource :h2 {:db h2-filename})))
 
 (defn delete-existing-h2-database-files!
   "Delete existing h2 database files."
diff --git a/src/metabase/cmd/dump_to_h2.clj b/src/metabase/cmd/dump_to_h2.clj
index 57631e2abdf7f05c211509b397a3521416e992e2..bb3888f28e8e887f6c5b1bc5fe377b438b1ac1cb 100644
--- a/src/metabase/cmd/dump_to_h2.clj
+++ b/src/metabase/cmd/dump_to_h2.clj
@@ -17,7 +17,6 @@
    [metabase.cmd.copy.h2 :as copy.h2]
    [metabase.cmd.rotate-encryption-key :as rotate-encryption]
    [metabase.db :as mdb]
-   [metabase.db.connection :as mdb.connection]
    [metabase.util.log :as log]))
 
 (defn dump-to-h2!
@@ -39,5 +38,5 @@
        (copy.h2/delete-existing-h2-database-files! h2-filename))
      (copy/copy! (mdb/db-type) (mdb/data-source) :h2 h2-data-source)
      (when dump-plaintext?
-       (binding [mdb.connection/*application-db* (mdb.connection/application-db :h2 h2-data-source)]
+       (mdb/with-application-db (mdb/application-db :h2 h2-data-source)
          (rotate-encryption/rotate-encryption-key! nil))))))
diff --git a/src/metabase/db.clj b/src/metabase/db.clj
index 054cad91f6d355da240527e6e35712ea1dd1b162..478f311bf019eb10273ee0e677de6a6ed7bb4578 100644
--- a/src/metabase/db.clj
+++ b/src/metabase/db.clj
@@ -11,7 +11,9 @@
    [metabase.config :as config]
    [metabase.db.connection :as mdb.connection]
    [metabase.db.connection-pool-setup :as mdb.connection-pool-setup]
+   [metabase.db.data-source :as mdb.data-source]
    [metabase.db.env :as mdb.env]
+   [metabase.db.jdbc-protocols :as mdb.jdbc-protocols]
    [metabase.db.setup :as mdb.setup]
    [metabase.db.spec :as mdb.spec]
    [potemkin :as p]))
@@ -19,24 +21,32 @@
 (set! *warn-on-reflection* true)
 
 (p/import-vars
- [mdb.connection
-  quoting-style
-  db-type
-  unique-identifier
-  data-source]
+  [mdb.connection
+   application-db
+   data-source
+   db-type
+   quoting-style
+   unique-identifier]
 
- [mdb.connection-pool-setup
-  recent-activity?]
+  [mdb.connection-pool-setup
+   recent-activity?]
 
- [mdb.env
-  db-file]
+  [mdb.data-source
+   broken-out-details->DataSource]
 
- [mdb.setup
-  migrate!]
+  [mdb.env
+   db-file]
 
- [mdb.spec
-  make-subname
-  spec])
+  [mdb.jdbc-protocols
+   clob->str]
+
+  [mdb.setup
+   migrate!
+   quote-for-application-db]
+
+  [mdb.spec
+   make-subname
+   spec])
 
 ;; TODO -- consider whether we can just do this automatically when `getConnection` is called on
 ;; [[mdb.connection/*application-db*]] (or its data source)
@@ -99,3 +109,15 @@
   (assert (or (not config/is-prod?)
               (config/config-bool :mb-enable-test-endpoints)))
   (alter-var-root #'mdb.connection/*application-db* assoc :id (swap! mdb.connection/application-db-counter inc)))
+
+(defn do-with-application-db
+  "Impl for [[with-application-db]] macro."
+  [application-db thunk]
+  (binding [mdb.connection/*application-db* application-db]
+    (thunk)))
+
+(defmacro with-application-db
+  "Bind the current application database and execute body."
+  {:style/indent [:defn]}
+  [application-db & body]
+  `(do-with-application-db ~application-db (^:once fn* [] ~@body)))
diff --git a/src/metabase/db/query.clj b/src/metabase/db/query.clj
index f313386b08718743ba7e78def48263f92a3fc94a..2cba988316c8aafba4fe1a5d7ef6aa332f11178a 100644
--- a/src/metabase/db/query.clj
+++ b/src/metabase/db/query.clj
@@ -158,25 +158,6 @@
                            :uncompiled sql-args-or-honey-sql-map}
                           e)))))))
 
-(defn reducible-query
-  "Replacement for [[toucan.db/reducible-query]] -- uses Honey SQL 2 instead of Honey SQL 1, to ease the transition to
-  the former (and to Toucan 2).
-
-  Query the application database and return an `IReduceInit`.
-
-  See namespace documentation for [[metabase.db.query]] for pro debugging tips."
-  [sql-args-or-honey-sql-map & {:as jdbc-options}]
-  ;; make sure [[metabase.db.setup]] gets loaded so default Honey SQL options and the like are loaded.
-  (classloader/require 'metabase.db.setup)
-  (let [sql-args (compile sql-args-or-honey-sql-map)]
-    ;; It doesn't really make sense to put a try-catch around this since it will return immediately and not execute
-    ;; until we actually reduce it
-    (reify clojure.lang.IReduceInit
-      (reduce [_this rf init]
-        (binding [t2.jdbc.options/*options* (merge t2.jdbc.options/*options* jdbc-options)]
-          (reduce rf init (t2/reducible-query sql-args)))))))
-
-
 (defmacro with-conflict-retry
   "Retry a database mutation a single time if it fails due to concurrent insertions.
    May retry for other reasons."
diff --git a/src/metabase/driver/h2.clj b/src/metabase/driver/h2.clj
index 08e04016b55ed998f933398c6e484bd1202a5df3..80297726c7c00e2cb80309ce97884a6dccd4d7d0 100644
--- a/src/metabase/driver/h2.clj
+++ b/src/metabase/driver/h2.clj
@@ -5,7 +5,6 @@
    [java-time.api :as t]
    [metabase.config :as config]
    [metabase.db :as mdb]
-   [metabase.db.jdbc-protocols :as mdb.jdbc-protocols]
    [metabase.driver :as driver]
    [metabase.driver.common :as driver.common]
    [metabase.driver.h2.actions :as h2.actions]
@@ -552,7 +551,7 @@
                           (Class/forName true (classloader/the-classloader)))]
     (if (isa? classname Clob)
       (fn []
-        (mdb.jdbc-protocols/clob->str (.getObject rs i)))
+        (mdb/clob->str (.getObject rs i)))
       (fn []
         (.getObject rs i)))))
 
diff --git a/src/metabase/sync/sync_metadata/fks.clj b/src/metabase/sync/sync_metadata/fks.clj
index 35a2a7d29f24b4a83f537a7d6b9ce3bf0af6534e..2a90c587eaaaeb0493411e3570516cc8b55c765e 100644
--- a/src/metabase/sync/sync_metadata/fks.clj
+++ b/src/metabase/sync/sync_metadata/fks.clj
@@ -2,7 +2,7 @@
   "Logic for updating FK properties of Fields from metadata fetched from a physical DB."
   (:require
    [honey.sql :as sql]
-   [metabase.db.connection :as mdb.connection]
+   [metabase.db :as mdb]
    [metabase.driver :as driver]
    [metabase.driver.util :as driver.u]
    [metabase.models.table :as table]
@@ -42,7 +42,7 @@
                                    [:= :t.visibility_type nil]]})
         fk-field-id-query (field-id-query db-id fk-table-schema fk-table-name fk-column-name)
         pk-field-id-query (field-id-query db-id pk-table-schema pk-table-name pk-column-name)
-        q (case (mdb.connection/db-type)
+        q (case (mdb/db-type)
             :mysql
             {:update [:metabase_field :f]
              :join   [[fk-field-id-query :fk] [:= :fk.id :f.id]
@@ -76,7 +76,7 @@
                       [:or
                        [:= :f.fk_target_field_id nil]
                        [:not= :f.fk_target_field_id pk-field-id-query]]]})]
-    (sql/format q :dialect (mdb.connection/quoting-style (mdb.connection/db-type)))))
+    (sql/format q :dialect (mdb/quoting-style (mdb/db-type)))))
 
 (mu/defn ^:private mark-fk!
   "Updates the `fk_target_field_id` of a Field. Returns 1 if the Field was successfully updated, 0 otherwise."
diff --git a/src/metabase/task/sync_databases.clj b/src/metabase/task/sync_databases.clj
index fe543b100726c71418f50be733551ba1ca66359b..5df390ec5097f7f40ee3ef2fa0d3945522435a13 100644
--- a/src/metabase/task/sync_databases.clj
+++ b/src/metabase/task/sync_databases.clj
@@ -11,7 +11,6 @@
    [java-time.api :as t]
    [malli.core :as mc]
    [metabase.config :as config]
-   [metabase.db.query :as mdb.query]
    [metabase.driver.h2 :as h2]
    [metabase.driver.util :as driver.u]
    [metabase.lib.schema.id :as lib.schema.id]
@@ -347,7 +346,7 @@
                   (catch Exception e
                     (log/warnf e "Error updating database %d for randomized schedules" (u/the-id db))
                     counter))))
-             (mdb.query/reducible-query
+             (t2/reducible-query
               {:select [:id :details]
                :from   [:metabase_database]
                :where  [:or
diff --git a/test/metabase/cmd/compare_h2_dbs.clj b/test/metabase/cmd/compare_h2_dbs.clj
index c7e1c62c96541b855564d75e02ef9fa2bfa6895b..e5be938e35f36a10123cadb6c38b925b43ade22c 100644
--- a/test/metabase/cmd/compare_h2_dbs.clj
+++ b/test/metabase/cmd/compare_h2_dbs.clj
@@ -3,7 +3,7 @@
   (:require
    [clojure.data :as data]
    [clojure.java.jdbc :as jdbc]
-   [metabase.db.jdbc-protocols]
+   [metabase.db]
    [metabase.driver :as driver]
    [metabase.driver.sql-jdbc.execute :as sql-jdbc.execute]
    [metabase.util :as u]
@@ -11,7 +11,7 @@
 
 (set! *warn-on-reflection* true)
 
-(comment metabase.db.jdbc-protocols/keep-me)
+(comment metabase.db/keep-me)
 
 (defn- jdbc-spec [db-file]
   {:classname         "org.h2.Driver"