Malli schema for a Field, aggregation, or expression reference (etc.)  | (ns metabase.lib.schema.ref (:require [clojure.string :as str] [medley.core :as m] [metabase.lib.dispatch :as lib.dispatch] [metabase.lib.hierarchy :as lib.hierarchy] [metabase.lib.schema.binning :as binning] [metabase.lib.schema.common :as common] [metabase.lib.schema.expression :as expression] [metabase.lib.schema.id :as id] [metabase.lib.schema.mbql-clause :as mbql-clause] [metabase.lib.schema.temporal-bucketing :as temporal-bucketing] [metabase.types] [metabase.util.malli.registry :as mr]))  | 
(comment metabase.types/keep-me)  | |
(mr/def ::field.options
  [:merge
   {:encode/serialize (fn [opts]
                        (m/filter-keys (fn [k]
                                         (or (simple-keyword? k)
                                             (= (namespace k) "lib")))
                                       opts))}
   ::common/options
   [:map
    [:temporal-unit                              {:optional true} [:ref ::temporal-bucketing/unit]]
    [:binning                                    {:optional true} [:ref ::binning/binning]]
    [:metabase.lib.field/original-effective-type {:optional true} [:ref ::common/base-type]]
    [:metabase.lib.field/original-temporal-unit  {:optional true} [:ref ::temporal-bucketing/unit]]
    ;; Inherited temporal unit captures the temporal unit, that has been set on a ref, for next stages. It is attached
    ;; _to a column_, which is created from this ref by means of `returned-columns`, ie. is visible [inherited temporal
    ;; unit] in next stages only. This information is used eg. to help pick a default _temporal unit_ for columns that
    ;; are bucketed -- if a column contains `:inherited-temporal-unit`, it was bucketed already in previous stages,
    ;; so nil default picked to avoid another round of bucketing. Shall user bucket the column again, they have to
    ;; select the bucketing explicitly in QB.
    [:inherited-temporal-unit  {:optional true} [:ref ::temporal-bucketing/unit]]]]) | |
(mr/def ::field.literal.options
  [:merge
   ::field.options
   [:map
    [:base-type [:ref ::common/base-type]]]]) | |
  | (mr/def ::field.literal [:tuple [:= :field] ::field.literal.options ::common/non-blank-string])  | 
(mr/def ::field.id [:tuple [:= :field] ::field.options ; TODO -- we should make `:base-type` required here too ::id/field])  | |
(mbql-clause/define-mbql-clause :field
  [:and
   [:tuple
    [:= {:decode/normalize common/normalize-keyword} :field]
    [:ref ::field.options]
    [:or ::id/field ::common/non-blank-string]]
   [:multi {:dispatch      (fn [clause]
                             ;; apparently it still tries to dispatch when humanizing errors even if the `:tuple`
                             ;; schema above failed, so we need to check that this is actually a tuple here again.
                             (when (sequential? clause)
                               (let [[_field _opts id-or-name] clause]
                                 (lib.dispatch/dispatch-value id-or-name))))
            ;; without this it gives us dumb messages like "Invalid dispatch value" if the dispatch function above
            ;; doesn't return something that matches.
            :error/message "Invalid :field clause ID or name: must be a string or integer"}
    [: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)
      ::expression/type.unknown)) | |
(mr/def ::expression.options
  [:merge
   ::common/options
   [:map
    [:temporal-unit                              {:optional true} [:ref ::temporal-bucketing/unit]]]]) | |
(mbql-clause/define-mbql-clause :expression
  [:tuple
   [:= {:decode/normalize common/normalize-keyword} :expression]
   [:ref ::expression.options]
   [:ref #_expression-name ::common/non-blank-string]]) | |
(defmethod expression/type-of-method :expression
  [[_tag opts _expression-name]]
  (or ((some-fn :effective-type :base-type) opts)
      ::expression/type.unknown)) | |
(lib.hierarchy/derive :expression ::ref)  | |
(mr/def ::aggregation-options
  [:merge
   ::common/options
   [:map
    [:name {:optional true} ::common/non-blank-string]
    [:display-name {:optional true} ::common/non-blank-string]
    [:lib/source-name {:optional true} ::common/non-blank-string]]]) | |
(mbql-clause/define-mbql-clause :aggregation
  [:tuple
   [:= {:decode/normalize common/normalize-keyword} :aggregation]
   ::aggregation-options
   :string]) | |
(defmethod expression/type-of-method :aggregation
  [[_tag opts _index]]
  (or ((some-fn :effective-type :base-type) opts)
      ::expression/type.unknown)) | |
(lib.hierarchy/derive :aggregation ::ref)  | |
(mbql-clause/define-tuple-mbql-clause :segment :- :type/Boolean #_segment-id [:schema [:ref ::id/segment]])  | |
(lib.hierarchy/derive :segment ::ref)  | |
(mbql-clause/define-tuple-mbql-clause :metric :- ::expression/type.unknown ;; String references are allowed to support legacy questions ;; (see metabase.lib.convert-test/round-trip-test for examples). ;; :string should be removed once the legacy questions don't have to be ;; supported. #_metric-id [:schema [:ref ::id/metric]])  | |
(lib.hierarchy/derive :metric ::ref)  | |
(mr/def ::ref
  [:and
   ::mbql-clause/clause
   [:fn
    {:error/fn (fn [_ _]
                 (str "Valid reference, must be one of these clauses: "
                      (str/join ", " (sort (descendants @lib.hierarchy/hierarchy ::ref)))))}
    (fn [[tag :as _clause]]
      (lib.hierarchy/isa? tag ::ref))]]) | |