Skip to content
Snippets Groups Projects
Unverified Commit b47f5045 authored by metamben's avatar metamben Committed by GitHub
Browse files

Add function for replacing filters (#29364)

* Add function for updating filters
parent 7abc9971
Branches
Tags
No related merge requests found
......@@ -106,6 +106,7 @@
add-filter
current-filter
current-filters
replace-filter
and
or
not
......
......@@ -312,3 +312,15 @@
(let [stage-number (clojure.core/or stage-number -1)
new-filter (->filter-clause query stage-number boolean-expression)]
(lib.util/update-query-stage query stage-number update :filter conjoin new-filter))))
(mu/defn replace-filter :- :metabase.lib.schema/query
"Replaces the expression with `target-uuid` with `boolean-expression` the filter of `query`."
([query target-uuid boolean-expression]
(metabase.lib.filter/replace-filter query -1 target-uuid boolean-expression))
([query stage-number target-uuid boolean-expression]
(let [stage-number (clojure.core/or stage-number -1)
new-filter (->filter-clause query stage-number boolean-expression)]
(lib.util/update-query-stage query stage-number
update :filter
lib.util/replace-clause target-uuid new-filter))))
......@@ -25,6 +25,34 @@
:cljs
(def format "Exactly like [[clojure.core/format]] but ClojureScript-friendly." gstring/format))
(defn- clause? [clause]
(and (vector? clause)
(> (count clause) 1)
(keyword? (first clause))))
(defn- clause-uuid [clause]
(when (clause? clause)
(get-in clause [1 :lib/uuid])))
(defn replace-clause
"Replace the clause with `target-uuid` in `clause` with `new-clause`.
If `clause` itself has :lib/uuid equal to `target-uuid`, `new-clause` is returned.
If `clause` contains no clause with `target-uuid` no replacement happens.
Since UUIDs are assumed to be unique, at most one replacement happens."
[clause target-uuid new-clause]
(if (clause? clause)
(if (= (clause-uuid clause) target-uuid)
new-clause
(reduce (fn [clause i]
(let [arg (clause i)
arg' (replace-clause arg target-uuid new-clause)]
(if (not= arg' arg)
(reduced (assoc clause i arg'))
clause)))
clause
(range 2 (count clause))))
clause))
;;; TODO -- all of this `->pipeline` stuff should probably be merged into [[metabase.lib.convert]] at some point in
;;; the near future.
......
......@@ -3,6 +3,7 @@
[clojure.test :refer [deftest is testing]]
[metabase.lib.core :as lib]
[metabase.lib.metadata :as lib.metadata]
[metabase.lib.options :as lib.options]
[metabase.lib.test-metadata :as meta]
#?@(:cljs ([metabase.test-runner.assert-exprs.approximately-equal]))))
......@@ -261,3 +262,81 @@
(is (=? extended-and-query third-add))
(is (=? [first-filter second-filter third-filter]
(lib/current-filters third-add))))))
(deftest ^:parallel replace-filter-test
(let [q1 (lib/query-for-table-name meta/metadata-provider "CATEGORIES")
venues-name-metadata (lib.metadata/field q1 nil "VENUES" "NAME")
venues-category-id-metadata (lib.metadata/field q1 nil "VENUES" "CATEGORY_ID")
simple-filtered-query
(-> q1
(lib/filter (lib/->between venues-category-id-metadata 42 100)))
between-uuid (-> (lib/current-filter simple-filtered-query)
lib.options/options
:lib/uuid)
result-query
(assoc-in simple-filtered-query
[:stages 0 :filter]
[:starts-with
{:lib/uuid string?}
[:field {:base-type :type/Text, :lib/uuid string?} (meta/id :venues :name)]
"part"])]
(testing "sanity"
(is (string? between-uuid)))
(testing "replacing a simple filter"
(is (=? result-query
(lib/replace-filter simple-filtered-query
between-uuid
(lib/starts-with
{:lib/metadata meta/metadata}
0
venues-name-metadata
"part")))))
(testing "setting a simple filter thunk"
(is (=? result-query
(lib/replace-filter simple-filtered-query
between-uuid
(lib/->starts-with venues-name-metadata "part")))))
(testing "setting a simple filter expression"
(is (=? result-query
(lib/replace-filter simple-filtered-query
between-uuid
[:starts-with venues-name-metadata "part"]))))
(let [contains-uuid (str (random-uuid))
nested-filtered-query
{:lib/type :mbql/query,
:database (meta/id),
:type :pipeline,
:stages
[{:lib/type :mbql.stage/mbql,
:source-table (meta/id :categories)
:lib/options {:lib/uuid (str (random-uuid))}
:filter
[:or
{:lib/uuid (str (random-uuid))}
[:between
{:lib/uuid (str (random-uuid))}
[:field {:base-type :type/Integer, :lib/uuid (str (random-uuid))} (meta/id :venues :category-id)]
42
100]
[:and
{:lib/uuid (str (random-uuid))}
[:contains
{:lib/uuid contains-uuid}
[:field {:base-type :type/Text, :lib/uuid (str (random-uuid))} (meta/id :venues :name)]
"part"]
[:=
{:lib/uuid (str (random-uuid))}
[:field {:base-type :type/Integer, :lib/uuid (str (random-uuid))} (meta/id :venues :category-id)]
242
[:field {:base-type :type/BigInteger, :lib/uuid (str (random-uuid))} "ID"]]]]}]}]
(testing "setting a nested filter expression"
(is (=? (assoc-in nested-filtered-query
[:stages 0 :filter 3 2]
[:starts-with
{:lib/uuid string?}
[:field {:base-type :type/Text, :lib/uuid string?} (meta/id :venues :name)]
"part"])
(lib/replace-filter nested-filtered-query
contains-uuid
[:starts-with venues-name-metadata "part"])))))))
(ns metabase.lib.util-test
(:require
[clojure.test :refer [are deftest is testing]]
[clojure.test.check.generators :as gen]
[com.gfredericks.test.chuck.clojure-test :refer [checking]]
[metabase.lib.test-metadata :as meta]
[metabase.lib.util :as lib.util]
#?@(:cljs ([metabase.test-runner.assert-exprs.approximately-equal]))))
......@@ -215,3 +217,28 @@
["a" "b"] "a and b"
["a" "b" "c"] "a, b, and c"
["a" "b" "c" "d"] "a, b, c, and d"))
(deftest ^:parallel replace-clause-test
(checking "can be called with anything"
[x gen/any]
(is (identical? x (lib.util/replace-clause x
(str (random-uuid))
(random-uuid)))))
(let [target-uuid (random-uuid)
replacement (random-uuid)]
(testing "full replacement"
(is (identical? replacement
(lib.util/replace-clause [:= {:lib/uuid target-uuid}]
target-uuid
replacement))))
(let [clause [:= {:lib/uuid (str (random-uuid))}
3
[:field {:lib/uuid (str (random-uuid))}]
[:+ 2 [:expression {:lib/uuid target-uuid} "a"] 7]]]
(testing "nested replacement"
(is (= (assoc-in clause [4 2] replacement)
(lib.util/replace-clause clause target-uuid replacement))))
(testing "only one occurrence is replaced"
(let [clause (assoc clause 5 [:< [:field {:lib/uuid target-uuid} 2]])]
(is (= (assoc-in clause [4 2] replacement)
(lib.util/replace-clause clause target-uuid replacement))))))))
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment