diff --git a/src/metabase/query_processor/middleware/annotate.clj b/src/metabase/query_processor/middleware/annotate.clj index 2194441b46dcf54e168e17916ad7ced95db7b2d2..31440e1236739bafa0be0630f7b4fc81107fcb74 100644 --- a/src/metabase/query_processor/middleware/annotate.clj +++ b/src/metabase/query_processor/middleware/annotate.clj @@ -114,41 +114,54 @@ {:base_type :type/Float :special_type :type/Number})) -(s/defn ^:private col-info-for-field-clause :- su/Map +(s/defn ^:private col-info-for-field-clause :- {:field_ref mbql.s/Field, s/Keyword s/Any} [{:keys [source-metadata expressions], :as inner-query} :- su/Map, clause :- mbql.s/Field] ;; for various things that can wrap Field clauses recurse on the wrapped Field but include a little bit of info ;; about the clause doing the wrapping (mbql.u/match-one clause [:binning-strategy field strategy _ resolved-options] - (assoc (col-info-for-field-clause inner-query field) - :binning_info (assoc (u/snake-keys resolved-options) - :binning_strategy strategy)) + (let [recursive-info (col-info-for-field-clause inner-query field)] + (assoc recursive-info + :binning_info (assoc (u/snake-keys resolved-options) + :binning_strategy strategy) + :field_ref (assoc (vec &match) 1 (:field_ref recursive-info)))) [:datetime-field field unit] - (assoc (col-info-for-field-clause inner-query field) :unit unit) - - [:joined-field alias field] - (let [{:keys [fk-field-id], :as join} (join-with-alias inner-query alias)] - (-> (col-info-for-field-clause inner-query field) - (assoc :fk_field_id fk-field-id) - (update :display_name display-name-for-joined-field join))) + (let [recursive-info (col-info-for-field-clause inner-query field)] + (assoc recursive-info + :unit unit + :field_ref (assoc (vec &match) 1 (:field_ref recursive-info)))) + + [:joined-field join-alias field] + (let [{:keys [fk-field-id], :as join} (join-with-alias inner-query join-alias)] + (let [recursive-info (col-info-for-field-clause inner-query field)] + (-> recursive-info + (merge (when fk-field-id {:fk_field_id fk-field-id})) + (assoc :field_ref (if fk-field-id + [:fk-> [:field-id fk-field-id] field] + (assoc (vec &match) 2 (:field_ref recursive-info)))) + (update :display_name display-name-for-joined-field join)))) ;; TODO - should be able to remove this now [:fk-> [:field-id source-field-id] field] - (assoc (col-info-for-field-clause inner-query field) :fk_field_id source-field-id) + (assoc (col-info-for-field-clause inner-query field) + :field_ref &match + :fk_field_id source-field-id) ;; TODO - should be able to remove this now ;; for FKs where source is a :field-literal don't include `:fk_field_id` [:fk-> _ field] - (recur field) + (assoc (col-info-for-field-clause inner-query field) + :field_ref &match) ;; for field literals, look for matching `source-metadata`, and use that if we can find it; otherwise generate ;; basic info based on the content of the field literal [:field-literal field-name field-type] - (or (some #(when (= (:name %) field-name) %) source-metadata) - {:name field-name - :base_type field-type - :display_name (humanization/name->human-readable-name field-name)}) + (assoc (or (some #(when (= (:name %) field-name) %) source-metadata) + {:name field-name + :base_type field-type + :display_name (humanization/name->human-readable-name field-name)}) + :field_ref &match) [:expression expression-name] (merge @@ -158,14 +171,16 @@ {:name expression-name :display_name expression-name ;; provided so the FE can add easily add sorts and the like when someone clicks a column header - :expression_name expression-name}) + :expression_name expression-name + :field_ref &match}) [:field-id id] (let [{parent-id :parent_id, :as field} (dissoc (qp.store/field id) :database_type)] - (if-not parent-id - field - (let [parent (col-info-for-field-clause inner-query [:field-id parent-id])] - (update field :name #(str (:name parent) \. %))))) + (assoc (if-not parent-id + field + (let [parent (col-info-for-field-clause inner-query [:field-id parent-id])] + (update field :name #(str (:name parent) \. %)))) + :field_ref &match)) ;; we should never reach this if our patterns are written right so this is more to catch code mistakes than ;; something the user should expect to see @@ -358,16 +373,14 @@ [{:keys [fields], :as inner-query} :- su/Map] (for [field fields] (assoc (col-info-for-field-clause inner-query field) - :source :fields - :field_ref field))) + :source :fields))) (s/defn ^:private cols-for-ags-and-breakouts [{aggregations :aggregation, breakouts :breakout, :as inner-query} :- su/Map] (concat (for [breakout breakouts] (assoc (col-info-for-field-clause inner-query breakout) - :source :breakout - :field_ref breakout)) + :source :breakout)) (for [[i aggregation] (m/indexed aggregations)] (assoc (col-info-for-aggregation-clause inner-query aggregation) :source :aggregation diff --git a/test/metabase/query_processor/middleware/annotate_test.clj b/test/metabase/query_processor/middleware/annotate_test.clj index 30071e986bdd7859494b1204296040efe5b47a6e..9f97366e3369f40cb357d653f5902092b30c8c8e 100644 --- a/test/metabase/query_processor/middleware/annotate_test.clj +++ b/test/metabase/query_processor/middleware/annotate_test.clj @@ -91,14 +91,14 @@ {:columns [:name]})))) ;; we should get `:fk_field_id` and information where possible when using `:joined-field` clauses; display_name should -;; include the joined table +;; include the joined table (for IMPLICIT JOINS) (expect [(data/$ids venues (assoc (info-for-field :categories :name) :display_name "Venues → Name" - :fk_field_id %category_id :source :fields - :field_ref &CATEGORIES__via__CATEGORY_ID.categories.name))] + :field_ref $category_id->categories.name + :fk_field_id %category_id))] (qp.test-util/with-everything-store (data/$ids venues (doall @@ -112,14 +112,34 @@ :fk-field-id %category_id}]}} {:columns [:name]}))))) +;; for EXPLICIT JOINS (which do not include an `:fk-field-id` in the Join info) the returned `:field_ref` should be a +;; `joined-field` clause instead of an `fk->` clause +(expect + [(data/$ids venues + (assoc (info-for-field :categories :name) + :display_name "Venues → Name" + :source :fields + :field_ref &CATEGORIES__via__CATEGORY_ID.categories.name))] + (qp.test-util/with-everything-store + (data/$ids venues + (doall + (annotate/column-info + {:type :query + :query {:fields [&CATEGORIES__via__CATEGORY_ID.categories.name] + :joins [{:alias "CATEGORIES__via__CATEGORY_ID" + :source-table $$venues + :condition [:= $category_id &CATEGORIES__via__CATEGORY_ID.categories.id] + :strategy :left-join}]}} + {:columns [:name]}))))) + ;; we shuld use the `display_name` of a Table instead of its `name` in joined display names (expect [(data/$ids venues (assoc (info-for-field :categories :name) :display_name "Geographical locations to share Tips about → Name" ; RIP GeoTips - :fk_field_id %category_id :source :fields - :field_ref &CATEGORIES__via__CATEGORY_ID.categories.name))] + :field_ref $category_id->categories.name + :fk_field_id %category_id))] (qp.test-util/with-everything-store (data/$ids venues (tu/with-temp-vals-in-db Table $$venues {:display_name "Geographical locations to share Tips about"} @@ -140,9 +160,9 @@ [(data/$ids venues (assoc (info-for-field :categories :name) :display_name "cats → Name" - :fk_field_id %category_id :source :fields - :field_ref &cats.categories.name))] + :field_ref $category_id->categories.name + :fk_field_id %category_id))] (qp.test-util/with-everything-store (data/$ids venues (doall @@ -220,7 +240,7 @@ ;; For fields with parents we should return them with a combined name including parent's name (tt/expect-with-temp [Field [parent {:name "parent", :table_id (data/id :venues)}] - Field [child {:name "child", :table_id (data/id :venues), :parent_id (u/get-id parent)}]] + Field [child {:name "child", :table_id (data/id :venues), :parent_id (u/get-id parent)}]] {:description nil :table_id (data/id :venues) :special_type nil @@ -231,14 +251,15 @@ :visibility_type :normal :display_name "Child" :fingerprint nil + :field_ref [:field-id (u/get-id child)] :base_type :type/Text} (qp.test-util/with-everything-store (#'annotate/col-info-for-field-clause {} [:field-id (u/get-id child)]))) ;; nested-nested fields should include grandparent name (etc) (tt/expect-with-temp [Field [grandparent {:name "grandparent", :table_id (data/id :venues)}] - Field [parent {:name "parent", :table_id (data/id :venues), :parent_id (u/get-id grandparent)}] - Field [child {:name "child", :table_id (data/id :venues), :parent_id (u/get-id parent)}]] + Field [parent {:name "parent", :table_id (data/id :venues), :parent_id (u/get-id grandparent)}] + Field [child {:name "child", :table_id (data/id :venues), :parent_id (u/get-id parent)}]] {:description nil :table_id (data/id :venues) :special_type nil @@ -249,6 +270,7 @@ :visibility_type :normal :display_name "Child" :fingerprint nil + :field_ref [:field-id (u/get-id child)] :base_type :type/Text} (qp.test-util/with-everything-store (#'annotate/col-info-for-field-clause {} [:field-id (u/get-id child)]))) @@ -258,10 +280,11 @@ {:name "sum" :display_name "sum of User ID" :base_type :type/Integer + :field_ref [:field-literal "sum" :type/Integer] :special_type :type/FK} (qp.test-util/with-everything-store (#'annotate/col-info-for-field-clause - {:source-metadata [{:name "abc", :display_name "another Field", :base_type :type/Integer, :special_type :type/FK} + {:source-metadata [{:name "abc", :display_name "another Field", :base_type :type/Integer, :special_type :type/FK} {:name "sum", :display_name "sum of User ID", :base_type :type/Integer, :special_type :type/FK}]} [:field-literal "sum" :type/Integer]))) diff --git a/test/metabase/query_processor_test.clj b/test/metabase/query_processor_test.clj index 7dee4e9bf434871d653a67333dde29753a1961f1..6cd400ef0517840817da07e8f7123e12f9c74226 100644 --- a/test/metabase/query_processor_test.clj +++ b/test/metabase/query_processor_test.clj @@ -12,7 +12,6 @@ [metabase.models [field :refer [Field]] [table :refer [Table]]] - [metabase.query-processor.middleware.add-implicit-joins :as add-implicit-joins] [metabase.test.data :as data] [metabase.test.data [datasets :as datasets] @@ -172,11 +171,10 @@ (let [source-col (col source-table-kw source-field-kw) dest-col (col dest-table-kw dest-field-kw) dest-table-display-name (db/select-one-field :display_name Table :id (data/id dest-table-kw)) - dest-table-name (db/select-one-field :name Table :id (data/id dest-table-kw)) - join-alias (#'add-implicit-joins/join-alias dest-table-name (:name source-col))] + dest-table-name (db/select-one-field :name Table :id (data/id dest-table-kw))] (-> dest-col (update :display_name (partial format "%s → %s" (or dest-table-display-name dest-table-name))) - (assoc :field_ref [:joined-field join-alias [:field-id (:id dest-col)]] + (assoc :field_ref [:fk-> [:field-id (:id source-col)] [:field-id (:id dest-col)]] :fk_field_id (:id source-col))))) (declare cols) diff --git a/test/metabase/query_processor_test/implicit_joins_test.clj b/test/metabase/query_processor_test/implicit_joins_test.clj index 7033de5f17714a1a62794110bb4ae3e513982f33..458916505d811818d7653d31a9c136a46e840f74 100644 --- a/test/metabase/query_processor_test/implicit_joins_test.clj +++ b/test/metabase/query_processor_test/implicit_joins_test.clj @@ -1,6 +1,7 @@ (ns metabase.query-processor-test.implicit-joins-test "Test for JOIN behavior." - (:require [metabase + (:require [expectations :refer [expect]] + [metabase [driver :as driver] [query-processor-test :as qp.test]] [metabase.test.data :as data] @@ -108,6 +109,17 @@ :limit 10})) [:status :error])) +;; Implicit joins should come back with `:fk->` field refs +(expect + (data/$ids venues $category_id->categories.name) + (-> (qp.test/cols + (data/run-mbql-query :venues + {:fields [$category_id->categories.name] + :order-by [[:asc $id]] + :limit 1})) + first + :field_ref)) + ;;; +----------------------------------------------------------------------------------------------------------------+ ;;; | MULTIPLE JOINS | diff --git a/test/metabase/query_processor_test/remapping_test.clj b/test/metabase/query_processor_test/remapping_test.clj index d4878f69be9fa8c7347217da203845145b0069d6..66674e1be7f0a34ab29105c538ea216248f82d10 100644 --- a/test/metabase/query_processor_test/remapping_test.clj +++ b/test/metabase/query_processor_test/remapping_test.clj @@ -7,7 +7,6 @@ [dimension :refer [Dimension]] [field :refer [Field]]] [metabase.query-processor.middleware.add-dimension-projections :as add-dimension-projections] - [metabase.query-processor.test-util :as qp.test-util] [metabase.test.data :as data] [metabase.test.data.datasets :as datasets] [toucan.db :as db])) @@ -59,9 +58,7 @@ :display_name "Foo" :name (data/format-name "name_2") :remapped_from (data/format-name "category_id") - :field_ref [:joined-field - (qp.test-util/fk-table-alias-name $$categories %category_id) - $categories.name]))]} + :field_ref $category_id->categories.name))]} (data/with-temp-objects (data/create-venue-category-fk-remapping "Foo") (select-columns (set (map data/format-name ["name" "price" "name_2"])) @@ -84,9 +81,7 @@ :display_name "Foo" :name (data/format-name "name_2") :remapped_from (data/format-name "category_id") - :field_ref [:joined-field - (qp.test-util/fk-table-alias-name $$categories %category_id) - $categories.name]))]} + :field_ref $category_id->categories.name))]} (data/with-temp-objects (data/create-venue-category-fk-remapping "Foo") (select-columns (set (map data/format-name ["name" "price" "name_2"]))