diff --git a/frontend/src/metabase-lib/join.ts b/frontend/src/metabase-lib/join.ts index 6715051d9d2e6a9a027f41edf1392f045ef35cd0..3201f9827703f8103645d10c01356332e39cb7fc 100644 --- a/frontend/src/metabase-lib/join.ts +++ b/frontend/src/metabase-lib/join.ts @@ -133,10 +133,23 @@ export function pickerInfo(query: Query, metadata: Joinable): PickerInfo { return ML.picker_info(query, metadata); } +type JoinOrJoinable = Join | Joinable; + export function joinableColumns( query: Query, stageIndex: number, - joinOrJoinable: Join | Joinable, + joinOrJoinable: JoinOrJoinable, ): ColumnMetadata[] { return ML.joinable_columns(query, stageIndex, joinOrJoinable); } + +// Get the display name to use when rendering a join for whatever we are joining against (e.g. a Table or Card of some +// sort). See #32015 for screenshot examples. For an existing join, pass in the join clause. When constructing a join, +// pass in the thing we are joining against, e.g. a TableMetadata or CardMetadata. +export function joinLHSDisplayName( + query: Query, + stageIndex: number, + joinOrJoinable: JoinOrJoinable, +): string { + return ML.join_lhs_display_name(query, stageIndex, joinOrJoinable); +} diff --git a/src/metabase/lib/core.cljc b/src/metabase/lib/core.cljc index 61ae75e9c1d471f5fe5006d697ab6ed60d52bd24..d3de1dc8354dad31bc925bda3095326298bb4a18 100644 --- a/src/metabase/lib/core.cljc +++ b/src/metabase/lib/core.cljc @@ -171,6 +171,7 @@ join-condition-rhs-columns join-conditions join-fields + join-lhs-display-name join-strategy joinable-columns joins diff --git a/src/metabase/lib/field.cljc b/src/metabase/lib/field.cljc index 6bf6961655cd8af1da696c0531aa648671907169..eae94ea0ba90aa38ef8035267f9ec9cb8899c942 100644 --- a/src/metabase/lib/field.cljc +++ b/src/metabase/lib/field.cljc @@ -55,7 +55,7 @@ field-id :- ::lib.schema.id/field] (merge (when (lib.util/first-stage? query stage-number) - (when-let [card-id (lib.util/source-card query)] + (when-let [card-id (lib.util/source-card-id query)] (when-let [card-metadata (lib.card/saved-question-metadata query card-id)] (m/find-first #(= (:id %) field-id) card-metadata)))) diff --git a/src/metabase/lib/join.cljc b/src/metabase/lib/join.cljc index 7c85c3808aedb0fd04dedc6025ac4f85cb42c47b..34729b31f38fb744d4bbcef2373deed3b1476edf 100644 --- a/src/metabase/lib/join.cljc +++ b/src/metabase/lib/join.cljc @@ -656,14 +656,22 @@ (dissoc ::ref))) cols))))) +(def ^:private JoinOrJoinable + [:or + [:ref ::lib.schema.join/join] + Joinable]) + +(defn- join? [x] + (= (lib.dispatch/dispatch-value x) :mbql/join)) + (mu/defn joinable-columns :- [:sequential lib.metadata/ColumnMetadata] "Return information about the fields that you can pass to [[with-join-fields]] when constructing a join against something [[Joinable]] (i.e., a Table or Card) or manipulating an existing join. When passing in a join, currently selected columns (those in the join's `:fields`) will include `:selected true` information." [query :- ::lib.schema/query stage-number :- :int - join-or-joinable :- [:or ::lib.schema.join/join Joinable]] - (let [a-join (when (= (lib.dispatch/dispatch-value join-or-joinable) :mbql/join) + join-or-joinable :- JoinOrJoinable] + (let [a-join (when (join? join-or-joinable) join-or-joinable) source (if a-join (joined-thing query join-or-joinable) @@ -672,3 +680,54 @@ (cond-> cols a-join (add-join-alias-to-joinable-columns a-join) a-join (mark-selected-joinable-columns a-join)))) + +(defn- first-join? + "Whether a `join-or-joinable` is (or will be) the first join in a stage of a query. + + If a join is passed, we need to check whether it's the first join in the first stage of a source-table query or + not. + + New joins get appended after any existing ones, so it would be safe to assume that if there are any other joins in + the current stage, this **will not** be the first join in the stage." + [query stage-number join-or-joinable] + (let [existing-joins (joins query stage-number)] + (or + ;; if there are no existing joins, then this will be the first join regardless of what is passed in. + (empty? existing-joins) + ;; otherwise there ARE existing joins, so this is only the first join if it is the same thing as the first join + ;; in `existing-joins`. + (when (join? join-or-joinable) + (= (lib.options/uuid join-or-joinable) + (lib.options/uuid (first existing-joins))))))) + +(mu/defn join-lhs-display-name :- ::lib.schema.common/non-blank-string + "Get the display name for whatever we are joining. See #32015 for screenshot examples. + + The rules, copied from MLv1, are as follows: + + 1. If this is the first join in the first stage of a query, and the query uses a `:source-table`, then use the + display name for the source Table. + + 2. Otherwise use `Previous results`. + + These rules do seem a little goofy -- why don't we use the name of a Saved Question or Model? But we can worry about + that in the future. For now, let's just replicate MLv1 behavior. + + This function needs to be usable while we are in the process of constructing a join in the context of a given stage, + but also needs to work for rendering existing joins. Pass a join in for existing joins, or something [[Joinable]] + for ones we are currently building." + ([query join-or-joinable] + (join-lhs-display-name query -1 join-or-joinable)) + + ([query :- ::lib.schema/query + stage-number :- :int + join-or-joinable :- JoinOrJoinable] + (if (and (zero? (lib.util/canonical-stage-index query stage-number)) ; first stage? + (first-join? query stage-number join-or-joinable) ; first join? + (lib.util/source-table-id query)) ; query ultimately uses source Table? + (let [table-id (lib.util/source-table-id query) + table (lib.metadata/table query table-id)] + ;; I think `:default` is okay here, there shouldn't be a difference between `:default` and `:long` for a + ;; Table anyway + (lib.metadata.calculation/display-name query stage-number table)) + (i18n/tru "Previous results")))) diff --git a/src/metabase/lib/js.cljs b/src/metabase/lib/js.cljs index 32cbf76109e328ba4b85e6b5848a5f4c4658a73c..92d5ff7e3f0112e0d0ce8596a79dcc146232703b 100644 --- a/src/metabase/lib/js.cljs +++ b/src/metabase/lib/js.cljs @@ -628,3 +628,9 @@ Returns `nil` if no matching metadata is found." [query-or-metadata-provider table-id] (lib.metadata/table-or-card query-or-metadata-provider table-id)) + +(defn ^:export join-lhs-display-name + "Get the display name for whatever we are joining. For an existing join, pass in the join clause. When constructing a + join, pass in the thing we are joining against, e.g. a TableMetadata or CardMetadata." + [a-query stage-number join-or-joinable] + (lib.core/join-lhs-display-name a-query stage-number join-or-joinable)) diff --git a/src/metabase/lib/util.cljc b/src/metabase/lib/util.cljc index 677fe770db1d8a2acd82fe63441171230cd34ab0..a96277156a4ed27a3ef067e252683d314e71139e 100644 --- a/src/metabase/lib/util.cljc +++ b/src/metabase/lib/util.cljc @@ -415,7 +415,7 @@ [query] (-> query :stages first :source-table)) -(mu/defn source-card :- [:maybe ::lib.schema.id/card] +(mu/defn source-card-id :- [:maybe ::lib.schema.id/card] "If this query has a `:source-card` ID, return it." [query] (-> query :stages first :source-card)) diff --git a/test/metabase/lib/breakout_test.cljc b/test/metabase/lib/breakout_test.cljc index 56a67e17aa593f10c08829c10d59d0ccb477a0eb..e0f0da413671a482aa2cace0dae63c8e1a9d1e71 100644 --- a/test/metabase/lib/breakout_test.cljc +++ b/test/metabase/lib/breakout_test.cljc @@ -214,7 +214,7 @@ (deftest ^:parallel breakoutable-columns-source-card-test (doseq [varr [#'lib.tu/query-with-card-source-table #'lib.tu/query-with-card-source-table-with-result-metadata] - :let [query (varr)]] + :let [query @varr]] (testing (str (pr-str varr) \newline (lib.util/format "Query =\n%s" (u/pprint-to-str query))) (let [columns (lib/breakoutable-columns query)] (is (=? [{:name "USER_ID" @@ -371,7 +371,7 @@ (deftest ^:parallel breakoutable-columns-with-source-card-e2e-test (testing "A column that comes from a source Card (Saved Question/Model/etc) can be broken out by." - (let [query (lib.tu/query-with-card-source-table)] + (let [query lib.tu/query-with-card-source-table] (testing (lib.util/format "Query =\n%s" (u/pprint-to-str query)) (let [name-col (m/find-first #(= (:name %) "USER_ID") (lib/breakoutable-columns query))] diff --git a/test/metabase/lib/card_test.cljc b/test/metabase/lib/card_test.cljc index e497400546f9f4d98c893e3d455dc643459cd79e..683bcb53308274010627d276487e307322a29e81 100644 --- a/test/metabase/lib/card_test.cljc +++ b/test/metabase/lib/card_test.cljc @@ -14,7 +14,7 @@ (deftest ^:parallel source-card-infer-metadata-test (testing "We should be able to calculate metadata for a Saved Question missing results_metadata" - (let [query (lib.tu/query-with-card-source-table)] + (let [query lib.tu/query-with-card-source-table] (is (=? [{:id (meta/id :checkins :user-id) :name "USER_ID" :lib/source :source/card @@ -73,7 +73,7 @@ (deftest ^:parallel card-results-metadata-merge-metadata-provider-metadata-test (testing "Merge metadata from the metadata provider into result-metadata (#30046)" - (let [query (lib.tu/query-with-card-source-table-with-result-metadata)] + (let [query lib.tu/query-with-card-source-table-with-result-metadata] (is (=? [{:lib/type :metadata/column :id (meta/id :checkins :user-id) :table-id (meta/id :checkins) diff --git a/test/metabase/lib/column_group_test.cljc b/test/metabase/lib/column_group_test.cljc index a528c56d5bf0639a9ab6a617cb7e855942bd424d..9030a96e7d7d1e533dbf460020aefb0dfd3b737c 100644 --- a/test/metabase/lib/column_group_test.cljc +++ b/test/metabase/lib/column_group_test.cljc @@ -87,7 +87,7 @@ (mapcat lib/columns-group-columns groups)))))) (deftest ^:parallel source-card-test - (let [query (lib.tu/query-with-card-source-table) + (let [query lib.tu/query-with-card-source-table columns (lib/orderable-columns query) groups (lib/group-columns columns)] (is (=? [{::lib.column-group/group-type :group-type/main @@ -106,7 +106,7 @@ (mapcat lib/columns-group-columns groups)))))) (deftest ^:parallel joins-test - (let [query (lib.tu/query-with-join) + (let [query lib.tu/query-with-join columns (lib/orderable-columns query) groups (lib/group-columns columns)] (is (=? [{::lib.column-group/group-type :group-type/main @@ -137,7 +137,7 @@ (mapcat lib/columns-group-columns groups)))))) (deftest ^:parallel expressions-test - (let [query (lib.tu/query-with-expression) + (let [query lib.tu/query-with-expression columns (lib/orderable-columns query) groups (lib/group-columns columns)] (is (=? [{::lib.column-group/group-type :group-type/main @@ -170,7 +170,7 @@ (mapcat lib/columns-group-columns groups)))))) (deftest ^:parallel source-card-with-expressions-test - (let [query (-> (lib.tu/query-with-card-source-table) + (let [query (-> lib.tu/query-with-card-source-table (lib/expression "expr" (lib/absolute-datetime "2020" :month))) columns (lib/orderable-columns query) groups (lib/group-columns columns)] @@ -191,7 +191,7 @@ (mapcat lib/columns-group-columns groups)))))) (deftest ^:parallel native-query-test - (let [query (lib.tu/native-query) + (let [query lib.tu/native-query groups (lib/group-columns (lib/orderable-columns query))] (is (=? [{::lib.column-group/group-type :group-type/main ::lib.column-group/columns [{:display-name "another Field", :lib/source :source/native} @@ -205,7 +205,7 @@ (lib/display-info query group))))))) (deftest ^:parallel native-source-query-test - (let [query (-> (lib.tu/native-query) + (let [query (-> lib.tu/native-query lib/append-stage) groups (lib/group-columns (lib/orderable-columns query))] (is (=? [{::lib.column-group/group-type :group-type/main diff --git a/test/metabase/lib/field_test.cljc b/test/metabase/lib/field_test.cljc index 709d9a5bf461b65eca3646631db1457a06b13793..802949238f0efb16a7c4e870b2da3557575da50d 100644 --- a/test/metabase/lib/field_test.cljc +++ b/test/metabase/lib/field_test.cljc @@ -93,7 +93,7 @@ :base-type :type/Integer :semantic-type :type/FK} (lib.metadata.calculation/metadata - (lib.tu/native-query) + lib.tu/native-query -1 [:field {:lib/uuid (str (random-uuid)), :base-type :type/Integer} "sum"]))))) diff --git a/test/metabase/lib/join_test.cljc b/test/metabase/lib/join_test.cljc index cfb68292e3a38cf591434599f2ae4b8387b21f1f..94ebaac08d70ce8278610346d74fac975b747a9e 100644 --- a/test/metabase/lib/join_test.cljc +++ b/test/metabase/lib/join_test.cljc @@ -7,8 +7,10 @@ [metabase.lib.metadata :as lib.metadata] [metabase.lib.metadata.calculation :as lib.metadata.calculation] [metabase.lib.metadata.composed-provider :as lib.metadata.composed-provider] + [metabase.lib.options :as lib.options] [metabase.lib.test-metadata :as meta] [metabase.lib.test-util :as lib.tu] + [metabase.util :as u] #?@(:cljs ([metabase.test-runner.assert-exprs.approximately-equal])))) #?(:cljs (comment metabase.test-runner.assert-exprs.approximately-equal/keep-me)) @@ -328,7 +330,7 @@ (lib.metadata.calculation/returned-columns query))))) (deftest ^:parallel join-strategy-test - (let [query (lib.tu/query-with-join) + (let [query lib.tu/query-with-join [join] (lib/joins query)] (testing "join without :strategy" (is (= :left-join @@ -368,16 +370,16 @@ (is (= [{:lib/type :option/join.strategy, :strategy :left-join, :default true} {:lib/type :option/join.strategy, :strategy :right-join} {:lib/type :option/join.strategy, :strategy :inner-join}] - (lib/available-join-strategies (lib.tu/query-with-join))))) + (lib/available-join-strategies lib.tu/query-with-join)))) (deftest ^:parallel join-strategy-display-name-test - (let [query (lib.tu/query-with-join)] + (let [query lib.tu/query-with-join] (is (= ["Left outer join" "Right outer join" "Inner join"] (map (partial lib.metadata.calculation/display-name query) (lib/available-join-strategies query)))))) (deftest ^:parallel join-strategy-display-info-test - (let [query (lib.tu/query-with-join)] + (let [query lib.tu/query-with-join] (is (= [{:short-name "left-join", :display-name "Left outer join", :default true} {:short-name "right-join", :display-name "Right outer join"} {:short-name "inner-join", :display-name "Inner join"}] @@ -574,7 +576,7 @@ (meta/table-metadata :categories)))))) (deftest ^:parallel join-conditions-test - (let [joins (lib/joins (lib.tu/query-with-join))] + (let [joins (lib/joins lib.tu/query-with-join)] (is (= 1 (count joins))) (is (=? [[:= @@ -595,7 +597,7 @@ meta/saved-question-CardMetadata)) (deftest ^:parallel joinable-columns-join-test - (let [query (lib.tu/query-with-join) + (let [query lib.tu/query-with-join [original-join] (lib/joins query)] (is (=? {:lib/type :mbql/join, :alias "Cat", :fields :all} original-join)) @@ -645,3 +647,31 @@ {:lib/uuid string?, :join-alias "Cat"} (meta/id :categories :name)]]} (lib/with-join-fields join cols))))))))) + +(deftest ^:parallel join-lhs-display-name-test + (doseq [[source-table? query] {true lib.tu/venues-query + false lib.tu/query-with-card-source-table} + [num-existing-joins query] {0 query + 1 (lib.tu/add-joins query "J1") + 2 (lib.tu/add-joins query "J1" "J2")} + [first-join? join-or-joinable] (list* + [(zero? num-existing-joins) (meta/table-metadata :venues)] + [(zero? num-existing-joins) meta/saved-question-CardMetadata] + (when-let [[first-join & more] (not-empty (lib/joins query))] + (cons [true first-join] + (for [join more] + [false join])))) + [num-stages query] {1 query + 2 (lib/append-stage query)}] + (testing (str "query w/ source table?" source-table? \newline + "num-existing-joins = " num-existing-joins \newline + "num-stages = " num-stages \newline + "join =\n" (u/pprint-to-str join-or-joinable) \newline + "existing joins = " (u/pprint-to-str (map lib.options/uuid (lib/joins query))) \newline + "first join? " first-join?) + (is (= (if (and source-table? + (= num-stages 1) + first-join?) + "Venues" + "Previous results") + (lib/join-lhs-display-name query join-or-joinable)))))) diff --git a/test/metabase/lib/js_test.cljs b/test/metabase/lib/js_test.cljs index fbb8dd8613919ccd53ab51cb3da4521067c24b6c..364304b9bf2fa7fcd6e4e98095fd091bc23b099e 100644 --- a/test/metabase/lib/js_test.cljs +++ b/test/metabase/lib/js_test.cljs @@ -68,7 +68,7 @@ (deftest available-join-strategies-test (testing "available-join-strategies returns an array of opaque strategy objects (#32089)" - (let [strategies (lib.js/available-join-strategies (lib.tu/query-with-join) -1)] + (let [strategies (lib.js/available-join-strategies lib.tu/query-with-join -1)] (is (array? strategies)) (is (= [{:lib/type :option/join.strategy, :strategy :left-join, :default true} {:lib/type :option/join.strategy, :strategy :right-join} diff --git a/test/metabase/lib/order_by_test.cljc b/test/metabase/lib/order_by_test.cljc index 965b6a270baf46dd8f647dc44cbc454c14821b9f..fb312039e5e1c8a87e7de53a982fc4e91e1a22f6 100644 --- a/test/metabase/lib/order_by_test.cljc +++ b/test/metabase/lib/order_by_test.cljc @@ -302,7 +302,7 @@ (deftest ^:parallel orderable-columns-source-card-test (doseq [varr [#'lib.tu/query-with-card-source-table #'lib.tu/query-with-card-source-table-with-result-metadata] - :let [query (varr)]] + :let [query @varr]] (testing (str (pr-str varr) \newline (lib.util/format "Query =\n%s" (u/pprint-to-str query))) (is (=? [{:name "USER_ID" :display-name "User ID" @@ -356,7 +356,7 @@ (deftest ^:parallel orderable-columns-with-source-card-e2e-test (testing "Make sure you can order by a column that comes from a source Card (Saved Question/Model/etc)" - (let [query (lib.tu/query-with-card-source-table)] + (let [query lib.tu/query-with-card-source-table] (testing (lib.util/format "Query =\n%s" (u/pprint-to-str query)) (let [name-col (m/find-first #(= (:name %) "USER_ID") (lib/orderable-columns query))] diff --git a/test/metabase/lib/remove_replace_test.cljc b/test/metabase/lib/remove_replace_test.cljc index 2769a5d802c2291a1eefbd6b4d094e59cea58192..f8d451990499f3f5998789c35ba6e414ee1132d0 100644 --- a/test/metabase/lib/remove_replace_test.cljc +++ b/test/metabase/lib/remove_replace_test.cljc @@ -767,7 +767,7 @@ (lib/remove-clause query' 0 (second (lib/joins query' 0))))))))))) (deftest ^:parallel replace-join-test - (let [query (lib.tu/query-with-join) + (let [query lib.tu/query-with-join expected-original {:stages [{:joins [{:lib/type :mbql/join, :alias "Cat", :fields :all}]}]} [original-join] (lib/joins query) new-join (lib/with-join-fields original-join :none)] diff --git a/test/metabase/lib/stage_test.cljc b/test/metabase/lib/stage_test.cljc index 3b652073f17599aa1d47f03a58e2c16c5c02b3ee..3a0a63509adfd97a6c86d4da9d29dabd09424476 100644 --- a/test/metabase/lib/stage_test.cljc +++ b/test/metabase/lib/stage_test.cljc @@ -70,7 +70,7 @@ (lib.metadata.calculation/returned-columns query))))))) (deftest ^:parallel stage-display-name-card-source-query - (let [query (lib.tu/query-with-card-source-table)] + (let [query lib.tu/query-with-card-source-table] (is (= "My Card" (lib.metadata.calculation/display-name query))))) @@ -169,7 +169,7 @@ (testing "visible-columns should not include implicitly joinable columns when the query has a source Card (#30950)" (doseq [varr [#'lib.tu/query-with-card-source-table #'lib.tu/query-with-card-source-table-with-result-metadata] - :let [query (varr)]] + :let [query @varr]] (testing (pr-str varr) (is (=? [{:name "USER_ID" :display-name "User ID" diff --git a/test/metabase/lib/test_metadata.cljc b/test/metabase/lib/test_metadata.cljc index 07209baba5d2a88ee69cf33a1745b4a53d667787..9ab19ddd23f4968602ad9fb88c55f065ad2db017 100644 --- a/test/metabase/lib/test_metadata.cljc +++ b/test/metabase/lib/test_metadata.cljc @@ -7,7 +7,9 @@ will not be reflected here, for example if we add new information to the metadata. We'll have to manually update these things if that happens and Metabase lib is meant to consume it." (:require - [metabase.lib.test-metadata.graph-provider :as meta.graph-provider])) + [metabase.lib.metadata :as lib.metadata] + [metabase.lib.test-metadata.graph-provider :as meta.graph-provider] + [metabase.util.malli :as mu])) (defonce ^:private ^{:doc "Generate a random prefix to add to all of the [[id]]s below, so that they change between test runs to catch places where people are hardcoding IDs rather than using [[id]]."} @@ -117,24 +119,16 @@ :body 804 ; :type/Text :product-id 805))))) ; :type/Integer -(defmulti table-metadata - "Get Table metadata for a one of the `test-data` Tables in the test metadata, e.g. `:venues`. This is here so you can - test things that should consume Table metadata. - - Metadata returned by this method matches the [[metabase.lib.metadata/TableMetadata]] schema." +(defmulti ^:private table-metadata-method {:arglists '([table-name])} keyword) -(defmulti field-metadata - "Get Field metadata for one of the `test-data` Fields in the test metadata, e.g. `:venues` `:name`. This is here so - you can test things that should consume Field metadata. - - Metadata returned by this method matches the [[metabase.lib.metadata/ColumMetadata]] schema." +(defmulti ^:private field-metadata-method {:arglists '([table-name field-name])} (fn [table-name field-name] [(keyword table-name) (keyword field-name)])) -(defmethod field-metadata [:categories :id] +(defmethod field-metadata-method [:categories :id] [_table-name _field-name] {:description nil :database-type "BIGINT" @@ -165,7 +159,7 @@ :points-of-interest nil :lib/type :metadata/column}) -(defmethod field-metadata [:categories :name] +(defmethod field-metadata-method [:categories :name] [_table-name _field-name] {:description nil :database-type "CHARACTER VARYING" @@ -202,15 +196,15 @@ :points-of-interest nil :lib/type :metadata/column}) -(defmethod table-metadata :categories +(defmethod table-metadata-method :categories [_table-name] {:description nil :entity-type :entity/GenericTable :schema "PUBLIC" :show-in-getting-started false :name "CATEGORIES" - :fields [(field-metadata :categories :id) - (field-metadata :categories :name)] + :fields [(field-metadata-method :categories :id) + (field-metadata-method :categories :name)] :caveats nil :segments [] :active true @@ -224,7 +218,7 @@ :points-of-interest nil :lib/type :metadata/table}) -(defmethod field-metadata [:checkins :id] +(defmethod field-metadata-method [:checkins :id] [_table-name _field-name] {:description nil :database-type "BIGINT" @@ -255,7 +249,7 @@ :points-of-interest nil :lib/type :metadata/column}) -(defmethod field-metadata [:checkins :date] +(defmethod field-metadata-method [:checkins :date] [_table-name _field-name] {:description nil :database-type "DATE" @@ -287,7 +281,7 @@ :points-of-interest nil :lib/type :metadata/column}) -(defmethod field-metadata [:checkins :user-id] +(defmethod field-metadata-method [:checkins :user-id] [_table-name _field-name] {:description nil :database-type "INTEGER" @@ -318,7 +312,7 @@ :points-of-interest nil :lib/type :metadata/column}) -(defmethod field-metadata [:checkins :venue-id] +(defmethod field-metadata-method [:checkins :venue-id] [_table-name _field-name] {:description nil :database-type "INTEGER" @@ -349,17 +343,17 @@ :points-of-interest nil :lib/type :metadata/column}) -(defmethod table-metadata :checkins +(defmethod table-metadata-method :checkins [_table-name] {:description nil :entity-type :entity/EventTable :schema "PUBLIC" :show-in-getting-started false :name "CHECKINS" - :fields [(field-metadata :checkins :id) - (field-metadata :checkins :date) - (field-metadata :checkins :user-id) - (field-metadata :checkins :venue-id)] + :fields [(field-metadata-method :checkins :id) + (field-metadata-method :checkins :date) + (field-metadata-method :checkins :user-id) + (field-metadata-method :checkins :venue-id)] :caveats nil :segments [] :active true @@ -373,7 +367,7 @@ :points-of-interest nil :lib/type :metadata/table}) -(defmethod field-metadata [:users :id] +(defmethod field-metadata-method [:users :id] [_table-name _field-name] {:description nil :database-type "BIGINT" @@ -404,7 +398,7 @@ :points-of-interest nil :lib/type :metadata/column}) -(defmethod field-metadata [:users :name] +(defmethod field-metadata-method [:users :name] [_table-name _field-name] {:description nil :database-type "CHARACTER VARYING" @@ -441,7 +435,7 @@ :points-of-interest nil :lib/type :metadata/column}) -(defmethod field-metadata [:users :last-login] +(defmethod field-metadata-method [:users :last-login] [_table-name _field-name] {:description nil :database-type "TIMESTAMP" @@ -473,7 +467,7 @@ :points-of-interest nil :lib/type :metadata/column}) -(defmethod field-metadata [:users :password] +(defmethod field-metadata-method [:users :password] [_table-name _field-name] {:description nil :database-type "CHARACTER VARYING" @@ -510,17 +504,17 @@ :points-of-interest nil :lib/type :metadata/column}) -(defmethod table-metadata :users +(defmethod table-metadata-method :users [_table-name] {:description nil :entity-type :entity/UserTable :schema "PUBLIC" :show-in-getting-started false :name "USERS" - :fields [(field-metadata :users :id) - (field-metadata :users :name) - (field-metadata :users :last-login) - (field-metadata :users :password)] + :fields [(field-metadata-method :users :id) + (field-metadata-method :users :name) + (field-metadata-method :users :last-login) + (field-metadata-method :users :password)] :caveats nil :segments [] :active true @@ -534,7 +528,7 @@ :points-of-interest nil :lib/type :metadata/table}) -(defmethod field-metadata [:venues :id] +(defmethod field-metadata-method [:venues :id] [_table-name _field-name] {:description nil :database-type "BIGINT" @@ -565,7 +559,7 @@ :points-of-interest nil :lib/type :metadata/column}) -(defmethod field-metadata [:venues :name] +(defmethod field-metadata-method [:venues :name] [_table-name _field-name] {:description nil :database-type "CHARACTER VARYING" @@ -602,7 +596,7 @@ :points-of-interest nil :lib/type :metadata/column}) -(defmethod field-metadata [:venues :category-id] +(defmethod field-metadata-method [:venues :category-id] [_table-name _field-name] {:description nil :database-type "INTEGER" @@ -633,7 +627,7 @@ :points-of-interest nil :lib/type :metadata/column}) -(defmethod field-metadata [:venues :latitude] +(defmethod field-metadata-method [:venues :latitude] [_table-name _field-name] {:description nil :database-type "DOUBLE PRECISION" @@ -671,7 +665,7 @@ :points-of-interest nil :lib/type :metadata/column}) -(defmethod field-metadata [:venues :longitude] +(defmethod field-metadata-method [:venues :longitude] [_table-name _field-name] {:description nil :database-type "DOUBLE PRECISION" @@ -711,7 +705,7 @@ :points-of-interest nil :lib/type :metadata/column}) -(defmethod field-metadata [:venues :price] +(defmethod field-metadata-method [:venues :price] [_table-name _field-name] {:description nil :database-type "INTEGER" @@ -749,19 +743,19 @@ :points-of-interest nil :lib/type :metadata/column}) -(defmethod table-metadata :venues +(defmethod table-metadata-method :venues [_table-name] {:description nil :entity-type :entity/GenericTable :schema "PUBLIC" :show-in-getting-started false :name "VENUES" - :fields [(field-metadata :venues :id) - (field-metadata :venues :name) - (field-metadata :venues :category-id) - (field-metadata :venues :latitude) - (field-metadata :venues :longitude) - (field-metadata :venues :price)] + :fields [(field-metadata-method :venues :id) + (field-metadata-method :venues :name) + (field-metadata-method :venues :category-id) + (field-metadata-method :venues :latitude) + (field-metadata-method :venues :longitude) + (field-metadata-method :venues :price)] :caveats nil :segments [] :active true @@ -775,7 +769,7 @@ :points-of-interest nil :lib/type :metadata/table}) - (defmethod field-metadata [:products :id] + (defmethod field-metadata-method [:products :id] [_table-name _field-name] {:description nil :database-type "BIGINT" @@ -806,7 +800,7 @@ :points-of-interest nil :lib/type :metadata/column}) -(defmethod field-metadata [:products :rating] +(defmethod field-metadata-method [:products :rating] [_table-name _field-name] {:description nil :database-type "DOUBLE PRECISION" @@ -843,7 +837,7 @@ :points-of-interest nil :lib/type :metadata/column}) - (defmethod field-metadata [:products :category] + (defmethod field-metadata-method [:products :category] [_table-name _field-name] {:description nil :database-type "CHARACTER VARYING" @@ -879,7 +873,7 @@ :points-of-interest nil :lib/type :metadata/column}) -(defmethod field-metadata [:products :price] +(defmethod field-metadata-method [:products :price] [_table-name _field-name] {:description nil :database-type "DOUBLE PRECISION" @@ -916,7 +910,7 @@ :points-of-interest nil :lib/type :metadata/column}) - (defmethod field-metadata [:products :title] + (defmethod field-metadata-method [:products :title] [_table-name _field-name] {:description nil :database-type "CHARACTER VARYING" @@ -952,7 +946,7 @@ :points-of-interest nil :lib/type :metadata/column}) -(defmethod field-metadata [:products :created-at] +(defmethod field-metadata-method [:products :created-at] [_table-name _field-name] {:description nil :database-type "TIMESTAMP WITH TIME ZONE" @@ -985,7 +979,7 @@ :points-of-interest nil :lib/type :metadata/column}) -(defmethod field-metadata [:products :vendor] +(defmethod field-metadata-method [:products :vendor] [_table-name _field-name] {:description nil :database-type "CHARACTER VARYING" @@ -1021,7 +1015,7 @@ :points-of-interest nil :lib/type :metadata/column}) -(defmethod field-metadata [:products :ean] +(defmethod field-metadata-method [:products :ean] [_table-name _field-name] {:description nil :database-type "CHARACTER VARYING" @@ -1057,7 +1051,7 @@ :points-of-interest nil :lib/type :metadata/column}) -(defmethod table-metadata :products +(defmethod table-metadata-method :products [_table-name] {:description nil :entity-type :entity/ProductTable @@ -1074,16 +1068,16 @@ :display-name "Products" :points-of-interest nil :lib/type :metadata/table - :fields [(field-metadata :products :id) - (field-metadata :products :rating) - (field-metadata :products :category) - (field-metadata :products :price) - (field-metadata :products :title) - (field-metadata :products :created-at) - (field-metadata :products :vendor) - (field-metadata :products :ean)]}) - -(defmethod field-metadata [:orders :id] + :fields [(field-metadata-method :products :id) + (field-metadata-method :products :rating) + (field-metadata-method :products :category) + (field-metadata-method :products :price) + (field-metadata-method :products :title) + (field-metadata-method :products :created-at) + (field-metadata-method :products :vendor) + (field-metadata-method :products :ean)]}) + +(defmethod field-metadata-method [:orders :id] [_table-name _field-name] {:description nil :database-type "BIGINT" @@ -1114,7 +1108,7 @@ :points-of-interest nil :lib/type :metadata/column}) -(defmethod field-metadata [:orders :subtotal] +(defmethod field-metadata-method [:orders :subtotal] [_table-name _field-name] {:description nil :database-type "DOUBLE PRECISION" @@ -1152,7 +1146,7 @@ :points-of-interest nil :lib/type :metadata/column}) -(defmethod field-metadata [:orders :total] +(defmethod field-metadata-method [:orders :total] [_table-name _field-name] {:description nil :database-type "DOUBLE PRECISION" @@ -1190,7 +1184,7 @@ :points-of-interest nil :lib/type :metadata/column}) -(defmethod field-metadata [:orders :tax] +(defmethod field-metadata-method [:orders :tax] [_table-name _field-name] {:description nil :database-type "DOUBLE PRECISION" @@ -1228,7 +1222,7 @@ :points-of-interest nil :lib/type :metadata/column}) -(defmethod field-metadata [:orders :discount] +(defmethod field-metadata-method [:orders :discount] [_table-name _field-name] {:description nil :database-type "DOUBLE PRECISION" @@ -1265,7 +1259,7 @@ :points-of-interest nil :lib/type :metadata/column}) -(defmethod field-metadata [:orders :quantity] +(defmethod field-metadata-method [:orders :quantity] [_table-name _field-name] {:description nil :database-type "INTEGER" @@ -1302,7 +1296,7 @@ :points-of-interest nil :lib/type :metadata/column}) -(defmethod field-metadata [:orders :created-at] +(defmethod field-metadata-method [:orders :created-at] [_table-name _field-name] {:description nil :database-type "TIMESTAMP WITH TIME ZONE" @@ -1335,7 +1329,7 @@ :points-of-interest nil :lib/type :metadata/column}) -(defmethod field-metadata [:orders :product-id] +(defmethod field-metadata-method [:orders :product-id] [_table-name _field-name] {:description nil :database-type "INTEGER" @@ -1366,7 +1360,7 @@ :points-of-interest nil :lib/type :metadata/column}) -(defmethod field-metadata [:orders :user-id] +(defmethod field-metadata-method [:orders :user-id] [_table-name _field-name] {:description nil :database-type "INTEGER" @@ -1397,7 +1391,7 @@ :points-of-interest nil :lib/type :metadata/column}) -(defmethod table-metadata :orders +(defmethod table-metadata-method :orders [_table-name] {:description nil :entity-type :entity/TransactionTable @@ -1414,17 +1408,17 @@ :display-name "Orders" :points-of-interest nil :lib/type :metadata/table - :fields [(field-metadata :orders :id) - (field-metadata :orders :subtotal) - (field-metadata :orders :total) - (field-metadata :orders :tax) - (field-metadata :orders :discount) - (field-metadata :orders :quantity) - (field-metadata :orders :created-at) - (field-metadata :orders :product-id) - (field-metadata :orders :user-id)]}) - -(defmethod field-metadata [:people :id] + :fields [(field-metadata-method :orders :id) + (field-metadata-method :orders :subtotal) + (field-metadata-method :orders :total) + (field-metadata-method :orders :tax) + (field-metadata-method :orders :discount) + (field-metadata-method :orders :quantity) + (field-metadata-method :orders :created-at) + (field-metadata-method :orders :product-id) + (field-metadata-method :orders :user-id)]}) + +(defmethod field-metadata-method [:people :id] [_table-name _field-name] {:description nil :database-type "BIGINT" @@ -1455,7 +1449,7 @@ :points-of-interest nil :lib/type :metadata/column}) -(defmethod field-metadata [:people :state] +(defmethod field-metadata-method [:people :state] [_table-name _field-name] {:description nil :database-type "CHARACTER VARYING" @@ -1491,7 +1485,7 @@ :points-of-interest nil :lib/type :metadata/column}) -(defmethod field-metadata [:people :city] +(defmethod field-metadata-method [:people :city] [_table-name _field-name] {:description nil :database-type "CHARACTER VARYING" @@ -1527,7 +1521,7 @@ :points-of-interest nil :lib/type :metadata/column}) -(defmethod field-metadata [:people :address] +(defmethod field-metadata-method [:people :address] [_table-name _field-name] {:description nil :database-type "CHARACTER VARYING" @@ -1563,7 +1557,7 @@ :points-of-interest nil :lib/type :metadata/column}) -(defmethod field-metadata [:people :name] +(defmethod field-metadata-method [:people :name] [_table-name _field-name] {:description nil :database-type "CHARACTER VARYING" @@ -1599,7 +1593,7 @@ :points-of-interest nil :lib/type :metadata/column}) -(defmethod field-metadata [:people :source] +(defmethod field-metadata-method [:people :source] [_table-name _field-name] {:description nil :database-type "CHARACTER VARYING" @@ -1635,7 +1629,7 @@ :points-of-interest nil :lib/type :metadata/column}) -(defmethod field-metadata [:people :zip] +(defmethod field-metadata-method [:people :zip] [_table-name _field-name] {:description nil :database-type "CHARACTER VARYING" @@ -1671,7 +1665,7 @@ :points-of-interest nil :lib/type :metadata/column}) -(defmethod field-metadata [:people :latitude] +(defmethod field-metadata-method [:people :latitude] [_table-name _field-name] {:description nil :database-type "DOUBLE PRECISION" @@ -1708,7 +1702,7 @@ :points-of-interest nil :lib/type :metadata/column}) -(defmethod field-metadata [:people :password] +(defmethod field-metadata-method [:people :password] [_table-name _field-name] {:description nil :database-type "CHARACTER VARYING" @@ -1744,7 +1738,7 @@ :points-of-interest nil :lib/type :metadata/column}) -(defmethod field-metadata [:people :birth-date] +(defmethod field-metadata-method [:people :birth-date] [_table-name _field-name] {:description nil :database-type "DATE" @@ -1777,7 +1771,7 @@ :points-of-interest nil :lib/type :metadata/column}) -(defmethod field-metadata [:people :longitude] +(defmethod field-metadata-method [:people :longitude] [_table-name _field-name] {:description nil :database-type "DOUBLE PRECISION" @@ -1814,7 +1808,7 @@ :points-of-interest nil :lib/type :metadata/column}) -(defmethod field-metadata [:people :email] +(defmethod field-metadata-method [:people :email] [_table-name _field-name] {:description nil :database-type "CHARACTER VARYING" @@ -1850,7 +1844,7 @@ :points-of-interest nil :lib/type :metadata/column}) -(defmethod field-metadata [:people :created-at] +(defmethod field-metadata-method [:people :created-at] [_table-name _field-name] {:description nil :database-type "TIMESTAMP WITH TIME ZONE" @@ -1883,7 +1877,7 @@ :points-of-interest nil :lib/type :metadata/column}) -(defmethod table-metadata :people +(defmethod table-metadata-method :people [_table-name] {:description nil :entity-type :entity/UserTable @@ -1900,21 +1894,21 @@ :display-name "People" :points-of-interest nil :lib/type :metadata/table - :fields [(field-metadata :people :id) - (field-metadata :people :state) - (field-metadata :people :city) - (field-metadata :people :address) - (field-metadata :people :name) - (field-metadata :people :source) - (field-metadata :people :zip) - (field-metadata :people :latitude) - (field-metadata :people :password) - (field-metadata :people :birth-date) - (field-metadata :people :longitude) - (field-metadata :people :email) - (field-metadata :people :created-at)]}) - -(defmethod field-metadata [:reviews :id] + :fields [(field-metadata-method :people :id) + (field-metadata-method :people :state) + (field-metadata-method :people :city) + (field-metadata-method :people :address) + (field-metadata-method :people :name) + (field-metadata-method :people :source) + (field-metadata-method :people :zip) + (field-metadata-method :people :latitude) + (field-metadata-method :people :password) + (field-metadata-method :people :birth-date) + (field-metadata-method :people :longitude) + (field-metadata-method :people :email) + (field-metadata-method :people :created-at)]}) + +(defmethod field-metadata-method [:reviews :id] [_table-name _field-name] {:description nil :database-type "BIGINT" @@ -1945,7 +1939,7 @@ :points-of-interest nil :lib/type :metadata/column}) -(defmethod field-metadata [:reviews :created-at] +(defmethod field-metadata-method [:reviews :created-at] [_table-name _field-name] {:description nil :database-type "TIMESTAMP WITH TIME ZONE" @@ -1978,7 +1972,7 @@ :points-of-interest nil :lib/type :metadata/column}) -(defmethod field-metadata [:reviews :rating] +(defmethod field-metadata-method [:reviews :rating] [_table-name _field-name] {:description nil :database-type "INTEGER" @@ -2015,7 +2009,7 @@ :points-of-interest nil :lib/type :metadata/column}) -(defmethod field-metadata [:reviews :reviewer] +(defmethod field-metadata-method [:reviews :reviewer] [_table-name _field-name] {:description nil :database-type "CHARACTER VARYING" @@ -2051,7 +2045,7 @@ :points-of-interest nil :lib/type :metadata/column}) -(defmethod field-metadata [:reviews :body] +(defmethod field-metadata-method [:reviews :body] [_table-name _field-name] {:description nil :database-type "CHARACTER VARYING" @@ -2087,7 +2081,7 @@ :points-of-interest nil :lib/type :metadata/column}) -(defmethod field-metadata [:reviews :product-id] +(defmethod field-metadata-method [:reviews :product-id] [_table-name _field-name] {:description nil :database-type "INTEGER" @@ -2118,7 +2112,7 @@ :points-of-interest nil :lib/type :metadata/column}) -(defmethod table-metadata :reviews +(defmethod table-metadata-method :reviews [_table-name] {:description nil :entity-type :entity/GenericTable @@ -2135,12 +2129,12 @@ :display-name "Reviews" :points-of-interest nil :lib/type :metadata/table - :fields [(field-metadata :reviews :id) - (field-metadata :reviews :created-at) - (field-metadata :reviews :rating) - (field-metadata :reviews :reviewer) - (field-metadata :reviews :body) - (field-metadata :reviews :product-id)]}) + :fields [(field-metadata-method :reviews :id) + (field-metadata-method :reviews :created-at) + (field-metadata-method :reviews :rating) + (field-metadata-method :reviews :reviewer) + (field-metadata-method :reviews :body) + (field-metadata-method :reviews :product-id)]}) (def metadata "Complete Database metadata for testing, captured from a call to `GET /api/database/:id/metadata`. For the H2 version @@ -2177,14 +2171,14 @@ :name "test-data" :settings nil :caveats nil - :tables [(table-metadata :categories) - (table-metadata :checkins) - (table-metadata :users) - (table-metadata :venues) - (table-metadata :products) - (table-metadata :orders) - (table-metadata :people) - (table-metadata :reviews)] + :tables [(table-metadata-method :categories) + (table-metadata-method :checkins) + (table-metadata-method :users) + (table-metadata-method :venues) + (table-metadata-method :products) + (table-metadata-method :orders) + (table-metadata-method :people) + (table-metadata-method :reviews)] :creator-id nil :is-full-sync true :cache-ttl nil @@ -2203,6 +2197,19 @@ "[[metabase.lib.metadata.protocols/MetadataProvider]] using the test [[metadata]]." (meta.graph-provider/->SimpleGraphMetadataProvider metadata)) +(mu/defn table-metadata :- lib.metadata/TableMetadata + "Get Table metadata for a one of the `test-data` Tables in the test metadata, e.g. `:venues`. This is here so you can + test things that should consume Table metadata." + [table-name :- :keyword] + (dissoc (table-metadata-method table-name) :fields :metrics :segments)) + +(mu/defn field-metadata :- lib.metadata/ColumnMetadata + "Get Field metadata for one of the `test-data` Fields in the test metadata, e.g. `:venues` `:name`. This is here so + you can test things that should consume Field metadata." + [table-name :- :keyword + field-name :- :keyword] + (field-metadata-method table-name field-name)) + (def card-results-metadata "Capture of the `result_metadata` saved with a Card with a `SELECT * FROM VENUES;` query. Actually this is a little different because this is pre-massaged into the MLv2 shape (it has `:lib/type` and uses `kebab-case` keys), to make diff --git a/test/metabase/lib/test_util.cljc b/test/metabase/lib/test_util.cljc index 202af1ab3519d9fc73ef158513ae453c4ab932d9..55a0268bff4a25b41d600dabf1ba72ac2672618d 100644 --- a/test/metabase/lib/test_util.cljc +++ b/test/metabase/lib/test_util.cljc @@ -109,10 +109,9 @@ :aggregation [[:count]] :breakout [[:field (meta/id :checkins :user-id) nil]]}}}]}))) -(defn query-with-card-source-table +(def query-with-card-source-table "A query with a `card__<id>` source Table, and a metadata provider that has that Card. Card's name is `My Card`. Card 'exports' two columns, `USER_ID` and `count`." - [] {:lib/type :mbql/query :lib/metadata metadata-provider-with-card :database (meta/id) @@ -158,9 +157,8 @@ :field_ref [:aggregation 0] :effective_type :type/BigInteger}]}]}))) -(defn query-with-card-source-table-with-result-metadata +(def query-with-card-source-table-with-result-metadata "A query with a `card__<id>` source Table and a metadata provider that has a Card with `:result_metadata`." - [] {:lib/type :mbql/query :lib/metadata metadata-provider-with-card-with-result-metadata :type :pipeline @@ -168,27 +166,33 @@ :stages [{:lib/type :mbql.stage/mbql :source-card 1}]}) -(defn query-with-join - "A query against `VENUES` with an explicit join against `CATEGORIES`." - [] - (-> (lib/query meta/metadata-provider (meta/table-metadata :venues)) - (lib/join (-> (lib/join-clause - (meta/table-metadata :categories) - [(lib/= - (meta/field-metadata :venues :category-id) - (lib/with-join-alias (meta/field-metadata :categories :id) "Cat"))]) - (lib/with-join-alias "Cat") +(defn- add-join + [query join-alias] + (-> query + (lib/join (-> (lib/join-clause (meta/table-metadata :categories)) + (lib/with-join-alias join-alias) + (lib/with-join-conditions + [(lib/= (meta/field-metadata :venues :category-id) + (lib/with-join-alias (meta/field-metadata :categories :id) join-alias))]) (lib/with-join-fields :all))))) -(defn query-with-expression +(defn add-joins + "Add joins with `join-aliases` against `CATEGORIES`. Assumes source table is `VENUES`, but that really shouldn't matter + for most tests." + [query & join-aliases] + (reduce add-join query join-aliases)) + +(def query-with-join + "A query against `VENUES` with an explicit join against `CATEGORIES`." + (add-joins venues-query "Cat")) + +(def query-with-expression "A query with an expression." - [] - (-> (lib/query meta/metadata-provider (meta/table-metadata :venues)) + (-> venues-query (lib/expression "expr" (lib/absolute-datetime "2020" :month)))) -(defn native-query +(def native-query "A sample native query." - [] {:lib/type :mbql/query :lib/metadata meta/metadata-provider :database (meta/id)