Skip to content
Snippets Groups Projects
Commit cb576c54 authored by Ryan Senior's avatar Ryan Senior
Browse files

Added support for multiple min/max affecting criteria clauses

parent b1e496aa
No related branches found
No related tags found
No related merge requests found
...@@ -4,17 +4,22 @@ ...@@ -4,17 +4,22 @@
[metabase.query-processor.interface :as i]) [metabase.query-processor.interface :as i])
(:import [metabase.query_processor.interface BinnedField ComparisonFilter BetweenFilter])) (:import [metabase.query_processor.interface BinnedField ComparisonFilter BetweenFilter]))
(defn- update! [^clojure.lang.ITransientAssociative coll k f] (defn- update!
"Similar to `clojure.core/update` but works on transient maps"
[^clojure.lang.ITransientAssociative coll k f]
(assoc! coll k (f (get coll k)))) (assoc! coll k (f (get coll k))))
(defn- filter->field-map [mbql-filter] (defn- filter->field-map
"A bit of a stateful hack using clojure.walk/prewalk to find any
comparison or between filter. This should be replaced by a zipper
for a more functional/composable approach to this problem."
[mbql-filter]
(let [acc (transient {})] (let [acc (transient {})]
(clojure.walk/prewalk (walk/prewalk
(fn [x] (fn [x]
(when (or (when (or (instance? BetweenFilter x)
(instance? BetweenFilter x) (and (instance? ComparisonFilter x)
(and (instance? ComparisonFilter x) (contains? #{:< :> :<= :>=} (:filter-type x))))
(contains? #{:< :> :<= :>=} (:filter-type x))))
(update! acc (get-in x [:field :field-id]) #(if (seq %) (update! acc (get-in x [:field :field-id]) #(if (seq %)
(conj % x) (conj % x)
[x]))) [x])))
...@@ -26,29 +31,35 @@ ...@@ -26,29 +31,35 @@
(u/round-to-decimals 5 (/ (- max-value min-value) (u/round-to-decimals 5 (/ (- max-value min-value)
num-bins))) num-bins)))
(defn- extract-bounds [{field-id :field-id, global-min :min-value, global-max :max-value} field-filter-map] (defn- extract-bounds
;; Assuming only one for now "Given query criteria, find a min/max value for the binning strategy
(let [bound-1 (first (for [{:keys [value filter-type]} (get field-filter-map field-id) using the greatest user specified min value and the smallest user
:when (or (= :> filter-type) specified max value. When a user specified min or max is not found,
(= :>= filter-type))] use the global min/max for the given field."
(:value value) )) [{field-id :field-id, global-min :min-value, global-max :max-value} field-filter-map]
bound-2 (first (for [{:keys [value filter-type]} (get field-filter-map field-id) (let [user-maxes (for [{:keys [filter-type] :as query-filter} (get field-filter-map field-id)
:when (or (= :< filter-type) :when (contains? #{:< :<= :between} filter-type)]
(= :<= filter-type))] (if (= :between filter-type)
(:value value))) (get-in query-filter [:max-val :value])
comparison-bounds (when (and bound-1 bound-2) (get-in query-filter [:value :value])))
(if (> bound-1 bound-2) user-mins (for [{:keys [filter-type] :as query-filter} (get field-filter-map field-id)
[bound-2 bound-1] :when (contains? #{:> :>= :between} filter-type)]
[bound-1 bound-2])) (if (= :between filter-type)
;;Assuming either >/< or between (get-in query-filter [:min-val :value])
between-bounds (first (for [{:keys [filter-type min-value max-value]} (get field-filter-map field-id) (get-in query-filter [:value :value])))]
:when (= :between filter-type)]
[min-value max-value]))]
(or (seq comparison-bounds)
(seq between-bounds)
[global-min global-max])))
(defn- update-bin-width [breakouts filter-field-map] [(or (when (seq user-mins)
(apply max user-mins))
global-min)
(or (when (seq user-maxes)
(apply min user-maxes))
global-max)]))
(defn- update-bin-width
"Calculates the bin width given the global min/max and user
specified crtieria that could impact that min/max. Throws an
Exception if no min/max values are found."
[breakouts filter-field-map]
(mapv (fn [{:keys [field num-bins] :as breakout}] (mapv (fn [{:keys [field num-bins] :as breakout}]
(if (instance? BinnedField breakout) (if (instance? BinnedField breakout)
(let [[min-value max-value] (extract-bounds field filter-field-map)] (let [[min-value max-value] (extract-bounds field filter-field-map)]
......
(ns metabase.query-processor.middleware.binning-test
(:require [expectations :refer [expect]]
[metabase.query-processor.middleware.binning :refer :all]
[metabase.query-processor.expand :as ql]
[metabase.test.util :as tu]))
(tu/resolve-private-vars metabase.query-processor.middleware.binning filter->field-map extract-bounds)
(expect
{}
(filter->field-map (ql/and
(ql/= (ql/field-id 1) 10)
(ql/= (ql/field-id 2) 10))))
(expect
{1 [(ql/< (ql/field-id 1) 10) (ql/> (ql/field-id 1) 1)]
2 [(ql/> (ql/field-id 2) 20) (ql/< (ql/field-id 2) 10)]
3 [(ql/between (ql/field-id 3) 5 10)]}
(filter->field-map (ql/and
(ql/< (ql/field-id 1) 10)
(ql/> (ql/field-id 1) 1)
(ql/> (ql/field-id 2) 20)
(ql/< (ql/field-id 2) 10)
(ql/between (ql/field-id 3) 5 10))))
(expect
[1 10]
(extract-bounds {:field-id 1 :min-value 100 :max-value 1000}
{1 [(ql/> (ql/field-id 1) 1) (ql/< (ql/field-id 1) 10)]}))
(expect
[1 10]
(extract-bounds {:field-id 1 :min-value 100 :max-value 1000}
{1 [(ql/between (ql/field-id 1) 1 10)]}))
(expect
[100 1000]
(extract-bounds {:field-id 1 :min-value 100 :max-value 1000}
{}))
(expect
[500 1000]
(extract-bounds {:field-id 1 :min-value 100 :max-value 1000}
{1 [(ql/> (ql/field-id 1) 500)]}))
(expect
[100 500]
(extract-bounds {:field-id 1 :min-value 100 :max-value 1000}
{1 [(ql/< (ql/field-id 1) 500)]}))
(expect
[600 700]
(extract-bounds {:field-id 1 :min-value 100 :max-value 1000}
{1 [(ql/> (ql/field-id 1) 200)
(ql/< (ql/field-id 1) 800)
(ql/between (ql/field-id 1) 600 700)]}))
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment