diff --git a/.clj-kondo/config.edn b/.clj-kondo/config.edn
index 782efeeca77e702dd8a1cb010e339539570d5d52..128678d789fe5d00d963223644fc5067f387f83a 100644
--- a/.clj-kondo/config.edn
+++ b/.clj-kondo/config.edn
@@ -3,11 +3,11 @@
  {:unresolved-symbol
   {:exclude
    [instaparse.core/transform
-    (cljs.test/is [=?])
+    (cljs.test/is [=? malli=])
     (clojure.core.logic/fresh)
     (clojure.core.logic/matcha)
     (clojure.core.logic/run)
-    (clojure.test/is [query= sql= =?])
+    (clojure.test/is [query= sql= =? malli=])
     (clojure.tools.macro/macrolet)
     (metabase.async.streaming-response/streaming-response)
     (metabase.driver.druid.query-processor-test/druid-query-returning-rows)
diff --git a/frontend/src/metabase-lib/metrics.ts b/frontend/src/metabase-lib/metrics.ts
new file mode 100644
index 0000000000000000000000000000000000000000..b695ffc8c288ec71b9081a87994f36c7b83f50c5
--- /dev/null
+++ b/frontend/src/metabase-lib/metrics.ts
@@ -0,0 +1,7 @@
+import * as ML from "cljs/metabase.lib.js";
+
+import type { MetricMetadata, Query } from "./types";
+
+export function availableMetrics(query: Query): MetricMetadata[] {
+  return ML.available_metrics(query);
+}
diff --git a/frontend/src/metabase-lib/types.ts b/frontend/src/metabase-lib/types.ts
index 55e0da7746038c4aad58a6124cea0a684a634b34..feeb70cfee14585f3bf75317fd970917a86b206c 100644
--- a/frontend/src/metabase-lib/types.ts
+++ b/frontend/src/metabase-lib/types.ts
@@ -14,6 +14,9 @@ export type TableMetadata = unknown & { _opaque: typeof TableMetadata };
 declare const CardMetadata: unique symbol;
 export type CardMetadata = unknown & { _opaque: typeof CardMetadata };
 
+declare const MetricMetadata: unique symbol;
+export type MetricMetadata = unknown & { _opaque: typeof MetricMetadata };
+
 export type Limit = number | null;
 
 declare const BreakoutClause: unique symbol;
diff --git a/frontend/src/metabase-lib/v2.ts b/frontend/src/metabase-lib/v2.ts
index 2d504f87d95704546d6cd03961384ac0d4fdc098..91a3a8b2a98cc35ec21d596f3487bb94cb05737a 100644
--- a/frontend/src/metabase-lib/v2.ts
+++ b/frontend/src/metabase-lib/v2.ts
@@ -12,6 +12,7 @@ export * from "./limit";
 export * from "./order_by";
 export * from "./filter";
 export * from "./join";
+export * from "./metrics";
 export * from "./query";
 export * from "./temporal_bucket";
 export * from "./types";
diff --git a/src/metabase/api/geojson.clj b/src/metabase/api/geojson.clj
index b92a673c54efe5ab5a9816734dbe59252660db2f..5663edfff6e4e5e97c349cc3e6d5ba4f38d49a1b 100644
--- a/src/metabase/api/geojson.clj
+++ b/src/metabase/api/geojson.clj
@@ -107,6 +107,8 @@
                (setting/set-value-of-type! :json :custom-geojson new-value)))
   :visibility :public)
 
+(def ^:private connection-timeout-ms 8000)
+
 (defn- read-url-and-respond
   "Reads the provided URL and responds with the contents as a stream."
   [url respond]
@@ -114,8 +116,8 @@
                                        (io/reader resource)
                                        (:body (http/get url {:as                 :reader
                                                              :redirect-strategy  :none
-                                                             :socket-timeout     8000
-                                                             :connection-timeout 8000})))
+                                                             :socket-timeout     connection-timeout-ms
+                                                             :connection-timeout connection-timeout-ms})))
               is                     (ReaderInputStream. reader)]
     (respond (-> (response/response is)
                  (response/content-type "application/json")))))
diff --git a/src/metabase/lib/aggregation.cljc b/src/metabase/lib/aggregation.cljc
index e485ab4d12ab3030705e1c436a12dc3f29b4a2c9..b9e72cd8f29f8031a5d15010de860fae59de9cd6 100644
--- a/src/metabase/lib/aggregation.cljc
+++ b/src/metabase/lib/aggregation.cljc
@@ -3,6 +3,7 @@
   (:require
    [medley.core :as m]
    [metabase.lib.common :as lib.common]
+   [metabase.lib.dispatch :as lib.dispatch]
    [metabase.lib.equality :as lib.equality]
    [metabase.lib.hierarchy :as lib.hierarchy]
    [metabase.lib.metadata :as lib.metadata]
@@ -223,12 +224,25 @@
   [aggregation-clause]
   aggregation-clause)
 
+(def ^:private Aggregatable
+  "Schema for something you can pass to [[aggregate]] to add to a query as an aggregation."
+  [:or
+   ::lib.schema.aggregation/aggregation
+   ::lib.schema.common/external-op
+   lib.metadata/MetricMetadata])
+
 (mu/defn aggregate :- ::lib.schema/query
   "Adds an aggregation to query."
-  ([query an-aggregate-clause]
-   (aggregate query -1 an-aggregate-clause))
-  ([query stage-number an-aggregate-clause]
-   (lib.util/add-summary-clause query stage-number :aggregation an-aggregate-clause)))
+  ([query aggregatable]
+   (aggregate query -1 aggregatable))
+
+  ([query        :- ::lib.schema/query
+    stage-number :- :int
+    aggregatable :- Aggregatable]
+   ;; if this is a Metric metadata, convert it to `:metric` MBQL clause before adding.
+   (if (= (lib.dispatch/dispatch-value aggregatable) :metadata/metric)
+     (recur query stage-number (lib.ref/ref aggregatable))
+     (lib.util/add-summary-clause query stage-number :aggregation aggregatable))))
 
 (mu/defn aggregations :- [:maybe [:sequential ::lib.schema.aggregation/aggregation]]
   "Get the aggregations in a given stage of a query."
diff --git a/src/metabase/lib/core.cljc b/src/metabase/lib/core.cljc
index 8120328a7ee20eb9027a593f1b2008446a957b94..a6280df5578a9d7462939b7dfb1aa2ec8890f0fd 100644
--- a/src/metabase/lib/core.cljc
+++ b/src/metabase/lib/core.cljc
@@ -188,6 +188,8 @@
    display-info
    suggested-name
    type-of]
+  [lib.metric
+   available-metrics]
   [lib.native
    #?@(:cljs [->TemplateTags
               TemplateTags->])
diff --git a/src/metabase/lib/hierarchy.cljc b/src/metabase/lib/hierarchy.cljc
index 0e19deeff2204e8a4dbb4a886188d70136aa3c1c..a8e5ab977ce2e3f67255dda33da8313e28b734fd 100644
--- a/src/metabase/lib/hierarchy.cljc
+++ b/src/metabase/lib/hierarchy.cljc
@@ -7,7 +7,9 @@
 (defn derive
   "Like [[clojure.core/derive]], but affects [[hierarchy]] rather than the global hierarchy."
   [tag parent]
-  (swap! hierarchy clojure.core/derive tag parent))
+  (swap! hierarchy clojure.core/derive tag parent)
+  ;; for REPL convenience so we don't dump a lot of garbage
+  nil)
 
 (defn isa?
   "Like [[clojure.core/isa?]], but uses [[hierarchy]]."
diff --git a/src/metabase/lib/js.cljs b/src/metabase/lib/js.cljs
index d24335dcedfc702d85b2aeb8e8fa4ad1c1679b16..3f0e2912c7b15016ea67b41ba22db7bb97cdfef5 100644
--- a/src/metabase/lib/js.cljs
+++ b/src/metabase/lib/js.cljs
@@ -555,3 +555,9 @@
   "Returns the native query's template tags"
   [a-query]
   (lib.core/TemplateTags-> (lib.core/template-tags a-query)))
+
+(defn ^:export available-metrics
+  "Get a list of Metrics that you may consider using as aggregations for a query. Returns JS array of opaque Metric
+  metadata objects."
+  [a-query]
+  (to-array (lib.core/available-metrics a-query)))
diff --git a/src/metabase/lib/js/metadata.cljs b/src/metabase/lib/js/metadata.cljs
index d7daa9943ce326264307ca9c2a926b08b05e7f9b..3207e2e27595ab46d85b86a4dbd6f803cbdb379d 100644
--- a/src/metabase/lib/js/metadata.cljs
+++ b/src/metabase/lib/js/metadata.cljs
@@ -375,6 +375,12 @@
         :when              (and a-field (= (:table-id a-field) table-id))]
     a-field))
 
+(defn- metrics [metadata table-id]
+  (for [[_id metric-delay] (some-> metadata :metrics deref)
+        :let               [a-metric (some-> metric-delay deref)]
+        :when              (and a-metric (= (:table-id a-metric) table-id))]
+    a-metric))
+
 (defn metadata-provider
   "Use a `metabase-lib/metadata/Metadata` as a [[metabase.lib.metadata.protocols/MetadataProvider]]."
   [database-id unparsed-metadata]
@@ -389,6 +395,7 @@
       (card     [_this card-id]    (card     metadata card-id))
       (tables   [_this]            (tables   metadata database-id))
       (fields   [_this table-id]   (fields   metadata table-id))
+      (metrics  [_this table-id]   (metrics  metadata table-id))
 
       ;; for debugging: call [[clojure.datafy/datafy]] on one of these to parse all of our metadata and see the whole
       ;; thing at once.
diff --git a/src/metabase/lib/metadata.cljc b/src/metabase/lib/metadata.cljc
index a250f3331794d865043177173b12071fc3956dd9..8c894a96021b89cd562c9636ae27138618e3a3fa 100644
--- a/src/metabase/lib/metadata.cljc
+++ b/src/metabase/lib/metadata.cljc
@@ -117,19 +117,27 @@
    ;; probably missing `:lib/type` and probably using `:snake_case` keys.
    [:result-metadata {:optional true} [:maybe [:sequential :map]]]])
 
-(def ^:private SegmentMetadata
+(def SegmentMetadata
   "More or less the same as a [[metabase.models.segment]], but with kebab-case keys."
   [:map
    [:lib/type [:= :metadata/segment]]
    [:id       ::lib.schema.id/segment]
    [:name     ::lib.schema.common/non-blank-string]])
 
-(def ^:private MetricMetadata
-  "More or less the same as a [[metabase.models.metric]], but with kebab-case keys."
+(def MetricMetadata
+  "Malli schema for a legacy v1 [[metabase.models.metric]], but with kebab-case keys. A Metric defines an MBQL snippet
+  with an aggregation and optionally a filter clause. You can add a `:metric` reference to the `:aggregations` in an
+  MBQL stage, and the QP treats it like a macro and expands it to the underlying clauses --
+  see [[metabase.query-processor.middleware.expand-macros]]."
   [:map
-   [:lib/type [:= :metadata/metric]]
-   [:id       ::lib.schema.id/metric]
-   [:name     ::lib.schema.common/non-blank-string]])
+   [:lib/type   [:= :metadata/metric]]
+   [:id         ::lib.schema.id/metric]
+   [:name       ::lib.schema.common/non-blank-string]
+   [:table-id   ::lib.schema.id/table]
+   ;; the MBQL snippet defining this Metric; this may still be in legacy
+   ;; format. [[metabase.lib.metric/metric-definition]] handles conversion to pMBQL if needed.
+   [:definition :map]
+   [:description {:optional true} [:maybe ::lib.schema.common/non-blank-string]]])
 
 (def TableMetadata
   "Schema for metadata about a specific [[metabase.models.table]]. More or less the same as a [[metabase.models.table]],
@@ -155,7 +163,9 @@
 
 (def MetadataProvider
   "Schema for something that satisfies the [[lib.metadata.protocols/MetadataProvider]] protocol."
-  [:fn lib.metadata.protocols/metadata-provider?])
+  [:fn
+   {:error/message "Valid MetadataProvider"}
+   #'lib.metadata.protocols/metadata-provider?])
 
 (def MetadataProviderable
   "Something that can be used to get a MetadataProvider. Either a MetadataProvider, or a map with a MetadataProvider in
diff --git a/src/metabase/lib/metadata/cached_provider.cljc b/src/metabase/lib/metadata/cached_provider.cljc
index d44da7ae6680291c5dc352e7a4ef7684a2ff682f..2bf14cb41e17a0c96afcc8d18b1355fb1f9ad9df 100644
--- a/src/metabase/lib/metadata/cached_provider.cljc
+++ b/src/metabase/lib/metadata/cached_provider.cljc
@@ -55,12 +55,13 @@
   lib.metadata.protocols/MetadataProvider
   (database [_this]            (get-in-cache-or-fetch cache [:metadata/database]            #(lib.metadata.protocols/database metadata-provider)))
   (table    [_this table-id]   (get-in-cache-or-fetch cache [:metadata/table table-id]      #(lib.metadata.protocols/table    metadata-provider table-id)))
-  (field    [_this field-id]   (get-in-cache-or-fetch cache [:metadata/column field-id]      #(lib.metadata.protocols/field    metadata-provider field-id)))
+  (field    [_this field-id]   (get-in-cache-or-fetch cache [:metadata/column field-id]     #(lib.metadata.protocols/field    metadata-provider field-id)))
   (card     [_this card-id]    (get-in-cache-or-fetch cache [:metadata/card card-id]        #(lib.metadata.protocols/card     metadata-provider card-id)))
   (metric   [_this metric-id]  (get-in-cache-or-fetch cache [:metadata/metric metric-id]    #(lib.metadata.protocols/metric   metadata-provider metric-id)))
   (segment  [_this segment-id] (get-in-cache-or-fetch cache [:metadata/segment segment-id]  #(lib.metadata.protocols/segment  metadata-provider segment-id)))
   (tables   [_this]            (get-in-cache-or-fetch cache [::database-tables]             #(lib.metadata.protocols/tables   metadata-provider)))
   (fields   [_this table-id]   (get-in-cache-or-fetch cache [::table-fields table-id]       #(lib.metadata.protocols/fields   metadata-provider table-id)))
+  (metrics  [_this table-id]   (get-in-cache-or-fetch cache [::table-metrics table-id]      #(lib.metadata.protocols/metrics  metadata-provider table-id)))
 
   lib.metadata.protocols/CachedMetadataProvider
   (cached-database [_this]                           (get-in-cache    cache [:metadata/database]))
diff --git a/src/metabase/lib/metadata/calculation.cljc b/src/metabase/lib/metadata/calculation.cljc
index 250f7971722942a089433ee36516a5125887dfe0..fe676fa5e778e9f6a4fcede40c41d42e4ab0be6f 100644
--- a/src/metabase/lib/metadata/calculation.cljc
+++ b/src/metabase/lib/metadata/calculation.cljc
@@ -174,7 +174,12 @@
       ;; otherwise if `:base-type` is specified, we can return that.
       (:base-type options)
       ;; if none of the special cases are true, fall back to [[type-of-method]].
-      (type-of-method query stage-number x)))))
+      (let [calculated-type (type-of-method query stage-number x)]
+        ;; if calculated type is not a true type but a placeholder like `:metabase.lib.schema.expression/type.unknown`
+        ;; or a union of types then fall back to `:type/*`, an actual type.
+        (if (isa? calculated-type :type/*)
+          calculated-type
+          :type/*))))))
 
 (defmethod type-of-method :default
   [_query _stage-number expr]
@@ -358,7 +363,7 @@
 (defmethod display-info-method :metadata/table
   [query stage-number table]
   (merge (default-display-info query stage-number table)
-         {:is-source-table (= (lib.util/source-table query) (:id table))}))
+         {:is-source-table (= (lib.util/source-table-id query) (:id table))}))
 
 (def ColumnsWithUniqueAliases
   "Schema for column metadata that should be returned by [[visible-columns]]. This is mostly used
diff --git a/src/metabase/lib/metadata/jvm.clj b/src/metabase/lib/metadata/jvm.clj
index 1b0231b87dbc1e6aec0a3cea134e90c77704d7e4..745aa3fe0dce8e6fe30bd22d142924b21094f1ed 100644
--- a/src/metabase/lib/metadata/jvm.clj
+++ b/src/metabase/lib/metadata/jvm.clj
@@ -50,6 +50,11 @@
   (mapv #(instance->metadata % :metadata/column)
         (t2/select :model/Field :table_id table-id)))
 
+(defn- metrics [table-id]
+  (log/debugf "Fetching all Metrics for Table %d" table-id)
+  (mapv #(instance->metadata % :metadata/metric)
+        (t2/select :model/Metric :table_id table-id)))
+
 (p/deftype+ UncachedApplicationDatabaseMetadataProvider [database-id]
   lib.metadata.protocols/MetadataProvider
   (database [_this]
@@ -72,6 +77,9 @@
   (fields [_this table-id]
     (fields table-id))
 
+  (metrics [_this table-id]
+    (metrics table-id))
+
   lib.metadata.protocols/BulkMetadataProvider
   (bulk-metadata [_this metadata-type ids]
     (bulk-instances metadata-type ids))
diff --git a/src/metabase/lib/metadata/protocols.cljc b/src/metabase/lib/metadata/protocols.cljc
index 9df210d30a0a8ec6977668b775278086ee33c0c2..876fc0039915bfa2585229e537f86d00098bbb62 100644
--- a/src/metabase/lib/metadata/protocols.cljc
+++ b/src/metabase/lib/metadata/protocols.cljc
@@ -61,7 +61,11 @@
 
   (fields [metadata-provider table-id]
     "Return a sequence of Fields associated with a Table with the given `table-id`. Fields should satisfy
-  the [[metabase.lib.metadata/ColumnMetadata]] schema. If no such Table exists, this should error."))
+  the [[metabase.lib.metadata/ColumnMetadata]] schema. If no such Table exists, this should error.")
+
+  (metrics [metadata-provider table-id]
+    "Return a sequence of legacy v1 Metrics associated with a Table with the given `table-id`. Metrics should satisfy
+  the [[metabase.lib.metadata/MetricMetadata]] schema. If no such Table exists, this should error."))
 
 (defn metadata-provider?
   "Whether `x` is a valid [[MetadataProvider]]."
diff --git a/src/metabase/lib/metric.cljc b/src/metabase/lib/metric.cljc
index 89ce5c3e8f2f16e796d1f1000714e15062079da3..ebd788ae54c75ddb5806c9069b70bd4604668a4f 100644
--- a/src/metabase/lib/metric.cljc
+++ b/src/metabase/lib/metric.cljc
@@ -5,7 +5,11 @@
    [metabase.lib.convert :as lib.convert]
    [metabase.lib.metadata :as lib.metadata]
    [metabase.lib.metadata.calculation :as lib.metadata.calculation]
+   [metabase.lib.metadata.protocols :as lib.metadata.protocols]
+   [metabase.lib.ref :as lib.ref]
    [metabase.lib.schema :as lib.schema]
+   [metabase.lib.schema.expression :as lib.schema.expression]
+   [metabase.lib.schema.id :as lib.schema.id]
    [metabase.lib.util :as lib.util]
    [metabase.mbql.normalize :as mbql.normalize]
    [metabase.shared.util.i18n :as i18n]
@@ -16,7 +20,7 @@
     (lib.metadata/metric query metric-id)))
 
 (mu/defn ^:private metric-definition :- [:maybe ::lib.schema/stage.mbql]
-  [{:keys [definition], :as _metric-metadata}]
+  [{:keys [definition], :as _metric-metadata} :- lib.metadata/MetricMetadata]
   (when definition
     (if (:mbql/type definition)
       definition
@@ -28,6 +32,18 @@
         lib.convert/->pMBQL
         (lib.util/query-stage -1)))))
 
+(defmethod lib.ref/ref-method :metadata/metric
+  [{:keys [id], :as metric-metadata}]
+  (let [effective-type (or (:effective-type metric-metadata)
+                           (:base-type metric-metadata)
+                           (when-let [aggregation (first (:aggregation (metric-definition metric-metadata)))]
+                             (let [ag-effective-type (lib.schema.expression/type-of aggregation)]
+                               (when (isa? ag-effective-type :type/*)
+                                 ag-effective-type))))
+        options (cond-> {:lib/uuid (str (random-uuid))}
+                  effective-type (assoc :effective-type effective-type))]
+    [:metric options id]))
+
 (defmethod lib.metadata.calculation/type-of-method :metadata/metric
   [query stage-number metric-metadata]
   (or
@@ -41,19 +57,58 @@
         (lib.metadata.calculation/type-of query stage-number metric-metadata))
       :type/*))
 
+(defn- fallback-display-name []
+  (i18n/tru "[Unknown Metric]"))
+
 (defmethod lib.metadata.calculation/display-name-method :metadata/metric
   [_query _stage-number metric-metadata _style]
   (or ((some-fn :display-name :name) metric-metadata)
-      (i18n/tru "[Unknown Metric]")))
+      (fallback-display-name)))
 
 (defmethod lib.metadata.calculation/display-name-method :metric
   [query stage-number [_tag _opts metric-id-or-name] style]
   (or (when-let [metric-metadata (resolve-metric query metric-id-or-name)]
         (lib.metadata.calculation/display-name query stage-number metric-metadata style))
-      (i18n/tru "[Unknown Metric]")))
+      (fallback-display-name)))
+
+(mu/defn ^:private aggregating-by-metric? :- :boolean
+  "Whether a given stage of a query currently includes a `:metric` ref clause in its aggregations."
+  [query        :- ::lib.schema/query
+   stage-number :- :int
+   metric-id    :- ::lib.schema.id/metric]
+  (let [{aggregations :aggregation} (lib.util/query-stage query stage-number)]
+    (boolean
+     (some (fn [[tag :as clause]]
+             (when (= tag :metric)
+               (let [[_tag _opts id] clause]
+                 (= id metric-id))))
+           aggregations))))
+
+(defmethod lib.metadata.calculation/display-info-method :metadata/metric
+  [query stage-number {:keys [id description], :as metric-metadata}]
+  (merge
+   ((get-method lib.metadata.calculation/display-info-method :default) query stage-number metric-metadata)
+   {:description description}
+   (when (aggregating-by-metric? query stage-number id)
+     {:selected true})))
+
+(defmethod lib.metadata.calculation/display-info-method :metric
+  [query stage-number [_tag _opts metric-id-or-name]]
+  (if-let [metric-metadata (resolve-metric query metric-id-or-name)]
+    (lib.metadata.calculation/display-info query stage-number metric-metadata)
+    {:effective-type    :type/*
+     :display-name      (fallback-display-name)
+     :long-display-name (fallback-display-name)}))
 
 (defmethod lib.metadata.calculation/column-name-method :metric
   [query stage-number [_tag _opts metric-id-or-name]]
   (or (when-let [metric-metadata (resolve-metric query metric-id-or-name)]
         (lib.metadata.calculation/column-name query stage-number metric-metadata))
       "metric"))
+
+(mu/defn available-metrics :- [:maybe [:sequential {:min 1} lib.metadata/MetricMetadata]]
+  "Get a list of Metrics that you may consider using as aggregations for a query. Only Metrics that have the same
+  `table-id` as the `source-table` for this query will be suggested."
+  [query :- ::lib.schema/query]
+  (when-let [source-table-id (lib.util/source-table-id query)]
+    (not-empty (lib.metadata.protocols/metrics (lib.metadata/->metadata-provider query) source-table-id))))
diff --git a/src/metabase/lib/schema/expression/conditional.cljc b/src/metabase/lib/schema/expression/conditional.cljc
index 96c28aaff206026635f5db4a61b12cbdbeabb6a9..825e1388c6f6e82552bda38fe8dd9a9bc935d232 100644
--- a/src/metabase/lib/schema/expression/conditional.cljc
+++ b/src/metabase/lib/schema/expression/conditional.cljc
@@ -5,7 +5,8 @@
    [metabase.lib.schema.expression :as expression]
    [metabase.lib.schema.mbql-clause :as mbql-clause]
    [metabase.types :as types]
-   [metabase.util.malli.registry :as mr]))
+   [metabase.util.malli.registry :as mr]
+   ))
 
 ;;; the logic for calculating the return type of a `:case` or similar statement is not optimal nor perfect. But it
 ;;; should be ok for now and errors on the side of being permissive. See this Slack thread for more info:
@@ -18,6 +19,11 @@
     (nil? x)
     y
 
+    ;; if the type of either x or y is unknown, then the overall type of this has to be unknown as well.
+    (or (= x ::expression/type.unknown)
+        (= y ::expression/type.unknown))
+    ::expression/type.unknown
+
     ;; if both types are keywords return their most-specific ancestor.
     (and (keyword? x)
          (keyword? y))
@@ -55,25 +61,15 @@
   [:pred-expr-pairs [:sequential {:min 1} [:ref ::case-subclause]]]
   [:default [:? [:schema [:ref ::expression/expression]]]])
 
-(defn- allow-unkown-type
-  "A hack to allow case and coalesce expressions using fields only.
-  The real fix would pass in metadata so that the types of fields
-  can be determined."
-  [expr-type]
-  (if (= expr-type ::expression/type.unknown)
-    :type/*
-    expr-type))
-
 (defmethod expression/type-of-method :case
   [[_tag _opts pred-expr-pairs default]]
-  (-> (reduce
-       (fn [best-guess [_pred expr]]
-         (let [expr-type (expression/type-of expr)]
-           (best-return-type best-guess expr-type)))
-       (when (some? default)
-         (expression/type-of default))
-       pred-expr-pairs)
-      allow-unkown-type))
+  (reduce
+   (fn [best-guess [_pred expr]]
+     (let [expr-type (expression/type-of expr)]
+       (best-return-type best-guess expr-type)))
+   (when (some? default)
+     (expression/type-of default))
+   pred-expr-pairs))
 
 ;;; TODO -- add constraint that these types have to be compatible
 (mbql-clause/define-tuple-mbql-clause :coalesce
@@ -82,5 +78,4 @@
 
 (defmethod expression/type-of-method :coalesce
   [[_tag _opts expr null-value]]
-  (-> (best-return-type (expression/type-of expr) (expression/type-of null-value))
-      allow-unkown-type))
+  (best-return-type (expression/type-of expr) (expression/type-of null-value)))
diff --git a/src/metabase/lib/schema/mbql_clause.cljc b/src/metabase/lib/schema/mbql_clause.cljc
index a81ae0e04cfde5d8de9b3beb4916b42eff34110f..15ab19ab39f440244b7691a4d1ead1992b56c6c3 100644
--- a/src/metabase/lib/schema/mbql_clause.cljc
+++ b/src/metabase/lib/schema/mbql_clause.cljc
@@ -85,7 +85,7 @@
 
   ([tag         :- simple-keyword?
     _arrow      :- [:= :-]
-    return-type :- [:fn {:error/message "valid base type"} #(isa? % :type/*)]
+    return-type :- ::expression/base-type
     schema]
    (define-mbql-clause tag schema)
    (defmethod expression/type-of-method tag
diff --git a/src/metabase/lib/schema/ref.cljc b/src/metabase/lib/schema/ref.cljc
index 499a4078e7ab08a8d6f9b4560771fc1e2ef80cd3..34c4fb4bd9428518ca1b519832a476a60bdefda1 100644
--- a/src/metabase/lib/schema/ref.cljc
+++ b/src/metabase/lib/schema/ref.cljc
@@ -1,7 +1,9 @@
 (ns metabase.lib.schema.ref
   "Malli schema for a Field, aggregation, or expression reference (etc.)"
   (:require
+   [clojure.string :as str]
    [metabase.lib.dispatch :as lib.dispatch]
+   [metabase.lib.hierarchy :as lib.hierarchy]
    [metabase.lib.schema.common :as common]
    [metabase.lib.schema.expression :as expression]
    [metabase.lib.schema.id :as id]
@@ -55,6 +57,8 @@
     [:dispatch-type/integer ::field.id]
     [:dispatch-type/string ::field.literal]]])
 
+(lib.hierarchy/derive :field ::ref)
+
 (defmethod expression/type-of-method :field
   [[_tag opts _id-or-name]]
   (or ((some-fn :effective-type :base-type) opts)
@@ -68,6 +72,8 @@
   (or ((some-fn :effective-type :base-type) opts)
       ::expression/type.unknown))
 
+(lib.hierarchy/derive :expression ::ref)
+
 (mr/def ::aggregation-options
   [:merge
    ::common/options
@@ -86,10 +92,19 @@
   (or ((some-fn :effective-type :base-type) opts)
       ::expression/type.unknown))
 
+(lib.hierarchy/derive :aggregation ::ref)
+
+(mbql-clause/define-tuple-mbql-clause :metric :- ::expression/type.unknown
+  #_metric-id [:schema [:ref ::id/metric]])
+
+(lib.hierarchy/derive :metric ::ref)
+
 (mr/def ::ref
   [:and
    ::mbql-clause/clause
    [:fn
-    {:error/message ":field, :expression, :or :aggregation reference"}
+    {:error/fn (fn [_ _]
+                 (str "Valid reference, must be one of these clauses: "
+                      (str/join ", " (sort (descendants @lib.hierarchy/hierarchy ::ref)))))}
     (fn [[tag :as _clause]]
-      (#{:field :expression :aggregation} tag))]])
+      (lib.hierarchy/isa? tag ::ref))]])
diff --git a/src/metabase/lib/util.cljc b/src/metabase/lib/util.cljc
index c66d2c156945f4709ef0f10500e61fe7754ce128..ead20fa0eac2b5546aa6c56139ab89b5b8ca95d6 100644
--- a/src/metabase/lib/util.cljc
+++ b/src/metabase/lib/util.cljc
@@ -456,7 +456,7 @@
     (when-let [[_match card-id-str] (re-find #"^card__(\d+)$" table-id)]
       (parse-long card-id-str))))
 
-(mu/defn source-table :- [:maybe ::lib.schema.id/table]
+(mu/defn source-table-id :- [:maybe ::lib.schema.id/table]
   "If this query has a `:source-table` ID, return it."
   [query]
   (-> query :stages first :source-table))
diff --git a/src/metabase/query_processor/store.clj b/src/metabase/query_processor/store.clj
index 343d82c7b96ecc4bf88dbddc27b169ba5f911453..f4053c52eaefa65c3bae24de697773b9dcdf1a4d 100644
--- a/src/metabase/query_processor/store.clj
+++ b/src/metabase/query_processor/store.clj
@@ -344,6 +344,7 @@
     (segment [_this _segment-id] nil)
     (tables [_metadata-provider] nil)
     (fields [_metadata-provider _table-id] nil)
+    (metrics [_metadata-provider _table-id] nil)
 
     pretty/PrettyPrintable
     (pretty [_this]
diff --git a/src/metabase/query_processor/store/metadata_provider.clj b/src/metabase/query_processor/store/metadata_provider.clj
deleted file mode 100644
index edfff889fea7bd71f90a143b8994ba1f29d3eeb1..0000000000000000000000000000000000000000
--- a/src/metabase/query_processor/store/metadata_provider.clj
+++ /dev/null
@@ -1,39 +0,0 @@
-(ns metabase.query-processor.store.metadata-provider
-  (:require
-   [metabase.lib.metadata.composed-provider
-    :as lib.metadata.composed-provider]
-   [metabase.lib.metadata.jvm :as lib.metadata.jvm]
-   [metabase.lib.metadata.protocols :as lib.metadata.protocols]
-   [metabase.query-processor.store :as qp.store]
-   [metabase.util :as u]
-   [pretty.core :as pretty]))
-
-(defn- base-metadata-provider []
-  (reify
-    lib.metadata.protocols/MetadataProvider
-    (database [_this]
-      (some-> (qp.store/database) (update-keys u/->kebab-case-en) (assoc :lib/type :metadata/database)))
-
-    (table [_this table-id]
-      (some-> (qp.store/table table-id) (update-keys u/->kebab-case-en) (assoc :lib/type :metadata/table)))
-
-    (field [_this field-id]
-      (some-> (qp.store/field field-id) (update-keys u/->kebab-case-en) (assoc :lib/type :metadata/column)))
-
-    (card [_this _card-id] nil)
-    (metric [_this _metric-id] nil)
-    (segment [_this _segment-id] nil)
-    (tables [_metadata-provider] nil)
-    (fields [_metadata-provider _table-id] nil)
-
-    pretty/PrettyPrintable
-    (pretty [_this]
-      `metadata-provider)))
-
-(defn metadata-provider
-  "Create a new MLv2 metadata provider that uses the QP store."
-  []
-  (qp.store/cached ::metadata-provider
-    (lib.metadata.composed-provider/composed-metadata-provider
-     (base-metadata-provider)
-     (lib.metadata.jvm/application-database-metadata-provider (:id (qp.store/database))))))
diff --git a/test/metabase/api/geojson_test.clj b/test/metabase/api/geojson_test.clj
index b74a0209ec1c6198a7e5cff3eec09760fe6bd121..eb53b2a3a9640283cb4ab0a727dd647461520357 100644
--- a/test/metabase/api/geojson_test.clj
+++ b/test/metabase/api/geojson_test.clj
@@ -35,11 +35,10 @@
                   :region_key  nil
                   :region_name nil}})
 
-(deftest geojson-schema-test
-  (is (= true
-         (boolean (s/validate @#'api.geojson/CustomGeoJSON test-custom-geojson)))))
+(deftest ^:parallel geojson-schema-test
+  (is (s/validate @#'api.geojson/CustomGeoJSON test-custom-geojson)))
 
-(deftest validate-geojson-test
+(deftest ^:parallel validate-geojson-test
   (testing "It validates URLs and files appropriately"
     (let [examples {;; Internal metadata for GCP
                     "metadata.google.internal"                 false
@@ -135,6 +134,25 @@
                                          {:value resource-geojson})
                    (mt/user-http-request :crowberto :get 200 "setting/custom-geojson")))))))))
 
+(deftest ^:parallel url-proxy-endpoint-test
+  (testing "GET /api/geojson"
+    (testing "test the endpoint that fetches JSON files given a URL"
+      (is (= {:type        "Point"
+              :coordinates [37.77986 -122.429]}
+             (mt/user-http-request :crowberto :get 200 "geojson" :url test-geojson-url))))
+    (testing "error is returned if URL connection fails"
+      (is (= "GeoJSON URL failed to load"
+             (mt/user-http-request :crowberto :get 400 "geojson"
+                                   :url test-broken-geojson-url))))
+    (testing "error is returned if URL is invalid"
+      (is (= (str "Invalid GeoJSON file location: must either start with http:// or https:// or be a relative path to "
+                  "a file on the classpath. URLs referring to hosts that supply internal hosting metadata are "
+                  "prohibited.")
+             (mt/user-http-request :crowberto :get 400 "geojson" :url "file://tmp"))))
+    (testing "cannot be called by non-admins"
+      (is (= "You don't have permissions to do that."
+             (mt/user-http-request :rasta :get 403 "geojson" :url test-geojson-url))))))
+
 (defprotocol GeoJsonTestServer
   (-port [_]))
 
@@ -155,17 +173,9 @@
       GeoJsonTestServer
       (-port [_] (.. server getURI getPort)))))
 
-(deftest url-proxy-endpoint-test
-  (testing "GET /api/geojson"
-    (testing "test the endpoint that fetches JSON files given a URL"
-      (is (= {:type        "Point"
-              :coordinates [37.77986 -122.429]}
-             (mt/user-http-request :crowberto :get 200 "geojson" :url test-geojson-url))))
-    (testing "error is returned if URL connection fails"
-      (is (= "GeoJSON URL failed to load"
-             (mt/user-http-request :crowberto :get 400 "geojson"
-                                   :url test-broken-geojson-url))))
-    (testing "error is returned if URL server never responds (#28752)"
+(deftest url-proxy-endpoint-non-responding-server-test
+  (testing "error is returned if URL server never responds (#28752)"
+    (with-redefs [api.geojson/connection-timeout-ms 200]
       ;; use a webserver which accepts a connection and never responds. The geojson endpoint opens a reader to the url
       ;; and responds with it. And if there are never any bytes going across, the whole thing just sits there. Our
       ;; test flakes after 45 seconds with `mt/user-http-request` times out. And presumably other clients have similar
@@ -175,15 +185,7 @@
           (testing "error is returned if URL connection fails"
             (is (= "GeoJSON URL failed to load"
                    (mt/user-http-request :crowberto :get 400 "geojson"
-                                         :url never-responds-url)))))))
-    (testing "error is returned if URL is invalid"
-      (is (= (str "Invalid GeoJSON file location: must either start with http:// or https:// or be a relative path to "
-                  "a file on the classpath. URLs referring to hosts that supply internal hosting metadata are "
-                  "prohibited.")
-             (mt/user-http-request :crowberto :get 400 "geojson" :url "file://tmp"))))
-    (testing "cannot be called by non-admins"
-      (is (= "You don't have permissions to do that."
-             (mt/user-http-request :rasta :get 403 "geojson" :url test-geojson-url))))))
+                                         :url never-responds-url)))))))))
 
 (deftest key-proxy-endpoint-test
   (testing "GET /api/geojson/:key"
diff --git a/test/metabase/lib/convert_test.cljc b/test/metabase/lib/convert_test.cljc
index a47da2d7d341c6402489cfdb97d91299147c5afd..1d0e013df02dae37e0c917d08e19986cc1b7b211 100644
--- a/test/metabase/lib/convert_test.cljc
+++ b/test/metabase/lib/convert_test.cljc
@@ -1,11 +1,12 @@
 (ns metabase.lib.convert-test
   (:require
-   #?@(:cljs ([metabase.test-runner.assert-exprs.approximately-equal]))
    [clojure.test :refer [are deftest is testing]]
    [malli.core :as mc]
    [metabase.lib.convert :as lib.convert]
    [metabase.lib.core :as lib]
-   [metabase.lib.test-metadata :as meta]))
+   [metabase.lib.test-metadata :as meta]
+   [metabase.util :as u]
+   #?@(:cljs ([metabase.test-runner.assert-exprs.approximately-equal]))))
 
 #?(:cljs (comment metabase.test-runner.assert-exprs.approximately-equal/keep-me))
 
@@ -227,9 +228,16 @@
     (is (= original
            (lib.convert/->legacy-MBQL (lib.convert/->pMBQL original))))))
 
+(defn- test-round-trip [query]
+  (testing (str "original =\n" (u/pprint-to-str query))
+    (let [converted (lib.convert/->pMBQL query)]
+      (testing (str "\npMBQL =\n" (u/pprint-to-str converted))
+        (is (= query
+               (lib.convert/->legacy-MBQL converted)))))))
+
 (deftest ^:parallel round-trip-test
   ;; Miscellaneous queries that have caused test failures in the past, captured here for quick feedback.
-  (are [query] (= query (-> query lib.convert/->pMBQL lib.convert/->legacy-MBQL))
+  (are [query] (test-round-trip query)
     ;; :aggregation-options on a non-aggregate expression with an inner aggregate.
     {:database 194
      :query {:aggregation [[:aggregation-options
@@ -375,6 +383,22 @@
      :type     :query
      :query    {:source-table "card__1"}}))
 
+(deftest ^:parallel round-trip-aggregation-with-case-test
+  (test-round-trip
+   {:database 2762
+    :type     :query
+    :query    {:aggregation  [[:sum [:case [[[:< [:field 139657 nil] 2] [:field 139657 nil]]] {:default 0}]]]
+               :breakout     [[:field 139658 nil]]
+               :limit        4
+               :source-table 33674}}))
+
+(deftest ^:parallel round-trip-aggregation-with-metric-test
+  (test-round-trip
+   {:database 1
+    :query    {:aggregation  [[:+ [:metric 82] 1]]
+               :source-table 1}
+    :type     :query}))
+
 (deftest ^:parallel round-trip-options-test
   (testing "Round-tripping (p)MBQL caluses with options (#30280)"
     (testing "starting with pMBQL"
diff --git a/test/metabase/lib/metric_test.cljc b/test/metabase/lib/metric_test.cljc
index 086a43c6331a34e4b3d0ad8114cd41b094464523..aaa06b343b44dc1e0d48ca452ac74bfc180bf2f4 100644
--- a/test/metabase/lib/metric_test.cljc
+++ b/test/metabase/lib/metric_test.cljc
@@ -1,7 +1,8 @@
 (ns metabase.lib.metric-test
   (:require
-   [clojure.test :refer [deftest is]]
+   [clojure.test :refer [are deftest is testing]]
    [metabase.lib.core :as lib]
+   [metabase.lib.metadata :as lib.metadata]
    [metabase.lib.metadata.calculation :as lib.metadata.calculation]
    [metabase.lib.test-metadata :as meta]
    [metabase.lib.test-util :as lib.tu]
@@ -9,27 +10,130 @@
 
 #?(:cljs (comment metabase.test-runner.assert-exprs.approximately-equal/keep-me))
 
-(defn- mock-metadata-provider []
+(def ^:private metric-id 100)
+
+(def ^:private metric-definition
+  {:source-table (meta/id :venues)
+   :aggregation  [[:sum [:field (meta/id :venues :price) nil]]]
+   :filter       [:= [:field (meta/id :venues :price) nil] 4]})
+
+(def ^:private metadata-provider
   (lib.tu/mock-metadata-provider
    {:database meta/metadata
     :tables   [(meta/table-metadata :venues)]
     :fields   [(meta/field-metadata :venues :price)]
-    :metrics  [{:id         100
-                :name       "My Metric"
-                :definition {:source-table (meta/id :venues)
-                             :aggregation [[:sum [:field (meta/id :venues :price) nil]]]
-                             :filter      [:= [:field (meta/id :venues :price) nil] 4]}}]}))
-
-(deftest ^:parallel metric-display-name-test
-  (let [metadata (mock-metadata-provider)
-        query    (-> (lib/query metadata (meta/table-metadata :venues))
-                     (lib/aggregate [:metric {:lib/uuid (str (random-uuid))} 100]))]
-    (is (= "Venues, My Metric"
-           (lib.metadata.calculation/suggested-name query)))))
-
-(deftest ^:parallel metric-type-of-test
-  (let [metadata (mock-metadata-provider)
-        query    (-> (lib/query metadata (meta/table-metadata :venues))
-                     (lib/aggregate [:metric {:lib/uuid (str (random-uuid))} 100]))]
-    (is (= :type/Integer
-           (lib.metadata.calculation/type-of query [:metric {:lib/uuid (str (random-uuid))} 100])))))
+    :metrics  [{:id          metric-id
+                :name        "Sum of Cans"
+                :table-id    (meta/id :venues)
+                :definition  metric-definition
+                :description "Number of toucans plus number of pelicans"}]}))
+
+(def ^:private metric-clause
+  [:metric {:lib/uuid (str (random-uuid))} metric-id])
+
+(def ^:private query-with-metric
+  (-> (lib/query metadata-provider (meta/table-metadata :venues))
+      (lib/aggregate metric-clause)))
+
+(def ^:private metric-metadata
+  (lib.metadata/metric query-with-metric metric-id))
+
+(deftest ^:parallel query-suggested-name-test
+  (is (= "Venues, Sum of Cans"
+         (lib.metadata.calculation/suggested-name query-with-metric))))
+
+(deftest ^:parallel display-name-test
+  (doseq [metric [metric-clause
+                  metric-metadata]
+          style [nil
+                 :default
+                 :long]]
+    (testing (str "metric = " (pr-str metric) "\n"
+                  "style = " (pr-str style))
+      (is (= "Sum of Cans"
+             (if style
+               (lib.metadata.calculation/display-name query-with-metric -1 metric style)
+               (lib.metadata.calculation/display-name query-with-metric metric)))))))
+
+(deftest ^:parallel unknown-display-name-test
+  (let [metric [:metric {} 1]]
+    (doseq [style [nil
+                   :default
+                   :long]]
+      (testing (str "style = " (pr-str style))
+        (is (= "[Unknown Metric]"
+               (if style
+                 (lib.metadata.calculation/display-name query-with-metric -1 metric style)
+                 (lib.metadata.calculation/display-name query-with-metric metric))))))))
+
+(deftest ^:parallel display-info-test
+  (are [metric] (=? {:name              "sum_of_cans"
+                     :display-name      "Sum of Cans"
+                     :long-display-name "Sum of Cans"
+                     :effective-type    :type/Integer
+                     :description       "Number of toucans plus number of pelicans"
+                     :selected          true}
+                    (lib.metadata.calculation/display-info query-with-metric metric))
+    metric-clause
+    metric-metadata))
+
+(deftest ^:parallel display-info-unselected-metric-test
+  (testing "Include `:selected false` in display info for Metrics not in aggregations"
+    (are [metric] (not (:selected (lib.metadata.calculation/display-info lib.tu/venues-query metric)))
+      metric-clause
+      metric-metadata)))
+
+(deftest ^:parallel unknown-display-info-test
+  (is (=? {:effective-type    :type/*
+           :display-name      "[Unknown Metric]"
+           :long-display-name "[Unknown Metric]"}
+          (lib.metadata.calculation/display-info query-with-metric [:metric {} 1]))))
+
+(deftest ^:parallel type-of-test
+  (are [metric] (= :type/Integer
+                   (lib.metadata.calculation/type-of query-with-metric metric))
+    metric-clause
+    metric-metadata))
+
+(deftest ^:parallel unknown-type-of-test
+  (is (= :type/*
+         (lib.metadata.calculation/type-of query-with-metric [:metric {} 1]))))
+
+(deftest ^:parallel available-metrics-test
+  (testing "Should return Metrics with the same Table ID as query's `:source-table`"
+    (is (=? [{:lib/type    :metadata/metric
+              :id          metric-id
+              :name        "Sum of Cans"
+              :table-id    (meta/id :venues)
+              :definition  metric-definition
+              :description "Number of toucans plus number of pelicans"}]
+            (lib/available-metrics (lib/query metadata-provider (meta/table-metadata :venues))))))
+  (testing "query with different Table -- don't return Metrics"
+    (is (nil? (lib/available-metrics (lib/query metadata-provider (meta/table-metadata :orders)))))))
+
+(deftest ^:parallel aggregate-with-metric-test
+  (testing "Should be able to pass a Metric metadata to `aggregate`"
+    (let [query   (lib/query metadata-provider (meta/table-metadata :venues))
+          metrics (lib/available-metrics query)]
+      (is (= 1
+             (count metrics)))
+      ;; test with both `:metadata/metric` and with a `:metric` ref clause
+      (doseq [metric [(first metrics)
+                      [:metric {:lib/uuid (str (random-uuid))} 100]]]
+        (testing (pr-str (list 'lib/aggregate 'query metric))
+          (let [query' (lib/aggregate query metric)]
+            (is (=? {:lib/type :mbql/query
+                     :stages   [{:lib/type     :mbql.stage/mbql
+                                 :source-table (meta/id :venues)
+                                 :aggregation  [[:metric {:lib/uuid string?} 100]]}]}
+                    query'))
+            (is (=? [[:metric {:lib/uuid string?} 100]]
+                    (lib/aggregations query')))
+            (is (=? [{:name              "sum_of_cans"
+                      :display-name      "Sum of Cans"
+                      :long-display-name "Sum of Cans"
+                      :effective-type    :type/Integer
+                      :description       "Number of toucans plus number of pelicans"
+                      :selected          true}]
+                    (map (partial lib/display-info query')
+                         (lib/aggregations query'))))))))))
diff --git a/test/metabase/lib/schema/expression/arithmetic_test.cljc b/test/metabase/lib/schema/expression/arithmetic_test.cljc
index 1900214092db84d9631d439afa87a562bbc0fe4f..bc8998e7fa956ba568fed325590b790fa0a8da78 100644
--- a/test/metabase/lib/schema/expression/arithmetic_test.cljc
+++ b/test/metabase/lib/schema/expression/arithmetic_test.cljc
@@ -4,6 +4,7 @@
    [malli.core :as mc]
    [malli.error :as me]
    [metabase.lib.schema]
+   [metabase.lib.schema.aggregation :as aggregation]
    [metabase.lib.schema.expression :as expression]
    [metabase.lib.test-metadata :as meta]))
 
@@ -151,3 +152,14 @@
               {:lib/uuid "00000000-0000-0000-0000-000000000000"}
               [:interval {:lib/uuid "00000000-0000-0000-0000-000000000002"} 3 :minute]
               [:field {:base-type :type/Date, :lib/uuid "00000000-0000-0000-0000-000000000001"} 1]]))))))
+
+
+(deftest ^:parallel metric-test
+  (are [schema] (not (me/humanize (mc/explain schema
+                                              [:+
+                                               {:lib/uuid "00000000-0000-0000-0000-000000000000"}
+                                               [:metric {:lib/uuid "00000000-0000-0000-0000-000000000000"} 1]
+                                               2])))
+    :mbql.clause/+
+    ::expression/number
+    ::aggregation/aggregation))
diff --git a/test/metabase/lib/schema/expression/conditional_test.cljc b/test/metabase/lib/schema/expression/conditional_test.cljc
index 5f83adf155948ce41d3aa4ddb42d9fb9a07a4182..15cbea5c8e867dd64ec297f91c6666eadace40cc 100644
--- a/test/metabase/lib/schema/expression/conditional_test.cljc
+++ b/test/metabase/lib/schema/expression/conditional_test.cljc
@@ -4,7 +4,16 @@
    [malli.core :as mc]
    [metabase.lib.schema :as lib.schema]
    [metabase.lib.schema.expression :as expression]
-   [metabase.lib.test-metadata :as meta]))
+   [metabase.lib.schema.expression.conditional :as expression.conditional]
+   [metabase.lib.test-metadata :as meta]
+   [metabase.test-runner.assert-exprs.malli-equals]
+   [metabase.util :as u]))
+
+(comment metabase.test-runner.assert-exprs.malli-equals/keep-me)
+
+(deftest ^:parallel best-return-type-test
+  (is (= ::expression/type.unknown
+         (#'expression.conditional/best-return-type :type/Integer ::expression/type.unknown))))
 
 (defn- case-expr [& args]
   (let [clause [:case
@@ -74,24 +83,48 @@
 (deftest ^:parallel case-type-of-with-fields-only-test
   ;; Ideally expression/type-of should have enough information to determine the types of fields.
   (testing "The type of a case expression can be determined even if it consists of fields only."
-    (is (= :type/*
-           (expression/type-of
-            [:case
-             {:lib/uuid "8c6e099e-b856-4aeb-a8f6-2266b5d3d1e3"}
-             [[[:>
-                {:lib/uuid "9c4cc3b0-f3c7-4d34-ab53-640ba6e911e5"}
-                [:field {:lib/uuid "435b08c8-9404-41a5-8c5a-00b415f14da6"} 25]
-                0]
-               [:field {:lib/uuid "1c93ba8b-6a39-4ef2-a9e6-e3bcff042800"} 32]]]
-             [:field
-              {:source-field 29
-               :lib/uuid "a5ab7f91-9826-40a7-9499-4a1a0184a450"}
-              23]])))))
+    (doseq [[message expr] {"no default"
+                            [:case
+                             {:lib/uuid "8c6e099e-b856-4aeb-a8f6-2266b5d3d1e3"}
+                             [[[:>
+                                {:lib/uuid "9c4cc3b0-f3c7-4d34-ab53-640ba6e911e5"}
+                                [:field {:lib/uuid "435b08c8-9404-41a5-8c5a-00b415f14da6"} 25]
+                                0]
+                               [:field {:lib/uuid "1c93ba8b-6a39-4ef2-a9e6-e3bcff042800"} 32]]]]
+
+                            "with default :field"
+                            [:case
+                             {:lib/uuid "8c6e099e-b856-4aeb-a8f6-2266b5d3d1e3"}
+                             [[[:>
+                                {:lib/uuid "9c4cc3b0-f3c7-4d34-ab53-640ba6e911e5"}
+                                [:field {:lib/uuid "435b08c8-9404-41a5-8c5a-00b415f14da6"} 25]
+                                0]
+                               [:field {:lib/uuid "1c93ba8b-6a39-4ef2-a9e6-e3bcff042800"} 32]]]
+                             [:field
+                              {:source-field 29
+                               :lib/uuid     "a5ab7f91-9826-40a7-9499-4a1a0184a450"}
+                              23]]
+
+                            "with default integer literal"
+                            [:case
+                             {:lib/uuid "66101767-c429-4499-b1b3-512e58267ea4"}
+                             [[[:<
+                                {:lib/uuid "580d9de8-8ade-4d64-a512-1e43a31fe869"}
+                                [:field {:lib/uuid "347d5337-5da8-47ff-bc05-b11154e8d19c"} 139657]
+                                2]
+                               [:field {:lib/uuid "a9f83548-d590-4dec-b7dd-ad2bbd0bbe9d"} 139657]]]
+                             0]}]
+      (testing (str message \newline (u/pprint-to-str expr))
+        (is (malli= :mbql.clause/case expr))
+        (is (= ::expression/type.unknown
+               (expression/type-of expr)))
+        (testing "type.unknown expressions should be allowed to be considered numeric expressions"
+          (is (malli= ::expression/number expr)))))))
 
 (deftest ^:parallel coalasce-type-of-with-fields-only-test
   ;; Ideally expression/type-of should have enough information to determine the types of fields.
   (testing "The type of a case expression can be determined even if it consists of fields only."
-    (is (= :type/*
+    (is (= ::expression/type.unknown
            (expression/type-of
             [:coalesce
              {:lib/uuid "8c6e099e-b856-4aeb-a8f6-2266b5d3d1e3"}
diff --git a/test/metabase/lib/stage_test.cljc b/test/metabase/lib/stage_test.cljc
index 39f810952527a88324b552a22e539b473432251a..299bb892b0f1a4d074a948ece8f5f2f5cd9e191d 100644
--- a/test/metabase/lib/stage_test.cljc
+++ b/test/metabase/lib/stage_test.cljc
@@ -33,13 +33,17 @@
     (testing "expressions in aggregations"
       (let [query (lib.tu/venues-query-with-last-stage
                    {:aggregation [[:*
-                                   {}
+                                   {:lib/uuid (str (random-uuid))}
                                    0.8
-                                   [:avg {} (lib.tu/field-clause :venues :price)]]
+                                   [:avg
+                                    {:lib/uuid (str (random-uuid))}
+                                    (lib.tu/field-clause :venues :price)]]
                                   [:*
-                                   {}
+                                   {:lib/uuid (str (random-uuid))}
                                    0.8
-                                   [:avg {} (lib.tu/field-clause :venues :price)]]]})]
+                                   [:avg
+                                    {:lib/uuid (str (random-uuid))}
+                                    (lib.tu/field-clause :venues :price)]]]})]
         (is (=? [{:base-type                :type/Float
                   :name                     "expression"
                   :display-name             "0.8 × Average of Price"
diff --git a/test/metabase/lib/test_metadata/graph_provider.cljc b/test/metabase/lib/test_metadata/graph_provider.cljc
index 21685d3cc637ccbe438944cdc6ce220a13ad3662..efe4288c36b00d02aaeeafb742095d0364a470e1 100644
--- a/test/metabase/lib/test_metadata/graph_provider.cljc
+++ b/test/metabase/lib/test_metadata/graph_provider.cljc
@@ -7,11 +7,12 @@
 (defn- graph-database [metadata-graph]
   (dissoc metadata-graph :tables))
 
+(defn- find-table [metadata-graph table-id]
+  (m/find-first #(= (:id %) table-id)
+                (:tables metadata-graph)))
+
 (defn- graph-table [metadata-graph table-id]
-  (some (fn [table-metadata]
-          (when (= (:id table-metadata) table-id)
-            (dissoc table-metadata :fields :metrics :segments)))
-        (:tables metadata-graph)))
+  (dissoc (find-table metadata-graph table-id) :fields :metrics :segments))
 
 (defn- graph-field [metadata-graph field-id]
   (some (fn [table-metadata]
@@ -40,10 +41,10 @@
     (dissoc table-metadata :fields :metrics :segments)))
 
 (defn- graph-fields [metadata-graph table-id]
-  (some (fn [table-metadata]
-          (when (= (:id table-metadata) table-id)
-            (:fields table-metadata)))
-        (:tables metadata-graph)))
+  (:fields (find-table metadata-graph table-id)))
+
+(defn- graph-metrics [metadata-graph table-id]
+  (:metrics (find-table metadata-graph table-id)))
 
 (deftype ^{:doc "A simple implementation of [[MetadataProvider]] that returns data from a complete graph
   e.g. the response provided by `GET /api/database/:id/metadata`."} SimpleGraphMetadataProvider [metadata-graph]
@@ -56,6 +57,7 @@
   (card     [_this card-id]    (graph-card     metadata-graph card-id))
   (tables   [_this]            (graph-tables   metadata-graph))
   (fields   [_this table-id]   (graph-fields   metadata-graph table-id))
+  (metrics  [_this table-id]   (graph-metrics  metadata-graph table-id))
 
   clojure.core.protocols/Datafiable
   (datafy [_this]
diff --git a/test/metabase/lib/test_util.cljc b/test/metabase/lib/test_util.cljc
index d35bc62739ed3168c1f13cc8c347d2d59b4a4616..202af1ab3519d9fc73ef158513ae453c4ab932d9 100644
--- a/test/metabase/lib/test_util.cljc
+++ b/test/metabase/lib/test_util.cljc
@@ -6,11 +6,13 @@
    [malli.core :as mc]
    [medley.core :as m]
    [metabase.lib.core :as lib]
+   [metabase.lib.metadata :as lib.metadata]
    [metabase.lib.metadata.composed-provider
     :as lib.metadata.composed-provider]
    [metabase.lib.metadata.protocols :as metadata.protocols]
    [metabase.lib.schema :as lib.schema]
    [metabase.lib.test-metadata :as meta]
+   [metabase.util.malli :as mu]
    #?@(:cljs ([metabase.test-runner.assert-exprs.approximately-equal]))))
 
 #?(:cljs
@@ -35,7 +37,24 @@
            options)
     (meta/id table field)]))
 
-(defn mock-metadata-provider
+(defn- with-optional-lib-type
+  "Create a version of `schema` where `:lib/type` is optional rather than required."
+  [schema lib-type]
+  [:merge
+   schema
+   [:map
+    [:lib/type {:optional true} [:= lib-type]]]])
+
+(def ^:private MockMetadata
+  [:map
+   [:database {:optional true} [:maybe (with-optional-lib-type lib.metadata/DatabaseMetadata :metadata/database)]]
+   [:tables   {:optional true} [:maybe [:sequential (with-optional-lib-type lib.metadata/TableMetadata   :metadata/table)]]]
+   [:fields   {:optional true} [:maybe [:sequential (with-optional-lib-type lib.metadata/ColumnMetadata  :metadata/column)]]]
+   [:cards    {:optional true} [:maybe [:sequential (with-optional-lib-type lib.metadata/CardMetadata    :metadata/card)]]]
+   [:metrics  {:optional true} [:maybe [:sequential (with-optional-lib-type lib.metadata/MetricMetadata  :metadata/metric)]]]
+   [:segments {:optional true} [:maybe [:sequential (with-optional-lib-type lib.metadata/SegmentMetadata :metadata/segment)]]]])
+
+(mu/defn mock-metadata-provider :- lib.metadata/MetadataProvider
   "Create a mock metadata provider to facilitate writing tests. All keys except `:database` should be a sequence of maps
   e.g.
 
@@ -44,7 +63,7 @@
   Normally you can probably get away with using [[metabase.lib.test-metadata/metadata-provider]] instead of using
   this; but this is available for situations when you need to test something not covered by the default test metadata,
   e.g. nested Fields."
-  [{:keys [database tables fields cards metrics segments] :as m}]
+  [{:keys [database tables fields cards metrics segments] :as m} :- MockMetadata]
   (reify
     metadata.protocols/MetadataProvider
     (database [_this]            (some-> database
@@ -65,8 +84,11 @@
                                    (-> (assoc table :lib/type :metadata/table)
                                        (dissoc :fields))))
     (fields   [_this table-id]   (for [field fields
-                                       :when (= (:table_id field) table-id)]
+                                       :when (= (:table-id field) table-id)]
                                    (assoc field :lib/type :metadata/column)))
+    (metrics  [_this table-id]   (for [metric metrics
+                                       :when (= (:table-id metric) table-id)]
+                                   (assoc metric :lib/type :metadata/metric)))
 
     clojure.core.protocols/Datafiable
     (datafy [_this]
diff --git a/test/metabase/lib/util_test.cljc b/test/metabase/lib/util_test.cljc
index d5dbfb21433bf75269286d4c1e605fdc5b412a9b..98030314edf69a04fba0ca6591d42b8633a6c464 100644
--- a/test/metabase/lib/util_test.cljc
+++ b/test/metabase/lib/util_test.cljc
@@ -137,7 +137,7 @@
   (is (=? {:database 1
            :stages   [{:lib/type     :mbql.stage/mbql
                        :source-table 1
-                       :aggregation  [[:count]]}]}
+                       :aggregation  [[:count {:lib/uuid "00000000-0000-0000-0000-000000000000"}]]}]}
           (lib.util/update-query-stage {:database 1
                                         :type     :query
                                         :query    {:source-table 1}}
@@ -145,7 +145,7 @@
                                        update
                                        :aggregation
                                        conj
-                                       [:count])))
+                                       [:count {:lib/uuid "00000000-0000-0000-0000-000000000000"}])))
   (are [stage expected] (=? expected
                             (lib.util/update-query-stage {:database 1
                                                           :type     :query
@@ -154,22 +154,22 @@
                                                          update
                                                          :aggregation
                                                          conj
-                                                         [:count]))
+                                                         [:count {:lib/uuid "00000000-0000-0000-0000-000000000000"}]))
     0 {:database 1
        :stages   [{:lib/type     :mbql.stage/mbql
                    :source-table 1
-                   :aggregation  [[:count]]}
+                   :aggregation  [[:count {:lib/uuid "00000000-0000-0000-0000-000000000000"}]]}
                   {:lib/type :mbql.stage/mbql}]}
     1 {:database 1
        :stages   [{:lib/type     :mbql.stage/mbql
                    :source-table 1}
                   {:lib/type    :mbql.stage/mbql
-                   :aggregation [[:count]]}]}
+                   :aggregation [[:count {:lib/uuid "00000000-0000-0000-0000-000000000000"}]]}]}
     -1 {:database 1
         :stages   [{:lib/type     :mbql.stage/mbql
                     :source-table 1}
                    {:lib/type    :mbql.stage/mbql
-                    :aggregation [[:count]]}]})
+                    :aggregation [[:count {:lib/uuid "00000000-0000-0000-0000-000000000000"}]]}]})
   (testing "out of bounds"
     (is (thrown-with-msg?
          #?(:clj Throwable :cljs js/Error)
@@ -181,7 +181,7 @@
                                       update
                                       :aggregation
                                       conj
-                                      [:count])))))
+                                      [:count {:lib/uuid "00000000-0000-0000-0000-000000000000"}])))))
 
 (deftest ^:parallel ensure-mbql-final-stage-test
   (is (=? {:database 1
diff --git a/test/metabase/query_processor_test/test_mlv2.clj b/test/metabase/query_processor_test/test_mlv2.clj
index 06c00133d7eb27c33845a9fa8d9c1cb105e7423b..0fcca31fa8516946cc95c1bcfdc3a645220ce75d 100644
--- a/test/metabase/query_processor_test/test_mlv2.clj
+++ b/test/metabase/query_processor_test/test_mlv2.clj
@@ -6,9 +6,8 @@
    [metabase.lib.convert :as lib.convert]
    [metabase.lib.core :as lib]
    [metabase.lib.metadata.calculation :as lib.metadata.calculation]
-   [metabase.lib.metadata.jvm :as lib.metadata.jvm]
    [metabase.lib.schema :as lib.schema]
-   [metabase.mbql.util :as mbql.u]
+   [metabase.query-processor.store :as qp.store]
    [metabase.util :as u]))
 
 (set! *warn-on-reflection* true)
@@ -29,18 +28,9 @@
   place, [[metabase.models.query.permissions-test/invalid-queries-test]]."
   false)
 
-(defn- skip-metadata-calculation-tests? [legacy-query]
-   ;; #29907: wrong column name for joined columns in `:breakout`
-   (mbql.u/match-one legacy-query
-     {:breakout breakouts}
-     (mbql.u/match-one breakouts
-       [:field _id-or-name {:join-alias _join-alias}]
-       "#29907")))
-
 (defn- test-mlv2-metadata [original-query _qp-metadata]
   {:pre [(map? original-query)]}
-  (when-not (or *skip-conversion-tests*
-                (skip-metadata-calculation-tests? original-query))
+  (when-not *skip-conversion-tests*
     (do-with-legacy-query-testing-context
      original-query
      (^:once fn* []
@@ -51,9 +41,8 @@
           (do-with-pMBQL-query-testing-context
            pMBQL
            (^:once fn* []
-            (let [metadata-provider (lib.metadata.jvm/application-database-metadata-provider (:database original-query))
-                  mlv2-query        (lib/query metadata-provider pMBQL)
-                  mlv2-metadata     (lib.metadata.calculation/metadata mlv2-query)]
+            (let [mlv2-query    (lib/query (qp.store/metadata-provider) pMBQL)
+                  mlv2-metadata (lib.metadata.calculation/metadata mlv2-query)]
               ;; Just make sure we can calculate some metadata (any metadata, even nothing) without throwing an
               ;; Exception at this point; making sure it is CORRECT will be the next step after this.
               (is (any? mlv2-metadata)))))))))))
diff --git a/test/metabase/test_runner/assert_exprs.clj b/test/metabase/test_runner/assert_exprs.clj
index ce3f57a3e1f666be1c5d5f254217fe634489a2a2..bc1fc3d901eb40ca07b758fa62bbba9101e30b68 100644
--- a/test/metabase/test_runner/assert_exprs.clj
+++ b/test/metabase/test_runner/assert_exprs.clj
@@ -6,7 +6,10 @@
   (:require
    [clojure.data :as data]
    [clojure.test :as t]
-   [clojure.walk :as walk]))
+   [clojure.walk :as walk]
+   [metabase.test-runner.assert-exprs.malli-equals]))
+
+(comment metabase.test-runner.assert-exprs.malli-equals/keep-me)
 
 (defn derecordize
   "Convert all record types in `form` to plain maps, so tests won't fail."
diff --git a/test/metabase/test_runner/assert_exprs/malli_equals.cljc b/test/metabase/test_runner/assert_exprs/malli_equals.cljc
new file mode 100644
index 0000000000000000000000000000000000000000..551743eeb0fd7ca323a908d9a73261fc0bf33298
--- /dev/null
+++ b/test/metabase/test_runner/assert_exprs/malli_equals.cljc
@@ -0,0 +1,34 @@
+(ns metabase.test-runner.assert-exprs.malli-equals
+  (:require
+   [clojure.test :as t]
+   [malli.core :as mc]
+   [malli.error :as me]
+   [metabase.util :as u])
+  #?(:cljs
+     (:require-macros [metabase.test-runner.assert-exprs.malli-equals])))
+
+(defn malli=-report [message schema actuals]
+  (doseq [actual actuals]
+    (t/testing (str \newline (u/pprint-to-str actual))
+      (let [error (me/humanize (mc/explain schema actual))]
+        (t/do-report
+         {:type     (if error :fail :pass)
+          :message  message
+          :expected schema
+          :actual   actual
+          :diffs    [[actual [error nil]]]})))))
+
+#?(:clj
+   (do
+     ;; Clojure for Clojure usage
+     (defmethod t/assert-expr 'malli=
+       [message [_ schema & actuals]]
+       `(malli=-report ~message ~schema ~(vec actuals)))
+
+     ;; Clojure doing macroexpansion for ClojureScript usage.
+     (when-let [assert-expr (try
+                              (requiring-resolve 'cljs.test/assert-expr)
+                              (catch Throwable _))]
+       (defmethod (var-get assert-expr) 'malli=
+         [_env message [_ schema & actuals]]
+         `(malli=-report ~message ~schema ~(vec actuals))))))