From 12f178e857b52311a11f8a470780b30352550611 Mon Sep 17 00:00:00 2001 From: Cam Saul <cam@getluckybird.com> Date: Thu, 28 May 2015 17:23:04 -0700 Subject: [PATCH] :smiling_imp: allow ordering by aggregate fields in generic SQL QP. --- .dir-locals.el | 4 +- .../driver/generic_sql/query_processor.clj | 17 ++- test/metabase/driver/query_processor_test.clj | 106 +++++++++++++++++- 3 files changed, 118 insertions(+), 9 deletions(-) diff --git a/.dir-locals.el b/.dir-locals.el index adf2f4927b2..430e49a10af 100644 --- a/.dir-locals.el +++ b/.dir-locals.el @@ -22,6 +22,8 @@ (expect-when-testing-against-dataset 1) (expect-when-testing-mongo 1) (expect-with-all-drivers 1) + (expect-with-dataset 1) + (expect-with-datasets 1) (ins 1) (let-400 1) (let-404 1) @@ -31,7 +33,7 @@ (macrolet 1) (org-perms-case 1) (pdoseq 1) - (qp-expect-with-all-drivers 1) + (qp-expect-with-datasets 1) (resolve-private-fns 1) (symbol-macrolet 1) (sync-in-context 2) diff --git a/src/metabase/driver/generic_sql/query_processor.clj b/src/metabase/driver/generic_sql/query_processor.clj index 65a48e470c5..ed2ec072873 100644 --- a/src/metabase/driver/generic_sql/query_processor.clj +++ b/src/metabase/driver/generic_sql/query_processor.clj @@ -177,11 +177,20 @@ (->> order-by-pairs (map (fn [pair] (when-not (vector? pair) (throw (Exception. "order_by clause must consists of pairs like [field_id \"ascending\"]"))) pair)) (mapv (fn [[field-id asc-desc]] - {:pre [(integer? field-id) + {:pre [(or (integer? field-id) + (= field-id "$$aggregation")) (string? asc-desc)]} - `(order ~(field-id->kw field-id) ~(case asc-desc - "ascending" :ASC - "descending" :DESC))))))) + `(order ~(if (integer? field-id) (field-id->kw field-id) + (let [[ag] (:aggregation (:query qp/*query*))] + `(raw ~(case ag + "avg" "\"avg\"" ; based on the type of the aggregation + "count" "\"count\"" ; make sure we ask the DB to order by the + "distinct" "\"count\"" ; name of the aggregate field + "stddev" "\"stddev\"" + "sum" "\"sum\"")))) + ~(case asc-desc + "ascending" :ASC + "descending" :DESC))))))) ;; ### `:page` ;; ex. diff --git a/test/metabase/driver/query_processor_test.clj b/test/metabase/driver/query_processor_test.clj index d6b7205b8eb..00ada5fb23a 100644 --- a/test/metabase/driver/query_processor_test.clj +++ b/test/metabase/driver/query_processor_test.clj @@ -5,7 +5,7 @@ [metabase.driver :as driver] [metabase.driver.query-processor :refer :all] (metabase.models [table :refer [Table]]) - [metabase.test.data.datasets :as datasets :refer [*dataset* expect-with-all-datasets]])) + [metabase.test.data.datasets :as datasets :refer [*dataset*]])) ;; ## Dataset-Independent Data Fns @@ -42,10 +42,26 @@ ;; ### Helper Fns + Macros +(defmacro qp-expect-with-datasets + "Slightly more succinct way of writing QP tests. Adds standard boilerplate to run QP tests against DATASETS." + [datasets {:keys [rows] :as data} query] + {:pre [(set? datasets) + (map? data) + (sequential? rows) + (map? query)]} + `(datasets/expect-with-datasets ~datasets + {:status :completed + :row_count ~(count rows) + :data ~data} + (driver/process-query + {:type :query + :database (db-id) + :query ~query}))) + (defmacro qp-expect-with-all-datasets - "Slightly more succinct way of writing QP tests. Adds standard boilerplate to actual/expected forms." + "Like `qp-expect-with-datasets`, but tests against *all* datasets." [data query] - `(expect-with-all-datasets + `(datasets/expect-with-all-datasets {:status :completed :row_count ~(count (:rows data)) :data ~data} @@ -53,6 +69,7 @@ :database (db-id) :query ~query}))) + (defn ->columns "Generate the vector that should go in the `columns` part of a QP result; done by calling `format-name` against each column name." [& names] @@ -516,7 +533,7 @@ ;; ## EMPTY QUERY ;; Just don't barf -(expect-with-all-datasets +(datasets/expect-with-all-datasets {:status :completed :row_count 0 :data {:rows [], :columns [], :cols []}} @@ -608,3 +625,84 @@ {:source_table (id :venues) :breakout [(id :venues :price)] :aggregation ["cum_sum" (id :venues :id)]}) + + +;;; ## order_by aggregate fields (SQL-only for the time being) + +;;; ### order_by aggregate ["count"] +(qp-expect-with-datasets #{:generic-sql} + {:columns [(format-name "price") + "count"] + :rows [[4 6] + [3 13] + [1 22] + [2 59]] + :cols [(venue-col :price) + {:base_type :IntegerField, :special_type :number, :description nil, :table_id nil, :name "count", :id nil}]} + {:source_table (id :venues) + :aggregation ["count"] + :breakout [(id :venues :price)] + :order_by [["$$aggregation" "ascending"]]}) + + +;;; ### order_by aggregate ["sum" field-id] +(qp-expect-with-datasets #{:generic-nsql} + {:columns [(format-name "price") + "sum"] + :rows [[2 (->sum-type 2855)] + [1 (->sum-type 1211)] + [3 (->sum-type 615)] + [4 (->sum-type 369)]] + :cols [(venue-col :price) + {:base_type (id-field-type), :special_type :id, :name "sum", :id nil, :table_id nil, :description nil}]} + {:source_table (id :venues) + :aggregation ["sum" (id :venues :id)] + :breakout [(id :venues :price)] + :order_by [["$$aggregation" "descending"]]}) + + +;;; ### order_by aggregate ["distinct" field-id] +(qp-expect-with-datasets #{:generic-sql} + {:columns [(format-name "price") + "count"] + :rows [[4 6] + [3 13] + [1 22] + [2 59]] + :cols [(venue-col :price) + {:base_type :IntegerField, :special_type :id, :name "count", :id nil, :table_id nil, :description nil}]} + {:source_table (id :venues) + :aggregation ["distinct" (id :venues :id)] + :breakout [(id :venues :price)] + :order_by [["$$aggregation" "ascending"]]}) + + +;;; ### order_by aggregate ["avg" field-id] +(qp-expect-with-datasets #{:generic-sql} + {:columns [(format-name "price") + "avg"] + :rows [[4 53] + [1 32] + [2 28] + [3 22]] + :cols [(venue-col :price) + {:base_type :IntegerField, :special_type :fk, :name "avg", :id nil, :table_id nil, :description nil}]} + {:source_table (id :venues) + :aggregation ["avg" (id :venues :category_id)] + :breakout [(id :venues :price)] + :order_by [["$$aggregation" "ascending"]]}) + +;;; ### order_by aggregate ["stddev" field-id] +(qp-expect-with-datasets #{:generic-sql} + {:columns [(format-name "price") + "stddev"] + :rows [[3 26.19160170741759] + [1 24.112111881665186] + [2 21.418692164795292] + [4 14.788509052639485]] + :cols [(venue-col :price) + {:base_type :IntegerField, :special_type :fk, :name "stddev", :id nil, :table_id nil, :description nil}]} + {:source_table (id :venues) + :aggregation ["stddev" (id :venues :category_id)] + :breakout [(id :venues :price)] + :order_by [["$$aggregation" "descending"]]}) -- GitLab