Skip to content

Commit

Permalink
Fix query building for related updates (metabase#50001)
Browse files Browse the repository at this point in the history
  • Loading branch information
crisptrutski authored Nov 14, 2024
1 parent aaedddf commit 70ea4a0
Show file tree
Hide file tree
Showing 3 changed files with 54 additions and 8 deletions.
17 changes: 16 additions & 1 deletion src/metabase/search/postgres/ingestion.clj
Original file line number Diff line number Diff line change
Expand Up @@ -139,10 +139,25 @@
(doseq [update updates]
(queue/put-with-delay! queue delay-ms update)))))

(defn- impossible-condition?
"An (incomplete) check where queries will definitely return nothing, to help avoid spurious index update queries."
[where]
(when (vector? where)
(case (first where)
:= (let [[a b] (rest where)]
(and (string? a) (string? b) (not= a b)))
:!= (let [[a b] (rest where)]
(and (string? a) (string? b) (= a b)))
:and (boolean (some impossible-condition? (rest where)))
:or (every? impossible-condition? (rest where))
false)))

(defn update-index!
"Given a new or updated instance, create or update all the corresponding search entries if needed."
[instance & [always?]]
(when-let [updates (seq (search.spec/search-models-to-update instance always?))]
(when-let [updates (->> (search.spec/search-models-to-update instance always?)
(remove (comp impossible-condition? second))
seq)]
;; We need to delay execution to handle deletes, which alert us *before* updating the database.
(ingest-maybe-async! updates)
nil))
Expand Down
26 changes: 19 additions & 7 deletions src/metabase/search/spec.clj
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@
[malli.error :as me]
[metabase.config :as config]
[metabase.util :as u]
[toucan2.core :as t2]))
[toucan2.core :as t2]
[toucan2.tools.transformed :as t2.transformed]))

(def ^:private SearchModel
[:enum "dashboard" "table" "dataset" "segment" "collection" "database" "action" "indexed-entity" "metric" "card"])
Expand Down Expand Up @@ -259,15 +260,26 @@
(for [[search-model spec-fn] (methods spec)]
(search-model-hooks (spec-fn search-model)))))

(defn- instance->db-values
"Given a transformed toucan map, get back a mapping to the raw db values that we can use in a query."
[instance]
(let [xforms (#'t2.transformed/in-transforms (t2/model instance))]
(reduce-kv
(fn [m k v]
(assoc m k (if-let [f (get xforms k)] (f v) v)))
{}
instance)))

(defn search-models-to-update
"Given an updated or created instance, return a description of which search-models to (re)index."
[instance & [always?]]
(into #{}
(keep
(fn [{:keys [search-model fields where]}]
(when (or always? (and fields (some fields (keys (or (t2/changes instance) instance)))))
[search-model (insert-values where :updated instance)])))
(get (model-hooks) (t2/model instance))))
(let [raw-values (delay (instance->db-values instance))]
(into #{}
(keep
(fn [{:keys [search-model fields where]}]
(when (or always? (and fields (some fields (keys (or (t2/changes instance) instance)))))
[search-model (insert-values where :updated @raw-values)])))
(get (model-hooks) (t2/model instance)))))

(comment
(doseq [d (descendants :hook/search-index)]
Expand Down
19 changes: 19 additions & 0 deletions test/metabase/search/postgres/ingestion_test.clj
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
(ns metabase.search.postgres.ingestion-test
(:require
[clojure.test :refer :all]
[metabase.search.postgres.ingestion :as search.ingestion]))

(def ^:private impossible-condition? #'search.ingestion/impossible-condition?)

(deftest impossible-condition?-test
(is (not (impossible-condition? [:= "card" :this.type])))
(is (not (impossible-condition? [:= :that.type :this.type])))
(is (impossible-condition? [:= "card" "dashboard"]))
(is (not (impossible-condition? [:= "card" "card"])))
(is (not (impossible-condition? [:!= "card" "dashboard"])))
(is (impossible-condition? [:!= "card" "card"]))
(is (not (impossible-condition? [:and [:= 1 :this.id] [:= "card" :this.type]])))
(is (impossible-condition? [:and [:= 1 :this.id] [:!= "card" "card"]]))
(is (not (impossible-condition? [:and [:= 1 :this.id] [:!= "card" "dashboard"]])))
(is (not (impossible-condition? [:or [:= 1 :this.id] [:!= "card" "dashboard"]])))
(is (impossible-condition? [:or [:= "oh" "no"] [:= "card" "dashboard"]])))

0 comments on commit 70ea4a0

Please sign in to comment.