(ns metabase.lib.schema.util (:refer-clojure :exclude [ref]) (:require [clojure.walk :as walk] [metabase.lib.options :as lib.options] [metabase.util :as u] [metabase.util.malli.registry :as mr])) | |
(declare collect-uuids*) | |
(defn- collect-uuids-in-map [m result]
(when-let [our-uuid (or (:lib/uuid (lib.options/options m))
(:lib/uuid m))]
;; Keep duplicates in metadata of the result.
(if (@result our-uuid)
(vswap! result vary-meta update :duplicates (fnil conj #{}) our-uuid)
(vswap! result conj our-uuid)))
(reduce-kv (fn [_ k v]
(when (not (qualified-keyword? k))
(collect-uuids* v result)))
nil m)) | |
(defn- collect-uuids-in-sequence [xs result] (run! #(collect-uuids* % result) xs)) | |
(defn- collect-uuids* [x result]
(cond
(map? x) (collect-uuids-in-map x result)
(sequential? x) (collect-uuids-in-sequence x result)
:else nil)) | |
Return all the | (defn collect-uuids
[x]
(let [result (volatile! #{})]
(collect-uuids* x result)
@result)) |
(defn- find-duplicate-uuid [x] (:duplicates (meta (collect-uuids x)))) | |
True if all the | (defn unique-uuids? [x] (empty? (find-duplicate-uuid x))) |
Malli schema for to ensure that all | (mr/def ::unique-uuids
[:fn
{:error/message "all :lib/uuids must be unique"
:error/fn (fn [{:keys [value]} _]
(str "Duplicate :lib/uuid " (pr-str (find-duplicate-uuid value))))}
#'unique-uuids?]) |
Is a sequence of | (defn distinct-refs?
[refs]
(or
(< (count refs) 2)
(apply
distinct?
(for [ref refs]
(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)
(#{:base-type :effective-type :ident} k))
(dissoc acc k)
acc))
options options))))))) |
Recursively remove all uuids, | (defn remove-randomized-idents
[x]
(walk/postwalk
(fn [x]
(if (map? x)
(dissoc x :lib/uuid :ident :entity_id :entity-id)
x))
x)) |
Convert all order-bys in a stage to refer to aggregations by index instead of uuid | (defn- indexed-order-bys-for-stage
[{:keys [aggregation order-by] :as stage}]
(if (and aggregation order-by)
(let [agg-lookups (->> aggregation
(map-indexed (fn [i [_type {agg-uuid :lib/uuid}]]
[agg-uuid i]))
(into {}))]
(update stage :order-by (fn [order-bys]
(mapv (fn [[_dir _opts [order-type agg-opts agg-uuid] :as order-by]]
(if (= order-type :aggregation)
(assoc order-by 2 [order-type agg-opts (agg-lookups agg-uuid)])
order-by))
order-bys))))
stage)) |
Convert all order-bys in a query to refer to aggregations by index instead of uuid. The result is not a valid query, but avoiding random uuids is important during hashing. | (defn indexed-order-bys
[query]
(if (:stages query)
(update query :stages #(mapv indexed-order-bys-for-stage %))
query)) |
(mr/def ::distinct-ignoring-uuids
[:fn
{:error/message "values must be distinct ignoring uuids"
:error/fn (fn [{:keys [value]} _]
(str "Duplicate values ignoring uuids in: " (pr-str (remove-randomized-idents value))))}
(comp u/empty-or-distinct? remove-randomized-idents)]) | |
Add an additional constraint to | (defn distinct-ignoring-uuids [schema] [:and schema [:ref ::distinct-ignoring-uuids]]) |