Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

MBQL Refactor: Combine various Field clauses into one new clause #14897

Merged
merged 62 commits into from
Feb 25, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
62 commits
Select commit Hold shift + click to select a range
638259c
Basic queries are working
camsaul Feb 19, 2021
afe5655
Fix auto-bucket-datetimes
camsaul Feb 19, 2021
7e8c4ac
Mostly fix add-implicit-joins
camsaul Feb 19, 2021
cb335dd
271/340 => 611
camsaul Feb 19, 2021
855f6f6
236/309 => 545
camsaul Feb 19, 2021
f8d19b1
224/306 => 530
camsaul Feb 19, 2021
73acd99
213/227 => 440
camsaul Feb 19, 2021
7fc28e3
209/212 => 421
camsaul Feb 19, 2021
d5051a3
187/198 => 385
camsaul Feb 19, 2021
91d4ca1
177/171 => 348
camsaul Feb 19, 2021
aaf2acf
173/131 => 304
camsaul Feb 19, 2021
87980e5
159/107 => 266
camsaul Feb 19, 2021
f50736d
158/98 => 256
camsaul Feb 19, 2021
53a50af
146/97 => 243
camsaul Feb 20, 2021
c34d0f3
145/91 => 236
camsaul Feb 20, 2021
357db50
131/66 => 197
camsaul Feb 20, 2021
3712a08
[WIP]
camsaul Feb 20, 2021
f6fbc38
106/73 => 179
camsaul Feb 20, 2021
870cbde
100/68 => 168
camsaul Feb 20, 2021
d56cfe7
94/41 => 135
camsaul Feb 20, 2021
c9dd945
51/10 => 61
camsaul Feb 20, 2021
8de90f0
34/10 => 44
camsaul Feb 20, 2021
7d753bf
Convert more tests to the new style
camsaul Feb 22, 2021
7f1d946
14/1 => 15
camsaul Feb 22, 2021
f796626
7/1 => 8
camsaul Feb 22, 2021
7c6d5ee
5/1 => 6
camsaul Feb 23, 2021
f6e6c88
2/1 => 3
camsaul Feb 23, 2021
4a9b13e
2/0 => 2
camsaul Feb 23, 2021
85b556d
ALL OSS TESTS ARE PASSING :partyparrot:
camsaul Feb 23, 2021
2706c4a
EE tests are passing
camsaul Feb 23, 2021
e424359
Some FE stuff working
camsaul Feb 23, 2021
f3b761f
Basic QB & group by stuff is working
camsaul Feb 23, 2021
b237374
Drill-thru seems to be working
camsaul Feb 23, 2021
e94c24b
Fix backend tests
camsaul Feb 23, 2021
a6d06b4
No more "joined-field"
camsaul Feb 24, 2021
60d40b2
No more "binning-strategy"
camsaul Feb 24, 2021
65bb5a1
No more "datetime-field"
camsaul Feb 24, 2021
525b7f3
Fixed most, but not all, of the FE stuff
camsaul Feb 24, 2021
ada7845
Prettier
camsaul Feb 24, 2021
a5138d8
Normalize visualization settings on backend
camsaul Feb 24, 2021
19047fb
Fix FK breakouts
camsaul Feb 24, 2021
5622049
Fix TemplateTagDimension
camsaul Feb 24, 2021
8a72ef4
Wow drill-down tests are passing
camsaul Feb 25, 2021
b8ef160
8/1620
camsaul Feb 25, 2021
264fe30
6/1620
camsaul Feb 25, 2021
ec28b82
<<< EVERYTHING PASS >>>
camsaul Feb 25, 2021
ceb382f
Fix backend/mongo tests
camsaul Feb 25, 2021
68c87e1
Fix Google Analytics
camsaul Feb 25, 2021
bebd064
Fix flow errors
camsaul Feb 25, 2021
4313479
Fix more stuff :wrench:
camsaul Feb 25, 2021
b5e6791
Fix some more busted stuff
camsaul Feb 25, 2021
e9f4676
Update frontend/src/metabase-lib/lib/Dimension.js
camsaul Feb 25, 2021
55d680f
Update frontend/test/metabase/modes/components/drill/UnderlyingRecord…
camsaul Feb 25, 2021
be163a1
Update frontend/src/metabase-lib/lib/Dimension.js
camsaul Feb 25, 2021
4aa6738
Test fix :wrench:
camsaul Feb 25, 2021
7760f26
Don't normalize fingerprints
camsaul Feb 25, 2021
a658711
More tests
camsaul Feb 25, 2021
7af3bc1
Test fix :wrench:
camsaul Feb 25, 2021
4a714f3
Try setting SQL Server memory limit
camsaul Feb 25, 2021
1ef440a
Recursive (empty) field options normalization
camsaul Feb 25, 2021
440f585
More PR feedback
camsaul Feb 25, 2021
9d9a838
More test fixes :wrench:
camsaul Feb 25, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,7 @@ executors:
environment:
ACCEPT_EULA: Y
SA_PASSWORD: 'P@ssw0rd'
MSSQL_MEMORY_LIMIT_MB: 1024

fe-mongo-4:
working_directory: /home/circleci/metabase/metabase/
Expand Down
470 changes: 291 additions & 179 deletions backend/mbql/src/metabase/mbql/normalize.clj

Large diffs are not rendered by default.

10 changes: 9 additions & 1 deletion backend/mbql/src/metabase/mbql/predicates.clj
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,16 @@
"Is `unit` a datetime bucketing unit referring only to time, such as `hour` or `minute`?"
(complement (s/checker mbql.s/TimeUnit)))

(def ^{:arglists '([unit])} DatetimeFieldUnit?
(def ^{:arglists '([unit])} ^:deprecated DateUnit?
"Is `unit` a valid date bucketing unit?"
(complement (s/checker mbql.s/DateUnit)))

(def ^{:arglists '([unit])} ^:deprecated DateTimeUnit?
"Is `unit` a valid datetime bucketing unit?"
(complement (s/checker mbql.s/DateTimeUnit)))

(def ^{:arglists '([unit])} ^{:deprecated "0.39.0"} DatetimeFieldUnit?
"Is `unit` a valid datetime bucketing unit? DEPRECATED -- use `DateUnit?` or `DateTimeUnit?` instead."
(complement (s/checker mbql.s/DatetimeFieldUnit)))

(def ^{:arglists '([ag-clause])} Aggregation?
Expand Down
372 changes: 222 additions & 150 deletions backend/mbql/src/metabase/mbql/schema.clj

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion backend/mbql/src/metabase/mbql/schema/helpers.clj
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@
[clause-name & arg-names-and-schemas]
(let [[symb-name clause-name] (if (vector? clause-name)
clause-name
[clause-name clause-name])]
[clause-name (or (:clause-name (meta clause-name)) clause-name)])]
`(def ~(vary-meta symb-name assoc
:clause-name (keyword clause-name)
:doc (format "Schema for a valid %s clause." clause-name))
Expand Down
174 changes: 85 additions & 89 deletions backend/mbql/src/metabase/mbql/util.clj
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
[java-time.amount :as t.amount]
[java-time.core :as t.core]
[metabase.mbql.schema :as mbql.s]
[metabase.mbql.schema.helpers :as mbql.s.helpers]
[metabase.mbql.util.match :as mbql.match]
[metabase.util.i18n :refer [tru]]
[metabase.util.schema :as su]
Expand All @@ -32,6 +31,7 @@
`normalize` this handles pre-normalized clauses as well.)"
[x]
(and (sequential? x)
(not (instance? clojure.lang.MapEntry x))
(keyword? (first x))))

(defn is-clause?
Expand Down Expand Up @@ -65,15 +65,16 @@
Examples:

;; keyword pattern
(match {:fields [[:field-id 10]]} :field-id) ; -> [[:field-id 10]]
(match {:fields [[:field 10 nil]]} :field) ; -> [[:field 10 nil]]

;; set of keywords
(match some-query #{:field-id :fk->}) ; -> [[:field-id 10], [:fk-> [:field-id 10] [:field-id 20]], ...]
(match some-query #{:field :expression}) ; -> [[:field 10 nil], [:expression \"wow\"], ...]

;; `core.match` patterns:
;; match any `:field-id` clause with one arg (which should be all of them)
(match some-query [:field-id _])
(match some-query [:field-id (_ :guard #(> % 100))]) ; -> [[:field-id 200], ...]
;; match any `:field` clause with two args (which should be all of them)
(match some-query [:field _ _])
;; match any `:field` clause with integer ID > 100
(match some-query [:field (_ :guard (every-pred integer? #(> % 100)))]) ; -> [[:field 200 nil], ...]

;; symbol naming a Class
;; match anything that is an instance of that class
Expand Down Expand Up @@ -111,11 +112,11 @@
optional result body. Whatever result body returns will be returned by `match`:

;; just return the IDs of Field ID clauses
(match some-query [:field-id id] id) ; -> [1 2 3]
(match some-query [:field (id :guard integer?) _] id) ; -> [1 2 3]

You can also use result body to filter results; any `nil` values will be skipped:

(match some-query [:field-id id]
(match some-query [:field (id :guard integer?) _]
(when (even? id)
id))
;; -> [2 4 6 8]
Expand All @@ -127,16 +128,16 @@

### `&match` and `&parents` anaphors

For more advanced matches, like finding `:field-id` clauses nested anywhere inside `:datetime-field` clauses,
`match` binds a pair of anaphors inside the result body for your convenience. `&match` is bound to the entire
match, regardless of how you may have destructured it; `&parents` is bound to a sequence of keywords naming the
parent top-level keys and clauses of the match.
For more advanced matches, like finding a `:field` clauses nested anywhere inside another clause, `match` binds a
pair of anaphors inside the result body for your convenience. `&match` is bound to the entire match, regardless of
how you may have destructured it; `&parents` is bound to a sequence of keywords naming the parent top-level keys and
clauses of the match.

(mbql.u/match {:fields [[:datetime-field [:fk-> [:field-id 1] [:field-id 2]] :day]]} :field-id
;; &parents will be [:fields :datetime-field :fk->]
(when (contains? (set &parents) :datetime-field)
(mbql.u/match {:filter [:time-interval [:field 1 nil] :current :month]} :field
;; &parents will be [:filter :time-interval]
(when (contains? (set &parents) :time-interval)
&match))
;; -> [[:field-id 1] [:field-id 2]]"
;; -> [[:field 1 nil]]"
{:style/indent 1}
[x & patterns-and-results]
;; Actual implementation of these macros is in `mbql.util.match`. They're in a seperate namespace because they have
Expand Down Expand Up @@ -298,26 +299,44 @@
[:time-interval field :last unit options] (recur [:time-interval field -1 unit options])
[:time-interval field :next unit options] (recur [:time-interval field 1 unit options])

[:time-interval field (n :guard #{-1}) unit (_ :guard :include-current)]
[:between [:datetime-field field unit] [:relative-datetime n unit] [:relative-datetime 0 unit]]

[:time-interval field (n :guard #{1}) unit (_ :guard :include-current)]
[:between [:datetime-field field unit] [:relative-datetime 0 unit] [:relative-datetime n unit]]

[:time-interval field (n :guard #{-1 0 1}) unit _]
[:= [:datetime-field field unit] [:relative-datetime n unit]]

[:time-interval field (n :guard neg?) unit (_ :guard :include-current)]
[:between [:datetime-field field unit] [:relative-datetime n unit] [:relative-datetime 0 unit]]

[:time-interval field (n :guard neg?) unit _]
[:between [:datetime-field field unit] [:relative-datetime n unit] [:relative-datetime -1 unit]]

[:time-interval field n unit (_ :guard :include-current)]
[:between [:datetime-field field unit] [:relative-datetime 0 unit] [:relative-datetime n unit]]

[:time-interval field n unit _]
[:between [:datetime-field field unit] [:relative-datetime 1 unit] [:relative-datetime n unit]]))
[:time-interval [_ id-or-name opts] (n :guard #{-1}) unit (_ :guard :include-current)]
[:between
[:field id-or-name (assoc opts :temporal-unit unit)]
[:relative-datetime n unit]
[:relative-datetime 0 unit]]

[:time-interval [_ id-or-name opts] (n :guard #{1}) unit (_ :guard :include-current)]
[:between
[:field id-or-name (assoc opts :temporal-unit unit)]
[:relative-datetime 0 unit]
[:relative-datetime n unit]]

[:time-interval [_ id-or-name opts] (n :guard #{-1 0 1}) unit _]
[:= [:field id-or-name (assoc opts :temporal-unit unit)] [:relative-datetime n unit]]

[:time-interval [_ id-or-name opts] (n :guard neg?) unit (_ :guard :include-current)]
[:between
[:field id-or-name (assoc opts :temporal-unit unit)]
[:relative-datetime n unit]
[:relative-datetime 0 unit]]

[:time-interval [_ id-or-name opts] (n :guard neg?) unit _]
[:between
[:field id-or-name (assoc opts :temporal-unit unit)]
[:relative-datetime n unit]
[:relative-datetime -1 unit]]

[:time-interval [_ id-or-name opts] n unit (_ :guard :include-current)]
[:between
[:field id-or-name (assoc opts :temporal-unit unit)]
[:relative-datetime 0 unit]
[:relative-datetime n unit]]

[:time-interval [_ id-or-name opts] n unit _]
[:between
[:field id-or-name (assoc opts :temporal-unit unit)]
[:relative-datetime 1 unit]
[:relative-datetime n unit]]))

(defn desugar-does-not-contain
"Rewrite `:does-not-contain` filter clauses as simpler `:not` clauses."
Expand All @@ -343,12 +362,11 @@

(defn desugar-current-relative-datetime
"Replace `relative-datetime` clauses like `[:relative-datetime :current]` with `[:relative-datetime 0 <unit>]`.
`<unit>` is inferred from the `:datetime-field` the clause is being compared to (if any), otherwise falls back to
`default.`"
`<unit>` is inferred from the `:field` the clause is being compared to (if any), otherwise falls back to `default.`"
[m]
(replace m
[clause field [:relative-datetime :current & _]]
[clause field [:relative-datetime 0 (or (match-one field [:datetime-field _ unit] unit)
[clause field [:relative-datetime 0 (or (match-one field [:field _ (opts :guard :temporal-unit)] (:temporal-unit opts))
:default)]]))

(s/defn desugar-filter-clause :- mbql.s/Filter
Expand Down Expand Up @@ -430,47 +448,17 @@
[join]
(query->source-table-id {:type :query, :query join}))

(s/defn unwrap-field-clause :- (mbql.s.helpers/one-of mbql.s/field-id mbql.s/field-literal)
"Un-wrap a `Field` clause and return the lowest-level clause it wraps, either a `:field-id` or `:field-literal`."
[clause :- mbql.s/Field]
(match-one clause
:field-id &match
:field-literal &match
[:fk-> _ dest-field] (recur dest-field)
[:joined-field _ field] (recur field)
[:datetime-field field _] (recur field)
[:binning-strategy field & _] (recur field)))

(defn maybe-unwrap-field-clause
"Unwrap a Field `clause`, if it's something that can be unwrapped (i.e. something that is, or wraps, a `:field-id` or
`:field-literal`). Otherwise return `clause` as-is."
[clause]
(if (is-clause? #{:field-id :fk-> :field-literal :datetime-field :binning-strategy :joined-field} clause)
(unwrap-field-clause clause)
clause))

(s/defn field-clause->id-or-literal :- (s/cond-pre su/IntGreaterThanZero su/NonBlankString)
"Get the actual Field ID or literal name this clause is referring to. Useful for seeing if two Field clauses are
referring to the same thing, e.g.

(field-clause->id-or-literal [:datetime-field [:field-id 100] ...]) ; -> 100
(field-clause->id-or-literal [:field-id 100]) ; -> 100

For expressions returns the expression name."
[clause :- mbql.s/Field]
(second (maybe-unwrap-field-clause clause)))

(s/defn add-order-by-clause :- mbql.s/MBQLQuery
"Add a new `:order-by` clause to an MBQL `inner-query`. If the new order-by clause references a Field that is
already being used in another order-by clause, this function does nothing."
[inner-query :- mbql.s/MBQLQuery, [_ field, :as order-by-clause] :- mbql.s/OrderBy]
(let [existing-fields (set (for [[_ existing-field] (:order-by inner-query)]
(maybe-unwrap-field-clause existing-field)))]
(if (existing-fields (maybe-unwrap-field-clause field))
[inner-query :- mbql.s/MBQLQuery, [_ [_ id-or-name :as field], :as order-by-clause] :- mbql.s/OrderBy]
(let [existing-fields (set (for [[_ [_ id-or-name]] (:order-by inner-query)]
id-or-name))]
(if (existing-fields id-or-name)
;; Field already referenced, nothing to do
inner-query
;; otherwise add new clause at the end
(update inner-query :order-by (comp vec conj) order-by-clause))))
(update inner-query :order-by (comp vec distinct conj) order-by-clause))))

(defn relative-date
"Return a new Temporal value relative to `t` using a relative date `unit`.
Expand Down Expand Up @@ -583,9 +571,12 @@
(defn datetime-arithmetics?
"Is a given artihmetics clause operating on datetimes?"
[clause]
(boolean
(match-one clause
#{:datetime-field :interval :relative-datetime})))
(match-one clause
#{:interval :relative-datetime}
true

[:field _ (_ :guard :temporal-unit)]
true))


;;; --------------------------------- Unique names & transforming ags to have names ----------------------------------
Expand Down Expand Up @@ -664,10 +655,10 @@
`:aggregation-options`.

(pre-alias-aggregations annotate/aggregation-name
[[:count] [:count] [:aggregation-options [:sum [:field-id 1] {:name \"Sum-41\"}]])
[[:count] [:count] [:aggregation-options [:sum [:field 1 nil] {:name \"Sum-41\"}]])
;; -> [[:aggregation-options [:count] {:name \"count\"}]
[:aggregation-options [:count] {:name \"count\"}]
[:aggregation-options [:sum [:field-id 1]] {:name \"Sum-41\"}]]
[:aggregation-options [:sum [:field 1 nil]] {:name \"Sum-41\"}]]

Most often, `aggregation->name-fn` will be something like `annotate/aggregation-name`, but for purposes of keeping
the `metabase.mbql` module seperate from the `metabase.query-processor` code we'll let you pass that in yourself."
Expand Down Expand Up @@ -722,16 +713,6 @@
max-results)]
(safe-min mbql-limit constraints-limit)))

(s/defn ->joined-field :- mbql.s/JoinField
"Convert a Field clause to one that uses an appropriate `alias`, e.g. for a joined table."
[table-alias :- s/Str, field-clause :- mbql.s/Field]
(replace field-clause
:joined-field
(throw (Exception. (format "%s already has an alias." &match)))

#{:field-id :field-literal}
[:joined-field table-alias &match]))

(def ^:private default-join-alias "source")

(s/defn deduplicate-join-aliases :- mbql.s/Joins
Expand All @@ -746,3 +727,18 @@
(assoc join :alias alias))
joins
unique-aliases)))

(s/defn update-field-options :- mbql.s/field
"Like `clojure.core/update`, but for the options in a `:field` clause."
[[_ id-or-name opts] :- mbql.s/field f & args]
[:field id-or-name (not-empty (apply f opts args))])

(defn assoc-field-options
"Like `clojure.core/assoc`, but for the options in a `:field` clause."
[field-clause & kvs]
(apply update-field-options field-clause assoc kvs))

(defn with-temporal-unit
"Set the `:temporal-unit` of a `:field` clause to `unit`."
[field-clause unit]
(assoc-field-options field-clause :temporal-unit unit))