From a13940ac3ef3645b7b1abdf697f442ac85907a56 Mon Sep 17 00:00:00 2001 From: metamben <103100869+metamben@users.noreply.github.com> Date: Tue, 15 Aug 2023 07:32:06 +0300 Subject: [PATCH] Fix alias of LHS from join (#33103) Porting fix alias of joined column LHS of standard join conditions (#32987) * Check if the LHS has the alias of the current join before stripping its alias. * Remove dependent joins when removing a join --- src/metabase/lib/join.cljc | 10 ++-- src/metabase/lib/remove_replace.cljc | 39 +++++++++------ test/metabase/lib/join_test.cljc | 58 ++++++++++++++++++---- test/metabase/lib/remove_replace_test.cljc | 20 ++++++++ 4 files changed, 98 insertions(+), 29 deletions(-) diff --git a/src/metabase/lib/join.cljc b/src/metabase/lib/join.cljc index 4513a17e16f..992ebebc254 100644 --- a/src/metabase/lib/join.cljc +++ b/src/metabase/lib/join.cljc @@ -483,19 +483,19 @@ ;; so we break ties by looking at the poisition of the field reference. (mbql.u.match/replace condition [op op-opts (lhs :guard lib.util/field-clause?) (rhs :guard lib.util/field-clause?)] - (let [lhs-aliased (contains? (lib.options/options lhs) :join-alias) - rhs-aliased (contains? (lib.options/options rhs) :join-alias)] + (let [lhs-alias (current-join-alias lhs) + rhs-alias (current-join-alias rhs)] (cond ;; no sides obviously belong to joined - (not (or lhs-aliased rhs-aliased)) + (not (or lhs-alias rhs-alias)) (if (lib.equality/find-closest-matching-ref metadata-providerable rhs home-refs) [op op-opts (with-join-alias lhs join-alias) rhs] [op op-opts lhs (with-join-alias rhs join-alias)]) ;; both sides seem to belong to joined assuming this resulted from ;; overly fuzzy matching, we remove the join alias from the LHS - ;; unless the RHS seems to belong to home too while the LHS doen't - (and lhs-aliased rhs-aliased) + ;; unless the RHS seems to belong to home too while the LHS doesn't + (and (= lhs-alias join-alias) (= rhs-alias join-alias)) (let [bare-lhs (lib.options/update-options lhs dissoc :join-alias) bare-rhs (lib.options/update-options rhs dissoc :join-alias)] (if (and (nil? (lib.equality/find-closest-matching-ref metadata-providerable bare-lhs home-refs)) diff --git a/src/metabase/lib/remove_replace.cljc b/src/metabase/lib/remove_replace.cljc index 0e0d2c3aed0..7ed858c1397 100644 --- a/src/metabase/lib/remove_replace.cljc +++ b/src/metabase/lib/remove_replace.cljc @@ -103,16 +103,16 @@ (defn- remove-local-references [query stage-number unmodified-query-for-stage target-op target-opts target-ref-id] (let [stage (lib.util/query-stage query stage-number) to-remove (mapcat - (fn [location] - (when-let [clauses (get-in stage location)] - (->> clauses - (keep (fn [clause] - (mbql.match/match-one clause - [target-op - (_ :guard #(or (empty? target-opts) - (set/subset? (set target-opts) (set %)))) - target-ref-id] [location clause])))))) - (stage-paths query stage-number))] + (fn [location] + (when-let [clauses (get-in stage location)] + (->> clauses + (keep (fn [clause] + (mbql.match/match-one clause + [target-op + (_ :guard #(or (empty? target-opts) + (set/subset? (set target-opts) (set %)))) + target-ref-id] [location clause])))))) + (stage-paths query stage-number))] (reduce (fn [query [location target-clause]] (remove-replace-location query stage-number unmodified-query-for-stage location target-clause lib.util/remove-clause)) @@ -206,12 +206,15 @@ (replace-join query stage-number target-clause new-clause) (remove-replace* query stage-number target-clause :replace new-clause)))) +(defn- field-clause-with-join-alias? + [field-clause join-alias] + (and (lib.util/field-clause? field-clause) + (= (lib.join/current-join-alias field-clause) join-alias))) + (defn- replace-join-alias [a-join old-name new-name] (mbql.match/replace a-join - (field :guard (fn [field-clause] - (and (lib.util/field-clause? field-clause) - (= (lib.join/current-join-alias field-clause) old-name)))) + (field :guard #(field-clause-with-join-alias? % old-name)) (lib.join/with-join-alias field new-name))) (defn- rename-join-in-stage @@ -313,6 +316,14 @@ (remove-invalidated-refs query-after query stage-number))) query))) +(defn- has-field-from-join? [form join-alias] + (some? (mbql.match/match-one form + (field :guard #(field-clause-with-join-alias? % join-alias))))) + +(defn- dependent-join? [join join-alias] + (or (= (:alias join) join-alias) + (has-field-from-join? join join-alias))) + (mu/defn remove-join :- :metabase.lib.schema/query "Remove the join specified by `join-spec` in `query` at `stage-number`. The join can be specified either by itself (as returned by [[joins]]), by its alias @@ -327,7 +338,7 @@ stage-number :- :int join-spec :- [:or :metabase.lib.schema.join/join :string :int]] (update-joins query stage-number join-spec (fn [joins join-alias] - (not-empty (filterv #(not= (:alias %) join-alias) + (not-empty (filterv #(not (dependent-join? % join-alias)) joins)))))) (mu/defn replace-join :- :metabase.lib.schema/query diff --git a/test/metabase/lib/join_test.cljc b/test/metabase/lib/join_test.cljc index dafa5e4fcf4..35460d82f2c 100644 --- a/test/metabase/lib/join_test.cljc +++ b/test/metabase/lib/join_test.cljc @@ -15,6 +15,8 @@ #?(:cljs (comment metabase.test-runner.assert-exprs.approximately-equal/keep-me)) +(def ^:private absent-key-marker (symbol "nil #_\"key is not present.\"")) + (deftest ^:parallel resolve-join-test (let [query lib.tu/venues-query join-clause (-> (lib/join-clause @@ -41,7 +43,7 @@ {:lib/uuid string?} [:field {:lib/uuid string? - :join-alias (symbol "nil #_\"key is not present.\"")} + :join-alias absent-key-marker} (meta/id :venues :category-id)] [:field {:lib/uuid string? @@ -100,7 +102,7 @@ {:lib/uuid string?} [:field {:lib/uuid string? - :join-alias (symbol "nil #_\"key is not present.\"")} + :join-alias absent-key-marker} (meta/id :categories :id)] [:field {:lib/uuid string? @@ -140,7 +142,7 @@ [:field {:base-type :type/BigInteger :lib/uuid string? - :join-alias (symbol "nil #_\"key is not present.\"")} + :join-alias absent-key-marker} "ID"] [:field {:lib/uuid string? @@ -381,7 +383,7 @@ (are [strategy] (=? {:stages [{:joins [{:conditions [[:= {} [:field - {:join-alias (symbol "nil #_\"key is not present.\"")} + {:join-alias absent-key-marker} (meta/id :venues :category-id)] [:field {:join-alias "Categories"} @@ -457,8 +459,8 @@ conditions))))] (is (=? {:conditions [[:= {} [:field {} (meta/id :venues :category-id)] - [:field {:join-alias (symbol "nil #_\"key is not present.\"")} (meta/id :categories :id)]]] - :alias (symbol "nil #_\"key is not present.\"")} + [:field {:join-alias absent-key-marker} (meta/id :categories :id)]]] + :alias absent-key-marker} join)) (let [join' (lib/with-join-alias join "New Alias")] (is (=? {:conditions [[:= {} @@ -532,7 +534,7 @@ join' (lib/with-join-conditions join new-conditions)] (is (=? [[:= {} [:field {} (meta/id :venues :id)] - [:field {:join-alias (symbol "nil #_\"key is not present.\"")} (meta/id :categories :id)]]] + [:field {:join-alias absent-key-marker} (meta/id :categories :id)]]] (lib/join-conditions join'))) (testing "Adding an alias later with lib/with-join-alias should update conditions that are missing aliases" (let [join'' (lib/with-join-alias join' "New Alias")] @@ -553,7 +555,7 @@ conditions' (lib/join-conditions (lib/with-join-conditions join new-conditions))] (is (=? [[:between {} [:field {} (meta/id :venues :id)] - [:field {:join-alias (symbol "nil #_\"key is not present.\"")} + [:field {:join-alias absent-key-marker} (meta/id :categories :id)] 1]] conditions')))) @@ -569,10 +571,10 @@ (is (=? [[:and {} [:= {} [:field {} (meta/id :venues :id)] - [:field {:join-alias (symbol "nil #_\"key is not present.\"")} (meta/id :categories :id)]] + [:field {:join-alias absent-key-marker} (meta/id :categories :id)]] [:= {} [:field {} (meta/id :venues :category-id)] - [:field {:join-alias (symbol "nil #_\"key is not present.\"")} (meta/id :categories :id)]]]] + [:field {:join-alias absent-key-marker} (meta/id :categories :id)]]]] conditions'))))))) (defn- test-with-join-fields [input expected] @@ -1041,6 +1043,42 @@ products-created-at) :year))))) +(deftest ^:parallel default-join-alias-test + (testing "default join-alias set without overwriting other aliases (#32897)" + (let [query (-> (lib/query meta/metadata-provider (meta/table-metadata :venues)) + (lib/join (-> (lib/join-clause + (meta/table-metadata :checkins) + [(lib/= (meta/field-metadata :venues :id) + (-> (meta/field-metadata :checkins :venue-id) + (lib/with-join-alias "Checkins")))]) + (lib/with-join-fields :all) + (lib/with-join-alias "Checkins"))) + (lib/join (-> (lib/join-clause + (meta/table-metadata :users) + [(lib/= (-> (meta/field-metadata :checkins :user-id) + (lib/with-join-alias "Checkins")) + (meta/field-metadata :users :id))]) + (lib/with-join-fields :all))))] + (is (=? [{:lib/type :mbql/join, + :stages [{:lib/type :mbql.stage/mbql + :source-table (meta/id :checkins)}] + :fields :all + :conditions [[:= + {} + [:field {:join-alias absent-key-marker} (meta/id :venues :id)] + [:field {:join-alias "Checkins"} (meta/id :checkins :venue-id)]]] + :alias "Checkins"} + {:lib/type :mbql/join + :stages [{:lib/type :mbql.stage/mbql + :source-table (meta/id :users)}] + :fields :all, + :conditions [[:= + {} + [:field {:join-alias "Checkins"} (meta/id :checkins :user-id)] + [:field {:join-alias "Users"} (meta/id :users :id)]]], + :alias "Users"}] + (lib/joins query)))))) + (deftest ^:parallel join-condition-columns-handle-temporal-units-test (testing "join-condition-lhs-columns and -rhs-columns should correctly mark columns regardless of :temporal-unit (#32390)\n" (let [orders-query (lib/query meta/metadata-provider (meta/table-metadata :orders)) diff --git a/test/metabase/lib/remove_replace_test.cljc b/test/metabase/lib/remove_replace_test.cljc index 70b575ab1b1..26ea4b33960 100644 --- a/test/metabase/lib/remove_replace_test.cljc +++ b/test/metabase/lib/remove_replace_test.cljc @@ -766,6 +766,26 @@ (is (=? result (lib/remove-clause query' 0 (second (lib/joins query' 0))))))))))) +(deftest ^:parallel remove-dependent-join + (let [first-join-clause (-> (lib/join-clause + (meta/table-metadata :checkins) + [(lib/= (meta/field-metadata :venues :id) + (-> (meta/field-metadata :checkins :venue-id) + (lib/with-join-alias "Checkins")))]) + (lib/with-join-fields :all) + (lib/with-join-alias "Checkins")) + query (-> (lib/query meta/metadata-provider (meta/table-metadata :venues)) + (lib/join first-join-clause) + (lib/join (-> (lib/join-clause + (meta/table-metadata :users) + [(lib/= (-> (meta/field-metadata :checkins :user-id) + (lib/with-join-alias "Checkins")) + (-> (meta/field-metadata :users :id) + (lib/with-join-alias "Users")))]) + (lib/with-join-fields :all) + (lib/with-join-alias "Users"))))] + (is (nil? (lib/joins (lib/remove-clause query 0 first-join-clause)))))) + (deftest ^:parallel replace-join-test (let [query lib.tu/query-with-join expected-original {:stages [{:joins [{:lib/type :mbql/join, :alias "Cat", :fields :all}]}]} -- GitLab