diff --git a/src/metabase/lib/convert.cljc b/src/metabase/lib/convert.cljc index bb7870dd70fe2d1d84c8b0498118cb12c0b2ae33..c5d63620a9152390af124cec158784c1b027676c 100644 --- a/src/metabase/lib/convert.cljc +++ b/src/metabase/lib/convert.cljc @@ -211,7 +211,7 @@ (update :stages ->pMBQL))] (cond-> join (:fields join) (update :fields (fn [fields] - (if (seqable? fields) + (if (sequential? fields) (mapv ->pMBQL fields) (keyword fields)))) (not (:alias join)) (assoc :alias legacy-default-join-alias)))) diff --git a/src/metabase/lib/remove_replace.cljc b/src/metabase/lib/remove_replace.cljc index 306a0dd663558a99ea56f5f91b8fd37aa37b05e9..f0bbe0a1aa45331caffff91beac2732f77c3a678 100644 --- a/src/metabase/lib/remove_replace.cljc +++ b/src/metabase/lib/remove_replace.cljc @@ -197,7 +197,7 @@ (if location (-> query (remove-replace-location stage-number query location target-clause remove-replace-fn) - normalize-fields-clauses) + (normalize-fields-clauses location)) query)))) (mu/defn remove-clause :- :metabase.lib.schema/query @@ -563,25 +563,36 @@ (lib.equality/matching-column-sets? query stage-number fields (lib.metadata.calculation/default-columns-for-stage query stage-number))))) -(defn- normalize-fields-for-join [query stage-number join] - (if (#{:none :all} (:fields join)) +(defn- normalize-fields-for-join [query stage-number removed-location join] + (cond ;; Nothing to do if it's already a keyword. - join - (cond-> join - (lib.equality/matching-column-sets? - query stage-number (:fields join) - (lib.metadata.calculation/returned-columns query stage-number (assoc join :fields :all))) - (assoc :fields :all)))) - -(defn- normalize-fields-for-stage [query stage-number] + (#{:none :all} (:fields join)) join + + ;; If it's missing, treat it as `:all` unless we just removed a field. + ;; TODO: This really should be a different function called by `remove-field`; it also needs to filter on the stage. + (and (or (= removed-location [:aggregation]) + (= removed-location [:breakout])) + (not (contains? join :fields))) + (assoc join :fields :all) + + (lib.equality/matching-column-sets? + query stage-number (:fields join) + (lib.metadata.calculation/returned-columns query stage-number (assoc join :fields :all))) + (assoc join :fields :all) + + :else join)) + +(defn- normalize-fields-for-stage [query stage-number removed-location] (let [stage (lib.util/query-stage query stage-number)] (cond-> query (specifies-default-fields? query stage-number) (lib.util/update-query-stage stage-number dissoc :fields) - (:joins stage) + (and (empty? (:aggregation stage)) + (empty? (:breakout stage)) + (:joins stage)) (lib.util/update-query-stage stage-number update :joins - (partial mapv #(normalize-fields-for-join query stage-number %)))))) + (partial mapv #(normalize-fields-for-join query stage-number removed-location %)))))) (mu/defn normalize-fields-clauses :- :metabase.lib.schema/query "Check all the `:fields` clauses in the query - on the stages and any joins - and drops them if they are equal to the @@ -589,5 +600,10 @@ - For stages, if the `:fields` list is identical to the default fields for this stage. - For joins, replace it with `:all` if it's all the fields that are in the join by default. - For joins, remove it if the list is empty (the default for joins is no fields)." - [query :- :metabase.lib.schema/query] - (reduce normalize-fields-for-stage query (range (count (:stages query))))) + ([query :- :metabase.lib.schema/query] + (normalize-fields-clauses query nil)) + ([query :- :metabase.lib.schema/query + removed-location :- [:maybe [:sequential :any]]] + (reduce #(normalize-fields-for-stage %1 %2 removed-location) + query + (range (count (:stages query)))))) diff --git a/test/metabase/lib/remove_replace_test.cljc b/test/metabase/lib/remove_replace_test.cljc index b9a5744d67495b81cafb51c8ca1faafd5c5559c4..220759ffe973b2a26e8f9625e01b41e70c93a53e 100644 --- a/test/metabase/lib/remove_replace_test.cljc +++ b/test/metabase/lib/remove_replace_test.cljc @@ -1042,6 +1042,53 @@ (is (empty? (lib/aggregations result))) (is (= (lib/breakouts query) (lib/breakouts result)))))) +(deftest ^:parallel removing-last-aggregation-brings-back-all-fields-on-joins + (testing "Removing the last aggregation puts :fields :all on join clauses" + (let [base (-> lib.tu/venues-query + (lib/join (lib/join-clause (meta/table-metadata :products) + [(lib/= (meta/field-metadata :orders :product-id) + (meta/field-metadata :products :id))]))) + query (lib/aggregate base (lib/count)) + result (lib/remove-clause query (first (lib/aggregations query)))] + (is (= :all (-> base :stages first :joins first :fields))) + (is (= :all (-> result :stages first :joins first :fields))) + (is (=? (map :name (lib/returned-columns base)) + (map :name (lib/returned-columns result))))))) + +(deftest ^:parallel removing-last-breakout-brings-back-all-fields-on-joins + (testing "Removing the last breakout puts :fields :all on join clauses" + (let [base (-> lib.tu/venues-query + (lib/join (lib/join-clause (meta/table-metadata :products) + [(lib/= (meta/field-metadata :orders :product-id) + (meta/field-metadata :products :id))]))) + query (-> base + (lib/aggregate (lib/count)) + (lib/breakout (meta/field-metadata :products :category))) + agg (first (lib/aggregations query)) + brk (first (lib/breakouts query))] + (is (= :all (-> base :stages first :joins first :fields))) + + (testing "no change to join" + (testing "when removing just the aggregation" + (is (= (m/dissoc-in query [:stages 0 :aggregation]) + (lib/remove-clause query agg)))) + (testing "when removing just the breakout" + (is (= (m/dissoc-in query [:stages 0 :breakout]) + (lib/remove-clause query brk))))) + (testing "join gets :fields :all" + (testing "removing aggregation and then breakout" + (is (= :all + (-> query + (lib/remove-clause agg) + (lib/remove-clause brk) + :stages first :joins first :fields)))) + (testing "removing breakout and then aggregation" + (is (= :all + (-> query + (lib/remove-clause brk) + (lib/remove-clause agg) + :stages first :joins first :fields)))))))) + (deftest ^:parallel simple-tweak-expression-test (let [table (lib/query meta/metadata-provider (meta/table-metadata :orders)) base (lib/expression table "Tax Rate" (lib// (meta/field-metadata :orders :tax)