Commit 26bcef97 authored by Douwe Maan's avatar Douwe Maan
Browse files

Merge branch 'rework-authorizations-performance' into 'master'

Rework project authorizations and nested groups for better performance

See merge request !10885
parents 7dc8961a 27d5f99e
Loading
Loading
Loading
Loading
+2 −0
Original line number Original line Diff line number Diff line
@@ -64,6 +64,8 @@ def show
  end
  end


  def subgroups
  def subgroups
    return not_found unless Group.supports_nested_groups?

    @nested_groups = GroupsFinder.new(current_user, parent: group).execute
    @nested_groups = GroupsFinder.new(current_user, parent: group).execute
    @nested_groups = @nested_groups.search(params[:filter_groups]) if params[:filter_groups].present?
    @nested_groups = @nested_groups.search(params[:filter_groups]) if params[:filter_groups].present?
  end
  end
+0 −83
Original line number Original line Diff line number Diff line
@@ -84,89 +84,6 @@ def where_full_path_in(paths)
        joins(:route).where(wheres.join(' OR '))
        joins(:route).where(wheres.join(' OR '))
      end
      end
    end
    end

    # Builds a relation to find multiple objects that are nested under user membership
    #
    # Usage:
    #
    #     Klass.member_descendants(1)
    #
    # Returns an ActiveRecord::Relation.
    def member_descendants(user_id)
      joins(:route).
        joins("INNER JOIN routes r2 ON routes.path LIKE CONCAT(r2.path, '/%')
               INNER JOIN members ON members.source_id = r2.source_id
               AND members.source_type = r2.source_type").
        where('members.user_id = ?', user_id)
    end

    # Builds a relation to find multiple objects that are nested under user
    # membership. Includes the parent, as opposed to `#member_descendants`
    # which only includes the descendants.
    #
    # Usage:
    #
    #     Klass.member_self_and_descendants(1)
    #
    # Returns an ActiveRecord::Relation.
    def member_self_and_descendants(user_id)
      joins(:route).
        joins("INNER JOIN routes r2 ON routes.path LIKE CONCAT(r2.path, '/%')
               OR routes.path = r2.path
               INNER JOIN members ON members.source_id = r2.source_id
               AND members.source_type = r2.source_type").
        where('members.user_id = ?', user_id)
    end

    # Returns all objects in a hierarchy, where any node in the hierarchy is
    # under the user membership.
    #
    # Usage:
    #
    #     Klass.member_hierarchy(1)
    #
    # Examples:
    #
    #     Given the following group tree...
    #
    #            _______group_1_______
    #           |                     |
    #           |                     |
    #     nested_group_1        nested_group_2
    #           |                     |
    #           |                     |
    #     nested_group_1_1      nested_group_2_1
    #
    #
    #     ... the following results are returned:
    #
    #     * the user is a member of group 1
    #       => 'group_1',
    #          'nested_group_1', nested_group_1_1',
    #          'nested_group_2', 'nested_group_2_1'
    #
    #     * the user is a member of nested_group_2
    #       => 'group1',
    #          'nested_group_2', 'nested_group_2_1'
    #
    #     * the user is a member of nested_group_2_1
    #       => 'group1',
    #          'nested_group_2', 'nested_group_2_1'
    #
    # Returns an ActiveRecord::Relation.
    def member_hierarchy(user_id)
      paths = member_self_and_descendants(user_id).pluck('routes.path')

      return none if paths.empty?

      wheres = paths.map do |path|
        "#{connection.quote(path)} = routes.path
         OR
         #{connection.quote(path)} LIKE CONCAT(routes.path, '/%')"
      end

      joins(:route).where(wheres.join(' OR '))
    end
  end
  end


  def full_name
  def full_name
+5 −1
Original line number Original line Diff line number Diff line
@@ -3,7 +3,11 @@ module SelectForProjectAuthorization


  module ClassMethods
  module ClassMethods
    def select_for_project_authorization
    def select_for_project_authorization
      select("members.user_id, projects.id AS project_id, members.access_level")
      select("projects.id AS project_id, members.access_level")
    end

    def select_as_master_for_project_authorization
      select(["projects.id AS project_id", "#{Gitlab::Access::MASTER} AS access_level"])
    end
    end
  end
  end
end
end
+5 −1
Original line number Original line Diff line number Diff line
@@ -38,6 +38,10 @@ class Group < Namespace
  after_save :update_two_factor_requirement
  after_save :update_two_factor_requirement


  class << self
  class << self
    def supports_nested_groups?
      Gitlab::Database.postgresql?
    end

    # Searches for groups matching the given query.
    # Searches for groups matching the given query.
    #
    #
    # This method uses ILIKE on PostgreSQL and LIKE on MySQL.
    # This method uses ILIKE on PostgreSQL and LIKE on MySQL.
@@ -78,7 +82,7 @@ def select_for_project_authorization
      if current_scope.joins_values.include?(:shared_projects)
      if current_scope.joins_values.include?(:shared_projects)
        joins('INNER JOIN namespaces project_namespace ON project_namespace.id = projects.namespace_id')
        joins('INNER JOIN namespaces project_namespace ON project_namespace.id = projects.namespace_id')
          .where('project_namespace.share_with_group_lock = ?',  false)
          .where('project_namespace.share_with_group_lock = ?',  false)
          .select("members.user_id, projects.id AS project_id, LEAST(project_group_links.group_access, members.access_level) AS access_level")
          .select("projects.id AS project_id, LEAST(project_group_links.group_access, members.access_level) AS access_level")
      else
      else
        super
        super
      end
      end
+9 −15
Original line number Original line Diff line number Diff line
@@ -176,26 +176,20 @@ def shared_runners_enabled?
    projects.with_shared_runners.any?
    projects.with_shared_runners.any?
  end
  end


  # Scopes the model on ancestors of the record
  # Returns all the ancestors of the current namespaces.
  def ancestors
  def ancestors
    if parent_id
    return self.class.none unless parent_id
      path = route ? route.path : full_path
      paths = []


      until path.blank?
    Gitlab::GroupHierarchy.
        path = path.rpartition('/').first
      new(self.class.where(id: parent_id)).
        paths << path
      base_and_ancestors
  end
  end


      self.class.joins(:route).where('routes.path IN (?)', paths).reorder('routes.path ASC')
  # Returns all the descendants of the current namespace.
    else
      self.class.none
    end
  end

  # Scopes the model on direct and indirect children of the record
  def descendants
  def descendants
    self.class.joins(:route).merge(Route.inside_path(route.path)).reorder('routes.path ASC')
    Gitlab::GroupHierarchy.
      new(self.class.where(parent_id: id)).
      base_and_descendants
  end
  end


  def user_ids_for_project_authorizations
  def user_ids_for_project_authorizations
Loading