diff --git a/src/metabase/query_processor/macros.clj b/src/metabase/query_processor/macros.clj
deleted file mode 100644
index 6d1016528ff586871c37b5ac5e451c2aee6772ee..0000000000000000000000000000000000000000
--- a/src/metabase/query_processor/macros.clj
+++ /dev/null
@@ -1,117 +0,0 @@
-(ns metabase.query-processor.macros
-  "Code in charge of expanding [\"METRIC\" ...] and [\"SEGMENT\" ...] forms in MBQL queries.
-   (METRIC forms are expanded into aggregations and sometimes filter clauses, while SEGMENT forms
-    are expanded into filter clauses.)
-
-   TODO - this namespace is ancient and written with MBQL '95 in mind, e.g. it is case-sensitive.
-   At some point this ought to be reworked to be case-insensitive and cleaned up.
-
-   TODO - The actual middleware that applies these functions lives in `metabase.query-processor.middleware.expand-macros`.
-   Not sure those two namespaces need to be divided. We can probably move all the functions in this namespace into that
-   one; that might require shuffling around some tests as well."
-  (:require [clojure.core.match :refer [match]]
-            [clojure.walk :as walk]
-            [toucan.db :as db]))
-
-(defn- non-empty-clause? [clause]
-  (and clause
-       (or (not (sequential? clause))
-           (and (seq clause)
-                (not (every? nil? clause))))))
-
-;;; ------------------------------------------------------------ Segments ------------------------------------------------------------
-
-(defn- segment-parse-filter-subclause [form]
-  (when (non-empty-clause? form)
-    (match form
-      ["SEGMENT" (segment-id :guard integer?)] (:filter (db/select-one-field :definition 'Segment :id segment-id))
-      subclause                                subclause
-      form                                     (throw (java.lang.Exception. (format "segment-parse-filter-subclause failed: invalid clause: %s" form))))))
-
-(defn- segment-parse-filter [form]
-  (when (non-empty-clause? form)
-    (match form
-      ["AND" & subclauses] (into ["AND"] (mapv segment-parse-filter subclauses))
-      ["OR"  & subclauses] (into ["OR"]  (mapv segment-parse-filter subclauses))
-      subclause            (segment-parse-filter-subclause subclause)
-      form                 (throw (java.lang.Exception. (format "segment-parse-filter failed: invalid clause: %s" form))))))
-
-(defn- macroexpand-segment [query-dict]
-  (if (non-empty-clause? (get-in query-dict [:query :filter]))
-    (update-in query-dict [:query :filter] segment-parse-filter)
-    query-dict))
-
-(defn- merge-filter-clauses [base addtl]
-  (cond
-    (and (seq base)
-         (seq addtl)) ["AND" base addtl]
-    (seq base)        base
-    (seq addtl)       addtl
-    :else             []))
-
-
-;;; ------------------------------------------------------------ Metrics ------------------------------------------------------------
-
-(defn- metric? [aggregation]
-  (match aggregation
-    ["METRIC" (_ :guard integer?)] true
-    _                              false))
-
-(defn- metric-id [metric]
-  (when (metric? metric)
-    (second metric)))
-
-(defn- maybe-unnest-ag-clause
-  "Unnest AG-CLAUSE if it's wrapped in a vector (i.e. if it is using the \"multiple-aggregation\" syntax).
-   (This is provided merely as a convenience to facilitate implementation of the Query Builder, so it can use the same UI for
-   normal aggregations and Metric creation. *METRICS DO NOT SUPPORT MULTIPLE AGGREGATIONS,* so if nested syntax is used, any
-   aggregation after the first will be ignored.)"
-  [ag-clause]
-  (if (and (coll? ag-clause)
-           (every? coll? ag-clause))
-    (first ag-clause)
-    ag-clause))
-
-(defn- expand-metric [metric-clause filter-clauses-atom]
-  (let [{filter-clause :filter, ag-clause :aggregation} (db/select-one-field :definition 'Metric, :id (metric-id metric-clause))]
-    (when filter-clause
-      (swap! filter-clauses-atom conj filter-clause))
-    (maybe-unnest-ag-clause ag-clause)))
-
-(defn- expand-metrics-in-ag-clause [query-dict filter-clauses-atom]
-  (walk/postwalk (fn [form]
-                   (if-not (metric? form)
-                     form
-                     (expand-metric form filter-clauses-atom)))
-                 query-dict))
-
-(defn- add-metrics-filter-clauses
-  "Add any FILTER-CLAUSES to the QUERY-DICT. If query has existing filter clauses, the new ones are
-   combined with an `:and` filter clause."
-  [query-dict filter-clauses]
-  (if-not (seq filter-clauses)
-    query-dict
-    (update-in query-dict [:query :filter] merge-filter-clauses (if (> (count filter-clauses) 1)
-                                                                  (cons "AND" filter-clauses)
-                                                                  (first filter-clauses)))))
-
-(defn- expand-metrics [query-dict]
-  (let [filter-clauses-atom (atom [])
-        query-dict          (expand-metrics-in-ag-clause query-dict filter-clauses-atom)]
-    (add-metrics-filter-clauses query-dict @filter-clauses-atom)))
-
-(defn- macroexpand-metric [{{aggregations :aggregation} :query, :as query-dict}]
-  (if-not (seq aggregations)
-    ;; :aggregation is empty, so no METRIC to expand
-    query-dict
-    ;; otherwise walk the query dict and expand METRIC clauses
-    (expand-metrics query-dict)))
-
-
-;;; ------------------------------------------------------------ Middleware ------------------------------------------------------------
-
-(defn expand-macros "Expand the macros (SEGMENT, METRIC) in a QUERY-DICT."
-  [query-dict]
-  (-> query-dict
-      macroexpand-metric
-      macroexpand-segment))
diff --git a/src/metabase/query_processor/middleware/expand_macros.clj b/src/metabase/query_processor/middleware/expand_macros.clj
index f8f89df8653897c580696e08ae4c669d211853d7..039b95275cbcd68123970c5422d97ec70990f718 100644
--- a/src/metabase/query_processor/middleware/expand_macros.clj
+++ b/src/metabase/query_processor/middleware/expand_macros.clj
@@ -1,15 +1,131 @@
 (ns metabase.query-processor.middleware.expand-macros
-  "Middleware for expanding `METRIC` and `SEGMENT` 'macros' in *unexpanded* MBQL queries."
+  "Middleware for expanding `METRIC` and `SEGMENT` 'macros' in *unexpanded* MBQL queries.
+
+   Code in charge of expanding [\"METRIC\" ...] and [\"SEGMENT\" ...] forms in MBQL queries.
+   (METRIC forms are expanded into aggregations and sometimes filter clauses, while SEGMENT forms
+    are expanded into filter clauses.)
+
+   TODO - this namespace is ancient and written with MBQL '95 in mind, e.g. it is case-sensitive.
+   At some point this ought to be reworked to be case-insensitive and cleaned up."
   (:require [clojure.tools.logging :as log]
             (metabase.query-processor [interface :as i]
-                                      [macros :as macros]
                                       [util :as qputil])
+            [clojure.core.match :refer [match]]
+            [clojure.walk :as walk]
+            [toucan.db :as db]
             [metabase.util :as u]))
 
+
+;;; ------------------------------------------------------------ Utils ------------------------------------------------------------
+
+(defn- non-empty-clause? [clause]
+  (and clause
+       (or (not (sequential? clause))
+           (and (seq clause)
+                (not (every? nil? clause))))))
+
+;;; ------------------------------------------------------------ Segments ------------------------------------------------------------
+
+(defn- segment-parse-filter-subclause [form]
+  (when (non-empty-clause? form)
+    (match form
+      ["SEGMENT" (segment-id :guard integer?)] (:filter (db/select-one-field :definition 'Segment :id segment-id))
+      subclause                                subclause
+      form                                     (throw (java.lang.Exception. (format "segment-parse-filter-subclause failed: invalid clause: %s" form))))))
+
+(defn- segment-parse-filter [form]
+  (when (non-empty-clause? form)
+    (match form
+      ["AND" & subclauses] (into ["AND"] (mapv segment-parse-filter subclauses))
+      ["OR"  & subclauses] (into ["OR"]  (mapv segment-parse-filter subclauses))
+      subclause            (segment-parse-filter-subclause subclause)
+      form                 (throw (java.lang.Exception. (format "segment-parse-filter failed: invalid clause: %s" form))))))
+
+(defn- expand-segments [query-dict]
+  (if (non-empty-clause? (get-in query-dict [:query :filter]))
+    (update-in query-dict [:query :filter] segment-parse-filter)
+    query-dict))
+
+(defn- merge-filter-clauses [base addtl]
+  (cond
+    (and (seq base)
+         (seq addtl)) ["AND" base addtl]
+    (seq base)        base
+    (seq addtl)       addtl
+    :else             []))
+
+
+;;; ------------------------------------------------------------ Metrics ------------------------------------------------------------
+
+(defn- metric? [aggregation]
+  (match aggregation
+    ["METRIC" (_ :guard integer?)] true
+    _                              false))
+
+(defn- metric-id [metric]
+  (when (metric? metric)
+    (second metric)))
+
+(defn- maybe-unnest-ag-clause
+  "Unnest AG-CLAUSE if it's wrapped in a vector (i.e. if it is using the \"multiple-aggregation\" syntax).
+   (This is provided merely as a convenience to facilitate implementation of the Query Builder, so it can use the same UI for
+   normal aggregations and Metric creation. *METRICS DO NOT SUPPORT MULTIPLE AGGREGATIONS,* so if nested syntax is used, any
+   aggregation after the first will be ignored.)"
+  [ag-clause]
+  (if (and (coll? ag-clause)
+           (every? coll? ag-clause))
+    (first ag-clause)
+    ag-clause))
+
+(defn- expand-metric [metric-clause filter-clauses-atom]
+  (let [{filter-clause :filter, ag-clause :aggregation} (db/select-one-field :definition 'Metric, :id (metric-id metric-clause))]
+    (when filter-clause
+      (swap! filter-clauses-atom conj filter-clause))
+    (maybe-unnest-ag-clause ag-clause)))
+
+(defn- expand-metrics-in-ag-clause [query-dict filter-clauses-atom]
+  (walk/postwalk (fn [form]
+                   (if-not (metric? form)
+                     form
+                     (expand-metric form filter-clauses-atom)))
+                 query-dict))
+
+(defn- add-metrics-filter-clauses
+  "Add any FILTER-CLAUSES to the QUERY-DICT. If query has existing filter clauses, the new ones are
+   combined with an `:and` filter clause."
+  [query-dict filter-clauses]
+  (if-not (seq filter-clauses)
+    query-dict
+    (update-in query-dict [:query :filter] merge-filter-clauses (if (> (count filter-clauses) 1)
+                                                                  (cons "AND" filter-clauses)
+                                                                  (first filter-clauses)))))
+
+(defn- expand-metrics* [query-dict]
+  (let [filter-clauses-atom (atom [])
+        query-dict          (expand-metrics-in-ag-clause query-dict filter-clauses-atom)]
+    (add-metrics-filter-clauses query-dict @filter-clauses-atom)))
+
+(defn- expand-metrics [{{aggregations :aggregation} :query, :as query-dict}]
+  (if-not (seq aggregations)
+    ;; :aggregation is empty, so no METRIC to expand
+    query-dict
+    ;; otherwise walk the query dict and expand METRIC clauses
+    (expand-metrics* query-dict)))
+
+
+;;; ------------------------------------------------------------ Middleware ------------------------------------------------------------
+
+(defn- expand-metrics-and-segments "Expand the macros (SEGMENT, METRIC) in a QUERY."
+  [query]
+  (-> query
+      expand-metrics
+      expand-segments))
+
+
 (defn- expand-macros* [query]
   (if-not (qputil/mbql-query? query)
     query
-    (u/prog1 (macros/expand-macros query)
+    (u/prog1 (expand-metrics-and-segments query)
       (when (and (not i/*disable-qp-logging*)
                  (not= <> query))
         (log/debug (u/format-color 'cyan "\n\nMACRO/SUBSTITUTED: %s\n%s" (u/emoji "😻") (u/pprint-to-str <>)))))))
diff --git a/test/metabase/query_processor/macros_test.clj b/test/metabase/query_processor/middleware/expand_macros_test.clj
similarity index 63%
rename from test/metabase/query_processor/macros_test.clj
rename to test/metabase/query_processor/middleware/expand_macros_test.clj
index dcfa8e281b4fb92765d59ab93a704984ed2cb500..c98831e5a0f682cc3a879922a1c1b84b4dba56cb 100644
--- a/test/metabase/query_processor/macros_test.clj
+++ b/test/metabase/query_processor/middleware/expand_macros_test.clj
@@ -1,4 +1,4 @@
-(ns metabase.query-processor.macros-test
+(ns metabase.query-processor.middleware.expand-macros-test
   (:require [expectations :refer :all]
             [metabase
              [query-processor :as qp]
@@ -9,14 +9,16 @@
              [metric :refer [Metric]]
              [segment :refer [Segment]]
              [table :refer [Table]]]
-            [metabase.query-processor
-             [expand :as ql]
-             [macros :refer :all]]
-            [metabase.test.data :as data]
+            [metabase.query-processor.expand :as ql]
+            [metabase.query-processor.middleware.expand-macros :refer :all]
+            [metabase.test
+             [data :as data]
+             [util :as tu]]
             [metabase.test.data.datasets :as datasets]
             [toucan.util.test :as tt]))
 
-;; expand-macros
+;; expand-metrics-and-segments
+(tu/resolve-private-vars metabase.query-processor.middleware.expand-macros expand-metrics-and-segments)
 
 ;; no Segment or Metric should yield exact same query
 (expect
@@ -25,11 +27,11 @@
    :query    {:aggregation ["rows"]
               :filter      ["AND" [">" 4 1]]
               :breakout    [17]}}
-  (expand-macros {:database 1
-                  :type     :query
-                  :query    {:aggregation ["rows"]
-                             :filter      ["AND" [">" 4 1]]
-                             :breakout    [17]}}))
+  (expand-metrics-and-segments {:database 1
+                                :type     :query
+                                :query    {:aggregation ["rows"]
+                                           :filter      ["AND" [">" 4 1]]
+                                           :breakout    [17]}}))
 
 ;; just segments
 (expect
@@ -37,8 +39,8 @@
    :type     :query
    :query    {:aggregation ["rows"]
               :filter      ["AND" ["AND" ["=" 5 "abc"]]
-                                  ["OR" ["AND" ["IS_NULL" 7]]
-                                        [">" 4 1]]]
+                            ["OR" ["AND" ["IS_NULL" 7]]
+                             [">" 4 1]]]
               :breakout    [17]}}
   (tt/with-temp* [Database [{database-id :id}]
                   Table    [{table-id :id}     {:db_id database-id}]
@@ -46,11 +48,11 @@
                                                 :definition {:filter ["AND" ["=" 5 "abc"]]}}]
                   Segment  [{segment-2-id :id} {:table_id   table-id
                                                 :definition {:filter ["AND" ["IS_NULL" 7]]}}]]
-    (expand-macros {:database 1
-                    :type     :query
-                    :query    {:aggregation ["rows"]
-                               :filter      ["AND" ["SEGMENT" segment-1-id] ["OR" ["SEGMENT" segment-2-id] [">" 4 1]]]
-                               :breakout    [17]}})))
+    (expand-metrics-and-segments {:database 1
+                                  :type     :query
+                                  :query    {:aggregation ["rows"]
+                                             :filter      ["AND" ["SEGMENT" segment-1-id] ["OR" ["SEGMENT" segment-2-id] [">" 4 1]]]
+                                             :breakout    [17]}})))
 
 ;; just a metric (w/out nested segments)
 (expect
@@ -58,7 +60,7 @@
    :type     :query
    :query    {:aggregation ["count"]
               :filter      ["AND" ["AND" [">" 4 1]]
-                                  ["AND" ["=" 5 "abc"]]]
+                            ["AND" ["=" 5 "abc"]]]
               :breakout    [17]
               :order_by    [[1 "ASC"]]}}
   (tt/with-temp* [Database [{database-id :id}]
@@ -66,12 +68,12 @@
                   Metric   [{metric-1-id :id} {:table_id   table-id
                                                :definition {:aggregation ["count"]
                                                             :filter      ["AND" ["=" 5 "abc"]]}}]]
-    (expand-macros {:database 1
-                    :type     :query
-                    :query    {:aggregation ["METRIC" metric-1-id]
-                               :filter      ["AND" [">" 4 1]]
-                               :breakout    [17]
-                               :order_by    [[1 "ASC"]]}})))
+    (expand-metrics-and-segments {:database 1
+                                  :type     :query
+                                  :query    {:aggregation ["METRIC" metric-1-id]
+                                             :filter      ["AND" [">" 4 1]]
+                                             :breakout    [17]
+                                             :order_by    [[1 "ASC"]]}})))
 
 ;; check that when the original filter is empty we simply use our metric filter definition instead
 (expect
@@ -86,12 +88,12 @@
                   Metric   [{metric-1-id :id} {:table_id   table-id
                                                :definition {:aggregation ["count"]
                                                             :filter      ["AND" ["=" 5 "abc"]]}}]]
-    (expand-macros {:database 1
-                    :type     :query
-                    :query    {:aggregation ["METRIC" metric-1-id]
-                               :filter      []
-                               :breakout    [17]
-                               :order_by    [[1 "ASC"]]}})))
+    (expand-metrics-and-segments {:database 1
+                                  :type     :query
+                                  :query    {:aggregation ["METRIC" metric-1-id]
+                                             :filter      []
+                                             :breakout    [17]
+                                             :order_by    [[1 "ASC"]]}})))
 
 ;; metric w/ no filter definition
 (expect
@@ -105,12 +107,12 @@
                   Table    [{table-id :id}    {:db_id database-id}]
                   Metric   [{metric-1-id :id} {:table_id   table-id
                                                :definition {:aggregation ["count"]}}]]
-    (expand-macros {:database 1
-                    :type     :query
-                    :query    {:aggregation ["METRIC" metric-1-id]
-                               :filter      ["AND" ["=" 5 "abc"]]
-                               :breakout    [17]
-                               :order_by    [[1 "ASC"]]}})))
+    (expand-metrics-and-segments {:database 1
+                                  :type     :query
+                                  :query    {:aggregation ["METRIC" metric-1-id]
+                                             :filter      ["AND" ["=" 5 "abc"]]
+                                             :breakout    [17]
+                                             :order_by    [[1 "ASC"]]}})))
 
 ;; a metric w/ nested segments
 (expect
@@ -129,12 +131,12 @@
                   Metric   [{metric-1-id :id}  {:table_id    table-id
                                                 :definition  {:aggregation ["sum" 18]
                                                               :filter      ["AND" ["=" 5 "abc"] ["SEGMENT" segment-1-id]]}}]]
-    (expand-macros {:database 1
-                    :type     :query
-                    :query    {:aggregation ["METRIC" metric-1-id]
-                               :filter      ["AND" [">" 4 1] ["SEGMENT" segment-2-id]]
-                               :breakout    [17]
-                               :order_by    [[1 "ASC"]]}})))
+    (expand-metrics-and-segments {:database 1
+                                  :type     :query
+                                  :query    {:aggregation ["METRIC" metric-1-id]
+                                             :filter      ["AND" [">" 4 1] ["SEGMENT" segment-2-id]]
+                                             :breakout    [17]
+                                             :order_by    [[1 "ASC"]]}})))
 
 ;; Check that a metric w/ multiple aggregation syntax (nested vector) still works correctly
 (datasets/expect-with-engines (engines-that-support :expression-aggregations)