diff --git a/modules/drivers/bigquery/src/metabase/driver/bigquery.clj b/modules/drivers/bigquery/src/metabase/driver/bigquery.clj
index 0759532eb80c97b9195fef06b3b16bbd36690537..914173636f0ce5a10de37c576ef923de284bb366 100644
--- a/modules/drivers/bigquery/src/metabase/driver/bigquery.clj
+++ b/modules/drivers/bigquery/src/metabase/driver/bigquery.clj
@@ -17,14 +17,11 @@
              [google :as google]]
             [metabase.driver.sql.query-processor :as sql.qp]
             [metabase.driver.sql.util.unprepare :as unprepare]
-            [metabase.mbql
-             [schema :as mbql.s]
-             [util :as mbql.u]]
+            [metabase.mbql.util :as mbql.u]
             [metabase.models.table :as table]
             [metabase.query-processor
              [store :as qp.store]
              [util :as qputil]]
-            [metabase.query-processor.middleware.annotate :as annotate]
             [metabase.util
              [date :as du]
              [honeysql-extensions :as hx]
@@ -349,21 +346,6 @@
                          (str/replace #"(^\d)" "_$1"))]
     (subs replaced-str 0 (min 128 (count replaced-str)))))
 
-(s/defn ^:private bq-aggregate-name :- su/NonBlankString
-  "Return an approriate name for an `ag-clause`."
-  [driver, ag-clause :- mbql.s/Aggregation]
-  (->> ag-clause annotate/aggregation-name (driver/format-custom-field-name driver)))
-
-(s/defn ^:private pre-alias-aggregations
-  "Expressions are not allowed in the order by clauses of a BQ query. To sort by a custom expression, that custom
-  expression must be aliased from the order by. This code will find the aggregations and give them a name if they
-  don't already have one. This name can then be used in the order by if one is present."
-  [driver {{aggregations :aggregation} :query, :as outer-query}]
-  (if-not (seq aggregations)
-    outer-query
-    (update-in outer-query [:query :aggregation] (partial mbql.u/pre-alias-and-uniquify-aggregations
-                                                          (partial bq-aggregate-name driver)))))
-
 ;; These provide implementations of `->honeysql` that prevent HoneySQL from converting forms to prepared statement
 ;; parameters (`?` symbols)
 (defmethod sql.qp/->honeysql [:bigquery String]
@@ -433,11 +415,10 @@
     {source-table-id :source-table, source-query :source-query} :query
     :as                                                         outer-query}]
   (let [dataset-id         (-> (qp.store/database) :details :dataset-id)
-        aliased-query      (pre-alias-aggregations driver outer-query)
         {table-name :name} (some-> source-table-id qp.store/table)]
     (assert (seq dataset-id))
-    (binding [sql.qp/*query* (assoc aliased-query :dataset-id dataset-id)]
-      {:query      (->> aliased-query
+    (binding [sql.qp/*query* (assoc outer-query :dataset-id dataset-id)]
+      {:query      (->> outer-query
                         (sql.qp/build-honeysql-form :bigquery)
                         honeysql-form->sql)
        :table-name (or table-name
diff --git a/modules/drivers/bigquery/test/metabase/driver/bigquery_test.clj b/modules/drivers/bigquery/test/metabase/driver/bigquery_test.clj
index e1af0169d3b2c2d4662ec552771b145f8c9bef5d..272e5dbbf058a29ea62c282c77d8fe9b29b67543 100644
--- a/modules/drivers/bigquery/test/metabase/driver/bigquery_test.clj
+++ b/modules/drivers/bigquery/test/metabase/driver/bigquery_test.clj
@@ -1,6 +1,5 @@
 (ns metabase.driver.bigquery-test
   (:require [clj-time.core :as time]
-            [expectations :refer [expect]]
             [honeysql.core :as hsql]
             [metabase
              [driver :as driver]
@@ -95,47 +94,9 @@
   (mbql.u/match (-> query :query :aggregation)
     [:named _ ag-name] ag-name))
 
-(defn- pre-alias-aggregations [outer-query]
-  (binding [driver/*driver* :bigquery]
-    (aggregation-names (#'bigquery/pre-alias-aggregations :bigquery outer-query))))
-
-(defn- query-with-aggregations
-  [aggregations]
-  {:database (data/id)
-   :type     :query
-   :query    {:source-table (data/id :venues)
-              :aggregation  aggregations}})
-
-;; make sure BigQuery can handle two aggregations with the same name (#4089)
-(expect
-  ["sum" "count" "sum_2" "avg" "sum_3" "min"]
-  (pre-alias-aggregations
-   (query-with-aggregations
-    [[:sum [:field-id (data/id :venues :id)]]
-     [:count [:field-id (data/id :venues :id)]]
-     [:sum [:field-id (data/id :venues :id)]]
-     [:avg [:field-id (data/id :venues :id)]]
-     [:sum [:field-id (data/id :venues :id)]]
-     [:min [:field-id (data/id :venues :id)]]])))
-
-(expect
-  ["sum" "count" "sum_2" "avg" "sum_2_2" "min"]
-  (pre-alias-aggregations
-   (query-with-aggregations
-    [[:sum [:field-id (data/id :venues :id)]]
-     [:count [:field-id (data/id :venues :id)]]
-     [:sum [:field-id (data/id :venues :id)]]
-     [:avg [:field-id (data/id :venues :id)]]
-     [:named [:sum [:field-id (data/id :venues :id)]] "sum_2"]
-     [:min [:field-id (data/id :venues :id)]]])))
-
-;; if query has no aggregations then pre-alias-aggregations should do nothing
-(expect
-  {}
-  (driver/with-driver :bigquery
-    (#'bigquery/pre-alias-aggregations :bigquery {})))
-
-
+;; make sure queries with two or more of the same aggregation type still work. Aggregations used to be deduplicated
+;; here in the BigQuery driver; now they are deduplicated as part of the main QP middleware, but no reason not to keep
+;; a few of these tests just to be safe
 (datasets/expect-with-driver :bigquery
   {:rows [[7929 7929]], :columns ["sum" "sum_2"]}
   (qp.test/rows+column-names
diff --git a/modules/drivers/druid/src/metabase/driver/druid/query_processor.clj b/modules/drivers/druid/src/metabase/driver/druid/query_processor.clj
index 0a4b33c9e71a79571511d6e95fbc4fa831126330..b05fecd24d25b4b63aab74ac2f32d62188503264 100644
--- a/modules/drivers/druid/src/metabase/driver/druid/query_processor.clj
+++ b/modules/drivers/druid/src/metabase/driver/druid/query_processor.clj
@@ -73,21 +73,26 @@
   [this]
   this)
 
-(defmethod ->rvalue :aggregation
-  [[_ index]]
-  (let [[ag-type :as ag] (nth (:aggregation *query*) index)]
-    (cond
-      (= [:count] ag)
-      :count
+(defn- ag-clause->rvalue [[ag-type :as ag]]
+  (cond
+    (= [:count] ag)
+    :count
 
-      (= ag-type :distinct)
-      :distinct___count
+    (= ag-type :distinct)
+    :distinct___count
 
-      ag-type
-      ag-type
+    (= ag-type :named)
+    (recur (second ag))
 
-      :else
-      (throw (Exception. "Unknown aggregation type!")))))
+    ag-type
+    ag-type
+
+    :else
+    (throw (Exception. "Unknown aggregation type!"))))
+
+(defmethod ->rvalue :aggregation
+  [[_ index]]
+  (ag-clause->rvalue (nth (:aggregation *query*) index)))
 
 (defmethod ->rvalue :field-id
   [[_ field-id]]
@@ -581,8 +586,8 @@
   [query-type ag-clause updated-query]
   (let [output-name               (annotate/aggregation-name ag-clause)
         [ag-type ag-field & args] (mbql.u/match-one ag-clause
-                                    [:named ag _] (recur ag)
-                                    [_ _ & _]     &match)]
+                                    [:named ag & _] (recur ag)
+                                    [_ _ & _]       &match)]
     (if-not (isa? query-type ::ag-query)
       updated-query
       (let [[projections ag-clauses] (create-aggregation-clause output-name ag-type ag-field args)]
@@ -593,7 +598,7 @@
 (defn- add-expression-aggregation-output-names
   [[operator & args :as expression]]
   (if (mbql.u/is-clause? :named expression)
-    [:named (add-expression-aggregation-output-names (second expression)) (last expression)]
+    (update (vec expression) 1 add-expression-aggregation-output-names)
     (into [operator]
           (for [arg args]
             (cond
@@ -661,21 +666,20 @@
 
 (defn- handle-aggregations
   [query-type {aggregations :aggregation} updated-query]
-  (let [aggregations (mbql.u/pre-alias-and-uniquify-aggregations annotate/aggregation-name aggregations)]
-    (loop [[ag & more] aggregations, query updated-query]
-      (cond
-        (and (mbql.u/is-clause? :named ag)
-             (mbql.u/is-clause? #{:+ :- :/ :*} (second ag)))
-        (handle-expression-aggregation query-type ag query)
+  (loop [[ag & more] aggregations, query updated-query]
+    (cond
+      (and (mbql.u/is-clause? :named ag)
+           (mbql.u/is-clause? #{:+ :- :/ :*} (second ag)))
+      (handle-expression-aggregation query-type ag query)
 
-        (mbql.u/is-clause? #{:+ :- :/ :*} ag)
-        (handle-expression-aggregation query-type ag query)
+      (mbql.u/is-clause? #{:+ :- :/ :*} ag)
+      (handle-expression-aggregation query-type ag query)
 
-        (not ag)
-        query
+      (not ag)
+      query
 
-        :else
-        (recur more (handle-aggregation query-type ag query))))))
+      :else
+      (recur more (handle-aggregation query-type ag query)))))
 
 
 ;;; ------------------------------------------------ handle-breakout -------------------------------------------------
@@ -859,11 +863,21 @@
 
 
 (defmethod handle-order-by ::topN
-  [_ {[[ag-type]] :aggregation, [breakout-field] :breakout, [[direction field]] :order-by} updated-query]
+  [_ {[ag] :aggregation, [breakout-field] :breakout, [[direction field]] :order-by} updated-query]
   (let [field             (->rvalue field)
         breakout-field    (->rvalue breakout-field)
         sort-by-breakout? (= field breakout-field)
-        ag-field          (if (= ag-type :distinct) :distinct___count ag-type)]
+        ag-field          (mbql.u/match-one ag
+                            :distinct
+                            :distinct___count
+
+                            [:named wrapped-ag & _]
+                            (recur wrapped-ag)
+
+                            [(ag-type :guard keyword?) & _]
+                            ag-type)]
+    (when-not sort-by-breakout?
+      (assert ag-field))
     (assoc-in updated-query [:query :metric] (match [sort-by-breakout? direction]
                                                [true  :asc]  {:type :alphaNumeric}
                                                [true  :desc] {:type :inverted, :metric {:type :alphaNumeric}}
diff --git a/modules/drivers/mongo/src/metabase/driver/mongo/query_processor.clj b/modules/drivers/mongo/src/metabase/driver/mongo/query_processor.clj
index 3e78b846ed85ee58e408712cc7f7faba8b2a8c67..82e4271ebab71bc8cce26bc322826ad50fd5e174 100644
--- a/modules/drivers/mongo/src/metabase/driver/mongo/query_processor.clj
+++ b/modules/drivers/mongo/src/metabase/driver/mongo/query_processor.clj
@@ -567,18 +567,16 @@
 (s/defn ^:private generate-aggregation-pipeline :- {:projections Projections, :query Pipeline}
   "Generate the aggregation pipeline. Returns a sequence of maps representing each stage."
   [inner-query :- mbql.s/MBQLQuery]
-  (let [inner-query (update inner-query :aggregation (partial mbql.u/pre-alias-and-uniquify-aggregations
-                                                              annotate/aggregation-name))]
-    (reduce (fn [pipeline-ctx f]
-              (f inner-query pipeline-ctx))
-            {:projections [], :query []}
-            [add-initial-projection
-             handle-filter
-             handle-breakout+aggregation
-             handle-order-by
-             handle-fields
-             handle-limit
-             handle-page])))
+  (reduce (fn [pipeline-ctx f]
+            (f inner-query pipeline-ctx))
+          {:projections [], :query []}
+          [add-initial-projection
+           handle-filter
+           handle-breakout+aggregation
+           handle-order-by
+           handle-fields
+           handle-limit
+           handle-page]))
 
 (s/defn ^:private create-unescaping-rename-map :- {s/Keyword s/Keyword}
   [original-keys :- Projections]
diff --git a/modules/drivers/oracle/test/metabase/test/data/oracle.clj b/modules/drivers/oracle/test/metabase/test/data/oracle.clj
index a05b9721195c4e5c9761c2479aab526da27c73ea..8254984b0d85379b356acf26153381ba867ec33e 100644
--- a/modules/drivers/oracle/test/metabase/test/data/oracle.clj
+++ b/modules/drivers/oracle/test/metabase/test/data/oracle.clj
@@ -69,12 +69,6 @@
           session-schema
           (tx/db-qualified-table-name database-name table-name)))
 
-(defmethod tx/expected-base-type->actual :oracle [_ base-type]
-  ;; Oracle doesn't have INTEGERs
-  (if (isa? base-type :type/Integer)
-    :type/Decimal
-    base-type))
-
 (defmethod sql.tx/create-db-sql :oracle [& _] nil)
 
 (defmethod sql.tx/drop-db-if-exists-sql :oracle [& _] nil)
diff --git a/modules/drivers/snowflake/test/metabase/test/data/snowflake.clj b/modules/drivers/snowflake/test/metabase/test/data/snowflake.clj
index e9164c459c6e9fb677ffb210d67c7895d5ff6de8..50e2edee9c93723b5daacde2068b2488ca9ea119 100644
--- a/modules/drivers/snowflake/test/metabase/test/data/snowflake.clj
+++ b/modules/drivers/snowflake/test/metabase/test/data/snowflake.clj
@@ -54,11 +54,6 @@
   (let [db (sql.tx/qualify-and-quote driver database-name)]
     (format "DROP DATABASE IF EXISTS %s; CREATE DATABASE %s;" db db)))
 
-(defmethod tx/expected-base-type->actual :snowflake [_ base-type]
-  (if (isa? base-type :type/Integer)
-    :type/Number
-    base-type))
-
 (defn- no-db-connection-spec
   "Connection spec for connecting to our Snowflake instance without specifying a DB."
   []
diff --git a/project.clj b/project.clj
index 51a3b3078681fad3883ed3c6ef7121baa3cdafcc..b2ffe7e60f23fef4f23620e6862c7fac94a7a884 100644
--- a/project.clj
+++ b/project.clj
@@ -99,7 +99,7 @@
                  com.sun.jdmk/jmxtools
                  com.sun.jmx/jmxri]]
    [medley "1.2.0"]                                                   ; lightweight lib of useful functions
-   [metabase/mbql "1.0.3"]                                            ; MBQL language schema & util fns
+   [metabase/mbql "1.2.0"]                                            ; MBQL language schema & util fns
    [metabase/throttle "1.0.1"]                                        ; Tools for throttling access to API endpoints and other code pathways
    [javax.xml.bind/jaxb-api "2.4.0-b180830.0359"]                     ; add the `javax.xml.bind` classes which we're still using but were removed in Java 11
    [net.sf.cssbox/cssbox "4.12" :exclusions [org.slf4j/slf4j-api]]    ; HTML / CSS rendering
diff --git a/src/metabase/driver/sql/query_processor.clj b/src/metabase/driver/sql/query_processor.clj
index ebf1b872ad0d706b745f5a5b6bfb53aed01deed6..a199357e753d96d7eba4362f4712cc7571cd1020 100644
--- a/src/metabase/driver/sql/query_processor.clj
+++ b/src/metabase/driver/sql/query_processor.clj
@@ -272,7 +272,7 @@
 (defmethod ->honeysql [:sql :aggregation]
   [driver [_ index]]
   (mbql.u/match-one (mbql.u/aggregation-at-index *query* index *nested-query-level*)
-    [:named _ ag-name]
+    [:named _ ag-name & _]
     (->honeysql driver (hx/identifier :field-alias ag-name))
 
     ;; For some arcane reason we name the results of a distinct aggregation "count", everything else is named the
diff --git a/src/metabase/plugins/classloader.clj b/src/metabase/plugins/classloader.clj
index e89fda818fcba7c766c6024ec64755ce04890f01..f59e35260618e9b292efb01615174f4344c811d9 100644
--- a/src/metabase/plugins/classloader.clj
+++ b/src/metabase/plugins/classloader.clj
@@ -119,9 +119,10 @@
   ultimately have access to that URL."
   (^DynamicClassLoader []
    (the-top-level-classloader (the-classloader)))
+
   (^DynamicClassLoader [^DynamicClassLoader classloader]
    (some #(when (instance? DynamicClassLoader %) %)
-         (classloader-hierarchy (.getContextClassLoader (Thread/currentThread))))))
+         (classloader-hierarchy classloader))))
 
 (defonce ^:private already-added (atom #{}))
 
diff --git a/src/metabase/query_processor.clj b/src/metabase/query_processor.clj
index f910d999c38fbc5e02ed029c94c8e03aa494c695..9e3b282ec55f58568108304f7b2fe455cef39692 100644
--- a/src/metabase/query_processor.clj
+++ b/src/metabase/query_processor.clj
@@ -38,6 +38,7 @@
              [normalize-query :as normalize]
              [parameters :as parameters]
              [permissions :as perms]
+             [pre-alias-aggregations :as pre-alias-ags]
              [process-userland-query :as process-userland-query]
              [reconcile-breakout-and-order-by-bucketing :as reconcile-bucketing]
              [resolve-database :as resolve-database]
@@ -107,6 +108,7 @@
    #'wrap-value-literals/wrap-value-literals
    #'annotate/add-column-info
    #'perms/check-query-permissions
+   #'pre-alias-ags/pre-alias-aggregations
    #'cumulative-ags/handle-cumulative-aggregations
    ;; ▲▲▲ NO FK->s POINT ▲▲▲ Everything after this point will not see `:fk->` clauses, only `:joined-field`
    #'resolve-joins/resolve-joins
diff --git a/src/metabase/query_processor/middleware/add_source_metadata.clj b/src/metabase/query_processor/middleware/add_source_metadata.clj
index 65ef49bb03718a5f14cf4acf516876ea91e4dc59..60fd485d3f4c47eaf5b6616f9818d3baf166ac88 100644
--- a/src/metabase/query_processor/middleware/add_source_metadata.clj
+++ b/src/metabase/query_processor/middleware/add_source_metadata.clj
@@ -9,7 +9,8 @@
              [store :as qp.store]]
             [metabase.query-processor.middleware
              [add-implicit-clauses :as add-implicit-clauses]
-             [annotate :as annotate]]
+             [annotate :as annotate]
+             [pre-alias-aggregations :as pre-alias-ags]]
             [metabase.util.i18n :refer [trs]]
             [schema.core :as s]))
 
@@ -21,7 +22,7 @@
   (let [field-ids (mbql.u/match (concat breakouts aggregations fields) [:field-id id] id)]
     (qp.store/fetch-and-store-fields! field-ids))
   (for [col (annotate/cols-for-mbql-query source-query)]
-    (select-keys col [:name :id :table_id :display_name :base_type :special_type :unit :fingerprint])))
+    (select-keys col [:name :id :table_id :display_name :base_type :special_type :unit :fingerprint :settings])))
 
 (defn- has-same-fields-as-nested-source?
   "Whether this source query itself has a nested source query, and will have the exact same fields in the results as its
@@ -78,11 +79,22 @@
          {:source-query source-query}))
       nil)))
 
+(defn- partially-preprocess-source-query
+  "Unfortunately there are a few middleware transformations that would normally come later but need to be done here for
+  nested source queries before proceeding. This middleware applies those transformations.
+
+  TODO - in a nicer world there would be no need to do such things. It defeats the purpose of having distinct
+  middleware stages."
+  [source-query]
+  (-> source-query
+      pre-alias-ags/pre-alias-aggregations-in-inner-query
+      add-implicit-clauses/add-implicit-mbql-clauses))
+
 (s/defn ^:private add-source-metadata :- {:source-metadata [mbql.s/SourceQueryMetadata], s/Keyword s/Any}
   [{{native-source-query? :native, :as source-query} :source-query, :as inner-query}]
   (let [source-query (if native-source-query?
                        source-query
-                       (add-implicit-clauses/add-implicit-mbql-clauses source-query))
+                       (partially-preprocess-source-query source-query))
         metadata     (source-query->metadata source-query)]
     (assoc inner-query :source-metadata metadata)))
 
diff --git a/src/metabase/query_processor/middleware/annotate.clj b/src/metabase/query_processor/middleware/annotate.clj
index a803cefa873c230e9883114aacba65a40bae9a84..161f2c033ebba73e1c8dd7a12ae2917c45888df9 100644
--- a/src/metabase/query_processor/middleware/annotate.clj
+++ b/src/metabase/query_processor/middleware/annotate.clj
@@ -107,7 +107,7 @@
     (format "%s → %s" join-display-name field-display-name)))
 
 (s/defn ^:private col-info-for-field-clause :- su/Map
-  [inner-query :- su/Map, clause :- mbql.s/Field]
+  [{:keys [source-metadata], :as inner-query} :- su/Map, clause :- mbql.s/Field]
   ;; for various things that can wrap Field clauses recurse on the wrapped Field but include a little bit of info
   ;; about the clause doing the wrapping
   (mbql.u/match-one clause
@@ -134,10 +134,13 @@
     [:fk-> _ field]
     (recur field)
 
+    ;; for field literals, look for matching `source-metadata`, and use that if we can find it; otherwise generate
+    ;; basic info based on the content of the field literal
     [:field-literal field-name field-type]
-    {:name         field-name
-     :base_type    field-type
-     :display_name (humanization/name->human-readable-name field-name)}
+    (or (some #(when (= (:name %) field-name) %) source-metadata)
+        {:name         field-name
+         :base_type    field-type
+         :display_name (humanization/name->human-readable-name field-name)})
 
     [:expression expression-name]
     {:name            expression-name
@@ -198,7 +201,7 @@
   (mbql.u/match-one ag-clause
     ;; if a custom name was provided use it. Some drivers have limits on column names, so call
     ;; `format-custom-field-name` so they can modify it as needed.
-    [:named _ ag-name]
+    [:named _ ag-name & _]
     (driver/format-custom-field-name driver/*driver* ag-name)
 
     ;; For unnamed expressions, just compute a name like "sum + count"
@@ -240,8 +243,13 @@
   "Return an appropriate user-facing display name for an aggregation clause."
   [ag-clause]
   (mbql.u/match-one ag-clause
-    ;; if a custom name was provided use it
-    [:named _ ag-name]
+    ;; if the `named` clause has options, and *explicity* specifies {:use-as-display-name? false}, generate a name based
+    ;; on the wrapped clause instead
+    [:named wrapped-clause _ (_ :guard #(false? (:use-as-display-name? %)))]
+    (aggregation-display-name wrapped-clause)
+
+    ;; otherwise for a `named` aggregation use the supplied name as the display name
+    [:named _ ag-name & _]
     ag-name
 
     [(operator :guard #{:+ :- :/ :*}) & args]
@@ -284,7 +292,7 @@
   [inner-query :- su/Map, clause]
   (mbql.u/match-one clause
     ;; ok, if this is a named aggregation recurse so we can get information about the ag we are naming
-    [:named ag _]
+    [:named ag & _]
     (merge
      (col-info-for-aggregation-clause inner-query ag)
      (ag->name-info &match))
@@ -394,9 +402,9 @@
     (maybe-merge-source-metadata source-metadata (column-info {:type :native} results))
     (mbql-cols source-query results)))
 
-(defn ^:private mbql-cols
+(s/defn ^:private mbql-cols
   "Return the `:cols` result metadata for an 'inner' MBQL query based on the fields/breakouts/aggregations in the query."
-  [{:keys [source-metadata source-query fields], :as inner-query} results]
+  [{:keys [source-metadata source-query fields], :as inner-query} :- su/Map, results]
   (let [cols (cols-for-mbql-query inner-query)]
     (cond
       (and (empty? cols) source-query)
@@ -466,14 +474,11 @@
 ;;; |                                     result-rows-maps-to-vectors middleware                                     |
 ;;; +----------------------------------------------------------------------------------------------------------------+
 
-(defn- uniquify-aggregations [inner-query]
-  (update inner-query :aggregation (partial mbql.u/pre-alias-and-uniquify-aggregations aggregation-name)))
-
 (s/defn ^:private expected-column-sort-order :- [s/Keyword]
   "Determine query result row column names (as keywords) sorted in the appropriate order based on a `query`."
-  [{query-type :type, inner-query :query, :as query} {[first-row] :rows, :as results}]
+  [{query-type :type, inner-query :query, :as query} :- su/Map, {[first-row] :rows, :as results}]
   (if (= query-type :query)
-    (map (comp keyword :name) (mbql-cols (uniquify-aggregations inner-query) results))
+    (map (comp keyword :name) (mbql-cols inner-query results))
     (map keyword (keys first-row))))
 
 (defn- result-rows-maps->vectors* [query {[first-row :as rows] :rows, columns :columns, :as results}]
@@ -512,11 +517,6 @@
      that drivers that use functionality provided by this middleware return deduplicated column names, e.g. `:sum` and
      `:sum_2` for queries with multiple `:sum` aggregations.
 
-     Call `mbql.u/pre-alias-and-uniquify-aggregations` on your query before processing it tp add appropriate aliases to
-     aggregations. Currently this assumes you are passing `annotate/aggregation-name` as the function to generate
-     aggregation names; if your driver is doing something drastically different, you may need to tweak the keys in the
-     result row maps so they match up with the keys generated by that function.
-
   *  For *nested* Fields, this namespace assumes result row maps will come back flattened, and Field name keys will
      come back qualified by names of their ancestors, e.g. `parent.child`, `grandparent.parent.child`, etc. This is done
      to remove any ambiguity between nested columns with the same name (e.g. a document with both `user.id` and
diff --git a/src/metabase/query_processor/middleware/pre_alias_aggregations.clj b/src/metabase/query_processor/middleware/pre_alias_aggregations.clj
new file mode 100644
index 0000000000000000000000000000000000000000..aebf60f76206be06d8cd89b2ab918c3e58c23eae
--- /dev/null
+++ b/src/metabase/query_processor/middleware/pre_alias_aggregations.clj
@@ -0,0 +1,39 @@
+(ns metabase.query-processor.middleware.pre-alias-aggregations
+  (:require [metabase.driver :as driver]
+            [metabase.mbql.util :as mbql.u]
+            [metabase.query-processor.middleware.annotate :as annotate]))
+
+(defn- ag-name [ag-clause]
+  (driver/format-custom-field-name driver/*driver* (annotate/aggregation-name ag-clause)))
+
+(defn- pre-alias-and-uniquify [aggregations]
+  (mapv
+   (fn [original-ag updated-ag]
+     (if (= original-ag updated-ag)
+       original-ag
+       (with-meta updated-ag {:auto-generated? true})))
+   aggregations
+   (mbql.u/pre-alias-and-uniquify-aggregations ag-name aggregations)))
+
+(defn pre-alias-aggregations-in-inner-query
+  "Make sure all aggregations have aliases, and all aliases are unique, in an 'inner' MBQL query."
+  [{:keys [aggregation source-query joins], :as inner-query}]
+  (cond-> inner-query
+    (seq aggregation)
+    (update :aggregation pre-alias-and-uniquify)
+
+    source-query
+    (update :source-query pre-alias-aggregations-in-inner-query)
+
+    joins
+    (update :joins (partial mapv pre-alias-aggregations-in-inner-query))))
+
+(defn- maybe-pre-alias-aggregations [{query-type :type, :as query}]
+  (if-not (= query-type :query)
+    query
+    (update query :query pre-alias-aggregations-in-inner-query)))
+
+(defn pre-alias-aggregations
+  "Middleware that generates aliases for all aggregations anywhere in a query, and makes sure they're unique."
+  [qp]
+  (comp qp maybe-pre-alias-aggregations))
diff --git a/src/metabase/query_processor/middleware/results_metadata.clj b/src/metabase/query_processor/middleware/results_metadata.clj
index ad99f653a12f2325c48ba9ef95df7185cb770144..492dfdb748e7c943f173bf1f0d17bfc25f0b9dba 100644
--- a/src/metabase/query_processor/middleware/results_metadata.clj
+++ b/src/metabase/query_processor/middleware/results_metadata.clj
@@ -52,7 +52,7 @@
        json/generate-string))
 
 (defn- metadata-checksum
-  "Simple, checksum of the column results METADATA.
+  "Simple, checksum of the column results `metadata`.
    Results metadata is returned as part of all query results, with the hope that the frontend will pass it back to
    us when a Card is saved or updated. This checksum (also passed) is a simple way for us to check whether the metadata
    is valid and hasn't been accidentally tampered with.
@@ -72,7 +72,7 @@
         codec/base64-encode)))
 
 (defn valid-checksum?
-  "Is the CHECKSUM the right one for this column METADATA?"
+  "Is the `checksum` the right one for this column `metadata`?"
   [metadata checksum]
   (and metadata
        checksum
diff --git a/test/expectation_options.clj b/test/expectation_options.clj
index 0525a642dae642742125979f68a217767675662a..238966175f4f056a70c94a9964fa65f92cd4691d 100644
--- a/test/expectation_options.clj
+++ b/test/expectation_options.clj
@@ -191,7 +191,7 @@
       ;; Uncomment `check-test-data-unchanged` when you want to debug situations where tests are being bad citizens
       ;; and leaving the metadata about the test data in a different state than it started out with. This is not
       ;; enabled by default because it adds ~5ms to each test which adds up when we have ~4000 tests (as of May 2019)
-      check-test-data-unchanged
+      #_check-test-data-unchanged
       log-slow-tests
       enforce-timeout
       check-table-cleanup))
diff --git a/test/metabase/query_processor/middleware/add_source_metadata_test.clj b/test/metabase/query_processor/middleware/add_source_metadata_test.clj
index 35a771e8fd9ef9cfd3b090a99a8273f8928f1168..3f44f68956bc3739b7cab27e1efd03d7bbb1b88a 100644
--- a/test/metabase/query_processor/middleware/add_source_metadata_test.clj
+++ b/test/metabase/query_processor/middleware/add_source_metadata_test.clj
@@ -19,7 +19,7 @@
 (defn- venues-metadata [field-name]
   (select-keys
    (Field (data/id :venues field-name))
-   [:id :table_id :name :display_name :base_type :special_type :unit :fingerprint]))
+   [:id :table_id :name :display_name :base_type :special_type :unit :fingerprint :settings]))
 
 (defn- venues-source-metadata
   ([]
@@ -85,7 +85,8 @@
                        [{:name         "avg"
                          :display_name "average of ID"
                          :base_type    :type/BigInteger
-                         :special_type :type/PK}])})
+                         :special_type :type/PK
+                         :settings     nil}])})
   (add-source-metadata
    (data/mbql-query venues
      {:source-query {:source-table $$venues
@@ -103,7 +104,8 @@
                        [{:name         "my_cool_aggregation"
                          :display_name "my_cool_aggregation"
                          :base_type    :type/BigInteger
-                         :special_type :type/PK}])})
+                         :special_type :type/PK
+                         :settings     nil}])})
   (add-source-metadata
    (data/mbql-query venues
      {:source-query {:source-table $$venues
diff --git a/test/metabase/query_processor/middleware/annotate_test.clj b/test/metabase/query_processor/middleware/annotate_test.clj
index 82cae575a5508229d52842380651ade6f6494b43..9f95ddcac90e83cc1af594409ffc4edb63450a67 100644
--- a/test/metabase/query_processor/middleware/annotate_test.clj
+++ b/test/metabase/query_processor/middleware/annotate_test.clj
@@ -47,7 +47,7 @@
 
 
 ;;; +----------------------------------------------------------------------------------------------------------------+
-;;; |                                              add-mbql-column-info                                              |
+;;; |                                       (MBQL) Col info for Field clauses                                        |
 ;;; +----------------------------------------------------------------------------------------------------------------+
 
 (defn- info-for-field
@@ -192,6 +192,58 @@
                         :max-value 100}]]}}
     {:columns [:price]})))
 
+;; For fields with parents we should return them with a combined name including parent's name
+(tt/expect-with-temp [Field [parent {:name "parent", :table_id (data/id :venues)}]
+                      Field [child  {:name "child",  :table_id (data/id :venues), :parent_id (u/get-id parent)}]]
+  {:description     nil
+   :table_id        (data/id :venues)
+   :special_type    nil
+   :name            "parent.child"
+   :settings        nil
+   :parent_id       (u/get-id parent)
+   :id              (u/get-id child)
+   :visibility_type :normal
+   :display_name    "Child"
+   :fingerprint     nil
+   :base_type       :type/Text}
+  (qp.test-util/with-everything-store
+    (#'annotate/col-info-for-field-clause {} [:field-id (u/get-id child)])))
+
+;; nested-nested fields should include grandparent name (etc)
+(tt/expect-with-temp [Field [grandparent {:name "grandparent", :table_id (data/id :venues)}]
+                      Field [parent      {:name "parent",      :table_id (data/id :venues), :parent_id (u/get-id grandparent)}]
+                      Field [child       {:name "child",       :table_id (data/id :venues), :parent_id (u/get-id parent)}]]
+  {:description     nil
+   :table_id        (data/id :venues)
+   :special_type    nil
+   :name            "grandparent.parent.child"
+   :settings        nil
+   :parent_id       (u/get-id parent)
+   :id              (u/get-id child)
+   :visibility_type :normal
+   :display_name    "Child"
+   :fingerprint     nil
+   :base_type       :type/Text}
+  (qp.test-util/with-everything-store
+    (#'annotate/col-info-for-field-clause {} [:field-id (u/get-id child)])))
+
+;; datetime literals should get the information from the matching `:source-metadata` if it was supplied
+(expect
+  {:name         "sum"
+   :display_name "sum of User ID"
+   :base_type    :type/Integer
+   :special_type :type/FK}
+  (qp.test-util/with-everything-store
+    (#'annotate/col-info-for-field-clause
+     {:source-metadata [{:name "abc", :display_name "another Field",  :base_type :type/Integer, :special_type :type/FK}
+                        {:name "sum", :display_name "sum of User ID", :base_type :type/Integer, :special_type :type/FK}]}
+     [:field-literal "sum" :type/Integer])))
+
+
+;;; +----------------------------------------------------------------------------------------------------------------+
+;;; |                                    (MBQL) Col info for Aggregation clauses                                     |
+;;; +----------------------------------------------------------------------------------------------------------------+
+
 ;; test that added information about aggregations looks the way we'd expect
 (defn- aggregation-names [ag-clause]
   (binding [driver/*driver* :h2]
@@ -269,6 +321,32 @@
     (data/$ids venues
       (col-info-for-aggregation-clause [:sum [:+ $price 1]]))))
 
+;; if a `:named` aggregation supplies optional `:use-as-display-name?` `options` we should respect that
+;; `use-as-disply-name?` is `true` by default, e.g. in cases where the user supplies the names themselves
+(expect
+  {:base_type    :type/Integer
+   :special_type :type/Category
+   :settings     nil
+   :name         "sum"
+   :display_name "sum"}
+  (qp.test-util/with-everything-store
+    (data/$ids venues
+      (col-info-for-aggregation-clause [:named [:sum $price] "sum" {:use-as-display-name? true}]))))
+
+;; `use-as-display-name?` will normally be `false` when the `:named` clause is generated automatically, e.g. by the
+;; `pre-alias-aggregations` middleware. In this case we want to use the name internally in the query to prevent
+;; duplicate column names, but do not want to use them as display names. See
+;; https://github.com/metabase/mbql/releases/tag/1.2.0 for detailed explanation.
+(expect
+  {:base_type    :type/Integer
+   :special_type :type/Category
+   :settings     nil
+   :name         "sum"
+   :display_name "sum of Price"}
+  (qp.test-util/with-everything-store
+    (data/$ids venues
+      (col-info-for-aggregation-clause [:named [:sum $price] "sum" {:use-as-display-name? false}]))))
+
 ;; if a driver is kind enough to supply us with some information about the `:cols` that come back, we should include
 ;; that information in the results. Their information should be preferred over ours
 (expect
@@ -285,7 +363,12 @@
      (data/mbql-query venues
        {:aggregation [[:metric "ga:totalEvents"]]}))))
 
-;; Make sure columns always come back with a unique `:name` key (#8759)
+
+;;; +----------------------------------------------------------------------------------------------------------------+
+;;; |                                           Other MBQL col info tests                                            |
+;;; +----------------------------------------------------------------------------------------------------------------+
+
+;; Make sure `:cols` always come back with a unique `:name` key (#8759)
 (expect
   {:cols
    [{:base_type    :type/Number
@@ -400,60 +483,3 @@
         ((annotate/result-rows-maps->vectors (constantly results))
          {:database (data/id)
           :type     :native})))))
-
-;; Does `result-rows-maps->vectors` handle multiple aggregations of the same type? Should assume column keys are
-;; deduplicated using the MBQL lib logic
-(expect
-  {:rows    [[2 409 20]
-             [3  56  4]]
-   :columns ["CATEGORY_ID" "sum" "sum_2"]}
-  (qp.test-util/with-everything-store
-    (driver/with-driver :h2
-      (let [results {:rows [{:CATEGORY_ID 2
-                             :sum         409
-                             :sum_2       20}
-                            {:CATEGORY_ID 3
-                             :sum         56
-                             :sum_2       4}]}]
-        ((annotate/result-rows-maps->vectors (constantly results))
-         (data/mbql-query venues
-           {:source-table $$venues
-            :aggregation  [[:sum $id]
-                           [:sum $price]]
-            :breakout     [$category_id]
-            :limit        2}))))))
-
-;; For fields with parents we should return them with a combined name including parent's name
-(tt/expect-with-temp [Field [parent {:name "parent", :table_id (data/id :venues)}]
-                      Field [child  {:name "child",  :table_id (data/id :venues), :parent_id (u/get-id parent)}]]
-  {:description     nil
-   :table_id        (data/id :venues)
-   :special_type    nil
-   :name            "parent.child"
-   :settings        nil
-   :parent_id       (u/get-id parent)
-   :id              (u/get-id child)
-   :visibility_type :normal
-   :display_name    "Child"
-   :fingerprint     nil
-   :base_type       :type/Text}
-  (qp.test-util/with-everything-store
-    (#'annotate/col-info-for-field-clause {} [:field-id (u/get-id child)])))
-
-;; nested-nested fields should include grandparent name (etc)
-(tt/expect-with-temp [Field [grandparent {:name "grandparent", :table_id (data/id :venues)}]
-                      Field [parent      {:name "parent",      :table_id (data/id :venues), :parent_id (u/get-id grandparent)}]
-                      Field [child       {:name "child",       :table_id (data/id :venues), :parent_id (u/get-id parent)}]]
-  {:description     nil
-   :table_id        (data/id :venues)
-   :special_type    nil
-   :name            "grandparent.parent.child"
-   :settings        nil
-   :parent_id       (u/get-id parent)
-   :id              (u/get-id child)
-   :visibility_type :normal
-   :display_name    "Child"
-   :fingerprint     nil
-   :base_type       :type/Text}
-  (qp.test-util/with-everything-store
-    (#'annotate/col-info-for-field-clause {} [:field-id (u/get-id child)])))
diff --git a/test/metabase/query_processor/middleware/parameters/mbql_test.clj b/test/metabase/query_processor/middleware/parameters/mbql_test.clj
index ff3017ffd6f6b9731d38d9adec5e26046d1fa037..b231ec88a384b9fa8a46baa7cd7a4fb6505dd085 100644
--- a/test/metabase/query_processor/middleware/parameters/mbql_test.clj
+++ b/test/metabase/query_processor/middleware/parameters/mbql_test.clj
@@ -4,8 +4,7 @@
             [metabase
              [driver :as driver]
              [query-processor :as qp]
-             [query-processor-test :as qp.test]
-             [util :as u]]
+             [query-processor-test :as qp.test]]
             [metabase.mbql.normalize :as normalize]
             [metabase.query-processor.middleware.parameters.mbql :as mbql-params]
             [metabase.test.data :as data]
@@ -305,7 +304,7 @@
    [37 "bigmista's barbecue" 5 34.118 -118.26 2]
    [38 "Zeke's Smokehouse" 5 34.2053 -118.226 2]
    [39 "Baby Blues BBQ" 5 34.0003 -118.465 2]]
-  (qp.test/format-rows-by [int str int (partial u/round-to-decimals 4) (partial u/round-to-decimals 4) int]
+  (qp.test/format-rows-by :venues
     (qp.test/rows
       (qp/process-query
         (data/query venues
diff --git a/test/metabase/query_processor/middleware/pre_alias_aggregations_test.clj b/test/metabase/query_processor/middleware/pre_alias_aggregations_test.clj
new file mode 100644
index 0000000000000000000000000000000000000000..8bc40efaa1cae782a659811d660973cacecb3e40
--- /dev/null
+++ b/test/metabase/query_processor/middleware/pre_alias_aggregations_test.clj
@@ -0,0 +1,105 @@
+(ns metabase.query-processor.middleware.pre-alias-aggregations-test
+  "Tests for the `pre-alias-aggregations` middleware. For the most part we don't need to test the actual pre-alias
+  logic, as that comes from the MBQL library and is tested thoroughly there -- we just need to test that it gets
+  applied in the correct places."
+  (:require [clojure.string :as str]
+            [expectations :refer [expect]]
+            [metabase.driver :as driver]
+            [metabase.query-processor.middleware.pre-alias-aggregations :as pre-alias-aggregations]
+            [metabase.test.data :as data]))
+
+(defn- pre-alias [query]
+  (driver/with-driver (or driver/*driver* :h2)
+    ((pre-alias-aggregations/pre-alias-aggregations identity) query)))
+
+;; do aggregations get pre-aliased by this middleware?
+(expect
+  (data/mbql-query checkins
+    {:source-table $$checkins
+     :aggregation [[:named [:sum $user_id]  "sum"   {:use-as-display-name? false}]
+                   [:named [:sum $venue_id] "sum_2" {:use-as-display-name? false}]]})
+  (pre-alias
+   (data/mbql-query checkins
+     {:source-table $$checkins
+      :aggregation  [[:sum $user_id] [:sum $venue_id]]})))
+
+;; if one or more aggregations are already named, do things still work correctly?
+(expect
+  (data/mbql-query checkins
+    {:source-table $$checkins
+     :aggregation [[:named [:sum $user_id]  "sum"   {:use-as-display-name? false}]
+                   [:named [:sum $venue_id] "sum_2"]]})
+  (pre-alias
+   (data/mbql-query checkins
+     {:source-table $$checkins
+      :aggregation  [[:sum $user_id]
+                     [:named [:sum $venue_id] "sum"]]})))
+
+;; do aggregations inside source queries get pre-aliased?
+(expect
+  (data/mbql-query checkins
+    {:source-query {:source-table $$checkins
+                    :aggregation  [[:named [:sum $user_id]  "sum"   {:use-as-display-name? false}]
+                                   [:named [:sum $venue_id] "sum_2" {:use-as-display-name? false}]]}
+     :aggregation  [[:named [:count] "count" {:use-as-display-name? false}]]})
+  (pre-alias
+   (data/mbql-query checkins
+     {:source-query {:source-table $$checkins
+                     :aggregation  [[:sum $user_id] [:sum $venue_id]]}
+      :aggregation  [[:count]]})))
+
+;; do aggregatons inside of source queries inside joins get pre-aliased?
+(expect
+  (data/mbql-query checkins
+    {:source-table $$venues
+     :aggregation  [[:named [:count] "count" {:use-as-display-name? false}]]
+     :joins        [{:source-query {:source-table $$checkins
+                                    :aggregation  [[:named [:sum $user_id]  "sum"   {:use-as-display-name? false}]
+                                                   [:named [:sum $venue_id] "sum_2" {:use-as-display-name? false}]]
+                                    :breakout     [$venue_id]}
+                     :alias        "checkins"
+                     :condition    [:= &checkins.venue_id $venues.id]}]})
+  (pre-alias
+   (data/mbql-query checkins
+     {:source-table $$venues
+      :aggregation  [[:count]]
+      :joins        [{:source-query {:source-table $$checkins
+                                     :aggregation  [[:sum $user_id] [:sum $venue_id]]
+                                     :breakout     [$venue_id]}
+                      :alias        "checkins"
+                      :condition    [:= &checkins.venue_id $venues.id]}]})))
+
+;; does pre-aliasing work the way we'd expect with expressions?
+(expect
+  {:database 1
+   :type     :query
+   :query    {:source-table 1
+              :aggregation  [[:named [:+ 20 [:sum [:field-id 2]]] "20 + sum" {:use-as-display-name? false}]]}}
+  (pre-alias
+   {:database 1
+    :type     :query
+    :query    {:source-table 1
+               :aggregation  [[:+ 20 [:sum [:field-id 2]]]]}}))
+
+;; we should use `driver/format-custom-field-name` on the generated aggregation names in case the drivers need to
+;; tweak the default names we generate
+(driver/register! ::test-driver, :abstract? true, :parent :sql)
+
+;; this implementation prepends an underscore to any name that starts with a number. Some DBs, such as BigQuery, do
+;; not allow aliases that start with an underscore
+(defmethod driver/format-custom-field-name ::test-driver [_ custom-field-name]
+  (str/replace custom-field-name #"(^\d)" "_$1"))
+
+(expect
+  {:database 1
+   :type     :query
+   :query    {:source-table 1
+              :aggregation  [[:named [:+ 20 [:sum [:field-id 2]]] "_20 + sum" {:use-as-display-name? false}]
+                             [:named [:count] "count" {:use-as-display-name? false}]]}}
+  (driver/with-driver ::test-driver
+    (pre-alias
+     {:database 1
+      :type     :query
+      :query    {:source-table 1
+                 :aggregation  [[:+ 20 [:sum [:field-id 2]]]
+                                [:count]]}})))
diff --git a/test/metabase/query_processor_test.clj b/test/metabase/query_processor_test.clj
index 67f15f87a8705af845477a80ebef21e1815b13a9..6ae7ef3b4b020d2bd9269923cbaca5f0001c0bf5 100644
--- a/test/metabase/query_processor_test.clj
+++ b/test/metabase/query_processor_test.clj
@@ -6,9 +6,13 @@
             [medley.core :as m]
             [metabase
              [driver :as driver]
+             [query-processor :as qp]
              [util :as u]]
             [metabase.driver.util :as driver.u]
-            [metabase.models.field :refer [Field]]
+            [metabase.models
+             [field :refer [Field]]
+             [table :refer [Table]]]
+            [metabase.query-processor.middleware.add-implicit-joins :as add-implicit-joins]
             [metabase.test.data :as data]
             [metabase.test.data
              [datasets :as datasets]
@@ -91,7 +95,7 @@
    (db/select-one [Field :id :table_id :special_type :base_type :name :display_name :fingerprint]
      :id (data/id table-kw field-kw))
    {:field_ref [:field-id (data/id table-kw field-kw)]}
-   (when (= field-kw :last_login)
+   (when (#{:last_login :date} field-kw)
      {:unit      :default
       :field_ref [:datetime-field [:field-id (data/id table-kw field-kw)] :default]})))
 
@@ -138,17 +142,70 @@
    (tx/aggregate-column-info (tx/driver) ag-type (col table-kw field-kw))))
 
 (defn breakout-col
+  "Return expected `:cols` info for a Field used as a breakout.
+
+    (breakout-col :venues :price)"
   ([col]
    (assoc col :source :breakout))
 
   ([table-kw field-kw]
    (breakout-col (col table-kw field-kw))))
 
+(defn field-literal-col
+  "Return expected `:cols` info for a Field that was referred to as a `:field-literal`.
+
+    (field-literal-col :venues :price)
+    (field-literal-col (aggregate-col :count))"
+  {:arglists '([col] [table-kw field-kw])}
+  ([{field-name :name, base-type :base_type, unit :unit, :as col}]
+   (-> col
+       (assoc :field_ref [:field-literal field-name base-type]
+              :source    :fields)
+       (dissoc :description :parent_id :visibility_type)))
+
+  ([table-kw field-kw]
+   (field-literal-col (col table-kw field-kw))))
+
+(defn fk-col
+  "Return expected `:cols` info for a Field that came in via an implicit join (i.e, via an `fk->` clause)."
+  [source-table-kw source-field-kw, dest-table-kw dest-field-kw]
+  (let [source-col      (col source-table-kw source-field-kw)
+        dest-col        (col dest-table-kw dest-field-kw)
+        dest-table-name (db/select-one-field :name Table :id (data/id dest-table-kw))
+        join-alias      (#'add-implicit-joins/join-alias dest-table-name (:name source-col))]
+    (-> dest-col
+        (update :display_name (partial format "%s → %s" dest-table-name))
+        (assoc :field_ref   [:joined-field join-alias [:field-id (:id dest-col)]]
+               :fk_field_id (:id source-col)))))
+
+(declare cols)
+
+(def ^:private ^{:arglists '([db-id table-id field-id])} native-query-col*
+  (memoize
+   (fn [db-id table-id field-id]
+     (first
+      (cols
+       (qp/process-query
+         {:database db-id
+          :type     :native
+          :native   (qp/query->native
+                      {:database db-id
+                       :type     :query
+                       :query    {:source-table table-id
+                                  :fields       [[:field_id field-id]]
+                                  :limit        1}})}))))))
+
+(defn native-query-col
+  "Return expected `:cols` info for a Field from a native query or native source query."
+  [table-kw field-kw]
+  (native-query-col* (data/id) (data/id table-kw) (data/id table-kw field-kw)))
+
 (defn ^:deprecated booleanize-native-form
   "Convert `:native_form` attribute to a boolean to make test results comparisons easier. Remove `data.results_metadata`
   as well since it just takes a lot of space and the checksum can vary based on whether encryption is enabled.
 
-  DEPRECATED: Just use `qp.test/rows` or `qp.test/row-and-cols` instead."
+  DEPRECATED: Just use `qp.test/rows`, `qp.test/row-and-cols`, or `qp.test/rows+column-names` instead, combined with
+  functions like `col` as needed."
   [m]
   (-> m
       (update-in [:data :native_form] boolean)
@@ -160,8 +217,16 @@
     :categories [int identity]
     :checkins   [int identity int int]
     :users      [int identity identity]
-    :venues     [int identity int (partial u/round-to-decimals 4) (partial u/round-to-decimals 4) int]
-    (throw (IllegalArgumentException. (format "Sorry, we don't have default format-rows-by fns for Table %s." table-kw)))))
+    :venues     [int identity int 4.0 4.0 int]
+    (throw
+     (IllegalArgumentException. (format "Sorry, we don't have default format-rows-by fns for Table %s." table-kw)))))
+
+(defn- format-rows-fn
+  "Handle a value formatting function passed to `format-rows-by`."
+  [x]
+  (if (float? x)
+    (partial u/round-to-decimals (int x))
+    x))
 
 (defn format-rows-by
   "Format the values in result `rows` with the fns at the corresponding indecies in `format-fns`. `rows` can be a
@@ -172,7 +237,12 @@
   `format-fns` can be a sequence of functions, or may be the name of one of the 'big four' test data Tables to use
   their defaults:
 
-    (format-rows-by :venue (data/run-mbql-query :venues))
+    (format-rows-by :venues (data/run-mbql-query :venues))
+
+  Additionally, you may specify an floating-point number in the rounding functions vector as shorthand for formatting
+  with `u/round-to-decimals`:
+
+    (format-rows-by [identity 4.0] ...) ;-> (format-rows-by [identity (partial u/round-to-decimals 4)] ...)
 
   By default, does't call fns on `nil` values; pass a truthy value as optional param `format-nil-values`? to override
   this behavior."
@@ -185,9 +255,11 @@
      (println "Error running query:" (u/pprint-to-str 'red response))
      (throw (ex-info (:error response) response)))
 
-   (let [format-fns (if (keyword? format-fns)
-                      (default-format-rows-by-fns format-fns)
-                      format-fns)]
+   (let [format-fns (map
+                     format-rows-fn
+                     (if (keyword? format-fns)
+                       (default-format-rows-by-fns format-fns)
+                       format-fns))]
      (-> response
          ((fn format-rows [rows]
             (cond
diff --git a/test/metabase/query_processor_test/aggregation_test.clj b/test/metabase/query_processor_test/aggregation_test.clj
index 7a5eeb7c858dff47546b29da8184b90f32a506af..e9ab05d224ddaaa7c36ea28f5a3e4710ee74755c 100644
--- a/test/metabase/query_processor_test/aggregation_test.clj
+++ b/test/metabase/query_processor_test/aggregation_test.clj
@@ -1,10 +1,8 @@
 (ns metabase.query-processor-test.aggregation-test
   "Tests for MBQL aggregations."
   (:require [expectations :refer [expect]]
-            [metabase
-             [query-processor-test :as qp.test]
-             [util :as u]]
             [metabase.models.field :refer [Field]]
+            [metabase.query-processor-test :as qp.test]
             [metabase.test
              [data :as data]
              [util :as tu]]
@@ -35,7 +33,7 @@
   {:rows [[35.5059]]
    :cols [(qp.test/aggregate-col :avg  :venues :latitude)]}
   (qp.test/rows-and-cols
-    (qp.test/format-rows-by [(partial u/round-to-decimals 4)]
+    (qp.test/format-rows-by [4.0]
       (data/run-mbql-query venues
         {:aggregation [[:avg $latitude]]}))))
 
@@ -72,7 +70,7 @@
   {:cols [(qp.test/aggregate-col :stddev :venues :latitude)]
    :rows [[3.4]]}
   (qp.test/rows-and-cols
-    (qp.test/format-rows-by [(partial u/round-to-decimals 1)]
+    (qp.test/format-rows-by [1.0]
       (data/run-mbql-query venues {:aggregation [[:stddev $latitude]]}))))
 
 ;; Make sure standard deviation fails for the Mongo driver since its not supported
@@ -104,14 +102,14 @@
 
 (qp.test/expect-with-non-timeseries-dbs
   [[1 34.0071] [2 33.7701] [3 10.0646] [4 33.983]]
-  (qp.test/formatted-rows [int (partial u/round-to-decimals 4)]
+  (qp.test/formatted-rows [int 4.0]
     (data/run-mbql-query venues
       {:aggregation [[:min $latitude]]
        :breakout    [$price]})))
 
 (qp.test/expect-with-non-timeseries-dbs
   [[1 37.8078] [2 40.7794] [3 40.7262] [4 40.7677]]
-  (qp.test/formatted-rows [int (partial u/round-to-decimals 4)]
+  (qp.test/formatted-rows [int 4.0]
     (data/run-mbql-query venues
       {:aggregation [[:max $latitude]]
        :breakout    [$price]})))
diff --git a/test/metabase/query_processor_test/breakout_test.clj b/test/metabase/query_processor_test/breakout_test.clj
index 4ed5b0a9128681338d4e346b0597255c1e2ff1ed..d71c591e58781afeaa14ef6f0379d26e606a00c3 100644
--- a/test/metabase/query_processor_test/breakout_test.clj
+++ b/test/metabase/query_processor_test/breakout_test.clj
@@ -132,21 +132,21 @@
 
 (datasets/expect-with-drivers (qp.test/non-timeseries-drivers-with-feature :binning)
   [[10.0 1] [32.0 4] [34.0 57] [36.0 29] [40.0 9]]
-  (qp.test/formatted-rows [(partial u/round-to-decimals 1) int]
+  (qp.test/formatted-rows [1.0 int]
     (data/run-mbql-query venues
       {:aggregation [[:count]]
        :breakout    [[:binning-strategy $latitude :num-bins 20]]})))
 
 (datasets/expect-with-drivers (qp.test/non-timeseries-drivers-with-feature :binning)
   [[0.0 1] [20.0 90] [40.0 9]]
-  (qp.test/formatted-rows [(partial u/round-to-decimals 1) int]
+  (qp.test/formatted-rows [1.0 int]
     (data/run-mbql-query venues
       {:aggregation [[:count]]
        :breakout    [[:binning-strategy $latitude :num-bins 3]]})))
 
 (datasets/expect-with-drivers (qp.test/non-timeseries-drivers-with-feature :binning)
   [[10.0 -170.0 1] [32.0 -120.0 4] [34.0 -120.0 57] [36.0 -125.0 29] [40.0 -75.0 9]]
-  (qp.test/formatted-rows [(partial u/round-to-decimals 1) (partial u/round-to-decimals 1) int]
+  (qp.test/formatted-rows [1.0 1.0 int]
     (data/run-mbql-query venues
       {:aggregation [[:count]]
        :breakout    [[:binning-strategy $latitude :num-bins 20]
@@ -156,7 +156,7 @@
 ;; specified
 (datasets/expect-with-drivers (qp.test/non-timeseries-drivers-with-feature :binning)
   [[10.0 1] [30.0 90] [40.0 9]]
-  (qp.test/formatted-rows [(partial u/round-to-decimals 1) int]
+  (qp.test/formatted-rows [1.0 int]
     (data/run-mbql-query venues
       {:aggregation [[:count]]
        :breakout    [[:binning-strategy $latitude :default]]})))
@@ -164,7 +164,7 @@
 (datasets/expect-with-drivers (qp.test/non-timeseries-drivers-with-feature :binning)
   [[10.0 1] [30.0 61] [35.0 29] [40.0 9]]
   (tu/with-temporary-setting-values [breakout-bin-width 5.0]
-    (qp.test/formatted-rows [(partial u/round-to-decimals 1) int]
+    (qp.test/formatted-rows [1.0 int]
       (data/run-mbql-query venues
         {:aggregation [[:count]]
          :breakout    [[:binning-strategy $latitude :default]]}))))
@@ -172,7 +172,7 @@
 ;; Testing bin-width
 (datasets/expect-with-drivers (qp.test/non-timeseries-drivers-with-feature :binning)
   [[10.0 1] [33.0 4] [34.0 57] [37.0 29] [40.0 9]]
-  (qp.test/formatted-rows [(partial u/round-to-decimals 1) int]
+  (qp.test/formatted-rows [1.0 int]
     (data/run-mbql-query venues
       {:aggregation [[:count]]
        :breakout    [[:binning-strategy $latitude :bin-width 1]]})))
@@ -180,7 +180,7 @@
 ;; Testing bin-width using a float
 (datasets/expect-with-drivers (qp.test/non-timeseries-drivers-with-feature :binning)
   [[10.0 1] [32.5 61] [37.5 29] [40.0 9]]
-  (qp.test/formatted-rows [(partial u/round-to-decimals 1) int]
+  (qp.test/formatted-rows [1.0 int]
     (data/run-mbql-query venues
       {:aggregation [[:count]]
        :breakout    [[:binning-strategy $latitude :bin-width 2.5]]})))
@@ -188,7 +188,7 @@
 (datasets/expect-with-drivers (qp.test/non-timeseries-drivers-with-feature :binning)
   [[33.0 4] [34.0 57]]
   (tu/with-temporary-setting-values [breakout-bin-width 1.0]
-    (qp.test/formatted-rows [(partial u/round-to-decimals 1) int]
+    (qp.test/formatted-rows [1.0 int]
       (data/run-mbql-query venues
         {:aggregation [[:count]]
          :filter      [:and
@@ -252,7 +252,7 @@
   (tt/with-temp Card [card (qp.test-util/card-with-source-metadata-for-query
                             (data/mbql-query nil
                               {:source-query {:source-table $$venues}}))]
-    (qp.test/formatted-rows [(partial u/round-to-decimals 1) int]
+    (qp.test/formatted-rows [1.0 int]
       (qp/process-query
         (nested-venues-query card)))))
 
diff --git a/test/metabase/query_processor_test/explicit_joins_test.clj b/test/metabase/query_processor_test/explicit_joins_test.clj
index d9b965433a2b254c85786ca9c610fab14c3f6cb6..56eb50044dbfb1e7c575b6951516ce2cb90861ed 100644
--- a/test/metabase/query_processor_test/explicit_joins_test.clj
+++ b/test/metabase/query_processor_test/explicit_joins_test.clj
@@ -3,8 +3,7 @@
             [metabase
              [driver :as driver]
              [query-processor :as qp]
-             [query-processor-test :as qp.test]
-             [util :as u]]
+             [query-processor-test :as qp.test]]
             [metabase.models.card :refer [Card]]
             [metabase.query-processor.test-util :as qp.test-util]
             [metabase.test.data :as data]
@@ -293,8 +292,7 @@
    :columns
    (mapv data/format-name ["id" "name" "category_id" "latitude" "longitude" "price" "id_2" "name_2"])}
   (tt/with-temp Card [{card-id :id} (qp.test-util/card-with-source-metadata-for-query (data/mbql-query categories))]
-    (qp.test/format-rows-by [int identity int (partial u/round-to-decimals 4) (partial u/round-to-decimals 4) int
-                             int identity]
+    (qp.test/format-rows-by [int identity int 4.0 4.0 int int identity]
       (qp.test/rows+column-names
         (data/run-mbql-query venues
           {:joins    [{:alias        "cat"
@@ -416,7 +414,7 @@
    :columns (mapv data/format-name
                   ["id" "date" "user_id" "venue_id" "id_2" "name" "category_id" "latitude" "longitude" "price"])}
   (qp.test/rows+column-names
-    (qp.test/format-rows-by [int str int int int str int (partial u/round-to-decimals 3) (partial u/round-to-decimals 3) int]
+    (qp.test/format-rows-by [int str int int int str int 3.0 3.0 int]
       (data/run-mbql-query checkins
         {:source-query {:source-table $$checkins
                         :joins
diff --git a/test/metabase/query_processor_test/expressions_test.clj b/test/metabase/query_processor_test/expressions_test.clj
index bf09b3e839262787fd2f40a54d27a91c0b73b37d..fe989c4d8d29fc16793e0c9faeed50461cbb29c8 100644
--- a/test/metabase/query_processor_test/expressions_test.clj
+++ b/test/metabase/query_processor_test/expressions_test.clj
@@ -2,90 +2,96 @@
   "Tests for expressions (calculated columns)."
   (:require [metabase
              [driver :as driver]
-             [query-processor-test :refer :all]
-             [util :as u]]
+             [query-processor-test :as qp.test]]
             [metabase.test.data :as data]
             [metabase.test.data.datasets :as datasets]))
 
 ;; Do a basic query including an expression
-(datasets/expect-with-drivers (non-timeseries-drivers-with-feature :expressions)
+(datasets/expect-with-drivers (qp.test/non-timeseries-drivers-with-feature :expressions)
   [[1 "Red Medicine"                 4  10.0646 -165.374 3 5.0]
    [2 "Stout Burgers & Beers"        11 34.0996 -118.329 2 4.0]
    [3 "The Apple Pan"                11 34.0406 -118.428 2 4.0]
    [4 "Wurstküche"                   29 33.9997 -118.465 2 4.0]
    [5 "Brite Spot Family Restaurant" 20 34.0778 -118.261 2 4.0]]
-  (format-rows-by [int str int (partial u/round-to-decimals 4) (partial u/round-to-decimals 4) int float]
-    (rows (data/run-mbql-query venues
-            {:expressions {:my-cool-new-field [:+ $price 2]}
-             :limit       5
-             :order-by    [[:asc $id]]}))))
+  (qp.test/format-rows-by [int str int 4.0 4.0 int float]
+    (qp.test/rows
+      (data/run-mbql-query venues
+        {:expressions {:my-cool-new-field [:+ $price 2]}
+         :limit       5
+         :order-by    [[:asc $id]]}))))
 
 ;; Make sure FLOATING POINT division is done
-(datasets/expect-with-drivers (non-timeseries-drivers-with-feature :expressions)
-  [[1 "Red Medicine"           4 10.0646 -165.374 3 1.5]     ; 3 / 2 SHOULD BE 1.5, NOT 1 (!)
+(datasets/expect-with-drivers (qp.test/non-timeseries-drivers-with-feature :expressions)
+  [[1 "Red Medicine"           4 10.0646 -165.374 3 1.5] ; 3 / 2 SHOULD BE 1.5, NOT 1 (!)
    [2 "Stout Burgers & Beers" 11 34.0996 -118.329 2 1.0]
    [3 "The Apple Pan"         11 34.0406 -118.428 2 1.0]]
-  (format-rows-by [int str int (partial u/round-to-decimals 4) (partial u/round-to-decimals 4) int float]
-    (rows (data/run-mbql-query venues
-            {:expressions {:my-cool-new-field [:/ $price 2]}
-             :limit       3
-             :order-by    [[:asc $id]]}))))
+  (qp.test/format-rows-by [int str int 4.0 4.0 int float]
+    (qp.test/rows
+      (data/run-mbql-query venues
+        {:expressions {:my-cool-new-field [:/ $price 2]}
+         :limit       3
+         :order-by    [[:asc $id]]}))))
 
 ;; Can we do NESTED EXPRESSIONS ?
-(datasets/expect-with-drivers (non-timeseries-drivers-with-feature :expressions)
+(datasets/expect-with-drivers (qp.test/non-timeseries-drivers-with-feature :expressions)
   [[1 "Red Medicine"           4 10.0646 -165.374 3 3.0]
    [2 "Stout Burgers & Beers" 11 34.0996 -118.329 2 2.0]
    [3 "The Apple Pan"         11 34.0406 -118.428 2 2.0]]
-  (format-rows-by [int str int (partial u/round-to-decimals 4) (partial u/round-to-decimals 4) int float]
-    (rows (data/run-mbql-query venues
-            {:expressions {:wow [:- [:* $price 2] [:+ $price 0]]}
-             :limit       3
-             :order-by    [[:asc $id]]}))))
+  (qp.test/format-rows-by [int str int 4.0 4.0 int float]
+    (qp.test/rows
+      (data/run-mbql-query venues
+        {:expressions {:wow [:- [:* $price 2] [:+ $price 0]]}
+         :limit       3
+         :order-by    [[:asc $id]]}))))
 
 ;; Can we have MULTIPLE EXPRESSIONS?
-(datasets/expect-with-drivers (non-timeseries-drivers-with-feature :expressions)
+(datasets/expect-with-drivers (qp.test/non-timeseries-drivers-with-feature :expressions)
   [[1 "Red Medicine"           4 10.0646 -165.374 3 2.0 4.0]
    [2 "Stout Burgers & Beers" 11 34.0996 -118.329 2 1.0 3.0]
    [3 "The Apple Pan"         11 34.0406 -118.428 2 1.0 3.0]]
-  (format-rows-by [int str int (partial u/round-to-decimals 4) (partial u/round-to-decimals 4) int float float]
-    (rows (data/run-mbql-query venues
-            {:expressions {:x [:- $price 1]
-                           :y [:+ $price 1]}
-             :limit       3
-             :order-by    [[:asc $id]]}))))
+  (qp.test/format-rows-by [int str int 4.0 4.0 int float float]
+    (qp.test/rows
+      (data/run-mbql-query venues
+        {:expressions {:x [:- $price 1]
+                       :y [:+ $price 1]}
+         :limit       3
+         :order-by    [[:asc $id]]}))))
 
 ;; Can we refer to expressions inside a FIELDS clause?
-(datasets/expect-with-drivers (non-timeseries-drivers-with-feature :expressions)
+(datasets/expect-with-drivers (qp.test/non-timeseries-drivers-with-feature :expressions)
   [[4] [4] [5]]
-  (format-rows-by [int]
-    (rows (data/run-mbql-query venues
-            {:expressions {:x [:+ $price $id]}
-             :fields      [[:expression :x]]
-             :limit       3
-             :order-by    [[:asc $id]]}))))
+  (qp.test/format-rows-by [int]
+    (qp.test/rows
+      (data/run-mbql-query venues
+        {:expressions {:x [:+ $price $id]}
+         :fields      [[:expression :x]]
+         :limit       3
+         :order-by    [[:asc $id]]}))))
 
 ;; Can we refer to expressions inside an ORDER BY clause?
-(datasets/expect-with-drivers (non-timeseries-drivers-with-feature :expressions)
+(datasets/expect-with-drivers (qp.test/non-timeseries-drivers-with-feature :expressions)
   [[100 "Mohawk Bend"         46 34.0777 -118.265 2 102.0]
    [99  "Golden Road Brewing" 10 34.1505 -118.274 2 101.0]
    [98  "Lucky Baldwin's Pub"  7 34.1454 -118.149 2 100.0]]
-  (format-rows-by [int str int (partial u/round-to-decimals 4) (partial u/round-to-decimals 4) int float]
-    (rows (data/run-mbql-query venues
-            {:expressions {:x [:+ $price $id]}
-             :limit       3
-             :order-by    [[:desc [:expression :x]]]}))))
+  (qp.test/format-rows-by [int str int 4.0 4.0 int float]
+    (qp.test/rows
+      (data/run-mbql-query venues
+        {:expressions {:x [:+ $price $id]}
+         :limit       3
+         :order-by    [[:desc [:expression :x]]]}))))
 
 ;; Can we AGGREGATE + BREAKOUT by an EXPRESSION?
-(datasets/expect-with-drivers (non-timeseries-drivers-with-feature :expressions)
+(datasets/expect-with-drivers (qp.test/non-timeseries-drivers-with-feature :expressions)
   [[2 22] [4 59] [6 13] [8 6]]
-  (format-rows-by [int int]
-    (rows (data/run-mbql-query venues
-            {:expressions {:x [:* $price 2.0]}
-             :aggregation [[:count]]
-             :breakout    [[:expression :x]]}))))
+  (qp.test/format-rows-by [int int]
+    (qp.test/rows
+      (data/run-mbql-query venues
+        {:expressions {:x [:* $price 2.0]}
+         :aggregation [[:count]]
+         :breakout    [[:expression :x]]}))))
 
 ;; Custom aggregation expressions should include their type
-(datasets/expect-with-drivers (non-timeseries-drivers-with-feature :expressions)
+(datasets/expect-with-drivers (qp.test/non-timeseries-drivers-with-feature :expressions)
   (conj #{{:name "x" :base_type :type/Float}}
         {:name      (data/format-name "category_id")
          :base_type (case driver/*driver*
@@ -106,32 +112,37 @@
 ;; (at least for the purposes of the tests below)
 ;;
 ;; e.g. scarcity = 100.0 / num-birds
+(defn- calculate-bird-scarcity* [formula filter-clause]
+  (qp.test/format-rows-by [2.0]
+    (qp.test/rows
+      (data/dataset daily-bird-counts
+        (data/run-mbql-query bird-count
+          {:expressions {"bird-scarcity" formula}
+           :fields      [[:expression "bird-scarcity"]]
+           :filter      filter-clause
+           :order-by    [[:asc $date]]
+           :limit       10})))))
+
 (defmacro ^:private calculate-bird-scarcity [formula & [filter-clause]]
   `(data/dataset ~'daily-bird-counts
-     (->> (data/run-mbql-query ~'bird-count
-            {:expressions {"bird-scarcity" ~formula}
-             :fields      [[:expression "bird-scarcity"]]
-             :filter      ~filter-clause
-             :order-by    [[:asc [:field-id ~'$date]]]
-             :limit       10})
-          rows
-          (format-rows-by [(partial u/round-to-decimals 2)]))))
+     (data/$ids ~'bird-count
+       (calculate-bird-scarcity* ~formula ~filter-clause))))
 
 ;; hey... expressions should work if they are just a Field! (Also, this lets us take a peek at the raw values being
 ;; used to calculate the formulas below, so we can tell at a glance if they're right without referring to the EDN def)
-(datasets/expect-with-drivers (non-timeseries-drivers-with-feature :expressions)
+(datasets/expect-with-drivers (qp.test/non-timeseries-drivers-with-feature :expressions)
   [[nil] [0.0] [0.0] [10.0] [8.0] [5.0] [5.0] [nil] [0.0] [0.0]]
   (calculate-bird-scarcity [:field-id $count]))
 
 ;; do expressions automatically handle division by zero? Should return `nil` in the results for places where that was
 ;; attempted
-(datasets/expect-with-drivers (non-timeseries-drivers-with-feature :expressions)
+(datasets/expect-with-drivers (qp.test/non-timeseries-drivers-with-feature :expressions)
   [[nil] [nil] [10.0] [12.5] [20.0] [20.0] [nil] [nil] [9.09] [7.14]]
   (calculate-bird-scarcity [:/ 100.0 [:field-id $count]]
                            [:!= $count nil]))
 
 ;; do expressions handle division by `nil`? Should return `nil` in the results for places where that was attempted
-(datasets/expect-with-drivers (non-timeseries-drivers-with-feature :expressions)
+(datasets/expect-with-drivers (qp.test/non-timeseries-drivers-with-feature :expressions)
   [[nil] [10.0] [12.5] [20.0] [20.0] [nil] [9.09] [7.14] [12.5] [7.14]]
   (calculate-bird-scarcity [:/ 100.0 [:field-id $count]]
                            [:or
@@ -139,37 +150,37 @@
                             [:!= $count 0]]))
 
 ;; can we handle BOTH NULLS AND ZEROES AT THE SAME TIME????
-(datasets/expect-with-drivers (non-timeseries-drivers-with-feature :expressions)
+(datasets/expect-with-drivers (qp.test/non-timeseries-drivers-with-feature :expressions)
   [[nil] [nil] [nil] [10.0] [12.5] [20.0] [20.0] [nil] [nil] [nil]]
   (calculate-bird-scarcity [:/ 100.0 [:field-id $count]]))
 
 ;; ok, what if we use multiple args to divide, and more than one is zero?
-(datasets/expect-with-drivers (non-timeseries-drivers-with-feature :expressions)
+(datasets/expect-with-drivers (qp.test/non-timeseries-drivers-with-feature :expressions)
   [[nil] [nil] [nil] [1.0] [1.56] [4.0] [4.0] [nil] [nil] [nil]]
   (calculate-bird-scarcity [:/ 100.0 [:field-id $count] [:field-id $count]]))
 
 ;; are nulls/zeroes still handled appropriately when nested inside other expressions?
-(datasets/expect-with-drivers (non-timeseries-drivers-with-feature :expressions)
+(datasets/expect-with-drivers (qp.test/non-timeseries-drivers-with-feature :expressions)
   [[nil] [nil] [nil] [20.0] [25.0] [40.0] [40.0] [nil] [nil] [nil]]
   (calculate-bird-scarcity [:* [:/ 100.0 [:field-id $count]] 2]))
 
 ;; if a zero is present in the NUMERATOR we should return ZERO and not NULL
 ;; (`0 / 10 = 0`; `10 / 0 = NULL`, at least as far as MBQL is concerned)
-(datasets/expect-with-drivers (non-timeseries-drivers-with-feature :expressions)
+(datasets/expect-with-drivers (qp.test/non-timeseries-drivers-with-feature :expressions)
  [[nil] [0.0] [0.0] [1.0] [0.8] [0.5] [0.5] [nil] [0.0] [0.0]]
  (calculate-bird-scarcity [:/ [:field-id $count] 10]))
 
 ;; can addition handle nulls & zeroes?
-(datasets/expect-with-drivers (non-timeseries-drivers-with-feature :expressions)
+(datasets/expect-with-drivers (qp.test/non-timeseries-drivers-with-feature :expressions)
   [[nil] [10.0] [10.0] [20.0] [18.0] [15.0] [15.0] [nil] [10.0] [10.0]]
   (calculate-bird-scarcity [:+ [:field-id $count] 10]))
 
 ;; can subtraction handle nulls & zeroes?
-(datasets/expect-with-drivers (non-timeseries-drivers-with-feature :expressions)
+(datasets/expect-with-drivers (qp.test/non-timeseries-drivers-with-feature :expressions)
   [[nil] [10.0] [10.0] [0.0] [2.0] [5.0] [5.0] [nil] [10.0] [10.0]]
   (calculate-bird-scarcity [:- 10 [:field-id $count]]))
 
 ;; can multiplications handle nulls & zeros?
-(datasets/expect-with-drivers (non-timeseries-drivers-with-feature :expressions)
+(datasets/expect-with-drivers (qp.test/non-timeseries-drivers-with-feature :expressions)
   [[nil] [0.0] [0.0] [10.0] [8.0] [5.0] [5.0] [nil] [0.0] [0.0]]
   (calculate-bird-scarcity [:* 1 [:field-id $count]]))
diff --git a/test/metabase/query_processor_test/nested_queries_test.clj b/test/metabase/query_processor_test/nested_queries_test.clj
index c2acf3075af4123ac834bf364c8f3f4c2de69d5b..ea1e9c1e62abcefc2b0eb7271f911db48b993cfe 100644
--- a/test/metabase/query_processor_test/nested_queries_test.clj
+++ b/test/metabase/query_processor_test/nested_queries_test.clj
@@ -1,10 +1,8 @@
 (ns metabase.query-processor-test.nested-queries-test
   "Tests for handling queries with nested expressions."
-  (:require [clojure.string :as str]
-            [expectations :refer [expect]]
+  (:require [expectations :refer [expect]]
             [honeysql.core :as hsql]
             [metabase
-             [driver :as driver]
              [query-processor :as qp]
              [query-processor-test :as qp.test]
              [util :as u]]
@@ -12,7 +10,6 @@
             [metabase.models
              [card :as card :refer [Card]]
              [collection :as collection :refer [Collection]]
-             [field :refer [Field]]
              [permissions :as perms]
              [permissions-group :as group]
              [segment :refer [Segment]]]
@@ -24,20 +21,8 @@
              [datasets :as datasets]
              [users :refer [user->client]]]
             [metabase.util.honeysql-extensions :as hx]
-            [toucan.db :as db]
             [toucan.util.test :as tt]))
 
-(defn- rows+cols
-  "Return the `:rows` and relevant parts of `:cols` from the `results`.
-   (This is used to keep the output of various tests below focused and manageable.)"
-  {:style/indent 0}
-  [results]
-  {:rows (qp.test/rows results)
-   :cols (for [col (get-in results [:data :cols])]
-           {:name      (str/lower-case (:name col))
-            :base_type (:base_type col)})})
-
-
 ;; make sure we can do a basic query with MBQL source-query
 (datasets/expect-with-drivers (qp.test/non-timeseries-drivers-with-feature :nested-queries)
   {:rows [[1 "Red Medicine"                  4 10.0646 -165.374 3]
@@ -45,21 +30,16 @@
           [3 "The Apple Pan"                11 34.0406 -118.428 2]
           [4 "Wurstküche"                   29 33.9997 -118.465 2]
           [5 "Brite Spot Family Restaurant" 20 34.0778 -118.261 2]]
-   :cols [{:name "id",          :base_type (data/id-field-type)}
-          {:name "name",        :base_type :type/Text}
-          {:name "category_id", :base_type (data/expected-base-type->actual :type/Integer)}
-          {:name "latitude",    :base_type :type/Float}
-          {:name "longitude",   :base_type :type/Float}
-          {:name "price",       :base_type (data/expected-base-type->actual :type/Integer)}]}
-  (qp.test/format-rows-by [int str int (partial u/round-to-decimals 4) (partial u/round-to-decimals 4) int]
-    (rows+cols
-     (qp/process-query
-       {:database (data/id)
-        :type     :query
-        :query    {:source-query {:source-table (data/id :venues)
-                                  :order-by     [[:asc (data/id :venues :id)]]
-                                  :limit        10}
-                   :limit        5}}))))
+   :cols (mapv
+          (partial qp.test/field-literal-col :venues)
+          [:id :name :category_id :latitude :longitude :price])}
+  (qp.test/rows-and-cols
+    (qp.test/format-rows-by :venues
+      (data/run-mbql-query nil
+        {:source-query {:source-table $$venues
+                        :order-by     [[:asc $venues.id]]
+                        :limit        10}
+         :limit        5}))))
 
 ;; make sure we can do a basic query with a SQL source-query
 (datasets/expect-with-drivers (qp.test/non-timeseries-drivers-with-feature :nested-queries)
@@ -68,66 +48,51 @@
           [3 -118.428 11 2 "The Apple Pan"                34.0406]
           [4 -118.465 29 2 "Wurstküche"                   33.9997]
           [5 -118.261 20 2 "Brite Spot Family Restaurant" 34.0778]]
-   ;; Oracle doesn't have Integer types, they always come back as DECIMAL
-   :cols [{:name "id",          :base_type (case driver/*driver* :oracle :type/Decimal :type/Integer)}
-          {:name "longitude",   :base_type :type/Float}
-          {:name "category_id", :base_type (case driver/*driver* :oracle :type/Decimal :type/Integer)}
-          {:name "price",       :base_type (case driver/*driver* :oracle :type/Decimal :type/Integer)}
-          {:name "name",        :base_type :type/Text}
-          {:name "latitude",    :base_type :type/Float}]}
-  (qp.test/format-rows-by [int (partial u/round-to-decimals 4) int int str (partial u/round-to-decimals 4)]
-    (rows+cols
-     (qp/process-query
-       {:database (data/id)
-        :type     :query
-        :query    {:source-query {:native (:query
-                                           (qp/query->native
-                                             (data/mbql-query venues
-                                               {:fields [[:field-id $id]
-                                                         [:field-id $longitude]
-                                                         [:field-id $category_id]
-                                                         [:field-id $price]
-                                                         [:field-id $name]
-                                                         [:field-id $latitude]]})))}
-                   :order-by     [[:asc [:field-literal (data/format-name :id) :type/Integer]]]
-                   :limit        5}}))))
-
-
-(def ^:private breakout-results
+   :cols (mapv (partial qp.test/native-query-col :venues) [:id :longitude :category_id :price :name :latitude])}
+  (qp.test/format-rows-by [int 4.0 int int str 4.0]
+    (let [{source-query :query} (qp/query->native
+                                  (data/mbql-query venues
+                                    {:fields [$id $longitude $category_id $price $name $latitude]}))]
+      (qp.test/rows-and-cols
+        (data/run-mbql-query venues
+          {:source-query {:native source-query}
+           :order-by     [[:asc *venues.id]]
+           :limit        5})))))
+
+
+(defn- breakout-results [& {:keys [has-source-metadata?], :or {has-source-metadata? true}}]
   {:rows [[1 22]
           [2 59]
           [3 13]
           [4  6]]
-   :cols [{:name "price", :base_type :type/Integer}
-          {:name "count", :base_type :type/Integer}]})
+   :cols [(cond-> (qp.test/breakout-col (qp.test/field-literal-col :venues :price))
+            (not has-source-metadata?)
+            (dissoc :id :special_type :settings :fingerprint :table_id))
+          (qp.test/aggregate-col :count)]})
 
 ;; make sure we can do a query with breakout and aggregation using an MBQL source query
 (datasets/expect-with-drivers (qp.test/non-timeseries-drivers-with-feature :nested-queries)
-  breakout-results
-  (rows+cols
-   (qp.test/format-rows-by [int int]
-     (qp/process-query
-       {:database (data/id)
-        :type     :query
-        :query    {:source-query {:source-table (data/id :venues)}
-                   :aggregation  [:count]
-                   :breakout     [[:field-literal (keyword (data/format-name :price)) :type/Integer]]}}))))
+  (breakout-results)
+  (qp.test/rows-and-cols
+    (qp.test/format-rows-by [int int]
+      (data/run-mbql-query venues
+        {:source-query {:source-table $$venues}
+         :aggregation  [:count]
+         :breakout     [*price]}))))
 
 ;; Test including a breakout of a nested query column that follows an FK
 (datasets/expect-with-drivers (qp.test/non-timeseries-drivers-with-feature :nested-queries :foreign-keys)
   {:rows [[1 174] [2 474] [3 78] [4 39]]
-   :cols [{:name "price", :base_type (data/expected-base-type->actual :type/Integer)}
-          {:name "count", :base_type :type/Integer}]}
-  (rows+cols
-   (qp.test/format-rows-by [int int]
-     (qp/process-query
-       {:database (data/id)
-        :type     :query
-        :query    {:source-query {:source-table (data/id :checkins)
-                                  :filter       [:> (data/id :checkins :date) "2014-01-01"]}
-                   :aggregation  [:count]
-                   :order-by     [[:asc [:fk-> (data/id :checkins :venue_id) (data/id :venues :price)]]]
-                   :breakout     [[:fk-> (data/id :checkins :venue_id) (data/id :venues :price)]]}}))))
+   :cols [(qp.test/breakout-col (qp.test/fk-col :checkins :venue_id :venues :price))
+          (qp.test/aggregate-col :count)]}
+  (qp.test/rows-and-cols
+    (qp.test/format-rows-by [int int]
+      (data/run-mbql-query checkins
+        {:source-query {:source-table $$checkins
+                        :filter       [:> $date "2014-01-01"]}
+         :aggregation  [:count]
+         :order-by     [[:asc $venue_id->venues.price]]
+         :breakout     [$venue_id->venues.price]}))))
 
 
 ;; Test two breakout columns from the nested query, both following an FK
@@ -137,21 +102,19 @@
           [2 33.9997 7]
           [3 10.0646 2]
           [4 33.983 2]],
-   :cols [{:name "price", :base_type (data/expected-base-type->actual :type/Integer)}
-          {:name "latitude", :base_type :type/Float}
-          {:name "count", :base_type :type/Integer}]}
-  (rows+cols
-   (qp.test/format-rows-by [int (partial u/round-to-decimals 4) int]
-     (qp/process-query
-       {:database (data/id)
-        :type     :query
-        :query    {:source-query {:source-table (data/id :checkins)
-                                  :filter       [:> (data/id :checkins :date) "2014-01-01"]}
-                   :filter       [:< [:fk-> (data/id :checkins :venue_id) (data/id :venues :latitude)] 34]
-                   :aggregation  [:count]
-                   :order-by     [[:asc [:fk-> (data/id :checkins :venue_id) (data/id :venues :price)]]]
-                   :breakout     [[:fk-> (data/id :checkins :venue_id) (data/id :venues :price)]
-                                  [:fk-> (data/id :checkins :venue_id) (data/id :venues :latitude)]]}}))))
+   :cols [(qp.test/breakout-col (qp.test/fk-col :checkins :venue_id :venues :price))
+          (qp.test/breakout-col (qp.test/fk-col :checkins :venue_id :venues :latitude))
+          (qp.test/aggregate-col :count)]}
+  (qp.test/rows-and-cols
+    (qp.test/format-rows-by [int 4.0 int]
+      (data/run-mbql-query checkins
+        {:source-query {:source-table $$checkins
+                        :filter       [:> $date "2014-01-01"]}
+         :filter       [:< $venue_id->venues.latitude 34]
+         :aggregation  [:count]
+         :order-by     [[:asc $venue_id->venues.price]]
+         :breakout     [$venue_id->venues.price
+                        $venue_id->venues.latitude]}))))
 
 ;; Test two breakout columns from the nested query, one following an FK the other from the source table
 (datasets/expect-with-drivers (qp.test/non-timeseries-drivers-with-feature :nested-queries :foreign-keys)
@@ -159,44 +122,41 @@
           [1 2 14]
           [1 3 13]
           [1 4 8]
-          [1 5 10]],
-   :cols [{:name "price",   :base_type (data/expected-base-type->actual :type/Integer)}
-          {:name "user_id", :base_type :type/Integer}
-          {:name "count",   :base_type :type/Integer}]}
-  (rows+cols
+          [1 5 10]]
+   :cols [(qp.test/breakout-col (qp.test/fk-col :checkins :venue_id :venues :price))
+          (qp.test/breakout-col (qp.test/field-literal-col :checkins :user_id))
+          (qp.test/aggregate-col :count)]}
+  (qp.test/rows-and-cols
     (qp.test/format-rows-by [int int int]
-      (qp/process-query
-        {:database (data/id)
-         :type     :query
-         :query    {:source-query {:source-table (data/id :checkins)
-                                   :filter       [:> (data/id :checkins :date) "2014-01-01"]}
-                    :aggregation  [:count]
-                    :filter       [:= [:fk-> (data/id :checkins :venue_id) (data/id :venues :price)] 1]
-                    :order-by     [[:asc [:fk-> (data/id :checkins :venue_id) (data/id :venues :price)]]]
-                    :breakout     [[:fk-> (data/id :checkins :venue_id) (data/id :venues :price)]
-                                   [:field-literal (keyword (data/format-name :user_id)) :type/Integer]]
-                    :limit        5}}))))
+      (data/run-mbql-query checkins
+        {:source-query {:source-table $$checkins
+                        :filter       [:> $date "2014-01-01"]}
+         :aggregation  [:count]
+         :filter       [:= $venue_id->venues.price 1]
+         :order-by     [[:asc $venue_id->venues.price]]
+         :breakout     [$venue_id->venues.price *user_id]
+         :limit        5}))))
 
 ;; make sure we can do a query with breakout and aggregation using a SQL source query
 (datasets/expect-with-drivers (qp.test/non-timeseries-drivers-with-feature :nested-queries)
-  breakout-results
-  (rows+cols
-   (qp.test/format-rows-by [int int]
-     (qp/process-query
-       {:database (data/id)
-        :type     :query
-        :query    {:source-query {:native (:query (qp/query->native (data/mbql-query venues)))}
-                   :aggregation  [:count]
-                   :breakout     [[:field-literal (keyword (data/format-name :price)) :type/Integer]]}}))))
+  (breakout-results :has-source-metadata? false)
+  (qp.test/rows-and-cols
+    (qp.test/format-rows-by [int int]
+      (data/run-mbql-query venues
+        {:source-query {:native (:query (qp/query->native (data/mbql-query venues)))}
+         :aggregation  [:count]
+         :breakout     [*price]}))))
 
 
 (defn- mbql-card-def
   "Basic MBQL Card definition. Pass kv-pair clauses for the inner query."
   {:style/indent 0}
-  [& {:as clauses}]
-  {:dataset_query {:database (data/id)
-                   :type     :query
-                   :query    clauses}})
+  ([m]
+   {:dataset_query {:database (data/id)
+                    :type     :query
+                    :query    m}})
+  ([k v & {:as more}]
+   (mbql-card-def (merge {k v} more))))
 
 (defn- venues-mbql-card-def
   "A basic Card definition that returns raw data for the venues test table.
@@ -206,80 +166,85 @@
   (apply mbql-card-def :source-table (data/id :venues) additional-clauses))
 
 
-(defn- query-with-source-card {:style/indent 1} [card & {:as additional-clauses}]
-  {:database mbql.s/saved-questions-virtual-database-id
-   :type     :query
-   :query    (merge {:source-table (str "card__" (u/get-id card))}
-                    additional-clauses)})
+(defn- query-with-source-card
+  {:style/indent 1}
+  ([card]
+   {:database mbql.s/saved-questions-virtual-database-id
+    :type     :query
+    :query    {:source-table (str "card__" (u/get-id card))}})
+
+  ([card m]
+   (update (query-with-source-card card) :query merge m))
+
+  ([card k v & {:as more}]
+   (query-with-source-card card (merge {k v} more))))
 
 ;; Make sure we can run queries using source table `card__id` format. This is the format that is actually used by the
 ;; frontend; it gets translated to the normal `source-query` format by middleware. It's provided as a convenience so
 ;; only minimal changes need to be made to the frontend.
 (expect
-  breakout-results
+  (breakout-results)
   (tt/with-temp Card [card (venues-mbql-card-def)]
-    (rows+cols
-     (qp.test/format-rows-by [int int]
-       (qp/process-query
-         (query-with-source-card card
-           :aggregation [:count]
-           :breakout    [[:field-literal (keyword (data/format-name :price)) :type/Integer]]))))))
+    (qp.test/rows-and-cols
+      (qp.test/format-rows-by [int int]
+        (qp/process-query
+          (query-with-source-card card
+            (data/$ids venues
+              {:aggregation [:count]
+               :breakout    [*price]})))))))
 
 ;; make sure `card__id`-style queries work with native source queries as well
 (expect
-  breakout-results
+  (breakout-results :has-source-metadata? false)
   (tt/with-temp Card [card {:dataset_query {:database (data/id)
                                             :type     :native
                                             :native   {:query "SELECT * FROM VENUES"}}}]
-    (rows+cols
-     (qp.test/format-rows-by [int int]
-       (qp/process-query
-         (query-with-source-card card
-           :aggregation [:count]
-           :breakout    [[:field-literal (keyword (data/format-name :price)) :type/Integer]]))))))
+    (qp.test/rows-and-cols
+      (qp.test/format-rows-by [int int]
+        (qp/process-query
+          (query-with-source-card card
+            (data/$ids venues
+              {:aggregation [:count]
+               :breakout    [*price]})))))))
 
 ;; Ensure trailing comments are trimmed and don't cause a wrapping SQL query to fail
 (expect
-  breakout-results
+  (breakout-results :has-source-metadata? false)
   (tt/with-temp Card [card {:dataset_query {:database (data/id)
                                             :type     :native
                                             :native   {:query "SELECT * FROM VENUES -- small comment here"}}}]
-    (rows+cols
-     (qp.test/format-rows-by [int int]
-       (qp/process-query
-         (query-with-source-card card
-           :aggregation [:count]
-           :breakout    [[:field-literal (keyword (data/format-name :price)) :type/Integer]]))))))
+    (qp.test/rows-and-cols
+      (qp.test/format-rows-by [int int]
+        (qp/process-query
+          (query-with-source-card card
+            (data/$ids venues
+              {:aggregation [:count]
+               :breakout    [*price]})))))))
 
 ;; Ensure trailing comments followed by a newline are trimmed and don't cause a wrapping SQL query to fail
 (expect
-  breakout-results
+  (breakout-results :has-source-metadata? false)
   (tt/with-temp Card [card {:dataset_query {:database (data/id)
                                             :type     :native
                                             :native   {:query "SELECT * FROM VENUES -- small comment here\n"}}}]
-    (rows+cols
-     (qp.test/format-rows-by [int int]
-       (qp/process-query
-         (query-with-source-card card
-           :aggregation [:count]
-           :breakout    [[:field-literal (keyword (data/format-name :price)) :type/Integer]]))))))
+    (qp.test/rows-and-cols
+      (qp.test/format-rows-by [int int]
+        (qp/process-query
+          (query-with-source-card card
+            (data/$ids venues
+              {:aggregation [:count]
+               :breakout    [*price]})))))))
 
 
 ;; make sure we can filter by a field literal
 (expect
   {:rows [[1 "Red Medicine" 4 10.0646 -165.374 3]]
-   :cols [{:name "id",          :base_type :type/BigInteger}
-          {:name "name",        :base_type :type/Text}
-          {:name "category_id", :base_type :type/Integer}
-          {:name "latitude",    :base_type :type/Float}
-          {:name "longitude",   :base_type :type/Float}
-          {:name "price",       :base_type :type/Integer}]}
-  (rows+cols
-   (qp/process-query
-     {:database (data/id)
-      :type     :query
-      :query    {:source-query {:source-table (data/id :venues)}
-                 :filter       [:= [:field-literal (data/format-name :id) :type/Integer] 1]}})))
+   :cols (mapv (partial qp.test/field-literal-col :venues)
+               [:id :name :category_id :latitude :longitude :price])}
+  (qp.test/rows-and-cols
+    (data/run-mbql-query venues
+      {:source-query {:source-table $$venues}
+       :filter       [:= *id 1]})))
 
 (defn- honeysql->sql
   "Convert `honeysql-form` to the format returned by `query->native`. Writing HoneySQL is a lot easier that writing
@@ -352,11 +317,11 @@
    :params nil}
   (qp/query->native
     (data/mbql-query venues
-     {:source-query {:source-table $$venues
-                     :aggregation  [[:stddev $id]]
-                     :breakout     [$price]
-                     :order-by     [[[:aggregation 0] :descending]]}
-      :aggregation  [[:avg *stddev/Integer]]})))
+      {:source-query {:source-table $$venues
+                      :aggregation  [[:stddev $id]]
+                      :breakout     [$price]
+                      :order-by     [[[:aggregation 0] :descending]]}
+       :aggregation  [[:avg *stddev/Integer]]})))
 
 ;; make sure that we handle [field-id [field-literal ...]] forms gracefully, despite that not making any sense
 (expect
@@ -425,108 +390,57 @@
        :type     :query
        :query    {:source-table (str "card__" (u/get-id card))}})))
 
-(defn results-metadata {:style/indent 0} [results]
-  (when (= :failed (:status results))
-    (throw (ex-info "No results metadata." results)))
-  (for [col (get-in results [:data :cols])]
-    (u/select-non-nil-keys col [:base_type :display_name :id :name :special_type :table_id :unit :datetime-unit])))
-
 ;; make sure a query using a source query comes back with the correct columns metadata
 (expect
-  [{:base_type    :type/BigInteger
-    :display_name "ID"
-    :id           (data/id :venues :id)
-    :name         "ID"
-    :special_type :type/PK
-    :table_id     (data/id :venues)}
-   {:base_type    :type/Text
-    :display_name "Name"
-    :id           (data/id :venues :name)
-    :name         "NAME"
-    :special_type :type/Name
-    :table_id     (data/id :venues)}
-   {:base_type    :type/Integer
-    :display_name "Category ID"
-    :id           (data/id :venues :category_id)
-    :name         "CATEGORY_ID"
-    :special_type :type/FK
-    :table_id     (data/id :venues)}
-   {:base_type    :type/Float
-    :display_name "Latitude"
-    :id           (data/id :venues :latitude)
-    :name         "LATITUDE"
-    :special_type :type/Latitude
-    :table_id     (data/id :venues)}
-   {:base_type    :type/Float
-    :display_name "Longitude"
-    :id           (data/id :venues :longitude)
-    :name         "LONGITUDE"
-    :special_type :type/Longitude
-    :table_id     (data/id :venues)}
-   {:base_type    :type/Integer
-    :display_name "Price"
-    :id           (data/id :venues :price)
-    :name         "PRICE"
-    :special_type :type/Category
-    :table_id     (data/id :venues)}]
-  (-> (tt/with-temp Card [card (venues-mbql-card-def)]
-        (qp/process-query (query-with-source-card card)))
-      results-metadata))
+  (map
+   (partial qp.test/field-literal-col :venues)
+   [:id :name :category_id :latitude :longitude :price])
+  (qp.test/cols
+    (tt/with-temp Card [card (venues-mbql-card-def)]
+      (qp/process-query (query-with-source-card card)))))
 
 ;; make sure a breakout/aggregate query using a source query comes back with the correct columns metadata
 (expect
-  [{:base_type    :type/Text
-    :name         "PRICE"
-    :display_name "Price"}
-   {:base_type    :type/Integer
-    :display_name "count"
-    :name         "count"
-    :special_type :type/Number}]
-  (-> (tt/with-temp Card [card (venues-mbql-card-def)]
-        (qp/process-query (query-with-source-card card
-                            :aggregation  [[:count]]
-                            :breakout     [[:field-literal "PRICE" :type/Text]])))
-      results-metadata))
+  [(qp.test/breakout-col (qp.test/field-literal-col :venues :price))
+   (qp.test/aggregate-col :count)]
+  (qp.test/cols
+    (tt/with-temp Card [card (venues-mbql-card-def)]
+      (qp/process-query
+        (query-with-source-card card
+          (data/$ids venues
+            {:aggregation [[:count]]
+             :breakout    [*price]}))))))
 
 ;; make sure nested queries return the right columns metadata for SQL source queries and datetime breakouts
 (expect
-  [{:base_type    :type/DateTime
-    :display_name "Date"
-    :name         "DATE"
-    :unit         :day}
-   {:base_type    :type/Integer
-    :display_name "count"
-    :name         "count"
-    :special_type :type/Number}]
-  (-> (tt/with-temp Card [card {:dataset_query {:database (data/id)
-                                                :type     :native
-                                                :native   {:query "SELECT * FROM CHECKINS"}}}]
-        (qp/process-query (query-with-source-card card
-                            :aggregation  [[:count]]
-                            :breakout     [[:datetime-field [:field-literal "DATE" :type/DateTime] :day]])))
-      results-metadata))
+ [(-> (qp.test/breakout-col (qp.test/field-literal-col :checkins :date))
+      (assoc :field_ref [:datetime-field [:field-literal "DATE" :type/Date] :day]
+             :unit      :day)
+      ;; because this field literal comes from a native query that does not include `:source-metadata` it won't have
+      ;; the usual extra keys
+      (dissoc :special_type :table_id :id :settings :fingerprint))
+  (qp.test/aggregate-col :count)]
+ (qp.test/cols
+   (tt/with-temp Card [card {:dataset_query {:database (data/id)
+                                             :type     :native
+                                             :native   {:query "SELECT * FROM CHECKINS"}}}]
+     (qp/process-query
+       (query-with-source-card card
+         (data/$ids checkins
+           {:aggregation [[:count]]
+            :breakout    [!day.*date]}))))))
 
 ;; make sure when doing a nested query we give you metadata that would suggest you should be able to break out a *YEAR*
 (expect
-  [{:base_type    :type/Date
-    :display_name "Date"
-    :id           (data/id :checkins :date)
-    :name         "DATE"
-    :table_id     (data/id :checkins)
-    :unit         :year}
-   {:base_type    :type/Integer
-    :display_name "Count"
-    :name         "count"
-    :special_type :type/Number}]
-  (-> (tt/with-temp Card [card (mbql-card-def
-                                 :source-table (data/id :checkins)
-                                 :aggregation  [[:count]]
-                                 :breakout     [[:datetime-field [:field-id (data/id :checkins :date)] :year]])]
-        (qp/process-query (query-with-source-card card)))
-      results-metadata))
-
-(defn- field-name [table-kw field-kw]
-  (db/select-one-field :name Field :id (data/id table-kw field-kw)))
+  [(assoc (qp.test/field-literal-col :checkins :date) :unit :year)
+   (qp.test/field-literal-col (qp.test/aggregate-col :count))]
+  (qp.test/cols
+    (tt/with-temp Card [card (mbql-card-def
+                               (data/$ids checkins
+                                 {:source-table $$checkins
+                                  :aggregation  [[:count]]
+                                  :breakout     [!year.date]}))]
+      (qp/process-query (query-with-source-card card)))))
 
 (defn- completed-status [{:keys [status], :as results}]
   (if (= status :completed)
@@ -536,52 +450,49 @@
 ;; make sure using a time interval filter works
 (datasets/expect-with-drivers (qp.test/non-timeseries-drivers-with-feature :nested-queries)
   :completed
-  (tt/with-temp Card [card (mbql-card-def
-                             :source-table (data/id :checkins))]
+  (tt/with-temp Card [card (mbql-card-def (data/$ids {:source-table $$checkins}))]
     (-> (query-with-source-card card
-          :filter [:time-interval [:field-literal (field-name :checkins :date) :type/DateTime] -30 :day])
+          (data/$ids checkins
+            {:filter [:time-interval *date -30 :day]}))
         qp/process-query
         completed-status)))
 
 ;; make sure that wrapping a field literal in a datetime-field clause works correctly in filters & breakouts
 (datasets/expect-with-drivers (qp.test/non-timeseries-drivers-with-feature :nested-queries)
   :completed
-  (tt/with-temp Card [card (mbql-card-def
-                             :source-table (data/id :checkins))]
+  (tt/with-temp Card [card (mbql-card-def (data/$ids {:source-table $$checkins}))]
     (-> (query-with-source-card card
-          :aggregation [[:count]]
-          :filter      [:= [:datetime-field [:field-literal (field-name :checkins :date) :type/Date] :quarter] "2014-01-01T08:00:00.000Z"]
-          :breakout    [[:datetime-field [:field-literal (field-name :checkins :date) :type/Date] :month]])
+          (data/$ids :checkins
+            {:aggregation [[:count]]
+             :filter      [:= !quarter.*date "2014-01-01T08:00:00.000Z"]
+             :breakout    [!month.*date]}))
         qp/process-query
         completed-status)))
 
 ;; make sure timeseries queries generated by "drag-to-filter" work correctly
 (expect
   :completed
-  (tt/with-temp Card [card (mbql-card-def
-                             :source-table (data/id :checkins))]
-    (-> (query-with-source-card card
-          :aggregation [[:count]]
-          :breakout    [[:datetime-field [:field-literal "DATE" :type/Date] :week]]
-          :filter      [:between
-                        [:datetime-field [:field-literal "DATE" :type/Date] :month]
-                        "2014-02-01T00:00:00-08:00"
-                        "2014-05-01T00:00:00-07:00"])
-        qp/process-query
-        completed-status)))
+  (tt/with-temp Card [card (mbql-card-def (data/$ids {:source-table $$checkins}))]
+    (completed-status
+     (qp/process-query
+       (query-with-source-card card
+         (data/$ids checkins
+           {:aggregation [[:count]]
+            :breakout    [!week.*date]
+            :filter      [:between !week.*date "2014-02-01T00:00:00-08:00" "2014-05-01T00:00:00-07:00"]}))))))
 
 ;; Make sure that macro expansion works inside of a neested query, when using a compound filter clause (#5974)
 (expect
   [[22]]
-  (tt/with-temp* [Segment [segment {:table_id   (data/id :venues)
-                                    :definition {:filter [:= (data/id :venues :price) 1]}}]
+  (tt/with-temp* [Segment [segment (data/$ids {:table_id   $$venues
+                                               :definition {:filter [:= $venues.price 1]}})]
                   Card    [card (mbql-card-def
                                   :source-table (data/id :venues)
                                   :filter       [:and [:segment (u/get-id segment)]])]]
-    (-> (query-with-source-card card
-          :aggregation [:count])
-        qp/process-query
-        qp.test/rows)))
+    (qp.test/rows
+      (qp/process-query
+        (query-with-source-card card
+          {:aggregation [:count]})))))
 
 
 ;; Make suer you're allowed to save a query that uses a SQL-based source query even if you don't have SQL *write*1337
@@ -699,7 +610,7 @@
    [37 "bigmista's barbecue"  5 34.118  -118.26  2]
    [38 "Zeke's Smokehouse"    5 34.2053 -118.226 2]
    [39 "Baby Blues BBQ"       5 34.0003 -118.465 2]]
-  (qp.test/format-rows-by [int str int (partial u/round-to-decimals 4) (partial u/round-to-decimals 4) int]
+  (qp.test/format-rows-by :venues
     (qp.test/rows
       (qp/process-query
         (data/mbql-query venues
@@ -718,9 +629,7 @@
         {:source-query {:source-table $$checkins
                         :order-by     [[:asc $id]]}
          :fields       [$id]
-         :filter       [:=
-                        [:field-literal (field-name :checkins :date) "type/DateTime"]
-                        "2014-03-30"]}))))
+         :filter       [:= *date "2014-03-30"]}))))
 
 ;; make sure filters in source queries are applied correctly!
 (datasets/expect-with-drivers (qp.test/non-timeseries-drivers-with-feature :nested-queries :foreign-keys)
@@ -735,3 +644,30 @@
          :order-by     [[:asc $venue_id->venues.name]]
          :breakout     [$venue_id->venues.name]
          :filter       [:starts-with $venue_id->venues.name "F"]}))))
+
+;; Do nested queries work with two of the same aggregation? (#9767)
+(datasets/expect-with-drivers (qp.test/non-timeseries-drivers-with-feature :nested-queries :foreign-keys)
+  {:rows
+   [["2014-02-01T00:00:00.000Z" 302 1804]
+    ["2014-03-01T00:00:00.000Z" 350 2362]]
+   :cols
+   [(assoc (qp.test/field-literal-col :checkins :date)
+      :unit :month)
+    (let [{base-type :base_type, :as literal-col} (qp.test/field-literal-col :checkins :user_id)]
+      (assoc (qp.test/aggregate-col :sum literal-col)
+        :source    :fields
+        :field_ref [:field-literal "sum" base-type]))
+    (let [{base-type :base_type, :as literal-col} (qp.test/field-literal-col :checkins :venue_id)]
+      (assoc (qp.test/aggregate-col :sum literal-col)
+        :name      "sum_2"
+        :source    :fields
+        :field_ref [:field-literal "sum_2" base-type]))]}
+  (qp.test/format-rows-by [identity int int]
+    (qp.test/rows-and-cols
+      (data/run-mbql-query checkins
+        {:source-query
+         {:source-table $$checkins
+          :aggregation  [[:sum $user_id] [:sum $venue_id]]
+          :breakout     [!month.date]}
+         :filter [:> *sum/Float 300]
+         :limit  2}))))
diff --git a/test/metabase/test/data.clj b/test/metabase/test/data.clj
index 2c40fc0af60a1ba62cbd7aea5606748bf8c7268d..58a7e999ca6f364ebadf801080b001b93ce87d59 100644
--- a/test/metabase/test/data.clj
+++ b/test/metabase/test/data.clj
@@ -167,16 +167,17 @@
 (defmacro run-mbql-query
   "Like `mbql-query`, but runs the query as well."
   {:style/indent 1}
-  [table & [query]]
+  [table-name & [query]]
   `(qp/process-query
-     (mbql-query ~table ~(or query {}))))
+     (mbql-query ~table-name ~(or query {}))))
 
 (defn format-name
   "Format a SQL schema, table, or field identifier in the correct way for the current database by calling the driver's
   implementation of `format-name`. (Most databases use the default implementation of `identity`; H2 uses
   `clojure.string/upper-case`.) This function DOES NOT quote the identifier."
-  [nm]
-  (tx/format-name (tx/driver) (name nm)))
+  [a-name]
+  {:pre [((some-fn keyword? string? symbol?) a-name)]}
+  (tx/format-name (tx/driver) (name a-name)))
 
 (defn id
   "Get the ID of the current database or one of its Tables or Fields. Relies on the dynamic variable `*get-db*`, which
@@ -250,13 +251,6 @@
 
 (defn id-field-type  [] (tx/id-field-type (tx/driver)))
 
-(defn expected-base-type->actual
-  "Return actual `base_type` that will be used for the given driver if we asked for BASE-TYPE. Mainly for Oracle
-  because it doesn't have `INTEGER` types and uses decimals instead."
-  [base-type]
-  (tx/expected-base-type->actual (tx/driver) base-type))
-
-
 ;; The functions below are used so infrequently they hardly belong in this namespace.
 
 (defn dataset-field-values
diff --git a/test/metabase/test/data/interface.clj b/test/metabase/test/data/interface.clj
index 07622ab36b8763035595c2a417b17416b4ffbd03..75e47a89c2adcd233c9539bea4b901f8091b0131 100644
--- a/test/metabase/test/data/interface.clj
+++ b/test/metabase/test/data/interface.clj
@@ -285,18 +285,6 @@
   :hierarchy #'driver/hierarchy)
 
 
-(defmulti expected-base-type->actual
-  "Return the base type type that is actually used to store Fields of `base-type`. The default implementation of this
-  method is an identity fn. This is provided so DBs that don't support a given base type used in the test data can
-  specifiy what type we should expect in the results instead. For example, Oracle has no `INTEGER` data types, so
-  `:type/Integer` test values are instead stored as `NUMBER`, which we map to `:type/Decimal`."
-  {:arglists '([driver base-type])}
-  dispatch-on-driver-with-test-extensions
-  :hierarchy #'driver/hierarchy)
-
-(defmethod expected-base-type->actual ::test-extensions [_ base-type] base-type)
-
-
 (defmulti format-name
   "Transform a lowercase string Table or Field name in a way appropriate for this dataset (e.g., `h2` would want to
   upcase these names; `mongo` would want to use `\"_id\"` in place of `\"id\"`. This method should return a string.
diff --git a/test/metabase/timeseries_query_processor_test.clj b/test/metabase/timeseries_query_processor_test.clj
index 054e32033b19d68da8039728db5bca3f6cdb15d3..035559e275fbd480ed5d3a90dfa76bb54ef0ee58 100644
--- a/test/metabase/timeseries_query_processor_test.clj
+++ b/test/metabase/timeseries_query_processor_test.clj
@@ -1,9 +1,7 @@
 (ns metabase.timeseries-query-processor-test
   "Query processor tests for DBs that are event-based, like Druid.
   There architecture is different enough that we can't test them along with our 'normal' DBs in `query-procesor-test`."
-  (:require [metabase
-             [query-processor-test :as qp.test :refer [rows]]
-             [util :as u]]
+  (:require [metabase.query-processor-test :as qp.test]
             [metabase.test.data :as data]
             [metabase.timeseries-query-processor-test.util :as tqp.test]))
 
@@ -137,7 +135,7 @@
    :rows    [[1.992]]}
   (->> (data/run-mbql-query checkins
          {:aggregation [[:avg $venue_price]]})
-       (qp.test/format-rows-by [(partial u/round-to-decimals 3)])
+       (qp.test/format-rows-by [3.0])
        qp.test/rows+column-names))
 
 ;;; distinct count
@@ -917,21 +915,23 @@
 (tqp.test/expect-with-timeseries-dbs
   ;; some sort of weird quirk w/ druid where all columns in breakout get converted to strings
   [["1" 34.0071] ["2" 33.7701] ["3" 10.0646] ["4" 33.983]]
-  (rows (data/run-mbql-query checkins
-          {:aggregation [[:min $venue_latitude]]
-           :breakout    [$venue_price]})))
+  (qp.test/rows
+    (data/run-mbql-query checkins
+      {:aggregation [[:min $venue_latitude]]
+       :breakout    [$venue_price]})))
 
 (tqp.test/expect-with-timeseries-dbs
   [["1" 37.8078] ["2" 40.7794] ["3" 40.7262] ["4" 40.7677]]
-  (rows (data/run-mbql-query checkins
-          {:aggregation [[:max $venue_latitude]]
-           :breakout    [$venue_price]})))
+  (qp.test/rows
+    (data/run-mbql-query checkins
+      {:aggregation [[:max $venue_latitude]]
+       :breakout    [$venue_price]})))
 
 ;; Do we properly handle queries that have more than one of the same aggregation? (#4166)
 (tqp.test/expect-with-timeseries-dbs
   [[35643 1992]]
   (qp.test/format-rows-by [int int]
-    (rows
+    (qp.test/rows
       (data/run-mbql-query checkins
         {:aggregation [[:sum $venue_latitude] [:sum $venue_price]]}))))
 
@@ -941,8 +941,8 @@
    ["Chinese"    3.0]
    ["Wine Bar"   3.0]
    ["Japanese"   2.7]]
-  (qp.test/format-rows-by [str (partial u/round-to-decimals 1)]
-    (rows
+  (qp.test/format-rows-by [str 1.0]
+    (qp.test/rows
       (data/run-mbql-query checkins
         {:aggregation  [[:avg $venue_price]]
          :breakout     [[:field-id $venue_category_name]]