Skip to content

Instantly share code, notes, and snippets.

@camsaul
Last active March 8, 2023 20:51
Show Gist options
  • Save camsaul/fff51d9f46ad34371d3cedd536fb0360 to your computer and use it in GitHub Desktop.
Save camsaul/fff51d9f46ad34371d3cedd536fb0360 to your computer and use it in GitHub Desktop.
Malli MBQL Clause Schemas improvements PoC
(ns metabase.lib.schema.mbql-clause
(:require
[metabase.lib.schema.common :as common]
[metabase.util.malli.registry :as mr]))
(defonce ^:private ^{:doc "map of clause keyword -> schema"} clause-schema-registry
(atom {}))
(defn- keyword-schema
"Build the schema for `::keyword`, for a valid MBQL clause type keyword."
[]
(into [:enum] (sort (keys @clause-schema-registry))))
(defn- update-keyword-schema! []
(mr/def ::keyword
(keyword-schema)))
(defn- clause*-schema
"Build the schema for `::clause*`, a `:multi` schema that maps MBQL clause type -> the schema
in [[clause-schema-registry]]."
[]
(into [:multi {:dispatch first}]
@clause-schema-registry))
(defn- update-clause-schema! []
(mr/def ::clause*
(clause*-schema)))
(update-keyword-schema!)
(update-clause-schema!)
(mr/def ::clause
[:and
[:schema
[:cat
[:schema [:ref ::keyword]]
[:* any?]]]
[:ref ::clause*]])
(defn define-mbql-clause [clause-name schema]
(let [schema-name (keyword "mbql-clause" (name clause-name))]
(mr/def schema-name schema)
(swap! clause-schema-registry assoc clause-name [:ref schema-name])
(update-keyword-schema!)
(update-clause-schema!)
;; return the newly created schema name for useful feedback when in a REPL!
schema-name))
(defmulti clause-type
"Return the [[metabase.types]] base type this `mbql-clause` should return."
{:arglists '([mbql-clause])}
(fn [[clause-keyword]]
(keyword "mbql-clause" (name clause-keyword))))
(defmethod clause-type :default
[_clause]
:type/*)
(defmethod clause-type :mbql-clause.type/boolean
[_clause]
:type/Boolean)
(defn mbql-clause-of-type
"Create a schema that:
1. this is a valid MBQL clause matching the `:metabase.lib.schema.mbql-clause/clause` schema
2. the clause returns a [[metabase.types]] [[clause-type]] that isa? `base-type`"
[base-type]
[:and
::clause
[:fn
{:error/message (str "Not an MBQL clause of type " clause-type)}
(fn [clause]
(isa? (clause-type clause) base-type))]])
(mr/def ::clause.boolean
(mbql-clause-of-type :type/Boolean))
(mr/def ::clause.string
(mbql-clause-of-type :type/Text))
;;; examples
(define-mbql-clause
:starts-with
[:tuple
[:= :starts-with]
::common/options
#_whole [:ref :metabase.lib.schema.expression/string]
#_part [:ref :metabase.lib.schema.expression/string]])
(derive :mbql-clause/starts-with :mbql-clause.type/boolean)
(malli.core/validate ::clause [:starts-with {:lib/uuid (str (random-uuid))} "X" "XY"])
;; => true
(malli.core/validate ::clause [:starts-with {:lib/uuid (str (random-uuid))} 1 "XY"])
;; => false
(clause-type [:starts-with])
;; => :type/Boolean
(malli.core/validate ::clause.boolean [:starts-with {:lib/uuid (str (random-uuid))} "X" "XY"])
;; => true
(malli.core/validate ::clause.string [:starts-with {:lib/uuid (str (random-uuid))} "X" "XY"])
;;; => false
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment