(ns metabase.lib.breakout (:require [clojure.string :as str] [metabase.lib.binning :as lib.binning] [metabase.lib.equality :as lib.equality] [metabase.lib.metadata.calculation :as lib.metadata.calculation] [metabase.lib.ref :as lib.ref] [metabase.lib.remove-replace :as lib.remove-replace] [metabase.lib.schema :as lib.schema] [metabase.lib.schema.expression :as lib.schema.expression] [metabase.lib.schema.metadata :as lib.schema.metadata] [metabase.lib.schema.ref :as lib.schema.ref] [metabase.lib.schema.util :as lib.schema.util] [metabase.lib.temporal-bucket :as lib.temporal-bucket] [metabase.lib.util :as lib.util] [metabase.util.i18n :as i18n] [metabase.util.malli :as mu])) | |
(defmethod lib.metadata.calculation/describe-top-level-key-method :breakout
[query stage-number _k]
(when-let [breakouts (not-empty (:breakout (lib.util/query-stage query stage-number)))]
(i18n/tru "Grouped by {0}"
(str/join (str \space (i18n/tru "and") \space)
(for [breakout breakouts]
(lib.metadata.calculation/display-name query stage-number breakout :long)))))) | |
(mu/defn breakouts :- [:maybe [:sequential ::lib.schema.expression/expression]]
"Return the current breakouts"
([query]
(breakouts query -1))
([query :- ::lib.schema/query
stage-number :- :int]
(not-empty (:breakout (lib.util/query-stage query stage-number))))) | |
(mu/defn breakouts-metadata :- [:maybe [:sequential ::lib.schema.metadata/column]]
"Get metadata about the breakouts in a given stage of a `query`."
([query]
(breakouts-metadata query -1))
([query :- ::lib.schema/query
stage-number :- :int]
(some->> (breakouts query stage-number)
(mapv (fn [field-ref]
(-> (lib.metadata.calculation/metadata query stage-number field-ref)
(assoc :lib/source :source/breakouts))))))) | |
(mu/defn breakout :- ::lib.schema/query
"Add a new breakout on an expression, presumably a Field reference. Ignores attempts to add a duplicate breakout."
([query expr]
(breakout query -1 expr))
([query :- ::lib.schema/query
stage-number :- :int
expr :- some?]
(let [expr (if (fn? expr) (expr query stage-number) expr)]
(if (lib.schema.util/distinct-refs? (map lib.ref/ref (cons expr (breakouts query stage-number))))
(lib.util/add-summary-clause query stage-number :breakout expr)
query)))) | |
(mu/defn breakoutable-columns :- [:sequential ::lib.schema.metadata/column]
"Get column metadata for all the columns that can be broken out by in
the stage number `stage-number` of the query `query`
If `stage-number` is omitted, the last stage is used.
The rules for determining which columns can be broken out by are as follows:
1. custom `:expressions` in this stage of the query
2. Fields 'exported' by the previous stage of the query, if there is one;
otherwise Fields from the current `:source-table`
3. Fields exported by explicit joins
4. Fields in Tables that are implicitly joinable."
([query :- ::lib.schema/query]
(breakoutable-columns query -1))
([query :- ::lib.schema/query
stage-number :- :int]
(let [columns (let [stage (lib.util/query-stage query stage-number)
options {:include-implicitly-joinable-for-source-card? false}]
(lib.metadata.calculation/visible-columns query stage-number stage options))]
(when (seq columns)
(let [existing-breakouts (breakouts query stage-number)
column->breakout-positions (group-by
(fn [position]
(lib.equality/find-matching-column query
stage-number
(get existing-breakouts position)
columns
{:generous? true}))
(range (count existing-breakouts)))]
(mapv #(let [positions (column->breakout-positions %)]
(cond-> (assoc % :lib/hide-bin-bucket? true)
positions (assoc :breakout-positions positions)))
columns)))))) | |
(mu/defn existing-breakouts :- [:maybe [:sequential {:min 1} ::lib.schema.ref/ref]]
"Returns existing breakouts (as MBQL expressions) for `column` in a stage if there are any. Returns `nil` if there
are no existing breakouts."
([query stage-number column]
(existing-breakouts query stage-number column nil))
([query :- ::lib.schema/query
stage-number :- :int
column :- ::lib.schema.metadata/column
{:keys [same-binning-strategy?
same-temporal-bucket?], :as _options} :- [:maybe
[:map
[:same-binning-strategy? {:optional true} [:maybe :boolean]]
[:same-temporal-bucket? {:optional true} [:maybe :boolean]]]]]
(not-empty
(into []
(filter (fn [a-breakout]
(and (lib.equality/find-matching-column query stage-number a-breakout [column])
(or (not same-temporal-bucket?)
(= (lib.temporal-bucket/temporal-bucket a-breakout)
(lib.temporal-bucket/temporal-bucket column)))
(or (not same-binning-strategy?)
(lib.binning/binning= (lib.binning/binning a-breakout)
(lib.binning/binning column))))))
(breakouts query stage-number))))) | |
Returns if | (defn breakout-column? ([query stage-number column] (breakout-column? query stage-number column nil)) ([query stage-number column opts] (seq (existing-breakouts query stage-number column opts)))) |
(mu/defn remove-existing-breakouts-for-column :- ::lib.schema/query
"Remove all existing breakouts against `column` if there are any in the stage in question. Disregards temporal
bucketing and binning."
([query column]
(remove-existing-breakouts-for-column query -1 column))
([query :- ::lib.schema/query
stage-number :- :int
column :- ::lib.schema.metadata/column]
(reduce
(fn [query a-breakout]
(lib.remove-replace/remove-clause query stage-number a-breakout))
query
(existing-breakouts query stage-number column)))) | |
(mu/defn breakout-column :- ::lib.schema.metadata/column
"Returns the input column used for this breakout."
([query :- ::lib.schema/query
breakout-ref :- ::lib.schema.ref/ref]
(breakout-column query -1 breakout-ref))
([query :- ::lib.schema/query
stage-number :- :int
breakout-ref :- ::lib.schema.ref/ref]
(when-let [column (lib.equality/find-matching-column breakout-ref
(breakoutable-columns query stage-number)
{:generous? true})]
(let [binning (lib.binning/binning breakout-ref)
bucket (lib.temporal-bucket/temporal-bucket breakout-ref)]
(cond-> column
binning (lib.binning/with-binning binning)
bucket (lib.temporal-bucket/with-temporal-bucket bucket)))))) | |
(mu/defn remove-all-breakouts :- ::lib.schema/query
"Remove all breakouts from a query stage."
([query]
(remove-all-breakouts query -1))
([query :- ::lib.schema/query
stage-number :- :int]
(reduce
(fn [query a-breakout]
(lib.remove-replace/remove-clause query stage-number a-breakout))
query
(breakouts query stage-number)))) | |