diff --git a/src/metabase/lib/aggregation.cljc b/src/metabase/lib/aggregation.cljc index a98506dab0cbc95094d7eb8d2bf651e5d9137af4..1d4f84daa4a9e2b809419d68c1537d314b2e7f8f 100644 --- a/src/metabase/lib/aggregation.cljc +++ b/src/metabase/lib/aggregation.cljc @@ -2,6 +2,7 @@ (:refer-clojure :exclude [count distinct max min]) (:require [metabase.lib.common :as lib.common] + [metabase.lib.hierarchy :as lib.hierarchy] [metabase.lib.metadata :as lib.metadata] [metabase.lib.metadata.calculation :as lib.metadata.calculation] [metabase.lib.schema :as lib.schema] @@ -71,6 +72,8 @@ (str "count_" (lib.metadata.calculation/column-name query stage-number x)) "count")) +(lib.hierarchy/derive :count ::aggregation) + (defmethod lib.metadata.calculation/display-name-method :case [_query _stage-number _case] (i18n/tru "Case")) @@ -79,85 +82,53 @@ [_query _stage-number _case] "case") -(defmethod lib.metadata.calculation/display-name-method :distinct - [query stage-number [_distinct _opts x]] - (i18n/tru "Distinct values of {0}" (lib.metadata.calculation/display-name query stage-number x))) - -(defmethod lib.metadata.calculation/column-name-method :distinct - [query stage-number [_distinct _opts x]] - (str "distinct_" (lib.metadata.calculation/column-name query stage-number x))) - -(defmethod lib.metadata.calculation/display-name-method :avg - [query stage-number [_avg _opts x]] - (i18n/tru "Average of {0}" (lib.metadata.calculation/display-name query stage-number x))) - -(defmethod lib.metadata.calculation/column-name-method :avg - [query stage-number [_avg _opts x]] - (str "avg_" (lib.metadata.calculation/column-name query stage-number x))) - -(defmethod lib.metadata.calculation/display-name-method :cum-count - [query stage-number [_cum-count _opts x]] - (i18n/tru "Cumulative count of {0}" (lib.metadata.calculation/display-name query stage-number x))) - -(defmethod lib.metadata.calculation/column-name-method :cum-count - [query stage-number [_avg _opts x]] - (str "cum_count_" (lib.metadata.calculation/column-name query stage-number x))) - -(defmethod lib.metadata.calculation/display-name-method :sum - [query stage-number [_sum _opts x]] - (i18n/tru "Sum of {0}" (lib.metadata.calculation/display-name query stage-number x))) - -(defmethod lib.metadata.calculation/column-name-method :sum - [query stage-number [_sum _opts x]] - (str "sum_" (lib.metadata.calculation/column-name query stage-number x))) - -(defmethod lib.metadata.calculation/display-name-method :cum-sum - [query stage-number [_cum-sum _opts x]] - (i18n/tru "Cumulative sum of {0}" (lib.metadata.calculation/display-name query stage-number x))) - -(defmethod lib.metadata.calculation/column-name-method :cum-sum - [query stage-number [_avg _opts x]] - (str "cum_sum_" (lib.metadata.calculation/column-name query stage-number x))) - -(defmethod lib.metadata.calculation/display-name-method :stddev - [query stage-number [_stddev _opts x]] - (i18n/tru "Standard deviation of {0}" (lib.metadata.calculation/display-name query stage-number x))) - -(defmethod lib.metadata.calculation/column-name-method :stddev - [query stage-number [_avg _opts x]] - (str "std_dev_" (lib.metadata.calculation/column-name query stage-number x))) - -(defmethod lib.metadata.calculation/display-name-method :min - [query stage-number [_min _opts x]] - (i18n/tru "Min of {0}" (lib.metadata.calculation/display-name query stage-number x))) - -(defmethod lib.metadata.calculation/column-name-method :min - [query stage-number [_min _opts x]] - (str "min_" (lib.metadata.calculation/column-name query stage-number x))) - -(defmethod lib.metadata.calculation/display-name-method :max - [query stage-number [_max _opts x]] - (i18n/tru "Max of {0}" (lib.metadata.calculation/display-name query stage-number x))) - -(defmethod lib.metadata.calculation/column-name-method :max - [query stage-number [_max _opts x]] - (str "max_" (lib.metadata.calculation/column-name query stage-number x))) - -(defmethod lib.metadata.calculation/display-name-method :var - [query stage-number [_var _opts x]] - (i18n/tru "Variance of {0}" (lib.metadata.calculation/display-name query stage-number x))) - -(defmethod lib.metadata.calculation/column-name-method :var - [query stage-number [_var _opts x]] - (str "var_" (lib.metadata.calculation/column-name query stage-number x))) - -(defmethod lib.metadata.calculation/display-name-method :median - [query stage-number [_median _opts x]] - (i18n/tru "Median of {0}" (lib.metadata.calculation/display-name query stage-number x))) - -(defmethod lib.metadata.calculation/column-name-method :median - [query stage-number [_median _opts x]] - (str "median_" (lib.metadata.calculation/column-name query stage-number x))) +(lib.hierarchy/derive :case ::aggregation) + +(lib.hierarchy/derive ::unary-aggregation ::aggregation) + +(doseq [tag [:avg + :cum-count + :cum-sum + :distinct + :max + :median + :min + :stddev + :sum + :var]] + (lib.hierarchy/derive tag ::unary-aggregation)) + +(defmethod lib.metadata.calculation/column-name-method ::unary-aggregation + [query stage-number [tag _opts arg]] + (let [arg (lib.metadata.calculation/column-name-method query stage-number arg)] + (str + (case tag + :avg "avg_" + :cum-count "cum_count_" + :cum-sum "cum_sum_" + :distinct "distinct_" + :max "max_" + :median "median_" + :min "min_" + :stddev "std_dev_" + :sum "sum_" + :var "var_") + arg))) + +(defmethod lib.metadata.calculation/display-name-method ::unary-aggregation + [query stage-number [tag _opts arg]] + (let [arg (lib.metadata.calculation/display-name query stage-number arg)] + (case tag + :avg (i18n/tru "Average of {0}" arg) + :cum-count (i18n/tru "Cumulative count of {0}" arg) + :cum-sum (i18n/tru "Cumulative sum of {0}" arg) + :distinct (i18n/tru "Distinct values of {0}" arg) + :max (i18n/tru "Max of {0}" arg) + :median (i18n/tru "Median of {0}" arg) + :min (i18n/tru "Min of {0}" arg) + :stddev (i18n/tru "Standard deviation of {0}" arg) + :sum (i18n/tru "Sum of {0}" arg) + :var (i18n/tru "Variance of {0}" arg)))) (defmethod lib.metadata.calculation/display-name-method :percentile [query stage-number [_percentile _opts x p]] @@ -167,16 +138,22 @@ [query stage-number [_percentile _opts x p]] (lib.util/format "p%d_%s" p (lib.metadata.calculation/column-name query stage-number x))) -;;; we don't currently have sophisticated logic for generating nice display names for filter clauses +(lib.hierarchy/derive :percentile ::aggregation) + +;;; we don't currently have sophisticated logic for generating nice display names for filter clauses. +;;; +;;; TODO : wait a minute, we do have that stuff now! (defmethod lib.metadata.calculation/display-name-method :sum-where [query stage-number [_sum-where _opts x _pred]] (i18n/tru "Sum of {0} matching condition" (lib.metadata.calculation/display-name query stage-number x))) (defmethod lib.metadata.calculation/column-name-method :sum-where - [query stage-number [_sum-where _opts x]] + [query stage-number [_sum-where _opts x _pred]] (str "sum_where_" (lib.metadata.calculation/column-name query stage-number x))) +(lib.hierarchy/derive :sum-where ::aggregation) + (defmethod lib.metadata.calculation/display-name-method :share [_query _stage-number _share] (i18n/tru "Share of rows matching condition")) @@ -185,6 +162,8 @@ [_query _stage-number _share] "share") +(lib.hierarchy/derive :share ::aggregation) + (defmethod lib.metadata.calculation/display-name-method :count-where [_query _stage-number _count-where] (i18n/tru "Count of rows matching condition")) @@ -193,18 +172,29 @@ [_query _stage-number _count-where] "count-where") -(lib.common/defop count [] [x]) -(lib.common/defop avg [x]) +(lib.hierarchy/derive :count-where ::aggregation) + +(defmethod lib.metadata.calculation/metadata-method ::aggregation + [query stage-number [_tag _opts first-arg :as clause]] + (merge + ;; flow the `:options` from the field we're aggregating. This is important, for some reason. + ;; See [[metabase.query-processor-test.aggregation-test/field-settings-for-aggregate-fields-test]] + (when first-arg + (select-keys (lib.metadata.calculation/metadata query stage-number first-arg) [:settings])) + ((get-method lib.metadata.calculation/metadata-method :default) query stage-number clause))) + +(lib.common/defop count [] [x]) +(lib.common/defop avg [x]) (lib.common/defop count-where [x y]) -(lib.common/defop distinct [x]) -(lib.common/defop max [x]) -(lib.common/defop median [x]) -(lib.common/defop min [x]) -(lib.common/defop percentile [x y]) -(lib.common/defop share [x]) -(lib.common/defop stddev [x]) -(lib.common/defop sum [x]) -(lib.common/defop sum-where [x y]) +(lib.common/defop distinct [x]) +(lib.common/defop max [x]) +(lib.common/defop median [x]) +(lib.common/defop min [x]) +(lib.common/defop percentile [x y]) +(lib.common/defop share [x]) +(lib.common/defop stddev [x]) +(lib.common/defop sum [x]) +(lib.common/defop sum-where [x y]) (mu/defn aggregate :- ::lib.schema/query "Adds an aggregation to query." @@ -220,10 +210,13 @@ (mu/defn aggregations :- [:maybe [:sequential lib.metadata/ColumnMetadata]] "Get metadata about the aggregations in a given stage of a query." - [query :- ::lib.schema/query - stage-number :- :int] - (when-let [aggregation-exprs (not-empty (:aggregation (lib.util/query-stage query stage-number)))] - (map-indexed (fn [i aggregation] - (let [metadata (lib.metadata.calculation/metadata query stage-number aggregation)] - (assoc metadata :lib/source :source/aggregations, ::aggregation-index i))) - aggregation-exprs))) + ([query] + (aggregations query -1)) + + ([query :- ::lib.schema/query + stage-number :- :int] + (when-let [aggregation-exprs (not-empty (:aggregation (lib.util/query-stage query stage-number)))] + (map-indexed (fn [i aggregation] + (let [metadata (lib.metadata.calculation/metadata query stage-number aggregation)] + (assoc metadata :lib/source :source/aggregations, ::aggregation-index i))) + aggregation-exprs)))) diff --git a/src/metabase/lib/common.cljc b/src/metabase/lib/common.cljc index cda9d9be0ab0807e574f77de58dd8a8bdad0e0d9..436467eb5acbda272a0b4de1401f8f76acfb35ea 100644 --- a/src/metabase/lib/common.cljc +++ b/src/metabase/lib/common.cljc @@ -1,6 +1,7 @@ (ns metabase.lib.common (:require [metabase.lib.dispatch :as lib.dispatch] + [metabase.lib.hierarchy :as lib.hierarchy] [metabase.lib.options :as lib.options] [metabase.lib.ref :as lib.ref] [metabase.lib.schema.common :as schema.common] @@ -23,7 +24,8 @@ "Ensures that clause arguments are properly unwrapped" {:arglists '([query stage-number x])} (fn [_query _stage-number x] - (lib.dispatch/dispatch-value x))) + (lib.dispatch/dispatch-value x)) + :hierarchy lib.hierarchy/hierarchy) (defmethod ->op-arg :default [_query _stage-number x] diff --git a/src/metabase/lib/convert.cljc b/src/metabase/lib/convert.cljc index d92cfa8c7bb2d8e6cdc5679a2f26b6bf215f98b9..261f8aa874119938852e36693801a704df6634da 100644 --- a/src/metabase/lib/convert.cljc +++ b/src/metabase/lib/convert.cljc @@ -61,11 +61,12 @@ [[_tag x y]] (let [[id-or-name options] (if (map? x) [y x] - [x y]) - options (cond-> options - (not (:lib/uuid options)) - (assoc :lib/uuid (str (random-uuid))))] - [:field options id-or-name])) + [x y])] + (lib.options/ensure-uuid [:field options id-or-name]))) + +(defmethod ->pMBQL :value + [[_tag value opts]] + (lib.options/ensure-uuid [:value opts value])) (defmethod ->pMBQL :aggregation-options [[_tag aggregation options]] @@ -172,6 +173,12 @@ (->legacy-MBQL id) (not-empty (disqualify opts))])) +(defmethod ->legacy-MBQL :value + [[_tag opts value]] + (if-let [opts (not-empty (disqualify opts))] + [:value value opts] + [:value value])) + (defmethod ->legacy-MBQL :mbql/join [join] (let [base (disqualify join)] (merge (-> base diff --git a/src/metabase/lib/core.cljc b/src/metabase/lib/core.cljc index f47949feab45c9c42170d7a0a9aedb173b48b2d7..fdf106cd52453c0888ffc67a3a7998373389b245 100644 --- a/src/metabase/lib/core.cljc +++ b/src/metabase/lib/core.cljc @@ -47,6 +47,7 @@ (shared.ns/import-fns [lib.aggregation + aggregations aggregate count avg @@ -145,7 +146,8 @@ describe-query describe-top-level-key display-name - suggested-name] + suggested-name + type-of] [lib.native #?@(:cljs [->TemplateTags TemplateTags->]) diff --git a/src/metabase/lib/equality.cljc b/src/metabase/lib/equality.cljc index 4fcdeb0ec24f07fbba437b1495a5852e2977a7ff..d046c5029a598487ebb47f30913d1a1e550eb143 100644 --- a/src/metabase/lib/equality.cljc +++ b/src/metabase/lib/equality.cljc @@ -2,7 +2,8 @@ "Logic for determining whether two pMBQL queries are equal." (:refer-clojure :exclude [=]) (:require - [metabase.lib.dispatch :as lib.dispatch])) + [metabase.lib.dispatch :as lib.dispatch] + [metabase.lib.hierarchy :as lib.hierarchy])) (defmulti = "Determine whether two already-normalized pMBQL maps, clauses, or other sorts of expressions are equal. The basic rule @@ -16,7 +17,8 @@ y-dispatch-value (lib.dispatch/dispatch-value y)] (if (not= x-dispatch-value y-dispatch-value) ::different-dispatch-values - x-dispatch-value)))) + x-dispatch-value))) + :hierarchy lib.hierarchy/hierarchy) (defmethod = ::different-dispatch-values [_x _y] diff --git a/src/metabase/lib/expression.cljc b/src/metabase/lib/expression.cljc index 395f54c036eeb41c02564a5246121352638a3702..d460c3ad3b6a03389df5e7552b40d34c489acaa9 100644 --- a/src/metabase/lib/expression.cljc +++ b/src/metabase/lib/expression.cljc @@ -5,6 +5,7 @@ (:require [clojure.string :as str] [metabase.lib.common :as lib.common] + [metabase.lib.hierarchy :as lib.hierarchy] [metabase.lib.metadata :as lib.metadata] [metabase.lib.metadata.calculation :as lib.metadata.calculation] [metabase.lib.schema :as lib.schema] @@ -14,6 +15,7 @@ :as lib.schema.temporal-bucketing] [metabase.lib.util :as lib.util] [metabase.shared.util.i18n :as i18n] + [metabase.types :as types] [metabase.util.malli :as mu])) (mu/defn column-metadata->expression-ref :- :mbql.clause/expression @@ -38,19 +40,18 @@ :query query :stage-number stage-number}))))) -(defmethod lib.schema.expression/type-of* :lib/external-op - [{:keys [operator options args] :or {options {}}}] - (lib.schema.expression/type-of* (into [(keyword operator) options] args))) +(defmethod lib.metadata.calculation/type-of-method :expression + [query stage-number [_expression _opts expression-name, :as _expression-ref]] + (let [expression (resolve-expression query stage-number expression-name)] + (lib.metadata.calculation/type-of query stage-number expression))) (defmethod lib.metadata.calculation/metadata-method :expression - [query stage-number [_expression opts expression-name, :as expression-ref]] - (let [expression (resolve-expression query stage-number expression-name)] - {:lib/type :metadata/field - :lib/source :source/expressions - :name expression-name - :display_name (lib.metadata.calculation/display-name query stage-number expression-ref) - :base_type (or (:base-type opts) - (lib.schema.expression/type-of expression))})) + [query stage-number [_expression _opts expression-name, :as expression-ref]] + {:lib/type :metadata/field + :name expression-name + :display_name (lib.metadata.calculation/display-name query stage-number expression-ref) + :base_type (lib.metadata.calculation/type-of query stage-number expression-ref) + :lib/source :source/expressions}) (defmethod lib.metadata.calculation/display-name-method :dispatch-type/number [_query _stage-number n] @@ -87,21 +88,18 @@ (map (partial lib.metadata.calculation/display-name query stage-number) args))))) -(defmethod lib.metadata.calculation/display-name-method :+ - [query stage-number [_plus _opts & args]] - (infix-display-name query stage-number "+" args)) - -(defmethod lib.metadata.calculation/display-name-method :- - [query stage-number [_minute _opts & args]] - (infix-display-name query stage-number "-" args)) +(def ^:private infix-operator-display-name + {:+ "+" + :- "-" + :* "×" + :/ "÷"}) -(defmethod lib.metadata.calculation/display-name-method :/ - [query stage-number [_divide _opts & args]] - (infix-display-name query stage-number "÷" args)) +(doseq [tag [:+ :- :/ :*]] + (lib.hierarchy/derive tag ::infix-operator)) -(defmethod lib.metadata.calculation/display-name-method :* - [query stage-number [_multiply _opts & args]] - (infix-display-name query stage-number "×" args)) +(defmethod lib.metadata.calculation/display-name-method ::infix-operator + [query stage-number [tag _opts & args]] + (infix-display-name query stage-number (get infix-operator-display-name tag) args)) (defn- infix-column-name [query stage-number operator-str args] @@ -109,48 +107,73 @@ (map (partial lib.metadata.calculation/column-name query stage-number) args))) -(defmethod lib.metadata.calculation/column-name-method :+ - [query stage-number [_plus _opts & args]] - (infix-column-name query stage-number "plus" args)) - -(defmethod lib.metadata.calculation/column-name-method :- - [query stage-number [_minute _opts & args]] - (infix-column-name query stage-number "minus" args)) - -(defmethod lib.metadata.calculation/column-name-method :/ - [query stage-number [_divide _opts & args]] - (infix-column-name query stage-number "divided_by" args)) - -(defmethod lib.metadata.calculation/column-name-method :* - [query stage-number [_multiply _opts & args]] - (infix-column-name query stage-number "times" args)) +(def ^:private infix-operator-column-name + {:+ "plus" + :- "minus" + :/ "divided_by" + :* "times"}) + +(defmethod lib.metadata.calculation/column-name-method ::infix-operator + [query stage-number [tag _opts & args]] + (infix-column-name query stage-number (get infix-operator-column-name tag) args)) + +;;; `:+`, `:-`, and `:*` all have the same logic; also used for [[metabase.lib.schema.expression/type-of]]. +;;; +;;; `:lib.type-of/type-is-type-of-arithmetic-args` is defined in [[metabase.lib.schema.expression.arithmetic]] +(defmethod lib.metadata.calculation/type-of-method :lib.type-of/type-is-type-of-arithmetic-args + [query stage-number [_tag _opts & args]] + ;; Okay to use reduce without an init value here since we know we have >= 2 args + #_{:clj-kondo/ignore [:reduce-without-init]} + (reduce + types/most-specific-common-ancestor + (for [arg args] + (lib.metadata.calculation/type-of query stage-number arg)))) + +(defn- interval-unit-str [amount unit] + (clojure.core/case unit + :millisecond (i18n/trun "millisecond" "milliseconds" (clojure.core/abs amount)) + :second (i18n/trun "second" "seconds" (clojure.core/abs amount)) + :minute (i18n/trun "minute" "minutes" (clojure.core/abs amount)) + :hour (i18n/trun "hour" "hours" (clojure.core/abs amount)) + :day (i18n/trun "day" "days" (clojure.core/abs amount)) + :week (i18n/trun "week" "weeks" (clojure.core/abs amount)) + :month (i18n/trun "month" "months" (clojure.core/abs amount)) + :quarter (i18n/trun "quarter" "quarters" (clojure.core/abs amount)) + :year (i18n/trun "year" "years" (clojure.core/abs amount)))) (mu/defn ^:private interval-display-name :- ::lib.schema.common/non-blank-string "e.g. something like \"- 2 days\"" [amount :- :int unit :- ::lib.schema.temporal-bucketing/unit.date-time.interval] ;; TODO -- sorta duplicated with [[metabase.shared.parameters.parameters/translated-interval]], but not exactly - (let [unit-str (clojure.core/case unit - :millisecond (i18n/trun "millisecond" "milliseconds" (clojure.core/abs amount)) - :second (i18n/trun "second" "seconds" (clojure.core/abs amount)) - :minute (i18n/trun "minute" "minutes" (clojure.core/abs amount)) - :hour (i18n/trun "hour" "hours" (clojure.core/abs amount)) - :day (i18n/trun "day" "days" (clojure.core/abs amount)) - :week (i18n/trun "week" "weeks" (clojure.core/abs amount)) - :month (i18n/trun "month" "months" (clojure.core/abs amount)) - :quarter (i18n/trun "quarter" "quarters" (clojure.core/abs amount)) - :year (i18n/trun "year" "years" (clojure.core/abs amount)))] + (let [unit-str (interval-unit-str amount unit)] (wrap-str-in-parens-if-nested (if (pos? amount) - (lib.util/format "+ %d %s" amount unit-str) + (lib.util/format "+ %d %s" amount unit-str) (lib.util/format "- %d %s" (clojure.core/abs amount) unit-str))))) +(mu/defn ^:private interval-column-name :- ::lib.schema.common/non-blank-string + "e.g. something like `minus_2_days`" + [amount :- :int + unit :- ::lib.schema.temporal-bucketing/unit.date-time.interval] + ;; TODO -- sorta duplicated with [[metabase.shared.parameters.parameters/translated-interval]], but not exactly + (let [unit-str (interval-unit-str amount unit)] + (if (pos? amount) + (lib.util/format "plus_%s_%s" amount unit-str) + (lib.util/format "minus_%d_%s" (clojure.core/abs amount) unit-str)))) + (defmethod lib.metadata.calculation/display-name-method :datetime-add [query stage-number [_datetime-add _opts x amount unit]] (str (lib.metadata.calculation/display-name query stage-number x) \space (interval-display-name amount unit))) +(defmethod lib.metadata.calculation/column-name-method :datetime-add + [query stage-number [_datetime-add _opts x amount unit]] + (str (lib.metadata.calculation/column-name query stage-number x) + \_ + (interval-column-name amount unit))) + ;;; for now we'll just pretend `:coalesce` isn't a present and just use the display name for the expr it wraps. (defmethod lib.metadata.calculation/display-name-method :coalesce [query stage-number [_coalesce _opts expr _null-expr]] diff --git a/src/metabase/lib/field.cljc b/src/metabase/lib/field.cljc index dbd505dcaebc9e4169be817e123e52caccef6a67..a9497c1f0ce35c12f03d45cea66f019d54895dc0 100644 --- a/src/metabase/lib/field.cljc +++ b/src/metabase/lib/field.cljc @@ -81,6 +81,14 @@ (update metadata :name (fn [field-name] (str parent-name \. field-name))))) +(defmethod lib.metadata.calculation/type-of-method :metadata/field + [_query _stage-number field-metadata] + ((some-fn :effective_type :base_type) field-metadata)) + +(defmethod lib.metadata.calculation/type-of-method :field + [query stage-number field-ref] + (lib.metadata.calculation/type-of query stage-number (resolve-field-metadata query stage-number field-ref))) + (defmethod lib.metadata.calculation/metadata-method :metadata/field [_query _stage-number field-metadata] field-metadata) diff --git a/src/metabase/lib/hierarchy.cljc b/src/metabase/lib/hierarchy.cljc new file mode 100644 index 0000000000000000000000000000000000000000..0e19deeff2204e8a4dbb4a886188d70136aa3c1c --- /dev/null +++ b/src/metabase/lib/hierarchy.cljc @@ -0,0 +1,15 @@ +(ns metabase.lib.hierarchy + (:refer-clojure :exclude [derive isa?])) + +(defonce ^{:doc "Keyword hierarchy for MLv2 stuff."} hierarchy + (atom (make-hierarchy))) + +(defn derive + "Like [[clojure.core/derive]], but affects [[hierarchy]] rather than the global hierarchy." + [tag parent] + (swap! hierarchy clojure.core/derive tag parent)) + +(defn isa? + "Like [[clojure.core/isa?]], but uses [[hierarchy]]." + [tag parent] + (clojure.core/isa? @hierarchy tag parent)) diff --git a/src/metabase/lib/join.cljc b/src/metabase/lib/join.cljc index 329cda5694fae795c494be3b4e2ee050e5a3c92e..ef5dff225f2033b15f0bf4ac01ef9d557764685d 100644 --- a/src/metabase/lib/join.cljc +++ b/src/metabase/lib/join.cljc @@ -2,6 +2,7 @@ (:require [medley.core :as m] [metabase.lib.dispatch :as lib.dispatch] + [metabase.lib.hierarchy :as lib.hierarchy] [metabase.lib.metadata :as lib.metadata] [metabase.lib.metadata.calculation :as lib.metadata.calculation] [metabase.lib.options :as lib.options] @@ -102,7 +103,8 @@ "Convert something to a join clause." {:arglists '([query stage-number x])} (fn [_query _stage-number x] - (lib.dispatch/dispatch-value x))) + (lib.dispatch/dispatch-value x)) + :hierarchy lib.hierarchy/hierarchy) ;; TODO -- should the default implementation call [[metabase.lib.query/query]]? That way if we implement a method to ;; create an MBQL query from a `Table`, then we'd also get [[join]] support for free? @@ -141,7 +143,8 @@ (defmulti ^:private ->join-condition {:arglists '([query stage-number x])} (fn [_query _stage-number x] - (lib.dispatch/dispatch-value x))) + (lib.dispatch/dispatch-value x)) + :hierarchy lib.hierarchy/hierarchy) (defmethod ->join-condition :default [_query _stage-number x] diff --git a/src/metabase/lib/js/metadata.cljs b/src/metabase/lib/js/metadata.cljs index e5ab1112051a502fc1a4c40e3fd061aa5718e073..eb52f60d4754cc5053304ede23a2de696232634c 100644 --- a/src/metabase/lib/js/metadata.cljs +++ b/src/metabase/lib/js/metadata.cljs @@ -40,6 +40,7 @@ xform) (gobject/getKeys obj)))) +;;; this intentionally does not use the lib hierarchy since it's not dealing with MBQL/lib keys (defmulti ^:private excluded-fields {:arglists '([object-type])} keyword) diff --git a/src/metabase/lib/metadata.cljc b/src/metabase/lib/metadata.cljc index 279827c85604a406541dd920aebca31143ab8821..d0223ad813bc43e349697b37bcc8da76ffd78009 100644 --- a/src/metabase/lib/metadata.cljc +++ b/src/metabase/lib/metadata.cljc @@ -1,6 +1,7 @@ (ns metabase.lib.metadata (:require [metabase.lib.dispatch :as lib.dispatch] + [metabase.lib.hierarchy :as lib.hierarchy] [metabase.lib.metadata.protocols :as lib.metadata.protocols] [metabase.lib.schema.common :as lib.schema.common] [metabase.lib.schema.id :as lib.schema.id] @@ -132,7 +133,8 @@ (defmulti ^:private ->metadata-provider* {:arglists '([x])} - lib.dispatch/dispatch-value) + lib.dispatch/dispatch-value + :hierarchy lib.hierarchy/hierarchy) (defmethod ->metadata-provider* :default [x] diff --git a/src/metabase/lib/metadata/calculation.cljc b/src/metabase/lib/metadata/calculation.cljc index 476360ec23a5a8c96c58865f9c6d58c1bfb8506a..502c387c3a9b442f5445e5dfcf971b246c5c5dcf 100644 --- a/src/metabase/lib/metadata/calculation.cljc +++ b/src/metabase/lib/metadata/calculation.cljc @@ -2,6 +2,7 @@ (:require [clojure.string :as str] [metabase.lib.dispatch :as lib.dispatch] + [metabase.lib.hierarchy :as lib.hierarchy] [metabase.lib.metadata :as lib.metadata] [metabase.lib.options :as lib.options] [metabase.lib.schema :as lib.schema] @@ -17,13 +18,15 @@ "Calculate a nice human-friendly display name for something." {:arglists '([query stage-number x])} (fn [_query _stage-number x] - (lib.dispatch/dispatch-value x))) + (lib.dispatch/dispatch-value x)) + :hierarchy lib.hierarchy/hierarchy) (defmulti column-name-method "Calculate a database-friendly name to use for something." {:arglists '([query stage-number x])} (fn [_query _stage-number x] - (lib.dispatch/dispatch-value x))) + (lib.dispatch/dispatch-value x)) + :hierarchy lib.hierarchy/hierarchy) (mu/defn ^:export display-name :- ::lib.schema.common/non-blank-string "Calculate a nice human-friendly display name for something." @@ -47,20 +50,23 @@ (mu/defn column-name :- ::lib.schema.common/non-blank-string "Calculate a database-friendly name to use for an expression." - [query :- ::lib.schema/query - stage-number :- :int - x] - (or - ;; if this is an MBQL clause with `:name` in the options map, then use that rather than calculating a name. - (:name (lib.options/options x)) - (try - (column-name-method query stage-number x) - (catch #?(:clj Throwable :cljs js/Error) e - (throw (ex-info (i18n/tru "Error calculating column name for {0}: {1}" (pr-str x) (ex-message e)) - {:x x - :query query - :stage-number stage-number} - e)))))) + ([query x] + (column-name query -1 x)) + + ([query :- ::lib.schema/query + stage-number :- :int + x] + (or + ;; if this is an MBQL clause with `:name` in the options map, then use that rather than calculating a name. + (:name (lib.options/options x)) + (try + (column-name-method query stage-number x) + (catch #?(:clj Throwable :cljs js/Error) e + (throw (ex-info (i18n/tru "Error calculating column name for {0}: {1}" (pr-str x) (ex-message e)) + {:x x + :query query + :stage-number stage-number} + e))))))) (defmethod display-name-method :default [_query _stage-number x] @@ -76,10 +82,14 @@ ;; anything else: use `pr-str` representation. (pr-str x))) +;;; TODO -- this logic is wack, we should probably be snake casing stuff and display names like +;;; +;;; "Sum of Products → Price" +;;; +;;; result in totally wacko column names like "sum_products_%E2%86%92_price", let's try to generate things that are +;;; actually going to be allowed here. (defn- slugify [s] (-> s - (str/replace #"\+" (i18n/tru "plus")) - (str/replace #"\-" (i18n/tru "minus")) (str/replace #"[\(\)]" "") u/slugify)) @@ -93,7 +103,8 @@ `:aggregation` part. Return `nil` if there is nothing to describe." {:arglists '([query stage-number top-level-key])} (fn [_query _stage-number top-level-key] - top-level-key)) + top-level-key) + :hierarchy lib.hierarchy/hierarchy) (def ^:private TopLevelKey "In the interest of making this easy to use in JS-land we'll accept either strings or keywords." @@ -109,16 +120,55 @@ top-level-key :- TopLevelKey] (describe-top-level-key-method query stage-number (keyword top-level-key)))) +(defmulti type-of-method + "Calculate the effective type of something. This differs from [[metabase.lib.schema.expression/type-of]] in that it is + called with a query/MetadataProvider and a stage number, allowing us to fully resolve information and return + complete, unambigous type information. Default implementation calls [[metabase.lib.schema.expression/type-of]]." + {:arglists '([query stage-number expr])} + (fn [_query _stage-number expr] + (lib.dispatch/dispatch-value expr)) + :hierarchy lib.hierarchy/hierarchy) + +(mu/defn type-of :- ::lib.schema.common/base-type + "Get the effective type of an MBQL expression." + ([query x] + (type-of query -1 x)) + ([query :- ::lib.schema/query + stage-number :- :int + x] + (or ((some-fn :effective-type :base-type) (lib.options/options x)) + (type-of-method query stage-number x)))) + +(defmethod type-of-method :default + [_query _stage-number expr] + (lib.schema.expresssion/type-of expr)) + +(defmethod type-of-method :dispatch-type/fn + [query stage-number f] + (type-of query stage-number (f query stage-number))) + +;;; for MBQL clauses whose type is the same as the type of the first arg. Also used +;;; for [[metabase.lib.schema.expression/type-of]]. +(defmethod type-of-method :lib.type-of/type-is-type-of-first-arg + [query stage-number [_tag _opts expr]] + (type-of query stage-number expr)) + +;;; Ugh +(defmethod type-of-method :lib/external-op + [query stage-number {:keys [operator options args]}] + (type-of query stage-number (into [(keyword operator) options] args))) + (defmulti metadata-method "Impl for [[metadata]]." {:arglists '([query stage-number x])} (fn [_query _stage-number x] - (lib.dispatch/dispatch-value x))) + (lib.dispatch/dispatch-value x)) + :hierarchy lib.hierarchy/hierarchy) (defmethod metadata-method :default [query stage-number x] {:lib/type :metadata/field - :base_type (lib.schema.expresssion/type-of x) + :base_type (type-of query stage-number x) :name (column-name query stage-number x) :display_name (display-name query stage-number x)}) diff --git a/src/metabase/lib/metric.cljc b/src/metabase/lib/metric.cljc index 2a7fee07c65466b8d48025be14d3a97634e45d6e..3392f0daa29d22f4961f7c079e8048c7714a44fd 100644 --- a/src/metabase/lib/metric.cljc +++ b/src/metabase/lib/metric.cljc @@ -1,8 +1,44 @@ (ns metabase.lib.metric + "A Metric is a saved MBQL query stage snippet with EXACTLY ONE `:aggregation` and optionally a `:filter` (boolean) + expression. Can be passed into the `:aggregation`s list." (:require + [metabase.lib.convert :as lib.convert] [metabase.lib.metadata :as lib.metadata] [metabase.lib.metadata.calculation :as lib.metadata.calculation] - [metabase.shared.util.i18n :as i18n])) + [metabase.lib.schema :as lib.schema] + [metabase.lib.util :as lib.util] + [metabase.mbql.normalize :as mbql.normalize] + [metabase.shared.util.i18n :as i18n] + [metabase.util.malli :as mu])) + +(defn- resolve-metric [query metric-id] + (when (integer? metric-id) + (lib.metadata/metric query metric-id))) + +(mu/defn ^:private metric-definition :- [:maybe ::lib.schema/stage.mbql] + [{:keys [definition], :as _metric-metadata}] + (when definition + (if (:mbql/type definition) + definition + ;; legacy; needs conversion + (-> + (lib.convert/legacy-query-from-inner-query nil definition) + mbql.normalize/normalize + lib.convert/->pMBQL + (lib.util/query-stage -1))))) + +(defmethod lib.metadata.calculation/type-of-method :metadata/metric + [query stage-number metric-metadata] + (or + (when-let [[aggregation] (not-empty (:aggregation (metric-definition metric-metadata)))] + (lib.metadata.calculation/type-of query stage-number aggregation)) + :type/*)) + +(defmethod lib.metadata.calculation/type-of-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/type-of query stage-number metric-metadata)) + :type/*)) (defmethod lib.metadata.calculation/display-name-method :metadata/metric [_query _stage-number metric-metadata] @@ -11,7 +47,12 @@ (defmethod lib.metadata.calculation/display-name-method :metric [query stage-number [_tag _opts metric-id-or-name]] - (or (when (integer? metric-id-or-name) - (when-let [metric-metadata (lib.metadata/metric query metric-id-or-name)] - (lib.metadata.calculation/display-name query stage-number metric-metadata))) + (or (when-let [metric-metadata (resolve-metric query metric-id-or-name)] + (lib.metadata.calculation/display-name query stage-number metric-metadata)) (i18n/tru "[Unknown Metric]"))) + +(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")) diff --git a/src/metabase/lib/normalize.cljc b/src/metabase/lib/normalize.cljc index b0e17e42088900dbf8cd39d092456be5472282ff..f2e629870a37b9c59d8a6c984e1023b99d9992ac 100644 --- a/src/metabase/lib/normalize.cljc +++ b/src/metabase/lib/normalize.cljc @@ -1,6 +1,7 @@ (ns metabase.lib.normalize (:require - [metabase.lib.dispatch :as lib.dispatch])) + [metabase.lib.dispatch :as lib.dispatch] + [metabase.lib.hierarchy :as lib.hierarchy])) (defn- mbql-clause-type [x] (when (and (vector? x) @@ -29,7 +30,8 @@ using [[default-map-value-fns]]; for MBQL clauses, it will convert the clause name to a keyword and recursively normalize its options and arguments. Implement this method if you need custom behavior for something." {:arglists '([x])} - dispatch-value) + dispatch-value + :hierarchy lib.hierarchy/hierarchy) (def default-map-value-fns "Default normalization functions keys when doing map normalization." diff --git a/src/metabase/lib/options.cljc b/src/metabase/lib/options.cljc index 30ecf1c1c58f0a051487022f5185bcbed1bc6b33..e83ff49ac13e564e9ea59c294acd44748e45b09f 100644 --- a/src/metabase/lib/options.cljc +++ b/src/metabase/lib/options.cljc @@ -51,7 +51,7 @@ (assoc x :lib/options new-options) (mbql-clause? x) - (if (map? (second x)) + (if ((some-fn nil? map?) (second x)) (assoc (vec x) 1 new-options) (into [(first x) new-options] (rest x))) diff --git a/src/metabase/lib/order_by.cljc b/src/metabase/lib/order_by.cljc index fdb8618045ed80d7d9949f7fd07c16367b33444a..8edcbc7eadc68da58032ced3d67ed3b48d034e40 100644 --- a/src/metabase/lib/order_by.cljc +++ b/src/metabase/lib/order_by.cljc @@ -3,6 +3,7 @@ [metabase.lib.aggregation :as lib.aggregation] [metabase.lib.breakout :as lib.breakout] [metabase.lib.dispatch :as lib.dispatch] + [metabase.lib.hierarchy :as lib.hierarchy] [metabase.lib.metadata :as lib.metadata] [metabase.lib.metadata.calculation :as lib.metadata.calculation] [metabase.lib.options :as lib.options] @@ -35,7 +36,8 @@ (defmulti ^:private ->order-by-clause {:arglists '([query stage-number x])} (fn [_query _stage-number x] - (lib.dispatch/dispatch-value x))) + (lib.dispatch/dispatch-value x)) + :hierarchy lib.hierarchy/hierarchy) (defmethod ->order-by-clause :asc [_query _stage-number clause] diff --git a/src/metabase/lib/query.cljc b/src/metabase/lib/query.cljc index 778a5a75aedd288b1b6c42c1316778075023871f..45a089b503484682e6dc940571b8d65e9d097a2b 100644 --- a/src/metabase/lib/query.cljc +++ b/src/metabase/lib/query.cljc @@ -4,6 +4,7 @@ [metabase.lib.common :as lib.common] [metabase.lib.convert :as lib.convert] [metabase.lib.dispatch :as lib.dispatch] + [metabase.lib.hierarchy :as lib.hierarchy] [metabase.lib.metadata :as lib.metadata] [metabase.lib.metadata.calculation :as lib.metadata.calculation] [metabase.lib.normalize :as lib.normalize] @@ -96,7 +97,8 @@ "Implementation for [[query]]." {:arglists '([metadata-provider x])} (fn [_metadata-provider x] - (lib.dispatch/dispatch-value x))) + (lib.dispatch/dispatch-value x)) + :hierarchy lib.hierarchy/hierarchy) (defmethod ->query :dispatch-type/map [metadata-provider query] diff --git a/src/metabase/lib/schema/aggregation.cljc b/src/metabase/lib/schema/aggregation.cljc index 20e7d8be512370957891d21d3aed84e17b6b6299..d61a47f909520f2d5540fb856cc3e13c3496f0cc 100644 --- a/src/metabase/lib/schema/aggregation.cljc +++ b/src/metabase/lib/schema/aggregation.cljc @@ -1,5 +1,6 @@ (ns metabase.lib.schema.aggregation (:require + [metabase.lib.hierarchy :as lib.hierarchy] [metabase.lib.schema.expression :as expression] [metabase.lib.schema.mbql-clause :as mbql-clause] [metabase.util.malli.registry :as mr])) @@ -20,23 +21,23 @@ (mbql-clause/define-tuple-mbql-clause :max [:schema [:ref ::expression/number]]) -(expression/register-type-of-first-arg :max) +(lib.hierarchy/derive :max :lib.type-of/type-is-type-of-first-arg) (mbql-clause/define-tuple-mbql-clause :median [:schema [:ref ::expression/number]]) -(expression/register-type-of-first-arg :median) +(lib.hierarchy/derive :median :lib.type-of/type-is-type-of-first-arg) (mbql-clause/define-tuple-mbql-clause :min [:schema [:ref ::expression/number]]) -(expression/register-type-of-first-arg :min) +(lib.hierarchy/derive :min :lib.type-of/type-is-type-of-first-arg) (mbql-clause/define-tuple-mbql-clause :percentile #_expr [:schema [:ref ::expression/number]] #_percentile [:schema [:ref ::expression/non-integer-real]]) -(expression/register-type-of-first-arg :percentile) +(lib.hierarchy/derive :percentile :lib.type-of/type-is-type-of-first-arg) (mbql-clause/define-tuple-mbql-clause :share :- :type/Float [:schema [:ref ::expression/boolean]]) @@ -47,13 +48,13 @@ (mbql-clause/define-tuple-mbql-clause :sum [:schema [:ref ::expression/number]]) -(expression/register-type-of-first-arg :sum) +(lib.hierarchy/derive :sum :lib.type-of/type-is-type-of-first-arg) (mbql-clause/define-tuple-mbql-clause :sum-where [:schema [:ref ::expression/number]] [:schema [:ref ::expression/boolean]]) -(expression/register-type-of-first-arg :sum-where) +(lib.hierarchy/derive :sum-where :lib.type-of/type-is-type-of-first-arg) (mr/def ::aggregation ;; placeholder! diff --git a/src/metabase/lib/schema/expression.cljc b/src/metabase/lib/schema/expression.cljc index 428d7798348efef0f4c44899dc4cf9fe2546365a..f61067a6e864977318803eec4b5dfc86a512c0bd 100644 --- a/src/metabase/lib/schema/expression.cljc +++ b/src/metabase/lib/schema/expression.cljc @@ -1,6 +1,7 @@ (ns metabase.lib.schema.expression (:require [metabase.lib.dispatch :as lib.dispatch] + [metabase.lib.hierarchy :as lib.hierarchy] [metabase.lib.schema.common :as common] [metabase.shared.util.i18n :as i18n] [metabase.types] @@ -10,6 +11,7 @@ (comment metabase.types/keep-me) +;;; TODO -- rename to `type-of-method` (defmulti type-of* "Impl for [[type-of]]. Use [[type-of]], but implement [[type-of*]]. @@ -25,14 +27,10 @@ ;; can implement support for some platform-specific classes like `BigDecimal` or `java.time.OffsetDateTime`, for ;; use inside QP code or whatever. In the future maybe we can add support for JS-specific stuff too. (let [dispatch-value (lib.dispatch/dispatch-value x)] - (if (= dispatch-value :type/*) + (if (= dispatch-value :dispatch-type/*) (type x) - dispatch-value)))) - -(defmethod type-of* :default - [expr] - (throw (ex-info (i18n/tru "Don''t know how to determine the base type of {0}" (pr-str expr)) - {:expr expr}))) + dispatch-value))) + :hierarchy lib.hierarchy/hierarchy) (defn- mbql-clause? [expr] (and (vector? expr) @@ -57,6 +55,17 @@ (:base-type (second expr))) (type-of* expr))) +(defmethod type-of* :default + [expr] + (throw (ex-info (i18n/tru "Don''t know how to determine the type of {0}" (pr-str expr)) + {:expr expr}))) + +;;; for MBQL clauses whose type is the same as the type of the first arg. Also used +;;; for [[metabase.lib.metadata.calculation/type-of-method]]. +(defmethod type-of* :lib.type-of/type-is-type-of-first-arg + [[_tag _opts expr]] + (type-of expr)) + (defn- is-type? [x y] (cond (set? x) (some #(is-type? % y) x) @@ -72,13 +81,6 @@ (i18n/tru "type-of {0} returned an invalid type {1}" (pr-str expr) (pr-str expr-type))) (is-type? expr-type base-type))) -#?(:clj - (defmacro register-type-of-first-arg - "Registers [[tag]] with [[type-of*]] in terms of its first incoming [[expr]]. - Useful for clauses that are polymorphic on their argument." - [tag] - `(defmethod type-of* ~tag [[_tag# _opts# expr#]] (type-of expr#)))) - (defn- expression-schema "Schema that matches the following rules: @@ -92,7 +94,9 @@ [:and [:or [:fn - {:error/message "shaped like an MBQL clause"} + {:error/message "valid MBQL clause" + :error/fn (fn [{:keys [value]} _] + (str "invalid MBQL clause: " (pr-str value)))} (complement mbql-clause?)] [:ref :metabase.lib.schema.mbql-clause/clause]] [:fn @@ -149,3 +153,7 @@ {:min 1, :error/message ":expressions definition map of expression name -> expression"} ::common/non-blank-string ::expression]) + +(defmethod type-of* :lib/external-op + [{:keys [operator options args] :or {options {}}}] + (type-of (into [(keyword operator) options] args))) diff --git a/src/metabase/lib/schema/expression/arithmetic.cljc b/src/metabase/lib/schema/expression/arithmetic.cljc index 4869e8dff9cc0245fcccf70e6171552c30ed24e7..b711f01784117405f6817b6a5fccf9d12217d7b4 100644 --- a/src/metabase/lib/schema/expression/arithmetic.cljc +++ b/src/metabase/lib/schema/expression/arithmetic.cljc @@ -2,6 +2,7 @@ "Arithmetic expressions like `:+`." (:require [malli.core :as mc] + [metabase.lib.hierarchy :as lib.hierarchy] [metabase.lib.schema.common :as common] [metabase.lib.schema.expression :as expression] [metabase.lib.schema.mbql-clause :as mbql-clause] @@ -73,21 +74,18 @@ (mbql-clause/define-catn-mbql-clause :/ :- :type/Float [:args ::args.numbers]) -(defmethod expression/type-of* :+ - [[_tag _opts & args]] - (type-of-arithmetic-args args)) - -(defmethod expression/type-of* :- - [[_tag _opts & args]] - (type-of-arithmetic-args args)) +(doseq [tag [:+ :- :*]] + (lib.hierarchy/derive tag :lib.type-of/type-is-type-of-arithmetic-args)) -(defmethod expression/type-of* :* +;;; `:+`, `:-`, and `:*` all have the same logic; also used for [[metabase.lib.metadata.calculation/type-of-method]] +(defmethod expression/type-of* :lib.type-of/type-is-type-of-arithmetic-args [[_tag _opts & args]] (type-of-arithmetic-args args)) (mbql-clause/define-tuple-mbql-clause :abs [:schema [:ref ::expression/number]]) -(expression/register-type-of-first-arg :abs) + +(lib.hierarchy/derive :abs :lib.type-of/type-is-type-of-first-arg) (doseq [op [:log :exp :sqrt]] (mbql-clause/define-tuple-mbql-clause op :- :type/Float diff --git a/src/metabase/lib/schema/expression/string.cljc b/src/metabase/lib/schema/expression/string.cljc index 248c15dcd063143e59b22dedaad3ba5131026769..60bfb300d4e33f6d22551a9ae59f0030f58f5840 100644 --- a/src/metabase/lib/schema/expression/string.cljc +++ b/src/metabase/lib/schema/expression/string.cljc @@ -26,4 +26,4 @@ #_end [:schema [:ref ::expression/integer]]) (mbql-clause/define-catn-mbql-clause :concat :- :type/Text - [:sequential {:min 2} [:schema [:ref ::expression/string]]]) + [:args [:repeat {:min 2} [:schema [:ref ::expression/string]]]]) diff --git a/src/metabase/lib/schema/expression/temporal.cljc b/src/metabase/lib/schema/expression/temporal.cljc index 6c6f67164ca4f0dc5771a28cc7aa6b6dba0783c0..68003029cb3112e2fdf2024c22baa21a79402bd1 100644 --- a/src/metabase/lib/schema/expression/temporal.cljc +++ b/src/metabase/lib/schema/expression/temporal.cljc @@ -1,5 +1,6 @@ (ns metabase.lib.schema.expression.temporal (:require + [metabase.lib.hierarchy :as lib.hierarchy] [metabase.lib.schema.expression :as expression] [metabase.lib.schema.mbql-clause :as mbql-clause] [metabase.lib.schema.temporal-bucketing :as temporal-bucketing] @@ -20,8 +21,7 @@ #_expr [:ref ::expression/temporal] #_amount :int #_unit [:ref ::temporal-bucketing/unit.date-time.interval]) - - (expression/register-type-of-first-arg op)) + (lib.hierarchy/derive op :lib.type-of/type-is-type-of-first-arg)) (doseq [op [:get-year :get-month :get-day :get-hour :get-minute :get-second :get-quarter]] (mbql-clause/define-tuple-mbql-clause op :- :type/Integer @@ -44,7 +44,7 @@ #_:target [:string] #_:source [:maybe [:string]]) -(expression/register-type-of-first-arg :convert-timezone) +(lib.hierarchy/derive :convert-timezone :lib.type-of/type-is-type-of-first-arg) (mbql-clause/define-tuple-mbql-clause :now :- :type/DateTimeWithTZ) diff --git a/src/metabase/lib/schema/order_by.cljc b/src/metabase/lib/schema/order_by.cljc index 18e697aea1926f118e3d51fcdca0b186fa218a0f..143ede6ac37a100cd5923a5f96290c0ee35f0580 100644 --- a/src/metabase/lib/schema/order_by.cljc +++ b/src/metabase/lib/schema/order_by.cljc @@ -1,30 +1,27 @@ (ns metabase.lib.schema.order-by "Schemas for order-by clauses." (:require - [metabase.lib.schema.common :as common] [metabase.lib.schema.expression :as expression] + [metabase.lib.schema.mbql-clause :as mbql-clause] [metabase.util.malli.registry :as mr])) (mr/def ::direction [:enum :asc :desc]) -(mr/def ::asc - [:catn - [:direction [:= :asc]] - [:options ::common/options] - [:expression ::expression/orderable]]) +(mbql-clause/define-tuple-mbql-clause :asc + [:ref ::expression/orderable]) -(mr/def ::desc - [:catn - [:direction [:= :desc]] - [:options ::common/options] - [:expression ::expression/orderable]]) +(mbql-clause/define-tuple-mbql-clause :desc + [:ref ::expression/orderable]) (mr/def ::order-by - [:or - ::asc - ::desc]) + [:and + [:ref ::mbql-clause/clause] + [:fn + {:error/message ":asc or :desc clause"} + (fn [[tag]] + (#{:asc :desc} tag))]]) ;;; TODO -- should there be a no-duplicates constraint here? (mr/def ::order-bys - [:sequential ::order-by]) + [:sequential {:min 1} [:ref ::order-by]]) diff --git a/src/metabase/lib/schema/temporal_bucketing.cljc b/src/metabase/lib/schema/temporal_bucketing.cljc index b51d7b4c5d3b6d51a4f9778701b3e9d932f1bfab..73d5ea324204d58bdb949810226c9c01406c66d9 100644 --- a/src/metabase/lib/schema/temporal_bucketing.cljc +++ b/src/metabase/lib/schema/temporal_bucketing.cljc @@ -39,6 +39,11 @@ ::unit.time.extract ::unit.time.truncate]) +(mr/def ::unit.date-time + [:or + ::unit.date + ::unit.time]) + (mr/def ::unit [:or [:= :default] diff --git a/src/metabase/lib/segment.cljc b/src/metabase/lib/segment.cljc index e77cf376ec580d49368fa79e9127018ab9426af4..e74bada9fac1d8363109faab23392bd181a35e33 100644 --- a/src/metabase/lib/segment.cljc +++ b/src/metabase/lib/segment.cljc @@ -1,4 +1,5 @@ (ns metabase.lib.segment + "A Metric is a saved MBQL query stage snippet with `:filter`. Segments are always boolean expressions." (:require [metabase.lib.metadata :as lib.metadata] [metabase.lib.metadata.calculation :as lib.metadata.calculation] diff --git a/src/metabase/lib/temporal_bucket.cljc b/src/metabase/lib/temporal_bucket.cljc index 37f304a705e7a8e4344791fd40f71089342d07a5..23364600d300f23b38feab029d94722aab73ac52 100644 --- a/src/metabase/lib/temporal_bucket.cljc +++ b/src/metabase/lib/temporal_bucket.cljc @@ -1,6 +1,7 @@ (ns metabase.lib.temporal-bucket (:require [metabase.lib.dispatch :as lib.dispatch] + [metabase.lib.hierarchy :as lib.hierarchy] [metabase.lib.schema.common :as lib.schema.common] [metabase.lib.schema.temporal-bucketing :as lib.schema.temporal-bucketing] @@ -41,7 +42,8 @@ particular MBQL clause." {:arglists '([x unit])} (fn [x _unit] - (lib.dispatch/dispatch-value x))) + (lib.dispatch/dispatch-value x)) + :hierarchy lib.hierarchy/hierarchy) (defmethod temporal-bucket-method :dispatch-type/fn [f unit] diff --git a/test/metabase/lib/aggregation_test.cljc b/test/metabase/lib/aggregation_test.cljc index b9c313aa026cde7d97348930661b7f5c31b746e1..449eb278e501f15b000a2646e9486875286175d7 100644 --- a/test/metabase/lib/aggregation_test.cljc +++ b/test/metabase/lib/aggregation_test.cljc @@ -6,6 +6,7 @@ [metabase.lib.metadata :as lib.metadata] [metabase.lib.metadata.calculation :as lib.metadata.calculation] [metabase.lib.query :as lib.query] + [metabase.lib.schema.expression :as lib.schema.expression] [metabase.lib.test-metadata :as meta] [metabase.lib.test-util :as lib.tu] #?@(:cljs ([metabase.test-runner.assert-exprs.approximately-equal])))) @@ -194,3 +195,60 @@ (lib/aggregate {:operator :sum :args [(lib/ref (lib.metadata/field q nil "VENUES" "CATEGORY_ID"))]}) (dissoc :lib/metadata))))))) + +(deftest ^:parallel type-of-sum-test + (is (= :type/BigInteger + (lib.metadata.calculation/type-of + lib.tu/venues-query + [:sum + {:lib/uuid (str (random-uuid))} + [:field {:lib/uuid (str (random-uuid))} (meta/id :venues :id)]])))) + +(deftest ^:parallel type-of-test + (testing "Make sure we can calculate correct type information for an aggregation clause like" + (doseq [tag [:max + :median + :percentile + :sum + :sum-where] + arg (let [field [:field {:lib/uuid (str (random-uuid))} (meta/id :venues :id)]] + [field + [:+ {:lib/uuid (str (random-uuid))} field 1] + [:- {:lib/uuid (str (random-uuid))} field 1] + [:* {:lib/uuid (str (random-uuid))} field 1]]) + :let [clause [tag + {:lib/uuid (str (random-uuid))} + arg]]] + (testing (str \newline (pr-str clause)) + (is (= (condp = (first arg) + :field :metabase.lib.schema.expression/type.unknown + :type/*) + (lib.schema.expression/type-of clause))) + (is (= (condp = (first arg) + :field :type/BigInteger + :type/Integer) + (lib.metadata.calculation/type-of lib.tu/venues-query clause))))))) + +(deftest ^:parallel expression-ref-inside-aggregation-type-of-test + (let [query (-> (lib/query-for-table-name meta/metadata-provider "VENUES") + (lib/expression "double-price" (lib/* (lib/field (meta/id :venues :price)) 2)) + (lib/aggregate (lib/sum [:expression {:lib/uuid (str (random-uuid))} "double-price"])))] + (is (=? [{:lib/type :metadata/field + :base_type :type/Integer + :name "sum_double-price" + :display_name "Sum of double-price"}] + (lib/aggregations query))) + (is (= :type/Integer + (lib/type-of query (first (lib/aggregations query))))))) + +(deftest ^:parallel preserve-field-settings-metadata-test + (testing "Aggregation metadata should return the `:settings` for the field being aggregated, for some reason." + (let [query (-> (lib/query-for-table-name meta/metadata-provider "VENUES") + (lib/aggregate (lib/sum (lib/field (meta/id :venues :price)))))] + (is (=? {:settings {:is_priceless true} + :lib/type :metadata/field + :base_type :type/Integer + :name "sum_price" + :display_name "Sum of Price" + :lib/source :source/aggregations} + (lib.metadata.calculation/metadata query (first (lib/aggregations query -1)))))))) diff --git a/test/metabase/lib/convert_test.cljc b/test/metabase/lib/convert_test.cljc index 1671e91b7ed3b64ba15a9b671e4ae50280017ac4..b6f5497987e86632cc1b509918da4723623dd210 100644 --- a/test/metabase/lib/convert_test.cljc +++ b/test/metabase/lib/convert_test.cljc @@ -141,4 +141,8 @@ :metabase.query-processor.util.add-alias-info/position 1 :metabase.query-processor.util.add-alias-info/source-alias "avg"}]] :source-table 224} - :type :query})) + :type :query} + + [:value nil {:base_type :type/Number}] + + [:case [[[:< [:field 1 nil] 10] [:value nil {:base_type :type/Number}]] [[:> [:field 2 nil] 2] 10]]])) diff --git a/test/metabase/lib/expression_test.cljc b/test/metabase/lib/expression_test.cljc index 6be081bec9465889bb9951c60ab5255aa48fafa5..d30c00c02e492f7ff398f4a7bb52a6ba1dfee126 100644 --- a/test/metabase/lib/expression_test.cljc +++ b/test/metabase/lib/expression_test.cljc @@ -1,6 +1,5 @@ (ns metabase.lib.expression-test (:require - #?@(:cljs ([metabase.test-runner.assert-exprs.approximately-equal])) [clojure.test :refer [deftest is testing]] [malli.core :as mc] [metabase.lib.core :as lib] @@ -8,21 +7,22 @@ [metabase.lib.metadata.calculation :as lib.metadata.calculation] [metabase.lib.schema :as lib.schema] [metabase.lib.schema.common :as schema.common] - [metabase.lib.schema.expression :as schema.expression] + [metabase.lib.schema.expression :as lib.schema.expression] [metabase.lib.test-metadata :as meta] - [metabase.lib.test-util :as lib.tu])) + [metabase.lib.test-util :as lib.tu] + #?@(:cljs ([metabase.test-runner.assert-exprs.approximately-equal])))) (comment lib/keep-me) #?(:cljs (comment metabase.test-runner.assert-exprs.approximately-equal/keep-me)) (deftest ^:parallel expression-test - (is (=? {:lib/type :mbql/query, - :database (meta/id) , - :type :pipeline, - :stages [{:lib/type :mbql.stage/mbql, - :source-table (meta/id :venues) , - :lib/options {:lib/uuid string?}, + (is (=? {:lib/type :mbql/query + :database (meta/id) + :type :pipeline + :stages [{:lib/type :mbql.stage/mbql + :source-table (meta/id :venues) + :lib/options {:lib/uuid string?} :expressions {"myadd" [:+ {:lib/uuid string?} 1 [:field {:base-type :type/Integer, :lib/uuid string?} (meta/id :venues :category-id)]]}}]} @@ -84,7 +84,7 @@ resolved (lib.expression/resolve-expression query 0 "myexpr")] (is (mc/validate ::lib.schema/query query)) (is (mc/validate ::schema.common/external-op resolved)) - (is (= typ (schema.expression/type-of resolved)))))))) + (is (= typ (lib.schema.expression/type-of resolved)))))))) (deftest ^:parallel col-info-expression-ref-test (is (=? {:base_type :type/Integer @@ -221,6 +221,29 @@ -1 [:expression {:lib/uuid (str (random-uuid))} "double-price"]))))) +(deftest ^:parallel arithmetic-expression-type-of-test + (testing "Make sure we can calculate correct type information for arithmetic expression" + (let [field [:field {:lib/uuid (str (random-uuid))} (meta/id :venues :id)]] + (testing "+, -, and * should return common ancestor type of all args") + (doseq [tag [:+ :- :*] + arg-2 [1 1.0] + :let [clause [tag {:lib/uuid (str (random-uuid))} field arg-2]]] + (testing (str \newline (pr-str clause)) + (is (= :type/* + (lib.schema.expression/type-of clause))) + (is (= (condp = arg-2 + 1 :type/Integer + 1.0 :type/Number) + (lib.metadata.calculation/type-of lib.tu/venues-query clause))))) + (testing "/ should always return type/Float" + (doseq [arg-2 [1 1.0] + :let [clause [:/ {:lib/uuid (str (random-uuid))} field arg-2]]] + (testing (str \newline (pr-str clause)) + (is (= :type/Float + (lib.schema.expression/type-of clause))) + (is (= :type/Float + (lib.metadata.calculation/type-of lib.tu/venues-query clause))))))))) + (deftest ^:parallel expressions-names-test (testing "expressions should include the original expression name" (is (=? [{:name "expr" diff --git a/test/metabase/lib/field_test.cljc b/test/metabase/lib/field_test.cljc index d653d60b88988c6e92ea9c42222510910bcb72fe..edf344a5be8b83d893de9b7f3a5baec7d95c3d39 100644 --- a/test/metabase/lib/field_test.cljc +++ b/test/metabase/lib/field_test.cljc @@ -4,6 +4,7 @@ [metabase.lib.core :as lib] [metabase.lib.metadata :as lib.metadata] [metabase.lib.metadata.calculation :as lib.metadata.calculation] + [metabase.lib.schema.expression :as lib.schema.expression] [metabase.lib.temporal-bucket :as lib.temporal-bucket] [metabase.lib.test-metadata :as meta] [metabase.lib.test-util :as lib.tu] @@ -149,3 +150,11 @@ (lib.temporal-bucket/current-temporal-bucket (lib.metadata.calculation/metadata query -1 field)))) (is (= "Date (year)" (lib.metadata.calculation/display-name query -1 field)))))) + +(deftest ^:parallel field-ref-type-of-test + (testing "Make sure we can calculate field ref type information correctly" + (let [clause [:field {:lib/uuid (str (random-uuid))} (meta/id :venues :id)]] + (is (= ::lib.schema.expression/type.unknown + (lib.schema.expression/type-of clause))) + (is (= :type/BigInteger + (lib.metadata.calculation/type-of lib.tu/venues-query clause)))))) diff --git a/test/metabase/lib/metric_test.cljc b/test/metabase/lib/metric_test.cljc index 42e03d44f688e2af86a0a5d837e54c737a5d5b01..990fe4a9237064cb55b79dd9fe8da77ce7e5dccb 100644 --- a/test/metabase/lib/metric_test.cljc +++ b/test/metabase/lib/metric_test.cljc @@ -1,21 +1,44 @@ (ns metabase.lib.metric-test (:require - [clojure.test :refer [deftest is]] + [clojure.test :refer [deftest is testing]] [metabase.lib.core :as lib] [metabase.lib.metadata.calculation :as lib.metadata.calculation] [metabase.lib.test-metadata :as meta] - [metabase.lib.test-util :as lib.tu])) + [metabase.lib.test-util :as lib.tu] + #?@(:cljs ([metabase.test-runner.assert-exprs.approximately-equal])))) + +#?(:cljs (comment metabase.test-runner.assert-exprs.approximately-equal/keep-me)) + +(defn- mock-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 {:aggregation [[:sum [:field (meta/id :venues :price) nil]]] + :filter [:= [:field (meta/id :venues :price) nil] 4]}}]})) (deftest ^:parallel metric-display-name-test - (let [metadata (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 {:database (meta/id) - :query {:filter [:= [:field (meta/id :venues :price) nil] 4]}}}]}) + (let [metadata (mock-metadata-provider) query (-> (lib/query-for-table-name 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-for-table-name 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]))))) + +(deftest ^:parallel ga-metric-metadata-test + (testing "Make sure we can calculate metadata for FAKE Google Analytics metric clauses" + (let [query (-> (lib/query-for-table-name meta/metadata-provider "VENUES") + (lib/aggregate [:metric {:lib/uuid (str (random-uuid))} "ga:totalEvents"]))] + (is (=? [{:lib/type :metadata/field + :name "metric" + :display_name "[Unknown Metric]" + :base_type :type/*}] + (lib.metadata.calculation/metadata query -1 query)))))) diff --git a/test/metabase/lib/schema/expression/string_test.cljc b/test/metabase/lib/schema/expression/string_test.cljc new file mode 100644 index 0000000000000000000000000000000000000000..327194b13db1e5102a162c44e7e468550b615a7e --- /dev/null +++ b/test/metabase/lib/schema/expression/string_test.cljc @@ -0,0 +1,18 @@ +(ns metabase.lib.schema.expression.string-test + (:require + [clojure.test :refer [are deftest]] + [malli.core :as mc] + [malli.error :as me] + [metabase.lib.schema])) + +(comment metabase.lib.schema/keep-me) + +(deftest ^:parallel concat-test + (are [clause] (not (me/humanize (mc/explain :mbql.clause/concat clause))) + [:concat {:lib/uuid "47cac41f-6240-4623-9a73-448addfdc735"} "1" "2"] + [:concat {:lib/uuid "47cac41f-6240-4623-9a73-448addfdc735"} "1" "2" "3"] + + [:concat + {:lib/uuid "47cac41f-6240-4623-9a73-448addfdc735"} + [:field {:lib/uuid "e47d33bc-c89c-48af-bffe-842c815f930b"} 1] + "2"])) diff --git a/test/metabase/lib/schema/literal/jvm_test.clj b/test/metabase/lib/schema/literal/jvm_test.clj new file mode 100644 index 0000000000000000000000000000000000000000..f5acd09b1fe2e50b06a8cb805c7e34ef0322e134 --- /dev/null +++ b/test/metabase/lib/schema/literal/jvm_test.clj @@ -0,0 +1,11 @@ +(ns metabase.lib.schema.literal.jvm-test + (:require + [clojure.test :refer [deftest is]] + [metabase.lib.schema] + [metabase.lib.schema.expression :as expression])) + +(comment metabase.lib.schema/keep-me) + +(deftest ^:parallel temporal-literal-type-of-test + (is (= :type/DateTimeWithZoneID + (expression/type-of* #t "2019-01-01T00:00Z[UTC]")))) diff --git a/test/metabase/lib/test_metadata.cljc b/test/metabase/lib/test_metadata.cljc index 451dd6d07c854cd00edd5af9f5a7145be573f43c..3ed284f6a8b6f1c9ef1bba2233cdd0c2a5fc5390 100644 --- a/test/metabase/lib/test_metadata.cljc +++ b/test/metabase/lib/test_metadata.cljc @@ -677,7 +677,7 @@ :name "PRICE" :fingerprint_version 5 :has_field_values :list - :settings nil + :settings {:is_priceless true} :caveats nil :fk_target_field_id nil :custom_position 0 diff --git a/test/metabase/query_processor/middleware/annotate_test.clj b/test/metabase/query_processor/middleware/annotate_test.clj index 111e1c4b9fb1e7bb40bc6afaf992517b0422fab9..8ca414cd3a8045418c5cab3eafc7f6ef5ee04c07 100644 --- a/test/metabase/query_processor/middleware/annotate_test.clj +++ b/test/metabase/query_processor/middleware/annotate_test.clj @@ -23,7 +23,7 @@ ;;; | column-info (:native) | ;;; +----------------------------------------------------------------------------------------------------------------+ -(deftest native-column-info-test +(deftest ^:parallel native-column-info-test (testing "native column info" (testing "should still infer types even if the initial value(s) are `nil` (#4256, #6924)" (is (= [:type/Integer] @@ -62,7 +62,7 @@ ([table-key field-key] (info-for-field (mt/id table-key field-key)))) -(deftest col-info-field-ids-test +(deftest ^:parallel col-info-field-ids-test (testing {:base-type "make sure columns are comming back the way we'd expect for :field clauses"} (mt/with-everything-store (mt/$ids venues @@ -74,7 +74,7 @@ {:type :query, :query {:fields [$price]}} {:columns [:price]})))))))) -(deftest col-info-for-fks-and-joins-test +(deftest ^:parallel col-info-for-fks-and-joins-test (mt/with-everything-store (mt/$ids venues (testing (str "when a `:field` with `:source-field` (implicit join) is used, we should add in `:fk_field_id` " @@ -125,7 +125,7 @@ :strategy :left-join}]}} {:columns [:name]}))))))))) -(deftest col-info-for-field-with-temporal-unit-test +(deftest ^:parallel col-info-for-field-with-temporal-unit-test (mt/with-everything-store (mt/$ids venues (testing "when a `:field` with `:temporal-unit` is used, we should add in info about the `:unit`" @@ -181,7 +181,7 @@ :limit 1}} nil)))))))) -(deftest col-info-for-binning-strategy-test +(deftest ^:parallel col-info-for-binning-strategy-test (testing "when binning strategy is used, include `:binning_info`" (is (= [{:name "price" :base_type :type/Number @@ -196,17 +196,16 @@ :bin-width 5 :min-value -100 :max-value 100}}]}] - (doall - (annotate/column-info - {:type :query - :query {:fields [[:field "price" {:base-type :type/Number - :temporal-unit :month - :binning {:strategy :num-bins - :num-bins 10 - :bin-width 5 - :min-value -100 - :max-value 100}}]]}} - {:columns [:price]})))))) + (annotate/column-info + {:type :query + :query {:fields [[:field "price" {:base-type :type/Number + :temporal-unit :month + :binning {:strategy :num-bins + :num-bins 10 + :bin-width 5 + :min-value -100 + :max-value 100}}]]}} + {:columns [:price]}))))) (deftest col-info-combine-parent-field-names-test (testing "For fields with parents we should return them with a combined name including parent's name" @@ -255,7 +254,7 @@ :base_type :type/Text} (into {} (#'annotate/col-info-for-field-clause {} [:field (u/the-id child) nil])))))))) -(deftest col-info-field-literals-test +(deftest ^:parallel col-info-field-literals-test (testing "field literals should get the information from the matching `:source-metadata` if it was supplied" (mt/with-everything-store (is (= {:name "sum" @@ -269,7 +268,7 @@ {:name "sum", :display_name "sum of User ID", :base_type :type/Integer, :semantic_type :type/FK}]} [:field "sum" {:base-type :type/Integer}])))))) -(deftest col-info-expressions-test +(deftest ^:parallel col-info-expressions-test (mt/with-everything-store (testing "col info for an `expression` should work as expected" (is (= {:base_type :type/Float @@ -331,7 +330,7 @@ {:name (annotate/aggregation-name ag-clause) :display_name (annotate/aggregation-display-name inner-query ag-clause)})))) -(deftest aggregation-names-test +(deftest ^:parallel aggregation-names-test (testing "basic aggregations" (testing ":count" (is (= {:name "count", :display_name "Count"} @@ -398,7 +397,7 @@ (binding [driver/*driver* :h2] (#'annotate/col-info-for-aggregation-clause inner-query clause)))) -(deftest col-info-for-aggregation-clause-test +(deftest ^:parallel col-info-for-aggregation-clause-test (mt/with-everything-store (testing "basic aggregation clauses" (testing "`:count` (no field)" @@ -472,7 +471,7 @@ first (select-keys [:base_type :semantic_type]))) -(deftest computed-columns-inference +(deftest ^:parallel computed-columns-inference (letfn [(infer [expr] (-> (mt/mbql-query venues {:expressions {"expr" expr} :fields [[:expression "expr"]] @@ -541,7 +540,7 @@ :year]]))) (is (not (#'annotate/datetime-arithmetics? [:+ [:field (mt/id :checkins :date) nil] 3])))) -(deftest temporal-extract-test +(deftest ^:parallel temporal-extract-test (is (= {:base_type :type/DateTime} (infered-col-type [:datetime-add [:field (mt/id :checkins :date) nil] 2 :month]))) (is (= {:base_type :type/DateTime} @@ -549,7 +548,7 @@ (is (= {:base_type :type/DateTime} (infered-col-type [:datetime-add [:field (mt/id :users :last_login) nil] 2 :month])))) -(deftest test-string-extracts +(deftest ^:parallel test-string-extracts (is (= {:base_type :type/Text} (infered-col-type [:trim "foo"]))) (is (= {:base_type :type/Text} @@ -576,7 +575,7 @@ :semantic_type :type/Name} (infered-col-type [:coalesce [:field (mt/id :venues :name) nil] "bar"])))) -(deftest unique-name-key-test +(deftest ^:parallel unique-name-key-test (testing "Make sure `:cols` always come back with a unique `:name` key (#8759)" (is (= {:cols [{:base_type :type/Number @@ -617,7 +616,7 @@ {:name "count", :display_name "count", :base_type :type/Number} {:name "count_2", :display_name "count_2", :base_type :type/Number}]}))))) -(deftest expressions-keys-test +(deftest ^:parallel expressions-keys-test (testing "make sure expressions come back with the right set of keys, including `:expression_name` (#8854)" (is (= {:name "discount_price" :display_name "discount_price" @@ -634,7 +633,7 @@ :cols second))))) -(deftest deduplicate-expression-names-test +(deftest ^:parallel deduplicate-expression-names-test (testing "make sure multiple expressions come back with deduplicated names" (testing "expressions in aggregations" (is (= [{:base_type :type/Float, :name "expression", :display_name "0.9 * Average of Price", :source :aggregation, :field_ref [:aggregation 0]} @@ -712,7 +711,7 @@ :cols (map :display_name))))))))) -(deftest inception-test +(deftest ^:parallel inception-test (testing "Should return correct metadata for an 'inception-style' nesting of source > source > source with a join (#14745)" (mt/dataset sample-dataset ;; these tests look at the metadata for just one column so it's easier to spot the differences. @@ -798,7 +797,7 @@ :columns second :display_name)) "Results metadata cols has wrong display name")))))) -(deftest preserve-original-join-alias-test +(deftest ^:parallel preserve-original-join-alias-test (testing "The join alias for the `:field_ref` in results metadata should match the one originally specified (#27464)" (mt/test-drivers (mt/normal-drivers-with-feature :left-join) (mt/dataset sample-dataset