Skip to content
Snippets Groups Projects
Unverified Commit 0b537921 authored by Cam Saul's avatar Cam Saul Committed by GitHub
Browse files

Fix field_refs returned for implicit join fields (#10380)

parent 050031f7
No related branches found
No related tags found
No related merge requests found
...@@ -114,41 +114,54 @@ ...@@ -114,41 +114,54 @@
{:base_type :type/Float {:base_type :type/Float
:special_type :type/Number})) :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] [{: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 ;; 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 ;; about the clause doing the wrapping
(mbql.u/match-one clause (mbql.u/match-one clause
[:binning-strategy field strategy _ resolved-options] [:binning-strategy field strategy _ resolved-options]
(assoc (col-info-for-field-clause inner-query field) (let [recursive-info (col-info-for-field-clause inner-query field)]
:binning_info (assoc (u/snake-keys resolved-options) (assoc recursive-info
:binning_strategy strategy)) :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] [:datetime-field field unit]
(assoc (col-info-for-field-clause inner-query field) :unit unit) (let [recursive-info (col-info-for-field-clause inner-query field)]
(assoc recursive-info
[:joined-field alias field] :unit unit
(let [{:keys [fk-field-id], :as join} (join-with-alias inner-query alias)] :field_ref (assoc (vec &match) 1 (:field_ref recursive-info))))
(-> (col-info-for-field-clause inner-query field)
(assoc :fk_field_id fk-field-id) [:joined-field join-alias field]
(update :display_name display-name-for-joined-field join))) (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 ;; TODO - should be able to remove this now
[:fk-> [:field-id source-field-id] field] [: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 ;; TODO - should be able to remove this now
;; for FKs where source is a :field-literal don't include `:fk_field_id` ;; for FKs where source is a :field-literal don't include `:fk_field_id`
[:fk-> _ field] [: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 ;; 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 ;; basic info based on the content of the field literal
[:field-literal field-name field-type] [:field-literal field-name field-type]
(or (some #(when (= (:name %) field-name) %) source-metadata) (assoc (or (some #(when (= (:name %) field-name) %) source-metadata)
{:name field-name {:name field-name
:base_type field-type :base_type field-type
:display_name (humanization/name->human-readable-name field-name)}) :display_name (humanization/name->human-readable-name field-name)})
:field_ref &match)
[:expression expression-name] [:expression expression-name]
(merge (merge
...@@ -158,14 +171,16 @@ ...@@ -158,14 +171,16 @@
{:name expression-name {:name expression-name
:display_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 ;; 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] [:field-id id]
(let [{parent-id :parent_id, :as field} (dissoc (qp.store/field id) :database_type)] (let [{parent-id :parent_id, :as field} (dissoc (qp.store/field id) :database_type)]
(if-not parent-id (assoc (if-not parent-id
field field
(let [parent (col-info-for-field-clause inner-query [:field-id parent-id])] (let [parent (col-info-for-field-clause inner-query [:field-id parent-id])]
(update field :name #(str (:name parent) \. %))))) (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 ;; 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 ;; something the user should expect to see
...@@ -358,16 +373,14 @@ ...@@ -358,16 +373,14 @@
[{:keys [fields], :as inner-query} :- su/Map] [{:keys [fields], :as inner-query} :- su/Map]
(for [field fields] (for [field fields]
(assoc (col-info-for-field-clause inner-query field) (assoc (col-info-for-field-clause inner-query field)
:source :fields :source :fields)))
:field_ref field)))
(s/defn ^:private cols-for-ags-and-breakouts (s/defn ^:private cols-for-ags-and-breakouts
[{aggregations :aggregation, breakouts :breakout, :as inner-query} :- su/Map] [{aggregations :aggregation, breakouts :breakout, :as inner-query} :- su/Map]
(concat (concat
(for [breakout breakouts] (for [breakout breakouts]
(assoc (col-info-for-field-clause inner-query breakout) (assoc (col-info-for-field-clause inner-query breakout)
:source :breakout :source :breakout))
:field_ref breakout))
(for [[i aggregation] (m/indexed aggregations)] (for [[i aggregation] (m/indexed aggregations)]
(assoc (col-info-for-aggregation-clause inner-query aggregation) (assoc (col-info-for-aggregation-clause inner-query aggregation)
:source :aggregation :source :aggregation
......
...@@ -91,14 +91,14 @@ ...@@ -91,14 +91,14 @@
{:columns [:name]})))) {:columns [:name]}))))
;; we should get `:fk_field_id` and information where possible when using `:joined-field` clauses; display_name should ;; 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 (expect
[(data/$ids venues [(data/$ids venues
(assoc (info-for-field :categories :name) (assoc (info-for-field :categories :name)
:display_name "Venues → Name" :display_name "Venues → Name"
:fk_field_id %category_id
:source :fields :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 (qp.test-util/with-everything-store
(data/$ids venues (data/$ids venues
(doall (doall
...@@ -112,14 +112,34 @@ ...@@ -112,14 +112,34 @@
:fk-field-id %category_id}]}} :fk-field-id %category_id}]}}
{:columns [:name]}))))) {: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 ;; we shuld use the `display_name` of a Table instead of its `name` in joined display names
(expect (expect
[(data/$ids venues [(data/$ids venues
(assoc (info-for-field :categories :name) (assoc (info-for-field :categories :name)
:display_name "Geographical locations to share Tips about → Name" ; RIP GeoTips :display_name "Geographical locations to share Tips about → Name" ; RIP GeoTips
:fk_field_id %category_id
:source :fields :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 (qp.test-util/with-everything-store
(data/$ids venues (data/$ids venues
(tu/with-temp-vals-in-db Table $$venues {:display_name "Geographical locations to share Tips about"} (tu/with-temp-vals-in-db Table $$venues {:display_name "Geographical locations to share Tips about"}
...@@ -140,9 +160,9 @@ ...@@ -140,9 +160,9 @@
[(data/$ids venues [(data/$ids venues
(assoc (info-for-field :categories :name) (assoc (info-for-field :categories :name)
:display_name "cats → Name" :display_name "cats → Name"
:fk_field_id %category_id
:source :fields :source :fields
:field_ref &cats.categories.name))] :field_ref $category_id->categories.name
:fk_field_id %category_id))]
(qp.test-util/with-everything-store (qp.test-util/with-everything-store
(data/$ids venues (data/$ids venues
(doall (doall
...@@ -220,7 +240,7 @@ ...@@ -220,7 +240,7 @@
;; For fields with parents we should return them with a combined name including parent's name ;; 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)}] (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 {:description nil
:table_id (data/id :venues) :table_id (data/id :venues)
:special_type nil :special_type nil
...@@ -231,14 +251,15 @@ ...@@ -231,14 +251,15 @@
:visibility_type :normal :visibility_type :normal
:display_name "Child" :display_name "Child"
:fingerprint nil :fingerprint nil
:field_ref [:field-id (u/get-id child)]
:base_type :type/Text} :base_type :type/Text}
(qp.test-util/with-everything-store (qp.test-util/with-everything-store
(#'annotate/col-info-for-field-clause {} [:field-id (u/get-id child)]))) (#'annotate/col-info-for-field-clause {} [:field-id (u/get-id child)])))
;; nested-nested fields should include grandparent name (etc) ;; nested-nested fields should include grandparent name (etc)
(tt/expect-with-temp [Field [grandparent {:name "grandparent", :table_id (data/id :venues)}] (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 [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 [child {:name "child", :table_id (data/id :venues), :parent_id (u/get-id parent)}]]
{:description nil {:description nil
:table_id (data/id :venues) :table_id (data/id :venues)
:special_type nil :special_type nil
...@@ -249,6 +270,7 @@ ...@@ -249,6 +270,7 @@
:visibility_type :normal :visibility_type :normal
:display_name "Child" :display_name "Child"
:fingerprint nil :fingerprint nil
:field_ref [:field-id (u/get-id child)]
:base_type :type/Text} :base_type :type/Text}
(qp.test-util/with-everything-store (qp.test-util/with-everything-store
(#'annotate/col-info-for-field-clause {} [:field-id (u/get-id child)]))) (#'annotate/col-info-for-field-clause {} [:field-id (u/get-id child)])))
...@@ -258,10 +280,11 @@ ...@@ -258,10 +280,11 @@
{:name "sum" {:name "sum"
:display_name "sum of User ID" :display_name "sum of User ID"
:base_type :type/Integer :base_type :type/Integer
:field_ref [:field-literal "sum" :type/Integer]
:special_type :type/FK} :special_type :type/FK}
(qp.test-util/with-everything-store (qp.test-util/with-everything-store
(#'annotate/col-info-for-field-clause (#'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}]} {:name "sum", :display_name "sum of User ID", :base_type :type/Integer, :special_type :type/FK}]}
[:field-literal "sum" :type/Integer]))) [:field-literal "sum" :type/Integer])))
......
...@@ -12,7 +12,6 @@ ...@@ -12,7 +12,6 @@
[metabase.models [metabase.models
[field :refer [Field]] [field :refer [Field]]
[table :refer [Table]]] [table :refer [Table]]]
[metabase.query-processor.middleware.add-implicit-joins :as add-implicit-joins]
[metabase.test.data :as data] [metabase.test.data :as data]
[metabase.test.data [metabase.test.data
[datasets :as datasets] [datasets :as datasets]
...@@ -172,11 +171,10 @@ ...@@ -172,11 +171,10 @@
(let [source-col (col source-table-kw source-field-kw) (let [source-col (col source-table-kw source-field-kw)
dest-col (col dest-table-kw dest-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-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)) 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-col (-> dest-col
(update :display_name (partial format "%s → %s" (or dest-table-display-name dest-table-name))) (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))))) :fk_field_id (:id source-col)))))
(declare cols) (declare cols)
......
(ns metabase.query-processor-test.implicit-joins-test (ns metabase.query-processor-test.implicit-joins-test
"Test for JOIN behavior." "Test for JOIN behavior."
(:require [metabase (:require [expectations :refer [expect]]
[metabase
[driver :as driver] [driver :as driver]
[query-processor-test :as qp.test]] [query-processor-test :as qp.test]]
[metabase.test.data :as data] [metabase.test.data :as data]
...@@ -108,6 +109,17 @@ ...@@ -108,6 +109,17 @@
:limit 10})) :limit 10}))
[:status :error])) [: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 | ;;; | MULTIPLE JOINS |
......
...@@ -7,7 +7,6 @@ ...@@ -7,7 +7,6 @@
[dimension :refer [Dimension]] [dimension :refer [Dimension]]
[field :refer [Field]]] [field :refer [Field]]]
[metabase.query-processor.middleware.add-dimension-projections :as add-dimension-projections] [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 :as data]
[metabase.test.data.datasets :as datasets] [metabase.test.data.datasets :as datasets]
[toucan.db :as db])) [toucan.db :as db]))
...@@ -59,9 +58,7 @@ ...@@ -59,9 +58,7 @@
:display_name "Foo" :display_name "Foo"
:name (data/format-name "name_2") :name (data/format-name "name_2")
:remapped_from (data/format-name "category_id") :remapped_from (data/format-name "category_id")
:field_ref [:joined-field :field_ref $category_id->categories.name))]}
(qp.test-util/fk-table-alias-name $$categories %category_id)
$categories.name]))]}
(data/with-temp-objects (data/with-temp-objects
(data/create-venue-category-fk-remapping "Foo") (data/create-venue-category-fk-remapping "Foo")
(select-columns (set (map data/format-name ["name" "price" "name_2"])) (select-columns (set (map data/format-name ["name" "price" "name_2"]))
...@@ -84,9 +81,7 @@ ...@@ -84,9 +81,7 @@
:display_name "Foo" :display_name "Foo"
:name (data/format-name "name_2") :name (data/format-name "name_2")
:remapped_from (data/format-name "category_id") :remapped_from (data/format-name "category_id")
:field_ref [:joined-field :field_ref $category_id->categories.name))]}
(qp.test-util/fk-table-alias-name $$categories %category_id)
$categories.name]))]}
(data/with-temp-objects (data/with-temp-objects
(data/create-venue-category-fk-remapping "Foo") (data/create-venue-category-fk-remapping "Foo")
(select-columns (set (map data/format-name ["name" "price" "name_2"])) (select-columns (set (map data/format-name ["name" "price" "name_2"]))
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment