(ns metabase.lib.schema.mbql-clause (:require [malli.core :as mc] [metabase.lib.schema.common :as common] [metabase.lib.schema.expression :as expression] [metabase.types] [metabase.util.malli :as mu] [metabase.util.malli.registry :as mr])) | |
(comment metabase.types/keep-me) | |
Set of all registered MBQL clause tags e.g. #{:starts-with} | (defonce ^:private tag-registry (atom #{})) |
Given an MBQL clause tag like | (defn tag->registered-schema-name [tag] (keyword "mbql.clause" (name tag))) |
(def ^:private invalid-clause-schema [:fn {:error/message "not a known MBQL clause"} (constantly false)]) | |
Build the schema for | (defn- clause-schema [] (into [:multi {:dispatch common/mbql-clause-tag :error/fn (fn [{:keys [value]} _] (if-let [tag (common/mbql-clause-tag value)] (str "Invalid " tag " clause: " (pr-str value)) "not an MBQL clause"))} [::mc/default invalid-clause-schema]] (map (fn [tag] [tag [:ref (tag->registered-schema-name tag)]])) @tag-registry)) |
(defn- update-clause-schema! [] (mr/def ::clause (clause-schema))) | |
create an initial empty definition of | (update-clause-schema!) |
whenever [[tag-registry]] is updated, update the | (add-watch tag-registry ::update-schemas (fn [_key _ref _old-state _new-state] (update-clause-schema!))) |
Register the (define-mbql-clause :is-null :- :type/Boolean [:tuple [:= :is-null] ::common/options [:ref :metabase.lib.schema.expression/expression]]) | (mu/defn define-mbql-clause ([tag :- simple-keyword? schema] (let [schema-name (tag->registered-schema-name tag)] (mr/def schema-name schema) ;; only need to update the registry and calculated schemas if this is the very first time we're defining this ;; clause. Otherwise since they're wrapped in `:ref` we don't need to recalculate them. This way we can avoid tons ;; of pointless recalculations every time we reload a namespace. (when-not (contains? @tag-registry tag) (swap! tag-registry conj tag))) nil) ([tag :- simple-keyword? _arrow :- [:= :-] return-type :- ::expression/base-type schema] (define-mbql-clause tag schema) (defmethod expression/type-of-method tag [_clause] return-type) nil)) |
TODO: Support options more nicely - these don't allow for overriding the options, but we have a few cases where that
is necessary. See for example the inclusion of | |
Helper intended for use with [[define-mbql-clause]]. Create an MBQL clause schema with | (defn catn-clause-schema [tag & args] {:pre [(simple-keyword? tag) (every? vector? args) (every? keyword? (map first args))]} [:schema (into [:catn {:error/message (str "Valid " tag " clause")} [:tag [:= {:decode/normalize common/normalize-keyword} tag]] [:options [:schema [:ref ::common/options]]]] args)]) |
Helper intended for use with [[define-mbql-clause]]. Create a clause schema with | (defn tuple-clause-schema [tag & args] {:pre [(simple-keyword? tag)]} (into [:tuple {:error/message (str "Valid " tag " clause")} [:= {:decode/normalize common/normalize-keyword} tag] [:ref ::common/options]] args)) |
Even more convenient functions! | |
Helper. Combines [[define-mbql-clause]] and the result of applying | (defn define-mbql-clause-with-schema-fn [schema-fn tag & args] (let [[return-type & args] (if (= (first args) :-) (cons (second args) (drop 2 args)) (cons nil args)) schema (apply schema-fn tag args)] (if return-type (define-mbql-clause tag :- return-type schema) (define-mbql-clause tag schema)))) |
Helper. Combines [[define-mbql-clause]] and [[tuple-clause-schema]]. | (defn define-tuple-mbql-clause [tag & args] (apply define-mbql-clause-with-schema-fn tuple-clause-schema tag args)) |
Helper. Combines [[define-mbql-clause]] and [[catn-clause-schema]]. | (defn define-catn-mbql-clause [tag & args] (apply define-mbql-clause-with-schema-fn catn-clause-schema tag args)) |
For REPL/test usage: get the definition of the schema associated with an MBQL clause tag. | (defn resolve-schema [tag] (mr/resolve-schema (tag->registered-schema-name tag))) |