From 3ced541f50e81aeaa1624476976bcdac12085241 Mon Sep 17 00:00:00 2001 From: Oleksandr Yakushev <alex@bytopia.org> Date: Thu, 26 Sep 2024 12:19:03 +0300 Subject: [PATCH] perf: Use vectors to iterate over all user permissions (#48025) --- src/metabase/models/permissions.clj | 15 +++++++++------ src/metabase/models/user.clj | 23 +++++++++++++---------- src/metabase/util/performance.clj | 7 ++++++- 3 files changed, 28 insertions(+), 17 deletions(-) diff --git a/src/metabase/models/permissions.clj b/src/metabase/models/permissions.clj index 80e755f7b12..e65681ed0df 100644 --- a/src/metabase/models/permissions.clj +++ b/src/metabase/models/permissions.clj @@ -182,6 +182,7 @@ [metabase.util.log :as log] [metabase.util.malli :as mu] [metabase.util.malli.schema :as ms] + [metabase.util.performance :as perf] [methodical.core :as methodical] [toucan2.core :as t2])) @@ -277,25 +278,27 @@ (defn set-has-full-permissions? "Does `permissions-set` grant *full* access to object with `path`?" ^Boolean [permissions-set path] - (boolean (some #(is-permissions-for-object? % path) permissions-set))) + (boolean (perf/some #(is-permissions-for-object? % path) permissions-set))) (defn set-has-partial-permissions? "Does `permissions-set` grant access full access to object with `path` *or* to a descendant of it?" ^Boolean [permissions-set path] - (boolean (some #(is-partial-permissions-for-object? % path) permissions-set))) + (boolean (perf/some #(is-partial-permissions-for-object? % path) permissions-set))) (mu/defn set-has-full-permissions-for-set? :- :boolean "Do the permissions paths in `permissions-set` grant *full* access to all the object paths in `paths-set`?" [permissions-set paths-set] - (every? (partial set-has-full-permissions? permissions-set) - paths-set)) + (let [permissions (or (:as-vec (meta permissions-set)) + permissions-set)] + (every? (partial set-has-full-permissions? permissions) paths-set))) (mu/defn set-has-partial-permissions-for-set? :- :boolean "Do the permissions paths in `permissions-set` grant *partial* access to all the object paths in `paths-set`? (`permissions-set` must grant partial access to *every* object in `paths-set` set)." [permissions-set paths-set] - (every? (partial set-has-partial-permissions? permissions-set) - paths-set)) + (let [permissions (or (:as-vec (meta permissions-set)) + permissions-set)] + (every? (partial set-has-partial-permissions? permissions) paths-set))) (mu/defn set-has-application-permission-of-type? :- :boolean "Does `permissions-set` grant *full* access to a application permission of type `perm-type`?" diff --git a/src/metabase/models/user.clj b/src/metabase/models/user.clj index b2fcb921c70..13989ed56f8 100644 --- a/src/metabase/models/user.clj +++ b/src/metabase/models/user.clj @@ -250,16 +250,19 @@ (defn permissions-set "Return a set of all permissions object paths that `user-or-id` has been granted access to. (2 DB Calls)" [user-or-id] - (set (when-let [user-id (u/the-id user-or-id)] - (concat - ;; Current User always gets readwrite perms for their Personal Collection and for its descendants! (1 DB Call) - (map perms/collection-readwrite-path (collection/user->personal-collection-and-descendant-ids user-or-id)) - ;; include the other Perms entries for any Group this User is in (1 DB Call) - (map :object (mdb.query/query {:select [:p.object] - :from [[:permissions_group_membership :pgm]] - :join [[:permissions_group :pg] [:= :pgm.group_id :pg.id] - [:permissions :p] [:= :p.group_id :pg.id]] - :where [:= :pgm.user_id user-id]})))))) + (let [s + (set (when-let [user-id (u/the-id user-or-id)] + (concat + ;; Current User always gets readwrite perms for their Personal Collection and for its descendants! (1 DB Call) + (map perms/collection-readwrite-path (collection/user->personal-collection-and-descendant-ids user-or-id)) + ;; include the other Perms entries for any Group this User is in (1 DB Call) + (map :object (mdb.query/query {:select [:p.object] + :from [[:permissions_group_membership :pgm]] + :join [[:permissions_group :pg] [:= :pgm.group_id :pg.id] + [:permissions :p] [:= :p.group_id :pg.id]] + :where [:= :pgm.user_id user-id]})))))] + ;; Append permissions as a vector for more efficient iteration in checks that go over each permission linearly. + (with-meta s {:as-vec (vec s)}))) ;;; --------------------------------------------------- Hydration ---------------------------------------------------- diff --git a/src/metabase/util/performance.clj b/src/metabase/util/performance.clj index f64d28308b8..202e7932c57 100644 --- a/src/metabase/util/performance.clj +++ b/src/metabase/util/performance.clj @@ -1,6 +1,6 @@ (ns metabase.util.performance "Functions and utilities for faster processing." - (:refer-clojure :exclude [reduce mapv]) + (:refer-clojure :exclude [reduce mapv some]) (:import (clojure.lang LazilyPersistentVector RT))) (set! *warn-on-reflection* true) @@ -126,3 +126,8 @@ ([x y] (mapv #(% x y) fns)) ([x y z] (mapv #(% x y z) fns)) ([x y z & args] (mapv #(apply % x y z args) fns))))) + +(defn some + "Like `clojure.core/some` but uses our custom `reduce` which in turn uses iterators." + [f coll] + (unreduced (reduce #(when-let [match (f %2)] (reduced match)) nil coll))) -- GitLab