From 77b649dbcbe295b497b3265b50d97928fe9b6da6 Mon Sep 17 00:00:00 2001
From: Alexander Solovyov <alexander@solovyov.net>
Date: Fri, 8 Mar 2024 15:39:26 +0300
Subject: [PATCH] [chain filters] should not be using inactive fields for joins
 (#39635)

---
 src/metabase/models/params/chain_filter.clj   | 24 ++++++----
 .../models/params/chain_filter_test.clj       | 44 +++++++++++++++++++
 2 files changed, 60 insertions(+), 8 deletions(-)

diff --git a/src/metabase/models/params/chain_filter.clj b/src/metabase/models/params/chain_filter.clj
index d65028bdaaa..98bd4d495c6 100644
--- a/src/metabase/models/params/chain_filter.clj
+++ b/src/metabase/models/params/chain_filter.clj
@@ -195,12 +195,17 @@
                                            [:pk-field.id :f2]
                                            [:pk-field.table_id :t2]]
                                :from      [[:metabase_field :fk-field]]
-                               :left-join [[:metabase_table :fk-table]    [:= :fk-field.table_id :fk-table.id]
+                               :left-join [[:metabase_table :fk-table]    [:and [:= :fk-field.table_id :fk-table.id]
+                                                                                :fk-table.active]
                                            [:metabase_database :database] [:= :fk-table.db_id :database.id]
-                                           [:metabase_field :pk-field]    [:= :fk-field.fk_target_field_id :pk-field.id]]
+                                           [:metabase_field :pk-field]    [:and [:= :fk-field.fk_target_field_id :pk-field.id]
+                                                                                :pk-field.active]]
                                :where     [:and
                                            [:= :database.id database-id]
-                                           [:not= :fk-field.fk_target_field_id nil]]})]
+                                           [:not= :fk-field.fk_target_field_id nil]
+                                           :fk-field.active]
+                               :order-by [[:fk-field.id :desc]
+                                          [:pk-field.id :desc]]})]
     (reduce
      (partial merge-with merge)
      {}
@@ -298,11 +303,14 @@
                                   enable-reverse-joins?])}
            find-joins*
            :ttl/threshold find-joins-cache-duration-ms)]
-    (fn
-      ([database-id source-table-id other-table-id]
-       (f database-id source-table-id other-table-id *enable-reverse-joins*))
-      ([database-id source-table-id other-table-id enable-reverse-joins?]
-       (f database-id source-table-id other-table-id enable-reverse-joins?)))))
+    ;; expose memoize metadata
+    (with-meta
+     (fn
+       ([database-id source-table-id other-table-id]
+        (f database-id source-table-id other-table-id *enable-reverse-joins*))
+       ([database-id source-table-id other-table-id enable-reverse-joins?]
+        (f database-id source-table-id other-table-id enable-reverse-joins?)))
+     (meta f))))
 
 (def ^:private ^{:arglists '([source-table other-table-ids enable-reverse-joins?])} find-all-joins*
   (memoize/ttl
diff --git a/test/metabase/models/params/chain_filter_test.clj b/test/metabase/models/params/chain_filter_test.clj
index be9c456e8a6..45f628ddf6b 100644
--- a/test/metabase/models/params/chain_filter_test.clj
+++ b/test/metabase/models/params/chain_filter_test.clj
@@ -670,3 +670,47 @@
           (testing "`true` if the limit is less than the number of values the field has"
             (is (= true
                    (:has_more_values (chain-filter venues.latitude {venues.price 4} :limit 1))))))))))
+
+;; TODO: make this test parallel, but clj-kondo complains about t2/update! being destructive and no amount of
+;; :clj-kondo/ignore convinces it.
+(deftest chain-filter-inactive-test
+  (testing "Inactive fields are not used to generate joins"
+    ;; custom dataset so that destructive operations (especially marking PK inactive) won't have any effect on other
+    ;; tests
+    (mt/with-temp-test-data [["users"
+                              [{:field-name "name"
+                                :base-type :type/Text}]
+                              []]
+                             ["messages"
+                              [{:field-name "receiver_id"
+                                :base-type :type/Integer
+                                :fk :users}
+                               {:field-name "sender_id"
+                                :base-type :type/Integer
+                                :fk :users}]
+                              []]]
+      (mt/$ids nil
+        (mt/with-dynamic-redefs [chain-filter/database-fk-relationships @#'chain-filter/database-fk-relationships*
+                                 chain-filter/find-joins                (fn
+                                                                          ([a b c]
+                                                                           (#'chain-filter/find-joins* a b c false))
+                                                                          ([a b c d]
+                                                                           (#'chain-filter/find-joins* a b c d)))]
+          (testing "receiver_id is active and should be used for the join"
+            (is (= [{:lhs {:table $$messages, :field %messages.receiver_id}
+                     :rhs {:table $$users, :field %users.id}}]
+                   (#'chain-filter/find-joins (mt/id) $$messages $$users))))
+
+          (try
+            (t2/update! :model/Field {:id %messages.receiver_id} {:active false})
+            (testing "check that it switches to sender once receiver is inactive"
+              (is (= [{:lhs {:table $$messages, :field %messages.sender_id}
+                       :rhs {:table $$users, :field %users.id}}]
+                     (#'chain-filter/find-joins (mt/id) $$messages $$users))))
+            (finally
+              (t2/update! :model/Field {:id %messages.receiver_id} {:active true})))
+
+          ;; mark field
+          (t2/update! :model/Field {:id %users.id} {:active false})
+          (testing "there are no connections when PK is inactive"
+            (is (nil? (#'chain-filter/find-joins (mt/id) $$messages $$users)))))))))
-- 
GitLab