(ns metabase.lib.normalize (:require [malli.core :as mc] [malli.transform :as mtx] [metabase.lib.schema :as lib.schema] [metabase.lib.schema.mbql-clause :as lib.schema.mbql-clause] [metabase.lib.schema.metadata :as lib.schema.metadata] [metabase.util :as u] [metabase.util.i18n :as i18n] [metabase.util.log :as log] [metabase.util.malli.registry :as mr])) | |
(defn- lib-type [x]
(when (map? x)
(keyword (some #(get x %) [:lib/type "lib/type"])))) | |
TODO -- we are missing some stuff for sure. | (def ^:private lib-type->schema
{:mbql/query ::lib.schema/query
:mbql.stage/mbql ::lib.schema/stage.mbql
:mbql.stage/native ::lib.schema/stage.native
:metadata/database ::lib.schema.metadata/database
:metadata/table ::lib.schema.metadata/table
:metadata/column ::lib.schema.metadata/column
:metadata/card ::lib.schema.metadata/card
:metadata/segment ::lib.schema.metadata/segment
:metadata/metric ::lib.schema.metadata/metric}) |
(defn- infer-schema [x]
(cond
(map? x)
(or (-> x lib-type lib-type->schema)
:map)
(and (vector? x)
((some-fn simple-keyword? string?) (first x)))
(lib.schema.mbql-clause/tag->registered-schema-name (first x))
:else
:any)) | |
If normalization errors somewhere, just log the error and return the partially-normalized result. Easier to debug this way. | (defn- default-error-fn [error] (log/warnf "Error normalizing pMBQL:\n%s" (u/pprint-to-str error)) (:value error)) |
(def ^:private ^:dynamic *error-fn* default-error-fn) | |
(defn- coercer [schema]
(mr/cached ::coercer
schema
(fn []
(let [respond identity
raise #'*error-fn*] ; capture var rather than the bound value at the time this is eval'ed
(mc/coercer schema (mtx/transformer {:name :normalize}) respond raise))))) | |
Ensure some part of an MBQL query Normalization logic is defined in various schemas; grep for The default implementation will keywordize keys for maps, and convert some known keys using [[default-map-value-fns]]; for MBQL clauses, it will convert the clause name to a keyword and recursively normalize its options and arguments. Implement this method if you need custom behavior for something. Pass in a By default, does not throw Exceptions -- just logs them and returns what it was able to normalize, but you can pass
in the option | (defn normalize
([x]
(normalize nil x))
([schema x]
(normalize schema x nil))
([schema x {:keys [throw?], :or {throw? false}, :as _options}]
(let [schema (or schema (infer-schema x))
thunk (^:once fn* []
((coercer schema) x))]
(if throw?
(binding [*error-fn* (fn [error]
(throw (ex-info (i18n/tru "Normalization error")
{:schema schema, :x x, :error error})))]
(thunk))
(thunk))))) |