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))]]) | |