From df163979fd0a8466045d6319e8dbd9dfd7723bc5 Mon Sep 17 00:00:00 2001 From: metamben <103100869+metamben@users.noreply.github.com> Date: Thu, 24 Aug 2023 21:06:00 +0300 Subject: [PATCH] Add filter-parts conversion helper (#33419) --- frontend/src/metabase-lib/filter.ts | 9 ++++++ frontend/src/metabase-lib/types.ts | 7 ++++ src/metabase/lib/core.cljc | 1 + src/metabase/lib/filter.cljc | 50 ++++++++++++++++++++++++----- src/metabase/lib/js.cljs | 9 ++++++ test/metabase/lib/filter_test.cljc | 28 ++++++++++++++++ 6 files changed, 96 insertions(+), 8 deletions(-) diff --git a/frontend/src/metabase-lib/filter.ts b/frontend/src/metabase-lib/filter.ts index 4f3ddc7bcb0..2cd69e732f0 100644 --- a/frontend/src/metabase-lib/filter.ts +++ b/frontend/src/metabase-lib/filter.ts @@ -7,6 +7,7 @@ import type { ExternalOp, FilterOperator, FilterClause, + FilterParts, Query, } from "./types"; @@ -50,3 +51,11 @@ export function filterOperator( ) { return ML.filter_operator(query, stageIndex, filterClause); } + +export function filterParts( + query: Query, + stageIndex: number, + filterClause: FilterClause, +): FilterParts { + return ML.filter_parts(query, stageIndex, filterClause); +} diff --git a/frontend/src/metabase-lib/types.ts b/frontend/src/metabase-lib/types.ts index be49cfc4da7..c7d28ce3e29 100644 --- a/frontend/src/metabase-lib/types.ts +++ b/frontend/src/metabase-lib/types.ts @@ -156,6 +156,13 @@ export type ExternalOp = { args: [ColumnMetadata, ...ExpressionArg[]]; }; +export type FilterParts = { + operator: FilterOperator; + options: Record<string, unknown>; + column: ColumnWithOperators | null; + args: ExpressionArg[]; +}; + declare const Join: unique symbol; export type Join = unknown & { _opaque: typeof Join }; diff --git a/src/metabase/lib/core.cljc b/src/metabase/lib/core.cljc index ae0befbcefe..6a3989a6468 100644 --- a/src/metabase/lib/core.cljc +++ b/src/metabase/lib/core.cljc @@ -158,6 +158,7 @@ filterable-column-operators filter-clause filter-operator + filter-parts and or not diff --git a/src/metabase/lib/filter.cljc b/src/metabase/lib/filter.cljc index e84e207cfc2..101e10a8d94 100644 --- a/src/metabase/lib/filter.cljc +++ b/src/metabase/lib/filter.cljc @@ -15,6 +15,7 @@ [metabase.lib.options :as lib.options] [metabase.lib.ref :as lib.ref] [metabase.lib.schema :as lib.schema] + [metabase.lib.schema.common :as lib.schema.common] [metabase.lib.schema.expression :as lib.schema.expression] [metabase.lib.schema.filter :as lib.schema.filter] [metabase.lib.temporal-bucket :as lib.temporal-bucket] @@ -189,6 +190,11 @@ [filterable-column :- ColumnWithOperators] (:operators filterable-column)) +(defn- add-column-operators + [column] + (let [operators (lib.filter.operator/filter-operators column)] + (m/assoc-some column :operators (clojure.core/not-empty operators)))) + (mu/defn filterable-columns :- [:sequential ColumnWithOperators] "Get column metadata for all the columns that can be filtered in the stage number `stage-number` of the query `query` @@ -209,15 +215,12 @@ ([query :- ::lib.schema/query stage-number :- :int] - (let [stage (lib.util/query-stage query stage-number) - columns (lib.metadata.calculation/visible-columns query stage-number stage) - with-operators (fn [column] - (when-let [operators (clojure.core/not-empty (lib.filter.operator/filter-operators column))] - (assoc column :operators operators)))] + (let [stage (lib.util/query-stage query stage-number)] (clojure.core/not-empty - (into [] - (keep with-operators) - columns))))) + (into [] + (comp (map add-column-operators) + (clojure.core/filter :operators)) + (lib.metadata.calculation/visible-columns query stage-number stage)))))) (mu/defn filter-clause :- ::lib.schema.expression/boolean "Returns a standalone filter clause for a `filter-operator`, @@ -246,3 +249,34 @@ (clojure.core/or (m/find-first #(clojure.core/= (:short %) op) (lib.filter.operator/filter-operators (ref->col col-ref))) (lib.filter.operator/operator-def op))))) + +(def ^:private FilterParts + [:map + [:lib/type [:= :mbql/filter-parts]] + [:operator ::lib.schema.filter/operator] + [:options ::lib.schema.common/options] + [:column [:maybe ColumnWithOperators]] + [:args [:sequential :any]]]) + +(mu/defn filter-parts :- FilterParts + "Return the parts of the filter clause `a-filter-clause` in query `query` at stage `stage-number`. + Might obsolate [[filter-operator]]." + ([query a-filter-clause] + (filter-parts query -1 a-filter-clause)) + + ([query :- ::lib.schema/query + stage-number :- :int + a-filter-clause :- ::lib.schema.expression/boolean] + (let [[op options first-arg & rest-args] a-filter-clause + stage (lib.util/query-stage query stage-number) + columns (lib.metadata.calculation/visible-columns query stage-number stage) + ref->col (m/index-by lib.ref/ref columns) + col-ref (lib.equality/find-closest-matching-ref query first-arg (keys ref->col)) + col (ref->col col-ref)] + {:lib/type :mbql/filter-parts + :operator (clojure.core/or (m/find-first #(clojure.core/= (:short %) op) + (lib.filter.operator/filter-operators col)) + (lib.filter.operator/operator-def op)) + :options options + :column (some-> col add-column-operators) + :args (vec rest-args)}))) diff --git a/src/metabase/lib/js.cljs b/src/metabase/lib/js.cljs index ed78f6e11ca..31ceb5576dc 100644 --- a/src/metabase/lib/js.cljs +++ b/src/metabase/lib/js.cljs @@ -372,6 +372,15 @@ [a-query stage-number a-filter-clause] (lib.core/filter-operator a-query stage-number a-filter-clause)) +(defn ^:export filter-parts + "Returns the parts (operator, args, and optionally, options) of `filter-clause`." + [a-query stage-number a-filter-clause] + (let [{:keys [operator options column args]} (lib.core/filter-parts a-query stage-number a-filter-clause)] + #js {:operator operator + :options (clj->js (select-keys options [:case-sensitive :include-current])) + :column column + :args (to-array args)})) + (defn ^:export filter "Sets `boolean-expression` as a filter on `query`." [a-query stage-number boolean-expression] diff --git a/test/metabase/lib/filter_test.cljc b/test/metabase/lib/filter_test.cljc index a22042b57e3..838e95d77aa 100644 --- a/test/metabase/lib/filter_test.cljc +++ b/test/metabase/lib/filter_test.cljc @@ -318,6 +318,34 @@ (is (= op (lib/filter-operator query filter-clause))))))) +(deftest ^:parallel filter-parts-test + (let [query (-> (lib/query meta/metadata-provider (meta/table-metadata :users)) + (lib/join (-> (lib/join-clause (meta/table-metadata :checkins) + [(lib/= + (meta/field-metadata :checkins :user-id) + (meta/field-metadata :users :id))]) + (lib/with-join-fields :all))) + (lib/join (-> (lib/join-clause (meta/table-metadata :venues) + [(lib/= + (meta/field-metadata :checkins :venue-id) + (meta/field-metadata :venues :id))]) + (lib/with-join-fields :all))))] + (doseq [col (lib/filterable-columns query) + op (lib/filterable-column-operators col) + :let [filter-clause (case (:short op) + :between (lib/filter-clause op col 123 456) + (:contains :does-not-contain :starts-with :ends-with) (lib/filter-clause op col "123") + (:is-null :not-null :is-empty :not-empty) (lib/filter-clause op col) + :inside (lib/filter-clause op col 12 34 56 78 90) + (lib/filter-clause op col 123))]] + (testing (str (:short op) " with " (lib.types.isa/field-type col)) + (is (=? {:lib/type :mbql/filter-parts + :operator op + :column {:lib/type :metadata/column + :operators (comp vector? not-empty)} + :args vector?} + (lib/filter-parts query filter-clause))))))) + (deftest ^:parallel replace-filter-clause-test (testing "Make sure we are able to replace a filter clause using the lib functions for manipulating filters." (let [query (lib/query meta/metadata-provider (meta/table-metadata :users)) -- GitLab