(ns metabase.lib.order-by (:require [metabase.lib.aggregation :as lib.aggregation] [metabase.lib.breakout :as lib.breakout] [metabase.lib.dispatch :as lib.dispatch] [metabase.lib.equality :as lib.equality] [metabase.lib.hierarchy :as lib.hierarchy] [metabase.lib.metadata.calculation :as lib.metadata.calculation] [metabase.lib.options :as lib.options] [metabase.lib.ref :as lib.ref] [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.order-by :as lib.schema.order-by] [metabase.lib.util :as lib.util] [metabase.lib.util.match :as lib.util.match] [metabase.util.i18n :as i18n] [metabase.util.malli :as mu])) | |
(lib.hierarchy/derive :asc ::order-by-clause) (lib.hierarchy/derive :desc ::order-by-clause) | |
(defmethod lib.metadata.calculation/describe-top-level-key-method :order-by
[query stage-number _k]
(when-let [order-bys (not-empty (:order-by (lib.util/query-stage query stage-number)))]
(i18n/tru "Sorted by {0}"
(lib.util/join-strings-with-conjunction
(i18n/tru "and")
(for [order-by order-bys]
(lib.metadata.calculation/display-name query stage-number order-by :long)))))) | |
(defmethod lib.metadata.calculation/display-name-method ::order-by-clause
[query stage-number [tag _opts expr] style]
(let [expr-display-name (lib.metadata.calculation/display-name query stage-number expr style)]
(case tag
:asc (i18n/tru "{0} ascending" expr-display-name)
:desc (i18n/tru "{0} descending" expr-display-name)))) | |
(defmethod lib.metadata.calculation/display-info-method ::order-by-clause
[query stage-number [tag _opts expr]]
(assoc (lib.metadata.calculation/display-info query stage-number expr)
:direction tag)) | |
(defmulti ^:private order-by-clause-method
{:arglists '([orderable])}
lib.dispatch/dispatch-value
:hierarchy lib.hierarchy/hierarchy) | |
(defmethod order-by-clause-method ::order-by-clause [clause] (lib.options/ensure-uuid clause)) | |
by default, try to convert | (defmethod order-by-clause-method :default
[x]
(when (nil? x)
(throw (ex-info (i18n/tru "Can''t order by nil") {})))
(lib.options/ensure-uuid [:asc (lib.ref/ref x)])) |
(mu/defn- with-direction :- ::lib.schema.order-by/order-by "Update the direction of an order by clause." [clause :- ::lib.schema.order-by/order-by direction :- ::lib.schema.order-by/direction] (assoc (vec clause) 0 direction)) | |
Create an order-by clause independently of a query, e.g. for | (mu/defn order-by-clause
([orderable]
(order-by-clause orderable :asc))
([orderable :- some?
direction :- [:maybe [:enum :asc :desc]]]
(-> (order-by-clause-method orderable)
(with-direction (or direction :asc))))) |
Add an MBQL order-by clause (i.e., You can teach Metabase lib how to generate order by clauses for different things by implementing the underlying [[order-by-clause-method]] multimethod. | (mu/defn order-by
([query orderable]
(order-by query -1 orderable nil))
([query orderable direction]
(order-by query -1 orderable direction))
([query
stage-number :- [:maybe :int]
orderable :- some?
direction :- [:maybe [:enum :asc :desc]]]
(let [stage-number (or stage-number -1)
new-order-by (cond-> (order-by-clause-method orderable)
direction (with-direction direction))]
(lib.util/update-query-stage query stage-number update :order-by (fn [order-bys]
(conj (vec order-bys) new-order-by)))))) |
(mu/defn order-bys :- [:maybe ::lib.schema.order-by/order-bys]
"Get the order-by clauses in a query."
([query :- ::lib.schema/query]
(order-bys query -1))
([query :- ::lib.schema/query
stage-number :- :int]
(not-empty (get (lib.util/query-stage query stage-number) :order-by)))) | |
(defn- orderable-column? [{:keys [base-type]}]
(some (fn [orderable-base-type]
(isa? base-type orderable-base-type))
lib.schema.expression/orderable-types)) | |
(mu/defn orderable-columns :- [:maybe [:sequential ::lib.schema.metadata/column]]
"Get column metadata for all the columns you can order by in a given `stage-number` of a `query`. Rules are as
follows:
1. If the stage has aggregations or breakouts, you can only order by those columns. E.g.
SELECT id, count(*) AS count FROM core_user GROUP BY id ORDER BY count ASC;
You can't ORDER BY something not in the SELECT, e.g. ORDER BY user.first_name would not make sense here.
2. If the stage has no aggregations or breakouts, you can order by any visible Field:
a. You can filter by any custom `:expressions` in this stage of the query
b. You can filter by any Field 'exported' by the previous stage of the query, if there is one; otherwise you can
filter by any Fields from the current `:source-table`.
c. You can filter by any Fields exported by any explicit joins
d. You can filter by and Fields in Tables that are implicitly joinable."
([query :- ::lib.schema/query]
(orderable-columns query -1))
([query :- ::lib.schema/query
stage-number :- :int]
(let [breakouts (not-empty (lib.breakout/breakouts-metadata query stage-number))
aggregations (not-empty (lib.aggregation/aggregations-metadata query stage-number))
columns (if (or breakouts aggregations)
(concat breakouts aggregations)
(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)))
columns (filter orderable-column? columns)
existing-order-bys (->> (order-bys query stage-number)
(map (fn [[_tag _opts expr]]
expr)))]
(cond
(empty? columns)
nil
(empty? existing-order-bys)
(vec columns)
:else
(let [matching (into {}
(comp (map lib.ref/ref)
(keep-indexed (fn [index an-order-by]
(when-let [col (lib.equality/find-matching-column
query stage-number an-order-by columns)]
[col index]))))
existing-order-bys)]
(mapv #(let [pos (matching %)]
(cond-> %
pos (assoc :order-by-position pos)))
columns)))))) | |
(def ^:private opposite-direction
{:asc :desc
:desc :asc}) | |
(mu/defn change-direction :- ::lib.schema/query
"Flip the direction of `current-order-by` in `query`."
([query :- ::lib.schema/query
current-order-by :- ::lib.schema.order-by/order-by]
(let [lib-uuid (lib.options/uuid current-order-by)]
(lib.util.match/replace query
[direction (_ :guard #(= (:lib/uuid %) lib-uuid)) _]
(assoc &match 0 (opposite-direction direction)))))) | |
(mu/defn remove-all-order-bys :- ::lib.schema/query
"Remove all order bys from this stage of the query."
([query]
(remove-all-order-bys query -1))
([query :- ::lib.schema/query
stage-number :- :int]
(lib.util/update-query-stage query stage-number dissoc :order-by))) | |