(ns metabase.query-processor.execute (:require [metabase.lib.schema.id :as lib.schema.id] [metabase.query-processor.middleware.cache :as cache] [metabase.query-processor.middleware.enterprise :as qp.middleware.enterprise] [metabase.query-processor.middleware.permissions :as qp.perms] [metabase.query-processor.middleware.update-used-cards :as update-used-cards] [metabase.query-processor.pipeline :as qp.pipeline] [metabase.query-processor.schema :as qp.schema] [metabase.query-processor.setup :as qp.setup] [metabase.query-processor.util :as qp.util] [metabase.util :as u] [metabase.util.log :as log] [metabase.util.malli :as mu])) | |
(set! *warn-on-reflection* true) | |
(defn- add-native-form-to-result-metadata [qp]
(fn [query rff]
(letfn [(rff* [metadata]
{:pre [(map? metadata)]}
(rff (cond-> metadata
(not (:native_form metadata))
(assoc :native_form ((some-fn :qp/compiled-inline :qp/compiled :native) query)))))]
(qp query rff*)))) | |
(defn- add-preprocessed-query-to-result-metadata-for-userland-query [qp]
(fn [query rff]
(letfn [(rff* [metadata]
{:pre [(map? metadata)]}
(rff (cond-> metadata
;; process-userland-query needs the preprocessed-query to find field usages
;; it'll then be removed from the result
(qp.util/userland-query? query)
(assoc :preprocessed_query query))))]
(qp query rff*)))) | |
Middleware that happens after compilation, AROUND query execution itself. Has the form (f qp) -> qp e.g. (f (f query rff)) -> (f query rff) | (def ^:private middleware [#'update-used-cards/update-used-cards! #'add-native-form-to-result-metadata #'add-preprocessed-query-to-result-metadata-for-userland-query #'cache/maybe-return-cached-results #'qp.perms/check-query-permissions #'qp.middleware.enterprise/check-download-permissions-middleware #'qp.middleware.enterprise/maybe-apply-column-level-perms-check-middleware]) |
(def ^:private execute* nil) | |
(defn- run [query rff]
;; if the query has a `:qp/compiled` key (i.e., this query was compiled from MBQL), rename it to `:native`, so the
;; driver implementations only need to look for one key. Can't really do this any sooner because it will break schema
;; checks in the middleware
(let [query (cond-> query
(not (:native query)) (assoc :native (:qp/compiled query)))]
(qp.pipeline/*run* query rff))) | |
(defn- rebuild-execute-fn! []
(alter-var-root #'execute* (constantly
(reduce
(fn [qp middleware-fn]
(u/prog1 (middleware-fn qp)
(assert (ifn? <>) (format "%s did not return a valid function" middleware-fn))))
run
middleware)))) | |
(rebuild-execute-fn!) | |
(doseq [varr middleware]
(add-watch varr ::reload (fn [_key _ref _old-state _new-state]
(log/infof "%s changed, rebuilding %s" varr `execute*)
(rebuild-execute-fn!)))) | |
(def ^:private CompiledQuery
[:and
[:map
[:database ::lib.schema.id/database]]
[:fn
{:error/message "Query must be compiled -- should have either :native or :qp/compiled."}
(some-fn :native :qp/compiled)]]) | |
TODO -- consider whether this should return an | (mu/defn execute :- some?
"Execute a compiled query, then reduce the results."
[compiled-query :- CompiledQuery
rff :- ::qp.schema/rff]
(qp.setup/with-qp-setup [compiled-query compiled-query]
(execute* compiled-query rff))) |