Skip to content

Commit

Permalink
perf: Optimize validation
Browse files Browse the repository at this point in the history
  • Loading branch information
alexander-yakushev committed Aug 5, 2024
1 parent aba0048 commit 8b9e056
Show file tree
Hide file tree
Showing 7 changed files with 41 additions and 32 deletions.
9 changes: 1 addition & 8 deletions src/metabase/analyze/schema.clj
Original file line number Diff line number Diff line change
@@ -1,17 +1,10 @@
(ns metabase.analyze.schema
"Schemas used by the analyze code."
(:require
[clojure.string :as str]
[metabase.util.malli.registry :as mr]
[metabase.util.malli.schema :as ms]))

(mr/def ::no-kebab-case-keys
[:fn
{:error/message "Map should not contain any kebab-case keys"}
(fn [m]
(every? (fn [k]
(not (str/includes? k "-")))
(keys m)))])
(mr/def ::no-kebab-case-keys (ms/MapWithNoKebabKeys))

(mr/def ::Table
[:and
Expand Down
6 changes: 4 additions & 2 deletions src/metabase/lib/metadata/protocols.cljc
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,8 @@
(defn metadata-provider?
"Whether `x` is a valid [[MetadataProvider]]."
[x]
(satisfies? MetadataProvider x))
#?(:clj (extends? MetadataProvider (class x))
:cljs (satisfies? MetadataProvider x)))

(mr/def ::metadata-provider
"Schema for something that satisfies the [[metabase.lib.metadata.protocols/MetadataProvider]] protocol."
Expand Down Expand Up @@ -158,7 +159,8 @@
(defn cached-metadata-provider?
"Whether `x` is a valid [[CachedMetadataProvider]]."
[x]
(satisfies? CachedMetadataProvider x))
#?(:clj (extends? CachedMetadataProvider (class x))
:cljs (satisfies? CachedMetadataProvider x)))

(mr/def ::cached-metadata-provider
[:fn
Expand Down
10 changes: 6 additions & 4 deletions src/metabase/lib/schema.cljc
Original file line number Diff line number Diff line change
Expand Up @@ -106,10 +106,12 @@
"For ref validation purposes we should ignore `:joins` and any namespaced keys that might be used to record additional
info e.g. `:lib/metadata`."
[stage]
(select-keys stage (into []
(comp (filter simple-keyword?)
(remove (partial = :joins)))
(keys stage))))
(reduce-kv (fn [acc k _]
(if (or (qualified-keyword? k)
(= k :joins))
(dissoc acc k)
acc))
stage stage))

(defn- expression-ref-errors-for-stage [stage]
(let [expression-names (into #{} (map (comp :lib/expression-name second)) (:expressions stage))]
Expand Down
19 changes: 10 additions & 9 deletions src/metabase/lib/schema/util.cljc
Original file line number Diff line number Diff line change
Expand Up @@ -52,11 +52,6 @@
(str "Duplicate :lib/uuid " (pr-str (find-duplicate-uuid value))))}
#'unique-uuids?])

(defn remove-namespaced-keys
"Remove all the namespaced keys from a map."
[m]
(into {} (remove (fn [[k _v]] (qualified-keyword? k))) m))

(defn distinct-refs?
"Is a sequence of `refs` distinct for the purposes of appearing in `:fields` or `:breakouts` (ignoring keys that
aren't important such as namespaced keys and type info)?"
Expand All @@ -66,7 +61,13 @@
(apply
distinct?
(for [ref refs]
(lib.options/update-options ref (fn [options]
(-> options
remove-namespaced-keys
(dissoc :base-type :effective-type))))))))
(let [options (lib.options/options ref)]
(lib.options/with-options ref
;; Using reduce-kv to remove namespaced keys and some other keys to perform the comparison.
(reduce-kv (fn [acc k _]
(if (or (qualified-keyword? k)
(= k :base-type)
(= k :effective-type))
(dissoc acc k)
acc))
options options)))))))
9 changes: 1 addition & 8 deletions src/metabase/sync/interface.clj
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
(ns metabase.sync.interface
"Schemas and constants used by the sync code."
(:require
[clojure.string :as str]
[malli.util :as mut]
[metabase.lib.schema.common :as lib.schema.common]
[metabase.util.malli.registry :as mr]
Expand Down Expand Up @@ -114,13 +113,7 @@
;; out from the ns declaration when running `cljr-clean-ns`. Plus as a bonus in the future we could add additional
;; validations to these, e.g. requiring that a Field have a base_type

(mr/def ::no-kebab-case-keys
[:fn
{:error/message "Map should not contain any kebab-case keys"}
(fn [m]
(every? (fn [k]
(not (str/includes? k "-")))
(keys m)))])
(mr/def ::no-kebab-case-keys (ms/MapWithNoKebabKeys))

(mr/def ::DatabaseInstance
[:and
Expand Down
2 changes: 1 addition & 1 deletion src/metabase/util/malli/registry.cljc
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
You generally shouldn't use this outside of this namespace unless you have a really good reason to do so! Make sure
you used namespaced keys if you are using it elsewhere."
[k schema value-thunk]
(or (get-in @cache [k schema])
(or (get (get @cache k) schema) ; get-in is terribly inefficient
(let [v (value-thunk)]
(swap! cache assoc-in [k schema] v)
v)))
Expand Down
18 changes: 18 additions & 0 deletions src/metabase/util/malli/schema.clj
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
"
(:require
[cheshire.core :as json]
[clojure.string :as str]
[malli.core :as mc]
[metabase.legacy-mbql.normalize :as mbql.normalize]
[metabase.legacy-mbql.schema :as mbql.s]
Expand Down Expand Up @@ -386,3 +387,20 @@
"Helper for creating a schema that coerces single-value to a vector. Useful for coercing query parameters."
[schema]
[:vector {:decode/string (fn [x] (cond (vector? x) x x [x]))} schema])

(defn MapWithNoKebabKeys
"Helper for creating a schema to check if a map doesn't contain kebab case keys."
[]
[:fn
{:error/message "Map should not contain any kebab-case keys"}
(fn [m]
;; reduce-kv is more efficient that iterating over (keys m). But we have to extract the underlying map from
;; Toucan2 Instance because it doesn't implement IKVReduce (yet).
(let [m (if (instance? toucan2.instance.Instance m)
(.m ^toucan2.instance.Instance m)
m)]
(reduce-kv (fn [_ k _]
(if (str/includes? k "-")
(reduced false)
true))
true m)))])

0 comments on commit 8b9e056

Please sign in to comment.